Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

How to Use CORS Requests in Internet Explorer 9 and Below

5.00/5 (2 votes)
17 Apr 2014Apache3 min read 14.1K  
This article explains how you can automatically proxy CORS requests in jQuery without changing your existing code.

This article explains how you can automatically proxy CORS requests (Cross-origin resource sharing) in jQuery without changing your existing code. The proxy is integrated in ASP.NET and works with all ASP.NET libraries like WebForms, Mvc and WebApi.

In OneTrueError, we have separated the UI from the back-end by using a pure REST service in WebApi. This requires that our UI can use Ajax requests across different sub domains. Unfortunately, that’s not supported in Internet Explorer 9 and below. Instead of having to recode all our knockout view models, I did another alternative.

I inject logic into the jQuery pipeline which checks the browser version before an Ajax request is made. If the browser is IE9 or below, I modify the URI to invoke the CorsProxy instead. This is done in the background without you having to do anything special.

So if you do an Ajax request like this...

JavaScript
 $(function() {
     $.ajax({
         url: "http://api.yourdomain.com/user/1",
         type: 'GET',
         dataType: 'json',
         crossDomain: true,
         data: '22',
         success: function(result) {
             //some logic in here.
         },
         error: function (request, status, error) {
             //handle errors here
         }
     });
});

... it will work for all browsers except Internet Explorer (9 and below). In IE9, you will get the “No transport” error message. If you do not want to use text/plain content-type and just GET/POST, you have to stop using CORS. More details can be found here.

The Solution

However, I’ve created a nuget package that will help you solve it.

cors

Install CorsProxy.aspnet in your front-end project and add the following to your RouteConfig:

JavaScript
public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        //important, must be added before the default route.
        routes.EnableCorsProxy();

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

The CorsProxy will copy all headers and the content from the request to our proxy request and finally copy all information from the proxied response to the ASP.NET response. That means that you can add custom headers, use whatever HTTP method or content you like. The proxy will handle that.

Finally, you have to add the JavaScript that injects the proxy into the jQuery ajax handling. Open your BundleConfig and add jquery.corsproxy.js to your jQuery bundle:

JavaScript
public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                    "~/Scripts/jquery-{version}.js",
                    "~/Scripts/jquery-corsproxy-{version}.js"));
        
        // [...]
    }
}

Done! Now jQuery should gracefully downgrade to proxying for CORS requests if IE9 or below is used.

Implementation

jQuery has a method called $.ajaxSetup which was added in version 1.1. In it, there is an option called beforeSend which is invoked before every Ajax request is made. Hence, it can be used to check Internet Explorer version and then change the URI if required. However, since version 1.5, there is another method called $.ajaxPrefilter which is intended to replace $.ajaxSetup. I therefore check if $.ajaxPrefilter is defined or not and use the method that exists.

The trick is to change the URI and then add the original uri as a custom HTTP header.

JavaScript
$.ajaxPrefilter(function (options, originalOptions, jqXhr) {
    if (!window.CorsProxyUrl) {
        window.CorsProxyUrl = '/corsproxy/';
    }
    // only proxy those requests
    // that are marked as crossDomain requests.
    if (!options.crossDomain) {
        return;
    }

    if (getIeVersion() && getIeVersion() < 10) {
        var url = options.url;
        options.beforeSend = function (request) {
            request.setRequestHeader("X-CorsProxy-Url", url);
        };
        options.url = window.CorsProxyUrl;
        options.crossDomain = false;
    }
});

It’s not more complicated than that.

Server-side

At the server side, I’ve created a custom IHttpHandler. The great thing with IHttpHandler is that it’s defined in System.Web and will therefore work with all dialects of ASP.NET. The down side is that it requires some additional configuration to be activated compared to IHttpModule. But let’s start by looking at the IHttpHandler implementation:

JavaScript
public class CorsProxyHttpHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        var url = context.Request.Headers["X-CorsProxy-Url"];
        if (url == null)
        {
            context.Response.StatusCode = 501;
            context.Response.StatusDescription =
                "X-CorsProxy-Url was not specified. The corsproxy should only be invoked 
                 from the proxy JavaScript.";
            context.Response.End();
            return;
        }

        try
        {
            var request = WebRequest.CreateHttp(url);
            context.Request.CopyHeadersTo(request);
            request.Method = context.Request.HttpMethod;
            request.ContentType = context.Request.ContentType;
            request.UserAgent = context.Request.UserAgent;
            
            if (context.Request.AcceptTypes != null)
            request.Accept = string.Join(";", context.Request.AcceptTypes);

            if (context.Request.UrlReferrer != null)
                request.Referer = context.Request.UrlReferrer.ToString();

            if (!context.Request.HttpMethod.Equals("GET", StringComparison.OrdinalIgnoreCase))
                context.Request.InputStream.CopyTo(request.GetRequestStream());

            var response = (HttpWebResponse)request.GetResponse();
            response.CopyHeadersTo(context.Response);
            context.Response.ContentType = response.ContentType;
            context.Response.StatusCode =(int) response.StatusCode;
            context.Response.StatusDescription = response.StatusDescription;

            var stream = response.GetResponseStream();
            if (stream != null && response.ContentLength > 0)
            {
                stream.CopyTo(context.Response.OutputStream);
                stream.Flush();
            }
        }
        catch (WebException exception)
        {
            context.Response.AddHeader("X-CorsProxy-InternalFailure",  "false");

            var response = exception.Response as HttpWebResponse;
            if (response != null)
            {
                context.Response.StatusCode = (int)response.StatusCode;
                context.Response.StatusDescription = response.StatusDescription;
                response.CopyHeadersTo(context.Response);
                var stream = response.GetResponseStream();
                if (stream != null)
                    stream.CopyTo(context.Response.OutputStream);

                return;
            }

            context.Response.StatusCode = 501;
            context.Response.StatusDescription = exception.Status.ToString();
            var msg = Encoding.ASCII.GetBytes(exception.Message);
            context.Response.OutputStream.Write(msg, 0, msg.Length);
            context.Response.Close();
        }
        catch (Exception exception)
        {
            context.Response.StatusCode = 501;
            context.Response.StatusDescription = "Failed to call proxied url.";
            context.Response.AddHeader("X-CorsProxy-InternalFailure", "true");
            var msg = Encoding.ASCII.GetBytes(exception.Message);
            context.Response.OutputStream.Write(msg, 0, msg.Length);
            context.Response.Close();

        }
    }

    public bool IsReusable { get { return true; }}
}

As you can see, it will also handle error responses. The X-CorsProxy-InternalFailure header indicates if it’s the CorsProxy itself that has failed or the server that we call.

The routes.EnableCorsProxy(); is an extension method that adds our custom route to the route table. The route specifies that our CorsProxyRouteHandler should be used. Check the github repository for more information.

Code

The code is available on github along with a sample project. The library is released using the Apache license.

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0