mvc实现原理,了解.net MVC的实现原理

通过Reflector反编译,我们对IIS分发请求至w3wp.exe进程之后交由HttpRuntime处理过程的分析了解HttpApplication,HttpModule,HttpHandler,HttpContext的生成机制。那我们继续来了解.net MVC 路由的如何实现URL跳转到指定的Controller/Action过程。
  • UrlRoutingModule
  • MvcRoutingHandler
  • MvcHandler
  • Controller
一、UrlRoutingModule
当所有HttpModule被HttpApplication首次加载后,每个HttpModule会在自己的Init方法中注册HttpApplication事件实现对HttpRequest请求的拦截。当然UrlRoutingModule也不例外。我们反编译一下UrlRoutingModule源码
了解.net MVC的实现原理mvc实现原理了解.net MVC的实现原理mvc实现原理View Code [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level=AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.LinkDemand, Level=AspNetHostingPermissionLevel.Minimal)] public class UrlRoutingModule : IHttpModule { // Fields private static readonly object _requestDataKey = new object(); private RouteCollection _routeCollection; // Methods protected virtual void Dispose() { } protected virtual void Init(HttpApplication application) { application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache); application.PostMapRequestHandler += new EventHandler(this.OnApplicationPostMapRequestHandler); } private void _disibledevent=>object sender, EventArgs e) { HttpContextBase context = new HttpContextWrapper(((HttpApplication) sender).Context); this.PostMapRequestHandler(context); } private void _disibledevent=>object sender, EventArgs e) { HttpContextBase context = new HttpContextWrapper(((HttpApplication) sender).Context); this.PostResolveRequestCache(context); } public virtual void PostMapRequestHandler(HttpContextBase context) { RequestData data = (RequestData) context.Items[_requestDataKey]; if (data != null) { context.RewritePath(data.OriginalPath); context.Handler = data.HttpHandler; } } public virtual void PostResolveRequestCache(HttpContextBase context) { RouteData routeData = this.RouteCollection.GetRouteData(context); if (routeData != null) { IRouteHandler routeHandler = routeData.RouteHandler; if (routeHandler == null) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, RoutingResources.UrlRoutingModule_NoRouteHandler, new object[0])); } if (!(routeHandler is StopRoutingHandler)) { RequestContext requestContext = new RequestContext(context, routeData); IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); if (httpHandler == null) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, RoutingResources.UrlRoutingModule_NoHttpHandler, new object[] { routeHandler.GetType() })); } RequestData data2 = new RequestData(); data2.OriginalPath = context.Request.Path; data2.HttpHandler = httpHandler; context.Items[_requestDataKey] = data2; context.RewritePath("~/UrlRouting.axd"); } } } void IHttpModule.Dispose() { this.Dispose(); } void IHttpModule.Init(HttpApplication application) { this.Init(application); } // Properties public RouteCollection RouteCollection { get { if (this._routeCollection == null) { this._routeCollection = RouteTable.Routes; } return this._routeCollection; } set { this._routeCollection = value; } } // Nested Types private class RequestData { // Fields [CompilerGenerated] private IHttpHandler k__BackingField; [CompilerGenerated] private string k__BackingField; // Properties public IHttpHandler HttpHandler { [CompilerGenerated] get { return this.k__BackingField; } [CompilerGenerated] set { this.k__BackingField = value; } } public string OriginalPath { [CompilerGenerated] get { return this.k__BackingField; } [CompilerGenerated] set { this.k__BackingField = value; } } } }

可以看到UrlRoutingModule订阅的两个 HttpApplication 事件。其中PostResolveRequestCache 要比 PostMapRequestHandler 先执行,先看PostResolveRequestCache 事件,他首先从首先从 RouteCollection 中获取一个 RouteData 对象。而RouteCollection是通过路由表创建的
// Properties public RouteCollection RouteCollection { get { if (this._routeCollection == null) { this._routeCollection = RouteTable.Routes; } return this._routeCollection; } set { this._routeCollection = value; } }
我们在Global.asax.cs注册路由的时候(即HttpApplication),就已经往RouteTable.Routes添加了新的路由信息了,因此HttpModule可以从自己的RouteCollection查找到对应请求的路由。RouteTable只有一个静态集合属性RouteCollection。
View Code public class MvcApplication : System.Web.HttpApplication { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults ); } protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); } }

