ASP.NET Core应用中的路由机制实现在RouterMiddleware中间件中,它的目的在于通过路由解析为请求找到一个匹配的处理器,同时将请求携带的数据以路由参数的形式解析出来供后续请求处理流程使用。但是具体的路由解析功能其实并没有直接实现在RouterMiddleware中间件中,而是由一个Router对象来完成的。[本文已经同步到《ASP.NET Core框架揭秘》之中]

目录
一、IRouter接口
二、RouteContext
三、RouteData
四、Route
五、RouteHandler
总结

一、IRouter接口

Router是我们对所有实现了IRouter接口的所有类型以及对应对象的统称,如下面所示的RouterMiddleware类型定义可以看出,当我们创建这个中间件对象的时候,我们需要指定这个Router。

   1: public class RouterMiddleware
   2: {
   3:     public RouterMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IRouter router);
   4:     public Task Invoke(HttpContext httpContext);
   5: }

除了检验请求是否与自身设置的路由规则相匹配,并在成功匹配的情况下解析出路由参数并指定请求处理器之外,Router的路由解析还为另一个领用场景服务,那就是根据自身的路由规则和提供的参数生成一个URL。我们把这两个方面称为路由的两个“方向”,它们分别对应着RouteDirection枚举的两个选项。针对这两个方向的路由解析分别实现在IRouter的如下两个方法(RouteAsync和GetVirtualPath),目前我们主要关注针对前者的RouteAsync方法。

   1: public interface IRouter
   2: {
   3:     Task RouteAsync(RouteContext context);
   4:     VirtualPathData GetVirtualPath(VirtualPathContext context);
   5: }
   6:  
   7: public enum RouteDirection
   8: {
   9:     IncomingRequest,
  10:     UrlGeneration
  11: }

如上面的代码片段所示,针对请求实施路由解析的RouteAsync方法的输入参数是一个类型为RouteContext的上下文对象。这个RouteContext实际上是对一个HttpContext对象的封装,Router可以利用它得到所有与当前请求相关的信息。如果Router完成路由解析并判断当前请求与自身的路由规则一致,那么它会将解析出来的路由参数转换成一个RouteData并存放到RouteContext对象代表的上下文之中,另一个一并被放入上下文的是代表当前请求处理器的RequestDelegate对象。下图基本上展示了RouteAsync方法试试路由解析的原理。

二、RouteContext

接下来我们来了解一下整个路由解析涉及到了几个核心类型,首先来看看为整个路由解析提供执行上下文的这个RouteContext类型。如上图所示,一个RouteContext上下文包含三个核心对象,一个是代表当前请求上下文的HttpContext对象,对应的属性是HttpContext。它实际上是作为路由解析的输入,并在RouteContext创建的时候以构造函数参数的形式提供。另外两个则是作为路由解析的输出,一个是代表存放路由参数的RouteData对象,另一个则是作为请求处理器的RequestDelegate对象,对应的属性分别是RouteData和Handler。

   1: public class RouteContext
   2: {
   3:     public HttpContext         HttpContext { get; }
   4:     public RouteData           RouteData { get; set; }
   5:     public RequestDelegate     Handler { get; set; }
   6:  
   7:     public RouteContext(HttpContext httpContext);
   8: }

三、RouteData

我们先来看看用于存放路由参数的RouteData类型。从数据来源的角度来讲,路由参数具有两种类型,一种是通过请求路径携带的参数,另一种则是Router对象自身携带的参数,这两种路由参数分别对应着RouteData的Values和DataTonkens属性。至于另一个属性Routers,则保存着实施路由解析并提供路由参数的所有Router对象。

   1: public class RouteData
   2: {
   3:     public RouteValueDictionary     Values { get; }
   4:     public RouteValueDictionary     DataTokens { get; }
   5:     public IList<IRouter>           Routers { get; }
   6: }
   7:  
   8: public class RouteValueDictionary : IDictionary<string, object>, IReadOnlyDictionary<string, object>
   9: {
  10:     public RouteValueDictionary(object values);   
  11:     …
  12: }

