Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.
like image 514
Micah Zoltu Avatar asked Aug 02 '14 06:08

Micah Zoltu


People also ask

How to handle validation errors in Web API?

Handling Validation Errors Web API does not automatically return an error to the client when validation fails. It is up to the controller action to check the model state and respond appropriately. If model validation fails, this filter returns an HTTP response that contains the validation errors.

What does Jsonproperty required do?

required. Property that indicates whether a value (which may be explicit null) is expected for property during deserialization or not.

What is Jsonconvert DeserializeObject?

DeserializeObject(String, Type,JsonConverter[]) Deserializes the JSON to the specified . NET type using a collection of JsonConverter. DeserializeObject(String, Type, JsonSerializerSettings) Deserializes the JSON to the specified .

What is JsonSerializerSettings?

Specifies the settings on a JsonSerializer object. Newtonsoft.Json. JsonSerializerSettings. Namespace: Newtonsoft.Json.

How to validate the incoming JSON in web services?

Web services have to deal with incoming data from a POST, PUT or PATCH request that are usually in the popular JavaScript Object Notation ( JSON) format. Let’s look at a couple of approaches one can take to validate the incoming JSON. Not all of these are as effective as one would hope: Create a key/value pair object map using the JSON input.

How to validate the structure of a JSON file?

JSON schema is used to validate the structure and the data types of a piece of JSON. “ additionalProperties ” property is used to check the keys present in JSON or not. As I explained above, all the three validations are good enough.

How do I check if an API endpoint is valid using JSON?

Let's walk through it: 1 First, create a test that calls the API endpoint you want to check 2 On the test's page, navigate to the Assertions tab, select Add new assertion, and choose the JSON Validation assertion. More ...

How to check if a JSON code is valid?

JSON Validator is an online web-based tool designed to help users validate their JSON codes. By merely entering the JSON code in the given box and pressing the Check Validity button, you can check whether your JSON is valid or not. How to Use JSON Validator Online?


3 Answers

The default JsonMediaTypeFormatter does not rely on on he JsonProperty to decide whether model fields are required or not. It does rely however on the RequiredAttribute

If you want to do this then implement a new IRequiredMemberSelector and set it to MediaTypeFormatter.RequiredMemberSelector.

In your implementation of IRequiredMemberSelector you will be passed a MemberInfo. You can use that to evaluate if model members have the JsonProperty attribute and if the required flag is set, and finally return true or false. This will be propagated to the ModelState.IsValid property (it will not use the JSON.NET error message though, but the DataAnnotations/WebApi one.

If you do this, then I suggest you also keep the default behavior.

like image 61
Marcel N. Avatar answered Sep 30 '22 15:09

Marcel N.


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.

like image 31
Micah Zoltu Avatar answered Sep 30 '22 13:09

Micah Zoltu


I know this is an old question but I solved it like this:

var formatter = new JsonMediaTypeFormatter {
    SerializerSettings = {
        ContractResolver = new DefaultContractResolver(true)
    }
};
configuration.Formatters.Insert(0, formatter);

The parsing errors will then be included in ModelState

like image 36
adrianm Avatar answered Sep 30 '22 15:09

adrianm