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

2024-01-04

一、路由注册

我们演示的这个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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • 二分查找(二)

    点名 点名 某班级 n 位同学的学号为 0 n 1 点名结果记录于升序数组 records 假定仅有一位同学缺席 请返回他的学号 二分法思路 判断数组的值和对应的下标是否相等 将数组分为两个区间 不相等区间的最左端 就是第缺席的同学的学号
  • [rk3399 android11]系统通知声音默认关闭

    a build make target product full base mk b build make target product full base mk 45 7 45 7 PRODUCT PACKAGES Additional
  • Mybatis Plus 条件构造器的简单介绍 以及IService 的简单使用

    文章目录 Mybatis Plus 条件构造器 LambdaWrapper 自定义SQL Service接口 Mybatis Plus 条件构造器 简单案例演示 特殊案例更新
  • 如何查找iPhone中所有的应用程序

    Apple 的 App Store 共有约 200 万个适用于 iPhone 和 iPad 的应用程序 如果您像我们一样 您的 iOS 或 iPadOS 设备上可能有数十个应用程序 但没有机会将它们全部整理好 您很容易忘记主屏幕上应用程序图
  • 【深度学习】从huggingface上加载数据集到本地并保存为csv文件

    场景 想从huggingface上下载yelp数据集 并以csv格式保存到本地 方法 1 git clone 首先通过git在线下载数据集的相关配置文件 git代理配置参考 链接 终端上执行如下命令 1 git clone https hu
  • Js实现Web端录音播放上传下载功能

    1 新建recorder js js audio recorder js audio recorder plugin version v1 0 3 homepage https github com 2fps recorder author
  • 浩鲸科技:为什么要用雪花ID替代数据库自增ID?(转载)

    浩鲸科技的面试题如下 其他面试题相对来说比较简单 大部人题目都可以在我的网站上 www javacn site 找到答案 这里就不再赘述 咱们今天只聊 为什么要使用雪花 ID 替代数据库自增 ID 这个问题 1 什么是雪花 ID 雪花 ID
  • 卷积神经网络:专门用于图像和语音处理的深度学习模型

    随着人工智能技术的发展和应用 深度学习模型在图像和语音处理领域中扮演着越来越重要的角色 其中 卷积神经网络 Convolutional Neural Network 简称CNN 是一种专门用于图像和语音处理的深度学习模型 本文将介绍卷积神经
  • 创意无限,绘图轻松——Sketch for Mac矢量绘图软件全面介绍

    在现代设计领域 矢量绘图软件是设计师们必不可少的工具之一 而在众多矢量绘图软件中 Sketch for Mac凭借其强大的功能和友好的用户界面脱颖而出 成为众多设计师的首选 Sketch for Mac是一款专为Mac用户开发的矢量绘图软件
  • 「实战应用」如何用DHTMLX Gantt构建类似JIRA式的项目路线图(一)

    DHTMLX Gantt 是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表 可满足项目管理应用程序的所有需求 是最完善的甘特图图表库 在web项目中使用DHTMLX Gantt时 开发人员经常需要满足与UI外观相关的各种需求 因此他
  • 如何使用Urllib库采集体育头条数据

    你可以使用urllib库来发送HTTP请求并获取体育头条数据 以下是一个基本的示例 展示如何使用urllib来获取数据 import urllib request def fetch sports news url https www sp
  • 操作系统题库

    单选题 单选题 下列关于操作系统的说法中 错误的是 I 在通用操作系统管理下的计算机上运行程序 需要向操作系统预定运行时间 II 在通用操作系统管理下的计算机上运行程序 需要确定起始地址 并从这个地址开始执行 III 操作系统需要提供高级程
  • 【人工智能领域优质书籍】实战AI大模型

    文末送书 今天推荐一本人工智能领域好书 实战AI大模型 文章目录 导语 书籍亮点 初学者必备 文末送书 导语 人工智能领域资深专家尤洋老师倾力打造 获得了李开复 周鸿祎 颜水成三位大咖鼎力推荐 一经上市就登上了京东 计算机与互联网 图书排行
  • JProfiler for Mac/win中文版:功能强大的性能分析工具

    在当前的软件开发领域 Java语言是最为广泛使用的编程语言之一 然而 随着应用程序规模的增长和复杂性的提高 开发人员们经常面临着性能问题 为了解决这些问题 JProfiler作为一款专业的Java性能分析工具应运而生 JProfiler是一
  • 一文搞懂SiLM824x系列SiLM8243BBCL-DG 双通道死区可编程隔离驱动 主要特性与应用 让技术变得更有价值

    SiLM824x系列SiLM8243BBCL DG是一款具有不同配置的隔离双通道门极驱动器 SiLM8243BBCL DG配置为高 低边驱动 SiLM8243BBCL DG可提供4A的输出源电流和6A的灌电流能力 并且其驱动输出电压可以支持
  • 联邦学习:在保护数据隐私的前提下进行分布式机器学习的方法

    随着大数据时代的到来 机器学习正成为各个领域中重要的工具和技术 然而 传统的机器学习方法通常需要集中式地收集和处理大量的数据 这可能涉及到用户的隐私问题 为了解决这一问题 联邦学习应运而生 联邦学习是一种分布式机器学习的方法 它允许在保护数
  • LeetCode 2397. 被列覆盖的最多行数,状态压缩优化回溯法

    一 题目 1 题目描述 给你一个下标从 0 开始 大小为 m x n 的二进制矩阵 matrix 另给你一个整数 numSelect 表示你必须从 matrix 中选择的 不同 列的数量 如果一行中所有的 1 都被你选中的列所覆盖 则认为这
  • 通过Java编程提取Word文档的文本

    提取Word文档中的文本是一种常见的操作 便于单独获取Word文档中的内容以进行进一步的处理 分析等操作 我们可以直接复制并粘贴保存到指定文件中 但这一方法一般适用于文本内容较少时 除了费时费力地手动保存以外 我们也可以通过代码来一次性批量
  • elementui loading自定义图标和字体样式

    需求 页面是用了很多个loading 需要其中有一个字体大些 具体到图标也一样的方法 换下类名就行 遇见的问题 改不好的话会影响其他的loading样式 一起改变了 效果展示 改之前 改之后 关键知识点 element的loading自带的
  • ASP.NET Core路由中间件[1]: 终结点与URL的映射

    一 路由注册 我们演示的这个ASP NET Core应用是一个简易版的天气预报站点 如果用户希望获取某个城市在未来 N 天之内的天气信息 他可以直接利用浏览器发送一个GET请求并将对应城市 采用电话区号表示 和天数设置在URL中 如下图所示