《ASP.NET Core 6框架揭秘》實例演示[31]:路由高階用法

ASP.NET的路由是通過EndpointRoutingMiddleware和EndpointMiddleware這兩個中間件協作完成的,它們在ASP.NET平臺上具有舉足輕重的地位,MVC和gRPC框架,Dapr的Actor和發布訂閱編程模式都建立在路由系統之上。Minimal API更是將提升到了前所未有的高度,上一篇通過9個實例演示了基于路由的REST API開發,本篇演示一些“高階”的用法。

[S2010]解析路由模式 (源代碼)
[S2011]利用多個中間件來構建終結點處理器(源代碼)
[S2012]在參數上標注特性來決定綁定的數據源(源代碼)
[S2013]默認的參數綁定規則(源代碼)
[S2014]針對TryPar[Se方法的參數綁定(源代碼)
[S2015]針對BindA[Sync方法的參數綁定(源代碼)
[S2016]自定義路由約束(源代碼)

[S2010]解析路由模式

下面我們通過一個簡單的實例演示如何利用RoutePatternFactory對象解析指定的路由模板,并生成對應的RoutePattern對象。我們定義了如下所示的Format方法將指定的RoutePattern對象格式化成一個字符串。

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)=>?part?switch{RoutePatternLiteralPart?literal?=>?$"Literal:?{literal.Content}",RoutePatternSeparatorPart?separator?=>?$"Separator:?{separator.Content}",RoutePatternParameterPart?parameter?=>?@$"Parameter:?Name?=?{parameter.Name};?Default?=?{parameter.Default};?IsOptional?=?{?parameter.IsOptional};?IsCatchAll?=?{?parameter.IsCatchAll};ParameterKind?=?{?parameter.ParameterKind}",_?=>?throw?new?ArgumentException("Invalid?RoutePatternPart.")};
}

如下的演示程序調用了RoutePatternFactory 類型的靜態方法Parse解析指定的路由模板“weather/{city:regex(^0\d{{2,3}}$)=010}/{days:int:range(1,4)=4}/{detailed?}”生成一個RoutePattern對象,我們在調用該方法時還指定了requiredValues參數。我們調用創建的WebApplication對象的MapGet方法注冊了針對根路徑“/”的終結點,對應的處理器直接返回RoutePattern對象格式化生成的字符串。

using?Microsoft.AspNetCore.Routing.Patterns;
using?System.Text;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?});var?app?=?WebApplication.Create();
app.MapGet("/",?()=>?Format(pattern));
app.Run();

如果利用瀏覽器訪問啟動后的應用程序,回到得到如圖1所示結果,它結構化地展示了路由模式的原始文本、出入棧路由匹配權重、每個段的組成、路由參數的默認值和參數策略,以及生成URL必須提供的默認參數值。

5fe1bd9801fd81f8a18156e804c28e1c.png

圖1 針對路由模式的解析

[S2011]利用多個中間件來構建終結點處理器

如果某個終結點針對請求處理的邏輯相對復雜,需要多個中間件協同完成,我們可以調用IEndpointRouteBuilder 對象的CreateApplicationBuilder方法創建一個新的IApplicationBuilder對象,并將這些中間件注冊到這個該對象上,最后利用它這些中間件轉換成RequestDelegate委托。

var?app?=?WebApplication.Create();
IEndpointRouteBuilder?routeBuilder?=?app;
app.MapGet("/foobar",?routeBuilder.CreateApplicationBuilder().Use(FooMiddleware).Use(BarMiddleware).Use(BazMiddleware).Build());
app.Run();static?async?Task?FooMiddleware(HttpContext?context,RequestDelegate?next)
{await?context.Response.WriteAsync("Foo=>");await?next(context);
};
static?async?Task?BarMiddleware(HttpContext?context,?RequestDelegate?next)
{await?context.Response.WriteAsync("Bar=>");await?next(context);
};
static?Task?BazMiddleware(HttpContext?context,?RequestDelegate?next)?=>?context.Response.WriteAsync("Baz");