从上面的代码片可以看出,RouteData的Values和DataTokens属性的类型都是RouteValueDictionary,它实际上就是一个字典对象而已,其Key和Value分别代表路由参数的名称和值,而作为Key的字符串是不区分大小写的。值得一提的是RouteValueDictionary具有一个特殊的构造函数,作为唯一参数的是一个object类型的对象。如果我们指定的参数是一个RouteValueDictionary对象或者是一个元素类型为KeyValuePair<string, object>>的集合,指定的数据将会作为原始数据源。这个特性体现在如下所示的调试断言中。

   1: var values1 = new RouteValueDictionary() ;
   2: values1.Add("foo", 1);
   3: values1.Add("bar", 2);
   4: values1.Add("baz", 3);
   5:  
   6: var values2 = new RouteValueDictionary(values1);
   7: Debug.Assert(int.Parse(values2["foo"].ToString()) == 1);
   8: Debug.Assert(int.Parse(values2["bar"].ToString()) == 2);
   9: Debug.Assert(int.Parse(values2["baz"].ToString()) == 3);
  10:  
  11: values2 = new RouteValueDictionary(new Dictionary<string, object>
  12: {
  13:     ["foo"] = 1,
  14:     ["bar"] = 2,
  15:     ["baz"] = 3,
  16: });
  17: Debug.Assert(int.Parse(values2["foo"].ToString()) == 1);
  18: Debug.Assert(int.Parse(values2["bar"].ToString()) == 2);
  19: Debug.Assert(int.Parse(values2["baz"].ToString()) == 3);

RouteValueDictionary的这个构造函数的特殊之处其实并不止于此。除了将一个自身具有字典结构的对象作为原始数据源作为参数之外,我们还可以将一个普通的对象作为参数,在此情况下这个构造函数会解析定义在对象自身类型的所有属性定义,并将属性名称和值作为路由参数的名称和值。如下面的代码片段所示,我们创建一个匿名类型的对象并根据它来创建一个RouteValueDictionary,这种方式在MVC应用使用得比较多。

   1: RouteValueDictionary values = new RouteValueDictionary(new 
   2: {
   3:     Foo = 1,
   4:     Bar = 2,
   5:     Baz = 3
   6: });
   7:  
   8: Debug.Assert(int.Parse(values["foo"].ToString()) == 1);
   9: Debug.Assert(int.Parse(values["bar"].ToString()) == 2);
  10: Debug.Assert(int.Parse(values["baz"].ToString()) == 3);

由于RouteData被直接置于RouteContext这上下文中,所以任何可以访问到这个上下文的对象都可以随意地修改其中的路由参数,为了全局对象造成的“数据污染”问题,一种类型与“快照”的策略被应用到RouteData上。具体来说,我们为某个RouteData当前的状态创建一个快照,在后续的某个时刻我们利用这个快照让这个RouteData对象回复到当初的状态。

针对RouteData的这个快照通过具有如下定义的结构RouteDataSnapshot表示。当我们创建这个一个对象的时候,需要指定目标RouteData对象和当前的状态(Values、DataTokens和Routers)。当我们调用其Restore方法的时候,目标RouteData将会恢复到快照创建时的状态。我们可以直接调用RouteData的PushState为它自己创建一个快照。

   1: public struct RouteDataSnapshot
   2: {
   3:     public RouteDataSnapshot(RouteData routeData, RouteValueDictionary dataTokens, IList<IRouter> routers, RouteValueDictionary values);
   4:     public void Restore();
   5: }
   6:  
   7: public class RouteData
   8: {
   9:     public RouteDataSnapshot PushState(IRouter router, RouteValueDictionary values, RouteValueDictionary dataTokens);
  10: }

如下面的代码片段所示,我们创建了一个RouteData对象并调用其PushState方法为它创建了一个快照,调用该方法指定的三个参数均为null。虽然我们在后续步骤中修改了这个RouteData的状态,但是一旦我们调用了这个RouteDataSnapshot对象的Restore方法,这个RouteData将重新恢复到最初的状态。

   1: RouteData routeData = new RouteData();
   2: RouteDataSnapshot snapshot = routeData.PushState(null, null, null);
   3:  
   4: routeData.Values.Add("foo", 1);
   5: routeData.DataTokens.Add("bar", 2);
   6: routeData.Routers.Add(new RouteHandler(null));
   7:  
   8: snapshot.Restore();
   9: Debug.Assert(!routeData.Values.Any());
  10: Debug.Assert(!routeData.DataTokens.Any());
  11: Debug.Assert(!routeData.Routers.Any());

四、Route

除了IRouter这个最为基础的接口之外,路由系统中还定义了额外一些接口和抽象类,其中就包含如下这个INamedRouter接口。这个接口代表一个“具名的”Router,说白了就是这个Router具有一个通过属性Name表示的名字。

   1: public interface INamedRouter : IRouter
   2: {
   3:     string Name { get; }
   4: }

所有具体的Route基本上都最终继承自如下这个抽象基类RouteBase,前面演示实例体现的基于“路由模板”的路由解析策略就体现在这个类型中。如下面的代码片段所示,RouterBase实现了INamedRouter接口,所以它具有一个名称作为标识。它的ParsedTemplate属性返回的RouteTemplate对象表示这个路由模板,它的Defaults和Constraints则是针对以内联方式设置的默认值和约束的解析结果。针对内联约束的解析是利用一个InlineConstraintResolver对象来完成的,RouteBase的ConstraintResolver属性返回就是这么一个对象。RouteData的DataTokens来源于Router对象,对应的属性就是DataTokens。

   1: public abstract class RouteBase : INamedRouter
   2: {
   3:     public virtual string                           Name { get; protected set; }
   4:     public virtual RouteTemplate                    ParsedTemplate { get; protected set; }
   5:     protected virtual IInlineConstraintResolver     ConstraintResolver { get; set; }
   6:     
   7:     public virtual RouteValueDictionary                    DataTokens { get; protected set; }
   8:     public virtual RouteValueDictionary                    Defaults { get; protected set; }   
   9:     public virtual IDictionary<string, IRouteConstraint>   Constraints { get; protected set; } 
  10:  
  11:     public RouteBase(string template, string name, IInlineConstraintResolver constraintResolver, RouteValueDictionary defaults, IDictionary<string, object> constraints, RouteValueDictionary dataTokens);
  12:  
  13:     public virtual Task RouteAsync(RouteContext context);
  14:     protected abstract Task OnRouteMatched(RouteContext context); 
  15:     …
  16: }

对于实现在 RouteAsync方法中针对入栈请求而进行的路由解析,RouteBase中的实现只负责判断是否给定的条件是否满足自身的路由规则,并在规则满足的情况下将解析出来的路由参数保存到RouteContext这个上下文中。至于满足路由规则情况下实施的后续操作, 则实现在抽象方法OnRouteMatched中。

我们在进行路由注册的时候经常使用的Route类型是具有如下定义的Route它是上面这个抽象类RouteBase子类。从如下的代码片段我们不难看出,一个Route对象其实是对另一个Router对象的封装,它自身并没有承载任何具体的路由功能。我们在创建这个Route对象的时候,需要提供这个被封装的Router,这个Router对象在重写的OnRouteMatched方法中被添加到RouteData的Routers属性中,随后它的RouteAsync方法被执行。

   1: public class Route : RouteBase
   2: {
   3:     private readonly IRouter _target;
   4:     public string RouteTemplate
   5:     {
   6:         get { return this.ParsedTemplate.TemplateText; }
   7:     }
   8:  
   9:     public Route(IRouter target, string routeTemplate, IInlineConstraintResolver inlineConstraintResolver) : this(target, routeTemplate, null, null, null, inlineConstraintResolver){}
  10:  
  11:     public Route(IRouter target, string routeTemplate, RouteValueDictionary defaults, IDictionary<string, object> constraints, RouteValueDictionary dataTokens, IInlineConstraintResolver inlineConstraintResolver) 
  12:     : this(target, null, routeTemplate, defaults, constraints, dataTokens, inlineConstraintResolver){}
  13:  
  14:     public Route(IRouter target, string routeName, string routeTemplate, RouteValueDictionary defaults, IDictionary<string, object> constraints, RouteValueDictionary dataTokens, IInlineConstraintResolver inlineConstraintResolver) 
  15:        : base(routeTemplate, routeName, inlineConstraintResolver, defaults, constraints, dataTokens)
  16:     {
  17:         _target = target;
  18:     }
  19:  
  20:     protected override Task OnRouteMatched(RouteContext context)
  21:     {
  22:         context.RouteData.Routers.Add(_target);
  23:         return _target.RouteAsync(context);
  24:     }
  25:  
  26:     protected override VirtualPathData OnVirtualPathGenerated(VirtualPathContext context)
  27:     {
  28:         return _target.GetVirtualPath(context);
  29:     }    
  30: }

五、RouteHandler

一个Router在进行针对请求的路由解析过程中需要判断当前请求是否与自身设置的路由规则相匹配,并在匹配情况下将解析出来的路由参数存放与RouteContext这个上下文中,这些都实现在RouteBase这个基类中。由于Route派生于RouteBase,所以它自身也提供了这项基本功能。但是Router还具有另一个重要的任务,那就是在路由匹配情况下将作为处理器的RequestDelegate对象存放到RouteContext上下文中,这个任务最终落实到RouteHandler这个特殊的Router上。

RouteHandler是一种特殊的Router类型,它不仅实现了IRouter接口,还同时实现了另一个IRouteHandler接口,后者提供了一个GetRequestHandler方法根据表示当前请求上下文的HttpContext对象和封装了路由参数的RouteData对象得到一个RequestDelegate对象,后者将会用来处理当前请求。如下面的代码片段所示,我们创建一个RouteHandler对象是需要显式指定一个RequestDelegate对象,GetRequestHandler方法返回的正是这个对象。在实现的RouteAsync方法中,它将这个RequestDelegate赋值给RouteContext的Handler属性。

   1: public class RouteHandler : IRouteHandler, IRouter
   2: {
   3:     private readonly RequestDelegate _requestDelegate;
   4:  
   5:     public RouteHandler(RequestDelegate requestDelegate)
   6:     {
   7:         _requestDelegate = requestDelegate;
   8:     }
   9:  
  10:     public RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData)
  11:     {
  12:         return _requestDelegate;
  13:     }    
  14:  
  15:     public Task RouteAsync(RouteContext context)
  16:     {
  17:         context.Handler = _requestDelegate;
  18:         return Task.CompletedTask;
  19:     }
  20: }
  21:  
  22: public interface IRouteHandler
  23: {
  24:     RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData);
  25: }

基类RouteBase能够确定当前请求是否与自身设置的路由规则相匹配,并在匹配的情况下设置路由参数,而RouteHandler只提供设置请求处理器的功能,但是一个真正的Router必须同时具有这两项功能,那么后者究竟是怎样一个对象呢?我们在上面介绍继承自RouteBase的Route类型时,我们说一个Route对象是对另一个Router对象的封装,那么被封装的Router如果是一个RouteHanlder,那么这个Route对象不就具有完整的路由解析功能了吗?

总结

我们介绍了一系列与Router相关的接口和类,包括IRouter、INameRouter和IRouteHandler接口,抽象类RouteBase,以及两个具体的Route和RouteHandler类性。这些与Router相关额接口和类性具有如下图所示的关系。


ASP.NET Core的路由[1]:注册URL模式与HttpHandler的映射关系
ASP.NET Core的路由[2]:路由系统的核心对象——Router
ASP.NET Core的路由[3]:Router的创建者——RouteBuilder
ASP.NET Core的路由[4]:来认识一下实现路由的RouterMiddleware中间件
ASP.NET Core的路由[5]:内联路由约束的检验

转载于:https://www.cnblogs.com/artech/p/asp-net-core-routing-02.html

ASP.NET Core的路由[2]:路由系统的核心对象——Router相关推荐

  1. ASP.NET Core Blazor Webassembly 之 路由

    web最精妙的设计就是通过url把多个页面串联起来,并且可以互相跳转.我们开发系统的时候总是需要使用路由来实现页面间的跳转.传统的web开发主要是使用a标签或者是服务端redirect来跳转.那今天来 ...

  2. ASP.NET Core MVC 配置全局路由前缀

    前言 大家好,今天给大家介绍一个 ASP.NET Core MVC 的一个新特性,给全局路由添加统一前缀.严格说其实不算是新特性,不过是Core MVC特有的. 应用背景 不知道大家在做 Web Ap ...

  3. 第十五节:Asp.Net Core MVC和WebApi路由规则的总结和对比-第二十节

    一. Core Mvc 1.传统路由 Core MVC中,默认会在 Startup类→Configure方法→UseMvc方法中,会有默认路由:routes.MapRoute("defaul ...

  4. ASP.NET Core 2.1 : 图解路由(2.1 or earler)

    本文通过一张图来看一下路由的配置以及请求处理的机制. 一.概述 路由主要有两个主要功能: 将请求的URL与已定义的路由进行匹配,找到该URL对应的处理程序并传入该请求进行处理. 根据已定义的路由生成U ...

  5. ASP.NET Core的路由[1]:注册URL模式与HttpHandler的映射关系

    ASP.NET Core的路由是通过一个类型为RouterMiddleware的中间件来实现的.如果我们将最终处理HTTP请求的组件称为HttpHandler,那么RouterMiddleware中间 ...

  6. ASP.NET Core的路由[4]:来认识一下实现路由的RouterMiddleware中间件

    虽然ASP.NET Core应用的路由是通过RouterMiddleware这个中间件来完成的,但是具体的路由解析功能都落在指定的Router对象上,不过我们依然有必要以代码实现的角度来介绍一下这个中 ...

  7. java grpc 服务路由_.NET Core 3.0 Preview 3中关于ASP.NET Core的更新内容

    原标题:.NET Core 3.0 Preview 3中关于ASP.NET Core的更新内容 英文:devblogs.microsoft.com 来源:艾心 译文:cnblogs.com/ediso ...

  8. ASP.NET Core MVC 从入门到精通之路由

    随着技术的发展,ASP.NET Core MVC也推出了好长时间,经过不断的版本更新迭代,已经越来越完善,本系列文章主要讲解ASP.NET Core MVC开发B/S系统过程中所涉及到的相关内容,适用 ...

  9. 使用SWAGGER和ASP.NET CORE设置可选路由参数

    使用SWAGGER和ASP.NET CORE设置可选路由参数 根据OpenAPI 3.0,这是不可能的.但是,如果您真的希望成为现实呢?您是否必须解决并允许您的Swagger文档出错?我在这里向您展示 ...

  10. ASP.NET Core - 基于IHttpContextAccessor实现系统级别身份标识

    问题引入: 我们知道当请求通过认证模块时,会给当前的HttpContext赋予当前用户身份标识,我们在需要授权的控制器中打上[Authorize]授权标签,就可以在ControllerBase的Use ...

最新文章

  1. 独家 | 使用Python实现机器学习特征选择的4种方法(附代码)
  2. Vue.js 模板语法
  3. 问题:c语言简单的循环和字符串,错在哪里了?结果怎么是0(已解决)
  4. post multipart/form-data 类型表单如何获取File外其他参数
  5. python接口自动化测试二十二:文件下载
  6. 如何在一台电脑上使用两个git@osc的账号进行操作
  7. 龙芯pmon 中Nand配置说明
  8. 计算机内存条能装几个,4G内存条和2G内存条能不能装到一个电脑上?
  9. bouncing results问题
  10. 捣鼓PlantUML(一、环境)
  11. 正负图表,让数据展示更炫酷
  12. 用OpenStack搭建简单的云平台并启动云主机
  13. 数字电视音视频马赛克和不同步现象原因
  14. 联想手机鸿蒙刷机包,联想手机刷机教程 卡刷
  15. Python导入arcpy包出现“ImportError: No module named arcpy”
  16. Edit conflicts
  17. 发一个随机红包 100块钱给10个人 每个人最多12块钱 最少6块钱 怎么分
  18. 【论文分享】异构图神经网络域名检测方法GAMD:Attributed Heterogeneous Graph Neural Network for Malicious Domain Detection
  19. 流浪的阿猫阿狗我要怎么帮助你?
  20. MCS-51单片机常用汇编指令大全

热门文章

  1. 华为轮值董事长郭平:美国在5G方面已落后
  2. 虚拟化技术中,不同层级结构间的虚拟化运用
  3. 2、pip2.7安装Ansible
  4. 【学习笔记9】Linux常用命令6 - 压缩解压命令
  5. android应用调试之如何保存logcat日志到本地
  6. oracle10g rac导出ocr,Oracle RAC OCR磁盘故障快速恢复方法
  7. head 查看文件前n行
  8. 如何在mysql查询结果集中得到记录行号_如何在MySQL查询结果集中得到记录行号...
  9. mysql5.7.22.zip使用,mysql5.7.22 zip 版安装
  10. java文件删除操作_Java文件复制删除操作合集