I am a huge fan of Ajax. If you want to create a great experience for the users of your website – regardless of whether you are building an ASP.NET MVC or an ASP.NET Web Forms site — then you need to use Ajax. Otherwise, you are just being cruel to your customers.
We use Ajax extensively in several of the ASP.NET applications that my company, Superexpert.com, builds. We expose data from the server as JSON and use jQuery to retrieve and update that data from the browser.
One challenge, when building an ASP.NET website, is deciding on which technology to use to expose JSON data from the server. For example, how do you expose a list of products from the server as JSON so you can retrieve the list of products with jQuery? You have a number of options (too many options) including ASMX Web services, WCF Web Services, ASHX Generic Handlers, WCF Data Services, and MVC controller actions.
Fortunately, the world has just been simplified. With the release of ASP.NET 4 Beta, Microsoft has introduced a new technology for exposing JSON from the server named the ASP.NET Web API. You can use the ASP.NET Web API with both ASP.NET MVC and ASP.NET Web Forms applications.
The goal of this blog post is to provide you with a brief overview of the features of the new ASP.NET Web API. You learn how to use the ASP.NET Web API to retrieve, insert, update, and delete database records with jQuery. We also discuss how you can perform form validation when using the Web API and use OData when using the Web API.
Creating an ASP.NET Web API Controller
The ASP.NET Web API exposes JSON data through a new type of controller called an API controller. You can add an API controller to an existing ASP.NET MVC 4 project through the standard Add Controller dialog box.
Right-click your Controllers folder and select Add, Controller. In the dialog box, name your controller MovieController and select the Empty API controller template:
A brand new API controller looks like this:
using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Web.Http; namespace MyWebAPIApp.Controllers { public class MovieController : ApiController { } }
An API controller, unlike a standard MVC controller, derives from the base ApiController class instead of the base Controller class.
Using jQuery to Retrieve, Insert, Update, and Delete Data
Let’s create an Ajaxified Movie Database application. We’ll retrieve, insert, update, and delete movies using jQuery with the MovieController which we just created. Our Movie model class looks like this:
namespace MyWebAPIApp.Models { public class Movie { public int Id { get; set; } public string Title { get; set; } public string Director { get; set; } } }
Our application will consist of a single HTML page named Movies.html. We’ll place all of our jQuery code in the Movies.html page.
Getting a Single Record with the ASP.NET Web API
To support retrieving a single movie from the server, we need to add a Get method to our API controller:
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; using MyWebAPIApp.Models; namespace MyWebAPIApp.Controllers { public class MovieController : ApiController { public Movie GetMovie(int id) { // Return movie by id if (id == 1) { return new Movie { Id = 1, Title = "Star Wars", Director = "Lucas" }; } // Otherwise, movie was not found throw new HttpResponseException(HttpStatusCode.NotFound); } } }
In the code above, the GetMovie() method accepts the Id of a movie. If the Id has the value 1 then the method returns the movie Star Wars. Otherwise, the method throws an exception and returns 404 Not Found HTTP status code.
After building your project, you can invoke the MovieController.GetMovie() method by entering the following URL in your web browser address bar: http://localhost:[port]/api/movie/1 (You’ll need to enter the correct randomly generated port).
In the URL api/movie/1, the first “api” segment indicates that this is a Web API route. The “movie” segment indicates that the MovieController should be invoked. You do not specify the name of the action. Instead, the HTTP method used to make the request – GET, POST, PUT, DELETE — is used to identify the action to invoke.
The ASP.NET Web API uses different routing conventions than normal ASP.NET MVC controllers. When you make an HTTP GET request then any API controller method with a name that starts with “GET” is invoked. So, we could have called our API controller action GetPopcorn() instead of GetMovie() and it would still be invoked by the URL api/movie/1.
The default route for the Web API is defined in the Global.asax file and it looks like this:
routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } );
We can invoke our GetMovie() controller action with the jQuery code in the following HTML page:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Get Movie</title> </head> <body> <div> Title: <span id="title"></span> </div> <div> Director: <span id="director"></span> </div> <script type="text/javascript" src="Scripts/jquery-1.6.2.min.js"></script> <script type="text/javascript"> getMovie(1, function (movie) { $("#title").html(movie.Title); $("#director").html(movie.Director); }); function getMovie(id, callback) { $.ajax({ url: "/api/Movie", data: { id: id }, type: "GET", contentType: "application/json;charset=utf-8", statusCode: { 200: function (movie) { callback(movie); }, 404: function () { alert("Not Found!"); } } }); } </script> </body> </html>
In the code above, the jQuery $.ajax() method is used to invoke the GetMovie() method. Notice that the Ajax call handles two HTTP response codes. When the GetMove() method successfully returns a movie, the method returns a 200 status code. In that case, the details of the movie are displayed in the HTML page.
Otherwise, if the movie is not found, the GetMovie() method returns a 404 status code. In that case, the page simply displays an alert box indicating that the movie was not found (hopefully, you would implement something more graceful in an actual application).
You can use your browser’s Developer Tools to see what is going on in the background when you open the HTML page (hit F12 in the most recent version of most browsers). For example, you can use the Network tab in Google Chrome to see the Ajax request which invokes the GetMovie() method:
Getting a Set of Records with the ASP.NET Web API
Let’s modify our Movie API controller so that it returns a collection of movies. The following Movie controller has a new ListMovies() method which returns a (hard-coded) collection of movies:
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; using MyWebAPIApp.Models; namespace MyWebAPIApp.Controllers { public class MovieController : ApiController { public IEnumerable<Movie> ListMovies() { return new List<Movie> { new Movie {Id=1, Title="Star Wars", Director="Lucas"}, new Movie {Id=1, Title="King Kong", Director="Jackson"}, new Movie {Id=1, Title="Memento", Director="Nolan"} }; } } }
Because we named our action ListMovies(), the default Web API route will never match it. Therefore, we need to add the following custom route to our Global.asax file (at the top of the RegisterRoutes() method):
routes.MapHttpRoute( name: "ActionApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } );
This route enables us to invoke the ListMovies() method with the URL /api/movie/listmovies.
Now that we have exposed our collection of movies from the server, we can retrieve and display the list of movies using jQuery in our HTML page:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>List Movies</title> </head> <body> <div id="movies"></div> <script type="text/javascript" src="Scripts/jquery-1.6.2.min.js"></script> <script type="text/javascript"> listMovies(function (movies) { var strMovies=""; $.each(movies, function (index, movie) { strMovies += "<div>" + movie.Title + "</div>"; }); $("#movies").html(strMovies); }); function listMovies(callback) { $.ajax({ url: "/api/Movie/ListMovies", data: {}, type: "GET", contentType: "application/json;charset=utf-8", }).then(function(movies){ callback(movies); }); } </script> </body> </html>
Inserting a Record with the ASP.NET Web API
Now let’s modify our Movie API controller so it supports creating new records:
public HttpResponseMessage<Movie> PostMovie(Movie movieToCreate) { // Add movieToCreate to the database and update primary key movieToCreate.Id = 23; // Build a response that contains the location of the new movie var response = new HttpResponseMessage<Movie>(movieToCreate, HttpStatusCode.Created); var relativePath = "/api/movie/" + movieToCreate.Id; response.Headers.Location = new Uri(Request.RequestUri, relativePath); return response; }
The PostMovie() method in the code above accepts a movieToCreate parameter. We don’t actually store the new movie anywhere. In real life, you will want to call a service method to store the new movie in a database.
When you create a new resource, such as a new movie, you should return the location of the new resource. In the code above, the URL where the new movie can be retrieved is assigned to the Location header returned in the PostMovie() response.
Because the name of our method starts with “Post”, we don’t need to create a custom route. The PostMovie() method can be invoked with the URL /Movie/PostMovie – just as long as the method is invoked within the context of a HTTP POST request.
The following HTML page invokes the PostMovie() method.
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Create Movie</title> </head> <body> <script type="text/javascript" src="Scripts/jquery-1.6.2.min.js"></script> <script type="text/javascript"> var movieToCreate = { title: "The Hobbit", director: "Jackson" }; createMovie(movieToCreate, function (newMovie) { alert("New movie created with an Id of " + newMovie.Id); }); function createMovie(movieToCreate, callback) { $.ajax({ url: "/api/Movie", data: JSON.stringify( movieToCreate ), type: "POST", contentType: "application/json;charset=utf-8", statusCode: { 201: function (newMovie) { callback(newMovie); } } }); } </script> </body> </html>
This page creates a new movie (the Hobbit) by calling the createMovie() method. The page simply displays the Id of the new movie:
The HTTP Post operation is performed with the following call to the jQuery $.ajax() method:
$.ajax({ url: "/api/Movie", data: JSON.stringify( movieToCreate ), type: "POST", contentType: "application/json;charset=utf-8", statusCode: { 201: function (newMovie) { callback(newMovie); } } });
Notice that the type of Ajax request is a POST request. This is required to match the PostMovie() method.
Notice, furthermore, that the new movie is converted into JSON using JSON.stringify(). The JSON.stringify() method takes a JavaScript object and converts it into a JSON string.
Finally, notice that success is represented with a 201 status code. The HttpStatusCode.Created value returned from the PostMovie() method returns a 201 status code.
Updating a Record with the ASP.NET Web API
Here’s how we can modify the Movie API controller to support updating an existing record. In this case, we need to create a PUT method to handle an HTTP PUT request:
public void PutMovie(Movie movieToUpdate) { if (movieToUpdate.Id == 1) { // Update the movie in the database return; } // If you can't find the movie to update throw new HttpResponseException(HttpStatusCode.NotFound); }
Unlike our PostMovie() method, the PutMovie() method does not return a result. The action either updates the database or, if the movie cannot be found, returns an HTTP Status code of 404.
The following HTML page illustrates how you can invoke the PutMovie() method:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Put Movie</title> </head> <body> <script type="text/javascript" src="Scripts/jquery-1.6.2.min.js"></script> <script type="text/javascript"> var movieToUpdate = { id: 1, title: "The Hobbit", director: "Jackson" }; updateMovie(movieToUpdate, function () { alert("Movie updated!"); }); function updateMovie(movieToUpdate, callback) { $.ajax({ url: "/api/Movie", data: JSON.stringify(movieToUpdate), type: "PUT", contentType: "application/json;charset=utf-8", statusCode: { 200: function () { callback(); }, 404: function () { alert("Movie not found!"); } } }); } </script> </body> </html>
Deleting a Record with the ASP.NET Web API
Here’s the code for deleting a movie:
public HttpResponseMessage DeleteMovie(int id) { // Delete the movie from the database // Return status code return new HttpResponseMessage(HttpStatusCode.NoContent); }
This method simply deletes the movie (well, not really, but pretend that it does) and returns a No Content status code (204).
The following page illustrates how you can invoke the DeleteMovie() action:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Delete Movie</title> </head> <body> <script type="text/javascript" src="Scripts/jquery-1.6.2.min.js"></script> <script type="text/javascript"> deleteMovie(1, function () { alert("Movie deleted!"); }); function deleteMovie(id, callback) { $.ajax({ url: "/api/Movie", data: JSON.stringify({id:id}), type: "DELETE", contentType: "application/json;charset=utf-8", statusCode: { 204: function () { callback(); } } }); } </script> </body> </html>
Performing Validation
How do you perform form validation when using the ASP.NET Web API? Because validation in ASP.NET MVC is driven by the Default Model Binder, and because the Web API uses the Default Model Binder, you get validation for free.
Let’s modify our Movie class so it includes some of the standard validation attributes:
using System.ComponentModel.DataAnnotations; namespace MyWebAPIApp.Models { public class Movie { public int Id { get; set; } [Required(ErrorMessage="Title is required!")] [StringLength(5, ErrorMessage="Title cannot be more than 5 characters!")] public string Title { get; set; } [Required(ErrorMessage="Director is required!")] public string Director { get; set; } } }
In the code above, the Required validation attribute is used to make both the Title and Director properties required. The StringLength attribute is used to require the length of the movie title to be no more than 5 characters.
Now let’s modify our PostMovie() action to validate a movie before adding the movie to the database:
public HttpResponseMessage PostMovie(Movie movieToCreate) { // Validate movie if (!ModelState.IsValid) { var errors = new JsonArray(); foreach (var prop in ModelState.Values) { if (prop.Errors.Any()) { errors.Add(prop.Errors.First().ErrorMessage); } } return new HttpResponseMessage<JsonValue>(errors, HttpStatusCode.BadRequest); } // Add movieToCreate to the database and update primary key movieToCreate.Id = 23; // Build a response that contains the location of the new movie var response = new HttpResponseMessage<Movie>(movieToCreate, HttpStatusCode.Created); var relativePath = "/api/movie/" + movieToCreate.Id; response.Headers.Location = new Uri(Request.RequestUri, relativePath); return response; }
If ModelState.IsValid has the value false then the errors in model state are copied to a new JSON array. Each property – such as the Title and Director property — can have multiple errors. In the code above, only the first error message is copied over. The JSON array is returned with a Bad Request status code (400 status code).
The following HTML page illustrates how you can invoke our modified PostMovie() action and display any error messages:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Create Movie</title> </head> <body> <script type="text/javascript" src="Scripts/jquery-1.6.2.min.js"></script> <script type="text/javascript"> var movieToCreate = { title: "The Hobbit", director: "" }; createMovie(movieToCreate, function (newMovie) { alert("New movie created with an Id of " + newMovie.Id); }, function (errors) { var strErrors = ""; $.each(errors, function(index, err) { strErrors += "*" + err + "n"; }); alert(strErrors); } ); function createMovie(movieToCreate, success, fail) { $.ajax({ url: "/api/Movie", data: JSON.stringify(movieToCreate), type: "POST", contentType: "application/json;charset=utf-8", statusCode: { 201: function (newMovie) { success(newMovie); }, 400: function (xhr) { var errors = JSON.parse(xhr.responseText); fail(errors); } } }); } </script> </body> </html>
The createMovie() function performs an Ajax request and handles either a 201 or a 400 status code from the response. If a 201 status code is returned then there were no validation errors and the new movie was created.
If, on the other hand, a 400 status code is returned then there was a validation error. The validation errors are retrieved from the XmlHttpRequest responseText property. The error messages are displayed in an alert:
(Please don’t use JavaScript alert dialogs to display validation errors, I just did it this way out of pure laziness)
This validation code in our PostMovie() method is pretty generic. There is nothing specific about this code to the PostMovie() method. In the following video, Jon Galloway demonstrates how to create a global Validation filter which can be used with any API controller action:
http://www.asp.net/web-api/overview/web-api-routing-and-actions/video-custom-validation
His validation filter looks like this:
using System.Json; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; namespace MyWebAPIApp.Filters { public class ValidationActionFilter:ActionFilterAttribute { public override void OnActionExecuting(HttpActionContext actionContext) { var modelState = actionContext.ModelState; if (!modelState.IsValid) { dynamic errors = new JsonObject(); foreach (var key in modelState.Keys) { var state = modelState[key]; if (state.Errors.Any()) { errors[key] = state.Errors.First().ErrorMessage; } } actionContext.Response = new HttpResponseMessage<JsonValue>(errors, HttpStatusCode.BadRequest); } } } }
And you can register the validation filter in the Application_Start() method in the Global.asax file like this:
GlobalConfiguration.Configuration.Filters.Add(new ValidationActionFilter());
After you register the Validation filter, validation error messages are returned from any API controller action method automatically when validation fails. You don’t need to add any special logic to any of your API controller actions to take advantage of the filter.
Querying using OData
The OData protocol is an open protocol created by Microsoft which enables you to perform queries over the web. The official website for OData is located here:
For example, here are some of the query options which you can use with OData:
· $orderby – Enables you to retrieve results in a certain order.
· $top – Enables you to retrieve a certain number of results.
· $skip – Enables you to skip over a certain number of results (use with $top for paging).
· $filter – Enables you to filter the results returned.
The ASP.NET Web API supports a subset of the OData protocol. You can use all of the query options listed above when interacting with an API controller. The only requirement is that the API controller action returns its data as IQueryable.
For example, the following Movie controller has an action named GetMovies() which returns an IQueryable of movies:
public IQueryable<Movie> GetMovies() { return new List<Movie> { new Movie {Id=1, Title="Star Wars", Director="Lucas"}, new Movie {Id=2, Title="King Kong", Director="Jackson"}, new Movie {Id=3, Title="Willow", Director="Lucas"}, new Movie {Id=4, Title="Shrek", Director="Smith"}, new Movie {Id=5, Title="Memento", Director="Nolan"} }.AsQueryable(); }
If you enter the following URL in your browser:
/api/movie?$top=2&$orderby=Title
Then you will limit the movies returned to the top 2 in order of the movie Title. You will get the following results:
By using the $top option in combination with the $skip option, you can enable client-side paging. For example, you can use $top and $skip to page through thousands of products, 10 products at a time.
The $filter query option is very powerful. You can use this option to filter the results from a query. Here are some examples:
Return every movie directed by Lucas:
/api/movie?$filter=Director eq ‘Lucas’
Return every movie which has a title which starts with ‘S’:
/api/movie?$filter=startswith(Title,’S’)
Return every movie which has an Id greater than 2:
/api/movie?$filter=Id gt 2
The complete documentation for the $filter option is located here:
http://www.odata.org/developers/protocols/uri-conventions#FilterSystemQueryOption
Summary
The goal of this blog entry was to provide you with an overview of the new ASP.NET Web API introduced with the Beta release of ASP.NET 4. In this post, I discussed how you can retrieve, insert, update, and delete data by using jQuery with the Web API.
I also discussed how you can use the standard validation attributes with the Web API. You learned how to return validation error messages to the client and display the error messages using jQuery.
Finally, we briefly discussed how the ASP.NET Web API supports the OData protocol. For example, you learned how to filter records returned from an API controller action by using the $filter query option.
I’m excited about the new Web API. This is a feature which I expect to use with almost every ASP.NET application which I build in the future.
Is this an evolution of what used to be called WCF Http… or whatever other name changes it went through? Does it replace it? How about WCF Data Services which used to be THE way to provide OData services? Is there any value in using it instead of Web API (for example the query interceptors)?
Stephen – thanks, fantastic post. I’ve been looking at the web API and trying to figure out how to get started, and how it relates to WCF data services etc. etc. This blog post will save me tons of time – thank you!
Stephen, thanks for this nice and clean post.
Stilgar, WCF Web API is now ASP.Net Web API. You can find the details here: http://wcf.codeplex.com/wikipage?title=WCF%20Web%20API%20is%20now%20ASP.NET%20Web%20API
@Stilgar – ASP.NET Web API implements only a subset of OData. For example, the $inlinecount query option does not work with Web API and this option is useful for paging when you want to show the total page count. Standard ASP.NET MVC action filters can do the same work as WCF Data Services query interceptors.
Moving forward, I don’t see a big advantage to using WCF Data Services over the ASP.NET Web API. I like the ASP.NET API because it enables me to use the same validation and authentication mechanisms as the rest of my ASP.NET MVC application.
Stephen, do we have any chance to integrate the same business approach model to WCF Azure web service? Can we expose a list of products from the server as JSON with ASP.NET WEB API or it is integrated only with ASP.NET MVC and ASP.NET Web Forms applications?
@Slava — You can host Web API outside of IIS in a console application — see http://www.asp.net/web-api/overview/hosting-aspnet-web-api/self-host-a-web-api
With Azure, wouldn’t the easiest option to be to host a web role and use Web API with the web role?
Hi, I have been looking everywhere but cannot find the answer to this question. I am developing a RESTful API using WebAPI and want to do the following. Suppose you have a list of movies and each movie has a list of actors each with their own properties (eg.: age, sex, birthday, etc.). I would like to present the response of that subset of actors with URI’s inside every movie as follows:
The Artist
2011
api/actors/1
api/actors/2
Is there anyway to automatically do this using WebAPI?
Thanks,
Manuel
Really awesome post! Cool features and simple model!
CORRECTION: the xml tags were removed from my previous post but the tag that contains the “api/actors/1” content is the one I want to be automatically generated.
Great article and sample code. We passed it around to our entire department to read.
Mr. Walter, is this all that is planned as far as odata support before Web API is released and more specifically implementing things such as $inlinecount to enable paging and stuff like that. We’ve been playing around the forums of the http://www.KendoUI.com framework just to find out that its data source can’t work with the standard output of the Web API and their developers concluded the same that the Web API with its partial support for odata can’t work the way it’s written and they cited lack of $inlinecount as a reason why paging can’t be implemented for instance.
@Manuel @Vasselin — The ASP.NET Web API is still beta, so this might change before the final release. I don’t work at Microsoft, so I don’t have any special insight here. Currently, the Web API only supports OData querying syntax — just the querying syntax — it does not change the format of the data returned (Under the covers, the Web API appears to be using the standard DataContractJsonSerializer used by WCF to serialize data into JSON).
Great. But unless you use MVC this is not easy to follow. While its probably too much to ask for the examples in webforms, perhaps you can create a zipped webforms project with a link to it?
@Jeremy — Take a look at Henrik’s blog post on Web API and Web Forms at http://blogs.msdn.com/b/henrikn/archive/2012/02/23/using-asp-net-web-api-with-asp-net-web-forms.aspx
Hello Stephen – I’m hoping you can direct me to a reliable ASP-enabled hosting provider. We are struggling to find one that can support our current set up in an IIS environment: main site developed in ASP.net (SQL db) in tandem with WordPress blog utilizing a MY*SQL db. We can’t seem to find a reliable company (want them located in US) that can support both effectively with business processes in place (regular back ups, phone support). Cost is not our main concern, reliable service is … Would appreciate any insight you have. Thanks!
Hi Stephan,
This looks great. In the article you mentioned that we can use this with WebForms as well. I use webforms so it would be great to have an article with WebForms? is that a possibility?
thanks
Kamran
Great article!
Just one question. What would be the returned status code when there is an error (exception), let’s say, connecting to the database?
I would use a 400 Bad Request status code (the same status code returned for validation errors). See http://stackoverflow.com/questions/3290182/rest-http-status-codes
I’ve used already WebApi in my university project. Looks great, but compared to Java it is nothing that special.
Is it possible with using the API controllers you could all but eliminate the regular MVC controller and views from the project all together? If i can do everything from my API controller and just create regular html pages why would I bother with calling a regular controller to call a view in a typical MVC project?
@Zack — Yes, you could do everything with HTML pages and the Web API. I’d still recommend using a server-side view (a .cshtml file) instead of an HTML page because you can use partials and generate URLs using MVC helper methods in a server-side view. We follow a convention of placing all client-side templates and dialogs in separate server-side partials which makes our applications more maintainable.
@Stephen – Thanks.
Do you consider a good idea, in practice, to expose as IQueryable the whole table?
( What about a malicious user that tries to get 1.000.000 records at 5 minutes?)
@Andrei — Take a look at the ResultLimitAttribute which should enable you to limit the number of results returned http://msdn.microsoft.com/en-us/library/system.web.http.resultlimitattribute(v=vs.108).aspx
Do the APICONTROLLER is replacement of controller? I mean do we now need to create html pages and use jquery for data request?
Or controller would still be part of and API controller would be in parallel with controller just for other client applications.
Please suggest