ASP.NET Web API 2.0 Cross Origin Resource Sharing support

Posted by: Suprotim Agarwal , on 7/22/2013, in Category ASP.NET
Views: 48953
Abstract: WebAPI 2.0 has built in support for Cross Origin Resource Sharing (CORS). Today we see what it takes to enable CORS support in the upcoming Web API 2.0 release. Along the way we create a Project template for Web API 2.0 that can be used in Visual Studio 2012

Web API as we know is a way to build Services over HTTP. Building services over HTTP makes them available to a wider audience of clients that only need an HTTP implementation, as opposed to say SOAP services.

However, there is one caveat being AJAX request to HTTP services can be made only from URLs of the same Origin. For example if your web services are hosted on http://myservices:8080/api/ then the browser will allow applications hosted on http://myservices:8080/ to make AJAX calls to these services. But services/web applications hosted on http://myservices:8081/ or any other port for that matter, cannot access the HTTP resource/api over AJAX. This limitation is a default browser behavior for security considerations.

 

However, W3C has a CORS spec that outlines the ‘rules and regulations’ to enable Cross Origin Resource Sharing aka CORS. ASP.NET MVP Brock Allen built out CORS support for Thinktecture, which was later pulled into ASP.NET Web API core and is now available in pre-release version of Web API 2.0. Even though Web API 2.0 is going to be a part of Visual Studio 2013, you can try it out today in Visual Studio 2012 with .NET Framework 4.5.

Web API 2.0 and ASP.NET MVC have moved on to .NET Framework 4.5, so as a result you cannot try this out unless your project is targeted for 4.5.

So without further delay, let’s jump right in and see how to enable and use Cross Origin Resource Sharing in Web API 2.0.

Setting up the Web API Sample

Now setting up a Sample would have been very easy had we used Visual Studio 2013. It’s practically available out of the box. However, thanks to Nuget package based release of the Web API components, we can very well do it in VS 2012 too.

Getting Started with an Empty Project

Instead of starting with the usual Web API project that will use the stable release of Web API, we start off with an ASP.NET Project and use the Empty project template. This will give us minimum cruft with respect to dependencies.

mvc-project-template

project-type

Once the project is ready, right click on the Solution Explorer and go to Manage Nuget packages. Here we set the package types to – ‘Include Prerelease’ and then do a search for ‘Web API’. As shown below, we’ll get a set of search results from which we pick ‘Microsoft ASP.NET Web API’. Note, its version is 5.0.0 (beta2).

web-api-pre-release

Once we click install, Nuget Package Manager will do its thing to download the required dependencies. We’ll end up with a license dialog like the following, giving you an idea of the sub-dependencies getting installed:

web-api-pre-release-components

Next we install the CORS package as follows:

web-api-cors-dependency

Once these are installed, we have Web API setup and good to go.

Setting up Web API Help Pages and Test Client

Yao Huang Lin from the ASP.NET team built a very useful “Web API Help Pages” package that dynamically generates API documentation for your Web API services. However, he didn’t stop at that; he also built a test client framework that provides a dynamic client to post data to your services. These two packages are a must-have when doing Web API development.

Since we want the latest and greatest version (pre-release), we have one manual step to take before we can install these two packages. From the Package Manager Console, fire the following command

PM> uninstall-package Microsoft.AspNet.Mvc.FixedDisplayModes

We had to uninstall the FixedDisplayModes package because the Web API Test Client package updates MVC to MVC 5 beta and for reasons I have not investigated further, FixedDisplayModes still doesn’t have a version compliant with MVC 5 beta.

Next we go to the Nuget Package Manager again and install Web API Help Pages.

web-api-help-page

Note, if you search for ‘Web Api’, the Help Pages package is in the second page of the search results.

As the Web Pages get installed, you’ll see the following license agreement screen, which essentially hints at MVC and Razor engines getting updated.

help-page-updates

Finally, search for “Test Client for Web API” in the Package Manager and install the Test Client

simple-test-client

This has an additional dependency of jQuery, jQuery UI and KnockoutJs. You can install these from the Package Manager Console as follows

PM> install-package jquery
PM> install-package jquery.ui.combined
PM> install-package KnockoutJs

Configuring the Test Client

The test client requires two lines of configuration. Open the Api.cshtml in the following path \Areas\HelpPage\Views\Help\. Add the following at the bottom of the page (after the last </div> tag).

@Html.DisplayForModel("TestClientDialogs")

Inside the Scripts section add the following line

@Html.DisplayForModel("TestClientReferences")

With this the test client is good to go.

 

Creating an Entity and Building a CRUD service for it

In the Models folder, add a new class called BlogPost as follows:

public class BlogPost
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public string Author { get; set; }
}

Next we build the application.

On the Controller’s folder, right click and select Add Controller to add a Web API Controller using EntityFramework for data persistence.

scaffold-controller-ef

Setup the controller as above and click Add to scaffold the Api Controller. We are at a point where we could save this project template so that we can create a new Web API 2.0 project in one shot.

Saving the project template

To save the Project Template, click on File > Export as Template menu. We’ll get a Wizard as follows

save-project-template

Click Next and Provide a name and description

template-step-2

Creating a second Solution

To test out CORS, we need two web applications calling each other. To keep things simple, we’ll simply use the newly created template in a new Solution and create a new Project

new-project-using-template

Click OK and we are done. We have two projects with same code if we were to post from one project to another, it would be Cross Origin.

To avoid port conflicts, go to one of the Project’s property panel and change the Port of execution (the port gets saved as a part of the Template and is thus same). Here I’ve changed port of WebApiCors2 to 54733.

changed-ports

Update the Web.Config’s connection string name to WebApiCorsContext2 and update the Context class as well to use the new connection string

