使用x-www-form-urlencoded发布数据的ASP.Net Web API自定义模型绑定 – 似乎没什么用

发布x-www-form-urlencoded数据时,我在定制模型绑定时遇到了很多麻烦。 我已经尝试过各种我能想到的方式,似乎没有任何东西可以产生预期的结果。 注意发布JSON数据时,我的JsonConverters等都可以正常工作。 当我发布为x-www-form-urlencoded ,系统似乎无法弄清楚如何绑定我的模型。

我的测试用例是我想将TimeZoneInfo对象绑定为我的模型的一部分。

这是我的模型活页夹:

 public class TimeZoneModelBinder : SystemizerModelBinder { protected override object BindModel(string attemptedValue, Action addModelError) { try { return TimeZoneInfo.FindSystemTimeZoneById(attemptedValue); } catch(TimeZoneNotFoundException) { addModelError("The value was not a valid time zone ID. See the GetSupportedTimeZones Api call for a list of valid time zone IDs."); return null; } } } 

这是我正在使用的基类:

 public abstract class SystemizerModelBinder : IModelBinder { public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) { var name = GetModelName(bindingContext.ModelName); var valueProviderResult = bindingContext.ValueProvider.GetValue(name); if(valueProviderResult == null || string.IsNullOrWhiteSpace(valueProviderResult.AttemptedValue)) return false; var success = true; var value = BindModel(valueProviderResult.AttemptedValue, s => { success = false; bindingContext.ModelState.AddModelError(name, s); }); bindingContext.Model = value; bindingContext.ModelState.SetModelValue(name, new System.Web.Http.ValueProviders.ValueProviderResult(value, valueProviderResult.AttemptedValue, valueProviderResult.Culture)); return success; } private string GetModelName(string name) { var n = name.LastIndexOf(".", StringComparison.Ordinal); return n = name.Length - 1 ? name : name.Substring(n + 1); } protected abstract object BindModel(string attemptedValue, Action addModelError); } 

我使用这样的基类来简化创建其他自定义模型绑定器。

这是我的模型绑定提供程序。 请注意,这是从我的IoC容器中正确调用的,所以我不打算显示我的代码方面。

 public class SystemizerModelBinderProvider : ModelBinderProvider { public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType) { if(modelType == typeof(TimeZoneInfo)) return new TimeZoneModelBinder(); return null; } } 

最后,这是动作方法和模型类:

 [DataContract)] public class TestModel { [DataMember] public TimeZoneInfo TimeZone { get; set; } } [HttpPost] public HttpResponseMessage Test(TestModel model) { return Request.CreateResponse(HttpStatusCode.OK, model); } 

对于动作方法,我尝试过:

 public HttpResponseMessage Test([FromBody] TestModel model) 

这会调用FormUrlEncodedMediaFormatter ,它似乎完全忽略了我的自定义模型绑定器。

 public HttpResponseMessage Test([ModelBinder] TestModel model) 

这会调用我的自定义模型绑定器,如预期的那样,但它只为RouteDataQueryString提供ValueProviders,并且由于某种原因不提供正文内容的任何内容。 见下文:

价值提供者

我也尝试用ModelBinder(typeof(SystemizerModelBinderProvider))装饰类本身

为什么只在我使用[ModelBinder]属性时才会出现模型绑定,为什么它只会尝试读取路由和查询字符串值并忽略正文内容? 为什么FromBody会忽略我的自定义模型绑定提供程序?

如何创建一个场景,我可以使用自定义逻辑接收POSTED x-www-form-urlencoded数据并成功绑定模型属性?

我建议您阅读following blog post ,其中Mike Stall详细解释了模型绑定在Web API中的工作原理:

绑定参数有两种技术:模型绑定和格式化。 实际上,WebAPI使用模型绑定从查询字符串中读取,使用Formatters从正文中读取。

以下是确定是使用模型绑定还是格式化程序读取参数的基本规则:

  1. 如果参数没有属性,那么决定完全取决于参数的.NET类型。 “简单类型”使用模型绑定。 复杂类型使用格式化程序。 “简单类型”包括:基元,TimeSpan,DateTime,Guid,Decimal,String或具有从字符串转换的TypeConverter的东西。
  2. 您可以使用[FromBody]属性指定应从正文中读取参数。
  3. 您可以在参数或参数的类型上使用[ModelBinder]属性来指定参数应该是模型绑定的。 此属性还允许您配置模型绑定器。 [FromUri][FromUri]的派生实例,它专门配置模型绑定器以仅查看URI。
  4. 身体只能读一次。 因此,如果签名中有2个复杂类型,则其中至少有一个必须具有[ModelBinder]属性。

因此,如果数据源是请求主体,那么您可以创建自定义MediaTypeFormatter而不是模型绑定器。

使用ModelBinder比使用MediaTypeFormatter要好一些。 您无需在全球注册。

我找到了另一种使用模型绑定器来绑定Web API中的复杂对象类型的替代方法。 在模型绑定器中,我正在读取请求体作为字符串,然后使用JSON.NET将其反序列化为所需的对象类型。 它也可用于映射复杂对象类型的数组。

我添加了一个模型绑定器如下:

 public class PollRequestModelBinder : IModelBinder { public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) { var body = actionContext.Request.Content.ReadAsStringAsync().Result; var pollRequest = JsonConvert.DeserializeObject(body); bindingContext.Model = pollRequest; return true; } } 

然后我在Web API控制器中使用它如下:

  public async Task Post(Guid instanceId, [ModelBinder(typeof(PollRequestModelBinder))]PollRequest request) { // api implementation }