ASP.NET Core路由中间件[1]: 终结点与URL的映射

一、路由注册

我们演示的这个ASP.NET Core应用是一个简易版的天气预报站点。如果用户希望获取某个城市在未来N天之内的天气信息,他可以直接利用浏览器发送一个GET请求并将对应城市(采用电话区号表示)和天数设置在URL中。如下图所示,为了得到成都未来两天的天气信息,我们将发送请求的路径设置为“weather/028/2”。对于采用路径“weather/0512/4”的请求,返回的自然就是苏州未来4天的天气信息。

15-1

为了开发这个简单的应用,我们定义了如下所示的WeatherReport类型,表示某个城市在某段时间范围内的天气。如下面的代码片段所示,我们还定义了另一个WeatherInfo类型,表示具体某一天的天气。简单起见,我们让WeatherInfo对象只携带基本天气状况和气温区间的信息。创建一个WeatherReport对象时,我们会随机生成这些天气信息。

public class WeatherReport
{
    private static string[] _conditions = new string[] { "晴", "多云", "小雨" };
    private static Random _random = new Random();

    public string City { get; }
    public IDictionary<DateTime, WeatherInfo> WeatherInfos { get; }

    public WeatherReport(string city, int days)
    {
        City = city;
        WeatherInfos = new Dictionary<DateTime, WeatherInfo>();
        for (int i = 0; i < days; i++)
        {
            WeatherInfos[DateTime.Today.AddDays(i + 1)] = new WeatherInfo
            {
                Condition = _conditions[_random.Next(0, 2)],
                HighTemperature = _random.Next(20, 30),
                LowTemperature = _random.Next(10, 20)
            };
        }
    }

    public WeatherReport(string city, DateTime date)
    {
        City = city;
        WeatherInfos = new Dictionary<DateTime, WeatherInfo>
        {
            [date] = new WeatherInfo
            {
                Condition = _conditions[_random.Next(0, 2)],
                HighTemperature = _random.Next(20, 30),
                LowTemperature = _random.Next(10, 20)
            }
        };
    }

    public class WeatherInfo
    {
        public string Condition { get; set; }
        public double HighTemperature { get; set; }
        public double LowTemperature { get; set; }
    }
}

由于用于处理请求的处理器最终体现为一个RequestDelegate对象,所以我们定义了如下一个与这个委托类型具有一致声明的WeatherForecast方法来处理对应的请求。如下面的代码片段所示,我们在这个方法中直接调用HttpContext的GetRouteData扩展方法提取RoutingMiddleware中间件在路由解析过程中设置的路由参数。GetRouteData扩展方法返回的是一个具有字典结构的对象,它的Key和Value分别代表路由参数的名称与值,通过预先定义的参数名(city和days)可以得到目标城市和预报天数。

public class Program
{
    private static Dictionary<string, string> _cities = new Dictionary<string, string>
    {
        ["010"] = "北京",
        ["028"] = "成都",
        ["0512"] = "苏州"
    };

    public static async Task WeatherForecast(HttpContext context)
    {
        var city = (string)context.GetRouteData().Values["city"];
        city = _cities[city];
        int days = int.Parse(context.GetRouteData().Values["days"].ToString());
        var report = new WeatherReport(city, days);
        await RendWeatherAsync(context, report);
    }

    private static async Task RendWeatherAsync(HttpContext context, WeatherReport report)
    {
        context.Response.ContentType = "text/html;charset=utf-8";
        await context.Response.WriteAsync("<html><head><title>Weather</title></head><body>");
        await context.Response.WriteAsync($"<h3>{report.city}</h3>");
        foreach (var it in report.WeatherInfos)
        {
            await context.Response.WriteAsync($"{it.Key.ToString("yyyy-MM-dd")}:");
            await context.Response.WriteAsync($"{it.Value.Condition}({ it.Value.LowTemperature}℃ ~ { it.Value.HighTemperature}℃)< br />< br /> ");
        }
        await context.Response.WriteAsync("</body></html>");
    }    
    ...
}

有了这两个核心参数之后,我们可以据此生成一个WeatherReport对象,并将它携带的天气信息以一个HTML文档的形式响应给客户端,图15-1就是这个HTML文档在浏览器上的呈现效果。由于目标城市最初以电话区号的形式体现,所以在呈现天气信息的过程中我们还会根据区号获取具体城市的名称。简单起见,我们利用一个简单的字典来维护区号和城市之间的关系,并且只存储了3个城市而已。

