Cross Domain ASP.NET Web API Call Using ajax

If you have created RESTful API (using ASP.NET Web API) and creating a nice UI using jQuery ajax call, you may be running into cross domain ajax call issue.  Under the same origin policy, you CAN’T make a call to Web API that is hosted on different domain than the one you are making a call from. You may be getting this error message.

“XMLHttpRequest cannot load http://localhost:12345/api/articles. Origin http://localhost:58486 is not allowed by Access-Control-Allow-Origin.”

As per my knowledge, there are two solutions to resolve this cross domain issue.

1) JSONP (JSON with padding): You can read wiki article about JSONP which explains in more details about how it works.

Here is how you make a JSONP request using jQuery ajax.

$.ajax({
         type: "GET",
         url: "http://localhost:12345/api/articles",
         dataType: "jsonp",
         contentType: "application/json;charset=utf-8"
}).done(function (msg) {
         $('#results').text(JSON.stringify(msg));
});
You have to specify jsonp in dataType parameter when making ajax call.
OR
$.getJSON("http://localhost:12345/api/articles/?callback=?",
function (data) {
   $('#results').text("success");
});
Web API url must be ended by ?callback=? which jQuery generates random callback function and pass it to URL.  If you monitor request call in fiddler or any developer tool, you will see something similar to this.  http://localhost:123/api/articles?callback=jQuery17208638584909494966_1345764737438&_=1345764737668

There is nothing new here when you make JSONP request.  Server wraps the JSON data in callback function which is passed in callback parameter of api call.  In our example, Server returns something like jQuery17208638584909494966_1345764737438&_=1345764737668(jsondata).

I have shown you that how client should be making a call.  We also need to make a change on server code to respond to JSONP request.  Since client code is making a call using JSONP, server also needs to understand that format and respond accordingly.  Nice thing in ASP.NET Web API is that you can write your own media type formatter and plug that in.  We will need JSONP media type formatter which will return something like   jQuery17208638584909494966_1345764737438&_=1345764737668(jsondata).

Here is the code for JSONP media type formatter.

public class JsonpMediaTypeFormatter : JsonMediaTypeFormatter
{
  private string callbackQueryParameter;

  public JsonpMediaTypeFormatter()
  {
      SupportedMediaTypes.Add(DefaultMediaType);
      SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript"));

      MediaTypeMappings.Add(new UriPathExtensionMapping("jsonp", DefaultMediaType));
  }

  public string CallbackQueryParameter
  {
      get { return callbackQueryParameter ?? "callback"; }
      set { callbackQueryParameter = value; }
  }

  protected override Task OnWriteToStreamAsync(Type type, object value, Stream stream,
                                               HttpContentHeaders contentHeaders,
                                               FormatterContext formatterContext,
                                               TransportContext transportContext)
  {
      string callback;

      if (IsJsonpRequest(formatterContext.Response.RequestMessage, out callback))
      {
          return Task.Factory.StartNew(() =>
          {
              var writer = new StreamWriter(stream);
              writer.Write(callback + "(");
              writer.Flush();

              base.OnWriteToStreamAsync(type, value, stream, contentHeaders,
                                      formatterContext, transportContext).Wait();

              writer.Write(")");
              writer.Flush();
          });
      }
      else
      {
          return base.OnWriteToStreamAsync(type, value, stream, contentHeaders, formatterContext, transportContext);
      }
  }

  private bool IsJsonpRequest(HttpRequestMessage request, out string callback)
  {
      callback = null;

      if (request.Method != HttpMethod.Get)
      {
          return false;
      }

      var query = HttpUtility.ParseQueryString(request.RequestUri.Query);
      callback = query[CallbackQueryParameter];

      return !string.IsNullOrEmpty(callback);
  }
}
To register JSONP media type formatter, copy this code in application_start event in global.asax.
protected void Application_Start()
{
  ConfigureApi(GlobalConfiguration.Configuration);

  //register JSONP media type formatter
  var config = GlobalConfiguration.Configuration;
  config.Formatters.Insert(0, new JsonpMediaTypeFormatter());

  AreaRegistration.RegisterAllAreas();

  RegisterGlobalFilters(GlobalFilters.Filters);
  RegisterRoutes(RouteTable.Routes);

  BundleTable.Bundles.RegisterTemplateBundles();
}
JSONP only works with GET request.  You can not make HTTP PUT/DELETE call using JSONP.  Option# 2 works well in that case.

2) CORS Support in ASP.NET Web API. Carlo’s blog is very useful for me when implementing CORS support in ASP.NET Web API.

Quote from his blog about CORS support
CORS (Cross-Origin Resource Sharing) is a new specification which defines a set of headers which can be exchanged between the client and the server which allow the server to relax the cross-domain restrictions for all HTTP verbs, not only GET. Also, since CORS is implemented in the same XmlHttpRequest as “normal” AJAX calls (in Firefox 3.5 and above, Safari 4 and above, Chrome 3 and above, IE 10 and above – in IE8/9, the code needs to use the XDomainRequest object instead), the JavaScript code doesn’t need to worry about “un-padding” responses or adding dummy functions. The error handling is also improved with CORS, since services can use the full range of the HTTP response codes (instead of 200, which is required by JSONP) and the code also has access to the full response instead of only its body.
We only need to make a change in server side code to support CORS.  Client code will make json call normal way they are making.

We will need handler on server side that will investigate incoming request header and allow  access to request that is coming  from different domain.

Here is the handler code.
public class CorsHandler : DelegatingHandler
{
  const string Origin = "Origin";
  const string AccessControlRequestMethod = "Access-Control-Request-Method";
  const string AccessControlRequestHeaders = "Access-Control-Request-Headers";
  const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
  const string AccessControlAllowMethods = "Access-Control-Allow-Methods";
  const string AccessControlAllowHeaders = "Access-Control-Allow-Headers";

  protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
  {
      bool isCorsRequest = request.Headers.Contains(Origin);
      bool isPreflightRequest = request.Method == HttpMethod.Options;
      if (isCorsRequest)
      {
          if (isPreflightRequest)
          {
              HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
              response.Headers.Add(AccessControlAllowOrigin, request.Headers.GetValues(Origin).First());

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

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

              TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>();
              tcs.SetResult(response);
              return tcs.Task;
          }
          else
          {
              return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>(t =>
              {
                  HttpResponseMessage resp = t.Result;
                  resp.Headers.Add(AccessControlAllowOrigin, request.Headers.GetValues(Origin).First());
                  return resp;
              });
          }
      }
      else
      {
          return base.SendAsync(request, cancellationToken);
      }
  }
}
You have to register this handler in application_start event.
protected void Application_Start()
{
    ConfigureApi(GlobalConfiguration.Configuration);

    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);

    BundleTable.Bundles.RegisterTemplateBundles();

    //register cors handler
    GlobalConfiguration.Configuration.MessageHandlers.Add(new CorsHandler());
}
Reference:

Hope this helps.  If this post really helps you, please click the Google +1 button to show it really helps you save your time.

Posted in Microsoft Technology Tagged with: , ,
2 comments on “Cross Domain ASP.NET Web API Call Using ajax
  1. Raghav says:

    Very useful article…… Thanks a lot

  2. Thank you very much for posting this easy to understand CORS handler. I didn’t want to use JSONP and now I don’t have to. Awesome work sir, thank you :-)

Ads