This post was written for the Beta version of the ASP.NET MVC 4. The updates needed to make them run in the latest bits (Release Candidate) are listed in this new post.
The code in this post is published on the MSDN Code Gallery.
By default, a web page cannot make calls to services (APIs) on a domain other than the one where the page came from. This is a security measure against a group of cross-site forgery attacks in which browsing to a bad site could potentially use the browser cookies to get data from a good site which the user had previously logged on (think your bank). There are many situations, however, where getting data from different sites is a perfectly valid scenario, such as mash-up applications which need data from different sources.
There are a few ways to overcome this limitation. JSONP (JSON with Padding) uses the fact that <script> tags don’t have the cross-domain restriction, and if the server is willing to send the data back differently (i.e., padding the JSON response by wrapping it inside a function call), then the client can create a named function, and add a new <script> tag to the page DOM and that function will be called with the result from the service. It’s a very simple protocol, and has been used successfully in many applications.
The main problem (or limitation) of JSONP is that it only supports GET requests – <script> tags will cause the browser to issue a HTTP GET request for the script referenced by its “src” attribute. In situations where other HTTP verbs are required, JSONP simply doesn’t work. There are some alternatives to this, such as proxying the requests through a service (the cross-domain restrictions are imposed by the XmlHttpRequest object in the browsers, and don’t apply to “general”, server-side, code), but they usually introduce a new component in the system which is error-prone.
Cross-domain calls in ASP.NET Web APIs
Note: If you are completely new to ASP.NET Web API then I recommend that you check out some of the tutorials.
As of its Beta release, there is no native support for cross-domain calls in ASP.NET Web APIs. It’s fairly simple, though, to implement those – the comments sample has a simple JSONP formatter. This post shows one way to implement CORS in a Web API project, using a message handler. Message handlers can intercept the requests as they go through the pipeline and either modify them as they go through or respond to the request immediately, bypassing the rest of the pipeline, so they make a good candidate for this scenario.
A very quick CORS intro before diving into the code: requests for cross-domain resources have an additional HTTP header, “Origin”, which identifies the domain where the page was loaded from. If the server supports CORS, it should respond to such requests with an additional response header, “Access-Control-Allow-Origin”. There is also a special request, the preflight request, which is sent prior to the actual request, which uses the HTTP “OPTIONS” verb, which asks the server which HTTP methods and request headers it supports in cross-domain requests (using the “Access-Control-Request-Method” and “Access-Control-Request-Headers” request headers, respectively), and the server must respond with the appropriate headers (“Access-Control-Allow-Methods” and “Access-Control-Allow-Headers” response headers, respectively).
Let’s start with a new ASP.NET MVC 4 / Web API project (add a solution folder, since we’ll add a new project to do the cross-domain calls). In order to get something useful out of the controller from the template, I changed it a little to be able to test it, by storing the “values” in an in memory list.
Next, let’s add a new project to the solution, and ASP.NET Empty Web Application, and add a NuGet reference to the jQuery project. With that, we can add an HTML page and add some controls to test the controller.
With that ready, we can bind the “click” events from the buttons to make calls to the controller, as shown below (this isn’t beautiful code by any means, with a little more time I’d have combined many of the functions, but for a quick sample, this works)
Time to test the page. After setting both projects as the startup projects in the Visual Studio solution, we can F5 and start clicking on the buttons. Oops, nothing works – they all print the error on the result <div> element.
That’s where CORS enters the picture. We need to get the controller to return the appropriate headers, otherwise no cross-domain requests will succeed. Let’s add a new message handler to deal with those requests. The code is shown below. If the request contain the “Origin” header, we’ll treat it as a CORS request, and after dispatching the message through the pipeline (base.SendAsync), we’ll add the “Access-Control-Allow-Origin” header to let the browser know that we’re fine with that request.
We also must deal with preflight requests (OPTIONS). If this is the case, we don’t need to send the message through the pipeline, we can respond to it right away, by creating a response message and returning it at the handler itself.
Finally, we add the CORS handler to the list of message handlers in the global configuration of the application (in global.asax.cs):
That’s it. We can now call in any CORS-capable browser, as shown below.
The cross-domain restrictions in many browsers exist for a reason, so please understand the security implications of enabling such requests with CORS (or JSONP) before doing so in production systems.
Also, the solution shown in this post enables CORS for all actions in all controllers. There is a way to enable them selectively, and you can find more about it in the next post.