My colleague and DNC author Sumit was recently consulting for a project where devs were a little confused on how exactly Model Validation was working. Sumit and I thought it’s a good idea to write a quick refresher on Model Binding and Model Validation in ASP.NET Web API.
Let’s get started
The Sample ASP.NET Project
We’ll set up a sample ASP.NET Project using the Web API template and add a couple of entities to it.
The two entities we use are Account and Country
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WebApiValidation.Models
{
public class Account
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
[Range(1, 1000)]
public int KeyCode { get; set; }
[Required]
public int CountryId { get; set; }
[ForeignKey("CountryId")]
public string Country { get; set; }
}
}
We build the application and then scaffold the Accounts Web API Controller.
Once the controller has been scaffolded, we can add the excellent Test client by Yao Huang from the ASP.NET Team.
This client helps us test Web API calls without having to build full-fledged client apps. To hook up the test client, open the Api.cshtml in Areas\HelpPage\Views\Help folder of the solution and add the highlighted lines below to the markup.
@Html.DisplayForModel("TestClientDialogs")
@section Scripts {
@Html.DisplayForModel("TestClientReferences")
<link type="text/css" href="~/Areas/HelpPage/HelpPage.css" rel="stylesheet" />
}
Once done, we are all set to test out validation in Web API.
Model Validation via Annotations
If you noted, we had applied various types of annotation attributes to the Account model. You can checkout a comprehensive list of Annotations in MSDN Online. We are using the Key, Range and Required annotations only.
To test out the validations, run the Application and click on the API link on the top right corner of the home page.
This will navigate you to the Api Help page. Here you’ll notice that the Accounts related API that were scaffolded, are now listed.
Click on the POST link to navigate to the details page that shows sample data and the “TEST API” button.

Clicking on “TEST API” brings up the POST dialog that allows us to POST a blob of JSON.
As per our annotations, Name is required, KeyCode range is 0-1000. So let us remove the Name and set the KeyCode to a negative value and then hit Send.
Before that we put a breakpoint in our AccountsController in the PostAccount method.

Now we go back to the Browser and post the following data:
{
"Id": 1,
"Description": "sample string 3",
"KeyCode": -3,
"IsObsolete": false
}
The execution will stop at the break point and we’ll see that ModelState.IsValue has returned false. On deeper inspection, we’ll find the following errors:

If we let the execution flow to continue, we’ll get a HTTP 400/BAD Request and the Response Body will contain the errors in a JSON array as follows (reformatted for readability).
{
"Message":"The request is invalid.",
"ModelState":{
"account":
["Required property 'Name' not found in JSON. Path '', line 6, position 2."],
"account.Name":
["The Name field is required."],
"account.KeyCode":
["The field KeyCode must be between 1 and 1000."]
}}

Under Posting and Over Posting – Limits of Model Validation
From above we see our Validation is working fine, let’s add a proper Account by POSTing the following JSON
{
"Id": 1,
"Name": "Account 1",
"Description": "Description of Account 1",
"KeyCode": 4,
"IsObsolete": false
}
This time we’ll get the following response, with a nice Location Header giving us the URL where it was created, as well as the Body containing the complete JSON object.

The problem of Under-Posting
Now let’s say we want to Edit this Account, so we navigate to the PUT help Page and fire up the Test Client. Let’s post the following JSON to the Account with {id} = 1. The scenario we are trying to mimic is that User did not have any changes for Description, so they did not include Description at all (instead of including Description with the current value). In this case, the expectation is Description will be left alone.
{
"Id": 1,
"Name": "Updated Account 1",
"KeyCode": 4,
"IsObsolete": false
}

As soon as we hit send, we’ll get a HTTP 200 in response indicating all is well. However if we do a GET for id=1 now, we’ll see that the Description has been set to null so unfortunately the user had updated incorrect data. This scenario is referred to as Under Posting.
One easy way to avoid Under Posting is to manage things via View Models and client side libraries like Knockout. But this will avoid only non-malicious POST. People wanting to corrupt data will still be able to POST invalid data.
The better way to do it is opt for the ‘Required’ attribute. However this goes all the way down to the database if you are using the same entity. As a good practice, your View entities should be separate from Database Entities.
Over-Posting
In the Account Entity, let’s say that business rules imply that IsObsolete flag can only be set by Administrator. However if IsObsolete flag is sent with Get requests for everyone, and a value for it is accepted for all PUT and POST requests, then there is no stopping from Accidental or Malicious use. The only way in this case is to have ViewData specific for user types OR, do manual validation on Controller or Business logic to check if Current User can update a particular field or not.
Generalizing Validation via Custom Validation Filter Attribute
As we know we can hook in Action Filters via Filter Attributes. (Check the Lifecycle of a Web API Message article for more clarity on where Action Filters come into play.) To avoid having to check for model state in every Put/Post action method of every controller, we can generalize it by creating a custom action filter.

Note that the ActionFilterAttribute type is present in ASP.NET MVC and Web API (Http namespace), in this case we should select System.Web.Http.Filters.
In the class, override the OnActionExecuting method and check for Valid ModelState. If state is not valid, send back a HTTP 400 and the use the CreateErrorResponse helper on the Request property to pass the error messages. The final class is as follows
public class ValidateModelFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid == false)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
Note that it HttpActionContext is defined in System.Web.Http.Controllers, HttpStatusCode is defined in System.Net and CreateErrorResponse helper method is defined in the System.Net.Http namespaces respectively.
Register the Filter in App_Start\WebApiConfig.cs as follows
config.Filters.Add(new ValidateModelFilterAttribute());
To use the Filter, decorate the PutAccount method with this attribute and comment out the check for Model State
[ValidateModelFilter()]
public HttpResponseMessage PutAccount(int id, Account account)
{
//if (!ModelState.IsValid)
//{
// return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
//}
… //rest of the code remains same
}
Now run the App and try to PUT an invalid JSON like
{
"Id": 1,
"Description": "sample string 3",
"KeyCode": 4,
"IsObsolete": true
}
You will receive a Bad Request error

Conclusion
ASP.NET Web API Controllers can leverage Model State validation when Data Annotation attributes are correctly used. However, there are two cases to be always wary off, these are over-posting and under-posting.
To generalize Validation, we can use a custom action filter to check for Model State in the filter itself and decorating required action methods with the Filter Attribute, instead of repeating the Model State valid check all over.
Download the entire source code of this article (Github)
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Suprotim Agarwal, MCSD, MCAD, MCDBA, MCSE, is the founder of
DotNetCurry,
DNC Magazine for Developers,
SQLServerCurry and
DevCurry. He has also authored a couple of books
51 Recipes using jQuery with ASP.NET Controls and
The Absolutely Awesome jQuery CookBook.
Suprotim has received the prestigious Microsoft MVP award for Sixteen consecutive years. In a professional capacity, he is the CEO of A2Z Knowledge Visuals Pvt Ltd, a digital group that offers Digital Marketing and Branding services to businesses, both in a start-up and enterprise environment.
Get in touch with him on Twitter @suprotimagarwal or at LinkedIn