I took a quick look today at WCF's new capability to work with JSON data. I was looking forward to having WCF services providing this functionality and there are a few cool things that work with it. It's now essentially possible to create WCF Web Services that take JSON as input and produce JSON output and it's pretty easy to do in fact.
For example I can create a Svc style service in an ASP.NET application like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using TimeTrakker;
using System.Data;
using System.Web.Script.Services;
using System.ServiceModel.Web;
namespace TimeTrakkerWeb
{
[ServiceContract( Namespace="", Name="TimeTrakkerService")]
public interface ITimeTrakkerService
{
[OperationContract]
//[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest,
// ResponseFormat = WebMessageFormat.Json,
// UriTemplate = "helloworld/{name}")]
string Helloworld(string name);
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest,
ResponseFormat = WebMessageFormat.Json,
UriTemplate = "loadcustomer")]
//[WebGet(BodyStyle = WebMessageBodyStyle.WrappedRequest,
// ResponseFormat = WebMessageFormat.Json,
// UriTemplate = "loadcustomerGet/{Pk}")]
CustomerEntity LoadCustomer(string Pk);
}
public class TimeTrakkerService : ITimeTrakkerService
{
public string Helloworld(string name)
{
return "Hello World, " + name;
}
public CustomerEntity LoadCustomer(string Pk)
{
busCustomer Customer = new busCustomer();
Customer.Options.ThrowExceptions = true;
CustomerEntity cust = Customer.Load(int.Parse(Pk));
cust.InvoiceEntities = null;
cust.ProjectEntities = null;
return cust;
}
}
}
And the following in Web.config to configure the service:
<system.serviceModel>
<services>
<service name="TimeTrakkerWeb.TimeTrakkerService"
behaviorConfiguration="TimeTrakkerServiceBehavior">
<!--<endpoint contract="TimeTrakkerWeb.ITimeTrakkerService" binding="basicHttpBinding"/>-->
<endpoint contract="TimeTrakkerWeb.ITimeTrakkerService"
binding="webHttpBinding"
behaviorConfiguration="AjaxBehavior" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="TimeTrakkerServiceBehavior">
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="AjaxBehavior">
<enableWebScript />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
The key features to enable JSON access are the behavior configuration in the configuration file that adds the enableWebScript behavior and the additional attributes on each of the service method that should be exposed to JSON. Actually the attributes are required only if you explicitly need to use REST operations - the default behavior allows you to use POST based (ASMX style) JSON requests against the server where the input is a JSON parameter list.
That's all nice and neat if you're using MS Ajax. Unfortunately it appears that everything about this service uses the funky MS AJAX formatting for JSON result sets and inputs. For example the above result returns the following JSON string:
{"d":{"__type":"CustomerEntity:#TimeTrakker", "Pk":1,"UserId":"0 ","LastName":"Strahl", "FirstName":"Rick","Company":"West Wind Technologies","Address":"32 Kaiea Place","City":"Paia", "State":"HI ","Zip":"96779 ","Zip4":" ","Country":"USA", "CountryId":"US ","Phone":"(503) 914-6335", "Email":"rstradhl@west-wind.com","Fax":"1\/1\/1900","Notes":"", "Entered":"\/Date(1187593200000-0700)\/","Updated":"\/Date(1191366660000-0700)\/", "LastOrder":"\/Date(-2208960000000-0800)\/", "BillingRate":150.00,"Xml":null, "tversion":[0,0,0,0,0,0,184,67],"ProjectEntities":[],"InvoiceEntities":[]}}
Basically the result is wrapped in an additional type (presumably to allow for multiple results?) and there's a __type property attached to each object returned. Even simple result values at least include the wrapper type, so even say a string result includes the d: type.
If you're using MS AJAX on the client there's no issue - the client knows how to parse this JSON into a clean object. But if you're using some other mechanism - jQuery or Prototype for example to retrieve a result set you get back a funky object where you have to go result.d.LastName for example, which is ugly.
There are a few options on the WebGet (and WebInvoke) attribute that allow specifying the BodyStyle and one of the options there is BodyStyle.Bare, but unfortunately it doesn't work with any of these AJAX responses apparently.
That's a real shame too, because the WebGet and WebInvoke methods allow for a really nice REST model for accessing requests by using UrlTemplates. Notice the:
UriTemplate = "loadcustomer/{pk}")
in the attributes above which allows you to specify a parameter as part of the URL extension. So to retrieve a particular customer in this case you'd use a URL like this:
localhost/timetrakkerweb/services/TimeTrakkerService.svc/LoadCustomer/1
Multiple parameters can be specified in this same format. With a little URL Rewriting you can get some decent looking URLs to access your data fairly easily.
So what would client code (non MS Ajax look like to call this service?) Even with the funky JSON object results the code is still pretty trivial. Here I'm using my wwScriptLibrary (from wwHoverPanel) for the ajax callback:
<script type="text/javascript">
function CallService()
{
ajaxJson("services/timetrakkerservice.svc/LoadCustomer",{"Pk":"1"},Callback,onError);
}
function Callback(result)
{
alert(result); // string
alert( result.d.Company);
alert( result.d.ProjectEntities[1].CustomerEntity.LastName);
}
function onError(error)
{
alert("Error: " + error.message);
}
In this case I'm using the 'POST' mechanism to post back the result to the server. MS Ajax expects an object with a property for each of the parameters provided to the method which in this case is a single string parm of the customer id. I chose the string parm here because when using the GET mechanism WCF failed to convert the URL based 1 to an int on its own:
Operation 'LoadCustomer' in contract 'TimeTrakkerService' has a path variable named 'Pk' which does not have type 'string'. Variables for UriTemplate path segments must have type 'string'.
That's pretty lame given that simple values at least could easily be translated. However, typically it's better to use POST anyway.
If you're using MS Ajax you can add a script manager script reference to the service:
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Scripts>
<asp:ScriptReference Path="services/TimeTrakkerService.svc/js" />
</Scripts>
</asp:ScriptManager>
which adds the script proxy to the page and you can then directly call the Web Service by its fully qualified name (ie. Namespace.Interface.Class by default). Normally that'd be something like tempuri.org.ITimeTrakkerService.TimeTrakkerService, but I explicitly set the namespace of the ServiceContract to none and the name to just to TimeTrakkerService. So the service call is as simple as:
function CallService()
{
TimeTrakkerService.LoadCustomer("1",Callback);
}
function Callback(result)
{
alert(result);
alert( result.Company);
alert( result.ProjectEntities[1].CustomerEntity.LastName);
}
A bit cleaner of course.
Playing around a bit with the options there are a number of ways that services can be accessed using GET, POST and PUT. This is all controlled via attributes (WebGet, WebInvoke etc.) I found another annoying issue though: It appears you can't specify both WebGet and WebInvoke for the same service method. When you do you get:
Operation 'LoadCustomer' in contract 'TimeTrakkerService' has both WebGetAttribute and WebInvokeAttribute; only one can be present.
It's quite likely that you might want to expose both Get and Post behavior to a service, although POST is usually the preferred mechanism. Certainly in public access REST scenarios you'd want to give people access in a variety of ways.
On the other hand I was pleasantly surprised that WCF WAS able to handle a circular reference scenario with LINQ to SQL entities (which is what the above returns) nicely. It cut off the circular referencing nicely and appropriately returned me the list of child items.
All in all it's nice to see JSON support in WCF, but I'm not sure how well this will really play outside of MS AJAX. Given that REST services are supposed to be as lean as possible and easy to consume the issue of the extra type wrapper is kind of a show stopper. I hope that there is somehow a way to get WCF to produce clean JSON instead of this proprietary format.
Other Posts you might also like