Meditation and Mindfulness for Software / IT Professionals. Conducted by Bipin Joshi in Thane. Read more...

Use ASP.NET MVC Controller as API

A few months ago I wrote ASP.NET MVC Controller Vs. Web API - 5 Things You Should Know wherein I discussed the main differences between an ASP.NET MVC controller and Web API. Some readers asked how exactly one can use an MVC controller as an API. So, in this article I am going to discuss the same with an example.

Before I illustrate how an ASP.NET MVC controller can be used as an API or a service, let's recap a few things:

  • Web API controller implements actions that handle GET, POST, PUT and DELETE verbs.
  • Web API framework automatically maps the incoming request to an action based on the incoming requests' HTTP verb.
  • MVC controller usually handles GET and POST requests but you can handle other verbs also.
  • There is no automatic mapping of an incoming request to an action like Web API. The URL dictates which action you wish to invoke.

Ok. With this understanding let's proceed to develop a simple API using MVC controller. We won't use any Web API infrastructure or Web API specific assemblies / classes.

Begin by creating an empty ASP.NET MVC application using Visual Studio.

In the newly created project add an ADO.NET Entity Data Model (.edmx) for the Customers table of the Northwind database. If you wish, you can also use EF Code First to create the model. The model in this case looks like this:

Then add a controller named CustomerController in the Controllers folder. This controller will act as our API or service and will have actions to deal with GET, POST, PUT and DELETE methods. The following code shows how this controller looks like:

public class CustomerController : Controller
{

    [HttpGet]
    public JsonResult GetAll() 
    {
        NorthwindEntities db = new NorthwindEntities();
        List<Customer> data = db.Customers.ToList();
        return Json(data,JsonRequestBehavior.AllowGet);
    }

    [HttpGet]
    public ActionResult GetById(string id)
    {
        using (NorthwindEntities db = new NorthwindEntities())
        {
            Customer data = db.Customers.Find(id);
            return Json(data,JsonRequestBehavior.AllowGet);
        }
    }

    [HttpPost]
    public ActionResult Post(Customer obj)
    {
        using (NorthwindEntities db = new NorthwindEntities())
        {
            db.Entry<Customer>(obj).State = EntityState.Added;
            db.SaveChanges();
            return Json("Customer added successfully!");
        }
    }

    [HttpPut]
    public ActionResult Put(string id,Customer obj)
    {
        using (NorthwindEntities db = new NorthwindEntities())
        {
            db.Entry<Customer>(obj).State = EntityState.Modified;
            db.SaveChanges();
            return Json("Customer added successfully!");
        }
    }

    [HttpDelete]
    public ActionResult Delete(string id)
    {
        using (NorthwindEntities db = new NorthwindEntities())
        {
            Customer obj = db.Customers.Find(id);
            db.Entry<Customer>(obj).State = EntityState.Deleted;
            db.SaveChanges();
            return Json("Customer added successfully!");
        }
    }
}

The Customer controller contains five actions as described below:

  • Get() : This action method deals with GET verb and is decorated with [HttpGet] attribute. This method is intended to return all the data from Customers table to the caller.
  • GetById(id) : This action method takes ID as a parameter and returns a single Customer object based on its CustomerID. This method too handles GET verb as indicated by [HttpGet] attribute.
  • Post(obj) : This action method handles POST verb as indicated by [HttpPost] attribute. It accepts a Customer objet and adds it into the Customer table.
  • Put(obj) : This action method handles PUT verb as indicated by [HttpPut] attribute. It accepts a CustomerID of a Customer that is to be updated and a Customer object containing modified values.
  • Delete(obj) : This action method handles DELETE verb as indicated by [HttpDelete] attribute. It accepts a CustomerID and deletes that Customer from the database.

The code inside all the above methods is quite straightforward. What is important is - all of then return JSON data as indicated by the Json() method call in each method. Json() method accepts a .NET type and converts into its JSON representation. Also notice that GET action methods set JsonRequestBehavior to AllowGet.

As you can see the Customer controller now closely resembles a Web API as far as the overall functionality is concerned. Before you call this "API" from your client code you need to make a little addition to your web.config. So, open web.config and add the following markup to it after <system.web> section.

