Build a RESTful API architecture within an ASP.NET MVC 3 application.

ASP.NET MVC 3, with its glorious URL structures and ease of working with and controlling HTTP request/response data is primed to build REST type API services. But how does one accomplish that and what does the whole RESTful thing really mean?

Building a full blown API (of any type) involves a lot of architecture components, from data validation to security and beyond. This post does not attempt to address all of that. It focuses on the initial structure of a RESTful service within an ASP.NET MVC 3 application that works with JSON data in and out. We will look at how we can use the route engine, the HTTP verb attributes and a lean controller design to provide a starting point for a REST API.

 

We start by making use of Areas in MVC to create an API Area within an application. This will allow us to isolate the API and use another Area or even the top level to add documentation and other API support tools like a web interface for testing the API (outside the scope of this post).

Solution Tree

Our sample API will handle Comment data. The class for a comment:

namespace Website.Areas.Api.Models {     public class Comment     {         public int Id { get; set; }         public string Subject { get; set; }         public string Body { get; set; }         public string AuthorName { get; set; }     } } 

The API will support sending and receiving JSON structured data. We will be able to send in a Comment object in JSON like so:

{     "Subject": "A Subject",     "Body": "The Body",     "AuthorName": "Mike Jones" } 

or a batch of Comments:

{     items: [{         "Subject": "A Subject",         "Body": "The Body",         "AuthorName": "Mike Jones"     }, {         "Subject": "A Second Subject",         "Body": "The Other Body",         "AuthorName": "James Jones"     }] } 

Returned JSON for a single Comment will look like:

{     "Id": 3,     "Subject": "A Subject",     "Body": "The Body",     "AuthorName": "Mike Jones" } 

A set of Comments returned like so:

[{     "Id": 1,     "Subject": "A Subject",     "Body": "The Body",     "AuthorName": "Mike Jones" }, {     "Id": 2,     "Subject": "A Second Subject",     "Body": "The Other Body",     "AuthorName": "James Jones" }] 

A brief REST before we route and control

To craft a RESTful solution there are a couple of targets that we want to hit. The first is the use of HTTP verbs to handle relative actions.

  • GET Used to request data
  • POST Used to create a new data record or a set of new data records
  • PUT Used to update an existing data record
  • DELETE Used to delete an existing data record

The second is to use a url structure that embodies a human readable request for data.

  • GET
    /Api/Comments
    /Api/Comments/2/10
    /Api/Comments/Comment/3
  • POST
    /Api/Comments/Comment
  • PUT
    /Api/Comments/Comment/3
  • DELETE
    /Api/Comments/Comment/3

Routing

Within the area registration code (ApiAreaRegistration.cs) we can add routes for our RESTful url patterns. Let’s take a look at the code for the routes and then go over their purpose.

using System.Web.Mvc; namespace Website.Areas.Api {     public class ApiAreaRegistration : AreaRegistration     {         public override string AreaName { get { return "Api"; } }         public override void RegisterArea(AreaRegistrationContext context)         {             context.MapRoute(                 "SingleComment",                 "Api/Comments/Comment/{id}",                 new { controller = "Comments", action = "Comment",                     id = UrlParameter.Optional }             );             context.MapRoute(                 "ListComments",                 "Api/Comments/{page}/{count}",                 new { controller = "Comments", action = "CommentList",                     page = UrlParameter.Optional, count = UrlParameter.Optional }             );             context.MapRoute(                 "ListCommentsAll",                 "Api/Comments",                 new { controller = "Comments", action = "CommentList",                     page = UrlParameter.Optional, count = UrlParameter.Optional }             );             context.MapRoute(                 "Api_default",                 "Api/{controller}/{action}/{id}",                 new { action = "Index", id = UrlParameter.Optional }             );         }     } } 

Routes do not have any knowledge of the HTTP verbs, so the ones that we add are going to support multiple scenarios to get to our controller actions. The SingleComment route supports our GET, PUT and DELETE requests when an id is included in the url string, and the POST when the id is left off. The ListComments route supports a GET request for paging Comments in which a page number and a count per page is included in the url string. The ListCommentsAll route handles a GET request for all Comments (no paging). It will also support a POST request of a list of Comments where the url string does not include anything after the /Api/Comments.

