This article explains the mechanism of handling Model level error messages which occur in ASP.NET Web API action methods while responding to client applications. An article on Handling Custom Exceptions in ASP.NET Web API 2 has already been published. This article is limited to discussing errors that occur on the Model object while processing the HTTP request.
In Web API, validations on the Model object is either defined using DataAnnotations or custom validation attributes. This approach ensures that the posted data is validated first, before processing it in Action Methods.
The ModelState property of the type ModelStateDictionary is used to check the model state for validation after the model binding is in process. This means that after receiving an HTTP POST request, the posted data is read from the HTTP body and mapped with the Model object passed to the POST/PUT action method.
Here, the ModelState property object with its IsValid Boolean property is used to validate the Model, based on the DataAnnotations applied on each property of the Model class.
It is important that if any errors occur in the Model while processing the received request, then all validation messages related to the Model should be correctly communicated to the client. It is a good approach to have a helper method in the Web API Controller class which will accept a ModelStateDictionary parameter and return validation errors that occur on properties of the Model class. Figure 1 explains the process:
Figure 1: The mechanism of handling validation error messages in MVC
Consume WEB API using RestSharp and respond with Model Error Messages
The following steps discuss an application implementation. The Web API project contains SQL Server Database, and using EntityFramework Code-First approach, a table will be created in the database.
Step 1: Start Visual Studio 2015/2017 and create an ASP.NET Application. Name this application as REST_Api. Select an Empty Web API project as shown in Figure 2.
Figure 2: Selecting the Web Project template
Click on OK to create an application.
Step 2: In the project, right-click on the App_Data folder and add a new SQL Server Database and name it as AppDatabase.mdf.
Step 3: In the Models folder, add a new class file. Name this file as EmployeeInfo.cs and add the following code in it:
using System.ComponentModel.DataAnnotations;
namespace REST_Api.Models
{
public class EmployeeInfo
{
[Key]
public int Id { get; set; }
[Required(ErrorMessage = "EmpNo is Must")]
public int EmpNo { get; set; }
[Required(ErrorMessage ="EmpName is Must")]
public string EmpName { get; set; }
[Required(ErrorMessage = "Salary is Must")]
public int Salary { get; set; }
[Required(ErrorMessage = "DeptName is Must")]
public string DeptName { get; set; }
[Required(ErrorMessage = "Designation is Must")]
public string Designation { get; set; }
}
}
The model class shown above contains properties with DataAnnotation validations applied on each property. The Id property is applied with the KeyAttribute which will make the property as Identity and primary key.
Step 4: Right-Click on the Models folder and add a new ADO.NET Entity Data Model. Name this as AppDataEntities. In the Wizard of the Entity Data Model, select Code First from database option as shown in Figure 3.
Figure 3: Selecting Code-First from Database for EntityFramework from the Wizard
In the Wizard, select the AddDatabase.mdf which is created in Step 2.
Since the database is empty, there are no tables to be selected in the Wizard. After completing the wizard, the Models folder will show AppDataEntities.cs class file and will also show the database connection string added to the Web.config file.
The AppDataEntities class created in this file provides the mechanism to manage Database connectivity and CRUD operations. Modify this class by adding the following property in it:
public DbSet<EmployeeInfo> Employees { get; set; }
Step 5: In the Web API project, add the following NuGet Packages
- Unity.WebAPI for Dependency Injection using Unity Container and
- Microsoft.AspNet.WebApi.Cors for enabling CORS for the Web API Project.
Use following commands to install the packages:
Install-Package Microsoft.AspNet.WebApi.Cors
Install-Package Unity.WebAPI
Step 6: In the project, add a new folder of the name Repositories. In this folder, add a new class file and name it as EmpRepository.cs. This file will contain the following code:
using REST_Api.Models;
using System.Collections.Generic;
using System.Linq;
using Unity.Attributes;
namespace REST_Api.Repositories
{
public interface IRepository<TEntity,TPk> where TEntity: class
{
IEnumerable<TEntity> Get();
TEntity Get(TPk id);
TEntity Create(TEntity entity);
bool Update(TPk id, TEntity entity);
bool Delete(TPk id);
}
public class EmpRepository : IRepository<EmployeeInfo, int>
{
[Dependency]
public AppDataEntities ctx { get; set; }
public EmployeeInfo Create(EmployeeInfo entity)
{
ctx.Employees.Add(entity);
ctx.SaveChanges();
return entity;
}
public bool Delete(int id)
{
var res = false;
var emp = ctx.Employees.Find(id);
if (emp != null)
{
ctx.Employees.Remove(emp);
ctx.SaveChanges();
res = true;
}
return res;
}
public IEnumerable<EmployeeInfo> Get()
{
return ctx.Employees.ToList();
}
public EmployeeInfo Get(int id)
{
var emp = ctx.Employees.Find(id);
return emp;
}
public bool Update(int id, EmployeeInfo entity)
{
var res = false;
var emp = ctx.Employees.Find(id);
if (emp != null)
{
emp.EmpName = entity.EmpName;
emp.Salary = entity.Salary;
emp.DeptName = entity.DeptName;
emp.Designation = entity.Designation;
ctx.SaveChanges();
res = true;
}
return res;
}
}
}
The above code contains a IRepository generic interface with generic methods.
The EmpRepository class implements this interface to perform CRUD operations on EmployeeInfos table using EmployeeInfo entity class. This uses a simple repository pattern.
The AppDataEntities property is applied with Dependency attribute. This makes the AppDataEntities as a target for dependency injection. Please visit ASP.NET MVC 5: Using a Simple Repository Pattern for Performing Database Operations to understand more about implementing simple repository pattern in an ASP.NET MVC application.
Using this simple repository pattern, the EF layer will be isolated from the ApiController.
Step 7: After installing the Unity package, App_Start folder will be added with UnityConfig.cs.
This file will contain the UnityConfig class with RegisterComponents() method in it. This method will create a UnityContainer which will be used to register all dependency objects in it. Add the following code in it to register the AppDataEntities and EmpRepository classes as dependencies in UnityContainer using the following code (highlighted):
using System.Web.Http;
using Unity;
using Unity.WebApi;
using REST_Api.Models;
using REST_Api.Repositories;
namespace REST_Api
{
public static class UnityConfig
{
public static void RegisterComponents()
{
var container = new UnityContainer();
container.RegisterType(typeof(AppDataEntities));
container.RegisterType(typeof(IRepository<EmployeeInfo, int>), typeof(EmpRepository));
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
}
}
}
Modify the Register() method of WebApiConfig Class in App_Start folder to support CORS as shown in following code (highlighted).
using System.Web.Http;
namespace REST_Api
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.EnableCors();
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
Modify Application_Start() method of Global.asax to call RegisterComponents() method of the UnityConfig class as shown in following code (highlighted).
protected void Application_Start()
{
UnityConfig.RegisterComponents();
GlobalConfiguration.ConFigure(WebApiConfig.Register);
}
Step 8: In the Controllers folder, add a new empty Web API Controller. Name this API as EmployeeAPIController.cs. Add the following code in this API:
using REST_Api.Models;
using REST_Api.Repositories;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Description;
using System.Web.Http.ModelBinding;
using System.Web.Http.Results;
using System.Web.Http.Cors;
namespace REST_Api.Controllers
{
[EnableCors("*","*","*")]
public class EmployeeInfoAPIController : ApiController
{
IRepository<EmployeeInfo, int> repository;
public EmployeeInfoAPIController(IRepository<EmployeeInfo, int> repositiry)
{
this.repository = repository;
}
// GET: api/EmployeeInfoAPI
[ResponseType(typeof(IEnumerable<EmployeeInfo>))]
public IHttpActionResult Get()
{
return Ok(repository.Get());
}
// GET: api/EmployeeInfoAPI/5
[ResponseType(typeof(EmployeeInfo))]
public IHttpActionResult Get(int id)
{
var Emp = repository.Get(id);
if (Emp == null)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent(string.Format($"No Employee with ID = {id}")),
ReasonPhrase = "Employee ID Not Found"
});
}
return Ok(Emp);
}
// POST: api/EmployeeInfoAPI
[ResponseType(typeof(EmployeeInfo))]
public IHttpActionResult Post([FromBody]EmployeeInfo emp)
{
if (ModelState.IsValid)
{
return Ok(repository.Create(emp));
}
else
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotAcceptable)
{
Content = new StringContent(string.Format($"Error with Posted Data \n {GetModelErrorMessagesHelper(ModelState)}")),
ReasonPhrase = "Employee Data is Invalid"
});
}
}
// PUT: api/EmployeeInfoAPI/5
[ResponseType(typeof(bool))]
public IHttpActionResult Put(int id, [FromBody]EmployeeInfo emp)
{
if (ModelState.IsValid)
{
var res = repository.Update(id, emp);
if (!res)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent(string.Format($"No Employee with ID = {id}")),
ReasonPhrase = "Employee ID Not Found"
});
}
else
{
return new ResponseMessageResult(Request.CreateResponse(HttpStatusCode.OK, "Update Successful"));
// return Ok(true);
}
}
else
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotAcceptable)
{
Content = new StringContent(string.Format($"Error with Posted Data \n {GetModelErrorMessagesHelper(ModelState)}")),
ReasonPhrase = "Employee Data is Invalid"
});
}
}
// DELETE: api/EmployeeInfoAPI/5
[ResponseType(typeof(bool))]
public IHttpActionResult Delete(int id)
{
var res = repository.Delete(id);
if (res)
{
return new ResponseMessageResult(Request.CreateResponse(HttpStatusCode.OK, "Record Deleted Successfully"));
//return Ok(true);
}
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent(string.Format($"No Employee with ID = {id}")),
ReasonPhrase = " Employee ID Not Found"
});
}
/// <summary>
/// Helper method for reading the ModelstateDictionary for errors
/// </summary>
/// <param name="errors"></param>
/// <returns></returns>
private string GetModelErrorMessagesHelper(ModelStateDictionary errors)
{
string messages = "";
foreach (var item in errors)
{
for (int j = 0; j < item.Value.Errors.Count; j++)
{
messages += $"{item.Key.ToString()} \t {item.Value.Errors[j].ErrorMessage} \n";
}
}
return messages;
}
}
}
The API class shown in the above code contains the following specifications:
- The IRepository interface is constructor injected in Web API Controller class.
- The EnableCors attribute is applied to the controller class. This attribute will make sure that the request to the controller can be accepted from any client address (origin), HTTP method and HTTP header.
- Get(int id) method returns the EmployeeInfo object based on the id parameter. If Employee is not found, then HttpResponseException is raised which accepts HttpResponseMessage object with HttpStatusCode, Content and ReasonPhrase. This returns the corresponding error message in Response.
- GetModelErrorMessageHelper() method accepts ModelStateDictionary Object. This Object contains Key/Value pairs of Model Object properties. The Key represent property name from Model class and the Value represent Value of property or the error message. There might be more than one properties from the Model class which might be invalidated. A for loop is used to read error messages for all invalid properties from the ModelStateDictionary.
- Put() and Post() methods accepts an EmployeeInfo Model object and then check this object for validation using ModelState.IsValid object property. If this property returns false, a HttpResponseException is raised which creates HttpResponseMessage with HttpStatusCode, Content and ReasonPhrase property values. The value for Content property is set using the return value from GetModelErrorMessageHelper() method.
Thus, Web API project is created having helper for handling Model Validations in the Web API class.
Build the project.
Consuming Web API in Console Client using RestSharp Client for .NET
This section discusses mechanism of consuming Web API in a .NET application using RestSharp .NET Client.
RestSharp is a .NET Client library that provides ready-to-use methods for performing HTTP Operations e.g. GET/POST/PUT/DELETE. To read more about RestSharp, visit this link.
Step 1: In the same solution, add a new Console project. Name this project as ClientApp. In this project, add RestSharp package using NuGet package manager.
Step 2: In this project, add a new folder named Models. Add a new class file in this folder which contains EmployeeInfo class having the same properties as those in EmployeeInfo class defined in the Web API project.
Step 3: In project, add a new folder named Repositories. In this folder, add a class file named CallerRepository.cs. Add the following code in the file:
using ClientApp.Models;
using RestSharp;
using System;
using System.Collections.Generic;
namespace ClientApp.Repositories
{
public interface ICallRepository
{
List<EmployeeInfo> Get();
EmployeeInfo Get(int id);
EmployeeInfo Create(EmployeeInfo entity);
string Update(int id, EmployeeInfo entity);
string Delete(int id);
}
public class CallerRepository : ICallRepository
{
string apiUrl;
RestClient client;
public CallerRepository()
{
apiUrl = "http://localhost:54018/api/EmployeeInfoAPI";
client = new RestClient(apiUrl);
}
public EmployeeInfo Create(EmployeeInfo entity)
{
var request = new RestRequest(Method.POST);
request.AddJsonBody(entity);
var response = client.Execute<EmployeeInfo>(request);
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
throw new Exception($"Some Error Occured {response.Content}" +
$"{response.StatusDescription}");
}
return response.Data;
}
public string Delete(int id)
{
var request = new RestRequest($"{apiUrl}/{id}", Method.DELETE);
var response = client.Execute<bool>(request);
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
throw new Exception($"Some Error Occured {response.Content}");
}
return response.Content;
}
public List<EmployeeInfo> Get()
{
var request = new RestRequest(Method.GET);
var response = client.Execute<List<EmployeeInfo>>(request);
return response.Data;
}
public EmployeeInfo Get(int id)
{
IRestResponse<EmployeeInfo> response = null;
var request = new RestRequest($"{apiUrl}/{id}", Method.GET);
response = client.Execute<EmployeeInfo>(request);
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
throw new Exception($"Some Error Occured {response.Content}");
}
return response.Data;
}
public string Update(int id, EmployeeInfo entity)
{
var request = new RestRequest($"{apiUrl}/{id}", Method.PUT);
request.AddJsonBody(entity);
var response = client.Execute<bool>(request);
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
throw new Exception($"Some Error Occured {response.Content}");
}
return response.Content;
}
}
}
The above code has the following specifications:
- The RestSharp library provides various classes. The above code uses the following classes:
- RestClient - this class accepts Web API URL to connect to the API and execute HTTP request messages for performing HTTP Operations using Execute() method. This method returns IRestResponse object that represents the HTTP response received for the HTTP request executed on the Web API.
- RestRequest - this class represents HttpRequest. This class contains overloaded constructors that accepts various parameters. In the above code, various constructor overloads are used that accept either only Resharp.Method enum or a resource as string, as well as Resharp.Method enum parameters. This provides HTTP Methods as GET/POST/PUT/DELETE. The RestRequest class has an AddJsonBody() method that accepts Model object from the client application. This Model object will be serialized as JSON and added to the HTTP Request Body.
- The IRestResponse object has a Content property which receives HTTP Response Content. The Data property of the IRestResponse object reads the HTTP Response data after successful execution of HTTP Request on Web API.
- The above code uses the RestSharp library with RestClient and RestRequest classes to perform HTTP Operations.
Step 4: To complete the client application, add the following code in the Main method of Program.js.
static void Main(string[] args)
{
EmployeeInfo model = new EmployeeInfo();
CallerRepository caller = new CallerRepository();
try
{
var response = caller.Get();
foreach (var res in response)
{
Console.WriteLine($"{res.Id} {res.EmpNo} {res.EmpName} {res.Salary} {res.DeptName} {res.Designation}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Error Occured " +
$"{ex.Message}");
}
Console.ReadLine();
}
This code defines instances of EmployeeInfo and CallerRepository classes. The Main() method makes a call to the Get() method of the CallerRepository class in the try..catch block.
Step 5: Right-click on the solution and select multiple startup project. Set execution order as shown in Figure 4.
Figure 4: Setting Multiple Startup projects
Run the application using F5.
The Web API project will start and the Console client application will be executed. Since Web API application is using Code-First approach for Get() calls from the client application, the Get() method from Web API will be invoked and it will create an empty EmployeeInfos table in the database. Since this table is empty, a blank response will be returned to the client.
Step 6: To test POST request, add the following code in the Main() method of Program.cs
var emp = new EmployeeInfo()
{
EmpNo = 106,
EmpName = "Anil",
Salary = 220000,
DeptName = "SL",
Designation = "Sr.Manager"
};
var responsePost = caller.Create(emp);
Console.WriteLine($"{responsePost.Id} {responsePost.EmpNo} {responsePost.EmpName} {responsePost.Salary} {responsePost.DeptName} {responsePost.Designation}");
The code defines an EmployeeInfo object and passes it to the Create() method from CallRepository class. This object is further passed on to the HTTP Post request. Run the application. This will create a new Employee record and the following result will be displayed.
Figure 5: Successful result
Modify the EmployeeInfo object values and make a call to the Create() method without sending EmpName and DeptName. The client will receive the following detailed error information from the Web API.
Figure 6: Response with Error Handled on the Web API
The response received will show the detailed error messages shown in the above image. Since the GetModelErrorMessageHelper() accepts the ModelStateDictionary object, the Web API is processing ModelState posted from the client application. and After discovering that the Model state is invalid, a detailed error information is returned.
Similarly, you can also test the PUT HTTP request.
Conclusion:
In case of creating a Web API, it is very important that the Web API project must have an arrangement of handling and responding to exceptions by communicating appropriate error messages to client applications.
Download the entire source code of this article (Github)
This article was technically reviewed by Ravi Kiran and Suprotim Agarwal.
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