wordpress的编辑器,wordpress head 优化,重庆vi设计公司,如何打开建设网站后台借助路由系统提供的请求URL模式与对应终结点之间的映射关系#xff0c;我们可以将具有相同URL模式的请求分发给与之匹配的终结点进行处理。ASP.NET的路由是通过EndpointRoutingMiddleware和EndpointMiddleware这两个中间件协作完成的#xff0c;它们在ASP.NET平台上具有举足轻…借助路由系统提供的请求URL模式与对应终结点之间的映射关系我们可以将具有相同URL模式的请求分发给与之匹配的终结点进行处理。ASP.NET的路由是通过EndpointRoutingMiddleware和EndpointMiddleware这两个中间件协作完成的它们在ASP.NET平台上具有举足轻重的地位MVC和gRPC框架Dapr的Actor和发布订阅编程模式都建立在路由系统之上。Minimal API更是将提升到了前所未有的高度是我们直接在路由系统基础上定义REST API。[本文节选《ASP.NET Core 6框架揭秘》第20章][S2001]注册路由终结点 源代码[S2002]以内联方式设置路由参数的约束源代码[S2003]定义可缺省的路由参数源代码[S2004]为路由参数指定默认值源代码[S2005]一个路径分段定义多个路由参数源代码[S2006]一个路由参数跨越多个路径分段源代码[S2007]主机名绑定源代码[S2008]将终结点处理定义为任意类型的委托源代码[S2009]IResult 的应用源代码[S2001]注册路由终结点我们演示的这个ASP.NET应用是一个简易版的天气预报站点。服务端利用注册的一个终结点来提供某个城市在未来N天之内的天气信息对应城市采用电话区号表示和天数直接至于请求URL的路径中。如图1所示为了得到成都未来两天的天气信息我们将发送请求的路径设置为“weather/028/2”。路径为“weather/0512/4”的请求返回就是苏州未来4天的天气信息。图1 获取天气预报信息演示程序定义了如下这个WeatherReport记录类型来表示某个城市在某段时间范围内的天气报告。如代码片段所示某一天的天气体现为一个WeatherInfo记录。简单起见我们让WeatherInfo记录只携带基本天气状况和气温区间的信息。public readonly record struct WeatherInfo(string Condition, double HighTemperature, double LowTemperature);
public readonly record struct WeatherReport(string CityCode, string CityName,IDictionaryDateTime, WeatherInfo WeatherInfos);我们定义了如下这个工具类型WeatherReportUtility两个Generate方法会根据指定的城市代码和天数/日期生成一份由WeatherReport对象表示的天气报告。为了将这份报告呈现在网页上我们定义了另一个RenderAsync方法将指定的WeatherReport转换成HTML并利用指定的HttpContext上下文将它作为响应内容具体的HTML内容由AsHtml方法生成。public static class WeatherReportUtility
{private static readonly Random _random new();private static readonly Dictionarystring, string _cities new(){[010] 北京,[028] 成都,[0512] 苏州};private static readonly string[] _conditions new string[] { 晴, 多云, 小雨 };public static WeatherReport Generate(string city, int days){var report new WeatherReport(city, _cities[city], new DictionaryDateTime, WeatherInfo());for (int i 0; i days; i){report.WeatherInfos[DateTime.Today.AddDays(i 1)] new WeatherInfo(_conditions[_random.Next(0, 2)], _random.Next(20, 30), _random.Next(10, 20));}return report;}public static WeatherReport Generate(string city, DateTime date){var report new WeatherReport(city, _cities[city], new DictionaryDateTime, WeatherInfo());report.WeatherInfos[date] new WeatherInfo(_conditions[_random.Next(0, 2)], _random.Next(20, 30), _random.Next(10, 20));return report;}public static Task RenderAsync(HttpContext context, WeatherReport report){context.Response.ContentType text/html;charsetutf-8;return context.Response.WriteAsync(AsHtml(report));}public static string AsHtml(WeatherReport report){return $
html
headtitleWeather/title/head
body
h3{report.CityName}/h3
{AsHtml(report.WeatherInfos)}
/body
/html
;static string AsHtml(IDictionaryDateTime, WeatherInfo dictionary){var builder new StringBuilder();foreach (var kv in dictionary){var date kv.Key.ToString(yyyy-MM-dd);var tempFrom ${kv.Value.LowTemperature}℃ ;var tempTo ${kv.Value.HighTemperature}℃ ;builder.Append( ${date}: {kv.Value.Condition} {tempFrom}~{tempTo}br//br);}return builder.ToString();}}
}Minimal API会默认添加针对路由的服务注册完成路由的两个中间件RoutingMiddleware和EndpointRoutingMiddleware也会在自动注册到创建的WebApplication对象上。WebApplication类型同时实现了IEndpointRouteBuilder接口我们只需要利用它注册相应的终结点就可以了。如下的演示程序调用了WebApplication对象的MapGet方法注册了一个仅针对GET请求的终结点终结点采用的路径模板为“weather/{city}/{days}”携带的两个路由参数{city}和{days}分别代表目标城市代码区号和天数。using App;
var app WebApplication.Create();
app.MapGet(weather/{city}/{days}, ForecastAsync);
app.Run();static Task ForecastAsync(HttpContext context)
{var routeValues context.GetRouteData().Values;var city routeValues[city]!.ToString();var days int.Parse(routeValues[days]!.ToString()!);var report WeatherReportUtility.Generate(city!, days);return WeatherReportUtility.RenderAsync(context, report);
}注册中间件采用的处理器是一个RequestDelegate委托我们将它指向ForecastAsync方法。该方法调用HttpContext上下文的GetRouteData方法得到承载“路由数据”的RouteData对象后者的Values属性返回路由参数字典。我们从中提取出代表城市代码和天数的路由参数并创建出对应的天气报告最后将其转换成HTML作为响应内容。[S2002]以内联方式设置路由参数的约束上面的演示实例注册的路由模板中定义了两个参数{city}和{days}分别表示获取天气预报的目标城市对应的区号和天数。区号应该具有一定的格式以零开始的34位数字而天数除了必须是一个整数还应该具有一定的范围。由于没有对这两个路由参数坐任何约束所以请求URL携带的任何字符都是有效的。ForecastAsync方法也并没有对提取的路由参数做任何验证所以在执行过程中面对不合法的输入会直接抛出异常。为了确保路由参数值的有效性在进行中间件注册时可以采用内联Inline的方式直接将相应的约束规则定义在路由模板中。ASP.NET为常用的验证规则定义了相应的约束表达式我们可以根据需要为某个路由参数指定一个或者多个约束表达式。如下面的代码片段所示我们为路由参数“{city}”指定了一个基于“区号”的正则表达式“:regex(^0[1-9]{{2,3}}$)”。另一个路由参数{days}则应用了两个约束一个是针对数据类型的约束“:int”另一个是针对区间的约束“:range(1,4)”。using App;
var template weather/{city:regex(^0\d{{2,3}}$)}/{days:int:range(1,4)};
var app WebApplication.Create();
app.MapGet(template, ForecastAsync);
app.Run();如果在注册路由时应用了约束那么RoutingMiddleware中间件在进行路由解析时除了要求请求路径必须与路由模板具有相同的模式还要求携带的数据满足对应路由参数的约束条件。如果不能同时满足这两个条件RoutingMiddleware中间件将无法选择一个终结点来处理当前请求。对于我们演示的这个实例来说如果提供的是一个不合法的区号1014和预报天数5那么客户端都将得到图2所示的状态码为“404 Not Found”的响应。图2 不满足路由约束而返回的“404 Not Found”响应[S2003]定义可缺省的路由参数路由模板如“weather/{city}/{days}”可以包含静态的字符如“weather”也可以包含动态的参数如{city}和{days}我们将后者称为路由参数。并非每个路由参数都必须有请求URL对应的部分来指定如果赋予路由参数一个默认值那么它在请求URL中就是可以缺省的。对上面演示的实例来说我们可以采用如下方式在路由参数名后面添加一个问号“?”将原本必需的路由参数变成可以缺省的默认参数的。可以缺省的路由参数与在方法中定义可缺省的Optionalparams参数一样只能出现在路由模板尾部。using App;var template weather/{city?}/{days?};
var app WebApplication.Create();
app.MapGet(template, ForecastAsync);
app.Run();static Task ForecastAsync(HttpContext context)
{var routeValues context.GetRouteData().Values;var city routeValues.TryGetValue(city, out var v1) ? v1!.ToString() : 010;var days routeValues.TryGetValue(days, out var v2) ? v1!.ToString() : 4;var report WeatherReportUtility.Generate(city!, int.Parse(days!));return WeatherReportUtility.RenderAsync(context, report);
}既然路由变量占据的部分路径是可以缺省的那么即使请求的URL不具有对应的值如“weather”和“weather/010”它与路由规则也是匹配的但此时在路由参数字典中是找不到它们的。此时我们不得不对处理请求的ForecastAsync方法进行相应的改动。针对上述改动如果希望获取北京未来4天的天气状况我们可以采用图3所示的三种URL“weather”、“weather/010”和“weather/010/4”这三个请求的URL本质上是完全等效的。图3 不同URL针对默认路由参数的等效性[S2004]为路由参数指定默认值实际上可缺省路由参数默认值的设置还有一种更简单的方式那就是按照如下所示的方式直接将默认值定义在路由模板中。这样针对ForecastAsync方法的改动就完全没有必要。using App;var template weather/{city010}/{days4};
var app WebApplication.Create();
app.MapGet(template, ForecastAsync);
app.Run();static Task ForecastAsync(HttpContext context)
{var routeValues context.GetRouteData().Values;var city routeValues[city]!.ToString();var days int.Parse(routeValues[days]!.ToString()!);var report WeatherReportUtility.Generate(city!, days);return WeatherReportUtility.RenderAsync(context, report);
}[S2005]一个路径分段定义多个路由参数一个URL可以通过分隔符“/”划分为多个路径分段Segment路由参数一般来说会占据某个独立的分段如“weather/{city}/{days}”。但也有例外情况我们既可以在一个单独的路径分段中定义多个路由参数也可以让一个路由参数跨越多个连续的路径分段。以我们的演示程序为例我们需要设计一种路径模式来获取某个城市某一天的天气信息如使用“/weather/010/2019.11.11”这样URL获取北京在2019年11月11日的天气对应模板为“/weather/{city}/{year}.{month}.{day}”。using App;var template weather/{city}/{year}.{month}.{day};
var app WebApplication.Create();
app.MapGet(template, ForecastAsync);
app.Run();static Task ForecastAsync(HttpContext context)
{var routeValues context.GetRouteData().Values;var city routeValues[city]!.ToString();var year int.Parse(routeValues[year]!.ToString()!);var month int.Parse(routeValues[month]!.ToString()!);var day int.Parse(routeValues[day]!.ToString()!);var report WeatherReportUtility.Generate(city!, new DateTime(year,month,day));return WeatherReportUtility.RenderAsync(context, report);
}对于修改后的程序如果采用“/weather/{city}/{yyyy}.{mm}.{dd}”这样的URL我们就可以获取某个城市指定日期的天气。如图4所示我们采用请求路径“/weather/010/2019.11.11”可以获取北京在2019年11月11日的天气。图4 一个路径分段定义多个路由参数[S2006]一个路由参数跨越多个路径分段上面设计的路由模板采用“.”作为日期分隔符如果采用“/”作为日期分隔符如2019/11/11这个路由默认应该如何定义呢由于“/”同时也是路径分隔符就意味着同一个路由参数跨越了多个路径分段这种情况只能采用“通配符”的形式才能达成我们的目标。通配符路由参数采用{*variable}或者{**variable}的形式星号*表示路径“余下的部分”所以这样的路由参数也只能出现在模板的尾端。演示程序的路由模板可以定义成“/weather/{city}/{*date}”。using App;
using System.Globalization;var template weather/{city}/{*date};
var app WebApplication.Create();
app.MapGet(template, ForecastAsync);
app.Run();static Task ForecastAsync(HttpContext context)
{var routeValues context.GetRouteData().Values;var city routeValues[city]!.ToString();var date DateTime.ParseExact(routeValues[date]?.ToString()!,yyyy/MM/dd,CultureInfo.InvariantCulture);var report WeatherReportUtility.Generate(city!, date);return WeatherReportUtility.RenderAsync(context, report);
}我们可以对程序做如上修改来使用新的URL模板“/weather/{city}/{*date}”。为了得到北京在2019年11月11日的天气请求的URL可以替换成“/weather/010/2019/11/11”返回的天气信息如图5所示。图5 一个路由参数跨越多个路径分段[S2007]主机名绑定一般来说在利用某路由终结点与待路由的请求进行匹配的时候只需要考虑请求地址的路径部分并忽略主机Host名称和端口号但是一定要加上针对主机名称含端口的匹配策略也未尝不可。在如下这个演示程序中我们通过调用MapGet扩展方法为根路径“/”添加了三个路由终结点并调用该方法返回的IEndpointConventionBuilder对象的RequireHost扩展方法绑定了对应的主机名“*.artech.com”、“www.foo.artech.com”和“www.foo.artech.com:9999”。指定的第一个主机名包含一个前置通配符“*”最后一个则指定了端口号。注册的这三个终结点会直接将指定的主机名作为响应内容。var app WebApplication.Create();
app.Urls.Add(http://0.0.0.0:6666);
app.Urls.Add(http://0.0.0.0:9999);
app.MapHost(*.artech.com).MapHost(www.foo.artech.com).MapHost(www.foo.artech.com:9999);
app.Run();internal static class Extensions
{public static IEndpointRouteBuilder MapHost(this IEndpointRouteBuilder endpoints,string host){endpoints.MapGet(/, context context.Response.WriteAsync(host)).RequireHost(host);return endpoints;}
}为了能够在本机采用不同的域名对演示应用发起请求我们通过修改Hosts文件的方式将本地地址“127.0.0.1”映射为多个不同的域名。我们以管理员Administrator身份打开文件Hosts “%windir%\System32\drivers\etc\hosts”并以如下所示的方式添加了针对两个域名的映射。127.0.0.1 www.foo.artech.com
127.0.0.1 www.bar.artech.com应用启动之后我们利用浏览器使用不同的域名和端口对其发起请求并得到如图6所示的输出结果。输出的内容不仅仅体现了终结点选择过程中针对主机名的过滤还体现了终结点选择策略的一个重要的特性那就是路由系统总是试图选择一个与当前请求匹配度最高的终结点而不是选择第一个匹配的终结点。图6 主机名绑定[S2008]将终结点处理定义为任意类型的委托上面的例子都直接使用一个RequestDelegate委托作为终结点的处理器实际上我们在注册终结点时可以将处理器设置为任何类型的委托都可以。当路由请求分发给注册的委托进行处理器时会尽可能地从当前HttpContext上下文中提取相应的数据对委托的输入参数进行绑定。对于委托的执行结果路由系统也会按照预定义的规则“智能”地将它应用到针对请求的响应中。按照这个规则我们演示程序中用来处理请求的ForecastAsync方法可以简写成如下形式。第一个参数会自动绑定为当前HttpContext上下文后面的两个参数则自动与同名的路由参数进行绑定。using App;var app WebApplication.Create();
app.MapGet(weather/{city}/{days}, ForecastAsync);
app.Run();static Task ForecastAsync(HttpContext context, string city, int days){var report WeatherReportUtility.Generate(city,days);return WeatherReportUtility.RenderAsync(context, report);
}[S2009]IResult 的应用不论终结点处理器的委托返回何种类型的对象路由系统总能做出对应的处理。比如对于返回的字符串会直接作为响应的主体内容并将Content-Type报头设置为“text/plain”。如果希望对返回对象具有明确的控制最好返回一个IResult对象或者TaskIResult和ValueTaskIResultIResult相当ASP.NET MVC中的IActionResult。我们演示程序中的ForecastAsync方法也可以改写成如下这个返回类型为IResult的Forecast方法该方法通过调用Results类型的静态Content方法返回一个ContentResult对象它将天气报告转换成的HTML作为响应类型Content-Type报头设置为 “text/html” 。using App;var app WebApplication.Create();
app.MapGet(weather/{city}/{days}, Forecast);
app.Run();static IResult Forecast(HttpContext context, string city, int days)
{var report WeatherReportUtility.Generate(city,days);return Results.Content(WeatherReportUtility.AsHtml(report), text/html);
}