ASP.NET MVC 路由(Routing)
System.Web.Routing下的类型
此命名空间下定义了与路由机制有关的类型
RouteTable(路由表)
表示全局路由的类
RouteCollection(路由集合)
表示路由列表的类,此类通过RouteTable.Routes获得。
Ignore ( )//如果RouteExistingFiles是默认的false,则任何文件都可以通过请求该文件的物理地址从而得到这个文件//如果RouteExistingFiles被手动设为true,那么任何文件都不再可以通过请求该文件的物理地址从而得到这个文件//所以在RouteExistingFiles为true的情况下,如果我们想增加一些例外,比如你不希望css或js文件的请求需要通过路由地址访问,//你希望可以通过在浏览器直接输入文件的物理地址就能访问该类型的文件,那么此时才有使用Ignore()方法的必要。//注意,Ignore()方法必须放在路由注册的前面,否则无效//示例:routes.RouteExistingFiles = true; //请求的任何文件必须使用路由地址访问routes.Ignore ( "{resource}.css/{*pathInfo}" ); //指示css文件不必通过路由地址访问 IgnoreRoute ( )//与Ignore()方法等同 MapPageRoute ( )//注册一个路由,将Url地址映射为路由地址,比如将一个aspx页面地址映射为路由地址,这样你可以通过路由地址对default.aspx进行访问。//示例:RouteTable.Routes.MapPageRoute ( "" , "employees/{name}/{id}/{*other}" , "~/Default.aspx" , true , defaults );//有了以上的针对具体某一个页面(default.aspx)的路由映射,现在可以直接在浏览器输入http://localhost/employees/sam/1001访问到default.aspx MapRoute ( )//RouteCollectionExtentions为RouteCollection定义的扩展方法,此方法需要引用System.Web.Mvc程序集。//此方法专门用于在ASP.NET MVC框架下注册一个路由,区别于MapPageRoute ( )方法//此方法并非将某一页面的物理地址映射为一个路由地址,它是定义了一个与任何页面进行匹配的路由模板,且模板必须具有Contrller和Action变量。//以下使用MapRoute ( )方法添加了一个路由//注意,MapRoute()方法的Defaults、Constraint参数不再是RouteValueDictionary,而是object,这是为了编程的便利性,最终它们还是会被路由机制自动转化为RouteValueDictionary//示例:routes.MapRoute ( name: "Default" , url: "{controller}/{action}/{id}" , defaults: new { controller = "Home" , action = "Index" , id = UrlParameter.Optional }); //当请求的路由地址被服务端接收后,服务端将在全局路由范围内查找匹配的路由模板//假设在另一个项目或不同命名空间下存在同名的Controller,那么路由机制如何确定应将请求路由到哪个Controller中呢?//通过指定namespaces可以使路由查找机制优先考虑使用namespaces所指定的Controllerroutes.MapRoute ( name: "Default" , url: "{controller}/{action}/{id}" , namespaces: new string [ ] { "My.Controllers" } , //优先使用My.Controllers命名空间下的Controller defaults: new { controller = "Home" , action = "Index" , id = UrlParameter.Optional }); GetVirtualPath ( RequestContext requestContext,RouteValueDictionary values )//遍历全局路由列表,查找与values存储的路由变量完全匹配的路由//如果匹配成功则返回一个VirtualPathData对象,否则返回null。通过VirtualPathData.VirtualPath可以获取到匹配成功后的路由地址//示例:在某个页面中:string employeeID = RouteData.Values [ "id" ] as string;System.Web.Routing.RequestContext context = new System.Web.Routing.RequestContext ( );context.HttpContext = new HttpContextWrapper ( HttpContext.Current );context.RouteData = RouteData;string s = RouteData.Route.GetVirtualPath ( context , RouteData.Values ).VirtualPath; //return like:/employees/sam/1001 GetVirtualPath ( RequestContext requestContext,RouteValueDictionary values , string RouteName )//根据参数指定的路由名称找到该路由,将values存储的路由变量与该路由的路由模板进行匹配//如果匹配成功则返回一个VirtualPathData对象,否则返回null。通过VirtualPathData.VirtualPath可以获取到匹配成功后的路由地址 GetRouteData(RequestContext httpContext);//如果当前请求的Url成功匹配了路由模板则返回与当前请求相关的、存储了路由信息的RouteData实例//示例:手动创建一个HttpContextBase,通过代码来模拟一次客户端的请求以便获得RouteDataHttpRequest request = new HttpRequest ( "/Home/Index" , "http://localhost:53326/Home/Index" , null );HttpResponse response = new HttpResponse ( new System.IO.StringWriter ( ) );HttpContext context = new HttpContext ( request , response );HttpContextBase contextWraper = new HttpContextWrapper ( context ); routeData =RouteTable.Routes.GetRouteData ( contextWraper ); RouteExistingFiles//如果将此属性设为true,则所有Url请求都将强制路由匹配。默认false,也即在默认情况下,Url地址的模式可以是某个文件地址的模式也可以是路由模板的模式//比如假设现在请求的是aspx页面,在浏览器直接输入此页面的地址:http://localhost/default.aspx,因为默认情况下RouteExistingFiles是false,所以对这个页面的直接请求并不会进入路由匹配。
Route(路由)
表示单个路由的类
Defaults;//一个RouteValueDictionary泛型字典集合,用于为路由的路由模板设置变量的默认值//示例:Route route = new Route ( "employees/{name}/{id}/{*other}" , new PageRouteHandler ( "~/default.aspx" );route.Defaults= new RouteValueDictionary{ { "name","sam" }, { "id","1001" }}; Constraints;//一个RouteValueDictionary泛型字典集合,用于为路由设置约束,如果当前路由匹配成功,就要进入约束,约束用于验证某些自定义的逻辑,没通过验证则路由会失败route.Constraints = new RouteValueDictionary{ { "controller" , @"\d{4}" }, //为路由模板的controller变量设置了正则式约束 { "httpMethod", new HttpMethodConstraint ( "GET" ) } //设置了请求约束,请求必须是Get请求}; GetRouteData ( )//将请求的Url和路由进行匹配,如果匹配失败,返回null,否则返回一个RouteData实例 GetVirtualPath ( RequestContext requestContext,RouteValueDictionary values )//将values存储的路由变量与当前路由的路由模板进行匹配,如果匹配成功则返回一个VirtualPathData对象,否则返回null。通过VirtualPathData.VirtualPath可以获取到匹配成功后的路由地址 GetRequiredString ( string key )//在RouteData的Values中查找路由变量所对应的值,key是路由变量的名称。注意,如果key不存在则会抛出异常。如果要获取查询字符串则依然是使用Request["查询字符变量"] Url//获取路由对象的模板,即类似这样的路由模板:employees/{name}/{id}
RouteData(路由信息)
表示存储路由有关信息的类,此对象可以通过在Controller类或Page类中的RouData属性访问得到。
Route//获取路由对象 Values//一个RouteValueDictionary的泛型字典集合,存储了路由变量的默认值。Key是路由变量的名称,Value是变量的值。
//这是在RouteCollection的MapRoute方法中通过defaults参数配置的对象
//存储的是模板变量的默认值
//请求的Url如果与路由模板匹配成功则此字典存储的变量默认值会被替换为Url中的与Key对应的变量值
//如果Key不存在则会抛出异常
//如果要获取查询字符串则依然是使用Request["查询字符变量"] RouteHandler//一个实现了IRouteHandler接口的对象,默认是MvcRouteHandler类型的对象
//该对象提供接口方法GetHttpHandler(),该方法可以返回一个用于处理当前路由请求的处理程序,此属性用于设置或获取可处理当前路由请求的IRouteHandler对象
RouteValueDictionary(路由变量字典)
路由模板中的变量都存储在字典中,在ASP.NET MVC中,一个路由模板必须包含名称为Controller和Action的变量,以下两种方式都可以向字典添加键值对。
object obj = new { controller = "default" ,action="index"};RouteValueDictionary routeValue = new RouteValueDictionary ( obj );var controllerName= routeValue [ "controller" ];var actionName = routeValue [ "action" ];//或RouteValueDictionary routeValue2= new RouteValueDictionary{ { "controller","default" }, { "action","index" }}
VirtualPathData(路由信息)
表示存储路由有关信息的类
Route//获取路由对象 VirtualPath//获取已经成功匹配的路由所生成的路由地址,如类似这样:employee/3001
RequestContext(特殊的Http上下文)
此类封装了当前的路由信息和Http请求的上下文
HttpContext//获取当前Http请求的上下文 RouteData//当前的路由信息
ASP.NET中的路由
ASP.NET本身就具有路由的机制,只不过没有得到使用,当发起一个请求时是直接请求的该文件的物理地址。其实在System.Web.Routing命名空间中就有一些关于路由的类型,我们也可以使用这些类型将Url请求映射为路由地址,一个传统的Url请求可能是这样的:
http://www.cnblogs.com/employees/GetList.aspx
而路由地址是这样的:
http://www.cnblogs.com/em/All
传统的Url请求中的employees是文件夹,GetList则是具体的物理文件,而路由请求中的em不是文件夹,All也不是物理文件。路由地址的好处显而易见,它隐藏了真实文件的地址,而且便于搜索引擎优化。
ASP.NET MVC中的路由
ASP.NET MVC也使用System.Web.Routing命名空间中路由机制,但它有别于ASP.NET WEB Forms中的路由,在System.Web.Mvc程序集中有一个RouteCollectionExtentions类,此类为System.Web.Routing.RouteCollection类定义了一系列的扩展方法专门用于ASP.NET MVC框架下的路由机制。扩展出了MapRoute、IgnoreRoute方法,使用MapRoute注册的路由必须具有Contrller和Action,以便路由机制在成功匹配客户端请求的路由地址后可以将处理请求的程序(IHttpHandler)对接到对应的Controller和Action上。
注册路由
路由匹配的意思是:当用户在浏览器地址栏输入一个Url后,这个Url请求会发送到服务端,由服务端的处理程序解析,所有的路由都注册在RouteTable的路由列表中,所以路由处理程序会自动在RouteTable的路由列表中搜索与Url匹配的路由,如果这个Url能匹配某个路由模板,就会自动进入该路由所对应的Controller和Acion以便处理Http请求。路由模板定义在Route的Url属性中,也即如果我们要注册一个路由,就要为路由定义一个路由模板,这个模板被放在Route的Url属性中。
定义路由模板
Route具有一个Url属性,该属性用于为路由设置一个路由模板
Url: "employee / { controller } / { action } / { id }"//这是一个路由模板(也称Url模板、Url模式)//每个被 / 分隔的段称为segment//每个segment可以有两种类型的参数//要么是segment常量,要么是segment变量//segment常量就是一个文本值//segment变量则使用:{ 变量名 },以此作为占位符
路由匹配规则
请求的Url的段数与路由模板的段数必须相等且常量值相等。
所以下面的Url可以匹配上面定义的路由模板:
employee匹配了路由模板的常量值
details匹配了路由模板的Controller变量,即Controller变量的值是details
employeeDetails匹配了路由模板的Action变量,即Action变量的值是employeeDetails
1001匹配了路由模板的Id变量,即id变量的值是1001
注:常量不会当做控制器或Action,这两个段必须以变量的形式定义
路由模板的变量默认值
Route具有一个Defaults的属性,设置Defaults是预防当请求的Url没有提供变量值时将使用默认值填充路由模板,如果请求的Url提供了变量值则不会应用Defaults。
defaults: new { controller = "Home" , action = "Index" }//如果请求的Url没有提供controller变量的值和action变量的值则使用默认值进行填充
因为请求的Url没有提供变量值,所以使用了默认值对模板进行填充,模板最终生成的地址是:employees/Home/Index
默认值UrlParameter.Optional
如果不确定要为变量设置怎样的默认值,那就将变量的默认值设为UrlParameter.Optional即可。
url: "{controller}/{action}/{id}" ,defaults: new { controller = "Home" , action = "Index" , id = UrlParameter.Optional }
无需匹配的变量值
使用*号定义的变量不会参与匹配,如:
url: "Files / { controller } / { action } / { name } / { *otherSegments }"//请求的Url如果没有最后一个段,也能匹配,如果请求的Url提供了otherSegments的值。那么这样的变量会存储在RouteValueDictionary字典中,可通过Key获取
路由约束
Route具有一个constraints属性,该属性用于为路由设置约束。有两种方式可以添加路由约束,一是使用正则式,二是自定义一个约束类
正则式约束
//约束了controller变量的值必须是4个数字 同时该请求必须是GET的constraints: new { controller = @"\d{4}", httpMethod = new HttpMethodConstraint ("GET") },
自定义约束
自定义约束类必须实现IRouteConstraint接口,实现的Match()方法返回true表示通过验证。以下创建了一个LocalhostConstraint的类,它实现的Match()方法用于检测请求是否来自本地计算机,如果不是则不匹配路由。
public class LocalhostConstraint : IRouteConstraint{ public bool Match ( HttpContextBase httpContext , Route route , string parameterName , RouteValueDictionary values , RouteDirection routeDirection ) { return httpContext.Request.IsLocal; }}
接着我们需要在注册的路由上使用该约束 只需要在constraints中初始化该约束类即可
constraints: new { LocalhostConstraint = new LocalhostConstraint ( ) }
获取或创建路由地址
有两个类可以用来生成基于路由模板的Url连接(路由地址)。
UrlHelper类
此类封装了特殊的Http上下文(RequestContext)和路由列表集合(RouteCollection),其Action方法的内部实现就是通过获取RequestContext和RouteCollection取出当前的路由信息并返回拼接好的路由地址。
Action( )//根据参数获取一个路由地址//示例:UrlHelper作为Controller的属性Url被使用,所以可以在控制器的Action中使用UrlHelperpublic ActionResult Index ( ){ string s0 = Url.Action ( ); // like:/Default/Index string s1 = Url.Action ( "index" , new { controller = "default" , id = 1001 } ); // like:/default/index/1001 string s3 = Url.Action ( "index" , "default" , new RouteValueDictionary { { "id" , 1001 } } , "http" , "www.cnblogs.com" ); // like:http://www.cnblogs.com/default/index/1001 return View ( );} RouteUrl( )//与Action方法类似,区别在于可以指定路由名称,UrlHelper将根据路由的名称生成一个路由地址
HtmlHelper类
封装了视图上下文(ViewContext)和路由列表集合(RouteCollection),视图上下文(ViewContext)从控制器上下文(ControllerContext)派生。
ActionLink ( )//根据参数创建一个包含路由地址的超链接//Controller类没有HtmlHelper类型的属性,HtmlHelper是作为WebViewPage<dynamic>类的一个属性(Html)而存在的//所以只能在视图页面使用这个类//示例:HtmlHelper作为View的属性Html被使用,所以可以在视图页面使用HtmlHelper<body> @Html.ActionLink( "员工列表" , "employeeList" ) /*http://localhost/Default/employeeList*/ @Html.ActionLink( "员工列表" , "employeeList" , new { gender = "men" } ) /*http://localhost/Default/employeeList?gender=men*/ @Html.ActionLink( "员工列表" , "employeeList" , new RouteValueDictionary { { "gender" , "men" } } ) /*同上*/ @Html.ActionLink( "员工列表" , "employeeList" , "Employee" , new { gender = "men" } , new { fontSize = "12px" } ) /*http://localhost/Employee/employeeList?gender=men*/ @Html.ActionLink( "员工列表" , "employeeList" , "Employee" , "http" , "cnblogs" ,"", new { gender = "men" } , new { fontSize = "12px" } ) /*http://cnblogs/Employee/employeeList?gender=men*/</body> RouteLink ( )//与ActionLink方法类似,区别在于可以指定路由名称,HtmlHelper将根据路由的名称创建一个包含路由地址的超链接 Action ( )//根据参数创建一个路由地址并自动发起基于该路由地址的请求,并返回一串HTML,HTML是从服务端返回的数据
区域(Area)
如果项目结构非常庞大就需要使用区域,可以把区域看成是项目的一个一个的子系统。右击项目-添加-区域即可创建一个区域,每个区域都包含了Controller、Model和View目录。比如以下创建了一个名称为FirstArea的区域,区域默认包含在代表全部区域的总目录Areas中,此后可以右击Areas目录来创建更多的区域。
区域路由地址中的段默认会加上区域的名称,如:
每个区域都配置有一个后缀为xxxRegistration的cs文件,这样的文件中都定义了一个派生自AreaRegistration类(区域基类)的子类,表示为一个区域。在区域类的RegisterArea方法中可以在当前区域下注册路由,如下面代码所示:
public class FirstAreaAreaRegistration : AreaRegistration { public override string AreaName { get => "FirstArea"; } public override void RegisterArea(AreaRegistrationContext context) { context.MapRoute( //在当前区域注册路由 "FirstArea_default", "FirstArea/{controller}/{action}/{id}", new { action = "Index", id = UrlParameter.Optional } ); }}
在Global文件的Application_Start事件中默认使用了AreaRegistration的静态方法将所有区域进行了统一的初始化。
public class MvcApplication : System.Web.HttpApplication{ protected void Application_Start ( ) { AreaRegistration.RegisterAllAreas ( ); //…… }}
与区域有关联的类
AreaRegistration(区域)
此类表示为区域基类,我们在创建一个区域时就是在创建一个AreaRegistration的派生(上面例子中的FirstAreaAreaRegistration就是一个AreaRegistration的子类)。AreaRegistration提供一个RegisterAllAreas的静态方法,此方法会扫描所有被当前项目所引用的程序集,查找所有从AreaRegistration派生的类型,同时会new出每一个区域的实例,这些实例都将被看成是AreaRegistration类型的变量。与此同时,RegisterAllAreas方法还会针对每一个区域创建它们所对应的AreaRegistrationContext实例,区域实例的RegisterArea方法需要一个AreaRegistrationContext作为参数以便注册区域路由,所以RegisterAllAreas创建出AreaRegistrationContext实例后会将其作为参数传递给对应的区域实例的RegisterArea方法以便调用AreaRegistrationContext的MapRoute方法对区域路由进行注册。
//区域FirstAreaAreaRegistration的RegisterArea方法接收一个AreaRegistrationContextpublic override void RegisterArea(AreaRegistrationContext context) { context.MapRoute( "FirstArea_default", "FirstArea/{controller}/{action}/{id}", new { action = "Index", id = UrlParameter.Optional } );}
AreaRegistrationContex(区域上下文)
AreaName//区域的名称 Routes//获取全局路由,包含区域路由,返回一个RouteCollection列表。注:如果在RegisterArea方法中通过AreaRegistrationContext直接获取路由列表的路由个数,则只返回Areas目录下所包含的全部区域路由个数,不包含非区域内的路由//示例://在FirstArea区域下的Controller目录中创建了DefaultController并为DefaultController自定义了两个属性RouteList和RouteCountnamespace MVCArea.Areas.FirstArea.Controllers{ public class DefaultController : Controller { public static System.Web.Routing.RouteCollection RouteList { get; set; } public static int RouteCount { get; set; } public ActionResult Index() { ViewData [ "RouteList" ] = RouteList.Count; //包含全局路由,区域路由的路由列表总个数 ViewData [ "RouteCount" ] = RouteCount; //只有Areas目录下所包含的全部区域路由个数 return View(); } }}//在FirstAreaRegistration类的RegisterArea方法中通过AreaRegistrationContext访问路由列表FirstArea.Controllers.DefaultController.RouteList = context.Routes; //返回全局路由和区域路由的路由列表,在外部调用RouteList.Count时会包含这些路由列表总个数FirstArea.Controllers.DefaultController.RouteCount = context.Routes.Count; //在RegisterArea方法中则只返回Areas目录下所包含的全部区域路由个数 State//一个object类型的用户自定义的对象,当在Global文件的Application_Start事件中执行AreaRegistration的重载方法RegisterAllAreas(object State)时,State将会赋值给创建出的AreaRegistrationContext的State属性 Namespaces//这是一个只读的属性,该属性已经由ASP.NET MVC框架自动实现,用以当在全局路由列表中匹配上多个同名Controlle时将自动优先考虑AreaRegistrationContext所关联的区域下的Controller,如果在AreaRegistrationContex的MapRoute中显示指定了命名空间,则此属性会被自动覆盖。 MapRoute()//在当前AreaRegistrationContext所关联的区域下注册一个区域性的路由//示例:在区域类的RegisterArea方法中注册区域路由:public override void RegisterArea ( AreaRegistrationContext context ){ context.MapRoute ( name: "" , url: "FirstArea/{controller}/{action}/{id}" , defaults: new { action = "Index" , id = UrlParameter.Optional } );}
MVC-AreaRegistrationTypeCache.xml
这是一个临时缓存的xml文档,ASP.NET MVC框架会扫描当前项目引用的程序集,利用反射将程序集中的从AreaRegistration派生的类型集合成一个列表,然后对它们进行反序列化生成xml文档,这样,当AreaRegistration调用它的静态方法RegisterAllAreas创建区域实例的时候,就可以直接从xml文档将AreaRegistration列表反序列化为类型列表,省去了每次都要反射造成资源大量消耗的麻烦。
ASP.NET的管道
windows有一个叫做w3wp的exe工作进程,它与IIS(因特网信息服务器)绑定在一起,从IIS6开始,微软引入了应用程序池的概念,一个应用程序池对应着一个w3wp.exe,多个应用程序池则对应着多个w3wp.exe。当首次接收到Http请求时,w3wp.exe会创建.NET运行时(即把ASPNET_ISAPI.dll程序集载入).net运行时搭建完成后,就会将Http请求转交给一个windows的核心组件,叫做Http.Sys,它提供Http访问的接口,使你的应用程序可通过该接口进行Http通信。当Http.Sys确定它是首次接到转交过来的Http请求后,它将创建出一个AppDomainFactory(管理应用程序域的工厂)对象,该对象则会为web应用程序创建出一个应用程序域。接着,定义在System.Web.dll中的System.Web.Hosting命名空间下的IsapiRuntime类被创建出来,从此时开始,IsapiRuntime将接管所有的Http请求。IsapiRuntime会创建一个IsapiWorkerRequest对象,这个对象专门用于封装接收到的Http请求,封装完成后,IsapiRuntime会将IsapiWorkerRequest当做参数传递给ASP.NET运行时(HttpRuntime),于是,Http请求正式进入了ASP.NET的处理管道。也即,第一个Http请求从发起-到被Http.Sys接收-到为web程序创建应用程序域-到载入System.Web.dll程序集的System.Web.Hosting命名空间下的IsapiRuntime-到由IsapiRuntime创建封装Http请求的IsapiWorkerRequest-到把IsapiWorkerRequest传递给ASP.NET的HttpRuntime(ASP.NET运行时), Http请求才终于进入了我们能掌控的阶段。接下来一切就由ASP.NET掌控全局了。此时,HttpRuntime会做两样工作,第一,它会根据传递进来的IsapiWorkerRequest创建出表示Http请求的上下文对象,也就是我们灰常熟悉的HttpContext对象。要获取客户端请求的数据或向客户端写入数据都是通过上下文对象的Request、Response来完成的。第二,HttpApplicationFactory会从HttpApplication对象池中任取一个空闲的HttpApplication对象用于处理Http请求,假如池中没有空闲的HttpApplication对象,那么HttpApplicationFactory会自动创建新的HttpApplication用来处理Http请求。当Http请求处理完毕后则会自动将HttpApplication对象释放到池中。
HttpModule(Http请求拦截器)和 HttpHandler(Http处理程序)
请求拦截器也称为Http模块,你可以在web.config文件的<system.webServer>配置节下注册自定义的HttpModule模块。HttpApplication会自动从web.config文件中扫描模块,模块都必须实现IHttpMoudle接口,接口定义了Dispose和Init两个方法,Init会接收一个HttpApplication的参数。而在ASP.NET MVC中,拦截请求的是一个实现了IHttpMoudle的UrlRoutingModule类,它实现HttpModule的Init方法(初始化模块),在这个方法中只需要为HttpApplication注册一个叫做PostResolveRequestCache的事件就能拦截请求,UrlRoutingModule的部分实现大概如下面代码所示:
public class UrlRoutingModule : IHttpModule{ public RouteCollection RouteCollection { get => RouteTable.Routes; set; } protected virtual void Dispose ( ); //实现IHttpModule接口的Dispose方法 protected virtual void Init ( HttpApplication application ) //实现IHttpModule接口的Init方法 { application.PostResolveRequestCache += OnApplicationPostResolveRequestCache; //为HttpApplication注册PostResolveRequestCache事件 } private void OnApplicationPostResolveRequestCache ( object sender , EventArgs e ) //PostResolveRequestCache事件的事件处理程序 { HttpContext contex = ( sender as HttpApplication ).Context; HttpContextBase contextWrapper = new HttpContextWrapper ( contex ); RouteData routeData = RouteCollection.GetRouteData ( contextWrapper ); //将请求的Url和路由进行匹配,如果匹配成功则返回一个存储当前路由信息的RouteData实例 //封装Http上下文对象和路由信息的特殊Http上下文对象,此对象将作为参数传递给HttpHandler处理程序,以便处理程序能得到一些当前路由的信息 RequestContext requestContext = new RequestContext { HttpContext = contextWrapper , RouteData = routeData }; IHttpHandler handler= routeData.RouteHandler.GetHttpHandler ( requestContext ); //获取当前路由的处理程序,此处可以自定义一个处理程序 contex.RemapHandler ( handler ); //为当前请求指定处理程序 }}
路由匹配正是在Application的PostResolveRequestCache事件处理中执行的,一旦匹配成功GetRouteData 方法会返回一个RouteData实例,RouteData的RouteHandler属性是一个内置的MvcRouteHandler类型的对象,此对象实现了IRouteHandler接口,该接口定义了一个GetHttpHandler(RequestContext requestContext )的方法,此方法的GetHttpHandler方法返回的是一个实现了IHttpHandler接口的MvcHandler类型的对象,所以,默认情况下对路由请求的处理都是由MvcHandler完成的。你也可以自定义一个Http模块和HttpHandler处理程序,具体实现如下:
namespace MVC.Controllers{ public class MyRoutingModule : IHttpModule { public RouteCollection RouteCollection { get => RouteTable.Routes; } public void Dispose ( ) { } public void Init ( HttpApplication application ) { application.PostResolveRequestCache += OnApplicationPostResolveRequestCache; } private void OnApplicationPostResolveRequestCache ( object sender , EventArgs e ) { HttpContext contex = ( sender as HttpApplication ).Context; HttpContextBase contextWrapper = new HttpContextWrapper ( contex ); RouteData routeData = RouteCollection.GetRouteData ( contextWrapper ); RequestContext requestContext = new RequestContext { HttpContext = contextWrapper , RouteData = routeData }; routeData.RouteHandler = new MyRouteHandler ( ); IHttpHandler handler = routeData.RouteHandler.GetHttpHandler ( requestContext ); //针对Http路由请求的自定义处理程序 contex.RemapHandler ( handler ); //为当前请求指定处理程序 } } public class MyRouteHandler : IRouteHandler { public IHttpHandler GetHttpHandler ( RequestContext requestContext ) { return new MyHttpHandler ( ); } //嵌套类 private class MyHttpHandler : IHttpHandler { public bool IsReusable => throw new NotImplementedException ( ); public void ProcessRequest ( HttpContext context ) { context.Response.Write ( "这是自定义的路由处理程序" ); } } }}
<system.webServer> <modules> <add name="MyRoutingModule" type="MVC.Controllers.MyRoutingModule"/> </modules></system.webServer>