上面的演示程序注冊了一個路徑模板為“foobar”的路由,并注冊了三個中間件來處理路由的請求。該演示程序啟動之后,如果我們利用瀏覽器對路由地址“/foobar”發起請求,將會得到如圖2所示的輸出結果。呈現出來的字符串是通過注冊的三個中間件(FooMiddleware、BarMiddleware和BazMiddleware)輸出內容組合而成。

cdf5e233af54d065ac2b639c4a8dc17d.png

圖2 輸出結果

[S2012]在參數上標注特性來決定綁定的數據源

如下這個演示程序調用WebApplication對象的MapPost方法注冊了一個采用“/{foo}”作為模板的終結點。作為終結點處理器的委托指向靜態方法Handle,我們為這個方法定義了五個參數,分別標注了上述五個特性。我們將五個參數組合成一個匿名對象作為返回值。

using?Microsoft.AspNetCore.Mvc;
var?app?=?WebApplication.Create();
app.MapPost("/{foo}",?Handle);
app.Run();static?object?Handle([FromRoute]?string?foo,[FromQuery]?int?bar,[FromHeader]?string?host,[FromBody]?Point?point,[FromServices]?IHostEnvironment?environment)=>?new?{?Foo?=?foo,?Bar?=?bar,?Host?=?host,?Point?=?point,Environment?=?environment.EnvironmentName?};public?class?Point
{public?int?X?{?get;?set;?}public?int?Y?{?get;?set;?}
}

程序啟動之后,我們針對“http://localhost:5000/abc?bar=123”這個URL發送了一個POST請求,請求的主體內容為一個Point對象序列化成生成的JSON。如下所示的是請求報文和響應報文的內容,可以看出Handle方法的foo和bar參數分別綁定的是路由參數“foo”和查詢字符串“bar”的值,參數host綁定的是請求的Host報頭,參數point是請求主體內容反序列化的結果,參數environment則是由針對當前請求的IServiceProvider對象提供的服務(S2012)。

POST?http://localhost:5000/abc?bar=123?HTTP/1.1
Content-Type:?application/json
Host:?localhost:5000
Content-Length:?18{"x":123,?"y":456}
HTTP/1.1?200?OK
Content-Type:?application/json;?charset=utf-8
Date:?Sat,?06?Nov?2021?11:55:54?GMT
Server:?Kestrel
Content-Length:?100{"foo":"abc","bar":123,"host":"localhost:5000","point":{"x":123,"y":456},"environment":"Production"}

[S2013]默認的參數綁定規則

如果請求處理器方法的參數沒有顯式指定綁定數據的來源,路由系統也能根據參數的類型盡可能地從當前HttpContext上下文中提取相應的內容予以綁定。針對如下這幾個類型,對應參數的綁定源是明確的。

  • HttpContext:綁定為當前HttpContext上下文。

  • HttpRequest:綁定為當前HttpContext上下文的Request屬性。

  • HttpResponse: 綁定為當前HttpContext上下文的Response屬性。

  • ClaimsPrincipal: 綁定為當前HttpContext上下文的User屬性。

  • CancellationToken: 綁定為當前HttpContext上下文的RequestAborted屬性。

上述的綁定規則體現在如下演示程序的調試斷言中。這個演示實例還體現了另一個綁定規則,那就是只要當前請求的IServiceProvider能夠提供對應的服務,對應參數(“httpContextAccessor”)上標注的FromSerrvicesAttribute特性不是必要的。但是倘若缺少對應的服務注冊,請求的主體內容會一般會作為默認的數據來源,所以FromSerrvicesAttribute特性最好還是顯式指定為好。對于我們演示的這個例子,如果我們將前面針對AddHttpContextAccessor方法的調用移除,對應參數的綁定自然會失敗,但是錯誤消息并不是我們希望看到的。

