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

Posted on August 10th, 2011

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 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 CreateComments(List items);
        Comment Update(Comment item);
        Comment GetById(int id);
        List 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 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 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 CreateComments(List items)
        {
            return new List { 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 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 { 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 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

Discussion

mitsbits
mitsbits
12 Aug, 2011 06:55 AM

Just as I was wondering, should I do asmx or svc or should I use a controller for my json? This (as a lot of your posts) has been great help. Keep it up!

12 Aug, 2011 03:30 PM

mitsbits
Glad my posts are of help! Another thing you may want to check out is . It provides a way to do RESTful services too. Many options out there. Of course, it's just flat out fun to do it in MVC! :)

jie
jie
16 Aug, 2011 12:33 PM

Can this be used for Web Forms?

18 Aug, 2011 06:13 PM

jie
The route engine is part of ASP.NET so it can actually be used in web forms as well (see the MSDN site for more info). The controller code you couldn't use directly in web forms, but you could use the concept. You can check the HTTP verb in code behind and render JSON directly to the response stream, using the JavaScriptSerializer class to serialize the Comment objects to the JSON string. The same class can be used to deserialize incoming post/put data. You would just need to manually extract it from the Request.

mitsbits
mitsbits
20 Sep, 2011 12:06 PM

I can not state enough how well this worked with the Remote Attribute in DataAnnotations. Justin, thanks again. P.S. People switch to MVC now, the validation model alone is worth it!

Gustavo
20 Sep, 2011 04:30 PM

Excellent post.

One of the best introduce to Restfull and MVC.

Thanks to make me lost my time getting so much experience in a single post.

Last but not least, your blog its well done formatted and easy to read.

29 Sep, 2011 09:23 AM

Very very useful article. Easy to understand and very well put together :) I found myself being able to modify your code to create my own RESTful Api, so a big thanks for your help :)

George
George
29 Sep, 2011 08:24 PM

One question I have is what if you want to call your MVC RESTful api from a windows forms client? Is this possible? What about from an iPhone app? Is there any way to generate a proxy class like you can in WCF with SVCUTIL.EXE? It seems to me that if you can't call the MVC api from anything but another web browser client, then it's kind of limiting. Am I missing something?

30 Sep, 2011 04:15 PM

mitsbits, Gustavo, Jaymie
Thank you for the comments! Glad to see people finding it useful.

George
You can certainly hit the api from other clients. The use of Fiddler in the article is an example of that. As far as a proxy class or an endpoint on a REST service, I don't believe that the fundamentals of REST really include that approach (although I certainly could be wrong on that point...anyone else have any input?). Typically a REST service would have some sort of accompanying documentation to describe the URI structures and the objects that are worked with. With REST targeting basic HTTP principles it is designed to be easily consumed by any client able to work with HTTP protocols. Most of the REST APIs that I have consumed have required me to write my own client code to handle working with the data delivered rather than using a utility or service reference that would build that code out automatically.

robin
robin
08 Nov, 2011 07:43 PM

Thanks buddy, this is a great introduction. Please consider doing a part 2,3,etc. about "security, data validation, standard error response support, cross domain support, and on and on." :)

Will K
Will K
15 Nov, 2011 01:41 AM

Great article. I've developed RESTful api with ASP.NET MVC3 in my project for a period of time. I used to test my api. Every time when I changed the interface of api. It generates test page automatically for me. I found this very useful while I was changing the specification of api frequently in the development stage. I don't even need Fiddler.

Byron
06 Dec, 2011 08:39 AM

Awesome tutorial. Thanx for the write up.

Piers Lawson
19 Dec, 2011 02:24 PM

With my Resources Over MVC framework you can use an attribute to add RESTful support. If you don't want to use somebody else's framework, one idea you might want to take on board is adding a test harness directly into your API (i.e. if one of the formats you serve is HTML, add a little bit of JavaScript to allow a developer to experiment with your API from within the browser... I have an example here:

19 Dec, 2011 05:04 PM

Piers Lawson
Thanks for sharing that resource! Very nice (and extremely useful) work there.

Ken Pierce
Ken Pierce
26 Dec, 2011 05:49 AM

Thanks a bunch. This post really helped get me over a few stumbling blocks that have been plaguing me.

DeejayRaoul
DeejayRaoul
19 Jan, 2012 09:33 AM

Thanks for getting me started on building a REST service in MVC.

Is there any reason to use the filter to determine the Http method?

I would say this will do, since 'request' is available in your controller:

    [Authorize]
    public ActionResult yourAPIMethod(int id)
    {
        switch (Request.HttpMethod)
        {
            case "GET":
                //your repostory method
            case "DELETE":
                //your repostory method
        }
        return Json(new { Error = true, Message = "Unknown HTTP verb" });
    }
20 Jan, 2012 05:15 AM

DeejayRaoul
Glad to be of help! One of the benefits of the filter is that it abstracts the Request dependency out of the controller logic. This would allow you to test the controller method without having to include an instance of Request (we would probably need to add some additional stuff in the filter to get a bit better test experience, but with the filter approach that would certainly be doable). That being said, yes, your example would be fine as well. Just depends on how far you want to take your level of abstraction.

20 Jan, 2012 09:17 AM

@Justing: Thanks for the reply. I can see the use for more abstraction if needed. Generally i see my controller layer as a relatively thin/dumb layer to transer request to my Repository layer where i implement business logic and database interaction. Since i work on reletively straightforward web- project the need to independently test the controller layer hasn't arisen yet. Something to definitely consider in the future though.

PS: i linked to your article in my newly started blog about my adventures in codeland: http://deejayraoul.blogspot.com/2012/01/building-rest-service-in-mvc-net.html

24 Jan, 2012 04:28 AM

DeejayRaoul
Thanks for the link!

zakizakaria69
zakizakaria69
25 Jan, 2012 11:28 AM

Thanks for this great post. This is very smart approach and make it easy to return presentations as complete web services.

27 Jan, 2012 10:52 PM

Justin, what a great article. Excellent work, a great start. If you have any follow ups on security and the rest of the mix then please do post links here.

Fiddler rocks as well!

Thanls

Steve

John W
01 Feb, 2012 04:50 PM

Great Article,

I must agree that building out a REST API requires a bunch of components, after building a few for various projects and clients I realized there could be a better way.

Soon I will be launching a new product called RocketAPI that will allow developers to build and deploy a fully functional rest API without having to write a single line of code and without needing to config and manage any servers or hardware,

While its not quite ready yet, it will be soon and you will be able to signup and create API for free.

I really did find the article useful for developers, but felt I should also let everyone know about what we are doing.

John

Mau
Mau
06 Feb, 2012 07:28 AM

Sweet! Although I still have some questions unanswered, but your post gives enough to start with my own project. Thanks a lot.

Andrzej Bieniek
21 Feb, 2012 11:25 AM

Great article, it was exacly I was looking for to implement. Thank you...

The One
05 Mar, 2012 04:58 PM

This is certainly a step forward from the ASP.NET AJAX 1.0 extensions that I'm used to. Can't wait to connect this to an ExtJS REST store!

Shanty
Shanty
04 May, 2012 12:09 PM

I'm tryin' to implement the same thing using MVC2 and IIS6 using this post

but I think that I have to change the controller

Your Comments

Preview