下面完成所需的路由注册工作。如下面的代码片段所示,我们调用IApplicationBuilder的UseRouting方法和UseEndpoints方法分别完成针对EndpointRoutingMiddleware与EndpointMiddleware这两个终结点的注册。由于它们在进行路由解析过程中需要使用一些服务,所以可以调用IServiceCollection的AddRouting扩展方法来对它们进行注册。

public class Program
{
    public static void Main()
    {
        Host.CreateDefaultBuilder()
            .ConfigureWebHostDefaults(builder => builder
                .ConfigureServices(svcs => svcs.AddRouting())
                .Configure(app => app
                    .UseRouting()
                    .UseEndpoints(endpoints=> endpoints.MapGet("weather/{city}/{days}", WeatherForecast))))
            .Build()
            .Run();
    }
}

UseEndpoints方法提供了一个Action<IEndpointRouteBuilder>类型的参数,我们利用这个参数调用IEndpointRouteBuilder的MapGet方法提供了一个路由模板与对应处理器之间的映射。我们指定的路径模板为“weather/{city}/{days}”,其中携带两个路由参数({city}和{days}),分别代表获取天气预报的目标城市和天数。由于针对天气请求的处理实现在WeatherForecast方法中,所以将指向这个方法的RequestDelegate对象作为第二个参数。MapGet的后缀“Get”表示HTTP方法,这意味着与指定路由模板的模式相匹配的GET请求才会被路由到WeatherForecast方法对应的终结点。

二、设置内联约束

上面的演示实例注册的路由模板中定义了两个参数({city}和{days}),分别表示获取天气预报的目标城市对应的区号和天数。区号应该具有一定的格式(以零开始的3~4位数字),而天数除了必须是一个整数,还应该具有一定的范围。由于我们在注册的时候并没有为这个两个路由参数的值做任何约束,所以请求URL携带的任何字符都是有效的。而处理请求的WeatherForecast方法也并没有对提取的数据做任何验证,所以在执行过程中面对不合法的输入会直接抛出异常。如下图所示,由于请求URL(“/weather/0512/iv”)指定的天数不合法,所以客户端接收到一个状态为“500 Internal Server Error”的响应。

15-2

为了确保路由参数值的有效性,在进行路由注册时可以采用内联(Inline)的方式直接将相应的约束规则定义在路由模板中。ASP.NET Core为常用的验证规则定义了相应的约束表达式,我们可以根据需要为某个路由参数指定一个或者多个约束表达式。如下面的代码片段所示,为了确保URL携带的是合法的区号,我们为路由参数{city}指定了一个针对正则表达式的约束(:regex(^0[1-9]{{2,3}}$))。由于路由模板在被解析时会将{value}这样的字符理解为路由参数,如果约束表达式需要使用字符“{}”(如正则表达式^0[1-9]{2,3}$)),就需要采用“{{}}”进行转义。而路由参数{days}则应用了两个约束:第一个是针对数据类型的约束(:int),它要求参数值必须是一个整数;第二个是针对区间的约束(:range(1,4)),意味着我们的应用最多只提供未来4天的天气。

public class Program
{
    public static void Main()
    {
        var template = @"weather/{city:regex(^0\d{{2,3}}$)}/{days:int:range(1,4)}";
        Host.CreateDefaultBuilder()
            .ConfigureWebHostDefaults(builder => builder
                .ConfigureServices(svcs => svcs.AddRouting())
                .Configure(app => app
                    .UseRouting()
                    .UseEndpoints(routes => routes.MapGet(template, WeatherForecast))))
            .Build()
            .Run();    
    }
    ...
}

如果在注册路由时应用了约束,那么RoutingMiddleware中间件在进行路由解析时除了要求请求路径必须与路由模板具有相同的模式,还要求携带的数据满足对应路由参数的约束条件。如果不能同时满足这两个条件,RoutingMiddleware中间件将无法选择一个终结点来处理当前请求,在此情况下它会将请求直接递交给后续中间件进行处理。对于我们演示的这个实例来说,如果提供的是一个不合法的区号(1024)和预报天数(5),那么客户端都将得到下图所示的状态码为“404 Not Found”的响应。

15-3

三、默认路由参数

路由注册时提供的路由模板(如“weather/{city}/{days}”)可以包含静态的字符(如weather),也可以包含动态的参数(如{city}和{days}),我们将后者称为路由参数。并非每个路由参数都是必需的,有的路由参数是默认的。还是以上面演示的实例来说,我们可以采用如下方式在路由参数名后面添加一个问号(?)将原本必需的路由参数变成可以默认的。默认的路由参数只能出现在路由模板尾部,这个应该不难理解。

