Auto-generated JQuery Access to WCF REST Service

I’ve been working on a WCF REST service using Visual Studio 2010 RC, .NET 4.0 and Entity Framework.

I started building a quick site to administer some parts of the app and debug the data easier. This being a location aware project, I decided to use Bing Maps and therefore a lot of javascript and JQuery goodness.

After writing a the javascript code to perform basic CRUD operations on two data types it became clear this was going to be very repetitive. The code required to perform CRUD operations is very consistent. Consistent like the auto-generated help pages are.

I decided to build a way to auto-generate a javascript access object to my WCF REST service. Here’s my first crack at it.

The Pitch A js file that is a one-to-one javascript clone of the a webservice api. All implemented using jquery Ajax callbacks. “Sure. What’s so special about that?”

Here’s what’s so special my friends. Its 100% generated on the fly directly by the webservice itself! [oohs from the crowd] When I make a change to the webservice, any javascript client automagically gets the updated js file.[aahs from the crowd] Just add the 5 lines of code to jquery-enablify(real word) any webservice(seriously. any webservice), and you’re done. Just set it, and forget it![crowd waves $5 bills in the air]

This makes javascript access to a webservice much simpler. All you need to write in js is event handling and UI logic now.

Creating the JQuery Code Generator I came across this helpful blog post about programming your own WCF help page. This was the real kickstart to all of this.

First, find the current endpoint and all of its operations.

internal static string GenerateJqueryJs()
{
    ServiceEndpointCollection endpoints =
        OperationContext.Current.Host.Description.Endpoints;
    OperationDescriptionCollection operations = null;
    Type contract = null;
    ServiceEndpoint currentEndpoint = 
               endpoints.Where(e > e.Contract.Name == 
                            OperationContext.Current.EndpointDispatcher.ContractName).FirstOr   Default();

    contract = currentEndpoint.Contract.ContractType;
    operations = currentEndpoint.Contract.Operations;

    String functions = GenerateFunctions(operations, contract);
    String classTemplate = JQueryTemplates.ClassTemplate;

    return classTemplate.Replace("{functions}", functions)
        .Replace("{class_name}", contract.Name);

}

The GenerateFunction method iterates over all the operations and writes a jquery method for each one. It then takes the returned string off all the functions and insert it into the class template which simply wraps all the functions in a single javacscript “class” to isolate them from other javascript functions.

private static String   GenerateFunctions(OperationDescriptionCollection operations, Type contract)
{
StringBuilder js = new StringBuilder();
foreach (OperationDescription operation in operations)
{
    js.Append(Environment.NewLine);

    WebGetAttribute get = operation.Behaviors.Find<WebGetAttribute>();
    WebInvokeAttribute invoke = operation.Behaviors.Find<WebInvokeAttribute>();
    MethodInfo method = contract.GetMethod(operation.Name);

    if (get != null)
    {
        js.Append("\r\n" +
            GenerateGetMethod(JQueryTemplates.GetTemplate, get, method) + ",");
        js.Append("\r\n" +
                GenerateGetMethod(JQueryTemplates.GetByUriTemplate, get, method) + ",");
    }
    else if (invoke != null)
      {
      switch (invoke.Method)
      {
          case "PUT":
              js.Append("\r\n" +
                  GenerateInvokeMethod(JQueryTemplates.PostTemplate,invoke, method) + ",");
              break;
          case "DELETE":
              js.Append("\r\n" +
                  GenerateInvokeMethod(JQueryTemplates.DeleteTemplate,invoke, method) + ",");
              break;
          case "POST":
              js.Append("\r\n" +
                  GenerateInvokeMethod(JQueryTemplates.PostTemplate,invoke, method) + ",");
              break;
          default:
              continue;
      }
  }
  else
  {
      continue;
  }

} return js.ToString().TrimEnd(' ', '\n', '\r', ','); }

This method simply determines what the HTTP verb is and calls a generateXXXMethod method to generate the actual javascript function code. You’ll notice the using a JQueryTemplates class. This class simply contains string constants holding templates for the jquery functions.

For example. The javascript function template for GET verbs is as follows:

  public const string GetTemplate = @"
  {method_name} : function({params}successCallback, errorCallback) {
  jQuery.ajax({
      type: 'GET',
      url: {uri},
      contentType: 'application/json; charset=utf-8',
      success: function (responseText) {
          var response = eval(responseText);
          successCallback(response);
      },
      error: function (xhr, ajaxOptions, thrownError) {
          var error = eval('(' + xhr.responseText + ')');
          errorCallback(error);
      }
  });
  }";

The GenerateGetMethod simply starts with this template and does a search and replace on all placeholders with the real value. Placeholders are contained in {squiggly brackets}.

Enabling the service Now that the code is in place to generate templated JQuery code from a WCF service, the next step is to actually expose the endpoint. This requires only a slight modification to the standard approach to WCF REST service endpoints.

  [WebGet(UriTemplate = "/jqueryservice.js", BodyStyle = WebMessageBodyStyle.Bare)]
  [OperationContract]
  public Stream Javascript()
{
    string jsFileContents = WebHttpJavascriptGenerator.GenerateJqueryJs();
    Encoding encoding = Encoding.ASCII;
    WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain";
    byte[] returnBytes = encoding.GetBytes(jsFileContents);
    return new MemoryStream(returnBytes);
}

The only change here is returning that I’m returning a Stream instead of an object. WCF does not serialize Streams which allows you to write raw content to the output. The first line is the only real logic. The rest is boilerplate.

The Service is Ready That’s it! Your auto-generated JQuery javascript file exposing your WCF REST service is ready to go at

yourservice/jqueryservice.js Consuming The Service from Javascript All that hard work(not really) leads up to this. First you need to ensure all your ajax requests add the accept header of “application/json”.

jQuery.ajaxSetup({
    dataType: "json",
    contentType: "application/json; charset=utf-8",
    beforeSend: function (xhr) {
        xhr.setRequestHeader("Accept", "application/json")
        xhr.setRequestHeader("Authorization", authorization)
    }
});

This is also where you would add any other required headers such as Authorization. From this point forward, all JQuery ajax requests will contained the headers defined in the “beforeSend” function.

Now the fun stuff.

ItemService.GetItems(
    function(items){
    //access all items here...
    },
    function(error){
    //handle error
    }
);

ItemService.UpdateItem(item,
    function (response) {
    //item was updated
    },
    function (error) {
    //handle error</div>
    }
);

That’s it. Now functions to access the REST service are built on the fly and guaranteed always up to date. No more synchronizing APIs.

What’s Next? While I really like this solution, there are a few things to improve.

  1. As you can see these templates are JQuery specific. With a little bit of refactoring, I’d like to make it easier to add new outputs. Scriptaculous and actual HTML create/update forms come to mind. The first thing would be to move the templates out of code and into external files.
  2. I’d like to update the templates to allow optional and named parameters the same way JQuery does for callbacks.
  3. Javascript intellisense files for Visual Studio.
  4. Add caching to the endpoint to prevent regenerating the code every time.
  5. Finer control over output formating. Javascript naming convention lowercase function names.
  6. The code that generates the list of parameters is very limited right now. There’s no support for endpoints with querystring parameters and multiple parameters support is untested.

That’s it for now. I’ll post further improvements as they happen.