webapp導(dǎo)航橫向滑動(dòng)(div橫向?qū)Ш綑?
[S2010]解析路由模式 (源代碼)
[S2011]利用多個(gè)中間件來(lái)構(gòu)建終結(jié)點(diǎn)處理器(源代碼)
[S2012]在參數(shù)上標(biāo)注特性來(lái)決定綁定的數(shù)據(jù)源(源代碼)
[S2013]默認(rèn)的參數(shù)綁定規(guī)則(源代碼)
[S2014]針對(duì)TryPar[Se方法的參數(shù)綁定(源代碼)
[S2015]針對(duì)BindA[Sync方法的參數(shù)綁定(源代碼)
[S2016]自定義路由約束(源代碼)
[S2010]解析路由模式 (源代碼)
[S2011]利用多個(gè)中間件來(lái)構(gòu)建終結(jié)點(diǎn)處理器(源代碼)
[S2012]在參數(shù)上標(biāo)注特性來(lái)決定綁定的數(shù)據(jù)源(源代碼)
[S2013]默認(rèn)的參數(shù)綁定規(guī)則(源代碼)
[S2014]針對(duì)TryPar[Se方法的參數(shù)綁定(源代碼)
[S2015]針對(duì)BindA[Sync方法的參數(shù)綁定(源代碼)
[S2016]自定義路由約束(源代碼)
下面我們通過(guò)一個(gè)簡(jiǎn)單的實(shí)例演示如何利用RoutePatternFactory對(duì)象解析指定的路由模板,并生成對(duì)應(yīng)的RoutePattern對(duì)象。我們定義了如下所示的Format方法將指定的RoutePattern對(duì)象格式化成一個(gè)字符串。
staticstringFormat( RoutePattern pattern)
{
varbuilder = newStringBuilder;
builder.AppendLine(
$"RawText: {pattern.RawText}" );
builder.AppendLine(
$"InboundPrecedence: {pattern.InboundPrecedence}" );
builder.AppendLine(
$"OutboundPrecedence: {pattern.OutboundPrecedence}" );
展開(kāi)全文
varsegments = pattern.PathSegments;
builder.AppendLine( "Segments");
foreach( varsegment insegments)
{
foreach( varpart insegment.Parts)
{
builder.AppendLine( $"\t {ToString(part)}" );
}
}
builder.AppendLine( "Defaults");
foreach( var@ defaultinpattern.Defaults)
{
builder.AppendLine(
$"\t {@ default.Key} = {@ default.Value} " );
}
builder.AppendLine( "ParameterPolicies ");
foreach( varpolicy inpattern.ParameterPolicies)
{
builder.AppendLine(
$"\t {policy.Key}= { string.Join( ',',policy.Value.Select(it = it.Content))} " );
}
builder.AppendLine( "RequiredValues");
foreach( varrequired inpattern.RequiredValues)
{
builder.AppendLine(
$"\t {required.Key}= {required.Value}" );
}
returnbuilder.ToString;
staticstringToString( 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}" ,
_ = thrownewArgumentException( "Invalid RoutePatternPart.")
};
}
usingMicrosoft.AspNetCore.Routing.Patterns;
usingSystem.Text;
vartemplate = @"weather/{city:regex(^0\d{{2,3}}$)=010}/{days:int:range(1,4)=4}/{detailed?}";
varpattern = RoutePatternFactory.Parse(
pattern: template,
defaults: null,
parameterPolicies: null,
requiredValues: new{ city = "010", days = 4});
varapp = WebApplication.Create;
app.MapGet( "/", = Format(pattern));
app.Run;
如果利用瀏覽器訪問(wèn)啟動(dòng)后的應(yīng)用程序,回到得到如圖1所示結(jié)果,它結(jié)構(gòu)化地展示了路由模式的原始文本、出入棧路由匹配權(quán)重、每個(gè)段的組成、路由參數(shù)的默認(rèn)值和參數(shù)策略,以及生成URL必須提供的默認(rèn)參數(shù)值。
圖1 針對(duì)路由模式的解析
[S2011]利用多個(gè)中間件來(lái)構(gòu)建終結(jié)點(diǎn)處理器
如果某個(gè)終結(jié)點(diǎn)針對(duì)請(qǐng)求處理的邏輯相對(duì)復(fù)雜,需要多個(gè)中間件協(xié)同完成,我們可以調(diào)用IEndpointRouteBuilder 對(duì)象的CreateApplicationBuilder方法創(chuàng)建一個(gè)新的IApplicationBuilder對(duì)象,并將這些中間件注冊(cè)到這個(gè)該對(duì)象上,最后利用它這些中間件轉(zhuǎn)換成RequestDelegate委托。
varapp = WebApplication.Create;
IEndpointRouteBuilder routeBuilder = app;
app.MapGet( "/foobar", routeBuilder
.CreateApplicationBuilder
.Use(FooMiddleware)
.Use(BarMiddleware)
.Use(BazMiddleware)
.Build);
app.Run;
staticasyncTask FooMiddleware(
HttpContext context,RequestDelegate next )
{
awaitcontext.Response.WriteAsync( "Foo=");
awaitnext(context);
};
staticasyncTask BarMiddleware(
HttpContext context, RequestDelegate next )
{
awaitcontext.Response.WriteAsync( "Bar=");
awaitnext(context);
};
staticTask BazMiddleware(
HttpContext context, RequestDelegate next )
= context.Response.WriteAsync( "Baz");
上面的演示程序注冊(cè)了一個(gè)路徑模板為“foobar”的路由,并注冊(cè)了三個(gè)中間件來(lái)處理路由的請(qǐng)求。該演示程序啟動(dòng)之后,如果我們利用瀏覽器對(duì)路由地址“/foobar”發(fā)起請(qǐng)求,將會(huì)得到如圖2所示的輸出結(jié)果。呈現(xiàn)出來(lái)的字符串是通過(guò)注冊(cè)的三個(gè)中間件(FooMiddleware、BarMiddleware和BazMiddleware)輸出內(nèi)容組合而成。
圖2 輸出結(jié)果
[S2012]在參數(shù)上標(biāo)注特性來(lái)決定綁定的數(shù)據(jù)源
如下這個(gè)演示程序調(diào)用WebApplication對(duì)象的MapPost方法注冊(cè)了一個(gè)采用“/{foo}”作為模板的終結(jié)點(diǎn)。作為終結(jié)點(diǎn)處理器的委托指向靜態(tài)方法Handle,我們?yōu)檫@個(gè)方法定義了五個(gè)參數(shù),分別標(biāo)注了上述五個(gè)特性。我們將五個(gè)參數(shù)組合成一個(gè)匿名對(duì)象作為返回值。
usingMicrosoft.AspNetCore.Mvc;
varapp = WebApplication.Create;
app.MapPost( "/{foo}", Handle);
app.Run;
staticobjectHandle(
[FromRoute] stringfoo,
[FromQuery] intbar,
[FromHeader] stringhost,
[FromBody] Point point,
[FromServices] IHostEnvironment environment )
= new{ Foo = foo, Bar = bar, Host = host, Point = point,
Environment = environment.EnvironmentName };
publicclassPoint
{
publicintX { get; set; }
publicintY { get; set; }
}
程序啟動(dòng)之后,我們針對(duì)“http://localhost:5000/abc?bar=123”這個(gè)URL發(fā)送了一個(gè)POST請(qǐng)求,請(qǐng)求的主體內(nèi)容為一個(gè)Point對(duì)象序列化成生成的JSON。如下所示的是請(qǐng)求報(bào)文和響應(yīng)報(bào)文的內(nèi)容,可以看出Handle方法的foo和bar參數(shù)分別綁定的是路由參數(shù)“foo”和查詢字符串“bar”的值,參數(shù)host綁定的是請(qǐng)求的Host報(bào)頭,參數(shù)point是請(qǐng)求主體內(nèi)容反序列化的結(jié)果,參數(shù)environment則是由針對(duì)當(dāng)前請(qǐng)求的IServiceProvider對(duì)象提供的服務(wù)(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.1200OK
Content-Type: application/json; charset=utf -8
Date: Sat, 06Nov 202111: 55: 54GMT
Server: Kestrel
Content-Length: 100
{ "foo": "abc", "bar": 123, "host": "localhost:5000", "point":{ "x": 123, "y": 456}, "environment": "Production"}
[S2013]默認(rèn)的參數(shù)綁定規(guī)則
如果請(qǐng)求處理器方法的參數(shù)沒(méi)有顯式指定綁定數(shù)據(jù)的來(lái)源,路由系統(tǒng)也能根據(jù)參數(shù)的類型盡可能地從當(dāng)前HttpContext上下文中提取相應(yīng)的內(nèi)容予以綁定。針對(duì)如下這幾個(gè)類型,對(duì)應(yīng)參數(shù)的綁定源是明確的。
HttpContext:綁定為當(dāng)前HttpContext上下文。
HttpRequest:綁定為當(dāng)前HttpContext上下文的Request屬性。
HttpResponse: 綁定為當(dāng)前HttpContext上下文的Response屬性。
ClaimsPrincipal: 綁定為當(dāng)前HttpContext上下文的User屬性。
CancellationToken: 綁定為當(dāng)前HttpContext上下文的RequestAborted屬性。
上述的綁定規(guī)則體現(xiàn)在如下演示程序的調(diào)試斷言中。這個(gè)演示實(shí)例還體現(xiàn)了另一個(gè)綁定規(guī)則,那就是只要當(dāng)前請(qǐng)求的IServiceProvider能夠提供對(duì)應(yīng)的服務(wù),對(duì)應(yīng)參數(shù)(“httpContextAccessor”)上標(biāo)注的FromSerrvicesAttribute特性不是必要的。但是倘若缺少對(duì)應(yīng)的服務(wù)注冊(cè),請(qǐng)求的主體內(nèi)容會(huì)一般會(huì)作為默認(rèn)的數(shù)據(jù)來(lái)源,所以FromSerrvicesAttribute特性最好還是顯式指定為好。對(duì)于我們演示的這個(gè)例子,如果我們將前面針對(duì)AddHttpContextAccessor方法的調(diào)用移除,對(duì)應(yīng)參數(shù)的綁定自然會(huì)失敗,但是錯(cuò)誤消息并不是我們希望看到的。
usingSystem.Diagnostics;
usingSystem.Security.Claims;
varbuilder = WebApplication.CreateBuilder;
builder.Services.AddHttpContextAccessor;
varapp = builder.Build;
app.MapGet( "/", Handle);
app.Run;
staticvoidHandle(
HttpContext httpContext,
HttpRequest request,
HttpResponse response,
ClaimsPrincipal user,
CancellationToken cancellationToken,
IHttpContextAccessor httpContextAccessor )
{
varcurrentContext = 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]針對(duì)TryParse方法的參數(shù)綁定
如果我們?cè)谀硞€(gè)類型中定義了一個(gè)名為T(mén)ryParse的靜態(tài)方法將指定的字符串表達(dá)式轉(zhuǎn)換成當(dāng)前類型的實(shí)例,路由系統(tǒng)在對(duì)該類型的參數(shù)進(jìn)行綁定的時(shí)候會(huì)優(yōu)先從路由參數(shù)和查詢字符串中提取相應(yīng)的內(nèi)容,并通過(guò)調(diào)用這個(gè)方法生成綁定的參數(shù)。
varapp = WebApplication.Create;
app.MapGet( "/", (Point foobar) = foobar);
app.Run;
publicclassPoint
{
publicintX { get; set; }
publicintY { get; set; }
publicPoint( intx, inty )
{
X = x;
Y = y;
}
publicstaticboolTryParse(
stringexpression, outPoint? point )
{
varsplit = expression.Trim( '(', ')').Split( ',');
if(split.Length == 2
int.TryParse(split[ 0], outvarx)
int.TryParse(split[ 1], outvary))
{
point = newPoint(x, y);
returntrue;
}
point = null;
returnfalse;
}
}
上面的演示程序?yàn)樽远x的Point類型定義了一個(gè)靜態(tài)的TryParse方法使我們可以將一個(gè)以“(x,y)”形式定義的表達(dá)式轉(zhuǎn)換成Point對(duì)象。注冊(cè)的終結(jié)點(diǎn)處理器委托以該類型為參數(shù),指定的參數(shù)名稱為“foobar”。我們?cè)诎l(fā)送的請(qǐng)求中以查詢字符串的形式提供對(duì)應(yīng)的表達(dá)式“(123,456)”,從返回的內(nèi)容可以看出參數(shù)得到了成功綁定。
圖3 TryParse方法針對(duì)參數(shù)綁定的影響
[S2015]針對(duì)BindAsync方法的參數(shù)綁定
如果某種類型的參數(shù)具有特殊的綁定方式,我們還可以將具體的綁定實(shí)現(xiàn)在一個(gè)按照約定定義的BindAsync方法中。按照約定,這個(gè)BindAsync應(yīng)該定義成返回類型為ValueTaskT的靜態(tài)方法,它可以擁有一個(gè)類型為HttpContext的參數(shù),也可以額外提供一個(gè)ParameterInfo類型的參數(shù),這兩個(gè)參數(shù)分別與當(dāng)前HttpContext上下文和描述參數(shù)的ParameterInfo對(duì)象綁定。前面演示實(shí)例中為Point類型定義了一個(gè)TryParse方法可以替換成如下這個(gè) BingAsync方法。
publicclassPoint
{
publicintX { get; set; }
publicintY { get; set; }
publicPoint( intx, inty )
{
X = x;
Y = y;
}
publicstaticValueTaskPoint? BindAsync(
HttpContext httpContext,
ParameterInfo parameter)
{
Point? point = null;
varname = parameter.Name;
varvalue= httpContext.GetRouteData
.Values.TryGetValue(name!, outvarv)
? v
: httpContext.Request.Query[name!].SingleOrDefault;
if( valueisstringexpression)
{
varsplit = expression.Trim( '(', ')')?.Split( ',');
if(split?.Length == 2
int.TryParse(split[ 0], outvarx)
int.TryParse(split[ 1], outvary))
{
point = newPoint(x, y);
}
}
returnnewValueTaskPoint?(point);
}
}
[S2016]自定義路由約束
我們可以使用預(yù)定義的IRouteConstraint實(shí)現(xiàn)類型完成一些常用的約束,但是在一些對(duì)路由參數(shù)具有特定約束的應(yīng)用場(chǎng)景中,我們不得不創(chuàng)建自定義的約束類型。舉個(gè)例子,如果需要對(duì)資源提供針對(duì)多語(yǔ)言的支持,最好的方式是在請(qǐng)求的URL中提供對(duì)應(yīng)的Culture。為了確保包含在URL中的是一個(gè)合法有效的Culture,最好為此定義相應(yīng)的約束。下面將通過(guò)一個(gè)簡(jiǎn)單的實(shí)例來(lái)演示如何創(chuàng)建這樣一個(gè)用于驗(yàn)證Culture的自定義路由約束。我們創(chuàng)建了一個(gè)提供基于不同語(yǔ)言資源的API。我們將資源文件作為文本資源進(jìn)行存儲(chǔ),如圖4所示,我們創(chuàng)建了兩個(gè)資源文件 (Resources.resx和Resources.zh.resx),并定義了一個(gè)名為hello的文本資源條目。
圖4 存儲(chǔ)文本資源的兩個(gè)資源文件
如下演示程序中注冊(cè)了一個(gè)模板為“resources/{lang:culture}/{resourceName:required}”的終結(jié)點(diǎn)。路由參數(shù)“{resourceName}”表示資源條目的名稱(比如“hello”),另一個(gè)路由參數(shù)“{lang}”表示指定的語(yǔ)言,約束表達(dá)式名稱culture對(duì)應(yīng)的就是我們自定義的針對(duì)語(yǔ)言文化的約束類型CultureConstraint。因?yàn)檫@是一個(gè)自定義的路由約束,我們通過(guò)調(diào)用IServiceCollection接口的ConfigureTOptions方法將此約束采用的表達(dá)式名稱(“culture”)和CultureConstraint類型之間的映射關(guān)系添加到RouteOptions配置選項(xiàng)中。
usingApp;
usingApp.Properties;
usingSystem.Globalization;
varbuilder = WebApplication.CreateBuilder;
vartemplate = "resources/{lang:culture}/{resourceName:required}";
builder.Services.ConfigureRouteOptions(
options = options.ConstraintMap
.Add( "culture", typeof(CultureConstraint)));
varapp = builder.Build;
app.MapGet(template, GetResource);
app.Run;
staticIResult GetResource(
stringlang, stringresourceName )
{
CultureInfo.CurrentUICulture = newCultureInfo(lang);
vartext = Resources.ResourceManager.GetString(resourceName);
returnstring.IsNullOrEmpty(text)
? Results.NotFound
: Results.Content(text);
}
該終結(jié)點(diǎn)的處理方法GetResource定義了兩個(gè)參數(shù),我們知道它們會(huì)自動(dòng)綁定為同名的路由參數(shù)。由于系統(tǒng)自動(dòng)根據(jù)當(dāng)前線程的UICulture來(lái)選擇對(duì)應(yīng)的資源文件,我們對(duì)CultureInfo類型的CurrentUICulture靜態(tài)屬性進(jìn)行了設(shè)置。如果從資源文件將對(duì)應(yīng)的文本提取出來(lái),我們將創(chuàng)建一個(gè)ContentResult對(duì)象并返回。應(yīng)用啟動(dòng)之后,我們可以利用瀏覽器指定匹配的URL獲取對(duì)應(yīng)語(yǔ)言的文本。如圖5所示,如果指定一個(gè)不合法的語(yǔ)言(如“xx”),將會(huì)違反我們自定義的約束,此時(shí)就會(huì)得到一個(gè)狀態(tài)碼為“404 Not Found”的響應(yīng)。
圖5 采用相應(yīng)的URL得到某個(gè)資源針對(duì)某種語(yǔ)言的內(nèi)容
我們來(lái)看看針對(duì)語(yǔ)言文化的路由約束CultureConstraint究竟做了什么。如下面的代碼片段所示,我們?cè)贛atch方法中會(huì)試圖獲取作為語(yǔ)言文化內(nèi)容的路由參數(shù)值,如果存在這樣的路由參數(shù),就可以利用它創(chuàng)建一個(gè)CultureInfo對(duì)象。如果這個(gè)CultureInfo對(duì)象的EnglishName屬性名不以“Unknown Language”字符串作為前綴,我們就認(rèn)為指定的是合法的語(yǔ)言文件。
publicclassCultureConstraint: IRouteConstraint
{
publicboolMatch(
HttpContext? httpContext,
IRouter? route,
stringrouteKey,
RouteValueDictionary values,
RouteDirection routeDirection )
{
try
{
if(values.TryGetValue(routeKey, outvarvalue)
valueisnot null)
{
return! newCultureInfo(( string) value)
.EnglishName.StartsWith( "Unknown Language");
}
returnfalse;
}
catch
{
returnfalse;
}
}
}
掃描二維碼推送至手機(jī)訪問(wèn)。
版權(quán)聲明:本文由飛速云SEO網(wǎng)絡(luò)優(yōu)化推廣發(fā)布,如需轉(zhuǎn)載請(qǐng)注明出處。