In WebAPI, I've noticed an inconsistency that is messing with our validation practices. If you send a bad body/payload with a POST in xml, deserialization fails and you get an null pointer. If you send a bad body/payload in JSON, you get an empty object instead. It is misleading and I don't like it. Is there a way to force a null pointer with a failed json deserialization??
UPDATE: I'm not having a deserialization problem. I'm having a behavior problem that seems to be a difference between the DataContractSerializer and the Json.net serializer. When xml fails to deserialize, the payload is null. However, when Json fails to deserialize, it seems that it is instantiating a default instance of the expected payload.
Example of a bad xml payload:
Example of the same call using a bad json payload (payload is not null. instead it is a default instance of the payload class)
By default Web.API used the MissingMemberHandling.Ignore
setting in the JsonMediaTypeFormatter
.
You need to set it to MissingMemberHandling.Error
with:
GlobalConfiguration.Configuration
.Formatters.JsonFormatter
.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
and you should get null
when sending JSON like:
{
"somenotexistingprop": ""
}
However if you send a completely empty JSON object: {}
then you will still get an object with empty properties and not null
. Because JsonConvert.DeserializeObject
returns a empty object if it deserializes an empty JSON (see this unit test of github).
After one of my team members helped me understand why this is the case, following are some notes about this scenario:
We by default do not have "MissingMemberHandling.Error" on Json formatter because this can help in scenarios where you a newer version of client sending data with extra members to an older version of service. The older version of service should still be able to accept this request and ignore the extra properties. This behavior is consistent with how Xml formatter also behaves.
@Sinaesthetic: if you are setting "MissingMemberHandling.Error" and do not want to rely on "ModelState.IsValid" check only, then you could alway check for ModelState's Keys property to figure out if your request is indeed invalid because of a missing member or something else.
Example below:
public class Customer
{
public int Id { get; set; }
[MaxLength(5)]
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string Line1 { get;set;}
}
//action
public void Post([FromBody]Customer customer)
{
if (!ModelState.IsValid)
{
ModelStateDictionary msd = new ModelStateDictionary();
foreach (string key in ModelState.Keys)
{
if (ModelState[key].Errors.Count > 0)
{
foreach (ModelError error in ModelState[key].Errors)
{
Exception ex = error.Exception;
if (ex != null
&& typeof(JsonSerializationException).IsAssignableFrom(ex.GetType())
&& ex.Message.StartsWith("Could not find member"))
{
msd.AddModelError(key, ex);
}
}
}
}
if (msd.Count > 0)
{
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, msd));
}
}
//process the supplied customer
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With