我试图在web api 2中使用FromUri和FromBody来填充传入的请求模型。我知道我需要编写一个自定义模型绑定器来做到这一点。这是the example everyone references。此解决方案已合并到WebAPIContrib nuGet pacakge中,其源代码为can be seen here on github。
我无法使用MvcActionValueBinder来处理application / json正文内容。这是抛出异常的源代码的一部分。
class MvcActionBinding : HttpActionBinding
{
// Read the body upfront , add as a ValueProvider
public override Task ExecuteBindingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
HttpRequestMessage request = actionContext.ControllerContext.Request;
HttpContent content = request.Content;
if (content != null)
{
FormDataCollection fd = content.ReadAsAsync<FormDataCollection>().Result;
if (fd != null)
{
IValueProvider vp = new NameValuePairsValueProvider(fd, CultureInfo.InvariantCulture);
request.Properties.Add(Key, vp);
}
}
return base.ExecuteBindingAsync(actionContext, cancellationToken);
}
}
这一行抛出异常:
FormDataCollection fd = content.ReadAsAsync<FormDataCollection>().Result;
这是一个例外:
System.AggregateException
{“无法将当前JSON对象(例如{\”name \“:\”value \“})反序列化为类型'System.Net.Http.Formatting.FormDataCollection',因为该类型需要JSON数组(例如[1,2] ,3])正确反序列化。\ r \ n要解决此错误要么将JSON更改为JSON数组(例如[1,2,3]),要么更改反序列化类型以使其成为正常的.NET类型(例如,不是可以从JSON对象反序列化的整数类型,如整数,而不是类似数组或List的集合类型.JsonObjectAttribute也可以添加到类型中以强制它从JSON对象反序列化。\ r \ n“'creditLimit' ,第2行,第17位。“}
如何让模型绑定器使用applciation / json内容而不是x-www-form-urlencoded内容?这是asp.net论坛上的similar question with no answer。
更新:这是控制器方法:
[Route("{accountId:int}/creditlimit")]
[HttpPut]
public async Task<IHttpActionResult> UpdateAccountCreditLimit(int accountId, [FromBody] RequestObject request)
{
// omitted for brevity
}
这是RequestObject:
class RequestObject
{
public int AccountId { get; set; }
public decimal CreditLimit { get; set; }
}
这是要测试的邮递员端点,它是一个PUT:
http://localhost/api/accounts/47358/creditlimit
我已经设置为application / json的正文。这是示例内容。
{ "creditLimit": 125000.00 }
是的,我意识到我可以改变控制器方法来代替所有FromUri或所有FromBody。我没有这样做的自由。谢谢。
我有同样的问题,我想我终于搞清楚了。
这是代码:
internal sealed class MvcActionValueBinder : DefaultActionValueBinder
{
private static readonly Type stringType = typeof(string);
// Per-request storage, uses the Request.Properties bag. We need a unique key into the bag.
private const string Key = "5DC187FB-BFA0-462A-AB93-9E8036871EC8";
private readonly JsonSerializerSettings serializerSettings;
public MvcActionValueBinder(JsonSerializerSettings serializerSettings)
{
this.serializerSettings = serializerSettings;
}
public override HttpActionBinding GetBinding(HttpActionDescriptor actionDescriptor)
{
var actionBinding = new MvcActionBinding(serializerSettings);
HttpParameterDescriptor[] parameters = actionDescriptor.GetParameters().ToArray();
HttpParameterBinding[] binders = Array.ConvertAll(parameters, DetermineBinding);
actionBinding.ParameterBindings = binders;
return actionBinding;
}
private HttpParameterBinding DetermineBinding(HttpParameterDescriptor parameter)
{
HttpConfiguration config = parameter.Configuration;
var attr = new ModelBinderAttribute(); // use default settings
ModelBinderProvider provider = attr.GetModelBinderProvider(config);
IModelBinder binder = provider.GetBinder(config, parameter.ParameterType);
// Alternatively, we could put this ValueProviderFactory in the global config.
var valueProviderFactories = new List<ValueProviderFactory>(attr.GetValueProviderFactories(config)) { new BodyValueProviderFactory() };
return new ModelBinderParameterBinding(parameter, binder, valueProviderFactories);
}
// Derive from ActionBinding so that we have a chance to read the body once and then share that with all the parameters.
private class MvcActionBinding : HttpActionBinding
{
private readonly JsonSerializerSettings serializerSettings;
public MvcActionBinding(JsonSerializerSettings serializerSettings)
{
this.serializerSettings = serializerSettings;
}
// Read the body upfront, add as a ValueProvider
public override Task ExecuteBindingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
HttpRequestMessage request = actionContext.ControllerContext.Request;
HttpContent content = request.Content;
if (content != null)
{
string result = request.Content.ReadAsStringAsync().Result;
if (!string.IsNullOrEmpty(result))
{
var jsonContent = JObject.Parse(result);
var values = new Dictionary<string, object>();
foreach (HttpParameterDescriptor parameterDescriptor in actionContext.ActionDescriptor.GetParameters())
{
object parameterValue = GetParameterValue(jsonContent, parameterDescriptor);
values.Add(parameterDescriptor.ParameterName, parameterValue);
}
IValueProvider valueProvider = new NameValuePairsValueProvider(values, CultureInfo.InvariantCulture);
request.Properties.Add(Key, valueProvider);
}
}
return base.ExecuteBindingAsync(actionContext, cancellationToken);
}
private object GetParameterValue(JObject jsonContent, HttpParameterDescriptor parameterDescriptor)
{
string propertyValue = jsonContent.Property(parameterDescriptor.ParameterName)?.Value.ToString();
if (IsSimpleParameter(parameterDescriptor))
{
// No deserialization needed for value type, a cast is enough
return Convert.ChangeType(propertyValue, parameterDescriptor.ParameterType);
}
return JsonConvert.DeserializeObject(propertyValue, parameterDescriptor.ParameterType, serializerSettings);
}
private bool IsSimpleParameter(HttpParameterDescriptor parameterDescriptor)
{
return parameterDescriptor.ParameterType.IsValueType || parameterDescriptor.ParameterType == stringType;
}
}
// Get a value provider over the body. This can be shared by all parameters.
// This gets the values computed in MvcActionBinding.
private class BodyValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(HttpActionContext actionContext)
{
actionContext.Request.Properties.TryGetValue(Key, out object vp);
return (IValueProvider)vp; // can be null
}
}
}
要解释一下,诀窍是首先将请求内容读作string
,然后将其加载到JObject
中。对于actionContext.ActionDescriptor
中存在的每个参数,使用参数名称作为键填充字典,并使用参数类型添加对象值。
根据参数类型,我们要么进行简单的转换,要么使用Json.NET将值反序列化为所需的类型。请注意,您可能需要为值类型添加特殊情况以管理例如枚举或Guid
。
在我的例子中,我传递了一个JsonSerializerSettings
因为我有一些我想要使用的自定义转换器,你可能不需要它。
您应该能够使用Web API 2中的默认模型绑定功能来实现此目的。您需要做的第一件事是将数据作为JSON字符串传递如下。
data: JSON.stringify({ "creditLimit": 125000.00 })
将从URL读取accountId,Web API 2的默认JsonFormatter将尝试绑定来自正文的第二个参数请求。它将找到creditLimit并将创建一个RequestObject实例,并填充了creditLimit。
然后,您可以在控制器内部将accountId值分配给RequestObject其他属性。这样您就不需要将accountId作为请求正文的一部分传递。您只能将其作为URL端点的一部分传递。
以下链接是更深入细节的良好资源。 http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api