How does the request enter the ASP. NET MVC framework

  • 2021-07-10 19:29:08
  • OfStack

1. Preface

For WebForm development, the request is usually an url ending in. aspx, corresponding to a physical file, which is actually a control (Page) from a code point of view. In MVC, one request corresponds to Action in Controller. Friends who are familiar with asp. net know that asp. net requests are actually handed over to HttpHandler for processing (implementing the type of IHttpHandler). Whether it is. aspx,. ashx,. asmx or Action in MVC, the request will be handed to HttpHandler. Specifically, in the pipeline event, an HttpHandler is created on request and its PR method is executed. aspx and ashx are easy to understand, because they implement IHttpHandler interface, while Controller and Action of MVC have nothing to do with HttpHandler. How is it implemented? Next, let's look at how a request enters the mvc framework.

2. Examples

WebForm and MVC are both built on asp. net platform, and Webform appeared earlier, so how can MVC be extended without affecting the underlying framework? This is mainly due to the routing mechanism of asp. net. The routing mechanism does not belong to MVC, and WebForm can also use it. Its purpose is to separate a request from a physical file by mapping the request to a specified HttpHandler through a mapping relationship. For example, we can also add 1/Admin/User. aspx? name = requests for Sheet 3 mapped to better reader/Admin/Sheet 3. Here are two ways to register url:


public static void RegisterRoutes(RouteCollection routes)
{
  //MVC
  routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
  );
 
  //WebForm
  routes.MapPageRoute(
    routeName: "WebForm",
    routeUrl: "Admin/{user}",
    physicalFile: "~/Admin/User.aspx"
  );
}

RouteCollection is a collection of Route, and Route encapsulates routing related information such as name, url mode, constraints and default values. Among them, MapPageRoute is the method defined by RouteCollection, while MapRoute is an extension of MVC (the advantage of the extension method is that the required functions can be added without modifying the original code). Their purpose is to create an Route object and add it to the collection; We can also new 1 Route object, and then call RouteCollection. Add, the effect is like 1. Let's focus on the implementation process of MVC, but WebForm is actually similar.

3. Analyze the source code

Next, let's look at how MVC is extended by using routing mechanism. The routing mechanism is completed through an UrlRoutingModule, which is a class that implements IHttpModule. The routing module has been registered for us by default. HttpModule participates in pipeline processing requests by registering HttpApplication events, specifically subscribing to events at a certain stage of HttpApplication. Routing mechanism is to use this principle, UrlRoutingModule subscribes to PostResolveRequestCache events, url mapping. Why this incident? Because the next step of this event is to complete the mapping of the request to the physical file, interception must be performed before this. The core code is as follows:


public class UrlRoutingModule : IHttpModule {
  public RouteCollection RouteCollection {
    get {
      if (_routeCollection == null) {
        // Global RouteCollection Set 
        _routeCollection = RouteTable.Routes;
      }
      return _routeCollection;
    }
    set {
      _routeCollection = value;
    }
  }
 
  protected virtual void Init(HttpApplication application) {
    // Registration PostResolveRequestCache Events 
    application.PostResolveRequestCache += OnApplicationPostResolveRequestCache;
  }
 
  private void OnApplicationPostResolveRequestCache(object sender, EventArgs e) {
    // Create context 
    HttpApplication app = (HttpApplication)sender;
    HttpContextBase context = new HttpContextWrapper(app.Context);
    PostResolveRequestCache(context);
  }
 
  public virtual void PostResolveRequestCache(HttpContextBase context) {
    //1. Get RouteData
    RouteData routeData = RouteCollection.GetRouteData(context);
    if (routeData == null) {
      return;
    }
    //2. Get IRouteHandler
    IRouteHandler routeHandler = routeData.RouteHandler;
    if (routeHandler == null) {
       
    }
     
    //RequestContext Guaranteed HttpContext And RouteData, In subsequent use 
    RequestContext requestContext = new RequestContext(context, routeData);
 
    context.Request.RequestContext = requestContext;
 
    //3. Get IHttpHandler
    IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
 
    // Remap to handler 
    context.RemapHandler(httpHandler);
  }
}    

We focus on the main method PostResolveRequestCache, which has three key steps.

Step 1. Get RouteData

RouteData is a wrapper of Route and is used in subsequent processing. It is obtained through RouteCollection, which is the same collection object as RouteTable. Routes used in the above registration. GetRouteData that calls RouteCollection iterates through every item of it, that is, the Route object, and then calls the GetRouteData method of the Route object (this design is used in many collections within MVC). The following code:


public RouteData GetRouteData(HttpContextBase httpContext) {
  using (GetReadLock()) {
    foreach (RouteBase route in this) {
      RouteData routeData = route.GetRouteData(httpContext);
      if (routeData != null) {           
        return routeData;
      }
    }
  }
  return null;
}

The GetRouteData method of the Route object is as follows:


