In the previous post we’ve covered ASP.Net Web API 2 attribute routing, in this post we’ll complete covering new features, we’ll start by discussing the new response return type IHttpActionResult then cover the support for CORS.
Source code is available on GitHub.
ASP.Net Web API 2 IHttpActionResult:
As we talked before ASP.Net Web API 2 has introduced new simplified interface named IHttpActionResult, this interface acts like a factory for HttpResponseMessage, it comes with custom built in responses such as (Ok, BadRequest, NotFound, Unauthorized, Exception, Conflict, Redirect).
Let’s modify the GetCourse(int id) method to return IHttpActionResult instead of HttpResponseMessage as 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 |
[Route("{id:int}")] public IHttpActionResult 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 Ok<Learning.Data.Entities.Course>(course); } else { return NotFound(); } } catch (Exception ex) { return InternalServerError(ex); } finally { ctx.Dispose(); } } |
Notice how we’re returning Ok (200 HTTP status code) with custom negotiated content, the body of the response contains JSON representation of the returned course. As well we are returning NotFound (404 HTTP status code) when the course is not found.
But what if want to extend the NotFound() response and customize it to return plain text message in response body? This is straight forward using IHttpActionResult interface as the below:
Construct our own Action Result:
We want to build our own NotFound(“Your custom not found message”) action result, so we need to add a class which implements IHttpActionResult, let’s add new file named NotFoundPlainTextActionResult as 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 |
public class NotFoundPlainTextActionResult : IHttpActionResult { public string Message { get; private set; } public HttpRequestMessage Request { get; private set; } public NotFoundPlainTextActionResult(HttpRequestMessage request, string message) { this.Request = request; this.Message = message; } public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken) { return Task.FromResult(ExecuteResult()); } public HttpResponseMessage ExecuteResult() { HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.NotFound); response.Content = new StringContent(Message); response.RequestMessage = Request; return response; } } public static class ApiControllerExtension { public static NotFoundPlainTextActionResult NotFound(ApiController controller, string message) { return new NotFoundPlainTextActionResult(controller.Request, message); } } |
By looking at the code above you will notice that the class “NotFoundPlainTextActionResult” implements interface “IHttpActionResult”, then we’ve added our own implementation to the method “ExecuteAsync” which returns a Task of type HttpResponseMessage. This HttpResponseMessage will return HTTP 404 status code along with the custom message we’ll provide in the response body.
In order to be able to reuse this in different controllers we need and new class named ApiControllerExtension which contains a method returns this customized Not Found response type.
Now back to our” CoursesController” we need to change the implementation of the standard NotFound() to the new one as the code below:
1 2 3 4 5 6 7 8 |
if (course != null) { return Ok<Learning.Data.Entities.Course>(course); } else { return eLearning.WebAPI2.CustomIHttpActionResult.ApiControllerExtension.NotFound(this, "Course not found"); } |
You can read more about extending IHttpActionResult result by visiting Filip W. post.
ASP.Net Web API 2 CORS Support:
Enabling Cross Origin Resource Sharing in ASP.Net Web API 2 is simple, once it is enabled any client sending AJAX requests from webpage (on a different domain) to our API will be accepted.
By default CORS assembly doesn’t exist within ASP.NET Web API 2 assemblies so we need install it from NuGet, so open your NuGet package console, and type the following Install-Package Microsoft.AspNet.WebApi.Cors -Version 5.0.0 once the package is installed open “WebApiConfig” class and add the following line of code inside the register method config.EnableCors(); by doing this we didn’t enable CORS yet, there are different levels to enable CORS on ASP.Net Web API, levels are:
1. On controller level
You can now add attribute named EnableCors on the entire controller so by default every action on this controller will support CORS as well, to do this open file “CoursesController” and add the highlighted line of code below:
1 2 3 4 5 6 |
[RoutePrefix("api/courses")] [EnableCors("*", "*", "GET,POST")] public class CoursesController : ApiController { //Rest of implementation is here } |
The EnableCors attribute accept 3 parameters, in the first one you can specify the origin of the domain where requests coming from, so if you want to allow only domain www.example.com to send requests for your API; then you specify this explicitly in this parameter, most of the cases you need to allow * which means all requests are accepted from any origin. In the second parameter you can specify if you need a certain header to be included in each request, so you can force consumers to send this header along with each request, in our case we will use * as well. The third parameter is used to specify which HTTP verbs this controller accepts, you can put * as well, but in our case we want to allow only GET and POST verbs to be called from requests coming from different origin.
In case you want to exclude a certain action in the controller from CORS support you can add the attribute DisableCors to this action, let we assume we want to disable CORS support on method GetCourse so the code to disable CORS support on this action will be as the below:
1 2 3 4 5 6 |
[Route("{id:int}")] [DisableCors()] public IHttpActionResult GetCourse(int id) { //Rest of implementation is here } |
2. On action level
Enabling CORS on certain actions is fairly simple, all you want to do is adding the attribute EnableCors on this action, as well EnableCors accepts the same 3 parameters we discussed earlier. Enabling it on action level will be as the below code:
1 2 3 4 5 6 |
[Route(Name = "CoursesRoute")] [EnableCors("*", "*", "GET")] public HttpResponseMessage Get(int page = 0, int pageSize = 10) { //Rest of implemntation is here } |
3. On the entire API
In some situations where you have many controllers and you want to allow CORS on your entire API, it is not convenient to visit each controller and add EanbleCors attribute to it, in this situation we can allow it on the entire API inside the Register method in Class WebApiConfig, so open your WebApiConfig class and add the code below:
1 2 3 |
//Support for CORS EnableCorsAttribute CorsAttribute = new EnableCorsAttribute("*", "*", "GET,POST"); config.EnableCors(CorsAttribute); |
By adding this we enabled CORS support for every controller we have in our API, still you can disable CORS support on selected actions by adding DisableCors attribute on action level.
Source code is available on GitHub.
That’s all for now!
Thanks for reading and please feel free to drop any comment or question if there is nothing clear. Happy Coding!
why not to create real extension method -> so you will not need to add this all line code
public static class ApiControllerExtension
{
public static NotFoundPlainTextActionResult NotFound(this ApiController controller, string message)
{
return new NotFoundPlainTextActionResult(controller.Request, message);
}
}
if (course != null)
{
return Ok(course);
}
else
{
return NotFound(“Course not found”);
}
Hello li-raz,
Absolutely you can do this and what you suggested is a neat and reusable. Thanks!
Thank you for the tutorial.
I am getting an assembly error on IHttpActionResult. And it won’t resolve. Is there a step missing from this tutorial?
Hello Victor,
Did you try to download/fork the project from GitHub? It is building correctly, most probably you still referencing version one of Web API assembly.
hello Taissir,
Please can you help me retreiving data from many Table ( select from many table )
this is my successful attempt but using SQL query:
select A1.PrID,A3.Pr_Nom, A1.MpID, A2.Mp_Designation, A1.PR_Mp_etat, A1.Pr_Mp_quantite
from Pr_Mp A1, MatierePremiere A2, Projet A3
where A1.MpID = A2.MpID
and A3.PrID=A1.PrID
and A3.Pr_ID= 1
What I want please is to convert this SQl query using the same technic in your project ( with include etc …)
I am searching in the web and the problem that I find many solution but they don’t respect the architecture of your project. ( They talk about stored procedures …)
Thank you too much,
It’s very easy to set up and the topic is covered well.
What I can’t seem to get working is POST to a CORS Enabled WebApi with Chrome. Actually, I can POST but I can POST with any data in the body of the post and without that I might as well only ever use the GET verb.
Any thoughts on this. I don’t want to pollute the comments areas so I’ll past a link to a broader description. if anyone has any ideas I would appreciate it.
http://forums.asp.net/p/2014467/5798423.aspx?Re+WebApi+w+POST+w+body+data+w+origin+w+attribute+routing+w+Chrome+FF+IE
Hi,
That is strange, it should work correctly, can you try install the same Web API version 5.0.0 from NuGet, this is the version I used it in the tutorial, I’m afraid there is breaking changes with version 5.2.2 I’m not aware of.
Let me know if this works.
Hi Taiseer,
I have very much IHttpActionResult implementation. Except in place my response is a .cstml and I see it is completely formed with the model properly applied to the html placeholders. to give you the background I have the Controller method as follows
[HttpGet]
public IHttpActionResult ServeFilterPage(string page, int? take = null, int? leave = null)
{
HtmlActionResult view = null;
IEnumerable result = null;
result = paymentRepository.GetAllPayments(0);
view = new HtmlActionResult(page, result);
return view;
}
And my IHttpActionResult implemented class is
public class HtmlActionResult : IHttpActionResult
{
private readonly string _view;
//private readonly dynamic _model;
private readonly object _model;
public HtmlActionResult(string viewName, object model) // dynamic model)
{
_view = LoadView(viewName);
_model = model;
}
public Task ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
var parsedView = RazorEngine.Razor.Parse(_view, _model);
response.Content = new StringContent(parsedView);
response.Content.Headers.ContentType = new MediaTypeHeaderValue(“text/html”);
return Task.FromResult(response);
}
private static string LoadView(string name)
{
string ViewDirectory = “”;
//var view;
//var view = File.ReadAllText(Path.Combine(ViewDirectory, name + “.cshtml”));
var root = HttpContext.Current.Server.MapPath(“~/Views/Home”);
//var path = “”;
ViewDirectory = Path.Combine(root, name + “.cshtml”); //root + “Test.cshtml”;
var view = File.ReadAllText(ViewDirectory);
return view;
}
}
}
When I debug in LoadView method I see the path to my result page which is test.cshtml is properly mapped and also in the ExecuteAsync method I see the response being formed correctly. And when I capture the Network with F12 I do the Response Body with the complete markup with the values plugged in correctly. But my question is why isn’t it throwing the page on to the browser. Header info is also fine seems as I have set the contenttype to text/html.
FYI my Test.cshtml is
@model IEnumerable
Razor Results
Payments
@*Iterating Payment Model*@
@foreach (var item in Model)
{
@item.MessageDetail
}
and as I said in the Response Body on capturing I see the li items created properly.
Any help is appreciated.
You ought to be a part of a contest for one of the most
useful websites online. I will highly recommend this blog!