请求如何进入ASP.NET MVC框架

2019-05-22 20:45:11于海丽

一、前言

  对于WebForm开发,请求通常是一个以.aspx结尾的url,对应一个物理文件,从代码的角度来说它其实是一个控件(Page)。而在MVC中,一个请求对应的是一个Controller里的Action。熟悉asp.net的朋友都知道,asp.net请求实际都是交给HttpHandler处理(实现了IHttpHandler的类型)。无论是.aspx,.ashx,.asmx 还是MVC里的Action,请求都会交给HttpHandler。具体是在管道事件中,会根据请求创建一个HttpHandler,并执行它的PR方法。对于aspx和ashx都很好理解,因为它们本身就实现了IHttpHandler接口,而MVC的Controller和Action都和HttpHandler没有关系,它是如何实现的呢?接下来我们就看一个请求是如何进入mvc框架内部的。

二、例子

  WebForm和MVC都是建立在asp.net平台上的,Webform出现得比较早,那么MVC是如何做到在不影响底层框架,实现扩展的呢?这主要得益于asp.net的路由机制。路由机制并不属于MVC,WebForm也可以使用它。它的目的是让一个请求与物理文件分离,原理是通过映射关系,将请求映射到指定的HttpHandler。例如我们也可以将一个/Admin/User.aspx?name=张三 的请求映射成可读性更好的/Admin/张三。下面是两种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是一个Route集合,Route封装了名称、url模式、约束条件、默认值等路由相关信息。其中,MapPageRoute是RouteCollection定义的方法,而MapRoute是MVC扩展出来的(扩展方法的好处就是可以在不修改原有代码的情况下添加所需的功能)。它们的目的都是一样的,创建一个Route对象,添加到集合当中;我们也可以new 一个Route对象,然后调用RouteCollection.Add,效果是一样的。下面我们主要关注MVC的实现过程,WebForm其实也是类似的。

三、分析源码

  接下来我们看MVC是如何利用路由机制实现扩展的。路由机制是通过一个UrlRoutingModule完成的,它是一个实现了IHttpModule的类,路由模块已经默认帮我们注册好了。HttpModule通过注册HttpApplication事件参与到管道处理请求中,具体是订阅HttpApplication某个阶段的事件。路由机制就是利用这个原理,UrlRoutingModule订阅了PostResolveRequestCache 事件,实现url的映射。为什么是该事件呢?因为该事件的下一步就要完成请求和物理文件的映射,所以必须要此之前进行拦截。核心代码如下:

public class UrlRoutingModule : IHttpModule {
  public RouteCollection RouteCollection {
    get {
      if (_routeCollection == null) {
        //全局的RouteCollection集合
        _routeCollection = RouteTable.Routes;
      }
      return _routeCollection;
    }
    set {
      _routeCollection = value;
    }
  }
 
  protected virtual void Init(HttpApplication application) {
    //注册PostResolveRequestCache事件
    application.PostResolveRequestCache += OnApplicationPostResolveRequestCache;
  }
 
  private void OnApplicationPostResolveRequestCache(object sender, EventArgs e) {
    //创建上下文
    HttpApplication app = (HttpApplication)sender;
    HttpContextBase context = new HttpContextWrapper(app.Context);
    PostResolveRequestCache(context);
  }
 
  public virtual void PostResolveRequestCache(HttpContextBase context) {
    //1.获取RouteData
    RouteData routeData = RouteCollection.GetRouteData(context);
    if (routeData == null) {
      return;
    }
    //2.获取IRouteHandler
    IRouteHandler routeHandler = routeData.RouteHandler;
    if (routeHandler == null) {
       
    }
     
    //RequestContext保证了HttpContext和RouteData,在后续使用
    RequestContext requestContext = new RequestContext(context, routeData);
 
    context.Request.RequestContext = requestContext;
 
    //3.获取IHttpHandler
    IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
 
    //重新映射到处理程序
    context.RemapHandler(httpHandler);
  }
}