UrlRoutingModule 在 PostResolveRequestCache方法读取 RouteData routeData = this.RouteCollection.GetRouteData(context);
RouteData是包含了我们注册的每个Route的信息。在global中注册路由的时候,MapRote是RouteCollection扩展方法,默认使用了MvcRouteHandler。
public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) { if (routes == null) { throw new ArgumentNullException("routes"); } if (url == null) { throw new ArgumentNullException("url"); } Route route2 = new Route(url, new MvcRouteHandler()); route2.Defaults = new RouteValueDictionary(defaults); route2.Constraints = new RouteValueDictionary(constraints); route2.DataTokens = new RouteValueDictionary(); Route item = route2; if ((namespaces != null) && (namespaces.Length > 0)) { item.DataTokens["Namespaces"] = namespaces; } routes.Add(name, item); return item; }
二、MvcRoutingHandler
RouteData结构除了包含的Route信息还有默认的IRouteHandler即MvcRouteHandler。
了解.net MVC的实现原理mvc实现原理
在PostResolveRequestCache根据上下文比对查找到路由表中对应的RouteData后,并通过routeHandler.GetHttpHandler(requestContext)获得MvcHandler的实例。
View Code public class MvcRouteHandler : IRouteHandler { // Methods protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) { return new MvcHandler(requestContext); } IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) { return this.GetHttpHandler(requestContext); } }

三、 MvcHandler
从上面的过程可以看到UrlRouteModule每次拦截HttpApplication管线请求,先从RouteCollection找到与HttpContext请求对应的RouteData(包含MvcRouteHandler),并将HttpContextBase与RouteData封装为RequestContext,通过构造注入MvcHandler之中。MvcRouteHandler其实只起了一个过渡的作用,真正的工作是HttpApplication管线主导理,在UrlRouteModule与MvcHandler之间进行的。
在UrlRouteModule在 PostResolveRequestCache方法通过MvcRouteModule获取到MvcHandler的实例后,
RequestData data2 = new RequestData(); data2.OriginalPath = context.Request.Path; data2.HttpHandler = httpHandler; context.Items[_requestDataKey] = data2; context.RewritePath("~/UrlRouting.axd");
MvcHandler 接管默认的WebForm的HttpHandler注入到RequestData中并装入context.Items中,而这个context对象之前也一并封装RequestContext注入到了MvcHandler中。这个RequestData是UrlRouteModule的内部私有类,所以重载的时候没法实现它,如果想重写UrlRouteModule就要自己实现这个类了。
至此,了解从UrlRouteModule到MvcRouteHandler再到MvcHandler的创建过程,但是最重要的问题MvcHandler是如何工作的?UrlRouteModule在拦截了HttpApplication的管线请求后,根据路由表找到了对由的RouteData(路由信息与MvcHandler),替换掉始终贯穿整个过程HttpContext中处理请求的HttpHandler,即MvcHandler。这些工作完成后,HttpApplication管线要继续处理Step事件,最终调用了MvcHandler 的ProcessRequest或BeginProcessRequest方法,从而进入到Controller的过程中。
MvcHandler的触发过程:->HttpRuntime获取到HttpApplication->加载HttpModule,生成StepManager管线并加载MvcHandler->HttpRuntime调用HttpApplication.BeginProcessRequest->HttpApplication.StepManager.ResumeSteps
->执行StepManager中的HttpApplication.StepManager._execSteps[i].Execute ->MvcHandlers.ProcessRequest或BeginProcessRequest
下面是MvcHandler的反编译源码,可以看到除了要继承IHttpHandler,还继承了IRequireSeesionState 。自定义的HttpHandler想处理Session的话必须继承IRequireSeesionState。
View Code public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState { // Fields private ControllerBuilder _controllerBuilder; private static readonly object _processRequestTag = new object(); [CompilerGenerated] private static bool k__BackingField; [CompilerGenerated] private RequestContext k__BackingField; internal static readonly string MvcVersion = GetMvcVersionString(); public static readonly string MvcVersionHeaderName = "X-AspNetMvc-Version"; // Methods public MvcHandler(RequestContext requestContext) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } this.RequestContext = requestContext; } protected internal virtual void AddVersionHeader(HttpContextBase httpContext) { if (!DisableMvcResponseHeader) { httpContext.Response.AppendHeader(MvcVersionHeaderName, MvcVersion); } } protected virtual IAsyncResult BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, object state) { HttpContextBase base2 = new HttpContextWrapper(httpContext); return this.BeginProcessRequest(base2, callback, state); } protected internal virtual IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state) { BeginInvokeDelegate delegate4 = null; EndInvokeDelegate delegate5 = null; Action action2 = null; IController controller; IControllerFactory factory; this.ProcessRequestInit(httpContext, out controller, out factory); IAsyncController asyncController = controller as IAsyncController; if (asyncController != null) { if (delegate4 == null) { delegate4 = delegate (AsyncCallback asyncCallback, object asyncState) { IAsyncResult result; try { result = asyncController.BeginExecute(this.RequestContext, asyncCallback, asyncState); } catch { factory.ReleaseController(asyncController); throw; } return result; }; } BeginInvokeDelegate beginDelegate = delegate4; if (delegate5 == null) { delegate5 = delegate (IAsyncResult asyncResult) { try { asyncController.EndExecute(asyncResult); } finally { factory.ReleaseController(asyncController); } }; } EndInvokeDelegate endDelegate = delegate5; SynchronizationContext synchronizationContext = SynchronizationContextUtil.GetSynchronizationContext(); return AsyncResultWrapper.Begin(AsyncUtil.WrapCallbackForSynchronizedExecution(callback, synchronizationContext), state, beginDelegate, endDelegate, _processRequestTag); } if (action2 == null) { action2 = delegate { try { controller.Execute(this.RequestContext); } finally { factory.ReleaseController(controller); } }; } Action action = action2; return AsyncResultWrapper.BeginSynchronous(callback, state, action, _processRequestTag); } protected internal virtual void EndProcessRequest(IAsyncResult asyncResult) { AsyncResultWrapper.End(asyncResult, _processRequestTag); } private static string GetMvcVersionString() { return new AssemblyName(typeof(MvcHandler).Assembly.FullName).Version.ToString(2); } protected virtual void ProcessRequest(HttpContext httpContext) { HttpContextBase base2 = new HttpContextWrapper(httpContext); this.ProcessRequest(base2); } protected internal virtual void ProcessRequest(HttpContextBase httpContext) { IController controller; IControllerFactory factory; this.ProcessRequestInit(httpContext, out controller, out factory); try { controller.Execute(this.RequestContext); } finally { factory.ReleaseController(controller); } } private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) { this.AddVersionHeader(httpContext); this.RemoveOptionalRoutingParameters(); string requiredString = this.RequestContext.RouteData.GetRequiredString("controller"); factory = this.ControllerBuilder.GetControllerFactory(); controller = factory.CreateController(this.RequestContext, requiredString); if (controller == null) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, MvcResources.ControllerBuilder_FactoryReturnedNull, new object[] { factory.GetType(), requiredString })); } } private void RemoveOptionalRoutingParameters() { RouteValueDictionary values = this.RequestContext.RouteData.Values; foreach (string str in values.Where>(delegate (KeyValuePair entry) { return (entry.Value == UrlParameter.Optional); }).Select, string>(delegate (KeyValuePair entry) { return entry.Key; }).ToArray()) { values.Remove(str); } } IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData) { return this.BeginProcessRequest(context, cb, extraData); } void IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) { this.EndProcessRequest(result); } void IHttpHandler.ProcessRequest(HttpContext httpContext) { this.ProcessRequest(httpContext); } // Properties internal ControllerBuilder ControllerBuilder { get { if (this._controllerBuilder == null) { this._controllerBuilder = ControllerBuilder.Current; } return this._controllerBuilder; } set { this._controllerBuilder = value; } } public static bool DisableMvcResponseHeader { [CompilerGenerated] get { return k__BackingField; } [CompilerGenerated] set { k__BackingField = value; } } protected virtual bool IsReusable { get { return false; } } public RequestContext RequestContext { [CompilerGenerated] get { return this.k__BackingField; } [CompilerGenerated] private set { this.k__BackingField = value; } } bool IHttpHandler.IsReusable { get { return this.IsReusable; } } }

四、Controller登场
看下MvcHandler的ProcessRequest或BeginProcessRequest, 我们看一下ProcessRequest方法的细节
protected internal virtual void ProcessRequest(HttpContextBase httpContext) { IController controller; IControllerFactory factory; this.ProcessRequestInit(httpContext, out controller, out factory); try { controller.Execute(this.RequestContext); } finally { factory.ReleaseController(controller); } } private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) { this.AddVersionHeader(httpContext); this.RemoveOptionalRoutingParameters(); string requiredString = this.RequestContext.RouteData.GetRequiredString("controller"); factory = this.ControllerBuilder.GetControllerFactory(); controller = factory.CreateController(this.RequestContext, requiredString); if (controller == null) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, MvcResources.ControllerBuilder_FactoryReturnedNull, new object[] { factory.GetType(), requiredString })); } }
主要是通过ProcessRequestInit 获取IControllerFactory 工厂,工厂再通过上下文中的地址反射Controller实例。那这个工厂哪里来的呢?
factory = this.ControllerBuilder.GetControllerFactory(); 看一下这个ControllerBuilder 是不是很熟悉? 看看Global.asax.cs的代码
View Code using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; using System.Threading; namespace MvcApplication { // Note: For instructions _disibledevent=>// visit http://go.microsoft.com/?LinkId=9394801 public class MvcApplication : System.Web.HttpApplication { #region public static void RegisterRoutes(RouteCollection routes) { //routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults ); } #endregion protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); //DefaultControllerFactory 每次都是根据上下文中的Action字符串反射Controller实例 //可以重写DefaultControllerFactory 利用IOC容器实现Controller实例化工作(提高效率) ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory()); //Controller的反射工厂 } } }

一般大家会在这个时候把ControllerFactory注入,即使用默认的DefaultControllerFactory或自己利用IOC容器实现新的ControllerFactory工厂。 在ControllerFactory获取Controller并执行了 controller.Execute(this.RequestContext),就进入了Controller的具体业务了。
IController接口只有一个方法,ControllerBase继承IController,Controller又继承实现ControllerBase,而我们的具体业务的Controller名都是以文件夹名+Controller,并且继承了Controller。
public class AccountController : Controller public class HomeController : Controller
如果想实现一现共用的业务比如:验证用户,验证权限等。我们可以再实现一个基类继承MyControllerBase,然后其它的XXXController都继承这个MyControllerBase。
当然MVC提供了Filter机制(类似AOP拦截)也可以用来实现这些基本的业务验证。是不是比WebForm方便多了?
可以看到Execute方法只在ControllerBase中实现了,而Controller是直接继承的,我们的Controller也是直接继承的Controller。
View Code protected virtual void Execute(RequestContext requestContext) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } this.VerifyExecuteCalledOnce(); this.Initialize(requestContext); this.ExecuteCore(); } protected virtual void Initialize(RequestContext requestContext) { this.ControllerContext = new ControllerContext(requestContext, this); } protected abstract void ExecuteCore();

