使用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)
这会调用我的自定义模型绑定器,如预期的那样,但它只为RouteData
和QueryString
提供ValueProviders,并且由于某种原因不提供正文内容的任何内容。 见下文:
我也尝试用ModelBinder(typeof(SystemizerModelBinderProvider))
装饰类本身
为什么只在我使用[ModelBinder]属性时才会出现模型绑定,为什么它只会尝试读取路由和查询字符串值并忽略正文内容? 为什么FromBody
会忽略我的自定义模型绑定提供程序?
如何创建一个场景,我可以使用自定义逻辑接收POSTED x-www-form-urlencoded
数据并成功绑定模型属性?
我建议您阅读following blog post
,其中Mike Stall详细解释了模型绑定在Web API中的工作原理:
绑定参数有两种技术:模型绑定和格式化。 实际上,WebAPI使用模型绑定从查询字符串中读取,使用Formatters从正文中读取。
以下是确定是使用模型绑定还是格式化程序读取参数的基本规则:
- 如果参数没有属性,那么决定完全取决于参数的.NET类型。 “简单类型”使用模型绑定。 复杂类型使用格式化程序。 “简单类型”包括:基元,TimeSpan,DateTime,Guid,Decimal,String或具有从字符串转换的TypeConverter的东西。
- 您可以使用
[FromBody]
属性指定应从正文中读取参数。- 您可以在参数或参数的类型上使用
[ModelBinder]
属性来指定参数应该是模型绑定的。 此属性还允许您配置模型绑定器。[FromUri]
是[FromUri]
的派生实例,它专门配置模型绑定器以仅查看URI。- 身体只能读一次。 因此,如果签名中有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 }