Last week while conducting a training on HTML5, WEB API and jQuery, we had a good discussion about the default WebAPI controller class and its method mapping to HTTP GET and POST request. If you are already familiar with MVC 4 and WebAPI Controller, then you’ll know that we get an ApiController class as shown below:
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
// POST api/values
public void Post([FromBody]string value)
{
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
public void Delete(int id)
{
}
}
In the code above, we have methods which map themselves to the HTTP GET|POST|PUT|DELETE request. The Get method is overloaded, so if the HTTP GET request contains parameters, then the Get(int id) methods will be called. This is a simple approach provided for HTTP communication.
Now let’s consider a practical scenario which a developer may want to implement in the WebAPI Controller. For eg: If the developer wants to write his/her own method and wants to map it with the HTTP Request like GET|POST etc, then how do we implement it. Well I have found one possible solution that I will share in this article.
Consider that the developer is using ADO.NET EF and generating WebAPI Controller class from EF, then the API Controller class will be generated as shown below:
The APIController class will be as shown below:
public class EmployeeInfoAPIController : ApiController
{
private CompanyEntities db = new CompanyEntities();
// GET api/EmployeeInfoAPI
public IEnumerable<EmployeeInfo> GetEmployeeInfoes()
{
return db.EmployeeInfoes.AsEnumerable();
}
// GET api/EmployeeInfoAPI/5
public EmployeeInfo GetEmployeeInfo(int id)
{
EmployeeInfo employeeinfo = db.EmployeeInfoes.Single(e => e.EmpNo == id);
if (employeeinfo == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return employeeinfo;
}
// PUT api/EmployeeInfoAPI/5
public HttpResponseMessage PutEmployeeInfo(int id, EmployeeInfo employeeinfo)
{
if (ModelState.IsValid && id == employeeinfo.EmpNo)
{
db.EmployeeInfoes.Attach(employeeinfo);
db.ObjectStateManager.ChangeObjectState(employeeinfo, EntityState.Modified);
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
// POST api/EmployeeInfoAPI
public HttpResponseMessage PostEmployeeInfo(EmployeeInfo employeeinfo)
{
if (ModelState.IsValid)
{
db.EmployeeInfoes.AddObject(employeeinfo);
db.SaveChanges();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, employeeinfo);
response.Headers.Location = new Uri(Url.Link("DefaultApi2", new { id = employeeinfo.EmpNo }));
return response;
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
// DELETE api/EmployeeInfoAPI/5
public HttpResponseMessage DeleteEmployeeInfo(int id)
{
EmployeeInfo employeeinfo = db.EmployeeInfoes.Single(e => e.EmpNo == id);
if (employeeinfo == null)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
db.EmployeeInfoes.DeleteObject(employeeinfo);
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
return Request.CreateResponse(HttpStatusCode.OK, employeeinfo);
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
Now to make a call to the ‘GetEmployees()’ method, the url used can be as shown below:
http://localhost:2154/api/EmployeeInfoApi
If we place a breakpoint on the method, the debugger will look similar to the following:
This is a natural behavior, because the default Route expression in the WebConfig class is defined as follows:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Now let’s modify the EmployeeAPIController Class and the GetEmployeeInfoesNew() methods as shown below:
// GET api/EmployeeInfoAPI
public IEnumerable<EmployeeInfo> GetEmployeeInfoes()
{
return db.EmployeeInfoes.AsEnumerable();
}
// GET api/EmployeeInfoAPI
public IEnumerable<EmployeeInfo> GetEmployeeInfoesNew()
{
return db.EmployeeInfoes.AsEnumerable();
}
Apply breakpoints on the GetEmployeesInfoes() and GetEmployeesInfoesNew() and run the application using the same URL. Now test this URL in Chrome and you will get this exception:
The above behavior is correct because there are multiple GET type methods without parameter. The question is how to solve this? One of the ways which I found is to modify the Routing in the WebApiConfig class as shown here:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Route for POST method
config.Routes.MapHttpRoute(
name: "DefaultApi2",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// Route GET method
config.Routes.MapHttpRoute(
name: "DefaultApi1",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new {action = "get", id = RouteParameter.Optional }
);
}
}
The route template has the following expression with action name:
"api/{controller}/{action}/{id}"
and for the ‘GET’ request the defaults are set as shown below:
defaults: new {action = "get", id = RouteParameter.Optional }
This routes ensures that the WebAPI Routing uses the ActionName from the URL for the HTTP GET request. Now to test the API, apply breakpoints on the methods, run the application, and the URL will be:
http://localhost:2154/api/EmployeeInfoApi/GetEmployeeInfoes
The call will be processed in debug mode as shown below:
And for the url:
http://localhost:2154/api/EmployeeInfoApi/GetEmployeeInfoesNew
We can make similar changes for the POST request as well.
Conclusion
The WEB API can be used flexibly as per our business requirements by adding custom actions and necessary changes in the routing expressions.
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