問題
有群友希望將路由中的信息綁定到一個Dto對象中:
public?class?DDDDDto
{[FromRoute(Name?="collectionId")]public?Guid?collectionId?{?get;?set;?}[BindProperty(Name?=?"relativeUrl")]public?string?relativeUrl?{?get;?set;?}
}
這樣就不用在 Action 中定義一堆參數了:
[HttpGet("collections/{collectionId}/patn/{relativeUrl}/children",?Name?=?"QueryChildrenOfItem")]
public?async?Task<DDDDDto>?QueryChildren([FromRoute(Name?=?"collectionId")]?Guid?collectionId,[FromRoute(Name?=?"relativeUrl")]?string?relativeUrl)
想法很好,對吧!
但是實際運行,卻發現達不到想要的效果,沒有綁定到數據:
這說明 ASP.NET Core 默認是無法將其他不同的源綁定到單個類的。
那能不能換種方式,解決這個問題呢?
源碼探究
首先,我們查看源碼,想看看FromRouteAttribute
是如何工作的。
僅在InferParameterBindingInfoConvention
類中找到一處調用:
var?message?=?Resources.FormatApiController_MultipleBodyParametersFound(action.DisplayName,nameof(FromQueryAttribute),nameof(FromRouteAttribute),nameof(FromBodyAttribute));message?+=?Environment.NewLine?+?parameters;
throw?new?InvalidOperationException(message);
結果,這段代碼還是用來生成異常信息的!?
不過,這段代碼前面的部分引起了我們的注意:
這明顯是在設置綁定源信息:
bindingSource?=?InferBindingSourceForParameter(parameter);parameter.BindingInfo?=?parameter.BindingInfo????new?BindingInfo();
parameter.BindingInfo.BindingSource?=?bindingSource;
InferBindingSourceForParameter
的實現代碼如下:
internal?BindingSource?InferBindingSourceForParameter(ParameterModel?parameter)
{if?(IsComplexTypeParameter(parameter)){if?(_serviceProviderIsService?.IsService(parameter.ParameterType)?is?true){return?BindingSource.Services;}return?BindingSource.Body;}if?(ParameterExistsInAnyRoute(parameter.Action,?parameter.ParameterName)){return?BindingSource.Path;}return?BindingSource.Query;
}
單個類肯定是IsComplexTypeParameter
, 這將讓方法返回BindingSource.Body
。
這也正好解釋了: 正常情況下,如果使用單個類作為 Action 參數,默認從 Body 源綁定的原因。
那么,能否改變 ASP.NET Core 這一默認的綁定行為嗎?
柳暗花明
繼續查看InferParameterBindingInfoConvention
的使用,我們發現它的調用,居然在一個條件分支內:
if?(!options.SuppressInferBindingSourcesForParameters)
{var?serviceProviderIsService?=?serviceProvider.GetService<IServiceProviderIsService>();var?convention?=?options.DisableImplicitFromServicesParameters?||?serviceProviderIsService?is?null??new?InferParameterBindingInfoConvention(modelMetadataProvider)?:new?InferParameterBindingInfoConvention(modelMetadataProvider,?serviceProviderIsService);ActionModelConventions.Add(convention);
}
那么,如果讓SuppressInferBindingSourcesForParameters
設為true
,會有什么效果呢?
builder.Services.Configure<ApiBehaviorOptions>(options?=>
{options.SuppressInferBindingSourcesForParameters?=?true;
});
下面,就是見證奇跡的時刻:
我們也嘗試了從其他源,比如 Query,傳遞數據,都可以正常綁定。
1 句代碼,我們搞定了 ASP.NET Core 將多個來源綁定到同一個類的功能。
結論
后來,我們在官方文檔(https://docs.microsoft.com/zh-cn/aspnet/core/web-api/?view=aspnetcore-6.0)找到了解釋:
當沒有在參數上顯式指定[FromXXX]
時,ASP.NET Core 會進行綁定源推理,比如會推斷單個類的綁定源為 Body。
設置 SuppressInferBindingSourcesForParameter 為 true,則會禁用綁定源推理。ASP.NET Core 運行時會嘗試按順序從所有源中拉取數據進行綁定。
添加微信號【MyIO666】,邀你加入技術交流群