【问题标题】:WebAPI instantiating objects even if they are null in JSONWebAPI 实例化对象,即使它们在 JSON 中为空
【发布时间】:2016-04-09 20:43:29
【问题描述】:

在将 JSON 模型发布到 WebAPI 控制器方法时,我注意到如果 JSON 模型中有空对象,模型绑定器将实例化这些项目,而不是在服务器端对象中将它们保持为空。

这与普通 MVC 控制器绑定数据的方式形成对比......如果对象在 JSON 中为空,它不会实例化一个对象。

MVC 控制器

public class HomeController : Controller
{
    [HttpPost]
    public ActionResult Test(Model model)
    {
        return Json(model);
    }
}

WebAPI 控制器

public class APIController : ApiController
{
    [HttpPost]
    public Model Test(Model model)
    {
        return model;
    }
}

将被发布的模型类

public class Model
{
    public int ID { get; set; }
    public Widget MyWidget { get; set; }
}

Model类中使用的类

public class Widget
{
    public int ID { get; set; }
    public string Name { get; set; }
}

以下是我向每个控制器发布 JSON 模型时的结果:

$.post('/Home/Test', { ID: 29, MyWidget: null })
//Results in: {"ID":29,"MyWidget":null}

$.post('/api/api/Test', { ID: 29, MyWidget: null })
//Results in: {"ID":29,"MyWidget":{"ID":0,"Name":null}}

如您所见,WebAPI 方法用一个对象实例化了 MyWidget 属性,而 MVC 操作将其保留为空。

在我看来,WebAPI 会以这种方式运行并不直观。为什么会这样做?我可以让它在这方面表现得像 MVC 动作吗?

【问题讨论】:

  • 您的Model 不可能在其默认构造函数中构造Widget 并将其分配给MyWidget,是吗?
  • 您使用的是什么版本的 Web API?
  • @dbc,没有这个类和你看到的完全一样。没有指定构造函数。此外,如果这是一个基于构造函数的问题,那么两个控制器都会发生这种情况。
  • @Nikolai,WebAPI 版本 2

标签: jquery json ajax asp.net-mvc asp.net-web-api


【解决方案1】:

使用 newtonsoft 序列化程序。 Newtonsoft.Json 保持它们为空

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;


public static class WebApiConfig
        {
            public static void Register(HttpConfiguration config)
            {
              var jsonformatter = new JsonMediaTypeFormatter();
              config.Formatters.Clear();
              config.Formatters.Add(jsonformatter);
         }
    }

【讨论】:

  • Web API 默认不使用 Newtonsoft.Json 序列化器吗?
  • @NikolaiSamteladze 你是对的。我现在已经检查过了。但是 Json.Net 不会初始化空对象。
  • 对象在绑定期间被实例化。如果我在控制器操作中设置断点,我可以看到已经创建的新对象(或者在 MVC 控制器的情况下为 null)。
【解决方案2】:

我认为这与我们之前在项目中遇到的问题类似。

您必须将 jQuery 的邮政编码更改为以下代码:

$.ajax({
            type: 'POST',
            url: '/api/api/Test',
            data: JSON.stringify({ ID: 29, MyWidget: null }),
            contentType: "application/json",
            dataType: 'json',
            timeout: 30000
        })
        .done(function (data) {
        })
        .fail(function() {
        });

默认情况下,jQuery 'posts' 将参数作为 form-url-encoded 数据发送。

应用程序/x-www-form-urlencoded
编号 29
我的小部件

ID=29&MyWidget=

所以它被完全正确地反序列化了。 MyWidget 是空字符串,所以 Widget 类的值为空。

另外我建议你为 WebApi 控制器添加 Formatters 配置:

public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        // Formatters
        JsonMediaTypeFormatter json = config.Formatters.JsonFormatter;

        config.Formatters.Clear();
        config.Formatters.Add(json);
    }

因此,您将只使用 JSON 格式器进行 ​​API 调用。

更新

传递给 MVC 控制器的 form-url-encoded 数据将由运行时处理并最终由 DefaultModelBinder 处理的主要区别(如果自定义绑定器不可用)。因此,编码为 form-url 的数据对于 MVC 来说很常见,因为通常数据是由 HTML 表单发布生成的。但是 Web API 在设计上并不依赖于任何特定的编码。所以它使用特定的机制(格式化程序)来解析数据......例如上面的json。因此 System.Net.Http.Formatting 中的 FormUrlEncodedMediaTypeFormatter 和 System.Web.Mvc 中的 DefaultModelBinder 处理空字符串的方式不同。

对于 DefaultModelBinder,空字符串将被转换为 null。 分析代码我可以确定 BindModel 方法首先创建空模型:

 if (model == null)
 {
     model = CreateModel(controllerContext, bindingContext, modelType);
 }

之后会填充属性:

// call into the property's model binder
        IModelBinder propertyBinder = Binders.GetBinder(propertyDescriptor.PropertyType);
        object originalPropertyValue = propertyDescriptor.GetValue(bindingContext.Model);
        ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
        propertyMetadata.Model = originalPropertyValue;
        ModelBindingContext innerBindingContext = new ModelBindingContext()
        {
            ModelMetadata = propertyMetadata,
            ModelName = fullPropertyKey,
            ModelState = bindingContext.ModelState,
            ValueProvider = bindingContext.ValueProvider
        };
        object newPropertyValue = GetPropertyValue(controllerContext, innerBindingContext, propertyDescriptor, propertyBinder);

最后,GetBinder 将为 Widget 类型(属性类型)返回 fallbackBinder。 fallbackBinder 本身将调用 ConvertSimpleType ,其中字符串处理如下:

        string valueAsString = value as string;
        if (valueAsString != null && String.IsNullOrWhiteSpace(valueAsString))
        {
            return null;
        }

我猜没有任何标准描述从 url 编码字符串到 C# 对象的转换。所以我不知道哪个是正确的。无论如何,我确信您需要通过 AJAX 调用而不是表单 URL 编码的数据传递 json。

【讨论】:

  • 那行得通(特别是使用 $.ajax),感谢您的洞察力!有趣的是,MVC Action 和 WebAPI 方法在这方面的行为不同。
  • 我将添加关于原始答案差异的解释。
  • 很好的解释,谢谢!我希望我能再次投票给你的答案。
猜你喜欢
  • 2013-10-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-17
相关资源
最近更新 更多