public class Program
{    
    public static void Main()
    {
        var template = "weather/{city?}/{days?}";
        Host.CreateDefaultBuilder()
            .ConfigureWebHostDefaults(builder => builder
                .ConfigureServices(svcs => svcs.AddRouting())
                .Configure(app => app
                    .UseRouting()
                    .UseEndpoints(routes => routes.MapGet(template, WeatherForecast))))
            .Build()
            .Run();
    }
    ...
}

既然路由变量占据的部分路径是可以默认的,那么即使请求的URL不具有对应的内容(如“weather”和“weather/010”),它与路由规则也是匹配的,但此时在路由参数字典中是找不到它们的。由于表示目标城市和预测天数的两个路由参数都是默认的,所以需要对处理请求的WeatherForecast方法做相应的改动。下面的代码片段表明:如果请求URL为了显式提供对应参数的数据,那么它们的默认值分别为010(北京)和4(天),也就是说,应用默认提供北京未来4天的天气。

public class Program
{    
    public static async Task WeatherForecast(HttpContext context)
    {
        var routeValues = context.GetRouteData().Values;
        var city = routeValues.TryGetValue("city", out var v1)
            ? (string)v1
            : "010";
        city = _cities[city];
        var days = routeValues.TryGetValue("days", out var v2)
            ? int.Parse(v2.ToString())
            : 4;          
        var report = new WeatherReport(city, days); 
        await RendWeatherAsync(context, report);
    }
    ...
}

针对上述改动,如果希望获取北京未来4天的天气状况,我们可以采用下图所示的3种URL(“weather”、“weather/010”和“weather/010/4”),它们是完全等效的。

15-4

上面的程序相当于在进行请求处理时给予了默认路由参数一个默认值,实际上,路由参数默认值的设置还有一种更简单的方式,那就是按照如下所示的方式直接将默认值定义在路由模板中。如果采用这样的路由注册方式,针对WeatherForecast方法的改动就完全没有必要。

public class Program
{
    public static void Main()
    {
        var template = "weather/{city=010}/{days=4}";
        Host.CreateDefaultBuilder()
            .ConfigureWebHostDefaults(builder => builder
                .ConfigureServices(svcs => svcs.AddRouting())
                .Configure(app => app
                    .UseRouting()
                    .UseEndpoints(routes => routes.MapGet(template, WeatherForecast))))
            .Build()
            .Run();
    }
    ...
}

四、特殊的路由参数

一个URL可以通过分隔符“/”划分为多个路径分段(Segment),路由模板中定义的路由参数一般来说会占据某个独立的分段(如“weather/{city}/{days}”)。但也有例外情况,我们既可以在一个单独的路径分段中定义多个路由参数,也可以让一个路由参数跨越多个连续的路径分段。

下面先介绍在一个独立的路径分段中定义多个路由参数的情况。同样以前面演示的获取天气预报的路径为例,假设设计一种路径模式来获取某个城市某一天的天气信息,如“/weather/010/2019.11.11”这样一个URL可以获取北京在2019年11月11日的天气,那么路由模板为“/weather/{city}/{year}.{month}.{day}”。

public class Program
{
    public static void Main()
    {
        var template = "weather/{city}/{year}.{month}.{day}";
        Host.CreateDefaultBuilder().ConfigureWebHostDefaults(builder => builder
            .ConfigureServices(svcs => svcs.AddRouting())
            .Configure(app => app.UseRouter(builder => builder.MapGet(template, WeatherForecast))))
            .Build()
            .Run();
    }

    public static async Task WeatherForecast(HttpContext context)
    {
        var values = context.GetRouteData().Values;
        var city = values["city"].ToString();
        city = _cities[city];
        int year = int.Parse(values["year"].ToString());
        int month = int.Parse(values["month"].ToString());
        int day = int.Parse(values["day"].ToString());
        var report = new WeatherReport(city, new DateTime(year, month, day));
        await RendWeatherAsync(context, report);
    }
    ...
}

由于URL采用了新的设计,所以我们按照如上形式对相关程序进行了相应的修改。现在我们采用“/weather/{city}/{yyyy}.{mm}.{dd}”这样的URL,就可以获取某个城市指定日期的天气。如下图所示,我们采用请求路径“/weather/010/2019.11.11”可以获取北京在2019年11月11日的天气。

15-5

对于上面设计的这个URL来说,我们采用“.”作为日期分隔符,如果采用“/”作为日期分隔符(如2019/11/11),这个路由默认应该如何定义?由于“/”也是路径分隔符,如果表示日期的路由变量也采用相同的分隔符,就意味着同一个路由参数跨越了多个路径分段,我们只能采用定义“通配符”的形式来达到这个目的。通配符路由参数采用{*variable}或者{**variable}的形式,星号(*)表示路径“余下的部分”,所以这样的路由参数只能出现在模板的尾端。对我们的实例来说,路由模板可以定义成“/weather/{city}/{*date}”。

