我想在我的控制器/操作中支持以下所有 3 种内容类型。
application/json
application/x-www-form-urlencoded
multipart/form-data
使用此签名,我可以支持 urlencoded 和表单数据,但是 JSON 有效负载不会绑定到
Message
[HttpPost]
public async Task<IActionResult> PostAsync(Message message)
如果我想正确地将 JSON 负载绑定到
Message
,我需要使用 FromBody
属性,如下所示:
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]Message message)
但是这样做会开始为我感兴趣的其他 2 种内容类型抛出 415 错误。
我的问题是,如何向我的客户提供单一 API 端点,并让他们能够灵活地以这 3 种内容类型中的任何一种发送数据。
您应该使用 ConsumesAttribute 添加支持的不同内容类型,然后按如下方式更新您的操作;
[HttpPost]
[Consumes("application/json", "multipart/form-data", "application/x-www-form-urlencoded")]
public async Task<IActionResult> PostAsync([FromForm, FromBody, FromQuery]Message message)
实际上,您可以有一个接受多种内容类型的端点:
[HttpPost]
[Consumes("application/json")]
public async Task<IActionResult> CommonEndpoint([FromBody] JObject payload)
{
...
}
[HttpPost]
[Consumes("application/x-www-form-urlencoded")]
public async Task<IActionResult> CommonEndpoint(/* this overload doesn't specify the payload in its signature */)
{
var formFieldsDictionary = HttpContext.Request.Form.Keys
.ToDictionary(k => k, k => HttpContext.Request.Form[k].FirstOrDefault());
var payload = JsonConvert.SerializeObject(formFieldsDictionary);
...
}
首先,您应该避免将
application/json
和 multipart/form-data
组合用于同一操作,这会使应用程序不稳定。
如果您坚持这样做,您需要按照以下步骤实施您自己的
ModelBinder
:
MyComplexTypeModelBinder
public class MyComplexTypeModelBinder : ComplexTypeModelBinder
{
public MyComplexTypeModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, ILoggerFactory loggerFactory, bool allowValidatingTopLevelNodes) : base(propertyBinders, loggerFactory, allowValidatingTopLevelNodes)
{
}
protected override Task BindProperty(ModelBindingContext bindingContext)
{
try
{
var result = base.BindProperty(bindingContext);
if (bindingContext.Result.IsModelSet == false)
{
var request = bindingContext.HttpContext.Request;
var body = request.Body;
request.EnableRewind();
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
request.Body.Read(buffer, 0, buffer.Length);
var bodyAsText = Encoding.UTF8.GetString(buffer);
var jobject = JObject.Parse(bodyAsText);
var value = jobject.GetValue(bindingContext.FieldName, StringComparison.InvariantCultureIgnoreCase);
var typeConverter = TypeDescriptor.GetConverter(bindingContext.ModelType);
var model = typeConverter.ConvertFrom(
context: null,
culture: CultureInfo.InvariantCulture,
value: value.ToString());
bindingContext.Result = ModelBindingResult.Success(model);
request.Body.Seek(0, SeekOrigin.Begin);
}
return result;
}
catch (Exception ex)
{
throw;
}
}
}
MyComplexTypeModelBinderProvider
public class MyComplexTypeModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
{
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
for (var i = 0; i < context.Metadata.Properties.Count; i++)
{
var property = context.Metadata.Properties[i];
propertyBinders.Add(property, context.CreateBinder(property));
}
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
var mvcOptions = context.Services.GetRequiredService<IOptions<MvcOptions>>().Value;
return new MyComplexTypeModelBinder(
propertyBinders,
loggerFactory,
mvcOptions.AllowValidatingTopLevelNodes);
}
return null;
}
}
在
MyComplexTypeModelBinderProvider
注册
Startup.cs
services.AddMvc(options => {
options.ModelBinderProviders.Insert(0, new MyComplexTypeModelBinderProvider());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);