ASP.Net的Web API定制模型的X WWW的形式urlen codeD结合发布的数据 - 似乎没有任何工作 [英] ASP.Net Web API custom model binding with x-www-form-urlencoded posted data - nothing seems to work

查看:125
本文介绍了ASP.Net的Web API定制模型的X WWW的形式urlen codeD结合发布的数据 - 似乎没有任何工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个很大的麻烦自定义模型绑定工作发布 X-WWW的形式urlen codeD 数据时。我已经想尽办法,我能想到的似乎没有产生预期的结果。注意张贴JSON数据,我JsonConverters等等所有的工作就好了的时候。这是当我出任 X-WWW的形式urlen codeD 系统似乎无法弄清楚如何我的模型绑定。

我的测试情况是,我想一个的TimeZoneInfo对象绑定作为我的模型的一部分​​。

下面是我的模型绑定:

 公共类TimeZoneModelBinder:SystemizerModelBinder
{
    保护覆盖对象BindModel(字符串attemptedValue,动作<串GT; addModelError)
    {
        尝试
        {
            返回TimeZoneInfo.FindSystemTimeZoneById(attemptedValue);
        }
        赶上(TimeZoneNotFoundException)
        {
            addModelError(的值是无效的时区ID见GetSupportedTimeZones API调用有效的时区ID列表。);
            返回null;
        }
    }
}

下面是我使用的基类:

 公共抽象类SystemizerModelBinder:IModelBinder
{
    公共BOOL BindModel(HttpActionContext ActionContext中,ModelBindingContext的BindingContext)
    {
        变种名称= GetModelName(bindingContext.ModelName);
        VAR valueProviderResult = bindingContext.ValueProvider.GetValue(名);
        如果(valueProviderResult == NULL || string.IsNullOrWhiteSpace(valueProviderResult.AttemptedValue))
            返回false;        VAR成功=真;
        VAR值= BindModel(valueProviderResult.AttemptedValue,S = GT;
        {
            成功= FALSE;
            bindingContext.ModelState.AddModelError(姓名,多个);
        });
        bindingContext.Model =价值;
        bindingContext.ModelState.SetModelValue(名称,新System.Web.Http.ValueProviders.ValueProviderResult(值,valueProviderResult.AttemptedValue,valueProviderResult.Culture));
        返回成功;
    }    私人字符串GetModelName(字符串名称)
    {
        变种N = name.LastIndexOf(,StringComparison.Ordinal。);
        返回N'LT; 0 || N'GT = name.Length - 1?名称:name.Substring第(n + 1);
    }    保护抽象对象BindModel(字符串attemptedValue,动作<串GT; addModelError);
}

我用了一个基类,这样使它简单创造更多的自定义模型粘合剂。

下面是我的模型粘合剂供应商。请注意,这是越来越正确地从我的IoC容器调用的,所以我不会刻意去表现我的code的这一方面。

 公共类SystemizerModelBinderProvider:ModelBinderProvider
{
    公众覆盖IModelBinder GetBinder(HttpConfiguration配置型modelType)
    {
        如果(modelType == typeof运算(的TimeZoneInfo))
            返回新TimeZoneModelBinder();        返回null;
    }
}

最后,这里的操作方法和模型类:

  [DataContract)
公共类TestModel
{
    [数据成员]
    公众的TimeZoneInfo时区{搞定;组; }
}[HttpPost]
公众的Htt presponseMessage测试(TestModel模型)
{
    返回Request.CreateResponse(的HTTPStatus code.OK,模型);
}

有关操作方法,我曾尝试:

 公开的Htt presponseMessage测试([FromBody] TestModel模型)

这将调用 FormUrlEn codedMediaFormatter ,这似乎完全忽略了我的自定义模型粘合剂。

 公开的Htt presponseMessage测试([ModelBinder的] TestModel模型)

这调用到我的自定义模型粘结剂,符合市场预期,但它仅提供ValueProviders为的RouteData 查询字符串由于某种原因不能提供主体内容什么。见下图:

我也试过装饰类本身与 ModelBinder的(typeof运算(SystemizerModelBinderProvider))

为什么模型绑定只有当​​我使用[ModelBinder的]属性发生,并且它为什么只尝试读取路线和查询字符串值,而忽略主体内容?为什么 FromBody 无视我的自定义模型粘合剂供应商?

