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

Nat*_*ley 12 c# model-binding 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<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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我正在使用的基类:

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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

        return null;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

[DataContract)]
public class TestModel
{
    [DataMember]
    public TimeZoneInfo TimeZone { get; set; }
}

[HttpPost]
public HttpResponseMessage Test(TestModel model)
{
    return Request.CreateResponse(HttpStatusCode.OK, model);
}
Run Code Online (Sandbox Code Playgroud)

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

public HttpResponseMessage Test([FromBody] TestModel model)
Run Code Online (Sandbox Code Playgroud)

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

public HttpResponseMessage Test([ModelBinder] TestModel model)
Run Code Online (Sandbox Code Playgroud)

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

价值提供者

我也尝试过装饰课程本身 ModelBinder(typeof(SystemizerModelBinderProvider))

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

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

Dar*_*rov 29

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

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

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

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

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


Tus*_*are 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

    public async Task<PollResponse> Post(Guid instanceId, [ModelBinder(typeof(PollRequestModelBinder))]PollRequest request)
    {
       // api implementation  
    }
Run Code Online (Sandbox Code Playgroud)