路由(Route)、控制器(Controller)、行为(Action)、模型(Model)、视图(View)html
用一句简单地话来描述以上关键点:面试
路由(Route)就至关于一个公司的前台小姐,她负责带你(请求)找到跟你面试的面试官(控制器Controller),面试官可能会面试不一样的职位(Action),你(请求)也会拿到不一样的结果(ActionResult);数组
不管是ASP.NET WebForms仍是ASP.NET MVC,他们都只是一个框架,是创建在System.Web之上的框架。为了保证程序的纯净,咱们能够将全部默认的引用都移除。固然,咱们仍是得保留几个必要的dll引用:mvc
注意:这里咱们并无引入System.Web.Mvc.dll,由于咱们要实现的就是一个简单的MVC机制。app
按照ASP.NET MVC的惯例添加Controllers、Models和Views文件夹(不是必须的):框架
咱们首先在Controllers文件夹下新建一个接口,取名为IController,它约定了全部Controller都必需要实现的方法:Executeasp.net
public interface IController { void Execute(HttpContext context); }
IController接口只定义了一个方法声明,它接收一个HttpContext的上下文对象。ide
有了接口,咱们就能够实现具体的Controller了,这里咱们实现了两个Controller:HomeController和ProductController。函数
(1)HomeController测试
public class HomeController : IController { private HttpContext currentContext; // action 1 : Index public void Index() { currentContext.Response.Write("Home Index Success!"); } // action 2 : Add public void Add() { currentContext.Response.Write("Home Add Success!"); } public void Execute(HttpContext context) { currentContext = context; // 默认Action名称 string actionName = "index"; // 获取Action名称 if (!string.IsNullOrEmpty(context.Request["action"])) { actionName = context.Request["action"]; } switch (actionName.ToLower()) { case "index": this.Index(); break; case "add": this.Add(); break; default: this.Index(); break; } } }
(2)ProductController
public class ProductController : IController { private HttpContext currentContext; // action 1 : Index public void Index() { currentContext.Response.Write("Product Index Success!"); } // action 2 : Add public void Add() { currentContext.Response.Write("Product Add Success!"); } public void Execute(HttpContext context) { currentContext = context; // 默认Action名称 string actionName = "index"; // 获取Action名称 if (!string.IsNullOrEmpty(context.Request["action"])) { actionName = context.Request["action"]; } switch (actionName.ToLower()) { case "index": this.Index(); break; case "add": this.Add(); break; default: this.Index(); break; } } }
有了Controller以后,须要借助一个入口来指引请求到达指定Controller,因此这里咱们实现一个最简单的通常处理程序,它将url中的参数进行解析并实例化指定的Controller进行后续请求处理:
/// <summary> /// 模拟MVC程序的单一入口 /// </summary> public class Index : IHttpHandler { public void ProcessRequest(HttpContext context) { // 获取Controller名称 var controllerName = context.Request.QueryString["c"]; // 声明IControoler接口-根据Controller Name找到对应的Controller IController controller = null; if (string.IsNullOrEmpty(controllerName)) { controllerName = "home"; } switch (controllerName.ToLower()) { case "home": controller = new HomeController(); break; case "product": controller = new ProductController(); break; default: controller = new HomeController(); break; } controller.Execute(context); } public bool IsReusable { get { return false; } } }
该通常处理程序接收http请求的两个参数controller和action,并经过controller的参数名称生成对应的Controller实例对象,将HttpContext对象做为参数传递给对应的Controller对象进行后续处理。
在Global.asax中有一个Application_BeginRequest的事件,它发生在每一个Request开始处理以前,所以在这里咱们能够进行一些相似于URL重写的工做。解析URL固然也在这里进行,咱们要作的就是将用户输入的相似于MVC形式的URL:http://www.xxx.com/home/index 进行正确的解析,将该请求交由HomeController进行处理。
public class Global : System.Web.HttpApplication { protected void Application_BeginRequest(object sender, EventArgs e) { #region 方式一:伪静态方式实现路由映射服务 // 得到当前请求的URL地址 var executePath = Request.AppRelativeCurrentExecutionFilePath; // 得到当前请求的参数数组 var paraArray = executePath.Substring(2).Split('/'); // 若是没有参数则执行默认配置 if (string.IsNullOrEmpty(executePath) || executePath.Equals("~/") || paraArray.Length == 0) { return; } string controllerName = "home"; if (paraArray.Length > 0) { controllerName = paraArray[0]; } string actionName = "index"; if (paraArray.Length > 1) { actionName = paraArray[1]; } // 入口一:单一入口 Index.ashx Context.RewritePath(string.Format("~/Index.ashx?controller={0}&action={1}", controllerName, actionName)); // 入口二:指定MvcHandler进行后续处理 //Context.RemapHandler(new MvcHandler()); #endregion } }
这里咱们直接在代码中hardcode了一个默认的controller和action名称,分别是home和index。
能够看出,最后咱们实际上作的就是解析URL,并经过重定向到Index.ashx进行所谓的Route路由工做。
(1)默认路由
(2)/home/add
(3)/product/index
想一想咱们在ASP.NET MVC项目中是否是首先向程序注册一些指定的路由规则,所以这里咱们也在Global.asax中模拟一个路由规则表:
(1)增长一个静态的路由规则集合
// 定义路由规则 private static IList<string> Routes;
(2)在Application_Start事件中注册路由规则
protected void Application_Start(object sender, EventArgs e) { Routes = new List<string>(); // http://www.edisonchou.cn/controller/action Routes.Add("{controller}/{action}"); // http://www.edisonchou.cn/controller Routes.Add("{controller}"); }
(3)改写Application_BeginRequest事件,使URL与路由规则进行匹配
protected void Application_BeginRequest(object sender, EventArgs e) { #region 方式二:模拟路由表实现映射服务 // 模拟路由字典 IDictionary<string, string> routeData = new Dictionary<string, string>(); // 将URL与路由表中每一条记录进行匹配 foreach (var item in Routes) { var executePath = Request.AppRelativeCurrentExecutionFilePath;//// 得到当前请求的参数数组 // 若是没有参数则执行默认配置 if (string.IsNullOrEmpty(executePath) || executePath.Equals("~/")) { executePath += "/home/index"; } var executePathArray = executePath.Substring(2).Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); var routeKeys = item.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); if (executePathArray.Length == routeKeys.Length) { for (int i = 0; i < routeKeys.Length; i++) { routeData.Add(routeKeys[i], executePathArray[i]); } // 入口一:单一入口 Index.ashx //Context.RewritePath(string.Format("~/Index.ashx?c={0}&a={1}", routeData["{controller}"], routeData["{action}"])); // 入口二:指定MvcHandler进行后续处理 Context.RemapHandler(new MvcHandler(routeData)); // 只要知足一条规则就跳出循环匹配 break; } } #endregion }
在ASP.NET请求处理管道中,具体的处理工做都是转交给了实现IHttpHandler接口的Handler对象进行处理。所以,这里咱们也遵守这个规则,实现一个MvcHandler来代替刚才的Index.ashx来进行路由工做:
public class MvcHandler : IHttpHandler { // 路由表 private IDictionary<string, string> routeData; // 全部控制器的类型集合 private static IList<Type> alloctionControllerTypes; // 当前类第一次加载时调用静态构造函数 static MvcHandler() { alloctionControllerTypes = new List<Type>(); // 得到当前全部引用的程序集 var assemblies = BuildManager.GetReferencedAssemblies(); // 遍历全部的程序集 foreach (Assembly assembly in assemblies) { // 获取当前程序集中全部的类型 var allTypes = assembly.GetTypes(); // 遍历全部的类型 foreach (Type type in allTypes) { // 若是当前类型知足条件 if (type.IsClass && !type.IsAbstract && type.IsPublic && typeof(IController).IsAssignableFrom(type)) { // 将全部Controller加入集合 alloctionControllerTypes.Add(type); } } } } public MvcHandler(IDictionary<string, string> routeData) { this.routeData = routeData; } public void ProcessRequest(HttpContext context) { var controllerName = routeData["{controller}"]; if (string.IsNullOrEmpty(controllerName)) { // 指定默认控制器 controllerName = "home"; } IController controller = null; // 经过反射的方式加载具体实例 foreach (var controllerItem in alloctionControllerTypes) { if (controllerItem.Name.Equals(string.Format("{0}Controller", controllerName), StringComparison.InvariantCultureIgnoreCase)) { controller = Activator.CreateInstance(controllerItem) as IController; break; } } var requestContext = new HttpContextWrapper() { Context = context, RouteData = routeData }; controller.Execute(requestContext); } public bool IsReusable { get { throw new NotImplementedException(); } } }
上述代码中须要注意如下几点:
(1)在静态构造函数中初始化全部Controller
// 路由表 private IDictionary<string, string> routeData; // 全部控制器的类型集合 private static IList<Type> alloctionControllerTypes; // 当前类第一次加载时调用静态构造函数 static MvcHandler() { alloctionControllerTypes = new List<Type>(); // 得到当前全部引用的程序集 var assemblies = BuildManager.GetReferencedAssemblies(); // 遍历全部的程序集 foreach (Assembly assembly in assemblies) { // 获取当前程序集中全部的类型 var allTypes = assembly.GetTypes(); // 遍历全部的类型 foreach (Type type in allTypes) { // 若是当前类型知足条件 if (type.IsClass && !type.IsAbstract && type.IsPublic && typeof(IController).IsAssignableFrom(type)) { // 将全部Controller加入集合 alloctionControllerTypes.Add(type); } } } }
此段代码利用反射加载了全部实现了IController接口的Controller类,并存入了一个静态集合alloctionControllerTypes里面,便于后面全部请求进行匹配。
(2)在ProcessRequest方法中再次利用反射动态建立Controller实例
public void ProcessRequest(HttpContext context) { var controllerName = routeData["{controller}"]; if (string.IsNullOrEmpty(controllerName)) { // 指定默认控制器 controllerName = "home"; } IController controller = null; // 经过反射的方式加载具体实例 foreach (var controllerItem in alloctionControllerTypes) { if (controllerItem.Name.Equals(string.Format("{0}Controller", controllerName), StringComparison.InvariantCultureIgnoreCase)) { controller = Activator.CreateInstance(controllerItem) as IController; break; } } var requestContext = new HttpContextWrapper() { Context = context, RouteData = routeData }; controller.Execute(requestContext); }
这里因为要使用到RouteData这个路由表的Dictionary对象,因此咱们须要改写一下传递的对象由原来的HttpContext类型转换为自定义的包装类HttpContextWrapper:
public class HttpContextWrapper { public HttpContext Context { get; set; } public IDictionary<string, string> RouteData { get; set; } }
能够看出,其实就是简单地包裹了一下,添加了一个RouteData的路由表属性。
固然,IController接口的方法定义也得随之改一下:
public interface IController { void Execute(HttpContextWrapper context); }
至此,MvcHandler的代码就写完,咱们能够总结一下它的主要流程:
(1)HomeController
public class HomeController : IController { private HttpContext currentContext; public void Execute(HttpContextWrapper context) { currentContext = context.Context; // 获取Action名称 string actionName = "index"; if (context.RouteData.ContainsKey("{action}")) { actionName = context.RouteData["{action}"]; } switch (actionName.ToLower()) { case "index": this.Index(); break; case "add": this.Add(); break; default: this.Index(); break; } } // action 1 : Index public void Index() { currentContext.Response.Write("Home Index Success!"); } // action 2 : Add public void Add() { currentContext.Response.Write("Home Add Success!"); } }
(2)ProductController
public class ProductController : IController { private HttpContext currentContext; public void Execute(HttpContextWrapper context) { currentContext = context.Context; // 获取Action名称 string actionName = "index"; if (context.RouteData.ContainsKey("{action}")) { actionName = context.RouteData["{action}"]; } switch (actionName.ToLower()) { case "index": this.Index(); break; case "add": this.Add(); break; default: this.Index(); break; } } // action 1 : Index public void Index() { currentContext.Response.Write("Product Index Success!"); } // action 2 : Add public void Add() { currentContext.Response.Write("Product Add Success!"); } }
(1)默认路由
(2)/product/add
(3)/product
本文首先回顾了一下MVC的关键概念,并从一个“纯净”的ASP.NET Web空项目开始一步一步构建一个相似于MVC的应用程序,经过单一处理入口的伪静态方式与模拟路由表的方式进行了简单地实现,并进行了测试。这次实验,核心就在于获取路由数据,指定处理程序,也就是理解并模拟路由机制。路由模块就是一个很简单的HttpModule(若是您对HttpModule不熟悉,请浏览我翻译的一篇文章:ASP.NET应用程序和页面生命周期),而ASP.NET MVC帮咱们实现了UrlRoutingModule从而使咱们轻松实现了路由机制,该机制获取了路由数据,并制定处理程序(如MvcHandler),执行MvcHandler的ProcessRequest方法找到对应的Controller类型,最后将控制权交给对应的Controller对象,就至关于前台小妹妹帮你找到了面试官,你能够跟着面试官去进行相应的面试了(Actioin),但愿你能获得好的结果(ActionResult)。
固然,这个DEMO还有不少须要改进的地方,仍然须要不断的改进才能称之为一个“框架”。第一个版本就到此,后续我会写第二个版本,但愿到时再写一篇笔记来分享。
MySimpleMvc : 点我下载