当某个请求能够被成功路由的前提是它满足某个Route对象设置的路由规则,具体来说,当前请求的URL不仅需要满足路由模板体现的路径模式,请求还需要满足Route对象的所有约束。路由系统采用IRouteConstraint接口来表示路由约束,所以我们在接下来的内容中将路由约束统称为RouteConstraint。 在大部分情况下,约束都是针对路由模板中定义的某个路由参数,其目的在于验证URL携带的某部分的内容是否有效。不过也有一些约束与路由参数无关,这些约束规范往往是除URL之前的其他请求元素,比如前面提到的HttpMethodRouteConstraint检验的就是请求采用的方法。 [本文已经同步到《ASP.NET Core框架揭秘》之中]

   1: public interface IRouteConstraint

   2: {

   3:     bool Match(HttpContext httpContext, IRouter route, string routeKey, 

   4:     RouteValueDictionary values, RouteDirection routeDirection);

   5: }

如上面的代码片段所示,IRouteConstraint接口仅仅定义了如下一个唯一的Match方法来定义约束规范。方法的参数分别是代表当前请求上下文的HttpContext、当前Router对象、约束在约束字典中的Key(对于针对路由参数的约束,这个Key就是路由参数的名称)、从请求URL解析出来的所有路由参数和路由方向(针对入栈请求进行的路由解析还是为了生成URL而进行的路由解析)。

一、预定义RouteConstraint

路由系统定义了一系列原生的RouteConstraint类型,我们可以使用它们解决很多常见的约束问题,即使现有的RouteConstraint类型无法满足某些特殊的约束需求,我们还可以自定义对应的RouteConstraint类型。对于路由约束的应用,除了直接创建对应的RouteConstraint对象之外,我们知道还可以采用内联的方式直接在路由模板中定义为某个路由参数定义相应的约束表达式。这些以表达式定义的约束类型其实对应着一种具体的RouteConstraint类型。下表列出了两者之间的匹配关系。

内联约束类型

RouteConstraint类型

说明

int

IntRouteConstraint

要求路由参数值可能解析为一个int整数,比如{variable:int}

bool

BoolRouteConstraint

要求参数值可以解析为一个bool值,比如{ variable:bool}

datetime

DateTimeRouteConstraint

要求参数值可以解析为一个DateTime对象(采用CultureInfo. InvariantCulture进行解析),比如{ variable:datetime}

decimal

DecimalRouteConstraint

要求参数值可以解析为一个decimal数字,比如{ variable:decimal}

double

DoubleRouteConstraint

要求参数值可以解析为一个double数字,比如{ variable:double}

float

FloatRouteConstraint

要求参数值可以解析为一个float数字,比如{ variable:float}

guid

GuidRouteConstraint

要求参数值可以解析为一个Guid,比如{ variable:guid}

long

LongRouteConstraint

要求参数值可以解析为一个long整数,比如{ variable:long}

minlength

MinLengthRouteConstraint

要求参数值表示的字符串不于指定的长度{ variable:minlength(5)}

maxlength

MaxLengthRouteConstraint

要求参数值表示的字符串不大于指定的长度,比如{ variable:maxlength(10)}

length

LengthRouteConstraint

要求参数值表示的字符串长度限于指定的区间范围,比如{ variable:length(5,10)}

min

MinRouteConstraint

要求参数值不于指定的值,比如{ variable:min(5)}

max

MaxRouteConstraint

要求参数值大于指定的值,比如{ variable:max(10)}

range

RangeouteConstraint

要求参数值介于指定的区间范围,比如{variable:range(5,10)}

alpha

AlphaRouteContraint

要求参数值得所有字符都是字母,比如{variable:alpha}

regex

RegexInlineRouteConstraint

要求参数值表示字符串与指定的正则表达式相匹配,比如{variable:regex(^d{0[0-9]{{2,3}-d{2}-d{4}$)}}}$)}

required

RequiredRouteConstraint

要求参数值不应该是一个空字符串,比如{variable:required}

RangeRouteConstraint

为了让读者朋友们对这些RouteConstraint具有更加深刻的理解,我们选择一个用于限制变量值范围的RangeRouteConstraint类进行单独介绍。如下面的代码片断所示,RangeRouteConstraint类型具有两个长整型的只读属性Max和Min,它们分别表示约束范围的上下限。

   1: public class RangeRouteConstraint : IRouteConstraint

   2: {

   3:     public long Max { get;  }

   4:     public long Min { get; }

   5:     public RangeRouteConstraint(long min, long max)

   6:     {

   7:         this.Min = min;

   8:         this.Max = max;

   9:     }

  10:  

  11:     public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)

  12:     {

  13:         object value;

  14:         if (values.TryGetValue(routeKey, out value) && value != null)

  15:         {

  16:             long longValue;

  17:             var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);

  18:             if (long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out longValue))

  19:             {

  20:                 return longValue >= Min && longValue <= Max;

  21:             }

  22:         }

  23:         return false;

  24:     }

  25: }

具体的约束检验实现在Match方法中。具体来说,RangeRouteConstraint根据被检验变量的名称(对应于routeKey参数)从参数values(表示路由检验生成的所有路由变量)中提取被验证的参数值,然后判断它是否在通过属性Max和Min表示的数值范围内。

HttpMethodRouteConstraint

上面介绍的这些预定义的RouteConstraint类型都是对某个路由参数的值加以约束,除此之外还具有一个特殊的名为HttpMethodRouteConstraint的约束。我们在上面已经提到过,这个约束并不是应用在具有某个路由参数上,而是应用到整个请求上,它要求匹配的请求必须具有指定的方法。当我们在使用这种约束的时候,一般将对应的Key设置为“httpMethod”。

   1: public class HttpMethodRouteConstraint : IRouteConstraint

   2: {

   3:     public IList<string> AllowedMethods { get; }

   4:  

   5:     public HttpMethodRouteConstraint(params string[] allowedMethods)

   6:     {

   7:         this.AllowedMethods = new List<string>(allowedMethods);

   8:     }

   9:     

  10:     public virtual bool Match(HttpContext httpContext, IRouter route, string routeKey,RouteValueDictionary values, RouteDirection routeDirection)

  11:     {        

  12:         switch (routeDirection)

  13:         {

  14:             case RouteDirection.IncomingRequest:return AllowedMethods.Contains(httpContext.Request.Method, StringComparer.OrdinalIgnoreCase);

  15:  

  16:             case RouteDirection.UrlGeneration:

  17:                 object obj;

  18:                 if (!values.TryGetValue(routeKey, out obj))

  19:                 {

  20:                     return true;

  21:                 }

  22:                 return AllowedMethods.Contains(Convert.ToString(obj), StringComparer.OrdinalIgnoreCase);

  23:  

  24:             default:throw new ArgumentOutOfRangeException(nameof(routeDirection));

  25:         }

  26:     }

  27: }

当我们在创建一个 HttpMethodRouteConstraint对象的时候,需要指定一个允许的HTTP方法列表。对于针对入栈请求的路由解析来说,HttpMethodRouteConstraint会检验当前请求采用的方法是否在这个列表之内。如果路由解析是为了生成URL,HttpMethodRouteConstraint会从指定的参数列表中提取指定的HTTP方法,如果这样的参数存在,则会检验这个HTTP方法是否在允许的列表之内,否则意味着不需要针对HTTP方法进行验证。

二、InlineConstraintResolver

如果在进行路由注册的时候针对路由变量的约束是直接以内联表达式的形式定义在路由模板中,所以路由系统需要解析约束表达式来创建对应类型的RouteConstraint对象,这项任务由一个叫做InlineConstraintResolver的对象来完成。所有的InlineConstraintResolver类型实现了具有如下定义的IInlineConstraintResolver接口,定义其中的唯一方法ResolveConstraint实现了约束从字符串表达式到RouteConstraint对象之间的转换。

   1: public interface IInlineConstraintResolver

   2: {   

   3:     IRouteConstraint ResolveConstraint(string inlineConstraint);

   4: }

路由系统只定义了一个唯一的InlineConstraintResolver类型实现了这个接口,它就是DefaultInlineConstraintResolver类型。如下面的代码片断所示,它具有一个字典类型的字段_inlineConstraintMap,如表1所示的内联约束类型与对应RouteConstraint类型之间的映射关系就保存在这个字典中。

   1: public class DefaultInlineConstraintResolver : IInlineConstraintResolver

   2: {

   3:     private readonly IDictionary<string, Type> _inlineConstraintMap;

   4:     public DefaultInlineConstraintResolver(IOptions<RouteOptions> routeOptions)

   5:     {

   6:         _inlineConstraintMap = routeOptions.Value.ConstraintMap;

   7:     }

   8:     public virtual IRouteConstraint ResolveConstraint(string inlineConstraint);

   9: }

  10:  

  11: public class RouteOptions

  12: {

  13:     public IDictionary<string, Type> ConstraintMap { get; set; }

  14:     public bool                      LowercaseUrls { get; set; }

  15:     public bool                      AppendTrailingSlash { get; set; }

  16: }

DefaultInlineConstraintResolver首先根据指定的约束表达式获得以字符串表示的约束类型和参数列表。通过约束类型,它可以从ConstraintMap属性表示的映射关系中得到对应的HttpRouteConstraint类型。接下来它根据参数个数得到匹配的构造函数,然后将字符串表示的参数转换成对应的参数类型并以反射的形式将它们传入构造函数创建相应的HttpRouteConstraint对象。

对于一个通过指定的路由模板创建的Route对象来说,当它在初始化的时候会利用ServiceProvider采用依赖注入的形式获取这个InlineConstraintResolver对象来解析定义在路由模板中的内联约束表达式,并将它们全部转换成具体的RouteConstraint对象。这意味着在这之前,针对InlineConstraintResolver的服务注册就以及存在,那么这个服务是在什么时候注册的呢?

当我们在一个ASP.NET Core应用中使用路由功能的时候,除了需要注册这个RouterMiddleware中间件之外,一般还需要调用ServiceCollection的扩展方法AddRouting注册一些与路由相关的服务,针对InlineConstraintResolver的服务注册就实现在这个方法之中。

三、自定义约束

我们可以使用上述这些预定义的RouteConstraint类们完成一些常用的约束检验,但是在一些对路由变量具有特殊的约束的应用场景中,我们不得不创建自定义的约束。举个简单的例子,如果我们需要对资源提供针对多语言的支持,最好的方式是在请求的URL中提供目标资源所针对的Culture。为了确保包含在URL中的是一个合法有效的Culture,我们最好为此定义相应的约束。

接下来,我们将通过一个简单的实例来演示如何创建这么一个用于验证Culture的自定义约束。不过在这之前我们不妨先来看看使用这个约束最终实现的效果。在本例中我们创建了一个提供基于不同语言资源的Web API,简单起见,我们仅仅提供针对相应Culture的文本数据。我们利用资源文件来作为文本资源的存储,如下图所示,我们在一个ASP.NET Core应用中创建了两个资源文件Resources.resx(语言文化中性)和Resources.zh.resx(中文),并定义了一个名为“hello”的文本资源条目。

如下所示的是整个应用程序的定义。这段程序非常简单,我们注册了一个模板为“resources/{lang:culture}/{resourcename:required}”的路由。路由参数{ resourcename }表示获取的资源条目的名称(比如“hello”),这是一个必需的路由参数(应用了RequiredRouteConstraint约束)。另一个路由参数{lang}表示指定的语言,约束表达式名称“culture”对应的就是我们自定义的针对语言文件的约束类型CultureConstraint。也正是因为是一个自定义的路由约束,我们必须将内联约束表达式名称和CultureConstraint类型之间的应用,我们在调用ConfigureServices方法中将这样的映射添加到注册的RouteOptions之中。

   1: public class Program

   2: {

   3:     public static void Main()

   4:     {

   5:         string template = "resources/{lang:culture}/{resourceName:required}";

   6:  

   7:         Action<IApplicationBuilder> action = app => app

   8:             .UseMiddleware<LocalizationMiddleware>("lang")

   9:             .Run(async context =>

  10:             {

  11:                 var values = context.GetRouteData().Values;

  12:                 string resourceName = values["resourceName"].ToString().ToLower();

  13:                 await context.Response.WriteAsync(Resources.ResourceManager.GetString(resourceName));

  14:             });

  15:  

  16:         new WebHostBuilder()

  17:             .UseKestrel()

  18:             .ConfigureServices(svcs => svcs

  19:                 .AddRouting()

  20:                 .Configure<RouteOptions>(options=>options.ConstraintMap.Add("culture", typeof(CultureConstraint))))

  21:             .Configure(app =>app.UseRouter(builder=> builder.MapRoute(template, action)))

  22:             .Build()

  23:             .Run();

  24:     }

  25: }

我们通过调用扩展方法MapRoute注册了这个路由。利用作为参数的Action<IApplicationBuilder>对象,我们注册了一个自定义的LocalizationMiddleware中间件,这个中间件实现针对多语言的本地化。至于资源内容的响应,我们将它实现在通过调用ApplicationBuilder的Run方法注册的中间件上。我们从解析出来的路由参数中获取目标资源条目的名称,然后利用资源文件自动生成的Resoruces类型获取对应的资源内容并响应给客户端。

在揭秘CultureConstraint这个自定义路由约束以及LocalizationMiddleware中间件的实现原理之前,我们先来看看客户端采用是采用怎样的形式获取某个资源条目针对某种语言的内容。如下图所示,我们直接利用浏览器采用与注册路由相匹配的URL(“/resources/en/hello”或者“/resources/zh/hello”)不仅可以获取目标资源的内容,显示的语言也与我们指定的语言文化一致。如果指定一个不合法的语言(比如“xx”),将会违反我们自定义的约束,此时就会得到一个状态码为“404 Not Found”的响应。

接下来我们来看看这个针对语言文化的路由约束CultureConstraint就是做了些什么。如下面的代码片段所示,我们在Match方法中会试图获取作为语言文化内容的路由参数值,如果这样的路由参数存在,我们会利用它创建一个CultureInfo对象。如果这个CultureInfo的EnglishName属性名不以“Unknown Language”字符作为前缀,我们就认为指定的是合法的语言文件。

   1: public class CultureConstraint : IRouteConstraint

   2: {

   3:     public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)

   4:     {

   5:         try

   6:         {

   7:             object value;

   8:             if (values.TryGetValue(routeKey, out value))

   9:             {

  10:                 return !new CultureInfo(value.ToString()).EnglishName.StartsWith("Unknown Language");

  11:             }

  12:             return false;

  13:         }

  14:         catch

  15:         {

  16:             return false;

  17:         }

  18:     }

  19: }

我们.NET应用在运行的时候具有根据当前线程的语言文化选择资源文件的能力。就我们这实例提供的两个资源文件(Resources.resx和Resources.zh.resx)来说,如果当前线程的UICulture属性代表的是一个针对“zh”的语言文化,资源文件Resources.zh.resx会被选择。对于其他语言文件,则被选择的就是这个Resources.resx文件。换句话说,如果我们要让运行时选择某个我们希望的资源文件,我们可以为当前线程设置相应的语言文化,实际上LocalizationMiddleware这个中间件就是这么做的。

   1: public class LocalizationMiddleware

   2: {

   3:     private RequestDelegate     _next;

   4:     private string              _routeKey;

   5:  

   6:     public LocalizationMiddleware(RequestDelegate next, string routeKey)

   7:     {

   8:         _next = next;

   9:         _routeKey = routeKey;

  10:     }

  11:  

  12:     public async Task Invoke(HttpContext context)

  13:     {

  14:         object culture;

  15:         CultureInfo currentCulture = CultureInfo.CurrentCulture;

  16:         CultureInfo currentUICulture = CultureInfo.CurrentUICulture;

  17:         try

  18:         {

  19:             if (context.GetRouteData().Values.TryGetValue(_routeKey, out culture))

  20:             {

  21:                 CultureInfo.CurrentCulture = CultureInfo.CurrentUICulture = new CultureInfo(culture.ToString());

  22:             }

  23:             await _next(context);

  24:         }

  25:         finally

  26:         {

  27:             CultureInfo.CurrentCulture = currentCulture;

  28:             CultureInfo.CurrentUICulture = currentUICulture;

  29:         }

  30:     }

  31: }

如上面的代码片段所示,LocalizationMiddleware的Invoke方法被执行的时候,它会试图从路由参数中得到目标语言,代表路由参数名称的字段_routeKey是在构造函数中初始化的。如果这样的路由参数存在,它会据此创建一个CultureInfo对象并将其作为当前线程的Culture和CultureInfo属性。值得一提的是,在完成后续请求处理流程之后,我们需要将当前线程的语言文化恢复到之前的状态。


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]:内联路由约束的检验

ASP.NET Core的路由[5]:内联路由约束的检验的更多相关文章

  1. ASP.NET Core 1.1 静态文件、路由、自定义中间件、身份验证简介

    概述 之前写过一篇关于<ASP.NET Core 1.0 静态文件.路由.自定义中间件.身份验证简介>的文章,主要介绍了ASP.NET Core中StaticFile.Middleware ...

  2. 在 ASP.NET Core 项目中实现小写的路由URL

    在 ASP.NET MVC 早期版本中,我们可以通过在应用的 RegisterRoutes 方法中设置 routes.LowercaseUrls = true ; 来将页面的 URL 链接转小写.在 ...

  3. ExpandoObject与DynamicObject的使用 RabbitMQ与.net core(一)安装 RabbitMQ与.net core(二)Producer与Exchange ASP.NET Core 2.1 : 十五.图解路由(2.1 or earler) .NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了

    ExpandoObject与DynamicObject的使用   using ImpromptuInterface; using System; using System.Dynamic; names ...

  4. [06]ASP.NET Core中的进程内(InProcess)托管

    ASP.NET Core 进程内(InProcess)托管 本文作者:梁桐铭- 微软最有价值专家(Microsoft MVP) 文章会随着版本进行更新,关注我获取最新版本 本文出自<从零开始学 ...

  5. ASP.NET Core 1.0 静态文件、路由、自定义中间件、身份验证简介

    概述 ASP.NET Core 1.0是ASP.NET的一个重要的重新设计. 例如,在ASP.NET Core中,使用Middleware编写请求管道. ASP.NET Core中间件对HttpCon ...

  6. ASP.NET Core 2.1 : 十五.图解路由(2.1 or earler)

    本文通过一张图来看一下路由的配置以及请求处理的机制.(ASP.NET Core 系列目录) 一.概述 路由主要有两个主要功能: 将请求的URL与已定义的路由进行匹配,找到该URL对应的处理程序并传入该 ...

  7. ASP.NET Core 2.2中的Endpoint路由

    Endpoint路由 在ASP.NET Core 2.2中,新增了一种路由,叫做Endpoint(终结点)路由.本文将以往的路由系统称为传统路由. 本文通过源码的方式介绍传统路由和Endpoint路由 ...

  8. ASP.NET Core 3.x 中间件流程与路由体系

    中间件分类 ASP.NET Core 中间件的配置方法可以分为以上三种,对应的Helper方法分别是:Run(), Use(), Map(). Run(),使用Run调用中间件的时候,会直接返回一个响 ...

  9. 【asp.net core 系列】2 控制器与路由的恩怨情仇

    0. 前言 在上一篇文章中,我们初步介绍了asp.net core,以及如何创建一个mvc项目.从这一篇开始,我将为大家展示asp.net core 的各种内容,并且尝试带领大家来挖掘其中的内在逻辑. ...

随机推荐

  1. 消息队列——RabbitMQ学习笔记

    消息队列--RabbitMQ学习笔记 1. 写在前面 昨天简单学习了一个消息队列项目--RabbitMQ,今天趁热打铁,将学到的东西记录下来. 学习的资料主要是官网给出的6个基本的消息发送/接收模型, ...

  2. 开始学nodejs——net模块

    net模块的组成部分 详见 http://nodejs.cn/api/net.html 下面整理出了整个net模块的知识结构,和各个事件.方法.属性的用法 net.Server类 net.Socket ...

  3. SELECT INTO 和 INSERT INTO SELECT 两种表复制语句

    Insert是T-sql中常用语句,Insert INTO table(field1,field2,...) values(value1,value2,...)这种形式的在应用程序开发中必不可少.但我 ...

  4. 验证管理员权限(C#)

    参考页面: http://www.yuanjiaocheng.net/webapi/test-webapi.html http://www.yuanjiaocheng.net/webapi/web-a ...

  5. BPM配置故事之案例4-子表

    公司渐渐对采购管理重视起来了,新招聘了采购主管老李,老李对现有的申请表很不满意,要求将申请物资和申请原因改成物资明细表 物资明细表 小明只好继续致电大毛-- 大毛:把申请物资和申请原因删掉,新增一个数 ...

  6. mysql百万级分页优化

    普通分页 数据分页在网页中十分多见,分页一般都是limit start,offset,然后根据页码page计算start , 这种分页在几十万的时候分页效率就会比较低了,MySQL需要从头开始一直往后 ...

  7. [斜率优化DP]【学习笔记】【更新中】

    参考资料: 1.元旦集训的课件已经很好了 http://files.cnblogs.com/files/candy99/dp.pdf 2.http://www.cnblogs.com/MashiroS ...

  8. Linux服务器安全配置

    众所周知,网络安全是一个非常重要的课题,而服务器是网络安全中最关键的环节.Linux被认为是一个比较安全的Internet服务器,作为一种开放源代码操作系统,一旦Linux系统中发现有安全漏洞,Int ...

  9. ucos实时操作系统学习笔记——任务间通信(互斥锁)

    想讲一下ucos任务间通信中的mutex,感觉其设计挺巧妙,同sem一样使用的是event机制实现的,代码不每一行都分析,因为讲的没邵贝贝老师清楚,主要讲一下mutex的内核是如何实现的.可以理解互斥 ...

  10. PropertyGrid控件由浅入深(一):文章大纲

    Winform中PropertyGrid控件是一个非常好用的对象属性编辑工具,对于Key-Value形式的数据的处理也是非常的好用. 因为Property控件设计良好,在很小的空间内可以展示很多的内容 ...