public class Program
{
    public static void Main()
    {
        var template = "weather/{city}/{*date}";
        Host.CreateDefaultBuilder()
            .ConfigureWebHostDefaults(builder => builder
                .ConfigureServices(svcs => svcs.AddRouting())
                .Configure(app => app
                    .UseRouting()
                    .UseEndpoints(routes => routes.MapGet(template, WeatherForecast))))
            .Build()
            .Run();
    }

    public static async Task WeatherForecast(HttpContext context)
    {
        var values = context.GetRouteData().Values;
        var city = values["city"].ToString();
        city = _cities[city];
        var date = DateTime.ParseExact(values["date"].ToString(), "yyyy/MM/dd", CultureInfo.InvariantCulture);
        var report = new WeatherReport(city, date);
        await RendWeatherAsync(context, report);
    }
    ...
}

我们可以对程序做如上修改来使用新的URL模板(“/weather/{city}/{*date}”)。为了得到北京在2019年11月11日的天气,请求的URL可以替换成“/weather/010/2019/11/11”,返回的天气信息如下图所示。

15-6

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/292816.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ARM Cortex-Mx 权威指南笔记

用于中断或异常屏蔽特殊寄存器细节 1、PRIMASK 在许多应用中,可能都需要暂时禁止所有中断以执行一些时序关键的任务&#xff0c;此时可以使用PRIMASK寄存器。PRIMASK寄存器只能在特权状态访问。PRIMASK 用于禁止除NMI和 HardFault 外的所有异常它实际上是将当前优先级改为0(最…

Git 对项目更新的时候提示错误 repository not owned by current user

遇到 Git 提示的错误信息为&#xff1a;repository not owned by current user 上图显示的是错误的信息。 问题和解决 出现上面错误信息的原因是当前文件夹的权限和 Git 的执行权限不一直导致的。 我们的问题是我们希望在网盘上使用 Git 更新克隆后的代码&#xff0c;但登录…

Dockerfile语法和简单镜像构建

Dockerfile是一个用于定义Docker镜像的文本文件&#xff0c;包含了一系列的指令和参数&#xff0c;用于指示Docker在构建镜像时应该执行哪些操作&#xff0c;例如基于哪个基础镜像、复制哪些文件到镜像中、运行哪些命令等。 Dockerfile文件的内容主要有几个部分组成&#xff0c…

pyparamvalidate 项目背景和需求分析

目录 一、前置说明1、总体目录2、本节目标 二、项目背景三、需求分析三、后置说明1、要点小结2、下节预告 一、前置说明 1、总体目录 《 pyparamvalidate 参数校验器&#xff0c;从编码到发布全过程》 2、本节目标 阐述 pyparamvalidate 项目背景和需求分析。 二、项目背景…

设计模式学习

面向对象有两个思维模型&#xff1a; 底层思维&#xff1a;向下&#xff0c;如何把握机器底层从微观理解对象构造&#xff0c;封装、继承、多态抽象思维&#xff1a;向上&#xff0c;如何将我们周围世界抽象为程序代码&#xff0c;包括面向对象、组件封装、设计模式、架构模式…

React Hook 原理,及如何使用Hook

一、 Hook使用规则 只在最顶层使用Hook 不要在循环&#xff0c;条件或嵌套函数中调用Hook&#xff1b; 只在组件函数和自定义hook中调用Hook Q1 &#xff1a; 为什么 hook 不能 在循环&#xff0c;条件或嵌套函数中调用Hook &#xff1f; A1&#xff1a; 因为这跟React的…

搜索二维矩阵 II(LeetCode 240)

1.问题描述 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。每列的元素从上到下升序排列。 示例&#xff1a; 输入&#xff1a;matrix [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10…

灸哥问答:数据结构对软件开发的作用

在软件开发的浩瀚海洋中&#xff0c;数据结构如同一座坚固的灯塔&#xff0c;为开发者指明方向&#xff0c;确保他们在构建复杂系统时不会迷失。数据结构不仅仅是编程的基础&#xff0c;更是高效、稳定、可扩展软件的核心。 一、提升算法效率 数据结构与算法紧密相连&#xf…

Zabbix自定义监控内容实验(带自动报警)

实验前准备 zabbix服务端&#xff1a;192.168.188.17 zabbix客户端&#xff1a;192.168.188.11 部署zabbix服务端&#xff08;192.168.188.17&#xff09; zabbix-server 内存至少2G&#xff0c;推荐4G (1) 关闭防火墙 systemctl stop firewalld setenforce 0 (2)获取zabbix下…

