即使在JSON中它们为null,WebAPI也会实例化对象

Cas*_*ams 5 ajax asp.net-mvc jquery json asp.net-web-api

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

这与普通MVC控制器如何绑定数据形成对比...... 如果对象在JSON中为null,则不会实例化对象.

MVC控制器

public class HomeController : Controller
{
    [HttpPost]
    public ActionResult Test(Model model)
    {
        return Json(model);
    }
}
Run Code Online (Sandbox Code Playgroud)

WebAPI控制器

public class APIController : ApiController
{
    [HttpPost]
    public Model Test(Model model)
    {
        return model;
    }
}
Run Code Online (Sandbox Code Playgroud)

将获得POST的模型类

public class Model
{
    public int ID { get; set; }
    public Widget MyWidget { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

Model类中使用的类

public class Widget
{
    public int ID { get; set; }
    public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

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

如您所见,WebAPI方法使用对象实例化MyWidget属性,而MVC操作将其保留为null.

对我来说,WebAPI以这种方式运行似乎并不直观.它为什么要这样做?在这方面,我可以使其表现得像MVC一样吗?

Max*_*xim 3

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

您必须将 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() {
        });
Run Code Online (Sandbox Code Playgroud)

默认情况下,jQuery 'posts' 将参数作为表单 url 编码数据发送。

application/x-www-form-urlencoded
ID 29
MyWidget

ID=29&MyWidget=

所以它的反序列化绝对正确。MyWidget 是空字符串,因此 Widget 类的值为空。

此外,我建议您为 WebApi 控制器添加格式化程序配置:

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

因此,您将仅使用 JSON 格式程序进行 API 调用。

更新

主要区别是传递给 MVC 控制器的表单 url 编码数据将由运行时处理,并最终由 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);
 }
Run Code Online (Sandbox Code Playgroud)

之后它将填充属性:

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

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

        string valueAsString = value as string;
        if (valueAsString != null && String.IsNullOrWhiteSpace(valueAsString))
        {
            return null;
        }
Run Code Online (Sandbox Code Playgroud)

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