5/12/2008

installing a Subversion server on Windows

Update: This Guide is now largely obsolete, because Brian wrote an installer that will do all this stuff for you. Check out hisannoucement*, or go straight to the svn1clicksetup project page on tigris.

Subversion sounds pretty cool. It’s a mature, powerful revision-control system that acts a lot like CVS, adds support for atomic commits and real renames, just won the Jolt award, and is free. What more can you ask for?

I’ve been intending to install Subversion for quite a while, but I kept putting it off, because it looked like a daunting task. But when I actually decided to go do it, it took me all of an hour and a half to get it installed and working. If somebody had just written down what I needed to do to set up Subversion on Windows, with a real server running as a real Windows service, then it probably would’ve only taken me ten minutes, and I would’ve done it weeks ago.

Here, then, is the Mere-Moments Guide to installing a Subversion server on Windows. (It may look a bit intimidating, but really, it’s not.)

Some quick notes on the Guide:

  • These instructions assume you’re using Windows 2000 or XP. (You’d better be; the Subversion server won’t run on Win9x.)
  • If you want to know more about Subversion than just how to install it, check out the free O’Reilly Subversion book online and the not-free Pragmatic Version Control using Subversion.
  • For Subversion to do you much good, you’ll have to add a new “project” (essentially a directory) to your repository, to put files in. In these instructions, I’m assuming that your new project will be called monkey(because mine was).
  • Feel free to skip steps and to play around; you’ll learn more that way, because things won’t work right and you’ll have to figure out why.

