I think it is similar to issues previously experienced in our projects.
You have to change post code for jQuery to the following one:
$.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 form-url-encoded data.
application/x-www-form-urlencoded
ID 29
MyWidget
ID=29&MyWidget=
So it was deserialized absolutely correctly. MyWidget is empty string, so it will have empty value of Widget class.
In addition I recommend you to add 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);
}
So you will use only JSON-formatter for API calls.
UPDATE
Main difference that form-url-encoded data passed to MVC controller will be processed by runtime and finally handled by DefaultModelBinder (if custom binder is not available). So data encoded as form-url is usual for MVC because normally data is generated by HTML form post. But Web API is not rely on any specific encoding by design. So it uses specific mechanism (formatters) to parse data... for example json like it is above. So FormUrlEncodedMediaTypeFormatter from System.Net.Http.Formatting and DefaultModelBinder from System.Web.Mvc handle empty string differently.
For DefaultModelBinder empty string will be converted to null.
Analyzing code I can decide that BindModel method firstly creates empty model:
if (model == null)
{
model = CreateModel(controllerContext, bindingContext, modelType);
}
After it will fill properties:
// 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);
And finally GetBinder will return fallbackBinder for Widget type (type of property). And fallbackBinder itself will call ConvertSimpleType where string is processed as the following:
string valueAsString = value as string;
if (valueAsString != null && String.IsNullOrWhiteSpace(valueAsString))
{
return null;
}
I guess there are no any standards describing conversion from url-encoded strings to C# objects. So I do not know which one is correct. In any case I am sure you need to pass json through AJAX calls not form-url-encoded data.
Modelconstructs aWidgetin its default constructor and assigns it toMyWidget, is there?