CORS support in ASP.NET Web API – RC version

The code for this post is published in the MSDN Code Gallery .

A few months back I had posted some code to enable support for CORS (Cross-Origin Resource Sharing) in the ASP.NET Web API. At that point, that product was in its Beta version, and with the Release Candidate (RC) released last month, some of the API changes made the code stop working (and in the per-action example, it even stopped building). With many comments asking for an updated version which builds, here they are.

The first version (a global message handler, which enabled CORS for all controllers / actions in the application), actually didn’t need any update at all. Actually, the message handler didn’t need any updates, but the actions in the values controller which take the string as a parameter need a [FromBody] decoration due to the model binding changes between the Beta and the RC version. In RC, parameters of simple types (such as string) by default come from the URI (either in the route or in the query string), and the application passed the parameter via the request body.

  1. public class ValuesController : ApiController
  2. {
  3.     static List<string> allValues = new List<string> { "value1", "value2" };
  4.  
  5.     // GET /api/values
  6.     public IEnumerable<string> Get()
  7.     {
  8.         return allValues;
  9.     }
  10.  
  11.     // GET /api/values/5
  12.     public string Get(int id)
  13.     {
  14.         if (id < allValues.Count)
  15.         {
  16.             return allValues[id];
  17.         }
  18.         else
  19.         {
  20.             throw new HttpResponseException(this.Request.CreateResponse(HttpStatusCode.NotFound));
  21.         }
  22.     }
  23.  
  24.     // POST /api/values
  25.     public HttpResponseMessage Post([FromBody]string value)
  26.     {
  27.         allValues.Add(value);
  28.         return this.Request.CreateResponse<int>(HttpStatusCode.Created, allValues.Count - 1);
  29.     }
  30.  
  31.     // PUT /api/values/5
  32.     public void Put(int id, [FromBody] string value)
  33.     {
  34.         if (id < allValues.Count)
  35.         {
  36.             allValues[id] = value;
  37.         }
  38.         else
  39.         {
  40.             throw new HttpResponseException(this.Request.CreateResponse(HttpStatusCode.NotFound));
  41.         }
  42.     }
  43.  
  44.     // DELETE /api/values/5
  45.     public void Delete(int id)
  46.     {
  47.         if (id < allValues.Count)
  48.         {
  49.             allValues.RemoveAt(id);
  50.         }
  51.         else
  52.         {
  53.             throw new HttpResponseException(this.Request.CreateResponse(HttpStatusCode.NotFound));
  54.         }
  55.     }
  56. }

The second version – CORS support on a per-action basis – had some changes. That version was implemented using two components: one filter attribute, and one action selector (to define the action that would respond to preflight requests). The code for the filter was almost the same, with the exception that the property of the HttpActionExecutedContext in the action filter which stored the response changed from Result to Response:

  1. public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
  2. {
  3.     if (actionExecutedContext.Request.Headers.Contains(Origin))
  4.     {
  5.         string originHeader = actionExecutedContext.Request.Headers.GetValues(Origin).FirstOrDefault();
  6.         if (!string.IsNullOrEmpty(originHeader))
  7.         {
  8.             actionExecutedContext.Response.Headers.Add(AccessControlAllowOrigin, originHeader);
  9.         }
  10.     }
  11. }

The code for the action selector itself was actually unchanged, but the nested type to implement the new action descriptor had quite a lot of changes, mostly related to the OM changes in the HttpActionDescriptor class (related to return types). Besides trivial changes (e.g., from ReadOnlyCollection<T> to Collection<T>), there were two larger changes:

  • The Execute method is now asynchronous (and also named ExecuteAsync). Since we don’t need to execute any asynchronous operation (we already know the operation result at that point), we’re now using a TaskCompletionSource<TResult> as explained by Brad Wilson in his series about TPL and Servers.
  • The class also defines a new property, ActionBinding, which defines the binding from the request to the parameters. Unlike the other operations in the class which we can simply delegate to the original descriptor, we can’t do that for the preflight action, since it’s possible that the actions take some additional parameter (which is the case in the values controller used in the example). In this case, we simply create a new instance of HttpActionBinding which doesn’t take any parameters to return to the Web API runtime.
  1. class PreflightActionDescriptor : HttpActionDescriptor
  2. {
  3.     HttpActionDescriptor originalAction;
  4.     string accessControlRequestMethod;
  5.     private HttpActionBinding actionBinding;
  6.  
  7.     public PreflightActionDescriptor(HttpActionDescriptor originalAction, string accessControlRequestMethod)
  8.     {
  9.         this.originalAction = originalAction;
  10.         this.accessControlRequestMethod = accessControlRequestMethod;
  11.         this.actionBinding = new HttpActionBinding(this, new HttpParameterBinding[0]);
  12.     }
  13.  
  14.     public override string ActionName
  15.     {
  16.         get { return this.originalAction.ActionName; }
  17.     }
  18.  
  19.     public override Task<object> ExecuteAsync(HttpControllerContext controllerContext, IDictionary<string, object> arguments)
  20.     {
  21.         HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
  22.  
  23.         // No need to add the Origin; this will be added by the action filter
  24.         response.Headers.Add(AccessControlAllowMethods, this.accessControlRequestMethod);
  25.  
  26.         string requestedHeaders = string.Join(
  27.             ", ",
  28.             controllerContext.Request.Headers.GetValues(AccessControlRequestHeaders));
  29.  
  30.         if (!string.IsNullOrEmpty(requestedHeaders))
  31.         {
  32.             response.Headers.Add(AccessControlAllowHeaders, requestedHeaders);
  33.         }
  34.  
  35.         var tcs = new TaskCompletionSource<object>();
  36.         tcs.SetResult(response);
  37.         return tcs.Task;
  38.     }
  39.  
  40.     public override Collection<HttpParameterDescriptor> GetParameters()
  41.     {
  42.         return this.originalAction.GetParameters();
  43.     }
  44.  
  45.     public override Type ReturnType
  46.     {
  47.         get { return typeof(HttpResponseMessage); }
  48.     }
  49.  
  50.     public override Collection<FilterInfo> GetFilterPipeline()
  51.     {
  52.         return this.originalAction.GetFilterPipeline();
  53.     }
  54.  
  55.     public override Collection<IFilter> GetFilters()
  56.     {
  57.         return this.originalAction.GetFilters();
  58.     }
  59.  
  60.     public override Collection<T> GetCustomAttributes<T>()
  61.     {
  62.         return this.originalAction.GetCustomAttributes<T>();
  63.     }
  64.  
  65.     public override HttpActionBinding ActionBinding
  66.     {
  67.         get { return this.actionBinding; }
  68.         set { this.actionBinding = value; }
  69.     }
  70. }

And that’s basically it. The code in the gallery will contain both projects (global CORS + per-action CORS), along with a simple project which can be tested (in CORS-enabled browsers, such as Chrome, IE10+ and FF3.5+) for both services.

[Code from this post]