Customizing ASP.NET Web API Routing for the User Defined methods in ApiController class

Posted by: Mahesh Sabnis , on 1/14/2013, in Category ASP.NET MVC
Views: 56058
Abstract: 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 demonstrates a technique.

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:

employee-info

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:

debugger

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:

employee-info

image

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:

image004

And for the url:

http://localhost:2154/api/EmployeeInfoApi/GetEmployeeInfoesNew

image005

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.

Give a +1 to this article if you think it was well written. Thanks!
Recommended Articles


Page copy protected against web site content infringement by Copyscape


User Feedback
Comment posted by Man Eangla on Tuesday, January 15, 2013 9:40 PM
thank this webpage
Comment posted by sachin nimbalkar on Wednesday, January 16, 2013 10:55 PM
Great insights. Great article. Thank you sir.
Comment posted by Kiran Patange on Wednesday, January 16, 2013 10:58 PM
Nice Article. Thank you sir.
Comment posted by Eric Lewis on Friday, January 18, 2013 6:33 AM
This is a simple approach provided for HTTP communication and provided methods & API controls are required to developers to build the asp.Net app.
Comment posted by Raju Golla on Tuesday, January 22, 2013 9:55 AM
http://weblogs.asp.net/sukumarraju/default.aspx

Good resource and simply explains the solution.
Thanks,
Raju
Twitter @sukumarraju
Comment posted by Mahesh Sabnis on Monday, January 28, 2013 4:53 AM
Hi All,
  Thanks a lot.

Regards
Mahesh Sabnis
Comment posted by Santosh on Tuesday, May 14, 2013 7:52 AM
Very helping article.Explained very well.
Comment posted by sreekanth on Thursday, May 16, 2013 1:42 AM
great flexibility using simple idea.Thank you
Comment posted by hvm on Monday, May 20, 2013 4:05 AM
Nice article..
I have to create one restful web service in asp.net MVC, nd call this web service method from one class library (connector) or u can say console application and console application push one message, that message have to capture from asp.net mvc web service, so in web service have to develop one post method but how to start that I don't know.
so please help me to solve this problem...
Comment posted by Christoph on Thursday, May 30, 2013 3:10 PM
The default routing setup for Web API is useless.  But changing it to api/{controller}/{action} works well.  Thanks.  
Comment posted by Dhaval on Friday, February 14, 2014 11:59 PM
Great..

This solution is very helpful me in my application

Thanx

Regard

Dhaval
Comment posted by Yusuf on Sunday, September 21, 2014 10:27 AM
I learned so much from this article, thank you very much.

Post your comment
Name:  
E-mail: (Will not be displayed)
Comment:
Insert Cancel