Micah Zoltu Micah Zoltu - 1 year ago 114
C# Question

How do I get WebAPI to validate my JSON with JsonProperty(Required = Required.Always)?

public class MyModel
{
[JsonProperty(PropertyName = "foo", Required = Required.Always)]
public String Bar;
}

public class MyController : ApiController
{
public String PostVersion1([FromBody] MyModel myModel)
{
if (ModelState.IsValid)
{
if (myModel.Bar == null)
return "What?!";
else
return "Input is valid.";
}
else
{
return "Input is invalid.";
}
}
}


Results:

Input |Output
-------------------|------
{ "bad" : "test" } | What?!
{ "Bar" : "test" } | What?!
{ "foo" : "test" } | Input is valid.


JsonPropertyAttribute is clearly supported because I am able to set the PropertyName and have it take effect. However, I would expect the
ModelState.IsValid
to be false for the first two example inputs because the
Required
JsonProprty parameter was set to Always.

If I just run it through JsonConvert:

JsonConvert.DeserializeObject<MyModel>(@"{'bad':'test'}");


an exception is thrown during deserialization as expected:

Result Message: Newtonsoft.Json.JsonSerializationException : Required property 'foo' not found in JSON. Path '', line 1, position 14.

Answer Source

In order to solve this I ended up creating my own custom JSON.NET MediaTypeFormatter. My formatter allows the JSON.NET deserialization exceptions bubble out which results in the exception information being returned to the caller.

Here is the MediaTypeFormatter I built:

public class JsonMediaFormatter : MediaTypeFormatter
{
    private readonly JsonSerializer _jsonSerializer = new JsonSerializer();

    public JsonMediaFormatter()
    {
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
    }

    public override Boolean CanReadType(Type type)
    {
        if (type == null)
            return false;

        return true;
    }

    public override Boolean CanWriteType(Type type)
    {
        if (type == null)
            return false;

        return true;
    }

    public override Task<Object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
    {
        return Task.FromResult(Deserialize(readStream, type));
    }

    public override Task WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)
    {
        Serialize(writeStream, value);
        return Task.FromResult(0);
    }

    private Object Deserialize(Stream readStream, Type type)
    {
        var streamReader = new StreamReader(readStream);
        return _jsonSerializer.Deserialize(streamReader, type);
    }

    private void Serialize(Stream writeStream, Object value)
    {
        var streamWriter = new StreamWriter(writeStream);
        _jsonSerializer.Serialize(streamWriter, value);
        streamWriter.Flush();
    }
}

In order to use this formatter over the built-in one, I added this line to my WebApiConfig:

config.Formatters.Insert(0, new Formatters.JsonMediaFormatter());

By inserting it at index 0, it takes precedence over the built-in formatter. If you care, you could remove the built-in JSON formatter.

In this scenario, the ModelState is always valid in the action because an exception is thrown back to the user before the action is ever fired if deserialization fails. More work would need to be done in order to still execute the action with a null FromBody parameter.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download