MVC系列——MVC源碼學習:打造本身的MVC框架(三:自定義路由規則)

前言:上篇介紹了下本身的MVC框架前兩個版本,通過兩天的整理,版本三基本已經完成,今天仍是發出來供你們參考和學習。雖然微軟的Routing功能已經很是強大,徹底沒有必要再「重複造輪子」了,但博主仍是以爲本身動手寫一遍印象要深入許多,但願想深刻學習MVC的童鞋本身動手寫寫。好了,廢話就此打住。html

 本文原創地址:http://www.cnblogs.com/landeanfen/p/6016394.htmlgit

MVC源碼學習系列文章目錄:github

1、版本三功能介紹

在版本三裏面,爲了更加透徹理解UrlRoutingModule裏面的路由功能,博主本身寫了一遍路由的讀取和配置過程,完成以後整個框架的代碼目錄結構以下:bootstrap

主要仍是分爲兩大塊:MVC目錄裏面的對應着MvcHandler的邏輯,Routing目錄對象對應的UrlRoutingModule的邏輯。整個調用過程以下:swift

看到這個圖,你可能仍然是懵比狀態。不要緊,若是你有興趣,且往下看。app

2、UrlRoutingModule的實現

在整個UrlRoutingModule裏面,咱們全部的路由相關邏輯都和System.Web.Routing這個組件沒有任何聯繫,徹底是一塊獨立的區域。爲了方便理解,這些文件的命名在原來System.Web.Routing組件裏面的類前面都加上一個「Swift」。UrlRoutingModule的主要邏輯都在如下這些文件裏面:框架

一、SwiftRouteTable.cs代碼

namespace Swift.MVC.Routing
{
    public class SwiftRouteTable
    {
        //靜態構造函數,約束這個靜態對象是一個不被釋放的全局變量
        static SwiftRouteTable()
        {
            routes = new SwiftRouteCollection();
        }
        private static SwiftRouteCollection routes;
        public static SwiftRouteCollection Routes
        {
            get
            {
                return routes;
            }
        }
    }
}

這個類主要做用就是定義一個靜態全局的SwiftRoutingCollection對象,在Global.asax裏面能夠配置這個路由集合。爲何是一個靜態全局變量呢?靜態是爲了保證對象不被釋放(GC回收);全局是爲了保證整個應用程序均可以訪問獲得。ide

二、SwiftRouteCollection.cs代碼

上文在SwiftRouteTable裏面定義一個靜態全局的SwiftRoutingCollection變量,咱們來看這個裏面到底有些什麼東西。函數

namespace Swift.MVC.Routing
{
    public class SwiftRouteCollection
    {
        public SwiftRoute SwiftRoute { get; set; }

        public string Name { get; set; }

        //Global.asax裏面配置路由規則和默認路由
        public void Add(string name, SwiftRoute route)
        {
            SwiftRoute = route;
            Name = name;
        }

        //經過上下文對象獲得當前請求的路由表
        public SwiftRouteData GetRouteData(HttpContextBase context)
        {
            var swiftRouteData = new SwiftRouteData();
            //1.配置RouteHandler實例,這裏的RouteHandler是在全局配置裏面寫進來的
            swiftRouteData.RouteHandler = SwiftRoute.RouteHandler;

            //2.獲取當前請求的虛擬路徑和說明
            var virtualPath = context.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + context.Request.PathInfo;

            //3.先將默認路由配置寫入當前請求的路由表
            //每次請求只能讀取默認值,而不能覆蓋默認值
            swiftRouteData.RouteValue = new Dictionary<string, object>() ;
            foreach (var key in this.SwiftRoute.DefaultPath)
            {
                swiftRouteData.RouteValue[key.Key] = key.Value;
            }

            //4.若是當前請求虛擬路徑爲空,則訪問默認路由表。不然從當前請求的url裏面去取當前的controller和action的名稱
            if (!string.IsNullOrEmpty(virtualPath))
            {
                var arrTemplatePath = this.SwiftRoute.TemplateUrl.Split("{}/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                var arrRealPath = virtualPath.Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                for (var i = 0; i < arrTemplatePath.Length; i++)
                {
                    var realPath = arrRealPath.Length > i ? arrRealPath[i] : null;
                    if (realPath == null)
                    {
                        break;
                    }
                    swiftRouteData.RouteValue[arrTemplatePath[i]] = realPath;
                }
            }
            //5.去讀當前請求的參數列表
            var querystring = context.Request.QueryString.ToString();
            if (string.IsNullOrEmpty(querystring))
            {
                return swiftRouteData;
            }
            var parameters = querystring.Split("&".ToArray(), StringSplitOptions.RemoveEmptyEntries) ;
            var oparam = new Dictionary<string, string>();
            foreach (var parameter in parameters)
            {
                var keyvalue = parameter.Split("=".ToArray());
                oparam[keyvalue[0]] = keyvalue[1];
            }
            swiftRouteData.RouteValue["parameters"] = oparam;
            return swiftRouteData;
        }
    }
}

這個類的結構也不復雜,兩個屬性,兩個方法。方法Add()用來給兩個屬性賦值,方法GetRouteData()主要做用註釋中已經註明。要詳細瞭解GetRouteData()方法的邏輯,咱們有必要先看看SwiftRoute這個類型。post

三、SwiftRoute.cs代碼

namespace Swift.MVC.Routing
{
    public class SwiftRoute
    {
        public SwiftRoute()
        { }

        //在全局配置裏面寫入路由規則以及默認配置
        public SwiftRoute(string url, Dictionary<string, object> defaultPath, IRouteHandler routeHandler)
        {
            TemplateUrl = url;
            DefaultPath = defaultPath;
            RouteHandler = routeHandler;
        }
        public string TemplateUrl { get; set; }

        public IRouteHandler RouteHandler { get; set; }

        public Dictionary<string, object> DefaultPath { get; set; }
    }
}

在SwiftRoutingCollection的Add方法裏面,咱們須要傳入一個SwiftRoute對象,這裏的SwiftRoute對象就包含了路由規則的url、默認路由地址、IRouteHandler接口對象三個重要信息,這三個信息都是在Global.asax裏面寫入的,待會咱們測試的再來詳細說明。

四、SwiftRouteData.cs代碼

在上文SwiftRouteCollection的GetRouteData()裏面,返回了一個SwiftRouteData對象,這個對象的定義更加簡單,僅僅包含兩個屬性:

namespace Swift.MVC.Routing
{
    public class SwiftRouteData
    {
        public IRouteHandler RouteHandler { get; set; }

        public Dictionary<string, object> RouteValue { get; set; }
    }
}

RouteHandler屬性用來保存當前的IRouteHandler對象;

RouteValue屬性用來保存當前請求的路由表。

五、IRouteHandler.cs代碼

上文屢次提到了IRouteHandler接口,咱們來看看那這個接口內容:

namespace Swift.MVC.Routing
{
    public interface IRouteHandler
    {
        System.Web.IHttpHandler GetHttpHandler(SwiftRouteData routeData, HttpContextBase context);
    }
}

這個接口的意義很簡單,只有一個方法,用來返回處理當前Http請求的HttpHandler對象。既然這裏定義了這個接口,那咱們順便也提一下這個接口的實現類,在當前項目的MVC目錄下面,有一個MvcRouteHandler類型:

using Swift.MVC.Routing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace Swift.MVC
{
    public class MvcRouteHandler:IRouteHandler
    {
        /// <summary>
        /// 返回處理當前請求的HttpHandler對象
        /// </summary>
        /// <param name="routeData">當前的請求的路由對象</param>
        /// <param name="context">當前請求的下文對象</param>
        /// <returns>處理請求的HttpHandler對象</returns>
        public System.Web.IHttpHandler GetHttpHandler(SwiftRouteData routeData, HttpContextBase context)
        {
            return new MvcHandler(routeData, context) ;
        }
    }
}

這個實現類做用更加明確,返回一個具體的HttpHandler對象。

六、UrlRoutingModule.cs代碼

有了上文的幾個類型作支撐,最後咱們統籌調度的UrlRoutingModule閃亮登場了。

namespace Swift.MVC.Routing
{
    public class UrlRoutingModule : IHttpModule
    {
        #region Property
        private SwiftRouteCollection _swiftRouteCollection;

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly",
            Justification = "This needs to be settable for unit tests.")]
        public SwiftRouteCollection SwiftRouteCollection
        {
            get
            {
                if (_swiftRouteCollection == null)
                {
                    _swiftRouteCollection = SwiftRouteTable.Routes;
                }
                return _swiftRouteCollection;
            }
            set
            {
                _swiftRouteCollection = value;
            }
        }
        #endregion

        public void Dispose()
        {
            //throw new NotImplementedException();
        }

        public void Init(HttpApplication app)
        {
            app.PostResolveRequestCache += app_PostResolveRequestCache;
        }

        void app_PostResolveRequestCache(object sender, EventArgs e)
        {
            var app = (HttpApplication)sender;
            //0.將HttpContext轉換爲HttpContextWrapper對象(HttpContextWrapper繼承HttpContextBase)
            var contextbase = new HttpContextWrapper(app.Context);
            PostResolveRequestCache(contextbase);
        }

        public virtual void PostResolveRequestCache(HttpContextBase context)
        {
            //1.傳入當前上下文對象,獲得與當前請求匹配的SwiftRouteData對象
            SwiftRouteData routeData = this.SwiftRouteCollection.GetRouteData(context);
            if (routeData == null)
            {
                return;
            }
            //2.從SwiftRouteData對象裏面獲得當前的RouteHandler對象。
            IRouteHandler routeHandler = routeData.RouteHandler;
            if (routeHandler == null)
            {
                return;
            }

            //3.根據RequestContext對象獲得處理當前請求的HttpHandler(MvcHandler)。
            IHttpHandler httpHandler = routeHandler.GetHttpHandler(routeData, context);
            if (httpHandler == null)
            {
                return;
            }

            //4.請求轉到HttpHandler進行處理(進入到ProcessRequest方法)。這一步很重要,由這一步開始,請求才由UrlRoutingModule轉到了MvcHandler裏面
            context.RemapHandler(httpHandler);
        }
    }
}

 和版本二里面的區別不大,不少屬性名和方法名都採用和版本二相同的規則,最大的區別就是在版本三裏面,再也不有RequestContext對象。UrlRoutingModule和MvcHandler二者打交道的橋樑在版本二里面是RequestContext對象,在版本三裏面變成了直接將當前的路由對象和上下文傳到MvcHandler裏面。

3、MvcHandler的實現

在MvcHandler裏面變化比較大的只有兩個:MvcHandler.cs和Controller.cs

一、MvcHandler.cs

namespace Swift.MVC
{
    public class MvcHandler : IHttpHandler
    {
        public MvcHandler()
        { }

        public HttpContextBase SwiftContext { get; set; }
        public SwiftRouteData SwiftRouteData { get; set; }
        //經過構造函數將兩個對象傳過來,替代了原來RequestContext的做用
        public MvcHandler(SwiftRouteData routeData, HttpContextBase context)
        {
            SwiftRouteData = routeData;
            SwiftContext = context;
        }

        public virtual bool IsReusable
        {
            get { return false; }
        }

        public virtual void ProcessRequest(HttpContext context)
        {
            //寫入MVC的版本到HttpHeader裏面
            //AddVersionHeader(httpContext);
            //移除參數
            //RemoveOptionalRoutingParameters();

            //1.從當前的RouteData裏面獲得請求的控制器名稱
            string controllerName = SwiftRouteData.RouteValue["controller"].ToString();

            //2.獲得控制器工廠
            IControllerFactory factory = new SwiftControllerFactory();

            //3.經過默認控制器工廠獲得當前請求的控制器對象
            IController controller = factory.CreateController(SwiftRouteData, controllerName);
            if (controller == null)
            {
                return;
            }

            try
            {
                //4.執行控制器的Action
                controller.Execute(SwiftRouteData);
            }
            catch
            {}
            finally
            {
                //5.釋放當前的控制器對象
                factory.ReleaseController(controller);
            }

        }
    }
}

相比版本二,這個類多了一個有兩個參數的構造函數,用來將routeData和context傳進來。而後再ProcessRequest方法裏面直接經過傳進來的對象去取當前請求的相關信息。

二、Controller.cs

namespace Swift.MVC
{
    public abstract class Controller:ControllerBase,IDisposable
    {
        public override void Execute(SwiftRouteData routeData)
        {
            //1.獲得當前控制器的類型
            Type type = this.GetType();

            //2.從路由表中取到當前請求的action名稱
            string actionName = routeData.RouteValue["action"].ToString();

            //3.從路由表中取到當前請求的Url參數
            object parameter = null;
            if (routeData.RouteValue.ContainsKey("parameters"))
            {
                parameter = routeData.RouteValue["parameters"];
            }
            var paramTypes = new List<Type>();
            List<object> parameters = new List<object>();
            if (parameter != null)
            {
                var dicParam = (Dictionary<string, string>)parameter;
                foreach (var pair in dicParam)
                {
                    parameters.Add(pair.Value);
                    paramTypes.Add(pair.Value.GetType());
                }
            }

            //4.經過action名稱和對應的參數反射對應方法。
            //這裏第二個參數能夠不理會action字符串的大小寫,第四個參數決定了當前請求的action的重載參數類型
            System.Reflection.MethodInfo mi = type.GetMethod(actionName,
                BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase, null, paramTypes.ToArray(), null);

            //5.執行該Action方法
            mi.Invoke(this, parameters.ToArray());//調用方法
        }

        public void Dispose()
        {
            //throw new NotImplementedException();
        }
    }
}

在Execute()方法裏面,對基礎類型的Action方法參數重載提供了支持。

4、測試以及代碼釋疑

