The majority of ASP.NET websites are built using the ASP.NET Web Forms model. It provides a simple, stateful abstraction over the stateless HTTP protocol, and focuses on server-side processing. Recently, websites have focused more on a client-side model with the use of JavaScript, AJAX, and REST-style services. With the introduction of the ASP.NET Web API in ASP.NET 4.5, it is now possible to add advanced client-side functionality to existing sites, while retaining many of the benefits of the Web Forms model.
A Simple Web Forms Application
To understand how to supercharge an existing Web Forms application with the ASP.NET Web API, let's first take a brief look at a Web Forms app that I'll use as an example. Figure 1 shows the list view of a simple Tasks application, which was created using the new Web Forms template shipped as part of Visual Studio 2012.
Figure 1: The Tasks page in list mode.
The Add New Task button and the Edit links in the Grid allow the user to add new tasks or edit the relevant existing tasks.
Assume that you have been given a new requirement: Instead of just displaying the "Is Complete" status of the task, the application should display a checkbox and allow users to update the status of the task (rather than having to click "Edit" and be redirected to the edit page).
You could fulfill this requirement by using a checkbox column in the ASP.NET GridView
control and switching the GridView
into Edit mode, but this would require the use of server post-backs and would trigger a complete refresh of the grid whenever a task is updated. Instead, let's use some JavaScript in the client and the ASP.NET Web API to demonstrate how to make the updates more modern and responsive.
Adding the ASP.NET Web API using Nuget
ASP.NET Web API was released in conjunction with the .NET 4.5/Visual Studio 2012 release, but it doesn't actually require .NET 4.5. Instead, it's built against the earlier .NET 4.0 release. To add Web API support to an existing project, we can use Nuget to install the latest version, either via the website or via one of the integration points such as Manage Nuget Packages in Visual Studio (Figure 2).
Figure 2: Adding the Web API using Nuget.
This ensures you have the relevant components installed and will allow you to update those components if newer versions of the ASP.NET Web API are released. Note that, depending on what features of the ASP.NET Web API you want to use, you may still have to add some assembly references manually.
Adding a Web API Controller
The first step in adding our new feature using the Web API is to create a Web API Controller
class (Figure 3). The Web API has some similarities to ASP.NET MVC in that the new class extends the ApiController
base class and, by convention, is called TasksController
. I will show how this convention works when we review the URI that needs to be called for our methods to be executed.
Figure 3: Adding a new Web API controller class.
In the controller class, I will create a single method, UpdateStatus
, that updates the task and changes the value of the IsComplete
property, taking the ID as a single parameter (Listing One). As this is an update operation, I follow the convention of making the action method work with the PUT
HTTP method (verb). I indicate that by using the HttpPut
attribute on the method.
Listing One: The UpdateStatus Method
[HttpPut] public HttpResponseMessage UpdateStatus(int id) { try { var db = new TasksContext(); Task task = (from t in db.Tasks where t.TaskID == id select t).SingleOrDefault(); if (task == null) { return Request.CreateResponse(HttpStatusCode.NotFound); ; } task.IsComplete = !task.IsComplete; db.SaveChanges(); return Request.CreateResponse(HttpStatusCode.OK); } catch (Exception exc) { return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, exc); } }
In this simple example, I am the using Entity Framework for persistence, and the first few lines of this method retrieve the task to be updated. As long as I have a valid task (that is, the task is not null), I simply change the value of the IsComplete
property and save the changes. This is fairly straightforward Entity Framework code.
If the update is successful, I create a new HttpResponseMessage
with the OK status code (200
), and this response is sent back to the caller. If there are exceptions, I create an HttpResponseMessage
with the appropriate status code and, in the case of an Internal Server Error, details of the exception.
Adding Routing Information to Application_Start
Now that I have a Web API Controller
class to route to, I need to set up the routes in the Application_Start
event handler, so the Web API can execute the correct method. ASP.NET Web API routes are similar to ASP.NET MVC Routes. Two example routes are shown in Listing Two.
Listing Two: Adding ASP.NET Web API Routes to Application_Start.
RouteTable.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); RouteTable.Routes.MapHttpRoute( name: "TasksApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } );
The first route is the default Web API route. Using this route, it's possible to use URIs like api/Tasks/5
.
The {controller}
place-holder routes control to the relevant Web API controller class. In the aforementioned example, it will be routed to the TasksController
, and "api/products"
would route to the ProductsController
.
If used with the GET
HTTP method (or verb), this URI is mapped to an action method that starts with the prefix Get
, such as GetTask
or GetTaskById
, and the parameter (5
) is passed as the ID parameter of the action method. Similarly, if used with the DELETE
HTTP method, this URI is mapped to an action method that starts with the prefix Delete
, such as DeleteTask
or DeleteTaskById
. Note that as long as the method has the correct prefix, control is routed to the correct action method regardless of the rest of the name. Issues arise only if the routing is ambiguous because there are two or more methods that start with the same HTTP method prefix: GetTaskById
and GetTaskByCategory
.
I could have followed this convention and used the Put
prefix in the name of the action method I created in Listing One, but the action method does not contain any of these HTTP method prefixes. I can handle this by defining a second route to send control explicitly to a specific action. In this case, the {action}
place-holder maps to the name of the action method, so the URI would need to be api/Tasks/UpdateStatus/5
.
This URI will now route to the UpdateStatus
method of the TasksController
, passing the value 5
as the taskId
parameter.