Execute主要的业务在放在了ExecuteCore中了,而这个ExecuteCore没有任何代码,只是在Controller中又被重载了业务。(看来ExecuteCore的业务还是被下放到了Controller中了,而Initalize只是RequestContext封装成ControllerContext,微软很有意思每一个环节都重新封装个上下文)。
在看Controller的ExecuteCore的业务
protected override void ExecuteCore() { this.PossiblyLoadTempData(); try { string requiredString = this.RouteData.GetRequiredString("action"); if (!this.ActionInvoker.InvokeAction(base.ControllerContext, requiredString)) { this.HandleUnknownAction(requiredString); } } finally { this.PossiblySaveTempData(); } } public IActionInvoker ActionInvoker { get { if (this._actionInvoker == null) { this._actionInvoker = this.CreateActionInvoker(); } return this._actionInvoker; } set { this._actionInvoker = value; } } protected virtual IActionInvoker CreateActionInvoker() { return new ControllerActionInvoker(); }
ExectueCore调用this.ActionInvoker.InvokeAction(base.ControllerContext, requiredString)) 反射执行的Action。requiredString参数为Action的名字
那这个Action被反射执行前后还有其它的业务吗? 记得我们在.net mvc中看到每个Action方法或者Controller基类被贴了一些属性标签,Action执行前后又触发了哪些方面呢?
· Action执行前 Controller的OnActionExecuting先触发
· Action执行后 Controller的OnActionExecuted被触发
· Action执行前后 贴有继承ActionFilterAttribute属性标签中的OnActionExecuting,OnActionExecuted被执行(还有其的Attribute如权限)
也就说ControllerActionInvoker 的InvokeAction方法在执行前还对Action和Controller进行了属性和Filter继承的扫描,来看InvokeAction的具体内容

View Code public virtual bool InvokeAction(ControllerContext controllerContext, string actionName) { if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); } if (string.IsNullOrEmpty(actionName)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName"); } ControllerDescriptor controllerDescriptor = this.GetControllerDescriptor(controllerContext); ActionDescriptor actionDescriptor = this.FindAction(controllerContext, controllerDescriptor, actionName); if (actionDescriptor == null) { return false; } FilterInfo filters = this.GetFilters(controllerContext, actionDescriptor); try { AuthorizationContext context = this.InvokeAuthorizationFilters(controllerContext, filters.AuthorizationFilters, actionDescriptor); if (context.Result != null) { this.InvokeActionResult(controllerContext, context.Result); } else { if (controllerContext.Controller.ValidateRequest) { ValidateRequest(controllerContext); } IDictionary parameterValues = this.GetParameterValues(controllerContext, actionDescriptor); ActionExecutedContext context2 = this.InvokeActionMethodWithFilters(controllerContext, filters.ActionFilters, actionDescriptor, parameterValues); this.InvokeActionResultWithFilters(controllerContext, filters.ResultFilters, context2.Result); } } catch (ThreadAbortException) { throw; } catch (Exception exception) { ExceptionContext context3 = this.InvokeExceptionFilters(controllerContext, filters.ExceptionFilters, exception); if (!context3.ExceptionHandled) { throw; } this.InvokeActionResult(controllerContext, context3.Result); } return true; }

ControllerDescriptor controllerDescriptor = this.GetControllerDescriptor(controllerContext); ActionDescriptor actionDescriptor = this.FindAction(controllerContext, controllerDescriptor, actionName); 上面两段代码是读取Controller和Action的所有描述信息包括标签属性
FilterInfo filters = this.GetFilters(controllerContext, actionDescriptor); 这段代码是反射前面读到的所有具体的属性
ActionExecutedContext context2 = this.InvokeActionMethodWithFilters(controllerContext, filters.ActionFilters, actionDescriptor, parameterValues);
通过InvokeActionMethodWithFilters 先执行Action前置拦截的方法,然后Action,接着后置拦截的方法。 是不是实现了AOP的部分功能了?当然它没做到AOP的环绕拦截功能,但相比WebForm却是进步了很多。
其实上面的InvokeAction具体怎么实现前后拦截我也没看出来,我是通过实现了两个类的重写跟进断点查看的。
五、重写Controller与ControllerActionInvoker
下面是重写的代码
View Code using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.IO; namespace MvcApplication.Module { public class MyController:Controller { public MyController() { //this.ActionInvoker = new MyControllerActionInvoker(); } protected override void _disibledevent=>base.OnActionExecuting(filterContext); } protected override void _disibledevent=>base.OnActionExecuted(filterContext); } /// /// 具本的Action及前后拦截方法的调用者 /// /// protected override IActionInvoker CreateActionInvoker() { return new MyControllerActionInvoker(); } } public class MyControllerActionInvoker : ControllerActionInvoker { public override bool InvokeAction(System.Web.Mvc.ControllerContext controllerContext, string actionName) { if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); } if (string.IsNullOrEmpty(actionName)) { //throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName"); } ControllerDescriptor controllerDescriptor = this.GetControllerDescriptor(controllerContext); ActionDescriptor actionDescriptor = this.FindAction(controllerContext, controllerDescriptor, actionName); if (actionDescriptor == null) { return false; } //注意这里 FilterInfo filters = this.GetFilters(controllerContext, actionDescriptor); try { AuthorizationContext context = this.InvokeAuthorizationFilters(controllerContext, filters.AuthorizationFilters, actionDescriptor); if (context.Result != null) { this.InvokeActionResult(controllerContext, context.Result); } else { if (controllerContext.Controller.ValidateRequest) { //ValidateRequest(controllerContext); } IDictionary parameterValues = this.GetParameterValues(controllerContext, actionDescriptor); //这里方法执行后,OnActionExecuting,OnActionExecuted,Action分别相应被触发 ActionExecutedContext context2 = this.InvokeActionMethodWithFilters(controllerContext, filters.ActionFilters, actionDescriptor, parameterValues); this.InvokeActionResultWithFilters(controllerContext, filters.ResultFilters, context2.Result); } } catch (IOException ex) { throw; } catch (Exception exception) { ExceptionContext context3 = this.InvokeExceptionFilters(controllerContext, filters.ExceptionFilters, exception); if (!context3.ExceptionHandled) { throw; } this.InvokeActionResult(controllerContext, context3.Result); } return true; } } /// /// 自定义拦截标签属性用于测试 /// public class MyActionFilterAttribute : ActionFilterAttribute { public override void _disibledevent=>base.OnActionExecuted(filterContext); } public override void _disibledevent=>base.OnActionExecuting(filterContext); } } }

调试后进到MyControllerActionInvoker.InvokeAction 观察filters结构
了解.net MVC的实现原理mvc实现原理
当断点走到这个位置时
了解.net MVC的实现原理mvc实现原理
MyController及FilterAttribute属性的OnActionExecuting先被执行,接着Action被执行,然后MyController及FilterAttribute的OnActionExecuted也被执行了。
从客户端请求到Controller的具体Action执行的流程基本上走了一遍,对.net mvc机制的也有了一定的了解,相信对.net mvc开发有很大帮助,至少在理解的基础上才能充分发挥他的优点及特性。下一节我们继续通过Refletor去了解Action的返回结果ActionResult是如何返回绑定值到视图以及视图引擎的机制。
.net MVC3 加入了很多新特性,例如客户端远程验证RemoteAttribute,新的视图引擎Razor等。
Tags:  自我实现 mvc怎样实现的 mvc原理 mvc架构原理 mvc实现原理

延伸阅读

最新评论

发表评论