September 3, 2013

Accessing a ServiceStack RESTful API using jQuery with CORS

The following applies to jQuery version 2.0.3 and ServiceStack version 3.9.59
My latest project is using ServiceStack.net to create a REST-ful API for an existing data interchange format. There are a few gotchas that are important to understand:

REST API DELETE Methods and jQuery

The specification may be a bit vague, but most server frameworks (including ServiceStack) ignore any parameters passed to DELETE methods in the body. Only parameters passed on the URL are sent into the method. This behavior is similar to a HTTP GET.
That is okay, but when you call an $.ajax method using jQuery, it doesn't serialize and attach the data to the URL, but instead puts it in the body...where the server promptly ignores it.
In order to handle this apparent disconnect, and use the same semantics in $.ajax, you need to move the parameters from the body into the URL. The following ajax prefilter code does the trick:
$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
  if (options.type == "DELETE") {
    options.url += ((options.url.indexOf('?') == -1) ? '?' : '&');
    options.url += options.data;
  }
});
By the way, here are the $.ajax calls that I am making to my service, without the prefilter call, the DELETE method doesn't work doesn't receive the data that is passed to it. If you use fiddler, you can see that the values are passed in the body, but the ServiceStack api doesn't receive them.
        var url = "http://localhost:50647/api/hello";
        var data = { name: "fred foobar" };

            $("#btnGet").click(function () {
                $.ajax(url, {
                    type: "GET",
                    dataType: "json",
                    data: data,
                    success: function (d) {
                        $('#result').text(d);
                    }
                });
            });

            $("#btnPost").click(function () {
                $.ajax(url, {
                    type: "POST",
                    dataType: "json",
                    data: data,
                    success: function (d) {
                        $('#result').text(d);
                    }
                });
            });

            $("#btnPut").click(function () {
                $.ajax(url, {
                    type: "PUT",
                    dataType: "json",
                    data: data,
                    success: function (d) {
                        $('#result').text(d);
                    }
                });
            });

            $("#btnDelete").click(function () {
                $.ajax(url, {
                    type: "DELETE",
                    dataType: "json",
                    data: data,
                    success: function (d) {
                        $('#result').text(d);
                    }
                });
            });


CORS and ServiceStack

ServiceStack is a nice, light-weight alternative to MVC WebAPI. However, there are places where the documentation (cough) is lacking. You can grep the code, but even then, there are several coexisting versions of the API. This leads to no small amount of confusion.
One example is CORS (Cross-Origin Resource Sharing). CORS is a way to allow cross domain scripting to work on modern browsers. The browser is supposed to recognize that you are performing a cross-platform query, and make an OPTIONS call to the server (pre-flight request). If the OPTIONS call returns the appropriate header information, along with a 204 (empty content), then the actual request is made. Internet Explorer 10 doesn't seem to care about CORS, but Chrome and Firefox do.You can find out more about CORS here.
ServiceStack has several only one combination of ways that I've found to successfully implement CORS in a service.

Use the CORS Plug-in

Inside your AppHost.cs file, or Global.asax you need to register the CorsFeature plug in:
Plugins.Add(new CorsFeature());
According to some of the documentation I read, this is all you need to do, but I also had to handle the OPTIONS call on the individual web services like this:
public void Options(Hello request) { }

Things that didn't work

This post indicates several other things that should work; but they didn't for me.