MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)

简介:

 

正文

前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习。虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉得自己动手写一遍印象要深刻许多,希望想深入学习MVC的童鞋自己动手写写。好了,废话就此打住。

 本文原创地址:http://www.cnblogs.com/landeanfen/p/6016394.html

MVC源码学习系列文章目录:

一、版本三功能介绍

在版本三里面,为了更加透彻理解UrlRoutingModule里面的路由功能,博主自己写了一遍路由的读取和配置过程,完成之后整个框架的代码目录结构如下:

主要还是分为两大块:MVC目录里面的对应着MvcHandler的逻辑,Routing目录对象对应的UrlRoutingModule的逻辑。整个调用过程如下:

看到这个图,你可能仍然是懵比状态。没关系,如果你有兴趣,且往下看。

二、UrlRoutingModule的实现

在整个UrlRoutingModule里面,我们所有的路由相关逻辑都和System.Web.Routing这个组件没有任何联系,完全是一块独立的区域。为了方便理解,这些文件的命名在原来System.Web.Routing组件里面的类前面都加上一个“Swift”。UrlRoutingModule的主要逻辑都在以下这些文件里面:

1、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回收);全局是为了保证整个应用程序都可以访问得到。

2、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这个类型。

3、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里面写入的,待会我们测试的再来详细说明。

4、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属性用来保存当前请求的路由表。

5、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对象。

6、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里面。

三、MvcHandler的实现

在MvcHandler里面变化比较大的只有两个:MvcHandler.cs和Controller.cs

1、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方法里面直接通过传进来的对象去取当前请求的相关信息。

2、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方法参数重载提供了支持。

四、测试以及代码释疑

上文介绍了这么多,可能并不直观,很多类之间如何联系的看得并不清晰,反正如果是博主,别人这么介绍一个类又一个类,看完肯定还是蒙的的。那么我们来测试看下吧。

 首先,还是配置全局配置文件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这个地址,我们来看具体的过程:

1、启动项目,首先进到全局配置文件的Application_Start()方法

这里告诉我们在SwiftRouteTable.Routes这个全局静态变量里面,已经保存了路由规则、默认路由、RouteHandler对象三个重要的信息。这三个信息后面都会用到。

2、然后请求进到UrlRoutingModule里面,取SwiftRouteCollection的值:

我们看到,这里的SwiftRouteCollection的值就是在全局配置文件里面配置的类型。

3、然后请求进到SwiftRouteCollection类的GetRouteData()方法里面。这个方法的作用很明显,就是解析当前的请求的url,从中获取当前的controller、action、参数等信息。这个方法执行完之后得到的SwiftRouteData对象,结果如下:

这个对象包含两个属性,RouteHandler和当前请求的路由表。

4、通过步骤3知道,当前的swiftRouteData对象包含了RouteHandler对象, IRouteHandler routeHandler = routeData.RouteHandler; 结果如下:

5、得到RouteHandler对象之后,就是从该对象的GetHttpHandler()方法里面得到当前的HttpHandler。

这个应该不难理解,将routeData和context传入MvcHandler里面。这就是为什么之前MvcHandler里面有一个两个参数的构造函数的原因。

6、然后就是执行 context.RemapHandler(httpHandler); 将请求正式交给MvcHandler。

7、在MvcHandler的ProcessRequest方法里面,首先从当前请求的路由表里面去控制器名称,如下图,得到”Home“:

8、然后就是创建控制器工厂、从工厂里面得到当前请求的控制器的对象,这部分和之前变化不大。

9、得到控制器对象之后,执行对应的当前请求的action方法,请求尽到Controller这个父类的Execute()方法里面

10、通过反射,最终执行BootstrapTest()方法。

11、BootstrapTest()方法执行完成之后,释放当前的控制器对象: factory.ReleaseController(controller); 。请求结束。

五、支持方法的重载

 博主对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)
        {
            .....
        }
    }
复制代码

1、请求默认路由地址:http://localhost:16792/

2、请求地址:http://localhost:16792/Home/index?id=1

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

当然上文封装都是只是通过url传递参数的情况,等有时间可以扩展下,使得支持通过post请求传递参数。

六、总结