using?System.Diagnostics;
using?System.Security.Claims;var?builder?=?WebApplication.CreateBuilder();
builder.Services.AddHttpContextAccessor();
var?app?=?builder.Build();
app.MapGet("/",?Handle);
app.Run();static?void?Handle(HttpContext?httpContext,?HttpRequest?request,?HttpResponse?response,ClaimsPrincipal?user,?CancellationToken?cancellationToken,?IHttpContextAccessor?httpContextAccessor)
{var?currentContext?=?httpContextAccessor.HttpContext;Debug.Assert(ReferenceEquals(httpContext,?currentContext));Debug.Assert(ReferenceEquals(request,?currentContext.Request));Debug.Assert(ReferenceEquals(response,?currentContext.Response));Debug.Assert(ReferenceEquals(user,?currentContext.User));Debug.Assert(cancellationToken?==?currentContext.RequestAborted);
}

[S2014]針對TryParse方法的參數綁定

如果我們在某個類型中定義了一個名為TryParse的靜態方法將指定的字符串表達式轉換成當前類型的實例,路由系統在對該類型的參數進行綁定的時候會優先從路由參數和查詢字符串中提取相應的內容,并通過調用這個方法生成綁定的參數。

var?app?=?WebApplication.Create();
app.MapGet("/",?(Point?foobar)?=>?foobar);
app.Run();public?class?Point
{public?int?X?{?get;?set;?}public?int?Y?{?get;?set;?}public?Point(int?x,?int?y){X?=?x;Y?=?y;}public?static?bool?TryParse(string?expression,?out?Point??point){var?split?=?expression.Trim('(',?')').Split(',');if?(split.Length?==?2?&&?int.TryParse(split[0],?out?var?x)?&&?int.TryParse(split[1],?out?var?y)){point?=?new?Point(x,?y);return?true;}point?=?null;return?false;}
}

上面的演示程序為自定義的Point類型定義了一個靜態的TryParse方法使我們可以將一個以“(x,y)”形式定義的表達式轉換成Point對象。注冊的終結點處理器委托以該類型為參數,指定的參數名稱為“foobar”。我們在發送的請求中以查詢字符串的形式提供對應的表達式“(123,456)”,從返回的內容可以看出參數得到了成功綁定。

778865c73c944e5612e668417f26074b.png

圖3 TryParse方法針對參數綁定的影響

[S2015]針對BindAsync方法的參數綁定

如果某種類型的參數具有特殊的綁定方式,我們還可以將具體的綁定實現在一個按照約定定義的BindAsync方法中。按照約定,這個BindAsync應該定義成返回類型為ValueTask<T>的靜態方法,它可以擁有一個類型為HttpContext的參數,也可以額外提供一個ParameterInfo類型的參數,這兩個參數分別與當前HttpContext上下文和描述參數的ParameterInfo對象綁定。前面演示實例中為Point類型定義了一個TryParse方法可以替換成如下這個 BingAsync方法。

public?class?Point
{public?int?X?{?get;?set;?}public?int?Y?{?get;?set;?}public?Point(int?x,?int?y){X?=?x;Y?=?y;}public?static?ValueTask<Point?>?BindAsync(HttpContext?httpContext,?ParameterInfo?parameter){Point??point?=?null;var?name?=?parameter.Name;var?value?=?httpContext.GetRouteData().Values.TryGetValue(name!,?out?var?v)???v?:?httpContext.Request.Query[name!].SingleOrDefault();if?(value?is?string?expression){var?split?=?expression.Trim('(',?')')?.Split(',');if?(split?.Length?==?2?&&?int.TryParse(split[0],?out?var?x)??&&?int.TryParse(split[1],?out?var?y)){point?=?new?Point(x,?y);}}return?new?ValueTask<Point?>(point);}
}

[S2016]自定義路由約束

