How to Implement Cross Domain Requests (CORS) in WebAPI, old school

WebAPI is awesome. It allows .NET developers to quickly set up a public API for any data with minimal effort. WebAPI has been available for a while and with each iteration, it grows stronger and more versatile. However, there is no point in offering an API that no one can consume it.

WebAPI works great straight out of the box for GET requests. However, once you start using it for POST, PUT or DELETE operations, then CORS kicks in and drops requests from hitting the server. CORS stops any cross domain requests so if your api is running at www.myapi.com and a request from www.mywebsite.com comes in, the request will be dropped. This is a security feature to ensure that requests from unknown domains cannot hit the server.

This is great, as your data is secure by default. However, there may be a legitimate reason for wanting to allow public access, like in a public API. The latest WebAPI version (2.2 at the time of writing this) provides full and first-class support for COR. This has been a requested feature for a while and the workarounds have always a pain. A tutorial on how to enable CORS on WebAPI version 2 and later can be found in this post.

But we are going old school! This post describes how to implement CORS on all versions of WebAPI before the 2.0 release. There are a few ways to do this, e.g. through the web config, but I will show you how to implement a message handler to intercept Http Requests and handle them accordingly.

In your WebAPI project add a new class named CorsMessageHandler. Add the following code in the class:

public class CorsMessageHandler : DelegatingHandler
    {
        private const string Origin = "Origin";
        private const string AccessControlRequestMethod = "Access-Control-Request-Method";
        private const string AccessControlRequestHeaders = "Access-Control-Request-Headers";
        private const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
        private const string AccessControlAllowMethods = "Access-Control-Allow-Methods";
        private const string AccessControlAllowHeaders = "Access-Control-Allow-Headers";

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            return request.Headers.Contains(Origin) ?
                ProcessCorsRequest(request, ref cancellationToken) :
                base.SendAsync(request, cancellationToken);
        }

        private Task<HttpResponseMessage> ProcessCorsRequest(HttpRequestMessage request, ref CancellationToken cancellationToken)
        {
            if (request.Method == HttpMethod.Options)
            {
                return Task.Factory.StartNew(
                    () =>
                        {
                            var response = new HttpResponseMessage(HttpStatusCode.OK);
                            AddCorsResponseHeaders(request, response);
                            return response;
                        },
                    cancellationToken);
            }

            return base.SendAsync(request, cancellationToken).ContinueWith(
                task =>
                    {
                        var resp = task.Result;
                        resp.Headers.Add(AccessControlAllowOrigin, request.Headers.GetValues(Origin).First());
                        return resp;
                    },
                cancellationToken);
        }

        private static void AddCorsResponseHeaders(HttpRequestMessage request, HttpResponseMessage response)
        {
            response.Headers.Add(AccessControlAllowOrigin, request.Headers.GetValues(Origin).First());

            var accessControlRequestMethod = request.Headers.GetValues(AccessControlRequestMethod).FirstOrDefault();
            if (accessControlRequestMethod != null)
            {
                response.Headers.Add(AccessControlAllowMethods, accessControlRequestMethod);
            }

            var requestedHeaders = string.Join(", ", request.Headers.GetValues(AccessControlRequestHeaders));
            if (!string.IsNullOrEmpty(requestedHeaders))
            {
                response.Headers.Add(AccessControlAllowHeaders, requestedHeaders);
            }
        }
    }

Next, add a HandlerConfig class under the App_Start folder. Open and paste the following code:

public class HandlerConfig
    {
        public static void RegisterHandlers(Collection<DelegatingHandler> handlers)
        {
            handlers.Add(new CorsMessageHandler());
        }
    }

Now, we need to enable this handler, so open the Global.asax.cs file and add the following line inside the Application_Start() method:

HandlerConfig.RegisterHandlers(GlobalConfiguration.Configuration.MessageHandlers);

Your WebAPI is now ready to accept all HTTP verbs. If you are using a web client to execute ajax calls, then there is one more thing you need to add to your ajax call to ensure that CORS words on all browsers. In your javascript code, you need to add the following settings:

$.support.cors = true;
$.ajax({
    type: "POST",
    url: "your api url",
    data: JSON.stringify(someData),
    contentType: 'application/json',
    crossDomain: true,
    dataType: 'json',
    success: function(data) {
        if (typeof console == "object"){
            console.log("Success - " + data);
        }
    },
    error: function(xhr, textStatus, error) {
       if (typeof console == "object"){
           console.log(xhr.statusText);
           console.log(textStatus);
           console.log(error);
       }
   }
});

The important lines here are:

  • $.support.cors = true
  • crossDomain: true

Once you've configured all these, you are good to go.

Happy coding...

CM