Asp.Net MVC 路由

Asp.Net MVC 路由

当用户经过URL访问网站时,要把用户请求的URL映射到正确的应用程序的操做上。那么如何实现这个映射--Routing(路由)。html

路由并不专属于Asp.Net MVC,而是创建在Asp.Net Framework之上的一个组件,因此全部依赖Asp.Net Framework的均可以使用路由。如WebForms,API等,可是Asp.Net MVC 和路由密切相关。正则表达式

图:路由关系图mvc


路由工做流程

Asp.Net是一个管道模型,一个Http请求先通过HttpModule,再经过HttpHandlerFactory,建立一个对应的HttpHandler处理对应的请求。因此对Asp.Net的全部的扩展也是经过注册这些管道事件来实现的。由于路由是创建在Asp.Net Framework之上的,因此路由也是注册实现了管道事件。可是是经过注册HttpModulePostResolveRequestCache事件来实现的。app

为何不注册HttpHandler来实现呢?

由于:asp.net

若是把请求的管道模型比做一个运行的火车的话,HttpHandler是请求火车的目的地。HttpModule是一个沿途的站点,要在终点前分析好这个请求是到哪一个目的地。网站

  • HttpHandler多用来处理响应处理。
  • HttpModule多用来处理通用性和响应内容无关的功能。

小结:ui

路由就是一个实现了IHttpModule接口的UrlRoutingModuleHttpModule,在管道事件中拦截请求,分析Url,匹配路由,再交给HttpHandler处理的过程。this


路由如何拦截请求

上述认识到路由是经过实现了接口IHttpModule的类--UrlRoutingModule来注册管道事件,在该类中实现了请求拦截,路由匹配,建立指定HttpHandler。url

因此路由组件中UrlRoutingModule就是是关键。.net

UrlRoutingModule源码在线查看

经过该类的源代码能够发现。UrlRoutingModule注册了PostResolveRequestCache事件。注册该事件,纯粹是由于要在HttpHandler目的地建立以前执行路由。由于在管道事件中PostMapRequestHandler事件是把请求交给HttpHandler来处理。而PostResolveRequestCache在该事件以前。(Asp.Net管道事件)

//UrlRoutingModule源码
 ...
 //注册事件PostResolveRequestCache 
 application.PostResolveRequestCache += OnApplicationPostResolveRequestCache;
 ...

查看UrlRoutingModule代码,发现该类的一个PostResolveRequestCache方法,实现了路由的工做。

// UrlRoutingModule的本地方法
   public virtual void PostResolveRequestCache(HttpContextBase context) {
            // 根据HttpContext的Url匹配路由对象,该对象包含了Controller,Action和参数
            // Match the incoming URL against the route table
            RouteData routeData = RouteCollection.GetRouteData(context);
 
            // Do nothing if no route found
            if (routeData == null) {
                return;
            }

            //由匹配的路由对象建立一个MVCRouteHandler
            // If a route was found, get an IHttpHandler from the route's RouteHandler
            IRouteHandler routeHandler = routeData.RouteHandler;
            if (routeHandler == null) {
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        SR.GetString(SR.UrlRoutingModule_NoRouteHandler)));
            }
 
            // This is a special IRouteHandler that tells the routing module to stop processing
            // routes and to let the fallback handler handle the request.
            if (routeHandler is StopRoutingHandler) {
                return;
            }
 
            //封装匹配的路由对象和HttpContext,建立新的RequestContext
            RequestContext requestContext = new RequestContext(context, routeData);
 
            // Dev10 766875 Adding RouteData to HttpContext
            context.Request.RequestContext = requestContext;
            
            //获取MVCHandler
            IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
            if (httpHandler == null) {
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentUICulture,
                        SR.GetString(SR.UrlRoutingModule_NoHttpHandler),
                        routeHandler.GetType()));
            }
 
            if (httpHandler is UrlAuthFailureHandler) {
                if (FormsAuthenticationModule.FormsAuthRequired) {
                    UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
                    return;
                }
                else {
                    throw new HttpException(401, SR.GetString(SR.Assess_Denied_Description3));
                }
            }
 
            // Remap IIS7 to our handler
            context.RemapHandler(httpHandler);
        }

即:

  1. 根据HttpContext,路由匹配规则,匹配一个RouteData对象。
  2. 调用RouteData对象的RouteHandler获取IRouteHandlerMVCRouteHandler
  3. 由匹配的RouteDataHttpContext建立RequestContext
  4. 由2的MVCRouteHandler和3的RequestContext建立IHttpHandler-MVCHandler.
  5. HttpHandler管道事件执行。