上文介紹了這麼多,可能並不直觀,不少類之間如何聯繫的看得並不清晰,反正若是是博主,別人這麼介紹一個類又一個類,看完確定仍是蒙的的。那麼咱們來測試看下吧。

 首先,仍是配置全局配置文件Global.asax

  public class Global : System.Web.HttpApplication
    {

        protected void Application_Start(object sender, EventArgs e)
        {
            var defaultPath = new Dictionary<string, object>();
            defaultPath.Add("controller", "Home");
            defaultPath.Add("action", "Index");
            defaultPath.Add("id", null);
            defaultPath.Add("namespaces", "MyTestMVC.Controllers");
            defaultPath.Add("assembly", "MyTestMVC");

            SwiftRouteTable.Routes.Add("defaultRoute", new SwiftRoute("{controller}/{action}/{id}", defaultPath, new MvcRouteHandler()));
        }
    }

看到這裏應該知道上文中 SwiftRouteTable 、 SwiftRouteCollection 、 SwiftRoute 三個類的用處了吧,原來在這裏。IRouteHandler的實例new MvcRouteHandler()也是在這裏寫入的。

而後啓動項目,咱們默認訪問http://localhost:16792/Home/bootstrapTest這個地址,咱們來看具體的過程:

一、啓動項目,首先進到全局配置文件的Application_Start()方法

這裏告訴咱們在SwiftRouteTable.Routes這個全局靜態變量裏面,已經保存了路由規則、默認路由、RouteHandler對象三個重要的信息。這三個信息後面都會用到。

二、而後請求進到UrlRoutingModule裏面,取SwiftRouteCollection的值:

咱們看到,這裏的SwiftRouteCollection的值就是在全局配置文件裏面配置的類型。

三、而後請求進到SwiftRouteCollection類的GetRouteData()方法裏面。這個方法的做用很明顯,就是解析當前的請求的url,從中獲取當前的controller、action、參數等信息。這個方法執行完以後獲得的SwiftRouteData對象,結果以下:

這個對象包含兩個屬性,RouteHandler和當前請求的路由表。

四、經過步驟3知道,當前的swiftRouteData對象包含了RouteHandler對象, IRouteHandler routeHandler = routeData.RouteHandler; 結果以下:

五、獲得RouteHandler對象以後,就是從該對象的GetHttpHandler()方法裏面獲得當前的HttpHandler。

這個應該不難理解,將routeData和context傳入MvcHandler裏面。這就是爲何以前MvcHandler裏面有一個兩個參數的構造函數的緣由。

六、而後就是執行 context.RemapHandler(httpHandler); 將請求正式交給MvcHandler。

七、在MvcHandler的ProcessRequest方法裏面,首先從當前請求的路由表裏面去控制器名稱,以下圖,獲得」Home「:

八、而後就是建立控制器工廠、從工廠裏面獲得當前請求的控制器的對象,這部分和以前變化不大。

九、獲得控制器對象以後,執行對應的當前請求的action方法,請求盡到Controller這個父類的Execute()方法裏面

十、經過反射,最終執行BootstrapTest()方法。

十一、BootstrapTest()方法執行完成以後,釋放當前的控制器對象: factory.ReleaseController(controller); 。請求結束。

5、支持方法的重載

 博主對Swift.MVC框架進行了簡單的擴展,使得框架支持action方法的重載。好比咱們在HomeController裏面定義了以下方法

    public class HomeController : Controller
    {
        public void Index()
        {
            HttpContext.Current.Response.Write("Hello MVC");
        }

        public void Index(string id)
        {
            HttpContext.Current.Response.Write("Hello MVC  參數" + id);
        }

        public void Index(string aa, string bb)
        {
            HttpContext.Current.Response.Write("Hello MVC  兩個參數");
        }

        public void BootstrapTest()
        {
            .....
        }

        public void BootstrapTest(int id)
        {
            .....
        }
    }

一、請求默認路由地址:http://localhost:16792/

二、請求地址:http://localhost:16792/Home/index?id=1

三、請求地址:http://localhost:16792/Home/index?aa=kate&bb=lucy

固然上文封裝都是隻是經過url傳遞參數的狀況,等有時間能夠擴展下,使得支持經過post請求傳遞參數。

6、總結

經過上一篇和這一篇,咱們基本上把MVC的核心原理涉及到的技術都重寫了一遍,等有時間再擴展一個本身的」View「,加上模型驗證,數據綁定,咱們的Swift.MVC就算是一個相對完整的微型MVC框架了。固然,此框架僅僅是從學習理解MVC的原理層面去實現的,若是要應用於項目,還要考慮不少東西,不論如何,是個好的開始,有時間繼續完善。源碼地址

若是你以爲本文可以幫助你,能夠右邊隨意 打賞 博主,也能夠 推薦 進行精神鼓勵。你的支持是博主繼續堅持的不懈動力。

本文原創出處:http://www.cnblogs.com/landeanfen/

歡迎各位轉載,可是未經做者本人贊成,轉載文章以後必須在文章頁面明顯位置給出做者和原文鏈接,不然保留追究法律責任的權利

相關文章
相關標籤/搜索