Last active
March 3, 2016 07:49
-
-
Save richardneililagan/5091035 to your computer and use it in GitHub Desktop.
A bunch of classes for enabling CORS support for MVC 4 Web API, based off of Carlos Figueira's work.
https://gist.github.com/richardneililagan/5091035/#comment-791095
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Mvc.Cors | |
{ | |
using System.Linq; | |
using System.Web.Http.Filters; | |
public class CorsEnabledAttribute : ActionFilterAttribute | |
{ | |
private string[] allowedDomains; | |
public CorsEnabledAttribute() | |
{ | |
allowedDomains = new string[] { "*" }; | |
} | |
public CorsEnabledAttribute(params string[] domains) | |
{ | |
allowedDomains = domains; | |
} | |
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) | |
{ | |
if (actionExecutedContext.Request.Headers.Contains(Headers.Origin)) | |
{ | |
var origin = actionExecutedContext.Request.Headers.GetValues(Headers.Origin).FirstOrDefault(); | |
// if origin is not empty, and the allowed domains is either * or contains the origin domain | |
// then allow the request | |
if (!string.IsNullOrEmpty(origin) && (allowedDomains.Contains(origin) || allowedDomains.Contains("*"))) | |
{ | |
actionExecutedContext.Response.Headers.Add( | |
Headers.AccessControlAllowOrigin, origin | |
); | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Mvc.Cors | |
{ | |
using System; | |
using System.Collections.Generic; | |
using System.Collections.ObjectModel; | |
using System.Net; | |
using System.Net.Http; | |
using System.Threading.Tasks; | |
using System.Web.Http.Controllers; | |
using System.Web.Http.Filters; | |
internal class CorsPreflightActionDescriptor : HttpActionDescriptor | |
{ | |
private HttpActionDescriptor originalAction; | |
private string accessControlRequestMethod; | |
private HttpActionBinding actionBinding; | |
public CorsPreflightActionDescriptor(HttpActionDescriptor originalAction, string accessControlRequestMethod) | |
{ | |
this.originalAction = originalAction; | |
this.accessControlRequestMethod = accessControlRequestMethod; | |
this.actionBinding = new HttpActionBinding(this, new HttpParameterBinding[0]); | |
} | |
public override string ActionName | |
{ | |
get { return this.originalAction.ActionName; } | |
} | |
public override Task<object> ExecuteAsync(HttpControllerContext controllerContext, IDictionary<string, object> arguments, System.Threading.CancellationToken cancellationToken) | |
{ | |
var response = new HttpResponseMessage(HttpStatusCode.OK); | |
response.Headers.Add(Headers.AccessControlAllowMethods, this.accessControlRequestMethod); | |
var requestedHeaders = string.Join( | |
", ", | |
controllerContext.Request.Headers.GetValues(Headers.AccessControlRequestHeaders) | |
); | |
if (!string.IsNullOrEmpty(requestedHeaders)) | |
{ | |
response.Headers.Add(Headers.AccessControlAllowHeaders, requestedHeaders); | |
} | |
var tcs = new TaskCompletionSource<object>(); | |
tcs.SetResult(response); | |
return tcs.Task; | |
} | |
public override Collection<HttpParameterDescriptor> GetParameters() | |
{ | |
return this.originalAction.GetParameters(); | |
} | |
public override Type ReturnType | |
{ | |
get { return typeof(HttpResponseMessage); } | |
} | |
public override Collection<FilterInfo> GetFilterPipeline() | |
{ | |
return this.originalAction.GetFilterPipeline(); | |
} | |
public override Collection<IFilter> GetFilters() | |
{ | |
return this.originalAction.GetFilters(); | |
} | |
public override Collection<T> GetCustomAttributes<T>() | |
{ | |
return this.originalAction.GetCustomAttributes<T>(); | |
} | |
public override HttpActionBinding ActionBinding | |
{ | |
get { return this.actionBinding; } | |
set { this.actionBinding = value; } | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Mvc.Cors | |
{ | |
using System.Linq; | |
using System.Net.Http; | |
using System.Web.Http.Controllers; | |
internal class CorsPreflightActionSelector : ApiControllerActionSelector | |
{ | |
public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext) | |
{ | |
var originalRequest = controllerContext.Request; | |
var isCorsRequest = originalRequest.Headers.Contains(Headers.Origin); | |
if (originalRequest.Method == HttpMethod.Options && isCorsRequest) | |
{ | |
var accessControlRequestMethod = originalRequest.Headers.GetValues(Headers.AccessControlRequestMethod).FirstOrDefault(); | |
if (!string.IsNullOrEmpty(accessControlRequestMethod)) | |
{ | |
var modifiedRequest = new HttpRequestMessage( | |
new HttpMethod(accessControlRequestMethod), | |
originalRequest.RequestUri | |
); | |
controllerContext.Request = modifiedRequest; | |
HttpActionDescriptor actualDescriptor = base.SelectAction(controllerContext); | |
controllerContext.Request = originalRequest; | |
if (actualDescriptor != null && actualDescriptor.GetFilters().OfType<CorsEnabledAttribute>().Any()) | |
{ | |
return new CorsPreflightActionDescriptor(actualDescriptor, accessControlRequestMethod); | |
} | |
} | |
} | |
return base.SelectAction(controllerContext); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Mvc.Cors | |
{ | |
using System; | |
using System.Web.Http.Controllers; | |
public class CorsPreflightEnabledAttribute : Attribute, IControllerConfiguration | |
{ | |
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) | |
{ | |
controllerSettings.Services.Replace( | |
typeof(IHttpActionSelector), new CorsPreflightActionSelector() | |
); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Mvc.Cors | |
{ | |
internal static class Headers | |
{ | |
public static string Origin = "Origin"; | |
public static string AccessControlRequestMethod = "Access-Control-Request-Method"; | |
public static string AccessControlRequestHeaders = "Access-Control-Request-Headers"; | |
public static string AccessControlAllowMethods = "Access-Control-Allow-Methods"; | |
public static string AccessControlAllowHeaders = "Access-Control-Allow-Headers"; | |
public static string AccessControlAllowOrigin = "Access-Control-Allow-Origin"; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Mvc.Cors; | |
[CorsPreflightEnabled] | |
public class SampleApiController : ApiController | |
{ | |
[CorsEnabled] | |
public string Get() { | |
return "This is accessible by everyone."; | |
} | |
[CorsEnabled("http://stackoverflow.com", "http://google.com")] | |
public string Post() { | |
return "This is accessible by calls originating from the domains specified above."; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is based off of Carlos Figueira's work.
Cleaned it up a bit, updated the code to suit my own conventions, and added functionality for specifying allowed domains.
Usage
Decorate your Web API actions and/or controllers that are CORS-enabled with the [CorsEnabled] attribute.
By default, this allows CORS for all calling domains.
To specify allowed domains, just plug the allowed domains in the attribute constructor as a string[] params.
For browsers that support CORS pre-flight (i.e. sending an HTTP OPTIONS call prior to the real HTTP call to "ask for permission"), decorating with the [CorsPreflightEnabled] attribute allows your application to intercept the incoming HTTP OPTIONS call and respond accordingly.
Notes
If you're hosting your application in IIS, make sure that it's set to handle HTTP OPTIONS requests using the ISAPI handlers provided with ASP.NET.