流程以下图所示:

以后HttpHandler的运行能够参考以下整个生命周期:

Asp.Net MVC 生命周期图:


路由的使用

Global.asaxMVCApplication是管理Asp.Net应用程序生命周期的管道事件的类。在类中实现管道事件或方法会在对应的管道事件中调用。


配置路由

App_Start文件下,新建RouteConfig.cs文件里配置路由信息。经过静态方法RouteCollection.MapRoute()配置路由信息。

如:

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");//忽略该模式的URL

        routes.MapRoute(
            name: "Default",//路由名称
            url: "{controller}/{action}/{id}",//路由模板
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }//路由默认值,参数id能够为空
        );
    }
}
  • name:为该路由名称

  • url:为路由模板,{}是占位符。

  • defaults:为路由默认值

注册路由

Global.asaxMVCApplication继承HttpApplication。而HttpApplication则是管理整个管道周期的实例。在该类中经过注册事件,或方法能够在管道事件中被调用。注册路由到应用程序就是在Application_Start()方法中实现。

如:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);//路由注册到应用程序
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}

URL匹配

在配置路由里建立了一个路由名为Default的路由。该Default路由由controlleractionid三部分组成,其中id为可选参数。

该路由能够匹配以下url:

  • xxx.com/home/index/1
  • xxx.com/home/index
  • xxx.com/home
  • xxx.com/

这些URL都会映射到以下Action:

public class HomeController :Controller
{
    public ActionResult Index()
    {
        return View();
    }
}

//在路由中id参数是可为空的,因此对于值类型的参数必须是可空的值类型。
public class HomeController :Controller
{
    public ActionResult Index(int? id)
    {
        return View();
    }
}

而且该Action的参数名称须要和Route中的参数(id)一致。即也是id。才能够匹配xxx.com/home/index/1不然只能经过url传参匹配xxx.com/home/index?myparam=1

如:若是定义的Action以下

public class HomeController :Controller
{
    public ActionResult Index(string str)
    {
        return View();
    }
}

输入xxx.com/home/index/1时,会认为参数为空,即str并无被赋值,可是依然会调用index方法,只不过是认为str为空。可是当你经过url传参请求时xxx.com/home/index?str=hello,是能够匹配到这个Action,也能够给str赋值。


在同一个Controller下是不容许有Action重载的

如:

public class HomeController :Controller
{
    public ActionResult Index(int? id)
    {
        return View();
    }
    public ActionResult Index()
    {
        return View();
    }
}

在请求时提示错误:在对控制器类型“HomeController”的操做Index的请求方法不明确。


路由顺序和优先级

路由引擎在定位路由时,会遍历路由集合中的全部路由。只要发现了一个匹配的路由,会当即中止搜索。因此定义路由必定要注意路由的前后循序。通常是越是精确的放在前面。

如:有一个以下的路由配置

routes.MapRoute{
    name:  "one",
    url:"{site}",
    defaults:new{controller="MyControllerOne",action="Index"}
}
routes.MapRoute{
    name:"two",
    url:"Admin",
    defaults:new {controller="Admin",action="Index"}
}

第一个路由有一个{site}占位符。默认的控制器为MyControllerOne。第二个路由是一个常量Admin,默认的控制器为Admin。这两个都是正确的路由配置。可是当咱们输入urlxxx.com/admin时,咱们预想的是请求AdminController下的Index操做方法。可是根据上面的路由映射,该url会匹配第一个路由,而后就中止了路由查找。此时触发的ControllerMyControllerOne


路由约束

以前的路由配置,都没有url的参数的类型信息。若是咱们的Action是一个Int类型,可是url中的参数是个字符串,这样就会致使错误。因此若是有url的类型约束能够规避这个错误的发生。

在Asp.Net MVC中咱们能够经过正则表达式来约束路由。

如:

routes.MapRoute{
    "Default",
    "{controller}/{action}/{id}",
    new{controller="Home",action="Index",id=UrlParameter.Optional},
    new{id="\d+"}//该id为整数
}

除了使用正则表达式来约束路由,咱们还能够经过继承IRouteConstraint接口自定义约束规则

如:

public class MyRouteConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        //获取id的值
        var id = values[parameterName];

        //id验证方法

        return true;
    }
}

更新路由配置

routes.MapRoute{
    "Default",
    "{controller}/{action}/{id}",
    new{controller="Home",action="Index",id=UrlParameter.Optional},
    new{id=new MyRouteConstraint()}
}

That's it

参考资料:


若有不对,请多多指教。

相关文章
相关标签/搜索