Asp.Net Web API 2 has been released with the release of Asp.Net MVC 5 since 5 months ago, Web API 2 can be used with .NET framework 4.5 only, the Web API 2 template is available by default on VS 2013 and you can install Web Tools 2013.1 for VS 2012 to have this template as well by visiting this link.
In my previous tutorial on Building ASP.Net Web API RESTful service, I’ve covered different features of the first release of ASP.Net Web API, in this two series post (part 2 can be found here) I will focus on the main features in version 2, I’ll be using the same project I’ve used in the previous tutorial but I will start new Web API 2 project then we’ll implement and discuss the new features. The source code for the new Web API 2 project can be found on my GitHub repository.
Note: I will not upgrade the existing eLearning Web API to version 2, but if you are interested to know how to upgrade version 1 to version 2 then you can follow the steps in ASP.Net official post here.
What is new in ASP.Net Web API 2?
ASP.Net Web API 2 comes with a couple of nice features and enhancements, the most four important features in my opinion are:
-
ASP.Net Web API 2 Attribute Routing:
- Web API 2 now supports attribute routing along with the conventional based routing where we used to define a route per controller inside “WebApiConfig.cs” class. Using attribute routing is very useful in situation that we have a single controller which is responsible for multiple actions, with version 2 the routes now can be defined directly on the controller or at the action level.
-
IHttpActionResult:
- In the first version of Asp.Net Web API we always returned HTTP response messages for all HTTP verbs using the extension method Request.CreateResponse and this was really easy, now with version 2 of ASP.Net Web API this is simplified more by introducing a new interface named IHttpActionResult, this interface acts like a factory for HttpResponseMessage with a support for custom responses such as (Ok, BadRequest,Notfound, Unauthorized, etc…).
-
Cross Origin Resource Sharing Support (CORS):
- Usually webpages from different domain are not allowed to issue AJAX requests to HTTP Services (API) on other domains due to Same-Origin Policy, the solution for this was to enable JSONP response. But in ASP.Net Web API 2 enabling CORS support is made easy, once you enable CORS it will allow JavaScript on webpage to send AJAX requests to another HTTP Service (API) on a different domain. This can be configured on the entire API, on certain controller, or even on selected actions.
-
Enhancement on oData Support.
Note: I’ll not cover this in the current post as I’m planing to do dedicated series of posts on oData support for ASP.Net Web API.- Multi part series tutorial for Building OData Service using ASP.Net Web API is published now.
Let’s cover the new features in practical example
As stated before and to keep things simple we’ll depend on my previous tutorial eLearning API concepts to demonstrate those features. All we want to use from the eLearning API is its data access layer and database model, as well the same repository pattern, in other words w’ll use only project eLearning.Data, if you are interested to know how we built the eLearning.Data project then you can follow along on how we created the database model here and how we applied the repository pattern here.
Once you have your database access layer project ready then you can follow along with me to create new eLearning.WebAPI2 project to demonstrate those new features.
I will assume that you have Web Tools 2013.1 for VS 2012 installed on your machine, so you can use the ASP.NetWeb API 2 template directly which will add the needed assemblies for ASP.Net Web API2.
Step 1: Create new empty ASP.Net Web API 2 project
We’ll start by creating a new empty ASP.Net Web API 2 project as the image below, you can name your project “eLearning.WebAPI2”, do not forget to choose .NET framework 4.5 version. Once The project is added we need install Entity Framework version 6 using NuGet package manager or NuGet package console, the package we’ll install is named “EntityFramework“. When the package is installed, we need to add a reference for the class library “eLearning.Data” which will act as our database repository.
Step 2: Configuring routes
The ASP.Net Web API 2 template we used has by default a class named “WebApiConfig” inside the “App_Start” folder, when you open this class you will notice that there is new line config.MapHttpAttributeRoutes(); this didn’t exists in Web API version one, the main responsbility for this line of code is to enable Attribute Routing feature in Web API version 2 where we can define routes on the controller directly. In this tutorial we want to use routing by attributes only to route all requests, so feel free to delete the default route named “DefaultApi”.
The other important change introduced in Web API 2 is how the “WebApiConfig” file is registered, go ahead and open class “Global.asax” and you will notice that there is new line used for configuring the routes GlobalConfiguration.Configure(WebApiConfig.Register); this line is responsible for registering the routes when the global configuration class is initiated and ready to be called.
Step 3: Adding first controller (Courses Controller)
Now we want to add a Web API Controller which will handle HTTP requests issues against the URI “/api/courses”. To do this right-click on Controllers folder->Select Add->Name the controller “CoursesController” and choose “Empty API Controller” Template. Note: It is not required to name the controller “CoursesController” as we used in Web API version 1, now the controllers selection are based on the attributes we define not the routes which used be configured in class “WebApiConfig”
We’ll add support for getting single course by id and getting all courses. There are different ways to define routing attributes, you can define route prefix on controller level, and you can define them on action level. As well we’ll cover how we can configure routing to an exceptional route within the same controller. Below are the scenarios we’ll cover:
1. Define attributes on action level
Let’s Implement the code below which add support for handling and HTTP GET request sent to the URI “api/courses/23”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
public class CoursesController : ApiController { [Route("api/courses/{id:int}")] public HttpResponseMessage GetCourse(int id) { Learning.Data.LearningContext ctx = null; Learning.Data.ILearningRepository repo = null; try { ctx = new Learning.Data.LearningContext(); repo = new Learning.Data.LearningRepository(ctx); var course = repo.GetCourse(id, false); if (course != null) { return Request.CreateResponse(HttpStatusCode.OK, course); } else { return Request.CreateResponse(HttpStatusCode.NotFound); } } catch (Exception ex) { return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex); } finally { ctx.Dispose(); } } } |
What we have done here is simple, if you look at the highlighted line of code above you will notice how we defined the route on action level by using attribute System.Web.Http.Route where we specified the request URI and stated that id parameter should be integer, if you tried to issue GET request to URI “/api/courses/abc” you will get 404 response.
In Web API version 1 we used to define names for routes inside “WebApiConfig” class, those names were useful as we used them to link each resource to URI or for results navigation purpose, in our case we still need to return “PrevPageLink” and “NextPageLink” in the response body when user issue HTTP GET request to URI “/api/courses”. This still available in Web API version 2 but defining route names will take place on the attribute level, let’s implement the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public class CoursesController : ApiController { [Route("api/courses/", Name = "CoursesRoute")] public HttpResponseMessage Get(int page = 0, int pageSize = 10) { IQueryable<Course> query; Learning.Data.LearningContext ctx = new Learning.Data.LearningContext(); Learning.Data.ILearningRepository repo = new Learning.Data.LearningRepository(ctx); query = repo.GetAllCourses().OrderBy(c => c.CourseSubject.Id); var totalCount = query.Count(); var totalPages = (int)Math.Ceiling((double)totalCount / pageSize); var urlHelper = new UrlHelper(Request); var prevLink = page > 0 ? urlHelper.Link("CoursesRoute", new { page = page - 1, pageSize = pageSize }) : ""; var nextLink = page < totalPages - 1 ? urlHelper.Link("CoursesRoute", new { page = page + 1, pageSize = pageSize }) : ""; var results = query .Skip(pageSize * page) .Take(pageSize) .ToList(); var result = new { TotalCount = totalCount, TotalPages = totalPages, PrevPageLink = prevLink, NextPageLink = nextLink, Results = results }; return Request.CreateResponse(HttpStatusCode.OK, result); } } |
In the code above notice how we defined the route name on attribute level, you can not define route names on Controller level so if you have another actions need route names then you have to define them on each attribute.
2. Define attributes on controller level
Now instead of repeating the prefix “/api/courses” on each action, we can add this prefix on controller level and define the specific route attributes information on each action, the change is fairly simple as the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[RoutePrefix("api/courses")] public class CoursesController : ApiController { [Route(Name = "CoursesRoute")] public HttpResponseMessage Get(int page = 0, int pageSize = 10) { /*Rest of implementation goes here*/ } [Route("{id:int}")] public HttpResponseMessage GetCourse(int id) { /*Rest of implementation goes here*/ } } |
3. Routing to exceptional route
In some cases you need to route to different URI while you are on the same controller, this was not possible in Web API version 1 but it’s very easy to implement in version 2, assume that we want to get all courses names based on certain subject, our URI have to be on the form: “api/subjects/{subjectid}/courses” lets implement this by adding the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
[Route("~/api/subject/{subjectid:int}/courses")] public HttpResponseMessage GetCoursesBySubject(int subjectid) { Learning.Data.LearningContext ctx = null; Learning.Data.ILearningRepository repo = null; IQueryable<Course> query; try { ctx = new Learning.Data.LearningContext(); repo = new Learning.Data.LearningRepository(ctx); query = repo.GetCoursesBySubject(subjectid); var coursesList = query.Select(c => c.Name).ToList(); if (coursesList != null) { return Request.CreateResponse(HttpStatusCode.OK, coursesList); } else { return Request.CreateResponse(HttpStatusCode.NotFound); } } catch (Exception ex) { return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex); } finally { ctx.Dispose(); } } |
Now how we defined the routing attribute and prefixed it with “~” so Web API will route any HTTP GET request coming to the URI “api/subject/8/courses” to this route, this is really nice feature when you want to introduce exceptional routing in the same controller.
In the next post I’ll cover the IHttpActionResult return type as well the support for CORS in ASP.Net Web API 2.
Hello,
Managed to follow and understand everything up to this bit, “ctx.Dispose()”. Where does the “Dispose” come from?
Thanks.
Hello,
It is coming from class System.Data.Entity.DbContext where “LearningContext” inherits from.
I’m just disposing the database context. If you are wrapping it with “using” then no need to do this.
Thanks for the the tutorial.
In your previous post https://bitoftech.net/2013/12/16/asp-net-web-api-versioning-accept-header-query-string/ you explained how to do versioning by using the accept header and by overriding the DefaultHttpControllerSelector.
Would this also work well with Web API 2 and would you still recommend this?
At http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2 Mike Wasson mentioned (not explicit recommend) to use the “/api/v1/resource” / “/api/v2/resource” pattern.
And obviously this could be implemented very easy with attribute routing.
Hi Taiseer,
I tried it out and unfortunately your priviously mentioned example of versioning by using the accept header and by overriding the DefaultHttpControllerSelector will not work with attribute routing in the form it is.
At
http://stackoverflow.com/questions/19835015/versioning-asp-net-web-api-2-with-media-types
Kiran Challa described who to handle this issue when using attribute routing.
kind regards!
Hello Matthias,
You are right it won’t work if you are using routing on attribute level, the controller selection will fail.
There is nothing wrong with implementing versioning as part of the URI, but REST specification emphasize that URI should never change for the same resource; so versioning by header is better approach. Check this blog as well to see how you can implement custom route attributes using versioning by header: http://weblogs.asp.net/jgalloway/archive/2014/01/24/looking-at-asp-net-mvc-5-1-and-web-api-2-1-part-2-attribute-routing-with-custom-constraints.aspx
Good luck 🙂
Hi, I was wondering how could I change my program to accept an URL along with ID(integer) in the GET. If I say
localhost/api/myapi/GetMystuff/1/http://ww.google.com I get error Server Error in ‘/’ Application. How to handle this?
Thanks
Venki
Hi Venki,
If I were in your situation I won’t pass a URL in the API end point, I’ll treat it as a string (without http://) then once I receive the string I’ll construct the URI again, or you can use the URL passed as query string parameter not part of your API URI params.
Taiseer,
Will it cause problems if I use AttributeRouting, but leave the WebApiConfig routing in place? I am getting a 500 error indicating after I implement AttributeRouting at the controller “Multiple actions were found that match the request”.
Thanks,
Andy
Hi Andy, it should not cause issues, usually you will use WebApiConfig routing as a catch all route for your requests.
I’m not a fan of mixing both AttributeRouting and legacy routing, try to use AttributeRouting; IMHO I found it easier and better to manage than defining all the routes in single file.
Hi Taiseer,
I’m using your code as a learning tool for web api and I’m trying to figure out how to bring back from the database. I’m getting the data that I am searching for but I am only getting one record when I should be getting thousands of records back.
I tried the following:
Part 1:
[Route(“~/api/AllProfessions/{procde}”)]
public IHttpActionResult GetAllProfessionalsListByProCode(string procde)
{
Learning.Data.LearningContext ctx = null;
Learning.Data.ILearningRepository repo = null;
try
{
ctx = new Learning.Data.LearningContext();
repo = new Learning.Data.LearningRepository(ctx);
var course = repo.GetAllProfessionalsListByProCode(procde);
if (course != null)
{
return Ok(course);
}
else
{
return eLearning.WebAPI2.CustomIHttpActionResult.ApiControllerExtension.NotFound(this, “Professionals not found”);
}
}
catch (Exception ex)
{
return InternalServerError(ex);
}
finally
{
ctx.Dispose();
}
}
Part 2:
Professional GetAllProfessionalsListByProCode(String proCde);
Part 3:
public Professional GetAllProfessionalsListByProCode(string proCde)
{
return _ctx.Professionals
.Where(c => c.proCde == proCde)
.Where(c => c.proCde.Equals(proCde,StringComparison.OrdinalIgnoreCase))
.FirstOrDefault();
}
This displays one record with all the fields in my class when I should see hundreds of records.
Any clues you guys could give me would be very helpful.
Regards,
Kai Wen
Remove .FirstOrDefault() from method “GetAllProfessionalsListByProCode” and replace it with .ToList() and you should retrieve all the records. Good luck!
Hi there!
I’ve download the sample code and tried to run it on my machine using VS2013 (I had to change IIS Express to IIS Local in the project’s settings to make it work first because I was getting a “403.14 – Forbidden” error). However, I get a 404 error when trying to get courses by id using http://localhost/eLearning.WebAPI2/Courses/1. What am I missing here?
Thanks for the help!
Hi, I do not think that URL you provided is correct (eLearning.WebApi2) please check the virtual path. As well for WebApi projects, receiving 403.14 as when you run it is normal. There is no home page for WebApi projects.
Hi there,
Can attribute routing work with DI container (I am using autofac) in constructor injection mode? In my web api 2 project, if I use attribute routing, the controller can only be instantiated with a parameterless constructor and I have to use property injection.
Thanks
Hello,
I’m not very familiar with AutoFac, sorry about this.
Hello,
Thanks for the reply.
Does attribute routing require a parameterless default constructor on the controller, such that it prevents using any dependency injection framework in constructor injection mode?
Thanks
I see you don’t monetize your blog, don’t waste your traffic, you can earn extra cash every month because you’ve got
high quality content. If you want to know how to make extra bucks,
search for: Mrdalekjd methods for $$$