New in ASP.NET MVC4 Beta: All About the Web API
Join the DZone community and get the full member experience.
Join For FreeThis is a high level overview of new stuff that appeared in the VS2011 release. So, let's briefly look inside. Today will take a look at changes in the Web API.
Web API
The biggest and most important change for me is integration of the Web API into ASP.NET. The Web API is most commonly known as the WCF Web API - an attempt of the .NET team to build a framework for creation of RESTfull services based on the WCF infrastructure. There were several releases of the WCF Web API, and the general feedback from community was - "it's good enough". Since then, Microsoft made a strategic change, merged WCF Web API and ASP.NET teams together, so both products were to be developed in sync. Now Web API is an integral part of ASP.NET (and does not have any the WCF prefix any more) and is called ASP.NET Web API. It is not just re-branding; it reflects changes in both the programming model and processing architecture.
Simple Start
If we create a new ASP.NET MVC4 application, we see the following change in the global.asax file.
routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } );
MapHttpRoute is a new extension method for RouteCollection. Similar to the ASP.NET MVC routing definition, it maps an HTTP request with matching URL into a corresponding API controller action. By default, they are all prefixed with "api" to do not "interfere" with ASP.NET MVC routing. It's not a problem to change that prefix to anything you want, like "services", "methods" etc.
/api/contacts/ <- Web API /api/contacts/1 <- Web API /contacts/1 <- ASP.NET MVC
The API controllers are really similar to ASP.NET MVC controllers. Instead of inheriting from the Controller class, they inherit from the ApiController class. Instead of returning ActionResult from action, in API controllers you return either IEnumerable or a concrete instance, like Product (there are some more dedicated scenarios with returning types, as we will see later).
By just looking into the example from the project template it is very easy to understand what's going on. There are some small details you should know before API controller usage.
Action methods naming
API controllers are "Convention over configuration" based. The actual HTTP verb that action handles could be specified in the method name.
public class ProductsController : ApiController { // Handles GET public IEnumerable<Product> Get() { } // Handles POST public Product Post(Product product) { } // Handles PUT public Product Put(Product product) { } // Handles DELETE public void Delete(int id) { } }
This convention applies only to GET, POST, PUT, and DELETE methods. You might be more specific in methods names, like GetProducts or DeleteProduct. The important thing is the prefix (Get, Post, Put etc.) of the method.
If you don't want to rely on name-based conventions, you can explicitly specify the target HTTP verb by decorating methods with HttpGet, HttpPut, HttpPost, or HttpDelete attributes.
public class ProductsController : ApiController { [HttpGet] public IEnumerable<Product> AllProducts() { } [HttpPost] public Product CreateProduc(Product product) { } [HttpPut] public Product UpdateProduct(Product product) { } [HttpDelete] public void DeleteProduct(int id) { } }
One handler per controller
API Controllers are different from ASP.NET MVC controllers. The main difference is that API controller handles exactly one HTTP request that belongs to a particular entity.
If you try to write the code like:
public class ProductsController : ApiController { public IEnumerable<Product> Get() { } public IEnumerable<Product> GetAnother() { } }
It would build OK, but at runtime if you try to access '/api/products', you will get an exception:
"Multiple actions were found that match the request: \u000d\u000aSystem.Collections.Generic.IEnumerable`1[MvcApplication7.Api.Controllers.Product] Get() on type MvcApplication7.Api.Controllers.ProductsController\u000d\u000aSystem.Collections.Generic.IEnumerable`1[MvcApplication7.Api.Controllers.Product] GetAnother() on type MvcApplication7.Api.Controllers.ProductsController"
This stuff is very similar to something we get in ASP.NET MVC. And you might think - why do we need this Web API at all, I can do exactly the same stuff with good-old ASP.NET MVC controllers? Not exactly. Web API has several killer features that makes it best for data oriented API's. Let's keep going.
Content Negotiation
Content Negotiation is the process for mutual agreement between client and server about the received/sent data formats. Good news here: the Web API does a lot of work for you, leaving content negotiation internals behind the scene. Let's take a brief look at how things happen.
When the client sends an HTTP request to the server, besides the query string and payload, it also provides a different kind of information. This information is placed in the HTTP header and provides the server with specific details for better understanding each other. In particular, it contains the Accept header field which specifies client preferences on response format. I'm going use Fiddler to check how Web API behaves under different conditions.
First, let's not specify the Accept field at all. So, the payload we are going to send to the server from our client (Fiddler) will be like:
GET /api/products HTTP/1.1 User-Agent: Fiddler Host: localhost:5589
For that request, we will receive a response like:
HTTP/1.1 200 OK Cache-Control: no-cache Pragma: no-cache Content-Type: application/json; charset=utf-8 Expires: -1 Server: Microsoft-IIS/7.5 X-AspNet-Version: 4.0.30319 X-SourceFiles: =?UTF-8?B?RDpcRGV2ZWxvcG1lbnRcUHJvamVjdHNcZXhwZXJpbWVudHNcTXZjQXBwbGljYXRpb243XE12Y0FwcGxpY2F0aW9uN1xhcGlccHJvZHVjdHM=?= X-Powered-By: ASP.NET Date: Mon, 19 Mar 2012 17:31:23 GMT Content-Length: 88 [{"Description":"iPad","Id":1,"Price":1000},{"Description":"iPhone","Id":2,"Price":500}]
As you can see, the default Content-Type is "application/json; charset=utf-8" -- there is a json string in the HTTP response payload.
Let's make our request a little more specific.
GET /api/products HTTP/1.1 User-Agent: Fiddler Host: localhost:5589 Accept: text/xml
The corresponsing response:
HTTP/1.1 200 OK Cache-Control: no-cache Pragma: no-cache Content-Type: text/xml; charset=utf-8 Expires: -1 Server: Microsoft-IIS/7.5 X-AspNet-Version: 4.0.30319 X-SourceFiles: =?UTF-8?B?RDpcRGV2ZWxvcG1lbnRcUHJvamVjdHNcZXhwZXJpbWVudHNcTXZjQXBwbGljYXRpb243XE12Y0FwcGxpY2F0aW9uN1xhcGlccHJvZHVjdHM=?= X-Powered-By: ASP.NET Date: Mon, 19 Mar 2012 17:33:34 GMT Content-Length: 329 <?xml version="1.0" encoding="utf-8"?><ArrayOfProduct xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Product><Id>1</Id><Description>iPad</Description><Price>1000</Price></Product><Product><Id>2</Id><Description>iPhone</Description><Price>500</Price></Product></ArrayOfProduct>
As you can see, without any code changes, we made it "adapt" to particular client needs. This is a very decent feature of the Web API and available for use straight out of the box. You even don't need to worry about it at all. Leave the content negotiation for the infrastructure.
IQueryable<T> Support
Next cool feature is IQueryable<T> support. If you need to, instead of returning "plain" IEnumerable objects from the API action, you might return IQueryable<T>. Why?
Remember the times we implemented paging & sorting with the ASP.NET MVC application. It was possible of course, but it required a lot of manual labor. Actions had to be extended with additional parameters, code has to respect those parameters and return the exact portion of data we require. The same goes with sorting. In the Web API it much simpler.
Change the signature and return type to IQueryable.
public IQueryable<Product> Get() { return _storage.AsQueryable(); }
Now, if the Web API sees the method like that, it will allow access with Open Data Protocol (OData) query string parameters. OData provides support for the following queries: $filter, $orderby, $skip, $top.
Now, if I do the request:
http://localhost:5589/api/products?$top=3
I will receive the 3 top products. Or something like,
http://localhost:5589/api/products?$skip=2&$top=3
I will skip 2 and take the remaining 3. In short, having IQueryable and 4 OData query parameters, it's much more easy to do the stuff that required more time before.
Hosting
Hosting is a feature of the Web API that allows you to run a service inside different hosts. What does this mean?
We are used to ASP.NET hosting as default. I saw no problem with that at all, till working on one of my recent projects. The problem is: for an ASP.NET pipeline you need to have a web server (IIS), but web servers always have some limitations (permissions and stuff). For simple things that do not require any IIS benefits, it is easier to host the application just in .exe assembly. This is now possible with the Web API.
We have several hosting providers out of the box: self-hosting and web-hosting. Besides that, there are already some provided by community -- Louis DeJardin created a host on top of OWIN and Pedro Félix host for Azure.
Also, there is a great article by Pedro Felix about in-memory hosting. This is a very useful type of hosting for unit testing of services.
IoC improvements
Since Web API is now inside ASP.NET MVC, it uses the same strategy of resolving types byIDependencyResolver. That means that Web API classes are now supporting the same IoC style as ASP.NET MVC controller classes. Once setup, you can put different dependencies inside the API class constructor.
public class ProductsController : ApiController { public ProductsController(IServiceProvider service, IRepository<Product> productRepository) { // .. }
Conclusions
This is not everything, but very light overview of new stuff coming in ASP.NET MVC4. The ASP.NET Web API is very nice addition to the framework. There is already some good feedback, so let's hope it will be even more improved at release.
Published at DZone with permission of Alexander Beletsky, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments