ASP.NET Core路由中间件[2]: 路由模式

时间:2022-09-05 14:46:42

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

ASP.NET Core路由中间件[2]: 路由模式

对于路由系统来说,作为路由目标的终结点总是关联一个具体的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必须提供的默认参数值。

ASP.NET Core路由中间件[2]: 路由模式

除了提供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&period;net core mvc 中间件之路由

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

  2. ASP&period;NET Core:中间件

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

  3. 如何传递参数给ASP&period;NET Core的中间件&lpar;Middleware&rpar;

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

  4. asp&period;net core mvc 中间件之WebpackDevMiddleware

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

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

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

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

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

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

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

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

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

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

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

随机推荐

  1. 【Python网络爬虫二】使用urllib2抓去网页内容

    在Python中通过导入urllib2组件,来完成网页的抓取工作.在python3.x中被改为urllib.request. 爬取具体的过程类似于使用程序模拟IE浏览器的功能,把URL作为HTTP请求 ...

  2. iOS&lpar;本地通知与远程通知&rpar;

    iOS 推送通知有两种:本地推送.远程推送. 本地推送 :  在不需要联网的情况下,由APP发出推送,常用于某一时刻的通知,如闹钟.本地通送有局限性在于当APP处于后台或者退出时就无法发出通知. 远程 ...

  3. 【转载】MFC键盘响应

    转载自:传送门 一:首先介绍键盘消息 系统消息: ALT,F1,--F24等,是由系统内部处理的,程序本身就存在,比如F1是帮助键. WM_SYSKEYDOWN WM_SYSKEYUP WM_SYSC ...

  4. rpc rmi http

    1.RPC与RMI (1)RPC 跨语言,而 RMI只支持Java. (2)RMI 调用远程对象方法,允许方法返回 Java 对象以及基本数据类型,而RPC 不支持对象的概念,传送到 RPC 服务的消 ...

  5. 第四章 CSS基础

    1.CSS是cascading style sheets 层叠样式表.样式定义如何显示html元素,通常存储在样式表中,将样式添加到html中,是为了解决内容与表现分离的问题. 2.外部样式表可以极大 ...

  6. 安装MySQL总结

    由于MySQL的版本不同,所以会有不同的安装方式.在下以Linux系统下MySQL5.1.19版本为例,进行安装,初学还望指正. 一.安装编译软件(简单的yum安装) gcc gcc-c++ ncur ...

  7. NSJSONSerialization-JSON数据与NSDictionary和NSArray之间的转化

    转载▼     在iOS  5 中,苹果引入了一个解析JSON串的NSJSONSerialization类. 通过该类,我们可以完成JSON数据与NSDictionary和NSArray之间的转化. ...

  8. C&num;配置文件config的使用

    做程序的时候总会有一些参数,可能会调整,这时候一般情况下我都会写在配置文件里,这样方便一点. 配置文件的读取 <?xml version="1.0" encoding=&qu ...

  9. Flex读取txt文件里的内容(一)

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/you23hai45/article/details/25248307  Flex读取txt文件里的内 ...

  10. springboot 双数据源&plus;aop动态切换

    # springboot-double-dataspringboot-double-data 应用场景 项目需要同时连接两个不同的数据库A, B,并且它们都为主从架构,一台写库,多台读库. 多数据源 ...