CORS support for ASP.NET Web API

Overview

Cross-origin resource sharing (CORS) is a standard that allows web pages to make AJAX requests to another domain. It relaxes the same-origin policy implemented on the web browsers that limits the calls to be within the same domain.

The CORS spec (http://www.w3.org/TR/cors/) defines the way the server and browser interact in order to make cross origin calls (that is, cross domain). Most of the modern browsers today already support CORS. Our goal is to enable the support for our Web API services.

Required Assemblies

System.Web.Cors.dll

This assembly contains the core CORS library and has no dependency on System.Web.dll or System.Web.Http.dll.

System.Web.Http.Cors.dll

This assembly contains the library for enabling CORS on Web API and has dependency on System.Web.Cors.dll and System.Web.Http.dll.

Scenarios

Enabling CORS

We’ve added a new extension method to the HttpConfiguration to enable CORS. With this, you can enable the support globally, per controller or per action.

Globally

You can define a global setting when calling EnableCors. For example, the following will enable CORS globally, allowing all origins, methods, and headers. There are many settings on the EnableCorsAttribute that you can configure and are shown later in this document.

using System.Web.Http.Cors;
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // other settings removed for clarity

        config.EnableCors(new EnableCorsAttribute());
    }
}

Per Controller

The support can also be scoped to the controller. First you just need to call EnableCors without providing a global setting (that is, (new EnableCorsAttribute()).

using System.Web.Http.Cors;
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // other settings removed for clarity

        config.EnableCors();
    }
}

Then you can declare the EnableCorsAttribute on the controller to enable CORS.

[EnableCors]
public class ValuesController : ApiController
{
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    public string Get(int id)
    {
        return "value " + id;
    }
}

Per Action

In a similar fashion, you can enable CORS on a single action by first calling EnableCors.

using System.Web.Http.Cors;
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // other settings removed for clarity

        config.EnableCors();
    }
}

And then declare the EnableCorsAttribute on an action.

public class ValuesController : ApiController
{
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    [EnableCors]
    public string Get(int id)
    {
        return "value " + id;
    }
}

Attribute precedence

When you have the EnableCorsAttribute applied on all scopes (globally, per-controller, per-action), the closest one to the resource wins. Therefore the precedence is defined as follows:

  1. Action
  2. Controller
  3. Global

Excluding a controller or an action from EnableCors

You can use [DisableCors] attribute to exclude a controller or and action from the global or per-controller settings. For example, the following will enable CORS for all the actions in the ValuesController except for Get(int id).

[EnableCors]
public class ValuesController : ApiController
{
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    [DisableCors]
    public string Get(int id)
    {
        return "value " + id;
    }
}

Configuring [EnableCors] attribute

There’re few settings under the EnableCorsAttribute. These settings are defined by the CORS spec (http://www.w3.org/TR/cors/#resource-processing-model).

  • Origins
  • Headers
  • Methods
  • ExposedHeaders
  • SupportsCredentials
  • PreflightMaxAge

By default, EnableCorsAttribute will allow all origins, methods and headers. Note that when you declare the attribute on an action it automatically assumes the HTTP Method of the action that you declared on.

As soon as you specify the origins, you are basically limiting the access to the specified origins. The same applies to the methods and the headers.

For example, the following will only allow “http://localhost” and “http://sample.com” to access the ValuesController from the browser though AJAX. Note that it is still allowing any methods and headers because they’re not specified.

[EnableCors(Origins = new[] { "http://localhost", "http://sample.com" })]
public class ValuesController : ApiController
{
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    public string Get(int id)
    {
        return "value " + id;
    }
}

Implementing a custom ICorsPolicyProvider

You can implement ICorsPolicyProvider to load the CORS settings/policy dynamically from other sources such as the web.config file or a database. In fact, both the EnableCorsAttribute and DisableCorsAttribute implement this interface internally.

namespace System.Web.Http.Cors
{
    public interface ICorsPolicyProvider
    {
        Task GetCorsPolicyAsync(HttpRequestMessage request);
    }
}

Note that the ICorsPolicyProvider is async so that we don’t block the thread on I/O.

Sample

Here is a custom implementation of ICorsPolicyProvider that loads the origins from web.config.

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class EnableCorsAppSettingsAttribute : Attribute, ICorsPolicyProvider
{
    private CorsPolicy _policy;

    public EnableCorsAppSettingsAttribute(string appSettingOriginKey)
    {
        _policy = new CorsPolicy
        {
            AllowAnyMethod = true,
            AllowAnyHeader = true
        };

        // loads the origins from AppSettings
        string originsString = ConfigurationManager.AppSettings[appSettingOriginKey];
        if (!String.IsNullOrEmpty(originsString))
        {
            foreach (var origin in originsString.Split(','))
            {
                _policy.Origins.Add(origin);
            }
        }
    }

    public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request)
    {
        return Task.FromResult(_policy);
    }
}

