ASP.NET 学习小记 -- “迷你”MVC实现(2)

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 框架揭秘》

ASP.NET 学习小记 -- “迷你”MVC实现(2),古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。