<system.webServer>
  <handlers>
    <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
    <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*."
          verb="*"
          type="System.Web.Handlers.TransferRequestHandler"
          preCondition="integratedMode,runtimeVersionv4.0" />
  </handlers>
</system.webServer>

Why is this markup required? That's because the default section in the machine.config doesn't include all the HTTP verbs needed by our application. By removing the ExtensionlessUrlHandler-Integrated-4.0 entry and redefining it with verb = * ensures that all the verbs needed (GET, POST, PUT and DELETE) are allowed.

Rick Strahl explains in detail about this issue in his nice blog post. You can read Rick's complete post here to understand the issue and the solution outlined by him.

Ok. Now that your Customer controller is ready you can call it through some client. You can either use jQuery Ajax or HttpClient to call the API you just built. For the sake of simplicity I am going to use jQuery Ajax.

To add the client code, add another controller - HomeController - and Index view. The Index view will contain all the jQuery code required to call Customer controller. The Index view looks like this:

The HTML markup of the Index view is shown below:

<h1>Customer Add / Update / Delete</h1>
<form>
    <table border="1" cellpadding="10">
        <tr>
            <td>Customer ID :</td>
            <td>
                <select id="customerid"></select>
                    OR 
                <input type="text" id="newcustomerid" />
            </td>
        </tr>
        <tr>
            <td>Company Name :</td>
            <td><input type="text" id="companyname" /></td>
        </tr>
        <tr>
            <td>Contact Name :</td>
            <td><input type="text" id="contactname" /></td>
        </tr>
        <tr>
            <td>Country :</td>
            <td><input type="text" id="country" /></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="button" value="Insert" id="insert" />
                <input type="button" value="Update" id="update" />
                <input type="button" value="Delete" id="delete" />
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <div id="message"></div>
            </td>
        </tr>
    </table>
</form>

I won't discuss all the jQuery code in detail. So, let's see the code that calls Get() as soon as the page loads in the browser:

var options = {};
options.url = "/customer/getall";
options.type = "GET";
options.dataType = "json";
options.contentType = "application/json";
options.success = function (customers) {
    for (var i = 0; i < customers.length; i++) {
        $("#customerid").append("<option>" + 
          customers[i].CustomerID + "</option>");
    }
};
options.error = function () {
    alert("Error calling WebAPI");
};
$.ajax(options);

As you can see the URL points to /customer/getall. Notice that unlike Web API where you won't need to mention action name as the part of the URL, here you need to specify the action name because we are not using any auto-mapping between verbs and actions. The type is set to GET.

If you wish to invoke Get(id) action then your code will need the following changes:

 ...
options.url = "/customer/getbyid/" + $("#customerid").val();
options.type = "GET";
...

The URL now includes action name as well as CustomerID value. This way GetById() will receive the id parameter correctly.

Calling Post() method requires this code:

...
options.url = "/customer/post";
options.type = "POST";
options.data = JSON.stringify({
    CustomerID: $("#newcustomerid").val(),
    CompanyName: $("#companyname").val(),
    ContactName: $("#contactname").val(),
    Country: $("#country").val()
});
...

To call Put() you need this code:

...
options.url = "/customer/put/" + $("#customerid").val() ;
options.type = "PUT";
options.data = JSON.stringify({
    CustomerID: $("#customerid").val(),
    CompanyName: $("#companyname").val(),
    ContactName: $("#contactname").val(),
    Country: $("#country").val()
});
...

Finally, the Delete() action can be called using the following code:

...
options.url = "/customer/delete/" + $("#customerid").val();
options.type = "DELETE";
...

That's it! You can run the Index view and test whether actions of Customer controller get called as expected. In our example we used the default MVC routing. However, you can also tweak the routing to create your "API" specific routes.




Bipin Joshi is a software consultant, an author and a yoga mentor having 21+ years of experience in software development. He conducts online courses in ASP.NET MVC / Core, jQuery, and Design Patterns. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced Yoga way of life he also teaches Meditation to interested individuals. To know more about him click here.

Get connected : Twitter  Facebook  Google+  LinkedIn

Posted On : 06 May 2015



Tags : ASP.NET MVC C# jQuery JavaScript