Before we take a look at the CommentsController code, let’s check out a custom ActionFilterAttribute that we can craft to help us handle multiple verbs through a single controller action.

using System.Web.Mvc; namespace Website.Models {     public class RestHttpVerbFilter : ActionFilterAttribute     {         public override void OnActionExecuting(ActionExecutingContext filterContext)         {             var httpMethod = filterContext.HttpContext.Request.HttpMethod;             filterContext.ActionParameters["httpVerb"] = httpMethod;             base.OnActionExecuting(filterContext);         }     } } 

This code will capture the HTTP verb of the request and store it in the ActionParameters collection. By applying this attribute to a controller action we can add a method parameter named httpVerb and the RestHttpVerbFilter will handle binding the HTTP request verb value to it. Our controller needs to support an action method with a common signature (the same parameters) but take different actions based on the HTTP verb. It is not possible to override a method with the same parameter signature but a different HTTP verb attribute. This custom attribute will allow us to have a single controller action method that can take action based on the HTTP verb without having to contain the logic to determine the verb. With a single controller this is not that big of a deal, but I’d imagine that our API is going to embody more than just Comment management, and thus a need to repeat the verb capture code in multiple controllers for the API.

Let’s take a look at the CommentsController code and see how this all unfolds:

using System.Collections.Generic; using System.Web.Mvc; using Website.Areas.Api.Models; using Website.Models; namespace Website.Areas.Api.Controllers {     public class CommentsController : Controller     {         ICommentManager commentManager;         public CommentsController()         {             this.commentManager = new CommentManager();         }         [HttpGet]         public JsonResult CommentList(int? page, int? count)         {             var model = this.commentManager.GetComments(page, count);             return Json(model, JsonRequestBehavior.AllowGet);         }         [HttpPost]         public JsonResult CommentList(List<Comment> items)         {             var model = this.commentManager.CreateComments(items);             return Json(model);         }         [RestHttpVerbFilter]         public JsonResult Comment(int? id, Comment item, string httpVerb)         {             switch(httpVerb)             {                 case "POST":                     return Json(this.commentManager.Create(item));                 case "PUT":                     return Json(this.commentManager.Update(item));                 case "GET":                     return Json(this.commentManager.GetById(id.GetValueOrDefault()),                         JsonRequestBehavior.AllowGet);                 case "DELETE":                     return Json(this.commentManager.Delete(id.GetValueOrDefault()));             }             return Json(new { Error = true, Message = "Unknown HTTP verb" });         }     } } 

The controller has a private member of type ICommentManager and the controller constructor instantiates an object of type CommentManager that will implement the interface. The contract for the interface looks like so:

using System.Collections.Generic; namespace Website.Areas.Api.Models {     public interface ICommentManager     {         Comment Create(Comment item);         List<int> CreateComments(List<Comment> items);         Comment Update(Comment item);         Comment GetById(int id);         List<Comment> GetComments(int? page, int? count);         bool Delete(int id);     } } 

The first controller action, CommentList(int? page, int? count), supports the HTTP GET verb only and handles querying comments. The logic to determine if the list of comments is paged or not will be brokered off to the CommentManager. The returned List<Comment> data is sent to the JsonResult which will handle serializing it to JSON and providing the correct response headers to the client consuming the API. This method is hit whenever a GET request is made with the following url string structures:

/Api/Comments /Api/Comments/2/10 

The next controller action, CommentList(List<Comment> items), supports the HTTP POST verb only and is used to support adding multiple Comment objects in a single request. This method is hit whenever a POST request is made with the following url string structure:

/Api/Comments 

The final controller action, Comment(int? id, Comment item, string httpVerb), is where most of the magic happens. This method supports all four verbs when a request is made with the following url string structures:

/Api/Comments/Comment /Api/Comments/Comment/3 

The Comment method brokers all logic to work with a single comment to the appropriate CommentManager method and returns the result of those methods directly through the JsonResult. If, for some insane reason (HTML 6 arrives with new verbs), we receive an unsupported HTTP verb we return a custom JSON error object.