我們可以使用預定義的IRouteConstraint實現類型完成一些常用的約束,但是在一些對路由參數具有特定約束的應用場景中,我們不得不創建自定義的約束類型。舉個例子,如果需要對資源提供針對多語言的支持,最好的方式是在請求的URL中提供對應的Culture。為了確保包含在URL中的是一個合法有效的Culture,最好為此定義相應的約束。下面將通過一個簡單的實例來演示如何創建這樣一個用于驗證Culture的自定義路由約束。我們創建了一個提供基于不同語言資源的API。我們將資源文件作為文本資源進行存儲,如圖4所示,我們創建了兩個資源文件 (Resources.resx和Resources.zh.resx),并定義了一個名為hello的文本資源條目。

70889e2d0bfd510d8d0e3114afda9da5.png

圖4 存儲文本資源的兩個資源文件

如下演示程序中注冊了一個模板為“resources/{lang:culture}/{resourceName:required}”的終結點。路由參數“{resourceName}”表示資源條目的名稱(比如“hello”),另一個路由參數“{lang}”表示指定的語言,約束表達式名稱culture對應的就是我們自定義的針對語言文化的約束類型CultureConstraint。因為這是一個自定義的路由約束,我們通過調用IServiceCollection接口的Configure<TOptions>方法將此約束采用的表達式名稱(“culture”)和CultureConstraint類型之間的映射關系添加到RouteOptions配置選項中。

using?App;
using?App.Properties;
using?System.Globalization;var?builder?=?WebApplication.CreateBuilder();
var?template?=?"resources/{lang:culture}/{resourceName:required}";
builder.Services.Configure<RouteOptions>(options?=>?options.ConstraintMap.Add("culture",?typeof(CultureConstraint)));
var?app?=?builder.Build();
app.MapGet(template,?GetResource);
app.Run();static?IResult?GetResource(string?lang,?string?resourceName)
{CultureInfo.CurrentUICulture?=?new?CultureInfo(lang);var?text?=?Resources.ResourceManager.GetString(resourceName);return?string.IsNullOrEmpty(text)??Results.NotFound():?Results.Content(text);
}

該終結點的處理方法GetResource定義了兩個參數,我們知道它們會自動綁定為同名的路由參數。由于系統自動根據當前線程的UICulture來選擇對應的資源文件,我們對CultureInfo類型的CurrentUICulture靜態屬性進行了設置。如果從資源文件將對應的文本提取出來,我們將創建一個ContentResult對象并返回。應用啟動之后,我們可以利用瀏覽器指定匹配的URL獲取對應語言的文本。如圖5所示,如果指定一個不合法的語言(如“xx”),將會違反我們自定義的約束,此時就會得到一個狀態碼為“404 Not Found”的響應。

b9c6a79687f36112024f2133d113d3a5.png

圖5 采用相應的URL得到某個資源針對某種語言的內容

我們來看看針對語言文化的路由約束CultureConstraint究竟做了什么。如下面的代碼片段所示,我們在Match方法中會試圖獲取作為語言文化內容的路由參數值,如果存在這樣的路由參數,就可以利用它創建一個CultureInfo對象。如果這個CultureInfo對象的EnglishName屬性名不以“Unknown Language”字符串作為前綴,我們就認為指定的是合法的語言文件。

public?class?CultureConstraint?:?IRouteConstraint
{public?bool?Match(HttpContext??httpContext,?IRouter??route,?string?routeKey,RouteValueDictionary?values,?RouteDirection?routeDirection){try{if?(values.TryGetValue(routeKey,?out?var?value)?&&?value?is?not?null){return?!new?CultureInfo((string)value).EnglishName.StartsWith("Unknown?Language");}return?false;}catch{return?false;}}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/281425.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/281425.shtml
英文地址,請注明出處:http://en.pswp.cn/news/281425.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

java中文亂碼解決之道(五)—–java是如何編碼解碼的

編碼&解碼 1&#xff1a;I/O操作 2&#xff1a;內存 3&#xff1a;數據庫 4&#xff1a;javaWeb 下面主要介紹前面兩種場景&#xff0c;數據庫部分只要設置正確編碼格式就不會有什么問題&#xff0c;javaWeb場景過多需要了解URL、get、POST的編碼&#xff0c;servlet的解碼…

java反射--Class類

面向對象的世界里&#xff0c;萬事萬物皆對象。 1&#xff09;類是誰的對象呢&#xff1f; 類是對象&#xff0c;類是java.lang.Class類的實例對象。 2&#xff09;這個對象如何表示呢&#xff1f; package com.reflect;public class ClassDemo1 {public static void main(Stri…

win10系統按esc會彈出計算機,win10系統版本2004控制面板多出ESC是什么原因?

如果我們的電腦在升級了win102004控制面板多出ESC什么情況方法一&#xff1a;“干凈啟動”&#xff0c;排除第三方軟體的影響1.停止非核心的程序運作(包括第三方殺毒、優化軟體)2.情況允許的話&#xff0c;卸載設備中的第三方殺毒、管家、優化軟件3.同時按【4.點擊【服務】>…

CentOS6/7 配置守護進程

CentOS6.xCentOS6中轉用Upstrat代替以前的init.d/rcX.d的線性啟動方式。一、相關命令通過initctl help可以查看相關命令[rootlocalhost ~]# initctl help Job commands:start Start job.stop Stop job.restart …

Vue源碼解析之數組變異

力有不逮的對象 眾所周知&#xff0c;在 Vue 中&#xff0c;直接修改對象屬性的值無法觸發響應式。當你直接修改了對象屬性的值&#xff0c;你會發現&#xff0c;只有數據改了&#xff0c;但是頁面內容并沒有改變。 這是什么原因&#xff1f; 原因在于&#xff1a; Vue 的響應式…

linux守護進程的編寫

linux監控一個進程進行 代碼如下: #!/bin/shcd /home/autoprocess/ autopgrep -f autoProcessNew.php | wc -l if [ "$auto" 0 ] then nohup php autoProcessNew.php & fi 監視autoProcessNew.php,使他一直監視轉載于:https://www.cnblogs.com/matengfei123/p/…

微軟2014編程之美初賽第一場——題目3 : 活動中心

【來源】 題目3 : 活動中心 【分析】 本題採用的是三分法。 輸入的一組點中找出左右邊界。作為起始邊界。 while(右邊界-左邊界<精度){將左右邊界構成的線段均勻分成3段&#xff0c;推斷切割點的距離關系&#xff0c;抹去距離大的一段。更新左右邊界。 } 輸出左(右)邊界 【…

windows10計算機里輸入法,win10電腦上輸入法不見了怎么辦

好的輸入法可以加快我們的工作效率&#xff0c;當電腦上輸入法不見時&#xff0c;你會調出來嗎?下面小編告訴你win10電腦上輸入法不見時弄出來的一些訣竅吧。win10電腦上輸入法不見了的解決方法win10電腦上輸入法不見了的解決方法&#xff1a;Win10系統輸入法圖標不見了的找回…

Java并發(二十一):線程池實現原理

一、總覽 線程池類ThreadPoolExecutor的相關類需要先了解&#xff1a; &#xff08;圖片來自&#xff1a;https://javadoop.com/post/java-thread-pool#%E6%80%BB%E8%A7%88&#xff09; Executor&#xff1a;位于最頂層&#xff0c;只有一個 execute(Runnable runnable) 方法&a…

進程池

轉自&#xff1a;https://www.cnblogs.com/kaituorensheng/p/4465768.html 在利用Python進行系統管理的時候&#xff0c;特別是同時操作多個文件目錄&#xff0c;或者遠程控制多臺主機&#xff0c;并行操作可以節約大量的時間。當被操作對象數目不大時&#xff0c;可以直接利用…

gulp版本號管理插件注意事項

2019獨角獸企業重金招聘Python工程師標準>>> 打開node_modules\gulp-rev\index.js 第144行 manifest[originalFile] revisionedFile; 更新為: manifest[originalFile] originalFile ?v file.revHash; 打開node_modules\rev-path\index.js 第10行 return filena…

bigfile.to服務器位置,Cloudera Manager 遷移服務器

Cloudera Manager還是比較耗資源的&#xff0c;想把Cloudera Manager&#xff0c;移動到比較好的機器上。在這篇文章中&#xff0c;Cloudera Manager安裝在bigserver1上面&#xff0c;bigserver1是奔騰雙核的CPU。1&#xff0c;Cloudera Manager占資源比較多cloudera manager占…

vue定時ajax獲取數據,vue 中使用 AJAX獲取數據的方法

在VUE開發時&#xff0c;數據可以使用jquery和vue-resource來獲取數據。在獲取數據時&#xff0c;一定需要給一個數據初始值。看下例&#xff1a;new Vue({el:#app,data:{data:""},created:function(){var url"json.jsp";var _selfthis;$.get(url,function…

轉:shell awk

簡單使用&#xff1a; awk &#xff1a;對于文件中一行行的獨處來執行操作 。 awk -F &#xff1a;{print $1,$4} :使用‘&#xff1a;’來分割這一行&#xff0c;把這一行的第一第四個域打印出來 。 詳細介紹&#xff1a; AWK命令介紹 awk語言的最基本功能是在文件或字符串中基…

Mac使用crontab來實現定時任務

crontab 定時執行 配置文件都在/etc/crontab下&#xff0c;如果沒有就創建 語法&#xff1a; crontab [-e [UserName]|-l [UserName]|-r [UserName]|-v [UserName]|File ] 說明&#xff1a; crontab 是用來讓使用者在固定時間或固定間隔執行程序之用&#xff0c;換句話說&#…

前端技術周刊 2018-12-03:DOM

前端快爆 Chrome 71 開始將試用 SXG 功能&#xff0c;它是由 IETF 提出&#xff0c;Web Package 協議規范下的 Signed HTTP Exchanges 功能的縮寫。該技術使得一個第三方服務器可以直接向用戶提供可靠資源&#xff0c;且不用與原站共享 HTTPS 證書密鑰。?點評&#xff1a;一項…

公司新來了一位阿里P9,在全員大會上講葷段子!還是上個世紀的老段子,太爛了!...

阿里P9在坊間的名聲一向不好&#xff0c;這幾年在業界出了不少令人無語的新聞&#xff0c;今天又來了一個&#xff1a;公司新來了一位阿里P9偽高管&#xff0c;全員大會上來先講了一個葷段子&#xff0c;這個破段子還是上個世紀的&#xff0c;太爛了&#xff01;關于這個段子&a…

【轉】博客美化(1)基本后臺設置與樣式設置

閱讀目錄 1.博客園后臺設置2.自定義樣式的設置博客園美化相關文章目錄&#xff1a;博客園博客美化相關文章目錄 一直都拜膜那些博客園的皮膚設計高手&#xff0c;由于本人對前端研究甚少&#xff0c;所以js,css這種東西只能看得懂最基本的&#xff0c;會簡單改改。然后一直對自…

Airdoc創始人:工智能可以在醫療領域多個環節發揮作用 但有局限性

7月1日&#xff0c;在由武漢國家生物產業基地建設管理辦公室主辦、火石創造承辦、光谷健康智慧園協辦的醫療大數據與醫學人工智能高峰論壇上&#xff0c;Airdoc創始人兼董事長張大磊做了題為《AI在醫療領域中應用的問題與局限》的演講。 Airdoc是醫療領域人工智能領軍企業&…

我的世界服務器抽獎系統怎么弄,我的世界自動識別貨幣抽獎機如何制作

我的世界是一款很經典的沙盒類游戲&#xff0c;在游戲中紅石和命令方塊是這部作品的核心&#xff0c;可以制作很多裝備和道具&#xff0c;下面給大家分享下我的世界自動識別貨幣抽獎機如何制作&#xff0c;希望對大家有所幫助。自動識別貨幣抽獎機制作方法廢話不多說,(貌似一句…