public override RouteData GetRouteData(HttpContextBase httpContext) {
  string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;
 
  // Combine default values, match url
  RouteValueDictionary values = _parsedRoute.Match(requestPath, Defaults);
 
  if (values == null) {
    return null;
  }
 
  // Packed into RouteData Why not put it here if What about the back? 
  RouteData routeData = new RouteData(this, RouteHandler);
 
  // Matching constraint 
  if (!ProcessConstraints(httpContext, values, RouteDirection.IncomingRequest)) {
    return null;
  }
 
  //RouteData Adj. Values And DataTokens All come from Route
  foreach (var value in values) {
    routeData.Values.Add(value.Key, value.Value);
  }
  if (DataTokens != null) {
    foreach (var prop in DataTokens) {
      routeData.DataTokens[prop.Key] = prop.Value;
    }
  }
 
  return routeData;
}

As you can see, the GetRouteData method of the Route object matches the url schema, and checks for constraints, and returns null for non-compliance. If there is a match, new1 RouteData.

Step 2. Get the IRouteHandler interface object

The above RouteData was created with the parameters of the current Route object and its RouteHandler property. RouteHandler is an IRouteHandler, an important interface defined as follows:


public interface IRouteHandler {
  IHttpHandler GetHttpHandler(RequestContext requestContext);
}

Obviously, it is used to get IHttpHandler. So where is the RouteHandler property of the Route object initialized? Let's go back to the initial registration method, routes. MapRoute, which creates an Route object based on the parameters passed. The implementation of this method is as follows:


public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces)
{
  // Create 1 A Route Object, its IRouteHandler For MvcRouteHandler
  Route route = new Route(url, new MvcRouteHandler())
  {
    Defaults = CreateRouteValueDictionary(defaults),
    Constraints = CreateRouteValueDictionary(constraints),
    DataTokens = new RouteValueDictionary()
  };
 
  if ((namespaces != null) && (namespaces.Length > 0))
  {
    route.DataTokens["Namespaces"] = namespaces;
  }
 
  // Will Route Register to RouteCollection Medium 
  routes.Add(name, route);
 
  return route;
}

When creating Route, in addition to passing the url schema, we also passed an MvcRouteHandler by default, which implemented the IRouteHandler interface.
Step 3. Get the IHttpHandler interface object

With MvcRouteHandler, you can call its GetHttpHandler method to get IHttpHandler, which is implemented as follows:


protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
{
  // Settings session Status 
  requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
 
  // Return 1 Realized IHttpHandler Adj. MvcHandler
  return new MvcHandler(requestContext);
}

As you can see, it returns an MvcHandler, and MvcHandler implements the IHttpHandler interface. Therefore, at the beginning, the essence of the request is given to HttpHandler. In fact, the same is true of MVC, and the request is given to MvcHandler for processing. We can look at the MvcHandler definition and main methods:


public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState
{
   protected internal virtual IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state)
  {
    IController controller;
    IControllerFactory factory;
 
    // This method will activate Controller Object 
    ProcessRequestInit(httpContext, out controller, out factory);
 
    IAsyncController asyncController = controller as IAsyncController;
    if (asyncController != null)
    {
      // asynchronous controller
      BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState)
      {
        try
        {
          // Call Controller Adj. BeginExecute Method 
          return asyncController.BeginExecute(RequestContext, asyncCallback, asyncState);
        }
        catch
        {
          factory.ReleaseController(asyncController);
          throw;
        }
      };
 
      EndInvokeDelegate endDelegate = delegate(IAsyncResult asyncResult)
      {
        try
        {
          asyncController.EndExecute(asyncResult);
        }
        finally
        {
          factory.ReleaseController(asyncController);
        }
      };
 
      SynchronizationContext syncContext = SynchronizationContextUtil.GetSynchronizationContext();
      AsyncCallback newCallback = AsyncUtil.WrapCallbackForSynchronizedExecution(callback, syncContext);
      return AsyncResultWrapper.Begin(newCallback, state, beginDelegate, endDelegate, _processRequestTag);
    }
    else
    {
      // synchronous controller
      Action action = delegate
      {
        try
        {
          controller.Execute(RequestContext);
        }
        finally
        {
          factory.ReleaseController(controller);
        }
      };
 
      return AsyncResultWrapper.BeginSynchronous(callback, state, action, _processRequestTag);
    }
  }
}

As you can see, the task of MvcHandler is to activate Controller and execute its Execute method. This process is very similar to the page processing in Webform. When an aspx request arrives, it will find Page implementing IHttpHandler according to the virtual path (similar to the routing mechanism finding MvcHandler according to url mode), and then enter the page cycle of Page (similar to activating Controller of Mvc, and then executing Action process).

4. Summary

Next, briefly summarize the process of requesting to enter the MVC framework under 1:

1. Add the routing object Route to the global RouteCollection, and the IRouteHandler of Route is initialized to MvcRouteHandler.

2. UrlRoutingModule registers the HttpApplication PostResolveRequestCache event to intercept requests.
3. When the request arrives, traverse RouteCollection in the processing event, and call GetRouteData of every Route object to get RouteData wrapper object.

4. Call GetHttpHandler of MvcRouteHandler to get MvcHandler.

5. Call RemapHandler of HttpContext to map the request to the MvcHandler handler.

6. Execute the PR method of MvcHandler, activate Controller, and execute Action.

The above is the whole content of this paper, hoping to help everyone's study.


Related articles: