一个Web应用本质上体现为一组终结点的集合。终结点则体现为一个暴露在网络中可供外界采用HTTP协议调用的服务,路由的作用就是建立一个请求URL模式与对应终结点之间的映射关系。借助这个映射关系,客户端可以采用模式匹配的URL来调用对应的终结点。除了利用下图所示的映射关系对请求进行路由解析,然后选择并执行与之匹配的终结点,路由系统还可以注册路由的URL模式和指定的路由参数值生成一个完整的URL。我们将这两方面的工作称为两个路由方向(Routing Direction),前者为入栈路由(Inbound Routing),后者为出栈路由(Outbound Routing)。[更多关于ASP.NET Core的文章请点这里]

对于路由系统来说,作为路由目标的终结点总是关联一个具体的URL路径模式,我们将其称为路由模式(Route Pattern)。表示路由模式的RoutePattern是通过解析路由注册时提供的路由模板生成的,路由模式的基本组成元素通过抽象类型RoutePatternPart表示。

一、RoutePatternPart

RoutePatternPart在路由模板中主要有两种类型:一种是静态文本,另一种是路由参数。例如,包含两段的路由模板“foo/{bar}”,第一段为静态文本,第二段为路由参数。由于花括号在路由模板中被用来定义路由参数,如果静态文本中包含“{”和“}”字符,就需要采用“{{”和“}}”进行转义。

其实除了上述这两种基本类型,RoutePatternPart还有第三种类型。例如,如果采用字符串“files/{name}.{ext?}”来表示针对某个文件的路由模板,文件名({name})和扩展名(ext?)体现为路由参数,而它们之间的“.”就是RoutePattern的第三种展现形式,被称为分隔符。路由系统对于分隔符具有特殊的匹配逻辑:如果分隔符后面跟的是一个可以默认的路由参数,请求地址在没有提供该参数值的情况下,分隔符是可以默认的。对于“files/{name}.{ext?}”这个路由模板来说,扩展名是可以默认的,如果请求地址没有提供扩展名,请求路径只需要提供文件名(如/files/foobar)即可。RoutePatternPart的3种类型通过RoutePatternPartKind枚举表示。

public enum RoutePatternPartKind
{
Literal,
Parameter,
Separator
}

如下所示的代码片段是RoutePatternPart的定义,可以看出这是一个抽象类。除了定义表示类型的PartKind只读属性,RoutePatternPart还有3个布尔类型的属性(IsLiteral、IsParameter和IsSeparator),它们表示当前是否属于对应的类型。

public abstract class RoutePatternPart
{
public RoutePatternPartKind PartKind { get; } public bool IsLiteral { get; }
public bool IsParameter { get; }
public bool IsSeparator { get; }
}

针对RoutePatternPartKind枚举体现的3种类型,路由系统提供3个针对RoutePatternPart的派生类,如下所示的代码片段是针对静态文本和分隔符的RoutePatternLiteralPart与RoutePattern
SeparatorPart类型的定义,它们具有表示具体内容(静态文本内容和分隔符)的Content属性。

public sealed class RoutePatternLiteralPart : RoutePatternPart
{
public string Content { get; }
} public sealed class RoutePatternSeparatorPart : RoutePatternPart
{
public string Content { get; }
}

由于路由参数在路由模板中有多种定义形式,所以对应的RoutePatternParameterPart类型的成员会多一些。RoutePatternParameterPart的Name属性和ParameterKind属性表示路由参数的名称与类型。路由参数类型包括标准形式(如{foobar})、默认形式(如{foobar?}或者{foobar?=123})及通配符形式(如{*foobar}或者{**foobar})。路由参数的这3种定义形式通过RoutePatternParameterKind枚举表示。

public sealed class RoutePatternParameterPart : RoutePatternPart
{
public string Name { get; }
public RoutePatternParameterKind ParameterKind { get; }
public bool IsOptional { get; }
public object Default { get; }
public bool IsCatchAll { get; }
public bool EncodeSlashes { get; } public IReadOnlyList<RoutePatternParameterPolicyReference> ParameterPolicies { get; }
} public enum RoutePatternParameterKind
{
Standard,
Optional,
CatchAll
}

对于默认形式或者通配符形式对应的路由参数,对应RoutePatternParameterPart对象的IsOptional属性和IsCatchAll属性会返回True。如果为参数定义了默认值,该值体现在Default属性上。对于两种通配符形式定义的路由参数,针对请求URL的解析来说并没有什么不同,它们之间的差异体现在路由系统根据它生成对应URL的时候。具体来说,对于提供的包含分隔符“/”的参数值(如foo/bar),如果对应的路由参数采用{*variable}的方式,URL格式化过程中会对分隔符进行编码(foo%2bar),倘若路由参数采用{**variable}的形式定义,提供的字符串将不做任何改变。RoutePatternParameterPart的EncodeSlashes属性表示是否需要对路径分隔符“/”进行编码。

我们在定义路由参数时可以指定约束条件,路由系统将约束视为一种参数策略(Parameter Policy)。路由参数策略通过一个标记接口(不具有任何成员的接口)IParameterPolicy表示路由参数策略,如下所示的RoutePatternParameterPolicyReference是对IParameterPolicy对象的进一步封装,它定义的Content属性表示策略的原始(字符串)表现形式。应用在路由参数上的策略定义体现在RoutePatternParameterPart的ParameterPolicies属性上。

public sealed class RoutePatternParameterPolicyReference
{
public string Content { get; }
public IParameterPolicy ParameterPolicy { get; }
} public interface IParameterPolicy
{ }

二、RoutePattern

在了解了作为路由模式的基本组成元素RoutePatternPart之后,下面介绍表示路由模式的RoutePattern如何定义。表示路由模式的RoutePattern对象是通过解析路由模板生成的,以字符串形式表示的路由模板体现为它的RawText属性。

public sealed class RoutePattern
{
public string RawText { get; }
public IReadOnlyList<RoutePatternPathSegment> PathSegments { get; }
public IReadOnlyList<RoutePatternParameterPart> Parameters { get; }
public IReadOnlyDictionary<string, object> Defaults { get; }
public IReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>> ParameterPolicies { get; } public decimal InboundPrecedence { get; }
public decimal OutboundPrecedence { get; }
public IReadOnlyDictionary<string, object> RequiredValues { get; } public RoutePatternParameterPart GetParameter(string name);
}

URL的路径采用字符“/”作为分隔符,我们将分隔符内的内容称为段,路由模式下针对路径段的表示体现在如下所示的RoutePatternPathSegment类型上。RoutePatternPathSegment类型的Parts属性返回一个RoutePatternPart对象的集合,表示构成该路径段的基本元素。如果RoutePatternPathSegment的Parts集合只包含一个元素(一般为静态文本或者路由参数),那么它被视为一个简短的路径段,其IsSimple属性会返回True。

public sealed class RoutePatternPathSegment
{
public IReadOnlyList<RoutePatternPart>Parts { get; }
public bool IsSimple { get; }
}

路由参数是路由模式的一个重要组成部分,RoutePattern的Parameters属性返回的RoutePatternParameterPart列表是对所有路由参数的描述。路由参数的默认值会存放在Defaults属性表示的字典中,该字典对象的Key为路由参数的名称。RoutePattern的ParameterPolicies属性同样返回一个字典对象,针对每个路由参数的参数策略被存放到该字典中。借助RoutePattern类型的GetParameter方法,我们可以通过指定路由参数的名称得到对应的RoutePatternParameterPart对象。

应用具有一个全局的路由表,其中包含若干注册的通过RoutePattern表示的路由模式,无论是入栈方向上针对请求URL的路由解析,还是出栈方向上生成完整的URL,都需要从这个路由表中选择一个匹配的模式。如果注册的路由很多,就可能出现多个路由在模式上都与当前上下文匹配的情况,在这种状况下就需要为注册的路由模式指定不同的匹配的权重或者优先选择一个匹配度最高的路由模式,RoutePattern类型的InboundPrecedence属性和OutboundPrecedence属性分别代表当前路由模式针对两个路由方向上的匹配优先级,数值越大表示匹配度越高。

RoutePattern类型的RequiredValues属性与出栈URL的生成相关。“weather/{city=010}/{days=4}”是本章开篇实例演示中定义的一个路由模板,如果根据指定的路由参数值(city=010,days=4)生成一个完整的URL,由于提供的路由参数值为默认值,所以生成的如下所示的3个URL路径都是合法的。具体生成哪一种由RequiredValues属性来决定,该属性返回的字典中存放了生成URL时必须指定的路由参数默认值。

  • weather。
  • weather/010。
  • weather/010/4。

三、RoutePatternFactory

静态类型RoutePatternFactory提供的一系列静态方法可以帮助我们根据路由模板字符串创建表示路由模式的RoutePattern对象。如下所示的3个静态Parse方法重载帮助我们根据指定的路由模板和其他相关数据,包括路由参数的默认值和参数策略,以及必需的路由参数值(对应RoutePattern的RequiredValues属性),生成了一个表示路由模式的RoutePattern对象。

public static class RoutePatternFactory
{
public static RoutePattern Parse(string pattern);
public static RoutePattern Parse(string pattern, object defaults, object parameterPolicies);
public static RoutePattern Parse(string pattern, object defaults, object parameterPolicies, object requiredValues);
...
}

下面通过一个简单的实例演示如何利用RoutePatternFactory对象解析指定的路由模板,并生成一个表示路由模式的RoutePattern对象。我们在一个ASP.NET Core应用程序中定义了如下所示的Format方法,该方法将指定的RoutePattern对象格式化成一个字符串。

public class Program
{
private static string Format(RoutePattern pattern)
{
var builder = new StringBuilder();
builder.AppendLine($"RawText:{pattern.RawText}");
builder.AppendLine($"InboundPrecedence:{pattern.InboundPrecedence}");
builder.AppendLine($"OutboundPrecedence:{pattern.OutboundPrecedence}");
var segments = pattern.PathSegments;
builder.AppendLine("Segments");
foreach (var segment in segments)
{
foreach (var part in segment.Parts)
{
builder.AppendLine($"\t{ToString(part)}");
}
}
builder.AppendLine("Defaults");
foreach (var @default in pattern.Defaults)
{
builder.AppendLine($"\t{@default.Key} = {@default.Value}");
} builder.AppendLine("ParameterPolicies ");
foreach (var policy in pattern.ParameterPolicies)
{
builder.AppendLine($"\t{policy.Key} = {string.Join(',', policy.Value.Select(it => it.Content))}");
} builder.AppendLine("RequiredValues");
foreach (var required in pattern.RequiredValues)
{
builder.AppendLine($"\t{required.Key} = {required.Value}");
} return builder.ToString(); static string ToString(RoutePatternPart part)
{
if (part is RoutePatternLiteralPart literal)
{
return $"Literal: {literal.Content}";
}
if (part is RoutePatternSeparatorPart separator)
{
return $"Separator: {separator.Content}";
}
else
{
var parameter = (RoutePatternParameterPart)part;
return $"Parameter: Name = {parameter.Name}; Default = {parameter.Default};
IsOptional = {parameter.IsOptional};
IsCatchAll = {parameter.IsCatchAll};
ParameterKind = {parameter.ParameterKind}";
}
}
}
}

在如下所示的应用承载程序中,我们调用RoutePatternFactory 类型的静态方法Parse解析指定的路由模板“weather/{city:regex(^0\d{{2,3}}$)=010}/{days:int:range(1,4)=4}/{detailed?}”,并生成一个RoutePattern对象,该方法调用中还指定了requiredValues参数的值。我们调用IApplicationBuilder对象的Run方法注册了唯一的中间件,它会调用上面定义的Format方法将生成的RoutePattern对象格式化成字符串,并作为最终的响应内容。

public class Program
{
public static void Main()
{
var template = @"weather/{city:regex(^0\d{{2,3}}$)=010}/{days:int:range(1,4)=4}/{detailed?}";
var pattern = RoutePatternFactory.Parse(
pattern: template,
defaults: null,
parameterPolicies: null,
requiredValues: new { city = "010", days = 4 }); Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder => builder.Configure(app => app.Run(context => context.Response.WriteAsync(Format(pattern)))))
.Build()
.Run();
}
}

如果利用浏览器访问启动后的应用程序,得到的输出结果如下图所示,该结果结构化地展示了路由模式的原始文本、出入栈路由匹配权重、每个段的组成、路由参数的默认值和参数策略,以及生成URL必须提供的默认参数值。

除了提供Parse方法解析指定的路由模板并生成表示路由模式的RoutePattern对象,RoutePatternFactory还提供了用于解析其他与路由模式相关对象的静态方法,这些对象包括表示路径段的RoutePatternPathSegment对象、针对路由参数的RoutePatternParameterPart对象、针对参数策略的RoutePatternParameterPolicyReference对象等。由于篇幅有限,此处不再一一列举。

ASP.NET Core路由中间件[1]: 终结点与URL的映射
ASP.NET Core路由中间件[2]: 路由模式
ASP.NET Core路由中间件[3]: 终结点
ASP.NET Core路由中间件[4]: EndpointRoutingMiddleware和EndpointMiddleware
ASP.NET Core路由中间件[5]: 路由约束

