前言
跟我一起顺藤摸瓜剖析 Artech 老师的 MiniMVC 是如何运行的,了解它,我们就大体了解 ASP.NET MVC 是如何运行的了。既然是“顺藤摸瓜”,那我们就按照 ASP.NET 的执行顺序来反推代码。准备好了吗?Let's go!
解决方案大体结构
PS:原本很多代码没有注释,我按照自己的理解,增加了一些注释,希望能帮助您,共同提高,谢谢!
1. Global.asax 探究
ASP.NET 中的 Application_Start 方法一般是最先执行的,我们有必要知道当应用程序启动时到底发生了什么!
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Security; using System.Web.SessionState; using Artech.MiniMvc; namespace WebApp { public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { // 下面注册我们项目需要匹配的路由规则。ASP.NET Route 在接收到请求后,会把请求的 // URL 和下面我们注册的路由规则相比较(可以理解为正则表达式匹配的原理), 最先 // 匹配的规则(即 Route),就由该 Route 的 RouteHandler 来处理。所以注册路由 // 很关键。 RouteTable.Routes.Add("default_html", new RegexRoute { Url = "{controller}/{action}.html" }); // 注意:RegexRoute 类是本人扩展的,目的是替换原先的 Route 的匹配规则,以及增加一些 // 默认值(controller 和 action)的实现。 RouteTable.Routes.Add("default", new RegexRoute { Url = "{controller}/{action}", Defaults = new { controller = "Home", action = "Index" } }); // 下面是设置控制器工厂,MVC 内部仅仅只有一个实现了 IControllerFactory 的工厂 ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory()); // 下面是给控制器工厂添加默认的命名空间,以便 MVC 在找控制器时查询速度会更快。 ControllerBuilder.Current.DefaultNamespaces.Add("WebApp"); } } }
RouteTable.Routes 是一个静态成员,保存着所有被注册的“路由”。RegexRoute 是我自定义的路由,下面会有介绍。
2. 查看自定义的 HttpModule
找到 Web.Config 文件。
<?xml version="1.0"?> <configuration> <system.web> <compilation debug="false" targetFramework="4.0" /> <httpModules> <!-- 下面是指定我们自定义的 Url 路由 Module --> <add name="UrlRoutingModule" type="Artech.MiniMvc.UrlRoutingModule, Artech.MiniMvc"/> </httpModules> </system.web> <system.webServer> <validation validateIntegratedModeConfiguration="false"/> <modules> <!-- 如果需要在 IIS7 以上的版本的集成模式下运行,则需要添加下面的注册 --> <add name="UrlRoutingModule" type="Artech.MiniMvc.UrlRoutingModule, Artech.MiniMvc"/> </modules> </system.webServer> </configuration>
我们看到注册了一个自定义的 HttpModule,那我们找到这个类去。
/// <summary> /// Url 路由 Module /// </summary> public class UrlRoutingModule : IHttpModule { public void Dispose() { } public void Init(HttpApplication context) { context.PostResolveRequestCache += OnPostResolveRequestCache; // 关于这个事件,官方摘要如下: // 在 ASP.NET 跳过当前事件处理程序的执行并允许缓存模块满足来自缓存的请求时发生。 } protected virtual void OnPostResolveRequestCache(object sender, EventArgs e) { // 下面是得到当前请求的 Http 上下文 HttpContext currentHttpContext = ((HttpApplication)(sender)).Context; // 下面是一个包装类,把 HttpContext 转换成 HttpContextBase。如果你问我为什么 // HttpApplication.Context 返回的是 HttpContext,而不是 HttpContextBase,我 // 只能说一句:历史遗留问题 HttpContextWrapper httpContext = new HttpContextWrapper(currentHttpContext); // 下面是从当前 HttpContextBase 中获取 URL,把这个 URL 和应用程序启动( // Application_Start )中注册的静态路由表(RouteTable)相比较(也称匹配), // 第一个匹配的 RouteData 就立即返回,如果都没有匹配,则返回 NULL。 RouteData routeData = RouteTable.Routes.GetRouteData(httpContext); if (null == routeData) { return; } RequestContext requestContext = new RequestContext { RouteData = routeData, HttpContext = httpContext }; IHttpHandler handler = routeData.RouteHandler.GetHttpHandler(requestContext); httpContext.RemapHandler(handler); // 用于为请求指定处理程序。 } }
3. RouteTable.Routes.GetRouteData 探究
按 F12 进入 RouteTable.Routes.GetRouteData 方法。
/// <summary> /// 一个 Key-Value 集合,Key 表示路由名称,Value 表 /// 示路由(包含 URL Pattern、路由处理程序等等)。 /// 可以简单的理解它用于保存 Global.asax 中注册的值。 /// </summary> public class RouteDictionary : Dictionary<string, RouteBase> { /// <summary> /// 下面是从当前 HttpContextBase 中获取 URL,把这个 URL 和应用程序启动( /// Application_Start )中注册的静态路由表(RouteTable)相比较(也称匹配), /// 第一个匹配的 RouteData 就立即返回,如果都没有匹配,则返回 NULL。 /// </summary> /// <param name="httpContext">当前请求上下文</param> /// <returns></returns> public RouteData GetRouteData(HttpContextBase httpContext) { foreach (var route in this.Values) { RouteData routeData = route.GetRouteData(httpContext); if (null != routeData) { return routeData; } } return null; } }
我们看到是一个 RouteDictionary 类,继承自 Dictionary<string, RouteBase>。疑问来了, RouteBase 是什么?
4. RouteBase 探究
按 F12 进入 RouteBase 类。
/// <summary> /// 路由抽象类 /// </summary> public abstract class RouteBase { public abstract RouteData GetRouteData(HttpContextBase httpContext); }
我们看到是一个抽象类,包含一个抽象方法,意味着继承了这个 RouteBase 类的“非抽象”子类必须实现 GetRouteData 方法。疑问来了,返回的 RouteData 是什么?
5. RouteData 探究
按 F12 进入 RouteData 类。
/// <summary> /// 路由数据,一般用于保存当前 HttpContext 下 /// 获取的数据。 /// </summary> public class RouteData { public IDictionary<string, object> Values { get; private set; } public IDictionary<string, object> DataTokens { get; private set; } public IRouteHandler RouteHandler { get; set; } public RouteBase Route { get; set; } public RouteData() { this.Values = new Dictionary<string, object>(); this.DataTokens = new Dictionary<string, object>(); this.DataTokens.Add("namespaces", new List<string>()); } /// <summary> /// 获取当前匹配的 Controller /// </summary> public string Controller { get { object controllerName = string.Empty; this.Values.TryGetValue("controller", out controllerName); return controllerName.ToString(); } } /// <summary> /// 获取当前匹配的 Action /// </summary> public string ActionName { get { object actionName = string.Empty; this.Values.TryGetValue("action", out actionName); return actionName.ToString(); } } public IEnumerable<string> Namespaces { get { return (IEnumerable<string>)this.DataTokens["namespaces"]; } } }
正如注释对 RouteData 的解释:路由数据,一般用于保存当前 HttpContext 下获取的数据。疑问来了,包含有一个 IRouteHandler 接口的 RouteHandler 是什么?
PS:我们可以简单的理解它就只是一个封装有信息的数据实体。
6. IRouteHandler 探究
按 F12 进入 IRouteHandler 接口。
/// <summary> /// 路由处理程序的接口 /// </summary> public interface IRouteHandler { IHttpHandler GetHttpHandler(RequestContext requestContext); }
其中的 IHttpHandler 是 .NET 原生类库中 System.Web 命名空间下的接口,而 RequestContext 类却不是,是自定义的类,它到底是什么?我们进去瞧瞧。
7. RequestContext 探究
按 F12 进入 RequestContext 类。
/// <summary> /// 当前请求上下文,用于 MVC 调用链中的传递 /// </summary> public class RequestContext { public virtual HttpContextBase HttpContext { get; set; } public virtual RouteData RouteData { get; set; } }
其中的 HttpContextBaser 是 .NET 原生类库中 System.Web 命名空间下的类。RouteData 类是我们自定义的类,记起来了么?上面有介绍的。
8. 回到 UrlRoutingModule 类。
回到 UrlRoutingModule 类。
protected virtual void OnPostResolveRequestCache(object sender, EventArgs e) { // 下面是得到当前请求的 Http 上下文 HttpContext currentHttpContext = ((HttpApplication)(sender)).Context; // 下面是一个包装类,把 HttpContext 转换成 HttpContextBase。如果你问我为什么 // HttpApplication.Context 返回的是 HttpContext,而不是 HttpContextBase,我 // 只能说一句:历史遗留问题 HttpContextWrapper httpContext = new HttpContextWrapper(currentHttpContext); // 下面是从当前 HttpContextBase 中获取 URL,把这个 URL 和应用程序启动( // Application_Start )中注册的静态路由表(RouteTable)相比较(也称匹配), // 第一个匹配的 RouteData 就立即返回,如果都没有匹配,则返回 NULL。 RouteData routeData = RouteTable.Routes.GetRouteData(httpContext); if (null == routeData) { return; } RequestContext requestContext = new RequestContext { RouteData = routeData, HttpContext = httpContext }; IHttpHandler handler = routeData.RouteHandler.GetHttpHandler(requestContext); httpContext.RemapHandler(handler); // 用于为请求指定处理程序。 }
注意这段代码:
1
|
RouteData routeData = RouteTable.Routes.GetRouteData(httpContext);
|
回想我们在 Application_Start 看到的 RouteTable.Routes.Add,恍然大悟,这不就是那请求的 URL 和系统中已经匹配的路由进行匹配么?哦,原来如此!
/// <summary> /// 下面是从当前 HttpContextBase 中获取 URL,把这个 URL 和应用程序启动( /// Application_Start )中注册的静态路由表(RouteTable)相比较(也称匹配), /// 第一个匹配的 RouteData 就立即返回,如果都没有匹配,则返回 NULL。 /// </summary> /// <param name="httpContext">当前请求上下文</param> /// <returns></returns> public RouteData GetRouteData(HttpContextBase httpContext) { foreach (var route in this.Values) { RouteData routeData = route.GetRouteData(httpContext); if (null != routeData) { return routeData; } } return null; }
注意:如果都没有匹配,会返回 NULL,用来表示没有找到匹配的项。PS:这需要调用端做是否为 NULL 判断。如果找到,继续回到 UrlRoutingModule 类。
9. 封装信息到 RequestContext
下面的代码是把刚刚找到的 RouteData 和 HttpContext 一起打包到 RequestContext 中。
RequestContext requestContext = new RequestContext { RouteData = routeData, HttpContext = httpContext };
就把 RequestContext 类理解成一个比 RouteData 和 HttpContext 都大的大箱子吧!
10. 通过 RouteData 得到 HttpHandler
还记得通过路由匹配得到的 RouteData 吗?它有一个 RouteHandler 的属性,正是由它来得到 HttpHandler。
IHttpHandler handler = routeData.RouteHandler.GetHttpHandler(requestContext); httpContext.RemapHandler(handler); // 用于为请求指定处理程序。
你可能要问 RouteHandler 的实现类从何而来,是的,它通过 RouteBase 的实现类(也可以叫子类)扩展而来。
11. Route 探究
找到 Route 类。
/// <summary> /// ASP.NET MVC 默认的路由实现 /// </summary> public class Route : RouteBase { public IRouteHandler RouteHandler { get; set; } public Route() { this.DataTokens = new Dictionary<string, object>(); this.RouteHandler = new MvcRouteHandler(); } /// <summary> /// 从当前 URL 中提取路由的 Key-Value 并封装到 RouteData 中 /// </summary> /// <param name="httpContext"></param> /// <returns></returns> public override RouteData GetRouteData(HttpContextBase httpContext) { IDictionary<string, object> variables; if (this.Match(httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2), out variables)) { RouteData routeData = new RouteData(); foreach (var item in variables) { routeData.Values.Add(item.Key, item.Value); } foreach (var item in DataTokens) { routeData.DataTokens.Add(item.Key, item.Value); } routeData.RouteHandler = this.RouteHandler; return routeData; } return null; } public string Url { get; set; } /// <summary> /// 默认值 /// </summary> public object Defaults { get; set; } public IDictionary<string, object> DataTokens { get; set; } protected virtual bool Match(string requestUrl, out IDictionary<string,object> variables) { variables = new Dictionary<string,object>(); string[] strArray1 = requestUrl.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); string[] strArray2 = this.Url.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); if (strArray1.Length != strArray2.Length) { return false; } for (int i = 0; i < strArray2.Length; i++) { if(strArray2[i].StartsWith("{") && strArray2[i].EndsWith("}")) { variables.Add(strArray2[i].Trim("{}".ToCharArray()),strArray1[i]); } } return true; } private IDictionary<string, object> _defaultValueDictionary; /// <summary> /// 默认值集合 /// </summary> protected IDictionary<string, object> DefaultValueDictionary { get { if (_defaultValueDictionary != null) { return _defaultValueDictionary; } _defaultValueDictionary = new Dictionary<string, object>(); if (Defaults != null) { foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(Defaults)) { _defaultValueDictionary.Add(descriptor.Name, descriptor.GetValue(Defaults)); } } return _defaultValueDictionary; } } }
我们看到 Route 实现了 RouteBase(抽象类)。
12. 自定义 RegexRoute 类
下面我们自定义一个 RegexRoute 类来玩玩。
public class RegexRoute : Route { private List<string> _routeParameterList; /// <summary> /// 得到当前注册的路由中的参数 /// </summary> protected List<string> RouteParameterList { get { if (_routeParameterList != null) { return _routeParameterList; } Regex reg1 = new Regex(@"\{(.+?)\}", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled); MatchCollection matchCollection = reg1.Matches(this.Url); _routeParameterList = new List<string>(); if (matchCollection.Count == 0) { return _routeParameterList; } foreach (Match matchItem in matchCollection) { string value = matchItem.Groups[1].Value; if (!string.IsNullOrEmpty(value)) { _routeParameterList.Add(Regex.Escape(value)); } } return _routeParameterList; } } private string _urlRegexPattern; protected string UrlRegexPattern { get { if (_urlRegexPattern != null) { return _urlRegexPattern; } _urlRegexPattern = Regex.Escape(this.Url).Replace("\\{", "{").Replace(@"\\}", "}"); foreach (string param in RouteParameterList) { _urlRegexPattern = _urlRegexPattern.Replace("{" + param + "}", @"(?<" + param + ">.*?)"); } _urlRegexPattern = "^" + _urlRegexPattern + "/?$"; return _urlRegexPattern; // 比如: ^(?<controller>.*?)/(?<action>.*?)$ } } protected override bool Match(string requestUrl, out IDictionary<string, object> variables) { variables = new Dictionary<string, object>(); int tempIndex = requestUrl.IndexOf("?"); if (tempIndex > -1) { if (tempIndex > 0) { requestUrl = requestUrl.Substring(0, tempIndex); } else { requestUrl = string.Empty; } } if (!requestUrl.EndsWith("/")) { requestUrl += "/"; } Regex routeRegex = new Regex(UrlRegexPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline); Match match = routeRegex.Match(requestUrl); if (!match.Success) { return false; } foreach (string item in RouteParameterList) { string value = match.Groups[item].Value.ToLower(); if (string.IsNullOrEmpty(value) && DefaultValueDictionary.ContainsKey(item)) { value = DefaultValueDictionary[item].ToString(); } variables.Add(item, value); } if (!variables.ContainsKey("controller")) { throw new HttpException("从当前路由中没有找到 controller."); } if (!variables.ContainsKey("action")) { throw new HttpException("从当前路由中没有找到 action."); } return true; } }
我们看到 RegexRoute 继承了 Route 类,并重写了它核心方法 Match 方法,用正则表达式来匹配 URL。
13. MvcRouteHandler 探究
还记得 Route 类的构造函数中指定了
this.RouteHandler = new MvcRouteHandler();
吗?this.RouteHandler 的类型是 IRouteHandler 接口,意味着实现了 IRouteHandler 的子类都能赋值给 RouteHandler 属性。而 MVC 中 Route 默认的 RouteHandler(路由处理程序)是 MvcRouteHandler,下面我们按 F12 进去瞧瞧。
/// <summary> /// Mvc 路由处理程序 /// </summary> public class MvcRouteHandler: IRouteHandler { public IHttpHandler GetHttpHandler(RequestContext requestContext) { return new MvcHandler(requestContext); } }
我们看到 MvcRouteHandler 中的 GetHttpHandler 是返回一个 MvcHandler 类。并把 RequestContext(请求的上下文,上面我叫它“大箱子”的类)传给它。下面我们按 F12 进去瞧瞧 MvcHandler 是何方神圣。
14. MvcHandler 探究
进入 MvcHandler 。
/// <summary> /// Mvc 处理程序 /// </summary> public class MvcHandler: IHttpHandler { public bool IsReusable { get{return false;} } public RequestContext RequestContext { get; private set; } public MvcHandler(RequestContext requestContext) { this.RequestContext = requestContext; } public void ProcessRequest(HttpContext context) { // 下面是从当前请求上下文中获取控制器的名称 string controllerName = this.RequestContext.RouteData.Controller; // 下面是得到 MVC 注册的控制器工厂 IControllerFactory controllerFactory = ControllerBuilder.Current.GetControllerFactory(); // 下面是由控制器工厂生产出控制器 IController controller = controllerFactory.CreateController(this.RequestContext, controllerName); if(controller == null) { throw new System.Web.HttpException(string.Format("无法找到名称为 \"{0}\" 的控制器!", controllerName)); } // 执行控制器,以及控制器里面的 Action controller.Execute(this.RequestContext); } }
第一步是从当前请求上下文中获取控制器的名称。第二步调用了 ControllerBuilder.Current.GetControllerFactory(); 我们进去瞧瞧 ControllerBuilder 类。
15. ControllerBuilder 探究
按 F12 进入 ControllerBuilder 类。
public class ControllerBuilder { // 静态字段。该字段在下面的静态构造函数中实例化 public static ControllerBuilder Current { get; private set; } private Func<IControllerFactory> factoryThunk; public HashSet<string> DefaultNamespaces { get; private set; } static ControllerBuilder() { Current = new ControllerBuilder(); } public ControllerBuilder() { this.DefaultNamespaces = new HashSet<string>(); } public IControllerFactory GetControllerFactory() { return factoryThunk(); } public void SetControllerFactory(IControllerFactory controllerFactory) { factoryThunk = () => controllerFactory; } }
我们看到 Current 是一个静态字段,DefaultNamespaces 属性是用来保存要查找的控制器的命名空间集合,可以通过配置获取,也可以通过反射获取。factoryThunk 是一个委托属性,当调用 GetControllerFactory() 方法时,都会调用一下这个委托,而非直接从静态属性中获取,保证每次都要重新执行。可以看到 IControllerFactory 是由 ControllerBuilder 获得的。而 IControllerFactory 则可以有多个实现。而 Artech 老师的 MiniMVC 则是通过在 Application_Start 中注册获得。
protected void Application_Start(object sender, EventArgs e) { // 下面注册我们项目需要匹配的路由规则。ASP.NET Route 在接收到请求后,会把请求的 // URL 和下面我们注册的路由规则相比较(可以理解为正则表达式匹配的原理), 最先 // 匹配的规则(即 Route),就由该 Route 的 RouteHandler 来处理。所以注册路由 // 很关键。 RouteTable.Routes.Add("default_html", new RegexRoute { Url = "{controller}/{action}.html" }); // 注意:RegexRoute 是本人扩展的,目的是替换原先的 Route 的匹配规则,以及增加一些 // 默认值(controller 和 action)的实现。 RouteTable.Routes.Add("default", new RegexRoute { Url = "{controller}/{action}", Defaults = new { controller = "Home", action = "Index" } }); // 下面是设置控制器工厂,MVC 内部仅仅只有一个实现了 IControllerFactory 的工厂 ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory()); // 下面是给控制器工厂添加默认的命名空间,以便 MVC 在找控制器时查询速度会更快。 ControllerBuilder.Current.DefaultNamespaces.Add("WebApp"); }
这里的 ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory()); 就是把 DefaultControllerFactory 类作为 IControllerFactory 接口的实现。
16. DefaultControllerFactory 探究
按 F12 进入 DefaultControllerFactory 类。
/// <summary> /// 默认的控制器工厂。顾名思义,工厂是用来生产物品的, /// 对应在编程中,就是生成控制器的。 /// </summary> public class DefaultControllerFactory : IControllerFactory { private List<Type> controllerTypes = new List<Type>(); public DefaultControllerFactory() { // 下面是在当前应用下所有引用的程序集中找到 IController 的实现类 var allAssemblies = BuildManager.GetReferencedAssemblies(); foreach (Assembly assembly in allAssemblies) { foreach (Type type in assembly.GetTypes().Where(type => typeof(IController).IsAssignableFrom(type))) { controllerTypes.Add(type); } } } public IController CreateController(RequestContext requestContext, string controllerName) { string typeName = controllerName + "Controller"; List<string> namespaces = new List<string>(); namespaces.AddRange(requestContext.RouteData.Namespaces); namespaces.AddRange(ControllerBuilder.Current.DefaultNamespaces); foreach (var ns in namespaces) { string controllerTypeName = string.Format("{0}.{1}", ns, typeName); Type controllerType = controllerTypes.FirstOrDefault(type => string.Compare(type.FullName, controllerTypeName, true) == 0); if (null != controllerType) { return (IController)Activator.CreateInstance(controllerType); } } return null; } }
ControllerFactory 的作用是规范如何查找类,如何去创建 Controller 的实例。
我们看到 DefaultControllerFactory 的构造函数是在当前应用下所有引用的程序集中找到 IController 的实现类,并把这些实现类保存 List 集合中。IController 是什么?我们进去瞧瞧。
17. IController 探究
按 F12 进入 IController 接口。
/// <summary> /// 控制器接口 /// </summary> public interface IController { void Execute(RequestContext requestContext); }
我们看到 IController 就简简单单一个 Execute 方法,参数是 RequestContext 类(请求的上下文,上面我叫它“大箱子”的类)。至于 IController 的实现,是 ControllerBase 类。
18. ControllerBase 探究。
找到 ControllerBase 类。
/// <summary> /// 控制器基类 /// </summary> public abstract class ControllerBase : IController { protected IActionInvoker ActionInvoker { get; set; } public ControllerBase() { this.ActionInvoker = new ControllerActionInvoker(); } public void Execute(RequestContext requestContext) { ControllerContext context = new ControllerContext { RequestContext = requestContext, Controller = this }; string actionName = requestContext.RouteData.ActionName; // 下面是激活 Action,准备开始调用 Action this.ActionInvoker.InvokeAction(context, actionName); } }
我们所有定义的 Controller 都继承自 ControllerBase,这样就避免我们每次创建(新建)一个控制器都要手动地实现 IController,想想就比较麻烦。
19. 回到 DefaultControllerFactory
回到 DefaultControllerFactory 类(注意 CreateController 方法)。
/// <summary> /// 默认的控制器工厂。顾名思义,工厂是用来生产物品的, /// 对应在编程中,就是生成控制器的。 /// </summary> public class DefaultControllerFactory : IControllerFactory { private List<Type> controllerTypes = new List<Type>(); public DefaultControllerFactory() { // 下面是在当前应用下所有引用的程序集中找到 IController 的实现类 var allAssemblies = BuildManager.GetReferencedAssemblies(); foreach (Assembly assembly in allAssemblies) { foreach (Type type in assembly.GetTypes().Where(type => typeof(IController).IsAssignableFrom(type))) { controllerTypes.Add(type); } } } public IController CreateController(RequestContext requestContext, string controllerName) { string typeName = controllerName + "Controller"; List<string> namespaces = new List<string>(); namespaces.AddRange(requestContext.RouteData.Namespaces); // 从当前匹配的路由数据中得到命名空间(因为如果应用程序启动时给路由配置了命名空间) namespaces.AddRange(ControllerBuilder.Current.DefaultNamespaces); // 系统默认的命名空间中找 foreach (var ns in namespaces) { string controllerTypeName = string.Format("{0}.{1}", ns, typeName); Type controllerType = controllerTypes.FirstOrDefault(type => string.Compare(type.FullName, controllerTypeName, true) == 0); if (null != controllerType) { return (IController)Activator.CreateInstance(controllerType); } } return null; } }
我们看到 MVC 默认会从当前 Web 程序集和被当前匹配的路由数据中得到命名空间(因为如果应用程序启动时给路由配置了命名空间),如果找到了,就创建实例,没有找到就返回 NULL。
20. 回到 MvcHandler
再次回到 MvcHandler 类。
public void ProcessRequest(HttpContext context) { // 下面是从当前请求上下文中获取控制器的名称 string controllerName = this.RequestContext.RouteData.Controller; // 下面是得到 MVC 注册的控制器工厂 IControllerFactory controllerFactory = ControllerBuilder.Current.GetControllerFactory(); // 下面是由控制器工厂生产出控制器 IController controller = controllerFactory.CreateController(this.RequestContext, controllerName); if(controller == null) { throw new System.Web.HttpException(string.Format("无法找到名称为 \"{0}\" 的控制器!", controllerName)); } // 执行控制器,以及控制器里面的 Action controller.Execute(this.RequestContext); }
由于 Application_Start 中设置了
ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory());
那么 MvcHandler 中得到 IControllerFactory 的一段代码可以等于
IControllerFactory controllerFactory = new DefaultControllerFactory();
对于下一行代码,controllerFactory.CreateController 我们已经知道它是根据控制器名称从命名空间中找到相应的控制器,然后创建实例,如果没有找到,则返回 NULL。
最后一段代码,判断找到的控制器实例是否为 NULL,如果为 NULL,就抛出一个异常,提示 Web 访问者有错误。如果不为 NULL,就调用 Execute 方法。
21. 回到 ControllerBase
由于我们自定义的 Controller(比如:HomeController)要继承 ControllerBase,当 IController.Execute 时,其实当前程序会去执行 ControllerBase.Execute 方法。
/// <summary> /// 控制器基类 /// </summary> public abstract class ControllerBase: IController { protected IActionInvoker ActionInvoker { get; set; } public ControllerBase() { this.ActionInvoker = new ControllerActionInvoker(); } public void Execute(RequestContext requestContext) { ControllerContext context = new ControllerContext { RequestContext = requestContext, Controller = this }; string actionName = requestContext.RouteData.ActionName; // 下面是激活 Action,准备开始调用 Action this.ActionInvoker.InvokeAction(context, actionName); } }
我们看到第一步是实例化一个 ControllerContext 类,ControllerContext 类是什么呢?它其实和 RequestContext 类似,也是一个上下文类,用来传递封装的信息,好比一个比 RequestContext 还要大的“箱子”。
第二步是从路由数据中得到 Action 的名称,然后用 IActionInvoker 来调用。IActionInvoker 是什么?IActionInvoker 接口的默认实现类是 ControllerActionInvoker,那 ControllerActionInvoker 又是什么,我们进去瞧瞧。
22. IActionInvoker 探究
按 F12 进入 IActionInvoker 接口。
/// <summary> /// 定义调用 Action 的规则,可以理解为 Action 调用器的接口 /// </summary> public interface IActionInvoker { void InvokeAction(ControllerContext controllerContext, string actionName); }
我们看到 IActionInvoker 接口就定义了一个方法,就可以用来“调用”Action 方法。
23. ControllerActionInvoker 探究
按 F12 进入 ControllerActionInvoker 类。
/// <summary> /// 默认的 Action 调用器的实现 /// </summary> public class ControllerActionInvoker : IActionInvoker { /// <summary> /// 模型绑定实现 /// </summary> public IModelBinder ModelBinder { get; private set; } public ControllerActionInvoker() { this.ModelBinder = new DefaultModelBinder(); } public void InvokeAction(ControllerContext controllerContext, string actionName) { // 下面是根据当前路由中的 Action 名字,反射当前获取的 Controller 的类型,并找到 Action Method MethodInfo method = controllerContext.Controller.GetType().GetMethods().First(m => string.Compare(actionName, m.Name, true) == 0); List<object> parameters = new List<object>(); foreach (ParameterInfo parameter in method.GetParameters()) { parameters.Add(this.ModelBinder.BindModel(controllerContext, parameter.Name, parameter.ParameterType)); } ActionResult actionResult = method.Invoke(controllerContext.Controller, parameters.ToArray()) as ActionResult; if (actionResult != null) { actionResult.ExecuteResult(controllerContext); } } }
我们看到 InvokeAction 方法的第一步是反射刚刚创建完的 Controller 实例的所有公共方法(其实 MVC 内部远比 MiniMVC 要复杂,为了讲解,Artech 老师简化了。)取出名字为路由中的 ActionName 的方法。第二步则是反射这个 Action 方法,获取它所有的参数,然后把 HttpContext 中获取的 QueryString 集合、Forms 集合、Cookies 集合等等客户端提交的信息集合一同交给模型绑定 IModelBinder。那 IModelBinder 是什么?它的默认实现类 DefaultModelBinder 又是什么呢?我们进去瞧瞧。
24. IModelBinder 探究
按 F12 进入 IModelBinder 接口。
/// <summary> /// 模型绑定规则,当准备开始调用 Action 时, /// 就会触发,即在 ControllerActionInvoker 中。 /// </summary> public interface IModelBinder { object BindModel(ControllerContext controllerContext, string modelName, Type modelType); }
我们看到 IModelBinder 接口就定义了一个方法,就可以用来“绑定”模型给 Action 方法。
25. DefaultModelBinder 探究
按 F12 进入 DefaultModelBinder 类。
/// <summary> /// 默认的模型绑定规则 /// </summary> public class DefaultModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, string modelName, Type modelType) { if (modelType.IsValueType || typeof(string) == modelType) { // 这里是值类型或者 String 类型的绑定规则 object instance; if (GetValueTypeInstance(controllerContext, modelName, modelType, out instance)) { return instance; }; return Activator.CreateInstance(modelType); } object modelInstance = Activator.CreateInstance(modelType); // 复杂类型 foreach (PropertyInfo property in modelType.GetProperties()) { if (!property.CanWrite || (!property.PropertyType.IsValueType && property.PropertyType!= typeof(string))) { continue; } object propertyValue; if (GetValueTypeInstance(controllerContext, property.Name, property.PropertyType, out propertyValue)) { property.SetValue(modelInstance, propertyValue, null); } } return modelInstance; } /// <summary> /// 得到类型的实例 /// </summary> /// <param name="controllerContext"></param> /// <param name="modelName"></param> /// <param name="modelType"></param> /// <param name="value"></param> /// <returns></returns> private bool GetValueTypeInstance(ControllerContext controllerContext, string modelName, Type modelType, out object value) { var form = HttpContext.Current.Request.Form; string key; if (null != form) { key = form.AllKeys.FirstOrDefault(k => string.Compare(k, modelName, true) == 0); if (key != null) { value = Convert.ChangeType(form[key], modelType); return true; } } key = controllerContext.RequestContext.RouteData.Values .Where(item => string.Compare(item.Key, modelName, true) == 0) .Select(item => item.Key).FirstOrDefault(); if (null != key) { value = Convert.ChangeType(controllerContext.RequestContext.RouteData.Values[key], modelType); return true; } key = controllerContext.RequestContext.RouteData.DataTokens .Where(item => string.Compare(item.Key, modelName, true) == 0) .Select(item => item.Key).FirstOrDefault(); if (null != key) { value = Convert.ChangeType(controllerContext.RequestContext.RouteData.DataTokens[key], modelType); return true; } value = null; return false; } }
我们看到 BindModel 方法内部是先判断是否是值类型或 String 类型,还是复杂类型(一般只类)。然后依次把 HttpContext 中获取的 QueryString 集合、Forms 集合、Cookies 集合等等客户端提交的信息集合筛选出 Key-Value ,最后创建实例,赋值。当然其实 MVC 内部的模型绑定比这里复杂的多,这里只是演示,感兴趣的朋友可以去研究 MVC 的源代码,这里暂时跳过。
26. 回到 ControllerActionInvoker
回到 ControllerActionInvoker 类。(注意 InvokeAction 方法)
public void InvokeAction(ControllerContext controllerContext, string actionName) { // 下面是根据当前路由中的 Action 名字,反射当前获取的 Controller 的类型,并找到 Action Method MethodInfo method = controllerContext.Controller.GetType().GetMethods().First(m => string.Compare(actionName, m.Name, true) == 0); List<object> parameters = new List<object>(); foreach (ParameterInfo parameter in method.GetParameters()) { parameters.Add(this.ModelBinder.BindModel(controllerContext, parameter.Name, parameter.ParameterType)); } ActionResult actionResult = method.Invoke(controllerContext.Controller, parameters.ToArray()) as ActionResult; if (actionResult != null) { actionResult.ExecuteResult(controllerContext); } }
我们注意这一段代码:
ActionResult actionResult = method.Invoke(controllerContext.Controller, parameters.ToArray()) as ActionResult;
这一步就是反射调用找到的 Action 方法,并尝试着把返回值转换成 ActionResult。如果返回值是 ActionResult,则调用它的 ExecuteResult 方法,否则什么都不做。ActionResult 是什么?我们进去瞧瞧。
27. ActionResult 探究
按 F12 进入 ActionResult 类。
/// <summary> /// 抽象的 Action 返回的结果 /// </summary> public abstract class ActionResult { public abstract void ExecuteResult(ControllerContext context); }
我们看到,它是一个抽象类,定义了一个抽象方法 ExecuteResult。联想到 MVC ,我们仿佛明白了 ViewResult、FileResult、FileStreamResult、JsonResult、JavascriptResult 等等继承了 ActionResult 的子类。
28. RawContentResult 探究
找到 RawContentResult 类。
/// <summary> /// 原样输出结果 /// </summary> public class RawContentResult : ActionResult { public string RawData { get; private set; } public RawContentResult(string rawData) { RawData = rawData; } public override void ExecuteResult(ControllerContext context) { context.RequestContext.HttpContext.Response.Write(this.RawData); } } /// <summary> /// 原样输出结果 /// </summary> public class RawContentResult : ActionResult { public string RawData { get; private set; } public RawContentResult(string rawData) { RawData = rawData; } public override void ExecuteResult(ControllerContext context) { context.RequestContext.HttpContext.Response.Write(this.RawData); } }
我们看到 RawContentResult 类的构造函数接收一个字符串,然后执行 ExecuteResult 时直接 Response.Write 它,还真够简单的,呵呵。同样我们可以任意扩展 ActionResult,来完成我们自己的特定功能,比如 XmlResult、JsonpResult 等等。
29. 运行效果
运行效果截图:
30. 最后
至此, Artech 老师的 MiniMVC 已经全部介绍完了,感觉自己收获了很多,也希望读者您也能有所提高,谢谢!
最后,再次感谢 Artech 老师!
我优化后的 MiniMVC 下载:点击这里
谢谢浏览!