Controller的激活
ASP.NET MVC的URL路由系统通过注册的路由表对HTTO请求进行解析从而得到一个用户封装路由数据的RouteData对象,而这个过程是通过自定义的UrlRoutingModule对HttpApplication的PostResolveRequestCache事件进行注册实现的。RouteData中已经包含了目标Controller的名称,现在我们需要根据该名称激活对应的Controller对象。
MvcRouteHandler
对于这个“迷你版”的MVC框架来说,MvcRouteHandler是一个具有如下定义的类型,在实现的GetHttpHandler方法中,它会直接返回一个MvcHandler对象。
public class MvcRouteHandler:IRouteHandler { public System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext) { return new MvcHandler(requestContext); } }
MvcHandler
我们知道,ASP.NET MVC 框架是通过自定义HttpModule和HttpHandler对ASP.NET进行扩展实现的。在上一篇博客中,我们实现了自定义的HttpModule,即UrlRoutingModule。MvcHandler则是对HttpHandler进行自定义实现。
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; IControllerFactory controllerFactory = ControllerBuilder.Current.GetControllerFactory(); IController controller = controllerFactory.CreateController(this.RequestContext, controllerName); controller.Execute(this.RequestContext); } }
Controller与ControllerFactory
定义一个接口IController,该接口具有一个方法Execute,该方法表示对Controller的执行,传入参数表示当前请求的上下文RequestContext对象。
public interface IController { void Execute(RequestContext requestContext); }
从MvcHandler的定义可以看到Controller对象的激活是通过工厂模式实现的。
public interface IControllerFactory { IController CreateController(RequestContext requestContext, string controllerName); }
实现ControllerBuilder,用于对ControllerFactory的注册和获取。
public class ControllerBuilder { private Func<IControllerFactory> factoryThunk; public static ControllerBuilder Current { get; private set; } static ControllerBuilder() { Current = new ControllerBuilder(); } public IControllerFactory GetControllerFactory() { return factoryThunk(); } public void SetControllerFactory(IControllerFactory controllerFactory) { factoryThunk = () => controllerFactory; } }
接下来,我们需要创建控制器工厂。
public class DefaultControllerFactory : IControllerFactory { private static List<Type> controllerTypes = new List<Type>(); static DefaultControllerFactory() { foreach (Assembly assembly in BuildManager.GetReferencedAssemblies()) { 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"; Type controllerType = controllerTypes.FirstOrDefault(c => string.Compare(typeName, c.Name, true) == 0); if (controllerType == null) { return null; } return (IController)Activator.CreateInstance(controllerType); } }
通过实现Icontroller接口,我们为所有的Controller定义一个ControllerBase基类
public 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; this.ActionInvoker.InvokeAction(context, actionName); } }
Action的执行
作为Controller基类ControllerBase的Execute方法的核心在于对Action方法本身的执行和作为方法返回的ActionResult的执行。两者的执行是通过一个叫做ActionInvoker的组件来完成的。
ActionInvoker
定义接口IActionInvoker,该接口存在方法InvokerAction用于执行指定名称的Action方法。
public interface IActionInvoker { void InvokeAction(ControllerContext controllerContext, string actionName); }
public class ControllerContext { public ControllerBase Controller { get; set; } public RequestContext RequestContext { get; set; } }
实现具体的ActionInvoker
public class ControllerActionInvoker : IActionInvoker { public IModelBinder ModelBinder { get; private set; } public ControllerActionInvoker() { this.ModelBinder = new DefaultModelBinder(); } public void InvokeAction(ControllerContext controllerContext, string actionName) { 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; actionResult.ExecuteResult(controllerContext); } }
InvokerAction方法的目的在于实现针对Action方法的执行。由于Action方法具有相应的参数,在执行Action方法之前必须进行参数的绑定。ASP.NET MVC 将这个机制称之为Model的绑定。
ModelBinder
定义IModelBinder接口
public interface IModelBinder { object BindModel(ControllerContext controllerContext, string modelName, Type modelType); }
实现ModelBinder,对于简单的值类型,我们可以根据参数名称和Key进行匹配,对于复杂类型,则通过反射根据类型创建新的对象,并根据属性名称与Key的匹配关系对相应的属性进行赋值。
public class DefaultModelBinder:IModelBinder { public object BindModel(ControllerContext controllerContext, string modelName, Type modelType) { if (modelType.IsValueType || typeof(string) == modelType) { object instance; if (GetVauleTypeInstance(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 (GetVauleTypeInstance(controllerContext, property.Name, property.PropertyType, out propertyValue)) { property.SetValue(modelInstance, propertyValue, null); } } return modelInstance; } private bool GetVauleTypeInstance(ControllerContext controllerContext, string modelName, Type modelType, out object value) { var form = HttpContext.Current.Request.Form; string key; if (form != null) { 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 (key != null) { 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 (key != null) { value = Convert.ChangeType(controllerContext.RequestContext.RouteData.DataTokens[key], modelType); return true; } value = null; return false; } }
ActionResult
最后,我们为具体的ActionResult定义一个ActionResult抽象基类,以实现我们对请求的最终响应。
public abstract class ActionResult { public abstract void ExecuteResult(ControllerContext context); }
实现RawContentResult
public class RawContentResult : ActionResult { public string RawData { get; private set; } public RawContentResult(string rawData) { this.RawData = rawData; } public override void ExecuteResult(ControllerContext context) { context.RequestContext.HttpContext.Response.Write(this.RawData); } }
测试
创建一个空的ASP.NET Web应用(不是ASP.ENT MVC 应用),引用我们的“迷你版”MVC组件。
定义一个如下SimpleModel类型,它表示最终需要绑定到View上的数据。为了验证针对Controller和Action的解析机制,SimpleModel定义的两个属性分别表示当前请求的目标Controller和Action。
public class SimpleModel { public string Controller { get; set; } public string Action { get; set; } }
定义一个Controller类,继承自ControllerBase。
public class HomeController : ControllerBase { public ActionResult Index(SimpleModel model) { string content = string.Format("Controller:{0}<br/>Action:{1}", model.Controller, model.Action); return new RawContentResult(content); } }
在Global中注册路由模板和Controller工厂。
public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { RouteTable.Routes.Add("default", new Route { Url = "{controller}/{action}" }); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory()); } }
在web.config中对自定义的HttpModule进行注册。
<configuration> <system.web> <compilation debug="true" targetFramework="4.0" /> </system.web> <system.webServer> <modules runAllManagedModulesForAllRequests="true"> <remove name="FormsAuthenticationModule" /> <add name="UrlRoutingModule" type="MiniMVC.MVC.UrlRoutingModule,MiniMVC.MVC"/> </modules> </system.webServer> </configuration>
运行示例网站
本学习内容和代码来自《ASP.NET MVC 4 框架揭秘》