首页 > 解决方案 > 是否有开箱即用的方式将 HTTP 请求的整个主体绑定到 ASP.NET Core 控制器操作中的字符串参数?

问题描述

概括

给定一个带有字符串主体“汉堡包”的 HTTP 请求,
我希望能够将请求的整个主体绑定到控制器操作的方法签名中的字符串参数。

通过向相对 URL 发出 HTTP 请求来调用此控制器时,string-body-model-binding-example/get-body我收到一个错误,并且该操作永远不会被调用

控制器

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace MyProject
{
    [Route("string-body-model-binding-example")]
    [ApiController]
    public class ExampleController: ControllerBase
    {
        [HttpPut("get-body")]
        public string GetRequestBody(string body)
        {
            return body;
        }
    }
}

演示问题的集成测试

using FluentAssertions;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;

public class MyIntegrationTests : MyIntegrationTestBase
{
    [Fact]
    public async Task String_body_is_bound_to_the_actions_body_parameter()
    {
        var body = "hamburger";
        var uri = "string-body-model-binding-example/get-body";
        var request = new HttpRequestMessage(HttpMethod.Put, uri)
        {
            Content = new StringContent(body, Encoding.UTF8, "text/plain")
        };

        var result = await HttpClient.SendAsync(request); 
        var responseBody = await result.Content.ReadAsStringAsync();
        responseBody.Should().Be(body,
            "The body should have been bound to the controller action's body parameter");
    }
}

注意:在上面的示例中,测试 HttpClient 是使用Microsoft.AspNetCore.Mvc.Testing https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1设置的。我在动作方法签名中使用POCO模型的其他控制器动作是可以访问的,所以我知道我尝试进行模型绑定的方式有问题。

编辑:我尝试过的事情:

令我惊讶的是,字符串不是受支持的原语之一

标签: c#asp.net-coremodel-binding

解决方案


不确定这是否可以通过框架手段实现,但您可以为此创建自定义模型绑定器

public class RawBodyModelBinder : IModelBinder
{
    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        using (var streamReader = new StreamReader(bindingContext.HttpContext.Request.Body))
        {
            string result = await streamReader.ReadToEndAsync();
            bindingContext.Result = ModelBindingResult.Success(result);
        }
    }
}

并像这样使用它

[HttpPut("get-body")]
public string GetRequestBody([ModelBinder(typeof(RawBodyModelBinder))] string body)
{
    return body;
}

或者你可以告诉框架以更优雅的方式使用你的模型绑定器,使用IModelBinderProvider. 首先引入 newBindingSource作为单例

public static class CustomBindingSources
{
    public static BindingSource RawBody { get; } = new BindingSource("RawBod", "Raw Body", true, true);
}

并创建我们的[FromRawBody]属性

[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FromRawBodyAttribute : Attribute, IBindingSourceMetadata
{
    public BindingSource BindingSource => CustomBindingSources.RawBody;
}

该框架IBindingSourceMetadata以特殊的方式处理属性并BindingSource为我们获取其值,因此可以在模型绑定器提供程序中使用。

然后创建IModelBinderProvider

public class RawBodyModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        //use binder if parameter is string
        //and FromRawBody specified
        if (context.Metadata.ModelType == typeof(string) && 
            context.BindingInfo.BindingSource == CustomBindingSources.RawBody)
        {
            return new RawBodyModelBinder();
        }

        return null;
    }
}

将模型绑定器提供程序添加到Startup

services
    .AddMvc(options =>
    {
        options.ModelBinderProviders.Insert(0, new RawBodyModelBinderProvider());
        //..
    }

如下使用它

[HttpPut("get-body")]
public string GetRequestBody([FromRawBody] string body)
{
    return body;
}

推荐阅读