Chuck Norris doesn't use web standards. The web conforms to him.
Modern web applications sometimes need to provide an API. In some cases the API is needed for internal use by the web application, as a page is being downloaded and subsequent JavaScript AJAX calls communicate with the server through API calls. In other cases, an API is offered to partners and third party developers, prominent examples being the Twitter and Facebook APIs.
.NET has provided tools and techniques for creating API-like solutions since version 1.0, using XML web services and Remoting, and later on with WCF. While WCF is still a viable technology choice and has its place, mostly in enterprise solutions, the industry and the developer community took a different, should I say simpler and more open approach to writing APIs.
The idea was to utilize the HTTP protocol to create what is known as REST API. The HTTP protocol defines various methods, such as GET, POST, PUT and DELETE, which can be used to specify the intent of an API call. An HTTP request may also contain parameters in the URL, include metadata in HTTP headers, and some HTTP methods can also carry a payload (through the request body). It seems that the HTTP protocol, while simple, is rich enough to provide the necessary building blocks for an API. Perhaps more importantly, in today's technology landscape, where there is no shortage of device form factors and platform choices, we need an API that can easily be consumed by any client. Due to the ubiquity of the HTTP protocol, REST APIs can be utilized by pretty much any form of client, no matter if it's a web application within a desktop browser, or a native app running on a smartphone.
While building a REST API in .NET has always been possible, simply due to the fact that .NET has always supported HTTP, there hasn't been any out-of-the-box way to easily do so. But REST assured (pun intended) .NET continuously evolves, and as of ASP .NET MVC 4 there is a built-in way to easily create a REST API. OK, let's get down to business. Enter MVC 4 Web API.
Creating a Simple Web API
Let's build a Web API project to maintain a database of Chuck Norris facts. First we need to create a new ASP .NET MVC 4 Web Application and select Web API as the project template.
Let's define the following ChuckFact model, with Id, Text and Rating:
public class ChuckFact
{
public int Id { get; set; }
public string Text { get; set; }
public int Rating { get; set; }
}
Now let's create a new controller, name it ChuckController and select Empty API Controller as the template.
For the sake of simplicity, we will store the facts in a collection within the controller for now. In real life scenarios, we should probably use a database...
public class ChuckController : ApiController
{
private static List<ChuckFact> facts = new List<ChuckFact>()
{
new ChuckFact { Id = 1, Rating = 4,
Text = "Chuck Norris doesn't call the wrong number. You answer the wrong phone." },
new ChuckFact { Id = 2, Rating = 3,
Text = "Chuck Norris counted to infinity. Twice." },
new ChuckFact { Id = 3, Rating = 4,
Text = "When Alexander Bell invented the telephone he had 3 missed calls from Chuck Norris." },
};
}
As it stands now our controller doesn't do anything because it has no methods. Let's start building it.
Supporting GET Requests
Let's add the following method to the controller:
public IEnumerable<ChuckFact> GetAllFacts()
{
return facts;
}
The
GetAllFacts
method, as it name implies, simply returns all facts. Believe it or not, this tiny method is now part of our API offering. Build the project and browse to http://localhost:[port]/api/chuck (replace [port] with the actual port for your application) and you should get something like the following:
Note: if you use a different browser (I used Chrome), you might get the result in JSON instead of XML. More on that in part 3 of this tutorial, when we discuss content negotiation.
Let's talk about the URL: http://localhost:[port]/api/chuck. The
chuck
token in the URL specifies the controller class' name, sans the Controller suffix. This is how MVC knows to instantiate and invoke our controller. Don't worry about the api
token, I'll cover it when we discuss routes. One interesting question however, is how does MVC know which method of the controller to invoke? Right now we have only one method but we could (and will soon) add more methods. The answer is that by default MVC looks for a method whose name starts with the HTTP request's method (i.e. GET), and which accepts the parameters specified in the request and supported by the route (more on routes below). Since browsers always make an HTTP GET request when you type a URL in their address bar, MVC could find a method that fits the bill. GetAllFacts
starts with Get and has no parameters.
Let's add another GET method:
public ChuckFact GetFactByID(int id)
{
ChuckFact fact = (from f in facts where f.Id == id select f).FirstOrDefault();
if (fact == null)
throw new HttpResponseException(this.Request.CreateResponse(HttpStatusCode.NotFound));
return fact;
}
The
GetFactByID
method takes a parameter, id
, and returns the fact that matches it, if any. Keep in mind that in Web API, we rely on the HTTP protocol to relay any error or status codes. Therefore, if the fact isn't found, we need to return a Not Found (404) status code. We do that by throwing an HttpResponseException
that contains a response with the NotFound
status code.
Now if we open the browser and navigate to http://localhost:[port]/api/chuck/1, we'll get the Chuck fact that has an id of 1. Based on the route, MVC can tell that the last token in the URL is an id so it looks for a controller method that starts with Get and takes an id, which matches
GetFactByID
.Supporting POST Requests
Now let's add a method that allows creating a new fact. By convention we should use POST for that.
public HttpResponseMessage PostFact(ChuckFact fact)
{
if (!this.ModelState.IsValid)
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest));
facts.Add(fact);
HttpResponseMessage response = this.Request.CreateResponse(HttpStatusCode.Created, fact);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = fact.Id }));
return response;
}
Alright, let's talk about this method. When implementing a POST method, there isn't much to return (as opposed to a GET method whose whole raison d'etre is to return something). However, we should still return an HTTP response that indicates what happened. This is why the return type of the
PostFact
method is HttpResponseMessage
.
The method first checks if the model is valid, and if it isn't, the method aborts the request and returns a Bad Request (400) status code. Our
ChuckFact
model doesn't currently specify any validity constraints, but it's good practice to check for validity now, in case we add such constraints in the future.
Next we store the new fact, and are ready to return a response. While not mandated by MVC Web API, RFC 2616 stipulates that we should return a status code of Created (201) if a resource has been created on the server and provide a URL to the newly created resource through a
Location
HTTP header. We therefore create a response and specify the URL that can be used to fetch the new Chuck fact. Note that we do not create the URL manually. Instead, we invoke the Link
method and provide it with the name of the route and the parameters value. The default route created by the project template is named "DefaultApi" and it takes an optional parameter for the id.
To test this method, we can't use a browser (at least not in the manner we are used to). Typing a URL in the address bar generates a GET request, but now we want to test a POST request. There are several tools that can assist us here. I chose to use the ever popular Fiddler, which can be downloaded for free.
In the drop down left to the URL, I selected POST. You can use Fiddler to test requests of any HTTP method. For the URL, I specified the path to our controller. I wanted to use the JSON format to specify the new Chuck fact, so I had to include the Content-Type request header and set it to application/json. I could omit this header, in which case I would have to URL encode the new fact (even though it's in the request body rather than in the URL). And finally the request body has the actual new fact, in JSON format. Now we can click the Execute button to send the request to the server. We can then see the request on the left pane of Fiddler, and select it to easily inspect what was sent and what was received.
The response was 201 (Created) and we can also spot the Location header with the URL to the new Chuck fact (at the very bottom). Pasting this URL in a browser (or using Fiddler to issue the GET request) should return the newly added fact. Everything went well, Chuck would be proud!
Supporting PUT and DELETE Requests
To update an entity, we typically use the PUT HTTP method. To delete an entity we use the DELETE HTTP method (shocking, I know). Here are the corresponding controller methods:
public HttpResponseMessage PutFact(ChuckFact fact)
{
if (!this.ModelState.IsValid)
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest));
ChuckFact existFact = (from f in facts where f.Id == fact.Id select f).FirstOrDefault();
if (existFact == null)
throw new HttpResponseException(this.Request.CreateResponse(HttpStatusCode.NotFound));
facts.Remove(existFact);
facts.Add(fact);
return this.Request.CreateResponse(HttpStatusCode.NoContent);
}
public HttpResponseMessage DeleteFact(int id)
{
ChuckFact fact = (from f in facts where f.Id == id select f).FirstOrDefault();
if (fact == null)
throw new HttpResponseException(this.Request.CreateResponse(HttpStatusCode.NotFound));
facts.Remove(fact);
return this.Request.CreateResponse(HttpStatusCode.NoContent);
}
The methods are self-explanatory. The only thing worth mentioning is that upon success, they return a status code of NoContent (204). This is one of the options stipulated by RFC 2616 when PUT or DELETE are successful. The other option is OK (200), which should also contain the actual entity (the updated / deleted Chuck fact), as was done in the Post method.
A Few Words About Routes
The project template for the Web API project contains a file named
WebApiConfig.cs
in the App_Start
folder. This file has the WebApiConfig
class and its Register
method, which is called from the Application_Start
method in Global.asax
. By default, the method performs the following route configuration:config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
The
name
of the route is an arbitrary string, but recall that we used this name from in thePostFact
method when we had to to create a URL. routeTemplate
is probably the most interesting parameter. It defines that the route must have the api
token and it has placeholders for the controller name and the id parameter. Note that all of the URLs we used in our testing were in the form of http://localhost:[port]/api/chuck or http://localhost:[port]/api/chuck/[id]. The reason we can omit the id lies in the last parameter of the routing config, defaults
. It specifies that the id parameter is optional.
Note that any parameters of simple types that are not part of the route, or are optional, can be specified in a query string form. For example, http://localhost:[port]/api/chuck/1 is equivalent to http://localhost:[port]/api/chuck/?id=1. This also means that if you're OK with using query strings instead of URL path tokens for your Web API parameters, you don't need to create additional routes if you have methods that require parameters other than id. For example, here is a method that returns all Chuck facts of a certain minimum rating:
public IEnumerable<ChuckFact> GetFactsByRating(int minRating)
{
return from f in facts where f.Rating >= minRating select f;
}
We can invoke this API method with a URL such as http://localhost:[port]/api/chuck/?minrating=4. The part up to the query string matches our route above (it doesn't contain an id and that's fine as the id is optional), so MVC knows to invoke our controller. It then sees that this is a GET request and it contains the
minrating
parameter in the query string, so it looks for a method whose name starts with Get and which takes a minRating
parameter (case insensitive). The GetFactByRating
method matches the criteria and therefore gets invoked.Summary
In this tutorial we learned how to use MVC Web API to create a REST API that supports CRUD (create, read, update, delete), and also educated ourselves on a few important Chuck Norris facts. Our Web API can now be used by any client whose platform supports HTTP, which means pretty much any client... In the next parts of this tutorial, I will discuss routes, parameters and methods in more detail, as well as ways to customize the Web API framework, and will even show how to host Web API without using IIS. Stay tuned.
P.S - Chuck Norris can make HTTP requests without network connectivity.