The HTTP action method provided in ASP.NET Web API allows you to perform GET, POST, PUT and DELETE operations. When using Web API for decoupling data access from the client application, server-side model validation becomes very important.
Data Annotations provides a set of attributes that offers a declarative way to apply validation rules directly to a model. Like in ASP.NET MVC, in Web API too, DataAnnotations provide model field error messages while implementing server-side validations. Since Web API services are called using JavaScript based libraries and frameworks using Ajax calls, it is challenging to integrate server-side validation with client-side, in the response message. One solution is to use an ActionFilter for validation.
In the Web API pipeline, after the request message processing part of the message handler runs, the filters (if applied) starts getting executed. The first priority is to the AuthorizationFilter, (if applied), followed by the Model Binding and Model Validation which gets executed with the action filters. The following diagram explains this:
The Model Binding and Validation takes place during the Action Execution stage. This provides the ModelState property of the ApiController base class. This property validates each field of the Model class based on the DataAnnotation or any other custom validation logic. If each field of the Model class is valid, then the ModelState sets its IsValid property to true, else false. What action is to be taken on the invalidated model can be decided using code.
Create a Custom Action Filter
In the following steps, we will be creating custom action filter to respond to validation errors occurring on the Model class, and passing it to JavaScript client.
Step 1: Open the Free Visual Studio 2013 Community Edition and create a blank solution. In this solution, add an empty Web API project of name ‘WebAPI_Validation’.
Step 2: In the App_Data folder add a new Sql Server database of name ‘Application.mdf’. In this database add the EmployeeInfo table using the following script:
CREATE TABLE [dbo].[EmployeeInfo] (
[EmpNo] INT IDENTITY (1, 1) NOT NULL,
[EmpName] VARCHAR (50) NOT NULL,
[DeptName] VARCHAR (50) NOT NULL,
[Designation] VARCHAR (50) NOT NULL,
[Salary] DECIMAL (18) NOT NULL,
PRIMARY KEY CLUSTERED ([EmpNo] ASC)
);
In this table, add some sample data as shown in the following script:
INSERT INTO [dbo].[EmployeeInfo] ([EmpNo], [EmpName], [DeptName], [Designation], [Salary]) VALUES (1, N'MS', N'MS IT Services', N'Director', CAST(780000 AS Decimal(18, 0)))
INSERT INTO [dbo].[EmployeeInfo] ([EmpNo], [EmpName], [DeptName], [Designation], [Salary]) VALUES (2, N'LS', N'Administration', N'Manager', CAST(1556000 AS Decimal(18, 0)))
INSERT INTO [dbo].[EmployeeInfo] ([EmpNo], [EmpName], [DeptName], [Designation], [Salary]) VALUES (3, N'TS', N'Administration', N'Dy. Manager', CAST(45000 AS Decimal(18, 0)))
INSERT INTO [dbo].[EmployeeInfo] ([EmpNo], [EmpName], [DeptName], [Designation], [Salary]) VALUES (8, N'VB', N'HRD', N'Manager', CAST(78000 AS Decimal(18, 0)))
INSERT INTO [dbo].[EmployeeInfo] ([EmpNo], [EmpName], [DeptName], [Designation], [Salary]) VALUES (9, N'SS', N'IT', N'Manager', CAST(980000 AS Decimal(18, 0)))
INSERT INTO [dbo].[EmployeeInfo] ([EmpNo], [EmpName], [DeptName], [Designation], [Salary]) VALUES (10, N'AS', N'IT', N'Manager', CAST(980000 AS Decimal(18, 0)))
INSERT INTO [dbo].[EmployeeInfo] ([EmpNo], [EmpName], [DeptName], [Designation], [Salary]) VALUES (11, N'AS', N'IT', N'Manager', CAST(980000 AS Decimal(18, 0)))
INSERT INTO [dbo].[EmployeeInfo] ([EmpNo], [EmpName], [DeptName], [Designation], [Salary]) VALUES (12, N'AS', N'IT', N'Manager', CAST(890000 AS Decimal(18, 0)))
INSERT INTO [dbo].[EmployeeInfo] ([EmpNo], [EmpName], [DeptName], [Designation], [Salary]) VALUES (13, N'PB', N'HRD', N'Manager', CAST(98000 AS Decimal(18, 0)))
Step 3: In the Models folder, add a new ADO.NET EF. Name this EF as DataAccess. This will start a Wizard. In this wizard, select EF Designer from Database, (Note: Since the article uses VS 2013 with Update 4, the EF wizard shows EF Designer from Database, else the option may be visible to you as Generate from Database). The wizard selects Application.mdf, as shown in the following image:
In the next step of the wizard, select EmployeeInfo table as shown in the following image:
After completion of this wizard, the table mapping will be generated. This creates EmployeeInfo class and ApplicationEntities class in the Models folder.
Build the project.
Step 4: We need to add DataAnnotation on properties of the EmployeeInfo class for validation, but the EmployeeInfo class is generated using the EF wizard, and it is not recommended to change the classes generated using the wizard. So to apply DataAnnotations, we will use the partial class feature of the C#. Since the EmployeeInfo class generated is partial, we can write the same class in a separate class file under the same namespace. To implement it, in the Models folder, add a new class file of name ValidationRepository.cs. In this file add the EmployeeInfo class code as below:
using System.ComponentModel.DataAnnotations;
namespace WebAPI_Validation.Models
{
[MetadataType(typeof(EmployeeInfoMetadata))]
public partial class EmployeeInfo
{
private class EmployeeInfoMetadata
{
public int EmpNo { get; set; }
[Required(ErrorMessage="EmpName is Must")]
[RegularExpression("^[A-Z]+$", ErrorMessage = "Must be Upper Case")]
[MaxLength(30,ErrorMessage="Must be Maximum 30 Characters")]
public string EmpName { get; set; }
[Required(ErrorMessage="Salary is Must")]
[RegularExpression(@"^\d+$",ErrorMessage="Must be Nemeric")]
public decimal Salary { get; set; }
[Required(ErrorMessage = "DeptName is Must")]
[RegularExpression("^[A-Z]+$", ErrorMessage = "Must Start with UpperCase Character")]
[MaxLength(30, ErrorMessage = "Must be Maximum 20 Characters")]
public string DeptName { get; set; }
[Required(ErrorMessage = "Designation is Must")]
[MaxLength(30, ErrorMessage = "Must be Maximum 20 Characters")]
public string Designation { get; set; }
}
}
}
The above code has the partial implementation of the EmployeeInfo class, this contains a private class of name EmployeeInfoMetadata, and this class contains all properties of the EmployeeInfo class with DataAnnotation validation set on it. We are validating fields for Required, RegularExpression, MaxLength, etc. The regular expression for DeptName, EmpName demands that the expression for these values must start from Upper Case characters. The EmployeeInfoMetadata class is applied as MetadataType on the EmployeeInfo class, this means that EmployeeInfoMetadata is metadata of the EmployeeInfo and will be loaded when the EmployeeInfo is loaded.
Step 5: To implement the Custom Action Filter for sending the Validation response to the client, in the project, add a new folder of name ‘ValidationRepositoryFilter’. In this folder, add a new class file of name ValidationErrorHandlerFilterAttribute.cs. In this file add the following code.
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace WebAPI_Validation.ValidationRepositoryFilter
{
//The Model validation Filter
public class ModelValidationErrorHandlerFilterAttribute : ActionFilterAttribute
{
//The method responds with Bad Request HttpStatus Code with the
//Model state validation errors
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext.Request
.CreateErrorResponse(HttpStatusCode.BadRequest,actionContext.ModelState);
}
}
}
}
The above action filter class is derived from the ActionFilterAttribute class and overrides the OnActionExecuting method. This method contains logic for responding with HttpStatusCode code as BadRequest, and ModelState validation errors if the Model using in the current HttpActionContext has validation errors.
Step 6: We will register the validation filter for the HTTP request processing in the HttpConfiguration class. To do so, open the WebApiConfig.cs file from the App_Start folder and add the following line in it:
config.Filters.Add(new ModelValidationErrorHandlerFilterAttribute());
This line will make sure that the custom action filter will be used across all the Http Requests for every ApiController.
Step 7: In the Controllers folder, add a new Empty WEB API Controller of name EmployeeInfoAPIController, in this controller add the following code with GET and POST action methods.
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.Http.Description;
using WebAPI_Validation.Models;
using System.Web.Http.Cors;
namespace WebAPI_Validation.Controllers
{
public class EmployeeInfoAPIController : ApiController
{
ApplicationEntities ctx;
public EmployeeInfoAPIController()
{
ctx = new ApplicationEntities();
}
public IEnumerable Get()
{
return ctx.EmployeeInfoes.ToList();
}
[ResponseType(typeof(EmployeeInfo))]
public IHttpActionResult Get(int id)
{
var Emp = ctx.EmployeeInfoes.Find(id);
if (Emp != null)
{
return Ok(Emp);
}
else
{
return NotFound();
}
}
[ResponseType(typeof(EmployeeInfo))]
public IHttpActionResult Post(EmployeeInfo Emp)
{
ctx.EmployeeInfoes.Add(Emp);
ctx.SaveChanges();
return Ok(Emp);
}
}
}
In the above code, Get() method returns all Employees and Post() method accepts EmployeeInfo object and saves it to Database using EF.
Build the Project and make sure that it is error free.
Implementing the Client Application
Step 1: In the same solution add a new Empty ASP.NET application. Name this as ‘Client_App’ In this application, add the jQuery, Knockout and Bootstrap JavaScript references using NuGet Package Manager (Visual Studio > Tools > NuGet Package Manager).
Step 2: In the project add a new HTML page of name Application.html. Add the following Markup, Script and Databinding expression in it:
EmpNo |
EmpName |
Salary |
DeptName |
Designation |
|
|
|
|
|
Right-Click on the html page and select the option as Set as Start Page.
The Html page has the following features:
- Contains references for jQuery, Knockout and Bootstrap.
- Contains the Knockout ViewModel of name ‘viewModel’. This contains the observables objects for EmployeeInfo, e.g. EmpNo, EmpName, etc. The Employees observable array will be used for storing the Employees received by making Http GET request to Web API. The function loadData () function makes the Http Get call to Web API and the Employees information received is stored in the Employees observable array.
- The Save () function makes POST call to the Web API and sends the Emp object to it. In this Post method we are reading responseText for error responses from the server.
- The Html markup e.g. table, input elements are bound with observables declared in the knockout viewModel.
To run Web API and the Client project, right click on the Solution and select multiple startup project as shown in the following image:
Start the API first and then the Client application. Run application (F5), the following result will be displayed:
Without entering any data for Employee in the textboxes, click on Save button, the error message will be displayed as shown in the following image:
This shows the error message as EmpName is must, DeptName is must, etc. These are the same messages we applied on the EmployeeInfo Model class using DataAnnotations and which is now getting displayed while making client-side calls.
Now enter the Values as shown below:
EmpName : mAhesh, Salary: 98000YYY, DeptName: hRd, Designation: Manager this will show error message as shown in the following image:
This shows the EmpName must start with an Upper Case, the Salary value is invalid, and so on.
Conclusion
We saw how ASP.NET Web API saves valuable server resources by validating the request even before it reaches the action method. This allows us to relay the model validation error messages to the client using custom action filter.
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!
Mahesh Sabnis is a DotNetCurry author and a Microsoft MVP having over two decades of experience in IT education and development. He is a Microsoft Certified Trainer (MCT) since 2005 and has conducted various Corporate Training programs for .NET Technologies (all versions), and Front-end technologies like Angular and React. Follow him on twitter @
maheshdotnet or connect with him on
LinkedIn