And now, on to the Guide.

  1. Download everything
    1. Go to http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=91 and download the most recent svn-x.y.z-setup.exe. At the time of this writing, the latest version was svn-1.2.0-setup.exe.
    2. Go to http://dark.clansoft.dk/~mbn/svnservice/ and download SVNService.zip.
    3. Go to http://tortoisesvn.tigris.org/download.html and download the most recent installer. At the time of this writing, the latest version was TortoiseSVN-1.1.7-UNICODE_svn-1.1.4.msi. (It doesn’t have to be the exact same version as the svn installer you got in step 1. See the compatibility chart.)
  2. Install the server and the command-line client
    1. Run svn-x.y.z-setup.exe and let it install stuff.
    2. Go to Control Panel > System, go to the Advanced tab, and click the “Environment Variables” button at the bottom. Click the “New” button (either one, but if you’re undecided, use the one under “System variables”), set “variable name” to SVN_EDITOR, and “variable value” to the path and filename of a text editor of your choice (e.g., C:\Windows\Notepad.exe). OK all the way out.
  3. Create a repository and configure access
    1. Create a new directory somewhere out of the way; this is where your repository will live, but you’ll almost never actually open the files directly. I made a directory called svn_repos directly under my C:\Documents and Settings, just so it’d be out of the way.
    2. Open a command prompt and type: svnadmin create “C:\Documents and Settings\svn_repos”
    3. In Windows Explorer, browse to the C:\Documents and Settings\svn_repos\conf directory (which svnadmin just created for you), and edit a couple of config files:
      1. Open the svnserve.conf file in a text editor, and uncomment the [general]anon-access = readauth-access = write, and password-db = passwd lines. Save.
      2. Open the passwd file in a text editor, uncomment the [users] line, and add the username and password you want to use when connecting to your subversion server. Save.
  4. Start the server manually, and create a project
    1. In your command window, type: svnserve –daemon –root “C:\Documents and Settings\svn_repos”
    2. Open a second command window, and type svn mkdir svn://localhost/monkey
    3. You’ll see the text editor you specified in step II.2, with some text already in it. Type a comment, like “Created the monkey project”, at the beginning of the file (before the line starting with “–”). Save the file and close the editor.
    4. If your Subversion login is the same as your Windows login, then type your password (the one you put in the passwd file) at the prompt, and hit Enter. If your Subversion login is different from your Windows login, then just hit ENTER at the password prompt, and Subversion will then ask for both your login and your password.
    5. Subversion should tell you that it “Committed revision 1.” Congratulations! You just checked a change into Subversion. Throw yourself a party. (Yes, creating a directory is a revisioned change — you can go back and get the repository as of a time before that directory existed. This is novel stuff for folks like me who still use VSS at work.)
    6. It’s conventional to have /trunk, /branches, and /tags subdirectories for each project (your code goes into trunk, and the others are where you put, well, branches and tags). Go ahead and type svn mkdir svn://localhost/monkey/trunk (and notice that, after you enter a checkin comment, it doesn’t prompt you for your password again — it’s smart like that).
  5. Start the server for real
    1. Go back to the command window that’s running svnserve. Hit Ctrl+C to stop it.
    2. Open the SVNService.zip that you downloaded earlier. Extract SVNService.exe into your Subversion bin directory (Program Files\Subversion\bin). Yes, it’s important that you put it in this directory; it has to be in the same place as svnserve.exe from the Subversion distribution.
    3. In a command prompt, type svnservice -install –daemon –root “C:\Documents and Settings\svn_repos”
    4. Go to Control Panel > Administrative Tools > Services, double-click the SVNService service, and change its startup type from “Manual” to “Automatic”. Now Subversion will start every time you start Windows.
    5. Start the SVNService service (by selecting it in the Services list, and clicking the “play” toolbar button).
    6. Go back to a command prompt, and type svn ls svn://localhost/
      This will list all the files in the root of the repository. If all is well and you’ve got a real Subversion server running now, you should see: monkey/
  6. Install TortoiseSVN
    Sure, you can get by with a command-line client, but TortoiseSVN is cool — it integrates Subversion into Windows Explorer. You get little overlay icons showing the status of each file (in sync, needs to be checked in, not yet in the repository, etc.), and you can do pretty much everything you need by right-clicking on files and folders.

    1. Run the TortoiseSVN installer you got back in part I.
    2. Create a monkey directory somewhere on your hard drive. Right-click somewhere in that folder and select “SVN Checkout…” Type svn://localhost/monkey/trunk/ for the repository URL and click OK.
    3. Create a file in that directory, any file. Right-click the file and select TortoiseSVN > Add. Notice the little plus-sign icon that appears.
      The file hasn’t actually been checked in yet — Subversion’s commits are both batched and atomic, so this new file, together with any other new files you added, any files you changed, any files you deleted, any files you renamed, any directories you added or deleted or renamed, will all show up on the server all at once, as a single revision and a single checkin, the next time you right-click and select “SVN Commit”.
  7. Make it run on the network
    Are you kidding? You’re already networked. Go to another computer on your LAN, install TortoiseSVN, and do an “SVN Checkout…”. When you specify the repository URL, use the same URL you did before, but replace “localhost” with the actual name of the computer that’s running the Subversion service (so in my case, the repository URL is svn://marsupial/monkey/trunk/ — nice little menagerie, there).

And there ya go — Subversion up and running on Windows, in mere moments or less.

jQuery AJAX calls to a WCF REST Service

Since I've posted a few jQuery posts recently I've gotten a bunch of feedback to have more content on using jQuery in Ajax scenarios and showing some examples on how to use jQuery to cut out ASP.NET Ajax. In this post I'll show how you can use jQuery to call a WCF REST service without requiring the ASP.NET AJAX ScriptManager and the client scripts that it loads by default. Note although I haven't tried it recently the same approach should also work with ASMX style services.

WCF 3.5 includes REST functionality and one of the features of the new WCF webHttp binding is to return results in a variety of ways that are URL accessible. WCF has always supported plain URL HTTP access, but it's not been real formal and had somewhat limited functionality as parameters had to be encodable as query string parameters. With the webHttp binding there's now an official WCF protocol geared towards providing ASP.NET AJAX JSON compatibility (using WebScript behavior) as well of a slightly cleaner raw JSON implementation (basic webHttp binding).

You can return XML (default), JSON or raw data from WCF REST services. Regardless of content type, natively WCF always wants to return content in a 'wrapped' format which means that both inbound parameters and outbound results are wrapped into an object.

Let's take a look at the message format for a REST JSON service method.

[ServiceContract(Name="StockService",Namespace="JsonStockService")]    
public interface IJsonStockService
{
    [OperationContract]          
    [WebInvoke(Method="POST",
               BodyStyle=WebMessageBodyStyle.Wrapped,
               ResponseFormat=WebMessageFormat.Json
    )]
    StockQuote GetStockQuote(string symbol);

..

The input message on the wire looks like this:

{"symbol":"MSFT"}

The response looks like this:

{"GetStockQuoteResult":
        {"Company":"MICROSOFT CP",
        "LastPrice":30.00,
        "LastQuoteTime":
        "\/Date(1208559600000-0700)\/",
        "LastQuoteTimeString":"Apr 18, 4:00PM",
        "NetChange":0.78,
        "OpenPrice":29.99,
        "Symbol":"MSFT"}
}

Notice that in both cases an object is used. For the inbound data all parameters are wrapped into an object and rather than just passing the value, the name of the parameter becomes a property in the JSON object map that gets sent to the server. This is actually quite useful - if you're just sending a raw JSON structure you could only pass a single parameter to the server - and that option is also available via the Web BodyStyle=WebMessageBodyStyle.Bare option on the service method.

The outbound result set is also wrapped into an object which is a lot less useful. This is a hold over from WCF which wraps all responses into a message result object, which usually makes sense in order to support multiple result values (ie. out parameters etc.). In a Web scenario however this doesn't really buy you much. Nevertheless if you want to pass multiple parameters to the server you have to use this wrapped format along with the result value.

Calling with jQuery

If you're using jQuery and you'd like to call a WCF REST service it's actually quite easy either with bare or wrapped messages. Bare messages are easier to work with since they skip the wrapping shown above, but as I mentioned you're limited to a single input parameter. So if your service has any complexity you'll likely want to use wrapped messages.

You can opt to either call services using the ASP.NET Ajax logic (WebScriptService behavior) or using the raw service functionality which is shown above.

To call these methods with jQuery is fairly straight forward in concept - jQuery includes both low level and highlevel methods that can call a URL and return JSON data. The two methods available are $.getJSON() which automatically parses result JSON data and $.ajax(), which is a lower level function that has many options for making remote calls and returning data.

getJSON() is useful for simple scenarios where the server returns JSON, but it doesn't allow you to pass JSON data TO the server. The only way to send data to the server with getJSON is via query string or POST data that is sent as standard POST key/value pairs. In all but the simplest scenarios getJSON() is not all that useful.

The lower level $.ajax method is more flexible, but even so it still lacks the capability to pass JSON data TO the server. So little extra work and some external JSON support is required to create JSON output on the client as well as dealing with Microsoft Ajax's date formatting.

Personally I prefer to use a wrapper method for making JSON calls to the server to encapsulate this functionality. Note although this method seems somewhat lengthy it deals with a few important issues that you need to take care of when calling WCF REST Services:

// *** Service Calling Proxy Class
function serviceProxy(serviceUrl)
{
    var _I = this;
    this.serviceUrl = serviceUrl;
 
    // *** Call a wrapped object
    this.invoke = function(method,data,callback,error,bare)
    {
        // *** Convert input data into JSON - REQUIRES Json2.js
        var json = JSON2.stringify(data); 
 
        // *** The service endpoint URL        
        var url = _I.serviceUrl + method;
 
        $.ajax( { 
                    url: url,
                    data: json,
                    type: "POST",
                    processData: false,
                    contentType: "application/json",
                    timeout: 10000,
                    dataType: "text",  // not "json" we'll parse
                    success: 
                    function(res) 
                    {                                    
                        if (!callback) return;
 
                        // *** Use json library so we can fix up MS AJAX dates
                        var result = JSON2.parse(res);
 
                        // *** Bare message IS result
                        if (bare)
                        { callback(result); return; }
 
                        // *** Wrapped message contains top level object node
                        // *** strip it off
                        for(var property in result)
                        {
                            callback( result[property] );
                            break;
                        }                    
                    },
                    error:  function(xhr) {
                        if (!error) return;
                        if (xhr.responseText)
                        {
                            var err = JSON2.parse(xhr.responseText);
                            if (err)
                                error(err); 
                            else    
                                error( { Message: "Unknown server error." })
                        }
                        return;
                    }
                });   
    }
}
// *** Create a static instance
var Proxy = new serviceProxy("JsonStockService.svc/");

WCF services are called by their URL plus the methodname appended in the URL's extra path, so here:

JsonStockService.svc/GetStockQuote

is the URI that determines the service and method that is to be called on it.

The code above uses the core jQuery $.ajax() function which is the 'low level' mechanism for specifying various options. Above I'm telling it to accept raw string input (in JSON format), convert the response from JSON into an object by evaling the result, as well as specifying the content type and timeout. Finally a callback handler and error callback are specified.

Note that I override the success handler here to factor out the wrapped response object so that the value received in the callback handler is really only the result and not the wrapped result object. More on this in a second.

The call for the above StockQuote(symbol) call looks like this (including some app specific code that uses the result data):

var symbol = $("#txtSymbol").val();            
Proxy.invoke("GetStockQuote",{ symbol: symbol },
    function (result)
    {   
        //var result = serviceResponse.GetStockQuoteResult;
 
        $("#StockName").text( result.Company + " (" + result.Symbol + ")" ) ;
        $("#LastPrice").text(result.LastPrice.toFixed(2));
        $("#OpenPrice").text(result.OpenPrice.toFixed(2));
        $("#QuoteTime").text(result.LastQuoteTimeString); 
        $("#NetChange").text(result.NetChange.toFixed(2));   
 
        // *** if hidden make visible
        var sr = $("#divStockQuoteResult:hidden").slideDown("slow");
 
        // *** Also graph it
        var stocks = [];
        stocks.push(result.Symbol);
        var url = GetStockGraphUrl(stocks,result.Company,350,150,2);                
        $("#imgStockQuoteGraph").attr("src",url);
    },
    onPageError);

Parameters are passed in as { parm1: "value1", parm2: 120.00 } etc. - you do have to know the parameter names as parameters are matched by name not position.

The result is returned to the inline callback function in the code above and that code assigns the StockQuote data into the document. Notice that the result returned to the callback function is actually NOT a wrapped object. The top level object has been stripped off so the wrapper is not there anymore.

If you look at the the ajaxJSON function, you can see that it looks for the first result property in the actual object that WCF returns and uses IT to call the callback function instead - so it's indirect routing. This saves you from the one line of code commented out above and having to know exactly what that Result message name is ( WCF uses Result). Not that one line of code would kill you, but it's definitely cleaner and more portable.

The same approach should also work with ASMX style services BTW which uses the same messaging format.

JSON encoding

Note that the ajaxJSON function requires JSON encoding. jQuery doesn't have any native JSON encoding functionality (which seems a big omission, but was probably done to preserve the small footprint). However there are a number of JSON implementations available. Above I'm using the JSON2.js file from Douglas Crockford to serialize the parameter object map into JSON.

There's another wrinkle though: Date formatting. Take another look at the stock quote returned from WCF:

{"GetStockQuoteResult":
        {"Company":"MICROSOFT CP",
        "LastPrice":30.00,
        "LastQuoteTime":
        "\/Date(1208559600000-0700)\/",
        "LastQuoteTimeString":"Apr 18, 4:00PM",
        "NetChange":0.78,
        "OpenPrice":29.99,
        "Symbol":"MSFT"}
}

There's no JavaScript date literal and Microsoft engineered a custom date format that is essentially a marked up string. The format is a string that's encoded and contains the standard new Date(milliseconds since 1970) value. But the actual type of the date value in JSON is a string. If you use standard JSON converters the value will be returned as a string exactly as you see it above. I've talked about the date issues, and hacking existing JSON implementations before. I've modified Crockford's JSON2.JS to support the Microsoft date format so it properly encodes in and outbound data. You can download the hacked JSON2_MsDates.zip if you're interested. You can look at the code to see the modifications that were required, which essentially amounts to pre filtering parsed data before evaling on the .toJSON end and dropping the Data format that Date.prototype.toJSON() produces and instead creating a string in the required format above when doing object encoding.

Bare Messages

If you want a cleaner message format and you're content with single parameter inputs to functions then the WebMessageBodyStyle.Bare can work for you. Bare gives you a single JSON parameter you can pass that is automatically mapped to the first and only parameter of a method. You can't use Bare with any service methods (other than GET input) that include more than one parameter - the service will throw an exception when you access any method (beware: it's a RUNTIME error!).

Bare messages are easier to work with but they are limited because of the single parameter. You can use a single parameter on the server and make that input a complex type like an array to simulate multiple parameters. Using input objects or arrays can work for this. While this works realize that WCF requires an exact type match so any input 'wrapper' types you create yourself have to be mappable to a .NET type.

My first instinct with WCF's web bindings was always to use Bare, but ultimately the wrapped format provides more flexibility even if it is a little uglier on the wire. For AJAX services wrapped seems to make more sense.

Ideally, I would have preferred even more control - wrapped input messages and bare output messages, but I guess you can't have everything ...

Other Input Alternatives

Passing JSON messages is one thing you can do - the other option is to pass raw POST variables, which is something that can be done natively with jQuery without requiring a JSON encoder. Basically jQuery allows you to specify data as an object map, and it can turn the object into regular encoded POST parameters.

[OperationContract]          
[WebInvoke(Method="POST",
           BodyStyle=WebMessageBodyStyle.Bare,
           ResponseFormat=WebMessageFormat.Json
 )]
StockQuote GetStockQuote(string symbol);

You'd also need to mark your class:

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class JsonStockService : StockServiceBase

and enable ASP.NET compatibility in web.config (see my WCF REST Configuration Post for details)

If you prefer the simplified logic and you can use POST input data (which works well if you rather post back to a handler or the same ASPX page) you can get away with the following:

function ajaxJsonPost(method,data,callback,error)
{
    var url = "JsonStockService.svc/" + method;
    $.ajax( { 
                url: url,
                data: data,
                type: "POST",
                processData: true,
                contentType: "application/json",
                timeout: 10000,
                dataType: "json",
                success: callback,
                error: error
            });   
}

When you send data like this you can actually change the message format to Bare and get just a raw object response. jQuery can either except a raw POST string for the data parameter or an object whose properties and values are turned into POST key value pairs.

If you want to use POST behavior with WCF though, you need to enable ASPNET Compatibility on the REST service - otherwise the HttpContext.Current.Request is not available since WCF REST by default is trying to be host agnostic. For more information on how to configure WCF REST services check my previous post on WCF REST configuration last week.

This format might be preferrable if you are indeed building a public API that will be externally accessed. Raw POST data interchange is more common for many Ajax libraries, and also lends it self to straight HTTP clients that don't have JSON encoding features built in. For public APIs this makes plenty of sense. Remember that if you care about date formatting you may want to add the explicit JSON2 parsing code into the success callback (I left this out here for simplicities sake).

Error Handling

One more issue you'll want to be very careful of with WCF REST Services when you're using non-WebScriptService (ASP.NET AJAX style) behavior: When an error occurs WCF unfortunately throws an HTML error page rather than a JSON or XML fault message. That's a big problem if you want to return meaningful error messages to your client application - the only way to retrieve the error is by parsing the messy and very bulky HTML document returned.I've tried finding some way to get the REST services to throw a full JSON based error message and I haven't found a way to do this. JSON error messages seem to only work when you're using WebScriptService which is the full ASP.NET AJAX emulation. Under WebScriptService behavior the message returns the standard Exception like structure that includes a .Message and .StackTrace property that lets you echo back errors more easily.

In the end this means that even if you are using a non-MS Ajax client it might be the best solution to use the ASP.NET AJAX style WebHttp binding, simply because it provides the behavior that you most commonly require. There's nothing lost by doing so. You don't incur any client ASP.NET AJAX client requirements, but you do get the wrapped format input and exceptions properly wrapped on errors, plus this format is easier to implement because it doesn't require any special attributes on each individual operation/method as it's a fixed format. On the downside you do lose the ability to use UrlTemplates which might be useful in some situations, but it's probably not a common scenario that you need this for pure AJAX services.

Passing a JSON object to a WCF service with jQuery

This example uses WCF to create a service endpoint that will be accessible via an ASP.NET page with jQuery/AJAX. We will use AJAX to pass a JSON object from the client-side to the webservice. We will only use jQuery to connect to the web service, there will be no ASP.NET AJAX library used. Why no ASP.NET AJAX library? jQuery is already included in the project and it can handle all the necessary AJAX calls and functionality that we would want if we were using the ASP.NET AJAX script library. We're also going to save about 80kb of overhead (much more if in debug mode) by excluding the ASP.NET AJAX library. This is in no way saying that the ASP.NET AJAX library isn't useful... As a matter of fact if we were to do the same example with the library we could save ourselves from writing extra code. However the point of this example is to show that we can access the web service even if we don't have a nicely generated client-side proxy a la ASP.NET AJAX.

The WCF Service:

I'm going to start by adding an AJAX-enabled WCF Service to a Website. (Make sure you're running the correct version of .NET - I am using 3.5 here)

After adding the service it opens up to the service's code-behind file. Go ahead and browse around the file for a second.

The first thing I'm going to point out is to make sure that the "AspNetCompatibilityRequirements" is set to "Allowed":

[code:c#]
[AspNetCompatibilityRequirements( RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed )]
[/code]

This attribute indicates that our service should run in ASP.NET compatibility mode. If it were not "Allowed" we would not be able to access the service from ASP.NET. This attribute is automatically generated when you add the "AJAX-enabled WCF Service." For a detailed explanation of the attribute go to MSDN.

Looking at the generated code-behind file we can see it has placed a "DoWork()" method with the "OperationContract" attribute. This is created by default but lets keep it since we will be using this method to run this example. One thing we want to add is a "WebGet" attribute and set the "RequestFormat" to "Json." WebGet associates the operation with a UriTemplate (not discussed in this example) as well as the GET verb. Setting the RequestFormat allows us to define that the Request should be in JSON format. Our "DoWork()" method should now look like this:

[code:c#]
[OperationContract]
[WebGet( RequestFormat=WebMessageFormat.Json )]
public void DoWork()
{
// Add your operation implementation here
return;
}
[/code]



The Data/Object Structure:

We want to pass in a "Person" object to the "DoWork()" method so lets quickly create a Person object with properties for a Name, Age and the types of Shoes they own (first thing that popped into my head). This class will also serve as the structure for our JSON object.

[code:c#]
[Serializable]
[DataContract( Namespace = "http://www.dennydotnet.com/", Name = "Person" )]
public class Person
{
private string _name = string.Empty;
private int _age = 0;

[DataMember( IsRequired = true, Name = "Name" )]
public string Name
{
get { return _name; }
set { _name = value; }
}

[DataMember( IsRequired = true, Name = "Age" )]
public int Age
{
get { return _age; }
set { _age = value; }
}

[DataMember( IsRequired = true, Name = "Shoes" )]
public List Shoes;

}
[/code]

We've decorated our Person class as a DataContract specifying the Namespace and Name. We've also decorated our properties with a DataMember attribute. We've set "IsRequired" for each one to true and specified the Name. You really only need to specify the "Name" if it's going to be different than the property name. For example you could have a property named "Level" and the DataMember attribute's Name set to "Rank." We can now go back and modify our "DoWork()" method to receive a Person object as a param. It should now look like the following:

[code:c#]
[OperationContract]
[WebGet( RequestFormat=WebMessageFormat.Json )]
public void DoWork(Person p)
{
// Add your operation implementation here
return;
}
[/code]

The Web.Config File:

You'll need to make a few changes to your web.config file before you can access your service. You'll need to add a serviceBehavior to allow httpGet and we'll also add some helpful debugging options too. Add the following to your web.config:

Below

[code:xml]
<serviceBehaviors>
<behavior name="ServiceAspNetAjaxBehavior">
<serviceMetadata httpGetEnabled="true" httpGetUrl="" />
<serviceDebug httpHelpPageEnabled="true" includeExceptionDetailInFaults="true" />
behavior>
serviceBehaviors>
[/code]


Between [here] your service node should look like this:
[code:xml]
<service name="Service" behaviorConfiguration="ServiceAspNetAjaxBehavior">
<endpoint address="" behaviorConfiguration="ServiceAspNetAjaxBehavior"
binding="webHttpBinding" contract="Service" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
service>
[/code]

A security note about the following line:

[code:xml]
<serviceDebug httpHelpPageEnabled="true" includeExceptionDetailInFaults="true" />
[/code]


Allowing exception details can expose internal application information including personally identifiable or otherwise sensitive information. Setting the option to true is only recommended as a way to temporarily debug your service!!

Your Web.Config should look like the following: (pardon the colors)

The Front-End:

Now that the service is created and configured we can move to the front-end (make sure jQuery.js is included in your ASP.NET page). First let's create a sample JSON object that we will pass to the service. We'll create the JSON object based on the structure of the Person class.

[code:js]
var mydata = { "Name":"Denny", "Age":23, "Shoes":["Nike","Osiris","Etnies"] };
[/code]


If you're not too familiar with JSON this is what our JSON object looks like as an object (JsonViewer):

We need to somehow communicate with the WCF service and since we're using jQuery we can use the library's built-in AJAX methods. The code below creates an AJAX call. the headers are set to GET and the contentType is application/json. We set the url to the path to our WCF service's svc file with a trailing / and then the name of the method we want to execute. In this case we're calling the "DoWork()" method. "data" will be passed in to our function and processData should be set to false so that jquery does not try to auto-process our data. We've also added a success and error function to let us know what happens after executing the AJAX.

[code:js]
function sendAJAX(data) {
$.ajax({
type: "GET",
contentType: "application/json",
url: "Service.svc/DoWork",
data: data,
processData: false,
success:
function(msg){
alert( "Data Saved!" );
},
error:
function(XMLHttpRequest, textStatus, errorThrown){
alert( "Error Occured!" );
}
});
}
[/code]

Now unfortunately there is a small issue here. We must send the actual JSON string as the value for DoWork's Person p param and there's no easy way of turning your JSON object into a string. If you try "data.toString()" you'll just get an "[object Object]" value (remind you of anything?), which is not what we want. So here's a slightly modified function that will take your JSON and turn it into a string.

Note* The JSON de/serialization handles Date/Time in a specific way. The json2string function below does not take this into account. I'm sure there are some implementations out there which will work with ASP.NET AJAX but this one does not. For more information on this you can go here.

Update [4/11/08]: The javascript below has a few issues so it's been suggested that you should use the JSON.org version to "stringify" your object. You can download the script from here.

Update [4/25/08]: Rick Strahl has modified the JSON.org script so that it will properly create the dates to work with ASP.NET AJAX (read his post)

[code:js]
function json2string(strObject) {
var c, i, l, s = '', v, p;

switch (typeof strObject) {
case 'object':
if (strObject) {
if (strObject.length && typeof strObject.length == 'number') {
for (i = 0; i < v =" json2string(strObject[i]);" class="kwrd">if (s) {
s += ',';
}
s += v;
}
return '[' + s + ']';
} else if (typeof strObject.toString != 'undefined') {
for (i in strObject) {
v = strObject[i];
if (typeof v != 'undefined' && typeof v != 'function') {
v = json2string(v);
if (s) {
s += ',';
}
s += json2string(i) + ':' + v;
}
}
return '{' + s + '}';
}
}
return 'null';
case 'number':
return isFinite(strObject) ? String(strObject) : 'null';
case 'string':
l = strObject.length;
s = '"';
for (i = 0; i < c =" strObject.charAt(i);" class="kwrd">if (c >= ' ') {
if (c == '\\' || c == '"') {
s += '\\';
}
s += c;
} else {
switch (c) {
case '\b':
s += '\\b';
break;
case '\f':
s += '\\f';
break;
case '\n':
s += '\\n';
break;
case '\r':
s += '\\r';
break;
case '\t':
s += '\\t';
break;
default:
c = c.charCodeAt();
s += '\\u00' + Math.floor(c / 16).toString(16) +
(c % 16).toString(16);
}
}
}
return s + '"';
case 'boolean':
return String(strObject);
default:
return 'null';
}
}
[/code]

Now that we have a function to turn our JSON object into a string we need to go back and update the "mydata" variable that we defined above. After applying the json2string function we should have the following:

[code:js]
var mydata = { "Name":"Denny", "Age":23, "Shoes":["Nike","Osiris","Etnies"] };
var jsonStr = "p=" + json2string(mydata);
[/code]

Notice that I prepended the "p=" string to our json string. "p" matches the parameter name in our "DoWork()" method. So if our parameter name was "Dude" ( i.e. DoWork(Person Dude) ) then we would use "Dude=" instead.

Now that we've built the querystring to the web service we can see what our call is going to look like:

http://www.dennydotnet.com/Service.svc/DoWork/?p={ "Name":"Denny", "Age":23, "Shoes":["Nike","Osiris","Etnies"] }

You may get a URL Encoded value too, which would look like:

http://www.dennydotnet.com/Service.svc/DoWork/?p=%7b+%22Name%22%3a%22Denny%22%2c+%22Age%22%3a23%2c+%22Shoes%22%3a%5b%22Nike%22%2c%22Osiris%22%2c%22Etnies%22%5d+%7d%3b

Go ahead and link "jsonStr" to the "SendAjax()" javascript method so we can debug our service and verify that the data was passed through to the service... check it out:

And now you just need to implement your logic in the DoWork() method. Notice how you don't have to do any de/serialization on the WCF service side either, it's already done for you. Now you should certainly implement some exception management so that you don't get any invalid data, or even add some authentication, but I'll leave that up to you...