HTTP is not just for serving up web pages. It is also a powerful platform for building APIs that expose services and data. HTTP is simple, flexible, and ubiquitous. Almost any platform that you can think of has an HTTP library, so HTTP services can reach a broad range of clients, including browsers, mobile devices, and traditional desktop applications.
ASP.NET Web API is a framework for building web APIs on top of the .NET Framework. In this tutorial, you will use ASP.NET Web API to create a web API that returns a list of products. The front-end web page uses jQuery to display the results.
- Visual Studio 2012
- Visual Studio Express 2012 for Web
- Visual Studio 2010 with ASP.NET MVC 4 installed.
- Visual Web Developer 2010 Express with ASP.NET MVC 4 installed.
Create a Web API Project
Start Visual Studio and select New Project from the Start page. Or, from the File menu, select New and thenProject.
In the Templates pane, select Installed Templates and expand the Visual C# node. Under Visual C#, select Web. In the list of project templates, select ASP.NET MVC 4 Web Application. Name the project "HelloWebAPI" and clickOK.
In the New ASP.NET MVC 4 Project dialog, select Web API and click OK.
Adding a Model
A model is an object that represents the data in your application. ASP.NET Web API can automatically serialize your model to JSON, XML, or some other format, and then write the serialized data into the body of the HTTP response message. As long as a client can read the serialization format, it can deserialize the object. Most clients can parse either XML or JSON. Moreover, the client can indicate which format it wants by setting the Accept header in the HTTP request message.
Let's start by creating a simple model that represents a product.
If Solution Explorer is not already visible, click the View menu and select Solution Explorer. In Solution Explorer, right-click the Models folder. From the context menu, select Add then select Class.
Name the class "Product". Next, add the following properties to the
Product
class.namespace HelloWebAPI.Models { public class Product { public int Id { get; set; } public string Name { get; set; } public string Category { get; set; } public decimal Price { get; set; } } }
Adding a Controller
A controller is an object that handles HTTP requests. The New Project wizard created two controllers for you when it created the project. To see them, expand the Controllers folder in Solution Explorer.
HomeController
is a traditional ASP.NET MVC controller. It is responsible for serving HTML pages for the site, and is not directly related to Web API.ValuesController
is an example WebAPI controller.
Note If you have worked with ASP.NET MVC, then you are already familiar with controllers. They work similarly in Web API, but controllers in Web API derive from the ApiController class instead of Controllerclass. The first major difference you will notice is that actions on Web API controllers do not return views, they return data.
Go ahead and delete
ValuesController
, by right-clicking the file in Solution Explorer and selecting Delete.
Add a new controller, as follows:
In Solution Explorer, right-click the the Controllers folder. Select Add and then select Controller.
In the Add Controller wizard, name the controller "ProductsController". In the Template drop-down list, selectEmpty API Controller. Then click Add.
The Add Controller wizard will create a file named ProductsController.cs in the Controllers folder.
It is not necessary to put your contollers into a folder named Controllers. The folder name is not important; it is simply a convenient way to organize your source files.
If this file is not open already, double-click the file to open it. Add the following implementation:
namespace HelloWebAPI.Controllers { using HelloWebAPI.Models; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; public class ProductsController : ApiController { Product[] products = new Product[] { new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } }; public IEnumerable<Product> GetAllProducts() { return products; } public Product GetProductById(int id) { var product = products.FirstOrDefault((p) => p.Id == id); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } return product; } public IEnumerable<Product> GetProductsByCategory(string category) { return products.Where( (p) => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase)); } } }
To keep the example simple, products are stored in a fixed array inside the controller class. Of course, in a real application, you would query a database or use some other external data source.
The controller defines three methods that return either single products or lists of products:
- The
GetAllProducts
method returns the entire list of products as an IEnumerable<Product> type. - The
GetProductById
method looks up a single product by its ID. - The
GetProductsByCategory
method returns all products with a specified category.
That's it! You have a working web API. Each method on the controller maps to a URI:
Controller Method | URI |
---|---|
GetAllProducts | /api/products |
GetProductById | /api/products/id |
GetProductsByCategory | /api/products/?category=category |
A client can invoke the method by sending an HTTP GET request to the URI. Later, we'll look at how this mapping is done. But first, let's try it out.
Calling the Web API from the Browser
You can use any HTTP client to invoke your web API. In fact, you can call it directly from a web browser.
In Visual Studio, choose Start Debugging from the Debug menu, or use the F5 keyboard shortcut. The ASP.NET Development Server will start, and a notification will appear in the bottom corner of the screen showing the port number that it is running under. By default, the Development Server chooses a random port number.
Visual Studio will then automatically open a browser window whose URL points to http//www.localhost:xxxx/, wherexxxx is the port number. The home page should look like the following:
This home page is an ASP.NET MVC view, returned by the
HomeController
class. To invoke the web API, we must use one of the URIs listed previously. For example, to get the list of all products, browse tohttp://localhost:xxxx/api/products/. (Replace "xxxx" with the actual port number.)
The exact result depends on which web browser you are using. Internet Explorer will prompt you to open or save a "file" named products.
This "file" is actually just the body of the HTTP response. Click Open. In the Open with dialog, select Notepad. ClickOK, and when prompted, click Open. The file should contain a JSON representation of the array of products:
[{"Id":1,"Name":"Tomato soup","Category":"Groceries","Price":1.0},{"Id":2,"Name": "Yo-yo","Category":"Toys","Price":3.75},{"Id":3,"Name":"Hammer","Category": "Hardware","Price":16.99}]
Mozilla Firefox, on the other hand, will display the list as XML in the browser.
The reason for the difference is that Internet Explorer and Firefox send different Accept headers, so the web API sends different content types in the response.
Now try browsing to these URIs:
- http://localhost:xxxx/api/products/1
- http://localhost:xxxx/api/products?category=hardware
The first should return the entry with ID equal to 1. The second should return a list of all the products with Category equal to "hardware" (in this case, a single item).
Calling the Web API with Javascript and jQuery
In the previous section, we invoked the web API directly from the browser. But most web APIs are meant to be consumed programmatically by a client application. So let's write a simple javascript client.
In Solution Explorer, expand the Views folder, and expand the Home folder under that. You should see a file named Index.cshtml. Double-click this file to open it.
Index.cshtml renders HTML using the Razor view engine. However, we will not use any Razor features in this tutorial, because I want to show how a client can access the service using plain HTML and Javascript. Therefore, go ahead and delete everything in this file, and replace it with the following:
<!DOCTYPE html> <html lang="en"> <head> <title>ASP.NET Web API</title> <link href="../../Content/Site.css" rel="stylesheet" /> <script src="../../Scripts/jquery-1.7.1.min.js" type="text/javascript"> // TODO Add script </script> </head> <body id="body" > <div class="main-content"> <div> <h1>All Products</h1> <ul id="products"/> </div> <div> <label for="prodId">ID:</label> <input type="text" id="prodId" size="5"/> <input type="button" value="Search" onclick="find();" /> <p id="product" /> </div> </div> </body> </html>
Getting a List of Products
To get a list of products, send an HTTP GET request to "/api/products". You can do this with jQuery as follows:
<script type="text/javascript"> $(document).ready(function () { // Send an AJAX request $.getJSON("api/products/", function (data) { // On success, 'data' contains a list of products. $.each(data, function (key, val) { // Format the text to display. var str = val.Name + ': $' + val.Price; // Add a list item for the product. $('<li/>', { text: str }) .appendTo($('#products')); }); }); }); </script>
The getJSON function sends the AJAX request. The response will be an array of JSON objects. The second parameter to getJSON is a callback function that is invoked when the request successfully completes.
Getting a Product By ID
To get a product by ID, send an HTTP GET request to "/api/products/id", where id is the product ID. Add the following code to the script block:
function find() { var id = $('#prodId').val(); $.getJSON("api/products/" + id, function (data) { var str = data.Name + ': $' + data.Price; $('#product').text(str); }) .fail( function (jqXHR, textStatus, err) { $('#product').text('Error: ' + err); }); }
Again, we call the jQuery getJSON function to send the AJAX request, but this time we use the ID to construct the request URI. The response from this request is a JSON representation of a single Product object.
The following code shows the complete Index.cshtml file.
<!DOCTYPE html> <html lang="en"> <head> <title>ASP.NET Web API</title> <link href="../../Content/Site.css" rel="stylesheet" /> <script src="../../Scripts/jquery-1.7.1.min.js" type="text/javascript"></script> <script type="text/javascript"> $(document).ready(function () { // Send an AJAX request $.getJSON("api/products/", function (data) { // On success, 'data' contains a list of products. $.each(data, function (key, val) { // Format the text to display. var str = val.Name + ': $' + val.Price; // Add a list item for the product. $('<li/>', { text: str }) .appendTo($('#products')); }); }); }); function find() { var id = $('#prodId').val(); $.getJSON("api/products/" + id, function (data) { var str = data.Name + ': $' + data.Price; $('#product').text(str); }) .fail( function (jqXHR, textStatus, err) { $('#product').text('Error: ' + err); }); } </script> </head> <body id="body" > <div class="main-content"> <div> <h1>All Products</h1> <ul id="products"/> </div> <div> <label for="prodId">ID:</label> <input type="text" id="prodId" size="5"/> <input type="button" value="Search" onclick="find();" /> <p id="product" /> </div> </div> </body> </html>
Running the Application
Press F5 to start debugging the application. The web page should look like the following:
It's not the best in web design, but it shows that our HTTP service is working. You can get a product by ID by entering the ID in the text box:
If you enter an invalid ID, the server returns an HTTP error:
Understanding Routing
This section explains briefly how ASP.NET Web API maps URIs to controller methods. For more detail, see Routing in ASP.NET Web API.
For each HTTP message, the ASP.NET Web API framework decides which controller receives the request by consulting a route table. When you create a new Web API project, the project contains a default route that looks like this:
/api/{controller}/{id}
The {controller} and {id} portions are placeholders. When the framework sees a URI that matches this pattern, it looks for a controller method to invoke, as follows:
- {controller} is matched to the controller name.
- The HTTP request method is matched to the method name. (This rule applies only to GET, POST, PUT, and DELETE requests.)
- {id}, if present, is matched to a method parameter named id.
- Query parameters are matched to parameter names when possible
Here are some example requests, and the action that results from each, given our current implementation.
URI | HTTP Method | Action |
---|---|---|
/api/products | GET | GetAllProducts() |
/api/products/1 | GET | GetProductById(1) |
/api/products?category=hardware | GET | GetProductsByCategory("hardware") |
In the first example, "products" matches the controller named ProductsController. The request is a GET request, so the framework looks for a method on ProductsController whose name starts with "Get...". Furthermore, the URI does not contain the optional {id} segment, so the framework looks for a method with no parameters. The ProductsController::GetAllProducts method meets all of these requirements.
The second example is the same, except that the URI contains the {id} portion. Therefore, the frameworks calls GetProduct, which takes a parameter named id. Also, notice that value "5" from the URI is passed in as the value of the id parameter. The framework automatically converts the "5" to an int type, based on the method signature.
Here are some requests that cause routing errors:
URI | HTTP Method | Action |
---|---|---|
/api/products/ | POST | 405 Method Not Allowed |
/api/users/ | GET | 404 Not Found |
/api/products/abc | GET | 400 Bad Request |
In the first example, the client sends an HTTP POST request. The framework looks for a method whose name starts with "Post..." However, no such method exists in ProductsController, so the framework returns an HTTP response with status 405, Method Not Allowed.
The request for /api/users fails because there is no controller named UsersController. The request forapi/products/abc fails because the URI segment "abc" cannot be converted into the id parameter, which expects anint value.