分析Asp.Net MVC的请求过程,我们从以下几方面看:
配置:IIS网站的配置可以分为两个块:全局 Web.Config 和本站 Web.Config 。
Asp.Net Routing属于全局性的,所以它配置在全局Web.Config 中,我们可以在如下路径中找到:“C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\Web.config“
<?xml version="1.0" encoding="utf-8"?> <!-- the root web configuration file --> <configuration> <system.web> <httpModules> <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" /> </httpModules> </system.web> </configuration>
其实我们仔细看下它的Init方法和PostResolveRequestCache,可以得到这个UrlRoutingModule是做什么的,我们看下代码:它继承自,IHttpModule ,到此我们就明白了Asp.Net MVC处理请求的HttpModule就是UrlRoutingModule。
public class UrlRoutingModule : IHttpModule { protected virtual void Init(HttpApplication application) { application.PostResolveRequestCache += OnApplicationPostResolveRequestCache; } private void OnApplicationPostResolveRequestCache(object sender, EventArgs e) { HttpContextBase context = new HttpContextWrapper(((HttpApplication)sender).Context); PostResolveRequestCache(context); } public virtual void PostResolveRequestCache(HttpContextBase context) { // Match the incoming URL against the route table RouteData routeData = RouteCollection.GetRouteData(context); // Do nothing if no route found if (routeData == null) { return; } // If a route was found, get an IHttpHandler from the route's RouteHandler IRouteHandler routeHandler = routeData.RouteHandler; RequestContext requestContext = new RequestContext(context, routeData); // Remap IIS7 to our handler context.RemapHandler(httpHandler); } }
系统启动:
- Asp.Net MVC 入口:Application_Start() ,调用 RouteConfig.RegisterRoutes(RouteTable.Routes);
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); } }
public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}", defaults: new { controller = "Home", action = "Index" }, namespaces: new string[] { "ABC.Mr.Web.Controllers" } ); } }
2. RouteConfig.RegisterRoutes进行路由配置:根据其中代码会创建一个RouteTable(路由表)实现URL到处理程序之间的映射。
3. MapRoute会默认配置了一个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 route = new Route(url, new MvcRouteHandler()) { Defaults = CreateRouteValueDictionaryUncached(defaults), Constraints = CreateRouteValueDictionaryUncached(constraints), DataTokens = new RouteValueDictionary() }; ConstraintValidation.Validate(route); if ((namespaces != null) && (namespaces.Length > 0)) { route.DataTokens[RouteDataTokenKeys.Namespaces] = namespaces; } routes.Add(name, route); return route; }
public class MvcRouteHandler : IRouteHandler { private IControllerFactory _controllerFactory; public MvcRouteHandler() { } public MvcRouteHandler(IControllerFactory controllerFactory) { _controllerFactory = controllerFactory; } protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) { requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext)); return new MvcHandler(requestContext); } protected virtual SessionStateBehavior GetSessionStateBehavior(RequestContext requestContext) { string controllerName = (string)requestContext.RouteData.Values["controller"]; if (String.IsNullOrWhiteSpace(controllerName)) { throw new InvalidOperationException(MvcResources.MvcRouteHandler_RouteValuesHasNoController); } IControllerFactory controllerFactory = _controllerFactory ?? ControllerBuilder.Current.GetControllerFactory(); return controllerFactory.GetControllerSessionBehavior(requestContext, controllerName); } #region IRouteHandler Members IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) { return GetHttpHandler(requestContext); } #endregion }
4. MvcHandler实现了三个接口IHttpAsyncHandler, IHttpHandler, IRequiresSessionState4.MvcRouteHandler继承IRouteHandler,并实现了GetHttpHandler接口,返回一个IHttpHandler :MvcHandler
public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState { private static readonly object _processRequestTag = new object(); internal static readonly string MvcVersion = GetMvcVersionString(); public static readonly string MvcVersionHeaderName = "X-AspNetMvc-Version"; private ControllerBuilder _controllerBuilder; public MvcHandler(RequestContext requestContext) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } RequestContext = requestContext; } internal ControllerBuilder ControllerBuilder { get { if (_controllerBuilder == null) { _controllerBuilder = ControllerBuilder.Current; } return _controllerBuilder; } set { _controllerBuilder = value; } } public static bool DisableMvcResponseHeader { get; set; } protected virtual bool IsReusable { get { return false; } } public RequestContext RequestContext { get; private set; } 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 httpContextBase = new HttpContextWrapper(httpContext); return BeginProcessRequest(httpContextBase, callback, state); } protected internal virtual IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state) { IController controller; IControllerFactory factory; ProcessRequestInit(httpContext, out controller, out factory); IAsyncController asyncController = controller as IAsyncController; if (asyncController != null) { // asynchronous controller // Ensure delegates continue to use the C# Compiler static delegate caching optimization. BeginInvokeDelegate<ProcessRequestState> beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState, ProcessRequestState innerState) { try { return innerState.AsyncController.BeginExecute(innerState.RequestContext, asyncCallback, asyncState); } catch { innerState.ReleaseController(); throw; } }; EndInvokeVoidDelegate<ProcessRequestState> endDelegate = delegate(IAsyncResult asyncResult, ProcessRequestState innerState) { try { innerState.AsyncController.EndExecute(asyncResult); } finally { innerState.ReleaseController(); } }; ProcessRequestState outerState = new ProcessRequestState() { AsyncController = asyncController, Factory = factory, RequestContext = RequestContext }; SynchronizationContext callbackSyncContext = SynchronizationContextUtil.GetSynchronizationContext(); return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, outerState, _processRequestTag, callbackSyncContext: callbackSyncContext); } else { // synchronous controller Action action = delegate { try { controller.Execute(RequestContext); } finally { factory.ReleaseController(controller); } }; return AsyncResultWrapper.BeginSynchronous(callback, state, action, _processRequestTag); } } protected internal virtual void EndProcessRequest(IAsyncResult asyncResult) { AsyncResultWrapper.End(asyncResult, _processRequestTag); } private static string GetMvcVersionString() { // DevDiv 216459: // This code originally used Assembly.GetName(), but that requires FileIOPermission, which isn't granted in // medium trust. However, Assembly.FullName *is* accessible in medium trust. return new AssemblyName(typeof(MvcHandler).Assembly.FullName).Version.ToString(2); } protected virtual void ProcessRequest(HttpContext httpContext) { HttpContextBase httpContextBase = new HttpContextWrapper(httpContext); ProcessRequest(httpContextBase); } protected internal virtual void ProcessRequest(HttpContextBase httpContext) { IController controller; IControllerFactory factory; ProcessRequestInit(httpContext, out controller, out factory); try { controller.Execute(RequestContext); } finally { factory.ReleaseController(controller); } } private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) { // If request validation has already been enabled, make it lazy. This allows attributes like [HttpPost] (which looks // at Request.Form) to work correctly without triggering full validation. // Tolerate null HttpContext for testing. HttpContext currentContext = HttpContext.Current; if (currentContext != null) { bool? isRequestValidationEnabled = ValidationUtility.IsValidationEnabled(currentContext); if (isRequestValidationEnabled == true) { ValidationUtility.EnableDynamicValidation(currentContext); } } AddVersionHeader(httpContext); RemoveOptionalRoutingParameters(); // Get the controller type string controllerName = RequestContext.RouteData.GetRequiredString("controller"); // Instantiate the controller and call Execute factory = ControllerBuilder.GetControllerFactory(); controller = factory.CreateController(RequestContext, controllerName); if (controller == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_FactoryReturnedNull, factory.GetType(), controllerName)); } } private void RemoveOptionalRoutingParameters() { RouteValueDictionary rvd = RequestContext.RouteData.Values; // Ensure delegate is stateless rvd.RemoveFromDictionary((entry) => entry.Value == UrlParameter.Optional); } #region IHttpHandler Members bool IHttpHandler.IsReusable { get { return IsReusable; } } void IHttpHandler.ProcessRequest(HttpContext httpContext) { ProcessRequest(httpContext); } #endregion #region IHttpAsyncHandler Members IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData) { return BeginProcessRequest(context, cb, extraData); } void IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) { EndProcessRequest(result); } #endregion // Keep as value type to avoid allocating private struct ProcessRequestState { internal IAsyncController AsyncController; internal IControllerFactory Factory; internal RequestContext RequestContext; internal void ReleaseController() { Factory.ReleaseController(AsyncController); } } }
请求开始:
1. 在IIS中 HTTP.SYS文件负责把请求传入相应的应用程序池中。(当创建一个应用程序池,该池的ID就会生成并在HTTP.SYS文件中注册)
2. 当应用程序池接收到请求,会接着传给工作进程w3wp.exe,该进程检查来请求的URL后缀以确定加载哪个ISAPI扩展。ASP.NET加载时会附带自己的ISAPI扩展(aspnet_isapi.dll),以便在IIS中映射。
注意:如果先安装了asp.net,然后再安装IIS,就需要通过aspnet_regiis命令来注册ASP.NET中的ISAPI扩展。
3. 当工作进程加载了aspnet_isapi.dll, 就会构造一个HttpRuntime类,该类是应用程序的入口,通过ProcessRequest方法处理请求。
4. 当这个方法被调用,一个HttpContext的实例就产生了。可通过HTTPContent.Current获取到这个实例,且该实例会在整个生命周期中存活,我们通过它可以获取到一些常用对象,如Request,Response,Session 等
5. HttpRuntime会通过HttpApplicationFactory类加载一个HttpApplication对象。每一次请求都要穿过UrlRoutingModule到达HttpHandler,以便被响应.
6. 上面已经讲到了UrlRoutingModule,当PostResolveRequestCache事件得到响应后,首先就要在路由中进匹配。
public virtual void PostResolveRequestCache(HttpContextBase context) { // Match the incoming URL against the route table RouteData routeData = RouteCollection.GetRouteData(context); // Do nothing if no route found if (routeData == null) { return; } // If a route was found, get an IHttpHandler from the route's RouteHandler IRouteHandler routeHandler = routeData.RouteHandler; RequestContext requestContext = new RequestContext(context, routeData); // Remap IIS7 to our handler context.RemapHandler(httpHandler); }
7. UrlRoutingModule在RouteCollection中查找Request匹配的RouteHandler,默认是MvcRouteHandler。
8. MvcRouteHandler 创建 MvcHandler实例. 这就是我们得到的routeHandler,其实就是 MvcHandler
9. MvcHandler执行 ProcessRequest.
protected internal virtual void ProcessRequest(HttpContextBase httpContext) { IController controller; IControllerFactory factory; ProcessRequestInit(httpContext, out controller, out factory); try { controller.Execute(RequestContext); } finally { factory.ReleaseController(controller); } }
private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) { // If request validation has already been enabled, make it lazy. This allows attributes like [HttpPost] (which looks // at Request.Form) to work correctly without triggering full validation. // Tolerate null HttpContext for testing. HttpContext currentContext = HttpContext.Current; if (currentContext != null) { bool? isRequestValidationEnabled = ValidationUtility.IsValidationEnabled(currentContext); if (isRequestValidationEnabled == true) { ValidationUtility.EnableDynamicValidation(currentContext); } } AddVersionHeader(httpContext); RemoveOptionalRoutingParameters(); // Get the controller type string controllerName = RequestContext.RouteData.GetRequiredString("controller"); // Instantiate the controller and call Execute factory = ControllerBuilder.GetControllerFactory(); controller = factory.CreateController(RequestContext, controllerName); if (controller == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_FactoryReturnedNull, factory.GetType(), controllerName)); } }
10. MvcHandler 使用 IControllerFactory 获得实现了IController接口的实例,找到对应的业务controller
11. 根据Request触发业务controller的和Action方法 获取Controller与Action的描述信息和过滤器信息
public abstract class ControllerBase : IController { protected virtual void Execute(RequestContext requestContext) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } if (requestContext.HttpContext == null) { throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext"); } VerifyExecuteCalledOnce(); Initialize(requestContext); using (ScopeStorage.CreateTransientScope()) { ExecuteCore(); } } // 抽象方法-让Controller去具体实现 protected abstract void ExecuteCore(); }
public abstract class Controller : ControllerBase, IActionFilter, IAuthenticationFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IAsyncManagerContainer { protected override void ExecuteCore() { PossiblyLoadTempData(); try { string actionName = GetActionName(RouteData); if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) { HandleUnknownAction(actionName); } } finally { PossiblySaveTempData(); } } }
private static string GetActionName(RouteData routeData) { Contract.Assert(routeData != null); // If this is an attribute routing match then the 'RouteData' has a list of sub-matches rather than // the traditional controller and action values. When the match is an attribute routing match // we'll pass null to the action selector, and let it choose a sub-match to use. if (routeData.HasDirectRouteMatch()) { return null; } else { return routeData.GetRequiredString("action"); } }
public virtual bool InvokeAction(ControllerContext controllerContext, string actionName) { if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); } Contract.Assert(controllerContext.RouteData != null); if (String.IsNullOrEmpty(actionName) && !controllerContext.RouteData.HasDirectRouteMatch()) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName"); } ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext); ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName); if (actionDescriptor != null) { FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor); try { AuthenticationContext authenticationContext = InvokeAuthenticationFilters(controllerContext, filterInfo.AuthenticationFilters, actionDescriptor); if (authenticationContext.Result != null) { // An authentication filter signaled that we should short-circuit the request. Let all // authentication filters contribute to an action result (to combine authentication // challenges). Then, run this action result. AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge( controllerContext, filterInfo.AuthenticationFilters, actionDescriptor, authenticationContext.Result); InvokeActionResult(controllerContext, challengeContext.Result ?? authenticationContext.Result); } else { AuthorizationContext authorizationContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor); if (authorizationContext.Result != null) { // An authorization filter signaled that we should short-circuit the request. Let all // authentication filters contribute to an action result (to combine authentication // challenges). Then, run this action result. AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge( controllerContext, filterInfo.AuthenticationFilters, actionDescriptor, authorizationContext.Result); InvokeActionResult(controllerContext, challengeContext.Result ?? authorizationContext.Result); } else { if (controllerContext.Controller.ValidateRequest) { ValidateRequest(controllerContext); } IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor); ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters); // The action succeeded. Let all authentication filters contribute to an action result (to // combine authentication challenges; some authentication filters need to do negotiation // even on a successful result). Then, run this action result. AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge( controllerContext, filterInfo.AuthenticationFilters, actionDescriptor, postActionContext.Result); InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, challengeContext.Result ?? postActionContext.Result); } } } catch (ThreadAbortException) { // This type of exception occurs as a result of Response.Redirect(), but we special-case so that // the filters don't see this as an error. throw; } catch (Exception ex) { // something blew up, so execute the exception filters ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex); if (!exceptionContext.ExceptionHandled) { throw; } InvokeActionResult(controllerContext, exceptionContext.Result); } return true; } // notify controller that no method matched return false; }
12. 上面的代码显示,执行action之前 ,会对controller和action的filter做判断 ,并且进行认证和授权的判断。
13,验证全部通过后会通过GetParameterValues获取方法的参数信息,执行方法InvokeActionResultWithFilters:
protected virtual ActionExecutedContext InvokeActionMethodWithFilters(ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters) { ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters); Func<ActionExecutedContext> continuation = () => new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */) { Result = InvokeActionMethod(controllerContext, actionDescriptor, parameters) }; // need to reverse the filter list because the continuations are built up backward Func<ActionExecutedContext> thunk = filters.Reverse().Aggregate(continuation, (next, filter) => () => InvokeActionMethodFilter(filter, preContext, next)); return thunk(); }
protected virtual ActionResult InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters) { object returnValue = actionDescriptor.Execute(controllerContext, parameters); ActionResult result = CreateActionResult(controllerContext, actionDescriptor, returnValue); return result; }
protected virtual ActionResult CreateActionResult(ControllerContext controllerContext, ActionDescriptor actionDescriptor, object actionReturnValue) { if (actionReturnValue == null) { return new EmptyResult(); } ActionResult actionResult = (actionReturnValue as ActionResult) ?? new ContentResult { Content = Convert.ToString(actionReturnValue, CultureInfo.InvariantCulture) }; return actionResult; }
14 到此已经返回了我们熟悉的ActoinResult.