public WebApiCorsContext()
: base("name=WebApiCorsContext2")
{
}

Testing and Enabling CORS

Before we see how to enable CORS, let’s run our solutions, we use Firefox as the browser for WebApiCors project and IE as the browser for the WebApiCors2 project. In both, navigate to the /Help page. We’ll see the same APIs are available on both applications.

two-apis-side-by-side

Let’s do a POST from WebApiCors to the app @ Port 54732 (same domain). To do the post, click on the POST link in the Home Page. This will bring up a Page with sample data and a ‘Test API’ button at bottom right hand corner. Clicking on the Button will bring up a dialog with sample data that needs to be posted.

post-data-test-api

We update the sample data slightly and hit Send. The response we get is as follows

blog-post-response

As we can see, this is a RESTful response with the Body containing the new Entity, and the Location header containing the URL where the newly created Entity can be found. So we were able to post to same domain.

Now if we take the same Input dialog, and try to send it to the application on port 54733 as follows

post-data-test-api-cross-domain

We’ll get the following response. It’s not a very elegant response but essentially it’s a failure of a cross domain call.

post-data-test-api-cross-domain-error

Enabling CORS in WebApiCors2 (Port No: 54733)

Let’s keep the WebApiCors project running and stop the WebApiCors2 project. In the App_Start\WebApiConfig.cs we’ll enable CORS as follows

config.EnableCors(new EnableCorsAttribute("*", "*", "*"));

The three constructor arguments are

  • Origins: Which sources should we accept request from. * = every site on the internet
  • Headers: Which headers should be accepted. *=anything is fine.
  • Methods: Which HTTP Verbs should be accepted. * = all verbs

So the above configuration sets up the API as completely public and accessible to all Origins. To test, we run WebApiCors2 project and post the same data again from WebApiCors. This time we get a success message as follows

post-data-test-api-cross-domain-success

To confirm that this is indeed the same data, in IE, where we have WebApiCors2 site running, we navigate to the Help Page and do a get request. As we can see here, the data returned is the same that was posted across domains.

image

With that we have seen how to setup Web API Help Pages, Test Client, CORS and Enable CORS. We enabled blanket permissions for CORS in our example. Next we look at some of the options that the CORS library provides and see how we can fine tune CORS access to Web API resources.

CORS Options

In the above sample, we saw how we could enable CORS using an instance of EnableCorsAttribute object. Now the name would have given you a hint that you can enable CORS via attributes as well.

That is actually a very correct assumption. If we were to enable CORS only via a central location, we could potentially end up with the same dissonance that we had with routing. To avoid that, CORS can be enabled at API level as well.

For example, let’s modify the WebApiCors2 project and remove the permissions from the WebApiConfig.cs. Next in the BlogPostsController we add the following attribute

[EnableCorsAttribute("https://www.dotnetcurry.com/", "*", "*")]
public class BlogPostsController : ApiController
{

}

The above attribute implies that requests from the domain www.dotnetcurry.com will be accepted. So if requests are sent from localhost or any other domain other than dotnetcurry, the request will fail.

Similarly we can filter by headers also. The following configuration says that the request must accompany a custom header called x-myheader. If this is missing, the request will be rejected.

[EnableCorsAttribute("*", "origin,x-myheader", "*")]
public class BlogPostsController : ApiController
{

}

Note that origin is the default as per the CORS spec. Without it, the request will be denied as well.

Finally we can filter by Http Verbs. For example, the following enables GET requests from any origin with any header

[EnableCorsAttribute("*", "*", "GET")]
public class BlogPostsController : ApiController
{

}

If one tried to do a POST across origins it would be rejected.

Customizing Policies

You can implement your own custom class and either apply it globally or implement it as an attribute and apply it at the class level. With that, we wrap up this introduction to CORS in Web API 2.0.

Conclusion

CORS policies are an important part of providing correct authorization levels to ASP.NET Web API services. Without CORS, we had to use hacks like JSONP which we no longer have to use when building Web API based services.

Caveat: Not sure if it’s a bug in IE10 but CORS requests were going though in IE10 on Windows 8 hence I used Firefox to show how CORS requests get blocks or passed on depending on the permissions.

Download the entire source code of this article (Github)

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
Suprotim Agarwal, MCSD, MCAD, MCDBA, MCSE, is the founder of DotNetCurry, DNC Magazine for Developers, SQLServerCurry and DevCurry. He has also authored a couple of books 51 Recipes using jQuery with ASP.NET Controls and The Absolutely Awesome jQuery CookBook.

Suprotim has received the prestigious Microsoft MVP award for Sixteen consecutive years. In a professional capacity, he is the CEO of A2Z Knowledge Visuals Pvt Ltd, a digital group that offers Digital Marketing and Branding services to businesses, both in a start-up and enterprise environment.

Get in touch with him on Twitter @suprotimagarwal or at LinkedIn



Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!
Comment posted by Yazid on Tuesday, July 30, 2013 11:42 PM
I can't seem to get it working.
After creating the Entity and Building it, it shows compilation error.

Running your demo code also cause server error.

I'm running on VS2012.
Comment posted by Sumit on Wednesday, July 31, 2013 1:30 PM
Hello Yazid,
We use Nuget Package Restore to restore the dependencies. If you have Internet connection when building it 'should' get the dependencies automatically.
Can you expand the References folder and see if all the references are in place? If not some of them will have a yellow exclamation.

IF you are still having trouble, see if you can copy paste the errors here.

- Sumit.
Comment posted by Peter Bulloch on Monday, September 23, 2013 1:51 PM
I can't seem to create the controller.  It says "Unable to retrieve metadata for "WebApiCors.Models..."

Any thoughts?
Thx Peter
Comment posted by Bob on Friday, October 24, 2014 3:50 PM
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