You can apply it on the controller/action just like EnableCorsAttribute.

[EnableCorsAppSettings("internal:origins")]
public class ValuesController : ApiController
{
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    public string Get(int id)
    {
        return "value " + id;
    }
}

And it will read the “internal:origins” appSetting from the web.config.

<appSettings>
  <add key="webpages:Version" value="2.0.0.0" />
  <add key="webpages:Enabled" value="false" />
  <add key="PreserveLoginUrl" value="true" />
  <add key="ClientValidationEnabled" value="true" />
  <add key="UnobtrusiveJavaScriptEnabled" value="true" />
  <add key="internal:origins" value="http://example.com,http://webapisample.azurewebsites.net" />
</appSettings>

Implementing a custom ICorsPolicyProviderFactory

ICorsPolicyProviderFactory is an abstraction that allows you to specify how the ICorsPolicyProvider is retrieved. By default we provide the AttributeBasedPolicyProviderFactory which allows you to specify the ICorsPolicyProvider as attributes ([EnableCors], [DisableCors]). However you can extend the ICorsPolicyProviderFactory to create a centralized configuration model.

namespace System.Web.Http.Cors
{
    public interface ICorsPolicyProviderFactory
    {
        ICorsPolicyProvider GetCorsPolicyProvider(HttpRequestMessage request);
    }
}

You can register the custom ICorsPolicyProviderFactory through SetCorsPolicyProviderFactory extension method.

public static class HttpConfigurationExtensions
{
    // other extensions removed for clarity
    public static void SetCorsPolicyProviderFactory(this HttpConfiguration httpConfiguration, ICorsPolicyProviderFactory corsPolicyProviderFactory);
}
Sample

Here is a custom implementation of ICorsPolicyProviderFactory that allows you to configure the CORS settings through your own CorsConfiguration class instead of attributes.

public class ConfigBasedPolicyProviderFactory : ICorsPolicyProviderFactory
{
    private CorsConfiguration _configuration;

    public ConfigBasedPolicyProviderFactory(CorsConfiguration configuration)
    {
        _configuration = configuration;
    }

    public ICorsPolicyProvider GetCorsPolicyProvider(HttpRequestMessage request)
    {
        var routeData = request.GetRouteData();
        if (routeData == null || !routeData.Values.Keys.Contains("controller"))
        {
            return null;
        }
        var controller = routeData.Values["controller"] as string;
        return _configuration.GetPolicyForRequest(controller);
    }
}
public class CorsConfiguration
{
    private Dictionary<string, EnableCorsAttribute> _settings = 
   	new Dictionary<string, EnableCorsAttribute>();

    public void AddSetting(string controller, EnableCorsAttribute policyProvider)
    {
        _settings.Add(controller, policyProvider);
    }

    public virtual EnableCorsAttribute GetPolicyForRequest(string controller)
    {
        EnableCorsAttribute policyProvider;
        _settings.TryGetValue(controller, out policyProvider);
        return policyProvider;
    }
}

Once the ConfigBasedPolicyProviderFactory is registered, it will enable CORS on ValuesController and UsersController.

CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.AddSetting("Values", new EnableCorsAttribute());
corsConfig.AddSetting("Users", new EnableCorsAttribute { Origins = new[] { "http://localhost" } });
config.SetCorsPolicyProviderFactory(new ConfigBasedPolicyProviderFactory(corsConfig));

config.EnableCors();

Integration with Web API Tracing

When you call config.EnableCors(), it automatically adds the necessary tracers when the ITraceWriter is provided.

using System.Web.Http.Cors;
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// other settings removed for clarity

config.EnableSystemDiagnosticsTracing();

config.EnableCors();
}
}

It will emit traces similar to what’s highlighted below when you have the Web API tracing package installed and enabled.

iisexpress.exe Information: 0 : Request, Method=GET, Url=http://localhost:33150/api/Values, Message='http://localhost:33150/api/Values'
iisexpress.exe Information: 0 : Message='Will use same 'JsonMediaTypeFormatter' formatter', Operation=JsonMediaTypeFormatter.GetPerRequestFormatterInstance
iisexpress.exe Information: 0 : Message='Will use same 'XmlMediaTypeFormatter' formatter', Operation=XmlMediaTypeFormatter.GetPerRequestFormatterInstance
iisexpress.exe Information: 0 : Message='Will use same 'FormUrlEncodedMediaTypeFormatter' formatter', Operation=FormUrlEncodedMediaTypeFormatter.GetPerRequestFormatterInstance
iisexpress.exe Information: 0 : Message='Will use same 'JQueryMvcFormUrlEncodedFormatter' formatter', Operation=JQueryMvcFormUrlEncodedFormatter.GetPerRequestFormatterInstance
iisexpress.exe Information: 0 : Message='Values', Operation=DefaultHttpControllerSelector.SelectController
iisexpress.exe Information: 0 : Message='CorsSample.Controllers.ValuesController', Operation=DefaultHttpControllerActivator.Create
iisexpress.exe Information: 0 : Message='CorsSample.Controllers.ValuesController', Operation=HttpControllerDescriptor.CreateController
iisexpress.exe Information: 0 : Message='Selected action 'Get()'', Operation=ApiControllerActionSelector.SelectAction
iisexpress.exe Information: 0 : Operation=HttpActionBinding.ExecuteBindingAsync
iisexpress.exe Information: 0 : Operation=QueryableAttribute.ActionExecuting
iisexpress.exe Information: 0 : Message='Action returned 'System.String[]'', Operation=ReflectedHttpActionDescriptor.ExecuteAsync
iisexpress.exe Information: 0 : Message='Will use same 'JsonMediaTypeFormatter' formatter', Operation=JsonMediaTypeFormatter.GetPerRequestFormatterInstance
iisexpress.exe Information: 0 : Message='Selected formatter='JsonMediaTypeFormatter', content-type='application/json; charset=utf-8'', Operation=DefaultContentNegotiator.Negotiate
iisexpress.exe Information: 0 : Operation=ApiControllerActionInvoker.InvokeActionAsync, Status=200 (OK)
iisexpress.exe Information: 0 : Operation=QueryableAttribute.ActionExecuted, Status=200 (OK)
iisexpress.exe Information: 0 : Operation=ValuesController.ExecuteAsync, Status=200 (OK)
iisexpress.exe Information: 0 : Response, Status=200 (OK), Method=GET, Url=http://localhost:33150/api/Values, Message='Content-type='application/json; charset=utf-8', content-length=unknown'
iisexpress.exe Information: 0 : Operation=CorsMessageHandler.SendAsync, Status=200 (OK)
iisexpress.exe Information: 0 : Message='CorsPolicyProvider selected: 'System.Web.Http.Cors.EnableCorsAttribute'', Operation=ConfigBasedPolicyProviderFactory.GetCorsPolicyProvider
iisexpress.exe Information: 0 : Message='CorsPolicy selected: 'AllowAnyHeader: True, AllowAnyMethod: True, AllowAnyOrigin: True, PreflightMaxAge: null, SupportsCredentials: False, Origins: {}, Methods: {}, Headers: {}, ExposedHeaders: {}'', Operation=EnableCorsAttribute.GetCorsPolicyAsync
iisexpress.exe Information: 0 : Message='CorsResult returned: 'IsValid: True, AllowCredentials: False, PreflightMaxAge: null, AllowOrigin: *, AllowExposedHeaders: {}, AllowHeaders: {}, AllowMethods: {}, ErrorMessages: {}'', Operation=CorsEngine.EvaluatePolicy
iisexpress.exe Information: 0 : Operation=JsonMediaTypeFormatter.WriteToStreamAsync
iisexpress.exe Information: 0 : Operation=ValuesController.Dispose

Last edited Mar 28, 2013 at 11:42 PM by ricka0, version 13

Comments

robertdunaway Oct 24 at 9:32 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

snowattitudes Sep 9 at 3:02 PM 
I the above code for Cors policy Provider using web.config appsetting. However, the above code miss out Task<CorsPolicy> and the Interface level which expected by the implementation class for ICorsPolicyProvider. The following statement fix it:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web.Cors;
using System.Web.Http.Cors;


namespace System.Web.Http.Cors
{
public interface ICorsPolicyProvider
{
Task <CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request);
}
}

myazid Jul 30, 2013 at 10:18 AM 
Do you have sample for PUT, POST and DELETE. I could only do GET with this.

amarta78 May 11, 2013 at 1:35 AM 
I have got the answer for this ....check Yao's blog for the answer:

http://blogs.msdn.com/b/yaohuang1/archive/2013/04/05/try-out-asp.net-web-api-cors-support-using-the-nightly-builds.aspx

thanks a lot!

amarta78 May 10, 2013 at 2:04 AM 
I get a compiler error saying that EnableCorsAttribute does not contain a constructor that takes 0 arguments. Can you please let us know why we get this?

ramnaresh_t May 9, 2013 at 5:10 PM 
Excited when it will be part of WEB API..

Geminiman Apr 26, 2013 at 10:22 PM 
The latest nightlies has parameters on the EnableCorsAttribute. What should I be putting in for origin, headers and methods to make all of them work? (I just want to allow a request from anyone with any method type and any headers.)

mocsharp Apr 20, 2013 at 7:29 PM 
Is this available for self-hosted?

Fabske Apr 20, 2013 at 5:57 PM 
do you know if this will be included in the Web-API Self hosted package ? ty

jmanning Apr 4, 2013 at 8:31 PM 
you can catch Brock and the team talking about this awesome new feature on C9 @ http://channel9.msdn.com/Shows/Web+Camps+TV/ASPNET-Web-API-and-CORS-Support

BrockAllen Apr 3, 2013 at 3:23 PM 
@cmatskas -- if you can't wait for the WebAPI release of CORS, then you can use this OSS library in the mean time: http://brockallen.com/2012/06/28/cors-support-in-webapi-mvc-and-iis-with-thinktecture-identitymodel/

cmatskas Apr 3, 2013 at 10:13 AM 
Hi, can you please tell me if this is a proposal or currently supported by MVC4 WebAPI? I'm having terrible problems with CORS and I would like to know whether this can be used, even at a draft/beta stage

ricka0 Mar 28, 2013 at 11:49 PM 
@sukumarraju IE 7 CORS issue-
No, this addresses what is necessary on the Sever, it can't fix older browsers client interaction.

ricka0 Mar 28, 2013 at 11:43 PM 
For release date see http://aspnetwebstack.codeplex.com/wikipage?title=Roadmap

sukumarraju Mar 26, 2013 at 11:22 PM 
Does this resolved IE 7 CORS issue? please inform.

sukumarraju Mar 26, 2013 at 11:20 PM 
Please provide expected release date

jamiebarrow Mar 26, 2013 at 1:01 PM 
Any idea when this will be released?