Testing the API with Fiddler

We can make use of Fiddler to test the API. First we need to create the CommentManager class and give it some sample logic to return some test data.

using System.Collections.Generic; namespace Website.Areas.Api.Models {     public class CommentManager : ICommentManager     {         public Comment Create(Comment item)         {             item.Id = 1;             return item;         }         public List<int> CreateComments(List<Comment> items)         {             return new List<int> { 1, 2, 3 };         }         public Comment Update(Comment item) { return item; }         public Comment GetById(int id)         {             return new Comment             {                 Id = id,                 Subject = "Loaded Subject",                 Body = "Loaded Body",                 AuthorName = "Loaded Author"             };         }         public List<Comment> GetComments(int? page, int? count)         {             var comment1 = new Comment             {                 Id = 1,                 Subject = "First Subject",                 Body = "First Body",                 AuthorName = "First Author"             };             var comment2 = new Comment             {                 Id = 2,                 Subject = "Second Subject",                 Body = "Second Body",                 AuthorName = "Second Author"             };             var items = new List<Comment> { comment1, comment2 };             return items;         }         public bool Delete(int id) { return true; }     } } 

Then we can F5 the project to have the API running, copy the localhost with port url and use that in Fiddler to send in HTTP requests. The url for my instance:

http://localhost:24771/ 

NOTE:
The sample code download has a default HomeController class. I did not mention adding that in the article. If you are crafting the code as you read through it then you may get “The resource cannot be found” server error when running the debug. That’s ok, you can still hit the CommentsController with Fiddler. Just leave the browser with the server error open while you use Fiddler so the MVC application is running.

Creating a new request in Fiddler is done by clicking on the Request Builder tab. Leaving the drop down for the verb to GET and setting the url to http://localhost:24771/Api/Comments, we can execute it and see that application is hit (by the result in the Web Sessions panel on the left).

Get All Comments

Selecting the session result, clicking on the Inspectors tab and then on the Raw view button allows us to view the response data.

Get All Comments Result

The other GET requests are done the same, but with different urls. The POST, PUT and DELETE actions involve changing the HTTP verb drop down, using either the http://localhost:24771/Api/Comments url for a POST of multiple comments or the http://localhost:24771/Api/Comments/Comment/{id} url for working with a single comment. Within the Request Body text area we can add the JSON object that we want to send to the API. The only other piece that we need to handle is telling the API via the HTTP header that the content type is JSON. In the Request Headers text area we need to add the following line:

Content-Type: application/json 

MVC will identify this header and use the JSON model binding to map the request body data to a Comment object in our controller actions (or the List<Comment> item object if we are doing a POST of multiple Comments at once). Posting a new comment to the API with Fiddler looks like so:

Post New Comment

If we set breakpoints throughout the CommentsController we can go through the various requests with Fiddler and validate that our routing is working as planned and that we are reaching the correct action methods for each type of request.

Can I hand out my API url now?

Remember, this is just a starting point for creating REST type functionality in an MVC 3 application. There is a long way to go to craft out a true API solution. The next step would be to fill out the logic for the CommentManager to work with a data storage layer for persisting the Comment data. After that, adding some logic in the CommentsController.Comment method to support clients that don’t support the PUT or DELETE verbs. From there the fun begins. Thinking about security, data validation, standard error response support, cross domain support, and on and on.

Hey, writing software ain’t like dustin’ crops boy…’er, or girl. All smuggler’s humor aside, if you are in need of a way to write a RESTful service within MVC 3 hopefully this can help get you rolling.

Download the code
Sample Solution Zip File (RestApiMvc3.zip)

Reference : http://www.iwantmymvc.com/rest-service-mvc3

Advertisements

3 thoughts on “Build a RESTful API architecture within an ASP.NET MVC 3 application.

    • Greg Arroyo,

      Actually, I have attached the reference to the original article at the bottom of the article.
      So the reader can know where this article is originally from.
      It is for educational purpose only.

      Regards,
      Dara

  1. Pingback: Create a REST API with PHP « Chandara

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s