12

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

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

这是我的模型活页夹:

public class TimeZoneModelBinder : SystemizerModelBinder
{
    protected override object BindModel(string attemptedValue, Action<string> 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 < 0 || n >= name.Length - 1 ? name : name.Substring(n + 1);
    }

    protected abstract object BindModel(string attemptedValue, Action<string> 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)

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

价值提供者

我也尝试过用ModelBinder(typeof(SystemizerModelBinderProvider))

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

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

4

2 回答 2

29

我建议您阅读following blog postMike Stall 详细解释模型绑定在 Web API 中的工作原理的文章:

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

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

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

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

于 2013-02-16T10:10:10.660 回答
7

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<PollRequest>(body);
        bindingContext.Model = pollRequest;
        return true;
    }
}

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

    public async Task<PollResponse> Post(Guid instanceId, [ModelBinder(typeof(PollRequestModelBinder))]PollRequest request)
    {
       // api implementation  
    }
于 2015-02-26T14:32:51.483 回答