是否有开箱即用的方法将 HTTP 请求的整个主体绑定到 ASP.NET 核心控制器操作中的字符串参数?
Is there an out of the box way to bind the entire body of an HTTP request to a string parameter in an ASP.NET Core controller action?
总结
给定一个带有字符串正文的 HTTP 请求 "hamburger"
我希望能够将请求的整个主体绑定到控制器操作的方法签名中的字符串参数。
当通过向亲戚发出 HTTP 请求来调用此控制器时 URL 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. My other controller actions with a POCO 模型在操作方法签名中设置的,因此我知道我尝试进行模型绑定的方式有问题。
编辑:我尝试过的事情:
- 将 [FromBody] 添加到参数中 => 415 不支持的媒体类型
- 正在从控制器中删除 [ApiController] => 动作已命中但正文为空
- 将 [FromBody] 添加到参数并从控制器中删除 [ApiController] => 415 不支持的媒体类型
- 将 [Consumes("text/plain")] 添加到操作 w/wout [ApiController] 和 w/wout [FromBody]
- 使用上述任何组合发送内容类型为 application/json 的请求 => 错误或空值,具体取决于选项
令我惊讶的是,字符串不是 supported primitives
之一
不确定这是否可以通过框架方式实现,但您可以为此创建一个自定义模型绑定器
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;
}
总结
给定一个带有字符串正文的 HTTP 请求 "hamburger"
我希望能够将请求的整个主体绑定到控制器操作的方法签名中的字符串参数。
当通过向亲戚发出 HTTP 请求来调用此控制器时 URL 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. My other controller actions with a POCO 模型在操作方法签名中设置的,因此我知道我尝试进行模型绑定的方式有问题。
编辑:我尝试过的事情:
- 将 [FromBody] 添加到参数中 => 415 不支持的媒体类型
- 正在从控制器中删除 [ApiController] => 动作已命中但正文为空
- 将 [FromBody] 添加到参数并从控制器中删除 [ApiController] => 415 不支持的媒体类型
- 将 [Consumes("text/plain")] 添加到操作 w/wout [ApiController] 和 w/wout [FromBody]
- 使用上述任何组合发送内容类型为 application/json 的请求 => 错误或空值,具体取决于选项
令我惊讶的是,字符串不是 supported primitives
之一不确定这是否可以通过框架方式实现,但您可以为此创建一个自定义模型绑定器
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;
}