Working with untyped JSON in a WCF service
Since NetFX 3.5, WCF has support for working with JSON data. You can define your data contracts according to the JSON “schema” you expect to receive / want to return, plug them into your operation contracts, and your server is talking JSON. With this feature you can define the contract such as:
- public class Person
- {
- public string Name { get; set; }
- public int Age { get; set; }
- public Person[] Children { get; set; }
- }
- [ServiceContract]
- public interface ITest
- {
- [WebInvoke(RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
- int Create(Person person);
- [WebGet(ResponseFormat = WebMessageFormat.Json)]
- Person Find(int personId);
- }
and handle requests or return responses such as the ones below:
- { "Name": "John Doe",
- "Age": 30,
- "Children": [
- { "Name": "John Jr", "Age": 8 },
- { "Name": "Jane Doe", "Age": 5}]
- }
So this feature works quite well, with only one problem. You need to know the “schema” of the data you’re receiving (or returning). There are some cases where the service doesn’t know it upfront – for example, when the definition of the schema is part of the message itself (and you want to populate a data grid from it):
- {
- "columns": ["Name", "Age", "Occupation"],
- "data": [
- { "Name": "John Doe", "Age": 30, "Occupation": "Accountant" },
- { "Name": "Jane Roe", "Age": 29, "Occupation": "Doctor" }
- ]
- }
Currently, unless you want to parse the JSON yourself (or let WCF parse the JSON into “XML” using the JSON/XML mapping, and parse the information from the “XML” nodes yourself), WCF doesn’t support this scenario well. You’re tied to the data contract, and if you don’t have a data contract, you’re stuck.
In comes JsonValue
In Silverlight (since SL2), there is an API which deals with JSON in an untyped way. The JsonValue classes provide a DOM in which one can load a JSON document, and work with it without linking it to any specific CLR types. I like to think of JsonValue as the equivalent to XElement in the XML world: JsonValue : DataContractJsonSerializer :: XElement : DataContractSerializer. Silverlight, however, is a client-only platform, and as such the JsonValue classes didn’t have any integration with the service model (i.e., it can’t be used in an operation contract).
We found that the JsonValue abstraction, however, is quite interesting, so we decided to port it to the desktop framework, and hook it up to WCF. So you can now consume that “schema-less” value
- [ServiceContract]
- public class withUntypedJson
- {
- [WebInvoke(ResponseFormat = WebMessageFormat.Json, UriTemplate = "/SumFields/{fieldName}")]
- int SumFields(string fieldName, JsonObject input)
- {
- int result = 0;
- if (input.ContainsKey("data"))
- {
- JsonArray data = input["data"] as JsonArray;
- if (data != null)
- {
- for (int i = 0; i < data.Count; i++)
- {
- if (data[i].ContainsKey(fieldName))
- {
- result += data[i][fieldName].ReadAs<int>();
- }
- }
- }
- }
- return result;
- }
- }
Now, besides porting the code from Silverlight, we also added additional features such as Linq support, better casting, among others, to make the experience of using the new APIs simpler. The code above, for example, can be rewritten simply as
- [ServiceContract]
- public class withUntypedJson
- {
- [WebInvoke(ResponseFormat = WebMessageFormat.Json, UriTemplate = "/SumFields/{fieldName}")]
- int SumFields2(string fieldName, JsonObject input)
- {
- var values = from d in input.ValueOrDefault("data")
- select d.Value.ValueOrDefault(fieldName).ReadAs<int>(0);
- return values.Sum();
- }
- }
I want to use it; now what?
Since the next iteration of .NET Framework (where this feature may be included) is still a long time away, we decided to publish the code to enable users to take advantage of this feature now. As of this morning, we have a new Codeplex site at https://wcf.codeplex.com which contains the binaries needed to use this feature and all its source code, so if you need it the way it is, simply use it. If you need some change, you can either open an issue and wait for the next update (which should be fairly quick), or even fix it yourself, so you don’t get blocked. The JsonValue code is on the “jQuery Support” link on the main page.
Go ahead, download it, let us know what you think!
More information
I only touched the surface of this new API. You can find more information at the following pages:
- JsonValue walkthrough: https://hashtagfail.com/post/1432485895/jsonvalue-json-walkthrough
- JsonValue feature details: https://wcf.codeplex.com/wikipage?title=JsonValue
Comments
- Anonymous
November 04, 2010
Hi Carlos,I've seen that services that are being created to be consumed from jQuery using this new feature aren't implementing a service contract interface, like we're doing in traditional WCF services (SOAP and REST (System.ServiceModel.Web).What's WCF Team position about?!Thanks a lot. - Anonymous
November 04, 2010
What's WCF Team position about that?! - Anonymous
November 20, 2010
Hi Israel,It's true that traditionally in WCF, the contract-based approach indicated that one should first define the interface, then the implementation, but there is not an "official" guidance in this regard. That makes a lot of sense in many cases - when you're sharing the contract between the client and the server (i.e., using ChannelFactory<T> on the client), when you want to explictly define the operations of your service.For REST (and specifically the jQuery support), however, these advantages are not as clear. First, the contract cannot be used in the client (we only have support for plugging the JsonValue types in services, not on clients). Second (as mentioned in tomasz.janczuk.org/.../wcf-support-for-jquery-on.html), for WCF HTTP services we don't have any standard metadata / description format for HTTP/REST services, so a CLR construct that corresponds directly to such description is not as important. Third, for REST services we're actually not defining operations, we're defining resources which can be accessed by the clients, so the analogy with interfaces isn't as strong in the REST space.Having said all of that, you're welcome to continue splitting the interface and the implementation. Personally, I still do that, even for WCF HTTP REST services, because I like to be consistent in my code. But for people who are only interested in HTTP/REST services, the "rapid" approach of having all the definitions and implementation in one single place can be a valid choice as well. - Anonymous
February 01, 2011
Hiwhen i implemented using the same like this:[WebInvoke(UriTemplate = "", Method = "POST")] public SampleItem Create(JsonObject js) { // TODO: Add the new instance of SampleItem to the collection throw new NotImplementedException(); }I am getting an error :Type 'System.Json.JsonObject' cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute. If the type is a collection, consider marking it with the CollectionDataContractAttribute. See the Microsoft .NET Framework documentation for other supported typescan you please let me know how to correct the same - Anonymous
February 02, 2011
Prasad, in order for the JsonValue/JsonObject/JsonArray/JsonPrimitive to be recognized by WCF within operations, you need to add the WebHttpBehavior3 in your endpoint (instead of the WebHttpBehavior, which is what you'd normally use if writing a REST endpoint). That behavior is in the Microsoft.ServiceModel.Web.jQuery, which is installed by the installer from the codeplex project. - Anonymous
March 01, 2012
A side comment to the "what's next?" part - consider looking into the ASP.NET Web APIs, which have most of the functionality mentioned in this post in an architecture directed to HTTP services.