Unity 使用 Plastic 同步后,正常工程出现错误

class Newtonsoft.Json.Linq.JToken e CS0433:类型"JToken"同时存在于"Newtonsoft.Json.Net20,Version3.5.0.0,Cultureneutral,,PublicKeyToken30ad4fe6b2a6aeed"和"Newtonsoft.Json, Version12.0.0.0,Cultureneutral,PublicKeyToken30ad4fe6b2a6aeed…

消息中间件 —— ActiveMQ 使用及原理详解

目录 一. 前言 二. JMS 规范 2.1. 基本概念 2.2. JMS 体系结构 三. ActiveMQ 使用 3.1. ActiveMQ Classic 和 ActiveMQ Artemis 3.2. Queue 模式&#xff08;P2P&#xff09; 3.3. Topic 模式&#xff08;Pub/Sub&#xff09; 3.4. 持久订阅 3.5. 消息传递的可靠性 …

Django 快速整合 Swagger:实用步骤和最佳实践

Django &#xff0c;作为 Python 编写的一个优秀的开源 Web 应用框架&#xff0c;特别适用于快速开发的团队。对于很多场景来说&#xff0c;我们需要一份 API 文档&#xff0c;好处实在太多了&#xff1a; 提高开发效率&#xff1a;开发者可以基于 API 文档 快速学习和尝试 AP…

【bug】【VSCode】远程终端TERMINAL打不开

【bug】【VSCode】远程终端TERMINAL打不开 可能的原因现象分析解决 可能的原因 昨天晚上vscode在打开多个TERMINAL的情况下&#xff0c;挂了一晚上&#xff0c;今早上来看的时候全都lost connections…。然后关闭再打开就出现了如上现象。 早上一来到实验室就要debug… 现象…

出个花活:出街&秀场丨当维乐VELO遇上英伦时尚之都

到底是谁还没有看过我们维乐坐垫今年的新花活呀&#xff0c;身边好多从前不爱运动的朋友&#xff0c;如今也沉迷上了公路车。我相信原因一定是由于对产品设计有着更高的要求&#xff0c;对于审美有着越来越高的追求&#xff0c;也是因为此大多数朋友最终都选择了维乐专业坐垫&a…

学而时习之---状态模式

在软件系统中&#xff0c;有些对象也像水一样具有多种状态&#xff0c; 这些状态在某些情况下能够相互转换&#xff0c; 而且对象在不同的状态下具有不同的行为。 为了更好地对这些具有多种状态的对象进行设计。 使用一种被称为状态模式的设计模式。 状态模式用于解决系统中复…

AJAX(二)jQuery

一、jQuery中的AJAX BootCDN - Bootstrap 中文网开源项目免费 CDN 加速服务 我们将该链接引入get.html文件里面&#xff1a; service.js: //1.引入express const expressrequire(express); //2.创建应用对象 const appexpress(); //3.创建路由规则 //request是对请求报文的封…

yolov5旋转目标检测-遥感图像检测-无人机旋转目标检测(附代码和原理)

目前&#xff0c;无人机技术的快速发展带来了遥感图像处理领域的革命性改变。然而&#xff0c;由于无人机在飞行时可能会出现旋转的情况&#xff0c;因此对于旋转目标的检测也成为了一个重要的问题。针对这个问题&#xff0c;yolov5可以提供一种高效的解决方案。 以下是介绍的分…

编写.NET的Dockerfile文件构建镜像

创建一个WebApi项目&#xff0c;并且创建一个Dockerfile空文件&#xff0c;添加以下代码&#xff0c;7.0代表的你项目使用的SDK的版本&#xff0c;构建的时候也需要选择好指定的镜像tag FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443F…

适合 C++ 新手学习的开源项目——在 GitHub 学编程

作者&#xff1a;HelloGitHub-小鱼干 俗话说&#xff1a;万事开头难&#xff0c;学习编程也是一样。在 HelloGitHub 的群里&#xff0c;经常遇到有小伙伴询问编程语言如何入门方面的问题&#xff0c;如&#xff1a; 我要学习某一门编程语言&#xff0c;有什么开源项目可以推荐…

电源板设计方案怎么写 (评审文件)

1. 首先是大致的图形模块化说明。 1. 大致的框图 2. 统计项目需要的功率和需求 此表格数据是假的&#xff0c;只是为了展示 电源种类是&#xff1a; 板子需要供电需要的电压和对应电压最大的电流。 电源时序是&#xff1a; 板子…