1 句代码,搞定 ASP.NET Core 绑定多个源到同一个类

问题

有群友希望将路由中的信息绑定到一个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)

想法很好,对吧!

但是实际运行,却发现达不到想要的效果,没有绑定到数据:

1a71a041cbed332279945bd6a8219f67.jpeg

这说明 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);

结果,这段代码还是用来生成异常信息的!?

不过,这段代码前面的部分引起了我们的注意:

9fd3a40e8ca3e2e0b33cc5805fcfb778.jpeg

这明显是在设置绑定源信息:

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;
});

下面,就是见证奇迹的时刻:

bea6d77b6b97bc8f2fd8849d4fef2f79.png

我们也尝试了从其他源,比如 Query,传递数据,都可以正常绑定。

1 句代码,我们搞定了 ASP.NET Core 将多个来源绑定到同一个类的功能。

结论

后来,我们在官方文档(https://docs.microsoft.com/zh-cn/aspnet/core/web-api/?view=aspnetcore-6.0)找到了解释:

9ee1ab5e2ca5408f53ecd82db8955eb0.png

当没有在参数上显式指定[FromXXX]时,ASP.NET Core 会进行绑定源推理,比如会推断单个类的绑定源为 Body。

设置 SuppressInferBindingSourcesForParameter 为 true,则会禁用绑定源推理。ASP.NET Core 运行时会尝试按顺序从所有源中拉取数据进行绑定。

添加微信号【MyIO666】,邀你加入技术交流群

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值