通过上一篇和这一篇,我们基本上把MVC的核心原理涉及到的技术都重写了一遍,等有时间再扩展一个自己的”View“,加上模型验证,数据绑定,我们的Swift.MVC就算是一个相对完整的微型MVC框架了。当然,此框架仅仅是从学习理解MVC的原理层面去实现的,如果要应用于项目,还要考虑很多东西,不论如何,是个好的开始,有时间继续完善。源码地址

如果你觉得本文能够帮助你,可以右边随意 打赏 博主,也可以 推荐 进行精神鼓励。你的支持是博主继续坚持的不懈动力。





本文转自懒得安分博客园博客,原文链接:http://www.cnblogs.com/landeanfen/p/6016394.html,如需转载请自行联系原作者

目录
相关文章
|
7月前
|
安全 数据管理 中间件
云LIS系统源码JavaScript+B/S架构MVC+SQLSugar医院版检验科云LIS系统源码 可提供演示
检验科云LIS系统源码是医疗机构信息化发展的重要趋势。通过云计算技术实现数据的集中管理和共享可以提高数据利用效率和安全性;通过高效灵活的系统设计和可扩展性可以满足不同医疗机构的需求;通过移动性和智能化可以提高医疗服务的精准度和效率;通过集成性可以实现医疗服务的协同性和效率。因此,多医院版检验科云LIS系统源码将成为未来医疗机构信息化发展的重要方向之一。
98 2
|
2月前
|
前端开发 Java 数据库
springBoot:template engine&自定义一个mvc&后端给前端传数据&增删改查 (三)
本文介绍了如何自定义一个 MVC 框架,包括后端向前端传递数据、前后端代理配置、实现增删改查功能以及分页查询。详细展示了代码示例,从配置文件到控制器、服务层和数据访问层的实现,帮助开发者快速理解和应用。
|
2月前
|
前端开发 Java
【案例+源码】详解MVC框架模式及其应用
【案例+源码】详解MVC框架模式及其应用
166 0
|
4月前
|
设计模式 存储 前端开发
MVC 框架的主要问题是什么?
【8月更文挑战第30天】
99 0
|
6月前
|
设计模式 前端开发 Java
【Spring MVC】快速学习使用Spring MVC的注解及三层架构
【Spring MVC】快速学习使用Spring MVC的注解及三层架构
104 1
|
6月前
|
安全 前端开发 测试技术
安全开发-PHP应用&模版引用&Smarty渲染&MVC模型&数据联动&RCE安全&TP框架&路由访问&对象操作&内置过滤绕过&核心漏洞
安全开发-PHP应用&模版引用&Smarty渲染&MVC模型&数据联动&RCE安全&TP框架&路由访问&对象操作&内置过滤绕过&核心漏洞
|
7月前
|
前端开发 Java Spring
Java Web ——MVC基础框架讲解及代码演示(下)
Java Web ——MVC基础框架讲解及代码演示
77 1
|
7月前
|
前端开发 Java 应用服务中间件
Spring MVC框架概述
Spring MVC 是一个基于Java的轻量级Web框架,采用MVC设计模型实现请求驱动的松耦合应用开发。框架包括DispatcherServlet、HandlerMapping、Handler、HandlerAdapter、ViewResolver核心组件。DispatcherServlet协调这些组件处理HTTP请求和响应,Controller处理业务逻辑,Model封装数据,View负责渲染。通过注解@Controller、@RequestMapping等简化开发,支持RESTful请求。Spring MVC具有清晰的角色分配、Spring框架集成、多种视图技术支持以及异常处理等优点。
91 1
|
7月前
|
设计模式 前端开发 网络协议
Java Web ——MVC基础框架讲解及代码演示(上)
Java Web ——MVC基础框架讲解及代码演示
58 0
|
7月前
|
开发框架 前端开发 JavaScript
JavaScript云LIS系统源码ASP.NET CORE 3.1 MVC + SQLserver + Redis医院实验室信息系统源码 医院云LIS系统源码
实验室信息系统(Laboratory Information System,缩写LIS)是一类用来处理实验室过程信息的软件,云LIS系统围绕临床,云LIS系统将与云HIS系统建立起高度的业务整合,以体现“以病人为中心”的设计理念,优化就诊流程,方便患者就医。
87 0