ASP.NET Web API 2.0 Cross Origin Resource Sharing support

Posted by: Suprotim Agarwal , on 7/22/2013, in Category ASP.NET
Views: 13786
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("http://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)

Give me a +1 if you think it was a good article. Thanks!
Recommended Articles
Suprotim Agarwal, ASP.NET Architecture MVP, MCSD, MCAD, MCDBA, MCSE, is the CEO of A2Z Knowledge Visuals Pvt. He primarily works as an Architect Consultant and provides consultancy on how to design and develop .NET centric database solutions.

Suprotim is the founder and primary contributor to DotNetCurry, SQLServerCurry and DevCurry. He has also written an EBook 51 Recipes using jQuery with ASP.NET Controls.

Follow him on twitter @suprotimagarwal


Page copy protected against web site content infringement by Copyscape


User Feedback
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

Post your comment
Name:  
E-mail: (Will not be displayed)
Comment:
Insert Cancel