问题
有群友希望将路由中的信息绑定到一个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】,邀你加入技术交流群