WebAPI creating objects even if they are null in JSON

When publishing JSON models in WebAPI controller methods, I noticed that if there are null objects in the JSON model, the model’s middleware will create these elements instead of storing them in an object on the server side.

This contrasts with how a regular MVC controller will bind data ... it will not instantiate an object if it is not valid in JSON.

MVC controller

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

Webapi controller

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

The class of the model that will receive POSTed.

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

The class used in the Model class.

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

Here are my results when I send a JSON model to each controller:

 $.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}} 

As you can see, the WebAPI method created the MyWidget property with the object, while the MVC action left it null.

It doesn't seem intuitive to me that the WebAPI will function that way. Why do this? Can I make him act like an MVC in this regard?

+5
source share
2 answers

I think this is similar to the problems that were previously experienced in our projects.

You need to change the zip code for jQuery to the following:

 $.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() { }); 

By default, jQuery 'posts' sends parameters as data encoded as url.

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

ID = 29 & MyWidget =

Thus, he was deserialized absolutely correctly. MyWidget is an empty string, so it will have an empty value for the Widget class.

In addition, I recommend that you add the Formatters configuration for WebApi controllers:

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

Thus, for API calls you will only use JSON formatting.

UPDATE

The main difference is that the data encoded in the url form passed to the MVC will be processed at runtime and finally processed by DefaultModelBinder (if a custom binder is not available). Thus, data encoded as form-url is usually used for MVC, because usually the data is generated by HTML form mail. But the Web API does not rely on any particular design encoding. Therefore, it uses a specific mechanism (formatters) to analyze the data ... for example, json, as above. Thus, FormUrlEncodedMediaTypeFormatter from System.Net.Http.Formatting and DefaultModelBinder from System.Web.Mvc handles the empty string differently.

For DefaultModelBinder, an empty string will be converted to zero. By analyzing the code, I can decide that the BindModel method first creates an empty model:

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

After filling in the properties:

 // call into the property 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); 

Finally, GetBinder will return a fallbackBinder for the widget type (property type). And fallbackBinder itself will call ConvertSimpleType, where the string is processed as follows:

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

I think there are no standards describing the conversion of URL encoded strings to C # objects. Therefore, I do not know which one is correct. In any case, I'm sure you need to pass json through AJAX calls, not url encoded data.

+2
source

Use the newtonsoft serializer. Newtonsoft.Json saves them null

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

Source: https://habr.com/ru/post/1239933/


All Articles