MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)
前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习。虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉得自己动手写一遍印象要深刻许多,希望想深入学习MVC的童鞋自己动手写写。好了,废话就此打住。
本文原创地址:http://www.cnblogs.com/landeanfen/p/6016394.html
MVC源码学习系列文章目录:
- MVC系列——MVC源码学习:打造自己的MVC框架(一)
- MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)
- MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)
- MVC系列——MVC源码学习:打造自己的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() + 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 = ; 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[]] = keyvalue[];
}
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/
欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利
MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)的更多相关文章
- 源码学习之ASP.NET MVC Application Using Entity Framework
源码学习的重要性,再一次让人信服. ASP.NET MVC Application Using Entity Framework Code First 做MVC已经有段时间了,但看了一些CodePle ...
- Spring源码学习-容器BeanFactory(四) BeanDefinition的创建-自定义标签的解析.md
写在前面 上文Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签对Spring默认标签的解析做了详解,在xml元素的解析中,Spri ...
- SpringMVC源码学习:容器初始化+MVC初始化+请求分发处理+参数解析+返回值解析+视图解析
目录 一.前言 二.初始化 1. 容器初始化 根容器查找的方法 容器创建的方法 加载配置文件信息 2. MVC的初始化 文件上传解析器 区域信息解析器 handler映射信息解析 3. Handler ...
- 菜鸟系列Fabric源码学习 — 区块同步
Fabric 1.4 源码分析 区块同步 本文主要从源码层面介绍fabric peer同步区块过程,peer同步区块主要有2个过程: 1)peer组织的leader与orderer同步区块 2)pee ...
- 菜鸟系列Fabric源码学习 — peer节点启动
Fabric 1.4 源码分析peer节点启动 peer模块采用cobra库来实现cli命令. Cobra提供简单的接口来创建强大的现代化CLI接口,比如git与go工具.Cobra同时也是一个程序, ...
- 菜鸟系列Fabric源码学习—创建通道
通道创建源码解析 1. 与通道创建相关配置及操作命令 主要是configtx.yaml.通过应用通道的profile生成创建通道的配置文件. TwoOrgsChannel: Consortium: S ...
- 菜鸟系列Fabric源码学习 — committer记账节点
Fabric 1.4 源码分析 committer记账节点 本文档主要介绍committer记账节点如何初始化的以及committer记账节点的功能及其实现. 1. 简介 记账节点负责验证交易和提交账 ...
- 菜鸟系列Fabric源码学习 — MVCC验证
Fabric 1.4 源码分析 MVCC验证 读本节文档之前建议先查看[Fabric 1.4 源码分析 committer记账节点]章节. 1. MVCC简介 Multi-Version Concur ...
- Java集合源码学习(一)集合框架概览
>>集合框架 Java集合框架包含了大部分Java开发中用到的数据结构,主要包括List列表.Set集合.Map映射.迭代器(Iterator.Enumeration).工具类(Array ...
- 【mybatis源码学习】mybatis和spring框架整合,我们依赖的mapper的接口真相
转载至:https://www.cnblogs.com/jpfss/p/7799806.html Mybatis MapperScannerConfigurer 自动扫描 将Mapper接口生成代理注 ...
随机推荐
- ASP.NET Core 中文文档 第四章 MVC(3.6.2 )自定义标签辅助类(Tag Helpers)
原文:Authoring Tag Helpers 作者:Rick Anderson 翻译:张海龙(jiechen) 校对:许登洋(Seay) 示例代码查看与下载 从 Tag Helper 讲起 本篇教 ...
- AJAX(一)
AJAX(一) Ajax是Asynchronous Javascript和XML的简写,这一技术能够向服务器请求额外的数据而无需卸载页面,会带来更好的用户体验. [前面的基础知识][关于同步和异步的了 ...
- ASP.NET MVC5学习笔记01
由于之前在项目中也使用MVC进行开发,但是具体是那个版本就不是很清楚了,但是我觉得大体的思想是相同的,只是版本高的在版本低的基础上增加了一些更加方便操作的东西.下面是我学习ASP.NET MVC5高级 ...
- bzoj2820--莫比乌斯反演
题目大意: 给定N, M,求1<=x<=N, 1<=y<=M且gcd(x, y)为质数的(x, y)有多少对. 推导: 设n<=m ans= = 由于gcd(i,j)= ...
- Jedis的使用
Redis是常用的key-value存储服务器,Java使用Redis有很多方法,其中官方推荐的是Jedis. 使用Jedis,首先是下载jedis-x.x.x.jar文件并导入工程,然后运行Redi ...
- 包含修改字体,图片上传等功能的文本输入框-Bootstrap
通过jQuery Bootstrap小插件,框任何一个div转换变成一个富文本编辑框,主要特色: 在Mac和window平台下自动针对常用操作绑定热键 可以拖拽插入图片,支持图片上传(也可以获取移动设 ...
- CSS3鼠标滑过图标放大以及旋转
本人是HTML5-CSS3初学者,这次分享一款纯CSS3实现的图片动画,当鼠标滑过小图标时,图标会放大,同时图标会出现旋转的动画效果.我们在很多个性化个人博客中经常看到鼠标滑过人物头像后头像图片旋转就 ...
- python入门-python解释器执行
最近由于公司需要,接触了python这门神奇的语言,给我的感觉就是开发快速和代码简洁. 开始还是先罗列一下解释性语言和编译性语言的差别吧0.0! 编译性语言:是在程序运行前,需要专门的一个编译过程 ...
- SSM三大框架整合详细教程(Spring+SpringMVC+MyBatis)【转】
使用SSM(Spring.SpringMVC和Mybatis)已经有三个多月了,项目在技术上已经没有什么难点了,基于现有的技术就可以实现想要的功能,当然肯定有很多可以改进的地方.之前没有记录SSM整合 ...
- HTML5应用程序缓存Application Cache
什么是Application Cache HTML5引入了应用程序缓存技术,意味着web应用可进行缓存,并在没有网络的情况下使用,通过创建cache manifest文件,可以轻松的创建离线应用. A ...