如何创建在那里我可以接收发布了情景 X WWW的形式,urlen使用自定义逻辑codeD 数据,并成功绑定模特属性?


解决方案

我建议你读<一个href=\"http://blogs.msdn.com/b/jmstall/archive/2012/04/16/how-webapi-does-parameter-binding.aspx\"><$c$c>following博客文章 其中麦克失速解释详细介绍了如何在Web API中的模型绑定的作品:


  

有2技术结合参数:型号和绑定
  格式化程序。在实践中,使用的WebAPI模型绑定从读
  查询字符串和格式化程序,以从主体读


  
  

下面是基本的规则来确定参数是否被阅读
  模型绑定或格式化:


  
  

      
  1. 如果在参数上有没有属性,然后作出决定
      纯粹参数的.NET类型。 简单类型使用模式
      捆绑。复杂类型使用格式化。 A简单类型包括:
      元,时间跨度,日期时间,GUID十进制,String,或者什么
      与从字符串转换的TypeConverter。

  2.   
  3. 您可以使用 [FromBody] 属性来指定一个参数应该从读
      身体。

  4.   
  5. 您可以使用 [ModelBinder的] 属性的参数或
      参数的类型指定一个参数应该是模型界。
      这个属性还允许您配置模型绑定。 [FromUri]
      一个派生的实例 [ModelBinder的] 专门配置一个
      模型绑定只查找在URI。

  6.   
  7. 正文只能被读取一次。
      因此,如果你在签名2复杂类型,它们中的至少一种
      必须有它[ModelBinder的]属性。

  8.   

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

I am having a lot of trouble getting custom model binding to work when posting x-www-form-urlencoded data. I've tried every way I can think of and nothing seems to produce the desired result. Note when posting JSON data, my JsonConverters and so forth all work just fine. It's when I post as x-www-form-urlencoded that the system can't seem to figure out how to bind my model.

My test case is that I'd like to bind a TimeZoneInfo object as part of my model.

Here's my model binder:

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;
        }
    }
}

Here's the base class I'm using:

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);
}

I used a base class like this to make it simple to create additional custom model binders.

Here's my model binder provider. Note that this is getting invoked correctly from my IoC container, so I won't bother to show that aspect of my code.

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

        return null;
    }
}

Finally, here's the action method and model class:

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

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

For the action method, I have tried:

public HttpResponseMessage Test([FromBody] TestModel model)

This invokes the FormUrlEncodedMediaFormatter, which seems to ignore my custom model binder altogether.

public HttpResponseMessage Test([ModelBinder] TestModel model)

This calls into my custom model binder, as expected, but then it only provides ValueProviders for RouteData and QueryString and for some reason doesn't provide anything for body content. See below:

I've also tried decorating the class itself with ModelBinder(typeof(SystemizerModelBinderProvider))

Why does model binding ONLY occur when I use the [ModelBinder] attribute, and why does it ONLY try to read route and querystring values and ignore body content? Why does FromBody ignore my custom model binder provider?

How do I create a scenario where I can receive POSTED x-www-form-urlencoded data and successfully bind model properties using custom logic?

解决方案

I would recommend you reading the following blog post in which Mike Stall explains in details how model binding works in the Web API:

There are 2 techniques for binding parameters: Model Binding and Formatters. In practice, WebAPI uses model binding to read from the query string and Formatters to read from the body.

Here are the basic rules to determine whether a parameter is read with model binding or a formatter:

  1. If the parameter has no attribute on it, then the decision is made purely on the parameter’s .NET type. "Simple types" uses model binding. Complex types uses the formatters. A "simple type" includes: primitives, TimeSpan, DateTime, Guid, Decimal, String, or something with a TypeConverter that converts from strings.
  2. You can use a [FromBody] attribute to specify that a parameter should be read from the body.
  3. You can use a [ModelBinder] attribute on the parameter or the parameter’s type to specify that a parameter should be model bound. This attribute also lets you configure the model binder. [FromUri] is a derived instance of [ModelBinder] that specifically configures a model binder to only look in the URI.
  4. The body can only be read once. So if you have 2 complex types in the signature, at least one of them must have a [ModelBinder] attribute on it.

So if the source of your data is the request body then you can create a custom MediaTypeFormatter rather than a model binder.

这篇关于ASP.Net的Web API定制模型的X WWW的形式urlen codeD结合发布的数据 - 似乎没有任何工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