ASP.NET Core路由中间件[2]: 路由模式的更多相关文章

  1. asp.net core mvc 中间件之路由

    asp.net core mvc 中间件之路由 路由中间件 首先看路由中间件的源码 先用httpContext实例化一个路由上下文,然后把中间件接收到的路由添加到路由上下文的路由集合 然后把路由上下文 ...

  2. ASP.NET Core:中间件

    一.什么是中间件 我们都知道,任何的一个web框架都是把http请求封装成一个管道,每一次的请求都是经过管道的一系列操作,最终才会到达我们写的代码中.而中间件就是用于组成应用程序管道来处理请求和响应的 ...

  3. 如何传递参数给ASP.NET Core的中间件(Middleware)

    问题描述 当我们在ASP.NET Core中定义和使用中间件(Middleware)的时候,有什么好的办法可以给中间件传参数吗? 解决方案 在ASP.NET Core项目中添加一个POCO类来传递参数 ...

  4. asp.net core mvc 中间件之WebpackDevMiddleware

    asp.net core mvc 中间件之WebpackDevMiddleware WebpackDevMiddleware中间件主要用于开发SPA应用,启用Webpack,增强网页开发体验.好吧,你 ...

  5. 如何在ASP.NET Core自定义中间件中读取Request.Body和Response.Body的内容?

    原文:如何在ASP.NET Core自定义中间件中读取Request.Body和Response.Body的内容? 文章名称: 如何在ASP.NET Core自定义中间件读取Request.Body和 ...

  6. asp.net core 使用中间件拦截请求和返回数据,并对数据进行加密解密。

    原文:asp.net core 使用中间件拦截请求和返回数据,并对数据进行加密解密. GitHub demo https://github.com/zhanglilong23/Asp.NetCore. ...

  7. ASP.NET Core中使用自定义路由

    上一篇文章<ASP.NET Core中使用默认MVC路由>提到了如何使用默认的MVC路由配置,通过这个配置,我们就可以把请求路由到Controller和Action,通常情况下我们使用默认 ...

  8. (8)ASP.NET Core 中的MVC路由一

    1.前言 ASP.NET Core MVC使用路由中间件来匹配传入请求的URL并将它们映射到操作(Action方法).路由在启动代码(Startup.Configure方法)或属性(Controlle ...

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

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

随机推荐

  1. Python运算符的优先级是怎样的?

    优先级数字越高表示优先级越高,有关运算符的详细介绍请参考<Python运算符大全>

  2. PyQt(Python+Qt)学习随笔:QTreeWidgetItem项子项展开相关方法

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 树型部件QTreeWidget中的QTreeWidgetItem项如果一个项有子项,可以调用setE ...

  3. 笔试题.NET基础代码面试题

    题目如下,本随笔只是记录,都是一些自身面经的题目,您既然点开了的话,学习下无妨,说不定有帮助呢 以下答案都经过博主一个个去运行过. 题目1 (实例化后 x=?;y=? 输出什么): public cl ...

  4. 百度前端技术学院-基础-day7.8

    任务:参考如下设计稿实现HTML页面及CSS样式 代码 点击预览 HTML 1 <!DOCTYPE html> 2 <html lang="en"> 3 & ...

  5. C++ 虚函数表与多态 —— 多态的简单用法

    首先看下边的代码,先创建一个父类,然后在来一个继承父类的子类,两个类中都有自己的 play() 方法,在代码的第35-37行,创建一个父类指针,然后将子类地址引用赋值给父类,这时调用 P 指针的 pl ...

  6. github内的一些操作

    github远程仓库的克隆操作 1,找到你想要克隆的地址,复制下来 2,切入到git所在目录下,输入 git clone 复制的地址 设置过滤文件不纳入git管理 1,在git目录下创建一个.giti ...

  7. ModelViewSet+ModelSerializer使用

    1.DRF初始化 DRF框架的8个核心功能 1.认证(用户登录校验用户名密码或者token是否合法) 2.权限(根据不同的用户角色,可以操作不同的表) 3.限流(限制接口访问速度) 4.序列化(返回j ...

  8. PHP代码审计学习-PHP-Audit-Labs-day2

    filter_var()函数 filter_var() 函数通过指定的过滤器过滤一个变量.如果成功,则返回被过滤的数据.如果失败,则返回 FALSE. filter_var(variable, fil ...

  9. 手写开源ORM框架介绍

    手写开源ORM框架介绍 简介 前段时间利用空闲时间,参照mybatis的基本思路手写了一个ORM框架.一直没有时间去补充相应的文档,现在正好抽时间去整理下.通过思路历程和代码注释,一方面重温下知识,另 ...

  10. Linux OOM Killer造成数据库访问异常排查

    服务器上的服务器访问异常,查看/va/log/messages发现如下: Sep 22 16:08:21 safeserver kernel: java invoked oom-killer: gfp ...