Understanding WCF Services in Silverlight 2
Sunday, November 16, 2008
Contents
- Introduction
- The Async Pattern
- External Web Site Access
- Configuration Based Access
- Strongly Typed Client
- JSON Client Access
- Thread Waiting
- Using DevServer
- Conclusion
Introduction
In my article entitled Creating Streamlined, Simplified, yet Scalable WCF Connectivity, I explained streamlining and simplifying WCF connectivity and how you can use a private/public project model with WCF channel or a ChannelBase access to simplify your WCF connectivity. In this discussion, I would like to move our attention to the world of Silverlight. However, before we begin, it would probably be beneficial to you to review the concepts I wrote about in that article. Knowledge of the service and data contracts in the sample project is important for this discussion.
To begin, let me start off by reminding everyone that you shouldn't really rely on "Add Service Reference" in Visual Studio for magical service client creation. Frankly, the code is incredibly verbose, hard to manageable, edits are prone to being overwritten, and is almost always used as an excuse to not learning WCF. This is true for .NET and so very much more true in Silverlight. There's a reason Juval Lowy, in all his books and talks, repeatedly tells people to avoid using it. Fortunately, as professionals, we have the ability to understand how to do things without magic.
Since we are talking about WCF, we are talking about a system with a single consistent model. The concept of the ABC in WCF works the same for Hello World as it does for the most complex sales processing system. You need an address, a binding, and a contract for all WCF communication... or, well, any communication anywhere, even when talking to another person. You have to know to whom, how, and what. If you don't have these three, then there can't be any communication. With these three pieces of information, whoever, you can create a WCF channel to use for all service communication from ASMX interop to TCP connectivity to Named Pipes. WCF is completely streamlined.
However, since we are in the world of the web, there's one difference in how we access the services from our clients: we are dealing with asynchronous calls.
The Async Pattern
If you are familiar with the concepts of AJAX calls, then you should already be familiar with what I'm talking about. To put it simply, instead of calling a "do" method, you basically set a callback and call "beginDo". Then, instead of obtaining a response from "beginDo", we wait for a callback from the service. Think of it like phone tag between two people who never answer their phone. Person A calls person B and says "call me back at X". Then Person B calls Person A back at X; you don't sit there on someone's voicemail waiting for them to pick up (...like we did in the days of answering machines.)
When this idea is brought to the .NET/Silverlight world, we have something called the Async Pattern, which states that asynchronous methods are to follow the following pattern:
public IAsyncResult BeginOperation(/* <parameters> */, AsyncCallback callback, Object state) public /* <return> */ EndOperation(IAsyncResult result)
If you're never seen this before, then, it's high time you become deeply familiar with it. This pattern is used all over the place in myriad of different contexts. You can use MSDN to find various examples involving this pattern, or just keep reading to see how it's used to get the general idea. If you are going to take Silverlight seriously, then you absolutely must master this pattern.
Not only that, but you must also be very proficient with asynchronous call chains. This is when you call one async operation, wait for the callback, then that callback begins another async operation, whose callback may call another async operation. You just chain the async calls down the line to have a steady, deterministic program flow. This, of course, requires knowledge of the Async Pattern to which we return our focus,
When working with in Silverlight, this Async Pattern is the type of call pattern that you will be using for just about all service access with or without WCF. For example, there will be no GetPersonData method which returns a Person object. Instead, there will be a BeginGetPersonData method which returns an IAsyncResult object (which you may or may not use). Then, you wait for a callback, which has an IAsyncResult again which you use to obtain a result from EndGetPersonData.
Before we see seek to understand WCF in Silverlight using the Async Pattern, though, let's review how to access a WCF service using .NET so we can have a basis of comparison. As previously mentioned, all you need for any communication anywhere is an address (to whom), a binding (how), and a contract (what). This isn't just fancy marketing talk or something from a watered down presentation at a local free conference, the ABC of WCF is real. This is all you need.
As explained in my Creating Streamlined, Simplified, yet Scalable WCF Connectivity article, when you create a WCF service contract in .NET, you can reuse this same contract on both the service-side and the client-side per a private/public or service/implementation/client model. Below is the service contract used on the service-side for the duration for this article. This also services as the template for our Silverlight version of the contract.
using System; using System.ServiceModel; //+ namespace Contact.Service { [ServiceContract(Namespace = Information.Namespace.Contact)] public interface IPersonService { //- GetPersonData -// [OperationContract] Person GetPersonData(String personGuid); } }
We can use this contract with a binding and an address to create a channel, which you then use to communicate to the service. Here's all that's you need to do do call the WCF service:
BasicHttpBinding basicHttpBinding = new BasicHttpBinding(); EndpointAddress endpointAddress = new EndpointAddress("http://localhost:1003/Person.svc"); IPersonService personService = new ChannelFactory<IPersonService>(basicHttpBinding, endpointAddress).CreateChannel(); //+ Person person = personService.GetPersonData("F488D20B-FC27-4631-9FB9-83AF616AB5A6");
Replace BasicHttpBinding with NetTcpBinding and you have TCP communication, replace it with NetNamedPipeBinding and you have named pipes communication. It's that simple. No magic, code generation, parsing, fancy client tools, or even configuration is required. Just create a channel and make the call.
This is essentially the same type of thing that you would do in the world of Silverlight, but we need to do this following the Async Pattern. This means that our service contract in Silverlight will actually look slightly different than our service contract on the service. Here's our Silverlight version:
using System; using System.ServiceModel; //+ namespace Contact.Service { [ServiceContract(Namespace = Information.Namespace.Contact)] public interface IPersonService { //- BeginGetPersonData -// [OperationContract(AsyncPattern = true)] IAsyncResult BeginGetPersonData(String personGuid, AsyncCallback callback, Object state); //- LoadPerson -// Person EndGetPersonData(IAsyncResult result); } }
Notice that we are now following the Async Pattern and that the OperationContract attribute has declared that we are following the pattern. We may now rewrite our original .NET service-call code to follow the new paradigms and service contract to allow Silverlight interaction. Here's our new code:
BasicHttpBinding basicHttpBinding = new BasicHttpBinding(); EndpointAddress endpointAddress = new EndpointAddress("http://localhost:1003/Person.svc"); IPersonService personService = new ChannelFactory<IPersonService>(basicHttpBinding, endpointAddress).CreateChannel(); //+ AsyncCallback asyncCallBack = delegate(IAsyncResult result) { Person person = ((IPersonService)result.AsyncState).EndGetPersonData(result); this.Dispatcher.BeginInvoke(delegate { spMain.Children.Add(new TextBlock { Text = person.FirstName }); }); }; personService.BeginGetPersonData("F488D20B-FC27-4631-9FB9-83AF616AB5A6", asyncCallBack, personService);
Notice the first three lines are identical. After this, not much remains the same. Instead of doing a simple service-call with a synchronous response, we must set a callback and begin a request. In this particular example, the asyncCallBack object is being given an anonymous method to the BeginGetPersonData method. This anonymous method block will be called when the service calls returns a response back.
Take note of the third parameter in the BeginGetPersonData. Per the Async Pattern, this is "state". This is similar to the Tag object found in VB and in WPF. You can use this to place just about anything you want to send back to the callback. In this case, since we're it's an anonymous method, it's optional, but if the callback were a named method, then the callback may obtain that state by pulling the AsyncState property from the IAsyncResult object passed to the callback. In this example, I'm sending the channel itself as the state so that I can access the channel from the callback. This is important because this is how I'm able to call the EndGetPersonData method and retrieve the service operation response data, a Person object in this case.
Notice also also that the callback has a call to Dispatcher.BeginInvoke. You must do this if you are on a visual element and want to access visual elements in your callback. This will grant you access to the UI thread. Without this, an exception will be thrown.
External Web Site Access
Having said all that, if you were to try and run the code I sent to you based on our current service configuration, nothing would actually work. Why's that? Because in our original service-setup the service is hosted in a different web site than the web site itself. The service web site does nothing but WCF hosting, while our application web site hosts our Silverlight application. This basically allows you to keep all your entities highly cohesive and reusable. However, in this case, we are running up against a security wall. By default, you cannot use Silverlight to access a web site which differs by domain, IP address, or port number.
Fortunately, there's an easy way around this: you just need to add a ClientAccessPolicy.xml file in the service root. Here's a sample ClientAccessPolicy.xml file:
<?xml version="1.0" encoding="utf-8"?> <access-policy> <cross-domain-access> <policy> <allow-from> <domain uri="*"/> </allow-from> <grant-to> <resource path="/" include-subpaths="true"/> </grant-to> </policy> </cross-domain-access> </access-policy>
This particular example basically allows everyone everywhere to access the service, granting access to everything. This may be what you want or, perhaps, you want to lock it down a bit by placing a URI of the format http://www.tempuri.org/ in place of the *. Perhaps you even want to add multiple domain elements or, on the other end of the spectrum, restrict access to only a particular service path.
Now while this file looks all well and good, our Silverlight service call still won't work. Why in the world not? Because for WCF in Silverlight to call a service outside of the local web site, you must also specify what headers to allow. In our case, we want to allow the SOAPAction header. Here's our new example:
<?xml version="1.0" encoding="utf-8"?> <access-policy> <cross-domain-access> <policy> <allow-from http-request-headers="SOAPAction"> <domain uri="*"/> </allow-from> <grant-to> <resource path="/" include-subpaths="true"/> </grant-to> </policy> </cross-domain-access> </access-policy>
Now our WCF Silverlight service will work flawlessly.
For all the information you would ever need on this file, see Network Security Access Restrictions in Silverlight 2 on MSDN.
Configuration Based Access
Our service client call at this point is fully programmatic. This is nice, but perhaps you would like to utilize WCF configuration instead of creating Binding and Endpoint elements manually. This is a great idea, but how in the world do we access configuration in Silverlight when Silverlight is on the client-side, running in a web browser? As it turns out, Silverlight actually does include a configuration file, which gets embedded in to the final XAP file, the ServiceReferences.ClientConfig file.
To utilize this configuration feature, we first need to create the file and add it to our Silverlight project. Then, we must set its build action to Content. In Visual Studio 2008, you do this in the properties window for that file. This will force the file to be zipped in with the rest of the contents of the XAP file, which, if you didn't know, is really nothing more than a ZIP file readable by any ZIP client, much like Word 2007's DOCX format.
At this point, you may just copy/paste the configuration from a .NET WCF client to this Silverlight configuration file. Here's basically what it will look like at this point:
<configuration> <system.serviceModel> <client> <endpoint address="http://localhost:1003/Person.svc" binding="basicHttpBinding" contract="Contact.Service.IPersonService" name="BasicHttpBinding_IPersonService" /> </client> </system.serviceModel> </configuration>
At this point can change our code to use the configuration instead of the created binding and endpoint address objects:
IPersonService personService = new ChannelFactory<IPersonService>("BasicHttpBinding_IPersonService").CreateChannel(); //+ AsyncCallback asyncCallBack = delegate(IAsyncResult result) { Person person = ((IPersonService)result.AsyncState).EndGetPersonData(result); this.Dispatcher.BeginInvoke(delegate { spMain.Children.Add(new TextBlock { Text = person.FirstName }); }); }; personService.BeginGetPersonData("F488D20B-FC27-4631-9FB9-83AF616AB5A6", asyncCallBack, personService);
Now, once again, while this looks nice, when you compile and run the Silverlight application you will notice that it doesn't even pretend to work. Why? For some reason, you actually need to declare a basicHttpBinding element in the configuration file. Here's the new configuration file:
<configuration> <system.serviceModel> <bindings> <basicHttpBinding /> </bindings> <client> <endpoint address="http://localhost:1003/Person.svc" binding="basicHttpBinding" contract="Contact.Service.IPersonService" name="BasicHttpBinding_IPersonService" /> </client> </system.serviceModel> </configuration>
At this point, the Silverlight client will have full connectivity with the service.
Strongly Typed Client
At this point we could stop. The thing works. Mission Accomplished. However, when designing software, you want to make sure people call fall into success. In this case, a developer might forget how to create a WCF channel. Even then, system internals have nothing to do with the core competency of what's going on. Business developers need to be working on providing a business solution, not writing WCF interaction code. Thus, it's often a good idea to hide the internals of something before anyone else uses it. Even I, as a non-business developer, who love internals and gets great benefits from their understanding, prefer not to see internals in my every day development. Therefore, I normally hide the internal mechanics of the WCF channel by creating a client abstraction.
As with WCF in .NET, you may use the System.ServiceModel.ClientBase<T> class to create an abstraction of the internal WCF channel mechanics. However, due to the Async Pattern, using ClientBase in Silverlight is drastically different how I described it's usage for .NET in "Creating Streamlined, Simplified, yet Scalable WCF Connectivity". Creating a concrete ClientBase class is still fairly straightforward though.
The first thing we need to do is create a class that inherits from ClientBase<IYourServiceContract> where IYourServiceContract is, well, your service contract. However, unless you want a very complicated class, do not implement the IYourServiceContract on this ClientBase like you would normally do in .NET. Instead, for each operation in your service contract, create a method in this class. Don't worry about following the Async Pattern. Our point in creating this abstraction is to hide the internals mechanics, not to simply rearrange them. Having said that, you still need to have a callback parameter if your method returns a response.
In addition to this, make sure your method names make sense. By convention, you should suffix each operation method in your ClientBase with "Async". Then, inside the method, call your method prefixed with "Begin" on the channel created by the ClientBase class. Totally confused? Here's what I mean:
//- @GetPersonDataAsync -// public void GetPersonDataAsync(String personGuid, EventHandler<ClientEventArgs> callback) { Channel.BeginGetPersonData(personGuid, GetPersonDataCallback, callback); }
In reality, all you're doing is taking a call and passing it to the Channel with different parameters. Essentially you are hiding the Async Pattern used by this channel method with something that's more natural to developers. In this channel call, the second parameter, GetPersonDataCallback, is the WCF callback which is called when the actual service call is complete and the third parameter is the state for the Async Pattern. Here's the GetPersonDataCallback method:
//- $GetPersonDataCallback -// private void GetPersonDataCallback(IAsyncResult result) { EventHandler<ClientEventArgs> callBack = result.AsyncState as EventHandler<ClientEventArgs>; if (callBack != null) { callBack(this, new ClientEventArgs { Object = EndGetPersonData(result) }); } }
Since we set the callback as the state in the service call, it follows that we can pull that out and use it to send the response data back to the calling method using the standard .NET event pattern. In this case, the ClientEventArgs class is a simple EventArgs class which holds the response object:
public class ClientEventArgs : EventArgs { //- @Object -// public Object Object { get; set; } }
Now notice that when we are populating this newly created ClientEventArgs, we are setting it's Object property to the result of the EndGetPersonData, which is the second of the two Async Pattern components. It's implementation is extremely simple:
//- $EndGetPersonData -// private Person EndGetPersonData(IAsyncResult result) { return Channel.EndGetPersonData(result); }
Notice also that our ClientBase callback and End methods are also private, thus completely concealing the internal WCF mechanics from developers.
There's at least one more method we need to add to our class for it to be complete: the constructors. Personally, I like to add a constructor which accepts a System.ServiceModel.Channels.Binding object and a System.ServiceModel.EndpointAddress object as well as an overload which accepts a string representing the endpoint configuration name. These constructors basically do nothing but bridge to the base class:
//- @Ctor -// public PersonClient(String endpointConfigurationName) : base(endpointConfigurationName) { } public PersonClient(System.ServiceModel.Channels.Binding binding, EndpointAddress address) : base(binding, address) { }
Thus, we have successfully created a ClientBase class to abstract the internal mechanics for developers. To sum up, here's the complete class:
using System; using System.ServiceModel; //+ namespace SilverlightClient { public class PersonClient : ClientBase<Contact.Service.IPersonService> { //- @Ctor -// public PersonClient(String endpointConfigurationName) : base(endpointConfigurationName) { } public PersonClient(System.ServiceModel.Channels.Binding binding, EndpointAddress address) : base(binding, address) { } //+ //- @GetPersonDataAsync -// public void GetPersonDataAsync(String personGuid, EventHandler<ClientEventArgs> callback) { Channel.BeginGetPersonData(personGuid, GetPersonDataCallback, callback); } //- $GetPersonDataCallback -// private void GetPersonDataCallback(IAsyncResult result) { EventHandler<ClientEventArgs> callBack = result.AsyncState as EventHandler<ClientEventArgs>; if (callBack != null) { callBack(this, new ClientEventArgs { Object = EndGetPersonData(result) }); } } //- $EndGetPersonData -// private Contact.Service.Person EndGetPersonData(IAsyncResult result) { return Channel.EndGetPersonData(result); } } }
If you need to add more operations, and you most definitely will, all you need to do is copy/paste this pattern and do a which rename of a new things here and there. This will give you complete control over all your entire WCF in Silverlight. Using this class is now as simple as the following:
PersonClient personClient = new PersonClient("BasicHttpBinding_IPersonService"); personClient.GetPersonDataAsync("F488D20B-FC27-4631-9FB9-83AF616AB5A6", OnServiceCallback);
Then, of course, there's the callback method:
//- $OnServiceCallback -// private void OnServiceCallback(Object sender, ClientEventArgs ea) { Person person = ea.Object as Person; if (person == null) { return; } this.Dispatcher.BeginInvoke(delegate { spMain.Children.Add(new TextBlock { Text = person.FirstName }); wait.Set(); }); }
As simple as this is, though, you could actually keep the simplicity of this usage and still upgrade the class to be a bit more general. For this reason, I created my ObjectClient<TServiceContract> class, which is now part of my Themelia for Silverlight product (to be released in the future; see my .NET Themelia Framework here). Here's an example of using it:
ObjectClient<IPersonService> objectClient = new ObjectClient<IPersonService>("BasicHttpBinding_IPersonService"); objectClient.Begin("GetPersonData", OnObjectServiceCallback, null, "F488D20B-FC27-4631-9FB9-83AF616AB5A6");
Then, here's the service callback:
//- $OnObjectServiceCallback -// private void OnObjectServiceCallback(Object sender, ObjectClient<IPersonService>.ClientEventArgs ea) { Person person = ea.LoadResult<Person>(); this.Dispatcher.BeginInvoke(delegate { spMain.Children.Add(new TextBlock { Text = person.LastName }); wait.Set(); }); }
The mechanics of this class follow the same idea as the previous ClientBase, but this one does not use ClientBase, but, rather, creates the channel itself. Then, when the "Begin" method is called, the class internally passes the parameters to the proper "BeginMethodName" method property via dynamic invocation. Internally, the service callback also uses dynamic invocation to call "EndMethodName" to obtain the result. The result is then passed to the original callback using the same ClientEventArgs as seen in the PersonClient example.
Below is the ObjectClient code in its entirety:
#region Copyright //+ Copyright © Jampad Technology, Inc. 2008 //++ Lead Architect: David Betz [MVP] <dfb/davidbetz/net> #endregion using System; using System.ServiceModel; using System.ServiceModel.Channels; //+ namespace Themelia.ServiceModel { public class ObjectClient<TServiceContract> where TServiceContract : class { //- @ClientEventArgs -// public class ClientEventArgs : EventArgs { //- @Object -// public Object Object { get; set; } //- @UserState -// public Object UserState { get; set; } //+ //- @LoadResult -// public TResult LoadResult<TResult>() { if (this.Object is TResult) { return (TResult)this.Object; } //+ return default(TResult); } } //- $ObjectClientState -// private class ObjectClientState { //- @Callback -// public EventHandler<ClientEventArgs> Callback { get; set; } //- @MethodName -// public String MethodName { get; set; } //- @UserState -// public Object UserState { get; set; } } //+ //- $Channel -// private TServiceContract Channel { get; set; } //- $ContractType -// private Type ContractType { get; set; } //+ //- @Ctor private ObjectClient() { ContractType = typeof(TServiceContract); } public ObjectClient(Binding binding, EndpointAddress address) : this() { Channel = new ChannelFactory<TServiceContract>(binding, address).CreateChannel(); } public ObjectClient(String endpointConfigurationName) : this() { Channel = new ChannelFactory<TServiceContract>(endpointConfigurationName).CreateChannel(); } //+ //- @Begin -// public void Begin(String methodName, Object state, params Object[] parameterArray) { Begin(methodName, null, state, parameterArray); } public void Begin(String methodName, EventHandler<ClientEventArgs> callback, Object state, params Object[] parameterArray) { if (parameterArray != null) { Array.Resize<Object>(ref parameterArray, parameterArray.Length + 2); } else { parameterArray = new Object[2]; } parameterArray[parameterArray.Length - 1] = new ObjectClientState { Callback = callback, MethodName = methodName, UserState = state }; parameterArray[parameterArray.Length - 2] = new AsyncCallback(OnCallback); ContractType.InvokeMember("Begin" + methodName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.InvokeMethod | System.Reflection.BindingFlags.Public, null, Channel, parameterArray); } //- $OnCallback -// private void OnCallback(IAsyncResult result) { ObjectClientState state = result.AsyncState as ObjectClientState; if (state == null) { return; } Object obj = ContractType.InvokeMember("End" + state.MethodName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.InvokeMethod | System.Reflection.BindingFlags.Public, null, Channel, new Object[] { result }); if (state.Callback != null) { state.Callback(this, new ClientEventArgs { Object = obj, UserState = state.UserState }); } } } }
Feel free to upgrade it, modify it, or use it however you would like.
If you've been paying attention to the examples thus far, we have only been using the BasicHttpBinding binding. This is because it's the only native on that is supported by Silverlight 2.0. However, since we're on the web, we may very well be creating a Silverlight client to replace an AJAX client which used a WCF JSON service. Or, perhaps, you just prefer to use a JSON endpoint on your WCF service. Therefore, we need to be able to access JSON services efficiently. To do this, we turn to the System.Net namespace, namely the WebRequest class.
Essentially what we want to do is create an instance of the System.Net.WebRequest class and setup an async call chain. But first, you need to know the full URI you are going to access. This URI is the base endpoint address of the service combined with the relative endpoint address combined with the name of the service operation you are going to access.
We know from our service setup that the base address of our service is http://localhost:1003/Person.svc. The relative address is configured on the service side. Here's a configuration snippet from the service:
<service name="Contact.Service.PersonService"> <endpoint address="json" binding="webHttpBinding" contract="Contact.Service.IPersonService" behaviorConfiguration="JsonEndpointBehavior" /> </service>
The endpoint address, "json", is the second piece of information required, the relative endpoint address. The third piece of information in our case is "GetPersonData". Thus the full URI we need to pass to our WebRequest object is http://localhost:1003/Person.svc/json/GetPersonData.
Once we create a WebRequest object, we need to make sure the content type is set to "application/json" and make sure the HTTP method is set to POST. Without both of these set, we will get nothing but errors all over the place. With these set, we are then able to enter into an async chain of the steps that occur in a WebRequest.
The process begins by calling the BeginGetRequestStream method on our WebRequest object, passing it a callback. When that callback is called, we are then, per the Async Pattern, we able to call the EndGetRequestStream, which, if you recall, requires the IAsyncResult which was passed to our callback. At this point we are able to write information to the service and call BeginGetResponse, passing it another callback. Then, when that callback is called, we can call the EndGetResponse, passing it the new IAsyncResult object to obtain a System.Net.WebResponse object. Finally, we call GetResponseStream on this WebResponse object, which gives us the response data sent back to us from the service.
At this point we have the raw data sent back to us from the service. One hitch here is that an unmodified WCF JSON service likes to send back JSON with full namespaces for each member. Here's an example of what I mean:
{
"d": {
"__type": "Person:http:\/\/www.netfxharmonics.com\/service\/Contact\/2008\/11\/",
"City": "Unknown",
"FirstName": "John",
"Guid": "F488D20B-FC27-4631-9FB9-83AF616AB5A6",
"LastName": "Doe",
"PostalCode": "66062",
"State": "KS"
}
}
This kind of gets in the way of simple deserialization. Fortunately, that's not a problem as Silverlight 2 provides a different way to access JSON data through it's System.Json.JsonObject housed in the System.Json assembly. Be careful, though, every time you add an assembly to your Silverlight application, you increase the size of the Silverlight XAP file. When you add the 52k System.JSON assembly, you add 21k to the size of the XAP file. For this same, this takes the size of the XAP file from 12k to 33k. That's a huge increase. So, think ahead on how and if you really want to access JSON WCF services using this technique.
When we get our response data back in our final WebRequest callback, we can use the static System.Json.JsonObject.Load method to load a stream or TextReader into a System.Web.JsonValue object. This will give us a representation we can use to access any information we want by accessing the indexer of the object. However, before we can access our data, we need to open the data envelope. This is that "d" section in the WCF JSON response message. This is as simple as accessing the "d" indexer on the JsonValue object. Here's what were talking about so far:
JsonValue jsonValue = (JsonValue)JsonObject.Load(new StringReader(data))["d"];
This JsonValue object created here contains the WCF response information. The JsonValue class itself is the base class for System.Json.JsonArray and System.Json.JsonObject, thus, unless you know what your service is returning, you need to test which type the JsonObject.Load class created. This is as simple as this:
JsonType jsonType = value is JsonArray ? JsonType.Array : JsonType.Object;
At this point, if you're working with a JsonObject, as we will be in our example, you may access any member of the JSON data you want. You do this by reading values from a JsonValue by writing them to a stream or text writer. For example, here's the code to access the FirstName value:
StringBuilder builder = new StringBuilder(); StringWriter writer = new StringWriter(builder); jsonValue["FirstName"].Save(writer); //+ String firstName = builder.ToString().Replace("\"", "");
As you might imagine, I created my Themelia for Silverlight framework to abstract most of this stuff away from everyone and to simplify JSON and XML service access. For now, however, here's a simple and to-the-point WCF JSON client:
using System; using System.Collections.Generic; using System.IO; using System.Json; using System.Net; //+ namespace Service { public class WcfJsonClient { //- @JsonValueEventArgs -// public class JsonValueEventArgs : EventArgs { //- @JsonValue -// public JsonValue JsonValue { get; set; } //- @JsonType -// public JsonType JsonType { get; set; } //- @ValueAsText -// public String ValueAsText { get { System.Text.StringBuilder builder = new System.Text.StringBuilder(); System.IO.StringWriter writer = new System.IO.StringWriter(builder, System.Globalization.CultureInfo.CurrentCulture); JsonValue.Save(writer); //+ return builder.ToString(); } } } //- @Complete -// public event EventHandler<JsonValueEventArgs> Complete; //+ //- $PostJsonData -// private String PostJsonData { get; set; } //- @RawResponse -// public String RawResponse { get; set; } //+ //- @PostHttpRequest -// public void PostHttpRequest(String endpoint, String methodName, String jsonData) { PostHttpRequest(endpoint, methodName, jsonData, null); } public void PostHttpRequest(String endpoint, String methodName, String jsonData, Dictionary<String, String> headerDictionary) { WebRequest request = WebRequest.Create(new Uri(endpoint + "/" + methodName)); this.PostJsonData = jsonData; request.Method = "POST"; request.ContentType = "application/json"; if (headerDictionary != null) { foreach (String key in headerDictionary.Keys) { request.Headers[key] = headerDictionary[key]; } } request.BeginGetRequestStream(new AsyncCallback(OnPostBegin), request); } //- $OnPostBegin -// private void OnPostBegin(IAsyncResult result) { WebRequest request = result.AsyncState as WebRequest; using (StreamWriter writer = new StreamWriter(request.EndGetRequestStream(result))) { if (!String.IsNullOrEmpty(this.PostJsonData)) { writer.Write(this.PostJsonData); } writer.Close(); //+ response request.BeginGetResponse(new AsyncCallback(OnPostComplete), request); } } //- $OnPostComplete -// private void OnPostComplete(IAsyncResult result) { WebRequest request = result.AsyncState as WebRequest; using (WebResponse response = request.EndGetResponse(result)) { StreamReader reader = new StreamReader(response.GetResponseStream()); OnComplete(reader.ReadToEnd()); } } //- #OnComplete -// protected void OnComplete(String data) { RawResponse = data; if (Complete != null) { JsonValue jsonValue = (JsonValue)JsonObject.Load(new StringReader(data))["d"]; Complete(this, new JsonValueEventArgs { JsonValue = jsonValue, JsonType = jsonValue is JsonArray ? JsonType.Array : JsonType.Object }); } } } }
Here's a sample of using it:
WcfJsonClient client = new WcfJsonClient(); client.Complete += OnJsonServiceCallback; client.PostHttpRequest("http://localhost:1003/Person.svc/json", "GetPersonData", @"{ ""personGuid"": ""F488D20B-FC27-4631-9FB9-83AF616AB5A6"" }");
This is, of course, not complete without the callback method:
//- $OnJsonServiceCallback -// private void OnJsonServiceCallback(Object sender, WcfJsonClient.JsonValueEventArgs ea) { this.Dispatcher.BeginInvoke(delegate { StringBuilder builder = new StringBuilder(); StringWriter writer = new StringWriter(builder); ea.JsonValue["FirstName"].Save(writer); //+ String firstName = builder.ToString().Replace("\"", ""); spMain.Children.Add(new TextBlock { Text = firstName }); }); }
Thread Waiting
Many times when working in the Async Pattern, you will have an async chain with one service call's callback call another service, which in turn has a callback which calls another service. This is an effective way to manage the order or events in an application. However, depending on what you're doing, this model may not be the entirely optimal. Fortunately, we have another option: a thread wait.
Thread waiting has been in .NET since the beginning and is very effective in Silverlight to help us control the workflow of our applications. To use a thread wait, you create an System.Threading.AutoResetEvent object and call it's WaitOne method. This causes execution in the current thread to pause. Execution will resume when the object's Set method is called.
So, you can call WaitOne immediately after a service call and have the service call's callback call Set to continue execution. This allows you to keep a linear flow of your application instead of doing async chaining. An important note about this, though, is that you don't want to call WaitOne on the UI thread. This will basically lock up your web browser. Instead, wrap your service calls and WaitOne call in a new thread. This will cause that thread to pause, not the UI thread. Here's the basic usage:
private AutoResetEvent wait = new AutoResetEvent(false); //+ //- @OnButtonClick -// public void OnButtonClick(Object sender, System.Windows.RoutedEventArgs ea) { new Thread(new ThreadStart(delegate { //+ basicHttpBinding call ObjectClient<IPersonService> objectClient = new ObjectClient<IPersonService>("BasicHttpBinding_IPersonService"); objectClient.Callback += OnServiceCallback; objectClient.Begin("GetPersonData", "F488D20B-FC27-4631-9FB9-83AF616AB5A6"); wait.WaitOne(); //+ webHttpBinding call WcfJsonClient wcfJsonClient = new WcfJsonClient(); wcfJsonClient.Complete += OnJsonServiceCallback; wcfJsonClient.PostHttpRequest("http://localhost:1003/Person.svc/json", "GetPersonData", @"{ ""personGuid"": ""F488D20B-FC27-4631-9FB9-83AF616AB5A6"" }"); })).Start(); }
In this example, a new thread will start and make an ObjectClient service call. The thread will then be immediately pause. It will remain paused until the following callback runs the wait.Set method, which will resume execution allowing the WcfJsonClient to then run.
//- $OnServiceCallback -// private void OnServiceCallback(Object sender, ObjectClient<IPersonService>.ClientEventArgs ea) { this.Dispatcher.BeginInvoke(delegate { //+ do stuff here wait.Set(); }); }
Using DevServer
If you get seriously deep into HTTP-based service-development in either a Silverlight, AJAX, or general .NET context, there will definitely come a time your work when you need to know what messages are going over the wire, or if messages are going over the wire. In a strictly Silverlight context, you might even want to make sure your Silverlight client is even looking for a ClientAccessPolicy.xml file. Fortunately, there is a way to view all this information in a streamlined manner.
My NetFXHarmonics DevServer is a development web server which provides multi-instance web server hosting and the ability to look at the traffic in and out of each web server. Using this, you can host all your development servers in one centralized location, allowing you to attach your debugger to DevServer.Client.exe instead of many different WebDev.WebServer.exe instances.
To use it, just register your servers in the DevServer configuration file, setup a profile, and either set the startup profile or use a command line argument to specify what profile to load. This will load up all your development web servers at once. Here's a sample configuration:
<jampad.devServer> <startupProfiles activeProfile="Client"> <profile name="Client"> <server key="ClientWS" /> <server key="ClientSVC" /> </profile> </startupProfiles> <servers> <server key="ClientWS" name="ClientWS" port="1001" virtualPath="/" physicalPath="C:\Project\WCFService\WebSite"> </server> <server key="ClientSVC" name="ClientSVC" port="1003" virtualPath="/" physicalPath="C:\Project\WCFService\Service"> </server> </servers> </jampad.devServer>
You can also change the binding of each server to allow remote or Virtual PC access for testing purposes. For full documentation on DevServer see http://www.netfxharmonics.com/2008/04/NetFXHarmonics-DevServer-Released.
For now, though, just know that if you enable tracing as well as verbose tracing, you will see all requests that come into a particular web server, thus allowing you to see if your Silverlight application is even looking for the ClientAccessPolicy.xml file and allowing you to see service messages. Sometimes you don't want to write a full UI to see what a service is sending and you don't feel like attaching a debugger to look at an object's content. Using DevServer, you can just look at the raw data the server is sending. DevServer will automatically format anything it thinks is JSON or XML, allowing you to view it in its pretty form (of course, there's also a raw data view as well). Here's a screen shot of what I got when I ran the sample for this article:
As you can see, this can be tremendously helpful when you need to know how to parse or deserialize the incoming data or even to see if a response is even coming back from the server.
Conclusion
As you can see, there's more to Silverlight connectivity in WCF than the naive "Add Service Reference" option. By simply understanding the mechanics of what's going on, we are able to tailor a solution to our specific needs. WCF requires nothing more than an address, a binding, and a contract and with this information we can create a channel to access our service without the need of any extra overhead. Or, in the case of JSON, we can use tools already provided to us to obtain the exact JSON fields we need. You clearly have many, many options for Silverlight networking once you understanding the mechanics of Silverlight and WCF.
Links
- Sample Solution for this Discussion
- NetFXHarmonics DevServer on CodePlex
- Scott Hanselman's Mention of DevServer (and myself)
- Network Security Access Restrictions in Silverlight 2
- AutoResetEvent on MSDN
Love Sudoku? Love competition? Try the new Sudokian.com experience today.



