September 30, 2011 8:28 AM
by Loren
Creating .NET Web Services for Firefox, IE, Chrome and Safari
Guest blogger Peter Webb returns with another in an occasional series of postings about application deployment.
Contents
Calling .NET Web Services from JavaScript
Our story so far: I've deployed a MATLAB-based .NET web service using Builder NE and the Windows Communcation Foundation (WCF), and I've written a .NET client program that lets my users access that service. What happens when someone using a Mac or a Linux box asks for access? They can't run the Windows client -- but if they've got a web browser, I can provide them a client built with HTML and a quirky little language called JavaScript.
I'll demonstrate how to build a platform-independent web service client by extending a WCF-enabled deployed MATLAB component to support calls from JavaScript. My application has three parts:
- A deployed MATLAB component implementing a WCF-enabled type safe API.
- A WCF server publishing the type safe API via a service contract.
- A JavaScript client that requests service operations from the server.
The server requires WCF and must run under Windows, but the client can run in any browser that supports JavaScript and HTML 5 (and nowadays that's most of them -- except, ironically, IE).
Two words of warning here: first, this post builds on the others in the type safe API series, so you'd be well-served to read them first (at the very least, read the initial WCF post); and second, the complexity of client server applications makes it impossible to fully describe how they work in this (relatively) short article. I've included links for most of the jargon, I hope -- click on them if there's something you don't understand. That being said, I do encourage you to keep reading. You can likely download and build the application without understanding all the theory behind it.
Exchanging Data with JavaScript Object Notation
For the client and server to successfully communicate, they must agree on a data format. The defacto standard data format for JavaScript clients, JavaScript Object Notation (JSON), represents all types of data with strings of JavaScript code, sacrificing storage efficiency for readabilty and ease of use. Essentially, the server sends little program fragments to the client, which executes them to recreate the data objects. Since the client and server trust each other implicitly in this case, transmitting executable code creates no security risks.
In a previous post, I developed a WCF service that publishes an IFractal ServiceContract with a single operation, snowflake. Here, I extend IFractal to support JSON output by making two changes. First, I add the [WebGet] attribute, specifying the output format WebMessageFormat.Json:
[ServiceContract] public interface IFractal { [OperationContract(Name = "snowflake")] [WebGet(ResponseFormat = WebMessageFormat.Json, UriTemplate = "/?n={n}&width={width}&height={height}")] FractalOutline snowflake(int n, int width, int height); }
UriTemplate defines a pattern determining how to map named web request parameters to snowflake's inputs.
The original snowflake returns two values: the first in the function's return value and the second via a C# out parameter. But my client invokes the service through JavaScript's XMLHttpRequest API, which only permits a single return value. Hence, the second change: returning the two pieces of data in a single structure. To enable WCF to automatically marshall the sructure, I decorate the return type, FractalOutline, with the [DataContract] attribute, and each of its fields with [DataMember].
[DataContract] public struct FractalOutline { [DataMember] public int[][] vectors; [DataMember] public int[] bbox; }
With these changes, the service will send a block of JSON code in response to HTTP requests.
Hosting the Service
I host the KochJSON service in a Windows console application, configuring its HttpBinding endpoint to publish the IFractal contract using the webHttpBinding protocol. A webHttpBinding endpoint listens for requests that use XML rather than the more complex SOAP, greatly simplifying the client code. I add the new endpoint to the application's configuration file, App.config:
<endpoint address="" binding="webHttpBinding" bindingConfiguration="webHttpConfig" contract="IFractal" name="HttpBinding" />
Clients making an XMLHttpRequest require an webHttpBinding endpoint.
A JavaScript Client
My client performs two tasks: requesting the outline of the Koch snowflake from the KochJSON web service and then drawing the snowflake in the browser window. IFractal's [WebGet] attribute defines the format of the URL serving the snowflake data. To retrieve the 4th iteration of the Koch snowflake, scaled to fit within a 300x300 rectangle, make the following request:
http://localhost:42017/KochJSON/?n=4&width=300&hieght=300
I've configured the service host to listen for client requests on port 42017 on my local machine. The string of parameters following the service name match the pattern specified by the UriTemplate I defined in the [WebGet] attribute of the IFractal [ServiceContract]. The parameter n here maps to snowflake's input n, and so on, and the service calls snowflake(4, 300, 300).
Making this request in JavaScript requires an XMLHttpRequest object, which you create with new:
var request = new XMLHttpRequest();
Call open to initialize the request with the HTTP method (GET), the HTTP address of the service, and true to specify the request should be made asynchronously. Then call send to make the request.
request.open("GET", "http://localhost:42017/KochJSON/?n=4&width=300&height=300", true) request.send(null);
The XMLHttpRequest object notifies me when the asynchronous request completes by invoking a callback function I provide. I convert the response data from its JSON text notation into a live JavaScript object by invoking eval:
var jsonOutline = eval( '(' + request.responseText + ')' );
My [DataContract] structure, FractalOutline, contains two fields, vectors and bbox. Since JSON data marshalling preserves field names, I retrieve the data from jsonOutline by referencing its vectors and bbox fields.
var outline = jsonOutline.vectors; var bbox = jsonOutline.bbox;
vectors and bbox are native JavaScript arrays, so I manipulate them using native JavaScript syntax. I draw the outline with a for-loop, at each step calling the HTML 5 canvas function lineTo:
x = x + outline[i][0]; y = y + outline[i][1]; context.lineTo(x, y);
There's a bit more code in the callback to manage errors and ensure that it doesn't start drawing until the entire outline is available, but it can't be more than ten lines or so. One line for data marshalling (the eval statement), a few lines to make and manage the request, but the bulk of the code I had to write myself focuses on solving the problem at hand, drawing the outline.
Building and Running the Example
Download the code from MATLAB Central.
The download contains the MATLAB function snowflake.m, a Visual Studio 2008 solution KochJSON.sln and an HTML page, KochSnowflake.html, defining the JavaScript client. Create the server program by following these three steps:
- Build the IFractal interface DLL.
- Create the Snowflake .NET assembly and the KochIFractal type safe interface.
- Compile the server program, KochJSON, after referencing IFractal and KochIFractal in the KochJSON project.
The client does not require compilation.
The file ReadmeWCFJSON.txt contains detailed instructions.
To run the example, open a DOS window for the server. Make sure the DOS window's runtime environment supports the execution of deployed MATLAB .NET components (you'll need the MCR on your PATH), then run KochJSON\KochJSON\bin\Debug\KochJSON.exe. When the server is ready, it prints a short message:
Koch JSON Snowflake Service started. Press any key to terminate service...
Activate the client by double-clicking on KochSnowflake.html. The first client to contact the server causes the server to load the MCR, which takes some time. However, subsequent requests process very rapidly. The server supports multiple clients -- try connecting from different architectures, using different browsers. HTML 5-capable browsers should display something like this:
Doing the Right Kind of Work
Retrieving the data and drawing the outline are domain tasks, central to the work I want to get done. Anything else, bookkeeping, data marshalling, managing the client server connection, is a distraction, an artifact created by the technologies I've chosen to implement my solution. Ideally, I'd like those technologies to manage themselves -- I'd like to concentrate on writing the code that solves the problem. And that's what deployed type safe APIs let me do -- with a native C# interface for my MATLAB functions, I can take advantage of .NET automation and application management tools that were previously inaccessible.
This JavaScript client required more effort than a WCF client generated by Visual Studio, but it is lighter weight and much more widely usable. Does that matter to you? Will you write cross-platform clients like this one? How important is standards compliance to your applications? Let us know what you think.
Get the MATLAB code (requires JavaScript)
Published with MATLAB® 7.13
Dr. Art Trembanis