Understanding WCF Services in Silverlight 2
Understanding WCF Services in Silverlight 2
Sunday, November 16, 2008
Also Available for Download: XPS or PDF (74-pages)
If you like this document, please consider writing a recommendation for me on my LinkedIn account.
Contents
- Introduction
- Service Setup In Depth
- Client Access
- Security
- Handling Exceptions
- JSON Service Connectivity
- Thread Waiting
- Using DevServer
- Conclusion
Introduction
One of the most beautiful things about the Windows Communication Foundation (WCF) is that it's a completely streamlined technology. When you can provide solutions to myriad of diverse problems using the same principles, you know you're dealing with a work of genius. This is the case with WCF. With a single service implementation, you can provide access to ASMX, PHP, Java, TCP, named pipe, and JSON-based services by add a single XML element for each type of connection you want to support. On the flip side, with a single WCF client you can connect to each of these types of services, again, by adding a single like of XML for each. It's that simple and streamlined. Not only that, this client scenario works the same for both .NET and Silverlight.
In this document, I'm going to talk about how to access WCF services using Silverlight 2 without magic. There will be no proxies, no generated code, no 3rd party utilities, and no disgusting "Add Service Reference" usage. Just raw WCF. This document will cover WCF connectivity in quite some depth. We will talk about service setup, various WCF, SOA, and Silverlight paradigms, client setup, some security issues, and a few supplemental features and techniques to help you aide and optimize service access. You will learn about various WCF attributes, some interfaces, and a bunch of internals. Though this document will be in depth, nothing will ever surpass the depth of MSDN. So, for a more full discussion on any topic, see the WCF documentation on MSDN.
Even though we're focusing on Silverlight, most of what will be explained will be discussed in a .NET context and then applied to Silverlight 2. That is, instead of learning .NET WCF and Silverlight WCF, you will .NET WCF and how to vary this for Silverlight. This comparative learning method should help you both remember and understand the concepts better. Before we begin, though, let's begin with a certain WCF service setup. After all, we you don't have a service, we can't talk about accessing it.
Service Setup In Depth
When working with WCF, you are working with a completely streamlined system. The most fundamental concept in this system is the ABC. This concept scales from Hello World to the most complex sales processing system. That is, for all WCF communication, you need an address, a binding, and a contract. Actually, this is for 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, you either create a service-side endpoint which a client will access or a client-sidechannel which the client will use to communicate with the service.
WCF services are setup using a 3 step method:
- First, create a service contract with one or more operation contracts.
- Second, create a service implementation for those contracts.
- Third, configure a service host to provide that implementation with an endpoint for that specific contract.
Let's begin by defining a service contract. This is just a simple .NET interface with theSystem.ServiceModel.ServiceContractAttribute attribute applied to it. This interface will contain various operation contracts, which are simply method signatures with the System.ServiceModel.OperationContractAttribute applied to each. Both of these attributes are in the System.ServiceModel assembly.
Do not under any circumstances apply the ServiceContract attribute directly to the implementation (i.e. the class). The ability to do this is probably the absolute worst feature in WCF. It defeats the entire purpose of using WCF: your address, your binding, your contract and your implementation are complete separate. Because this essentially makes your implementation your contract, all your configuration files will be incredibly confusing to those of us who know WCF well. When I look for a contract, I look for something that starts with an "I". Don't confuse me with "PersonService" as my contract. Person service means person... service. Not only that, but later on you will see how to use a contract to access a service. It makes no sense to have my service access my service; thus with the implementation being the contract, your code will look painfully confusing to anyone who knows WCF.
Here's the sample contract that we will use for the duration of this document:
using System; using System.ServiceModel; //+ namespace Contact.Service { [ServiceContract(Namespace = Information.Namespace.Contact)] public interface IPersonService { //- GetPersonData -// [OperationContract] Person GetPersonData(String personGuid); } }
Keep in mind that when you design for WCF, you need to keep your interfaces as simple as possible. The general rule of thumb is that you should have somewhere between 3 to 7 operations per service contract. When you hit the 12-14 mark, it's seriously time to factor out your operations. This is very important. As I'll mention again later, in any one of my WCF projects I'll have upwards of dozens of service contracts per service. You need to continuously keep in mind what your purpose is for creating this service, filtering those purposes through the SOA filter. Don't design WCF services like you would a framework, which, even then shouldn't have many access points!
The Namespace property set on the attribute specified the namespace used to logically organize services. Much like how .NET uses namespaces to separate various classes, structs, and interfaces, SOAP services use namespaces to separate various actions. The name space may be arbitrarily chosen, but the client and service must just agree on this namespace. In this case, the namespace is the URI http://www.netfxharmonics.com/service/Contact/2008/11/. This namespace will also be on the client. This isn't a physical URL (universal resource locator), but a logical URI (universal resource identifier). Despite what some may say, both terms are in active use in daily life. Neither is more important than the other and neither is "deprecated". All URLs are URIs, but not all URIs are URLs as you can see here.
Notice in this interface, there is a method interface that returns Person. This is a data contract. Data contracts are classes which have theSystem.Runtime.Serialization.DataContractAttribute attribute applied to them. These have one or more data members, which are public or private properties or fields that have the System.Runtime.Serialization.DataMemberAttribute attribute applied to them. Both of these attributes are in theSystem.Runtime.Serialization assembly. This is important to remember; if you forget, you will probably assume them to be in the System.ServiceModel assembly and your contract will never compile.
Notice I said that data members are private or public properties or fields. That was not a typo. Unlike the serializer for the System.SerializableAttributeattribute, the serializer for DataContract attribute allows you to have private data members. This allows you to hide information from developers, but allow services to see it. Related to this is the how classes with the DataContract attribute differ from classes with the Serializable attribute. When you use the Serializable attribute, you are using an opt-out model. This means that when the attribute is applied to the class, each members is serializable. You then opt-out particular fields (not properties; thus one major inflexibility) using the System.NonSerializedAttribute attribute. On the other hand, when you apply the DataContract attribute, you are using an opt-in model. Thus, when you apply this attribute, you must opt-in each field or property you wish to be serialized by applying the DataMember attribute. Now to finally look at the Person data contract:
[DataContract(Namespace = Information.Namespace.Contact)] public class Person { //- @Guid -// [DataMember] public String Guid { get; set; } //- @FirstName -// [DataMember] public String FirstName { get; set; } //- @LastName -// [DataMember] public String LastName { get; set; } //- @City -// [DataMember] public String City { get; set; } //- @State -// [DataMember] public String State { get; set; } //- @PostalCode -// [DataMember] public String PostalCode { get; set; } }
Note how simple this class is. This is incredibly important. You need to remember what this class represents: data moving over the wire. Because of this, you need to make absolutely sure that you are sending only what you need. Just because your internal "business object" has 10,000 properties doesn't mean that your service client will ever be able to handle it. You can't get blood from a turnip. Your business desires will never change the physics of the universe. You need to design with this specific scenario of service-orientation in mind. In the case of Silverlight, this is even more important since you are dealing with information that needs to get delegated through a web browser before the plug-in ever sees it. Not only that, but every time you send an extra property over the wire, you are making your Silverlight application that less responsive.
When I coach architects on database design, I always remind them to design for the specific system which they'll be using (i.e. SQL Server) and always keepperformance, space, and API usability in mind (this is why it's the job of the architect, not the DBA, to design databases!) In the same way, if you are designing a system that you know will be used over the wire, account for that scenario ahead of time. Much like security, performance and proper API design aren't "features", they're core parts of the system. Do not design 10 different classes, each representing a property which will be used in another class which, in turn, will be serialized and sent over the wire. This will be so absolutely massive that no one will ever be able to handle it. If you have more than around 15 properties in your entire object graph, it's seriously time to rethink what you want to send. And, never, ever, ever send an instance of System.Data.DataSet over the wire. There has never been, is not now, and never will be any reason to ever send any instance of this type anywhere. It's beyond massive and makes the 10,000 property data transfer object seem lightweight. The fact that something is serializable doesn't mean that it should be.
This is main reason you should not apply the Serializable attribute to all classes. Remember, this attribute follows an opt-out model (and a weak one at that). If you want your "business objects" to work in your framework as well as over the wire, you need to remove this attribute and apply the DataContract attribute. This will allow you to specify via the DataMember attribute which properties will be used over the wire, while leaving your existing framework completely untouched. This is the reason the DataContract attribute exists! Microsoft realized that the Serializable attribute is not fine grained enough for SOA purposes. They also realized that there's no reason to force everyone in the world to write special data transfer objects for every operation. Even then, use DataContract sparingly. Just as you should keep as much private as possible and as much internal as possible, you want to keep as much un-serializable as possible. Less is more.
In my Creating Streamlined, Simplified, yet Scalable WCF Connectivity document, I explain that these contracts are considered public. That is, both the client and the service need the information. It's the actual implementation that's private. The client needs only the above information, where as the service needs the above information as well as the service implementation. Therefore, as my document explains, everything mentioned above should be in a publicly accessible assembly separate from the service implementation to maximize flexibility. This will also allow you to rely on the original contracts instead of relying on a situation where the contracts are converted to metadata over the wire and then converted to sloppily generated contracts. That's slower, adds latency, adds another point of failure, and completely destroys your hand crafted, highly-optimized contracts. Simply add a reference to the same assembly on both the client and server-side and you're done. If multiple people are using the service, just hand out the public assembly.
At this point, many will try to do what I've just mentioned in a Silverlight environment to find that it doesn't seem to work. That is, when you try to add a reference to a .NET assembly in a Silverlight project, you will get the following error message:
Fortunately, this isn't the end of the world. In my document entitled Reusing .NET Assemblies in Silverlight, I explain that this is only a Visual Studio 2008 constraint. There's absolutely no technical reason why Silverlight can't use .NET assemblies. Both the assembly and module formats are the same for Silverlight and .NET. When you try to reference an assembly in a Silverlight project, Visual Studio 2008 does a check to see what version of mscorlib the assembly references. If it's not 2.X.5.X, then it says it's not a Silverlight assembly. So, all you need to do is modify your assembly to have it use the appropriate mscorlib file. Of course, then it's still referencing the .NET System.ServiceModel and System.Runtime.Serialization assemblies. Not a big deal, just copy/paste the Silverlight references in. My aforementioned document explains everything you need to automate this procedure.
Therefore, there's no real problem here at all. You can reuse all your contracts on both the service-side and on the client-side in both a .NET Silverlight environment. As you will see a bit later, Silverlight follows an async communication model and, therefore, must use async-compatible service contracts. At that time you may begin to think that you can't simply have a one-stop shop for all your contract needs. However, this isn't the case. As it turns out .NET can do asynchronous communication too, so when you create that new contract, you can keep it right next to your original service contract. Thus, once again, you have a single point where you keep all your contracts.
Moving on to step 2, we need to use these contracts to create an implementation. The service implementation is just a class which implements a service contract. The service implementation for our document here is actually incredibly simple:
using System; //+ namespace Contact.Service { public class PersonService : Contact.Service.IPersonService { //- @GetPersonData -// public Person GetPersonData(String personGuid) { return new Person { FirstName = "John", LastName = "Doe", City = "Unknown", Guid = personGuid, PostalCode = "66062", State = "KS" }; } } }
That's it. So, if you already have some logic you know is architecturally sound and you would like to turn it into a service. Create an interface for your class and add some attributes to the interface. That's your entire service implementation.
Step 3 is to configure a service host with the appropriate endpoints. In our document, we are going to be using an HTTP based service. Thus after we setup a new web site, we create a Person.svc file in the root and add to it a service directive specifying our service implementation. Here's the entire Person.svc file:
<%@ ServiceHost Service="Contact.Service.PersonService" %>
No, I'm not joking. If you keep your implementation in this class as well, then you are not using WCF properly. In WCF, you keep your address, your binding, your contract, and your implementation completely separate. By putting your implementation in this file, you are essentially tying the address to the implementation. This defeats the entire purpose of WCF. So, again, the above code is all that should ever be in any svc file anywhere. Sometimes you may have another attribute set on your service directive, but this is basically it.
This is an unconfigured service host. Thus, we must configure it. We will do this in the service web site's web.config file. There's really only one step to this, but that one step has a prerequisite. The step is this: setup a service endpoint, but this requires a declared service. Thus, we will declare a service and add it to an endpoint. An endpoint specifies the WCF ABC: an address (where), a binding (how), and a contract (what). Below is the entire web.config file up to this point:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.serviceModel> <services> <service name="Contact.Service.PersonService"> <endpoint address="" binding="basicHttpBinding" contract="Contact.Service.IPersonService" /> </service> </services> </system.serviceModel> </configuration>
This states that there can be "basicHttpBinding" communication through Contact.Service.IPersonService at address Person.svc to Contact.Service.PersonService. Let's quickly cover each concept here.
The specified address is a relative address. This means that the value of this attribute is appended onto the base address. In this case the base address is the address specified by our web server. In the case of a service outside of a web server, then you can specify an absolute address here. But, remember, when using a web server, the web server is going to control the IP address and port bindings. Our service is at Person.svc, thus we have already provided for us the base URL. In this case the address is blank, but you will use this address attribute if you add more endpoints as you will see later.
The binding specifies how the information is to be format for transfer. There's actually nothing too magical about a binding, though. It's really just a collection ofbinding elements and pre-configured parameter defaults, which are easily changed in configuration. Each binding element will have at a minimum two binding elements. One of these is a message encoding binding element, which will specify how the message is formatted. For example, the message could be text (via the TextMessageEncodingBindingElement class; note: binding elements are in the System.ServiceModel.Channels namespace), binary (via the BinaryMessageEncodingBindingElement class), or some other encoding. The other required binding element is the transport binding element, which specified how the message is to go over the wire. For example, the message could go over HTTP (via the HttpTransportBindingElement), HTTPS (via the HttpsTransportBindingElement), TCP (via the TcpTransportBindingElement), or even a bunch of others. A binding may also have other binding elements to add more features. I'll mention this again later, when we actually use a binding.
The last part of an endpoint, the contract, has already been discussed earlier. One thing that you really need to remember about this though is that you are communication through a contract to the hosted service. If you are familiar with interface based development in .NET or COM, then you already have a strong understanding of what this means. However, let's review.
If a class implements an interface, you can access the instantiated object through the interface. For example, in the following code, you are able to access the Dude object through the ISpeak interface:
interface ISpeak { void Speak(String text); } class Dude : ISpeak { public void Speak(String text) { //+ speak text } } public class Program { public void Run() { ISpeak dude = new Dude(); dude.Speak("Hello"); } }
You can think of accessing a WCF service as being exactly like that. You can even push the comparison even further. Say the Dude class implemented IEat as well. Then we can access the instantiated Dude object through the IEat interface. Here's what I mean:
interface ISpeak { void Speak(String text); } interface IEat { void Eat(String nameOfFood); } class Dude : ISpeak, IEat { public void Speak(String text) { //+ speak text } } public class Program { public void Run() { IEat dude = new Dude(); dude.Eat("Pizza"); } }
In the same way, when configuring a WCF service, you will add an endpoint for contract through which you would like your service to be accessed.
Though it's beyond the scope of this document, WCF also allows you to version contracts. Perhaps you added or removed a parameter from your contract. Unless you want to break all the clients accessing the service, you must keep the old contract applied to your service (read: keep the old interface on the service class) and keep the old endpoint running by setting up a parallel endpoint.
You will add a new service endpoint every time you change your version, change your contract, or change your binding. On a given service, you have have dozens of endpoints. This is good thing. Perhaps you provide for four different bindings with two of them having two separate configurations each, three different contracts, and 2 different versions of one of the contracts. In this document, we are going to start out with one endpoint and add more later.
Now we have setup a complete service. However, it's an incredibly simple service setup, thus not requiring too much architectural attention. When you work with WCF in a real project, you will want to organize your WCF infrastructure to be a bit more architecturally friendly. In my document entitled Creating Streamlined, Simplified, yet Scalable WCF Connectivity, I explain streamlining and simplifying WCF connectivity and how you can use a private/public project model to simplify your WCF connectivity. See that document for more information on streamlining WCF services.
Service Access Without Magic
Now we may turn our attention to the client application. To begin, let me start off by reminding everyone that you shouldn't ever use "Add Service Reference" in Visual Studio for magical service client creation. The code is incredibly verbose, hard to manageable, edits are prone to being overwritten, and it's almost always used as an excuse to not actually learn WCF. There are few things worse than having to deal with people who thing they know a product simply because they know how to use a mouse. There are reasons why Juval Lowy, in all his books and talks, repeatedly tells people to avoid using this flawed feature. Fortunately, as professionals, we have the ability to understand how to do things without magic.
As I've mentioned many times already, WCF relies on the concept of the ABC. We've seen how we configure a WCF host by creating an endpoint specifying an address, binding and contract. As it turns out, this is all that's required on the client side as well. For both .NET and Silverlight, you merge an address and a binding with a contract in a channel factory to create a channel. This isn't just fancy conceptual architect speak, this is exactly what your code would look like (the sign of really good architecture!) Below is the .NET version of what I mean:
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");
In this you have an address (the EndpointAddress object), a binding (BasicHttpBinding), and a contract (IPersonService) meeting inSystem.ServiceModel.ChannelFactory<TServiceContract> to create a channel which implements the IPersonService interface (and others). We can then callthrough our local interface to the channel, which then calls the remote service through the service interface. No magic is required. You don't need any "proxy" or anything. WCF is literally all about the ABC on the service and on the client.
A few moments ago I mentioned that a binding is nothing more than a series of binding elements. At this time I would like to prove this to you. In the above sample, we are using a BasicHttpBinding on the client to communicate to a BasicHttpBinding on the service. This is slightly "magical". Let's peel back the covers to see what's really going on. The BasicHttpBinding is the WCF implementation of the WS-I Basic Profile standard used by ASMX services and most PHP clients. In other words, it's the basic SOAP binding. Internally though, it's really nothing more than a combination of the TextMessageEncodingBindingElement and, depending on your settings, either HttpTransportBindingElement or HttpsTransportBindingElement with the message version set to SOAP 1.1.
To prove this, let's completely remove the BasicHttpBinding from our client. Then let's create an instance of the System.ServiceModel.Channels.CustomBindingclass. This class allows us to make our own bindings from our own or preexisting binding elements. In this case, all we want to do is add the TextMessageEncodingBindingElement, setting the message version to SOAP 1.1 and add the HttpTransportBindingElement. With this, we have creating a binding that fits exactly what our service expects. Here's what we're talking about:
CustomBinding customBinding = new CustomBinding(); customBinding.Elements.Add(new TextMessageEncodingBindingElement { MessageVersion = MessageVersion.Soap11 }); customBinding.Elements.Add(new HttpTransportBindingElement()); EndpointAddress endpointAddress = new EndpointAddress("http://localhost:1003/Person.svc"); //+ IPersonService personService = new ChannelFactory<IPersonService>(customBinding, endpointAddress).CreateChannel();
If you think that's wild, you will be in shock when you realize that you don't even need to use code to create a custom binding. Let's completely remove the CustomBinding and address from our code and just do the exact same thing in web.config. Here is our new code:
IPersonService personService = new ChannelFactory<IPersonService>("PersonServiceCustomHttpBinding").CreateChannel();
Doesn't get much simpler than that. Now take a look at our web.config:
<system.serviceModel> <bindings> <customBinding> <binding name="HttpTextCustomBinding"> <textMessageEncoding messageVersion="Soap11" /> <httpTransport/> </binding> </customBinding> </bindings> <client> <endpoint name="PersonServiceCustomHttpBinding" bindingConfiguration="HttpTextCustomBinding" address="http://localhost:1003/Person.svc" binding="customBinding" contract="Contact.Service.IPersonService" /> </client> </system.serviceModel>
Believe it or not, our service still works perfectly.
Up to this point we have been discussing WCF in general, it's now time to move towards Silverlight. The ABC and channel concepts still apply, but, 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 now 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. This actually means that our service contract in Silverlight will look slightly different than our service contract for .NET. 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 declaratively stated that we are following the pattern via theAsyncPattern property. 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. In this case we are adding a TextBlock to a StackPanel. For the duration of this document, the following method will be used to write our output to the StackPanel:
//- $Write -// private void Write(String text) { this.Dispatcher.BeginInvoke(delegate { spMain.Children.Add(new TextBlock { Text = text }); }); }
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. The SOAPAction header is what a SOAP service uses to specify what action to run on the service.
Here's our new policy:
<?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); Write(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 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. You could create a Visual Studio code snippet to setup this pattern as well. 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; } //+ Write(person.FirstName); }
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>(); Write(person.LastName); }
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.
Security - Communication over SSL
At this point I would like to turn out attention to a critical part of WCF development: security. Many times, if not most of the time, you will be dealing with some sort of sensitive information in your application. This goes for Silverlight as much as it does for .NET. Fortunately, secure WCF interaction is essentially the same for both. Before we begin though, we need to update our service in order for it to handle secure connectivity. To do this, we are going to create a separate service from our Person service, called SecurePersonService. In most situations, you would just have a single service to handle both, but for the sake of this document we're going to keep them separate.
Before we add security to our service though, we need to have an SSL certificate installed on the server. This is the same type of certificate you would have if you wanted to provide a checkout experience on an e-commerce web site. If you already have an SSL certificate, great, if not, then you will need to go through the motions to install one. To install one for IIS6 or IIS7, just follow the instructions at http://blog.functionalfun.net/2008/05/how-to-create-server-certificate-for.html. This post explains how you can easily get a free 90-day signed SSL certificate from a certificate provider.
Once you have the certificate installed, you need to then configure the service for SSL. We haven't yet created the service, but that's OK. We know what it's going to be called. To configure our BasicHttpBinding service for SSL you essentially need to set the security mode to Transport in a binding configuration and specify the certificate in a service behavior. Here's the new configuration:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.serviceModel> <bindings> <basicHttpBinding> <binding name="SecureBasicHttpBinding"> <security mode="Transport" /> </binding> </basicHttpBinding> </bindings> <behaviors> <serviceBehaviors> <behavior name="SecurePersonServiceBehavior"> <serviceCredentials> <serviceCertificate findValue="localhost" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" /> </serviceCredentials> </behavior> </serviceBehaviors> </behaviors> <services> <service name="Contact.Service.PersonService"> <endpoint address="" binding="basicHttpBinding" contract="Contact.Service.IPersonService" /> </service> <service name="Contact.Service.SecurePersonService" behaviorConfiguration="SecurePersonServiceBehavior"> <endpoint bindingConfiguration="SecureBasicHttpBinding" address="" binding="basicHttpBinding" contract="Contact.Service.IPersonService" /> </service> </services> </system.serviceModel> </configuration>
There are two conceptual notes that should probably be mentioned at this point:
First, a binding configuration changes the defaults of a particular binding. As I mentioned earlier, a binding is really just a collection of binding elements with some pre-set parameters. In this case, we are applying a particular binding configuration to an endpoint to change the security from "None" to "Transport" (which is SSL in this case).
Second, behaviors essentially allow you to declaratively, either via configuration or programmatically via attributes, modify how a particular part of your service operates. You can have all kinds of different behaviors at different levels. For example, you can use an operation behavior to modify something about an operation. We will see this later when we use an operation behavior to apply operation-level security. In our configuration here, we are applying a service behavior to modify the service to use a particular certificate.
Next, for the time being, let's copy/paste our PersonService implementation as a new class called SecurePersonService. Again, this is only for the sake of this demo. In reality, there's no reason to use the word "Secure" for this service. Then, let's create a SecurePerson.svc file in our service web site root and insert the following service declaration:
<%@ ServiceHost Service="Contact.Service.SecurePersonService" %>
At this point we have an SSL BasicHttpBinding service setup. We can now alter alter our Silverlight client to use SSL. This is really just a matter of setting the BasicHttpBinding security more to Transport and changing the URL from the HTTP based URL to the HTTPS one. Here's how we have modified our previous Silverlight WCF client to allow SSL access:
BasicHttpBinding basicHttpBinding = new BasicHttpBinding(); //+ step 1 of 2: set security mode basicHttpBinding.Security.Mode = BasicHttpSecurityMode.Transport; //+ step 2 of 2: set to https _and_ set proper relative endpoint EndpointAddress endpointAddress = new EndpointAddress("https://localhost/SecurePerson.svc"); IPersonService personService = new ChannelFactory<IPersonService>(basicHttpBinding, endpointAddress).CreateChannel();
Those two steps are the only steps involved. Silverlight is not set to use the new SSL service.
Having said that, if you try to run the Silverlight client, it won't work. Why? Well, as Tim Heuer, Senior Program Manager for Silverlight points out, you can't rely on the existing ClientAccessPolicy.xml configuration we used previously. Previously we allowed the "*" domain, which allows all non-secure clients in. This does not include HTTPS clients. Thus, we need to specify them explicitly. Here is our new ClientAccessPolicy.xml file:
<?xml version="1.0" encoding="utf-8"?> <access-policy> <cross-domain-access> <policy> <allow-from http-request-headers= "SOAPAction"> <domain uri="http://*"/> <domain uri="https://*"/> </allow-from> <grant-to> <resource path="/" include-subpaths="true"/> </grant-to> </policy> </cross-domain-access> </access-policy>
Now our Silverlight client will communicate successfully with the service over SSL. But, SSL only secures the data going over the wire, it doesn't say anything about who can access the data. For this we need to add some form of authentication.
Security - UserName Authentication
If we were working in .NET, we could just use the WsHttpBinding to use the WS-Security standard to send our username and password to our service. Silverlight 2.0, however, doesn't include this binding. This doesn't mean we can't make username/password authentication happen. We could actually use a WebRequest technique that we will cover later to communicate with a WsHttpBinding service, but let's keep with BasicHttpBinding for the time being. In this case, all we need to do is add the username and password to the message to be sent over our secure SSL connection and have it checked at the other end. This is really as easy as it sounds.
Regardless of whether you are on the service-side or the client-side, you will have something called an operation context. This houses the information for the current operation. You can think of it as a the WCF operation version of HttpContext in ASP.NET. In fact, much like HttpContext, the OperationContext object is a single. You access it via System.ServiceModel.OperationContext.Current. As you progress in your WCF projects, you will find yourself going to the operation context time and time again for various reason. It's an incredibly important feature of WCF that provides much value to service development on both the service and client side.
Of the many properties in this context, there is one called OutgoingMessageHeaders of type System.ServiceModel.Channels.MessageHeaders. As the name suggestions this houses the message headers that are being sent out. Likewise there's also an IncomingMessageHeaders object of the same type, which contains the header information that has been received. These are just opposite sides of the coin. Both outgoing and incoming headers are like request headers in the web world. Outgoing headers are the "request headers" being sent and incoming headers are the "request headers" received on the other end. This works the same for both the service-side and client-side.
To enable authorization, we just need to add both the username and password to the OutgoingMessageHeader object on the client and read them from theIncomingMessageHeader object on the service. In Silverlight, this looks like this:
MessageHeaders messageHeadersElement = OperationContext.Current.OutgoingMessageHeaders; messageHeadersElement.Add(MessageHeader.CreateHeader("UserName", "", "JohnDoe")); messageHeadersElement.Add(MessageHeader.CreateHeader("Password", "", "MyPassword"));
Notice there is no mention of the service we are going to access. When the service-call happens, the information will automatically be applied in the background and sent over the wire.
Now while this sounds great, we see a reoccurring theme: it doesn't work. Why? If you try to do this, you will find that OperationContext.Current is null. The singleton has not yet been created. Well, that kind of throws a wrench in things. Fortunately, though, this is easily remedied. All we need to do is declare an operation context scope. This basically means instantiating a System.ServiceModel.OperationContextScope object, passing it an existing operation context or something which implements the System.ServiceModel.IContextChannel interface. We obviously don't have the former, but what about the latter?
As it turns out, the channel created by our channel factory doesn't only implement our service contract (IPersonService), but it also implements IContextChannel. Thus, we are able to pass this to the operation context scope, which will then create the operation context, allowing us to add the headers and make our call. Here's our new service call:
using (OperationContextScope scope = new OperationContextScope((IContextChannel)personService)) { MessageHeaders messageHeadersElement = OperationContext.Current.OutgoingMessageHeaders; messageHeadersElement.Add(MessageHeader.CreateHeader("UserName", "", "JohnDoe")); messageHeadersElement.Add(MessageHeader.CreateHeader("Password", "", "MyPassword")); //+ Person person = personService.GetPersonData("F488D20B-FC27-4631-9FB9-83AF616AB5A6"); }
Note that the operation context scope implements the System.IDisposable interface, thus allowing you to wrap it in a using block in C#.
At this point we can turn our attention to the service. This is actually about as simple as what we did on the client-side, if not simpler. All we need to do is see if theIncomingMessageHeaders object on our operation context contains our UserName and Password headers. If they do, then extract them and validate them. If all if well, then run the service logic.
To check to see if a particular message header is in the IncomingMessageHeaders object, use the FindHeader method. This will return the index of the header or -1 if the header was not found. Since both of our headers must exist, the sum of the indexes must be at least 0. -1 would mean that one of the two headers is missing whereas -2 would mean that both are missing. Therefore, if the sum is at least 0, we can grab the header information, which we do using the GetHeader<T>method. This is a generic method, so specify the type that you would like your header value to be as T. In our case they will both be System.String. Once we have pulled the username and password, we can validate them and run our service logic. Here's our entire service up to this point:
using System; using System.ServiceModel; using System.ServiceModel.Channels; //+ namespace Contact.Service { public class SecurePersonService : Contact.Service.IPersonService { //- @GetPersonData -// public Person GetPersonData(String personGuid) { MessageHeaders messageHeadersElement = OperationContext.Current.IncomingMessageHeaders; Int32 id = messageHeadersElement.FindHeader("UserName", "") + messageHeadersElement.FindHeader("Password", ""); if (id > -1) { String username = messageHeadersElement.GetHeader<String>("UserName", ""); String password = messageHeadersElement.GetHeader<String>("Password", ""); if (username == "JohnDoe" && password == "MyPassword") { return new PersonService().GetPersonData(personGuid); } } //+ return null; } } }
Now at this point we could reuse this same technique to secure each operation in our service. However, the technical term for doing this is insanity. This is absolutely crazy. There absolutely has to be a better way of doing this than dealing with the internal mechanics of message headers for each and every single operation in your service. We need something that will validate the username and password information before our logic ever runs so that we never, ever have to see it. Fortunately, there is. This is exactly the value an operation behavior provides. They are also incredibly straightforward to implement.
Before we do that though, we need to create operation invoker. We do this by creating a class which implements theSystem.ServiceModel.Dispatcher.IOperationInvoker interface. There are many member signatures in this interface, but we only care about the Invoke method. Every time you call an operation, you "invoke" the operation much like how you "invoke" a method via reflection. Earlier we saw how we passed an anonymous method to an Invoke method in WPF so we could access the UI thread. This is the same idea. When you run something, you "invoke" it. For an operation invoker, when an operation is called, the Invoke method is called to run the actual operation logic. Our mission is to house validation logic in our invoker and, if validation succeeds, have the invoker call the original service operation invoker.
We install an operation invoker using an operation behavior. An operation behavior is a class which implements theSystem.ServiceModel.Description.IOperationBehavior interface. This interface has four method signatures, but we really only care about one of them. Not only that, but the only thing this method will do is set our Invoker. Before this can matter though, we have to have a way to apply our bit to our operation. The operation behavior may set the operation invoker, but who sets the operation behavior? As it turns out, operation behaviors may be applied declaratively through attributes. This is rather interesting as it allows us to have our operation behavior class inherit from System.Attribute, thus making our operation behavior completely self-sufficient.
It's that time again to move from the lecture hall to the lab. We will first turn our attention to the operation behavior. Below is the entire operation behavior:
using System; using System.ServiceModel.Description; using System.ServiceModel.Dispatcher; //+ namespace Contact.Service.Behavior { [AttributeUsage(AttributeTargets.Method)] public class SecurityOperationBehavior : Attribute, IOperationBehavior { //- @AddBindingParameters -// public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { //+ blank } //- @ApplyClientBehavior -// public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) { //+ blank } //- @ApplyDispatchBehavior -// public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation) { dispatchOperation.Invoker = new SecurityOperationInvoker(dispatchOperation.Invoker); } //- @Validate -// public void Validate(OperationDescription operationDescription) { //+ blank } } }
As you can see, it's just a simple attribute whose attribute usage states that it may only be applied to methods. Notice also that the only method we care is the ApplyDispatchBehavior method and, as mentioned before, the only thing were doing is setting the new invoker, passing the original invoker to its constructor. Let's now turn to our operation invoker:
using System; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Dispatcher; //+ namespace Contact.Service.Behavior { public class SecurityOperationInvoker : IOperationInvoker { //- $InnerOperationInvoker -// private IOperationInvoker InnerOperationInvoker { get; set; } //+ //- @Ctor -// public SecurityOperationInvoker(IOperationInvoker operationInvoker) { this.InnerOperationInvoker = operationInvoker; } //+ //- @AllocateInputs -// public Object[] AllocateInputs() { return InnerOperationInvoker.AllocateInputs(); } //- @Invoke -// public Object Invoke(Object instance, Object[] inputs, out Object[] outputs) { //+ authorization MessageHeaders messageHeadersElement = OperationContext.Current.IncomingMessageHeaders; Int32 id = messageHeadersElement.FindHeader("UserName", "") + messageHeadersElement.FindHeader("Password", ""); if (id > -1) { String username = messageHeadersElement.GetHeader<String>("UserName", ""); String password = messageHeadersElement.GetHeader<String>("Password", ""); SecurityValidator.Authenticate(username, password); //+ return InnerOperationInvoker.Invoke(instance, inputs, out outputs); } //+ throw new FaultException<InvalidOperationException>(new InvalidOperationException(SecurityValidator.Message.InvalidCredentials), SecurityValidator.Message.InvalidCredentials); } //- @InvokeBegin -// public IAsyncResult InvokeBegin(Object instance, Object[] inputs, AsyncCallback callback, Object state) { return InnerOperationInvoker.InvokeBegin(instance, inputs, callback, state); } //- @InvokeEnd -// public Object InvokeEnd(Object instance, out Object[] outputs, IAsyncResult result) { return InnerOperationInvoker.InvokeEnd(instance, out outputs, result); } //- @IsSynchronous -// public Boolean IsSynchronous { get { return InnerOperationInvoker.IsSynchronous; } } } }
At first glance it may seem like there's a lot going on here, but if you look closely you will see that there are only three things of note here. First, there is theInternalOperationInvoker property. This just hold the original operation invoker so we can call it later. Second, every other member except Invoke does nothing but call the invoker in InternalOperationInvoker. Third, the Invoke method is not really doing anything but what we've already seen in the service operation. The only difference is that upon validation, instead of doing the actual logic, the Invoke method just calls the Invoke method of our InternalOperationInvoker property. So, it's not really all that busy after all.
With our operation behavior created, we can now return to modify our service operation. All we need to do is remove all the validation logic and apply our new SecurityOperationBehavior attribute. Our service operation now looks like this:
//- @GetPersonData -// [SecurityOperationBehavior] public Person GetPersonData(String personGuid) { return new PersonService().GetPersonData(personGuid); }
That's a lot better than before, isn't it? With this new toy at your disposal, whenever you want to secure a new operation in your service, just apply the attribute and move on.
Before moving on, note that if you would like to see another example of using operation behaviors, see my Minima Blog Engine 3.1 source code. Minima is my training software to demonstrate many areas of .NET and it relies heavily on WCF.
Handling Exceptions
We now have an authentication model for our WCF connectivity in our Silverlight application. There is one more thing we could do to improve this. Returning null from a service when validation failed is a good way to cause no end to confusion. The worst error is seeing no error. Therefore, we need to throw an exception if validation fails. Continuing in how we have been discussing things, let's move our attention back to the .NET world to discuss some concepts and theory. Then let's angle our knowledge a bit to make way for the Silverlight world.
To begin, you need to understand that the concept of an exception is a .NET concept. The messages over the wire have absolutely nothing to do with .NET and, therefore, have no idea what an exception is. Instead, messages over the wire and service-oriented architecture in general have a concept called a fault, which essentially serves the same purpose as an exception. Therefore, if you were to throw an exception in a service, the SOA world won't have the first clue what you're talking about. Not only that, but when your client hears about the exception, it won't see an exception, but, rather a signal that there was some sort of a catastrophic error, thus closing your client channel (our IPersonService instance). In other words, the following in the service will break your client:
String message = "Invalid credentials"; throw new System.InvalidOperationException(message);
You can, however, get around this by telling your WCF service to include exception information in fault. You can set this in configuration or in your service host factory. I'm not going to demonstrate this because I don't want to encourage this cop-out technique. This is only to be used for debugging. Do you really want the fully-detailed exception message and stack trace to go out into all the world to see? This would be even worst than turning ASP.NET custom errors off. This is the image of a security flaw.
WCF provides the proper mechanism for dealing with exceptions with the System.ServiceModel.FaultException<T> class. This allows us to to throw exceptions as fault. There's also a System.ServiceModel.FaultException, from which FaultException<T> inherits. You can either throw the non-generic one to send a simple message or you can use the generic one to send a message and the exception type. The generic one will probably be the one you use most often.
To use FaultException<T>, first instantiate the exception you would like to throw then pass that object as well as the exception message to the FaultException<T> instance. Then you may throw the FaultException<T> instance. That is, instead of throwing the InvalidOperationException as seen in the last example, do the following:
String message = "Invalid credentials"; throw new FaultException<System.InvalidOperationException>(new System.InvalidOperationException(message), message);
This will allow your client code to catch the exception gracefully and won't send your private stack trace for the world to see. Here's the code to handle this:
try { person = personService.GetPersonData("F488D20B-FC27-4631-9FB9-83AF616AB5A6"); } catch (FaultException ex) { Response.Write(ex.Message); }
At this point our service-side fault exception code is the same as the following:
throw new FaultException("Invalid credentials");
As I've already mentioned, using the FaultException class will send back a message. This is exactly what FaultException<T> at this point too. To make FaultException<T> work the way it was designed, you need to declare a fault contract on your service-side service contract. You do this by applying theSystem.ServiceModel.FaultContract attribute to your operation contract. Here's our new service contract:
using System; using System.ServiceModel; //+ namespace Contact.Service { [ServiceContract(Namespace = Information.Namespace.Contact)] public interface IPersonService { //- GetPersonData -// [OperationContract] [FaultContract(typeof(InvalidOperationException))] Person GetPersonData(String personGuid); } }
Now we are able to catch the various different fault exceptions a service may return:
try { person = personService.GetPersonData("F488D20B-FC27-4631-9FB9-83AF616AB5A6"); } catch (FaultException<InvalidOperationException> ex) { Response.Write(ex.Message); }
At this point some may suggest that, since we are only interested in security validation, we should turn our attention toSystem.Security.SecurityException as follows:
throw new System.Security.SecurityException("Invalid credentials");
This special exception will actually be a fault over the wire, give you graceful error handling, and it also doesn't publish your stack trace. One thing you need to remember about this exception, though, is that you don't catch the same exception that has been thrown. You actually catchSystem.ServiceModel.Security.SecurityAccessDeniedException. Not only that but the message that was sent from the service, isn't the message that will show in your client. The client side exception will always say "Access is denied.". Here's an example:
try { person = personService.GetPersonData("F488D20B-FC27-4631-9FB9-83AF616AB5A6"); } catch (System.ServiceModel.Security.SecurityAccessDeniedException ex) { Response.Write(ex.Message); }
You probably shouldn't rely on this mechanism though. WCF will replace the SecurityException with a Fault over the wire, but not in all places. For example, the above code will work beautifully if you are throw the exception from an operation, but if you throw it in an operation behavior, your client will see, not SecurityAccessDeniedException, but rather a FaultException (not the generic one either). You should use FaultException<SecurityException> either as this can cause the real exception to be System.Net.Sockets.SocketException, which is absolutely catastrophic. For a security validation error, I useFaultException<InvalidOperationException> as we've already seen.
Now, I must apologize at this point for getting you excited about all the awesomeness of faults handling in WCF, because... none of that works in Silverlight. Yeah. I know. It's like "why didn't you say that earlier!!??" Well, it's important to understand the underlying mechanics of the ideal, before you can work around them into the real-- otherwise, you're just making stuff up as you go. Not only that, you need to make sure you understand WCF in general before you understand WCF in Silverlight. Chances are that your WCF services will do more than simply serve Silverlight.
The reason none of this works in the Silverlight world is because has absolutely no concept of a fault. It actually can't. With this specific service, when your service call fails validation, your Silverlight application will only see a 404 error with the text "The remote server returned an error: NotFound". When a fault is thrown, regardless of how pretty you make it, the HTTP status code is not 200, where 200 means that everything went OK. This is a problem for Silverlight.
Silverlight, as we know, runs in the context of a web browser. Because of this, Silverlight is only provided the information that a web browser allows it. When a response is returned to Silverlight with a status code other than 200, the web browser intercepts it and never tells Silverlight about it. It's kind of like when you're at the office and you go to search for a critical piece of information only to find that your company's content filter blocks that particular technology web site thinking it was inappropriate.
There are various ways around this and every single technique that I know of requires you to modify your service. The method we are going to use modifies the service a tiny bit, but it does so in a reusable way. We are essentially going to create a custom FaultDetail class which we will implement in our Person data contract. If an exception is thrown on the service, a fault detail will be created and added to our Person object. Then, when we receive a response on the client side, we can check to see if FaultDetail is null. If not, then there was a fault.
However, I don't want to do this for all my service-clients. Service clients that can handle service faults are going to see the real fault. There's no reason to confuse whoever is accessing our service by having a separate FaultDetail. Having an error in two places can lead to disasters and mass confusion. Mindless consistency is often the fastest way to catastrophic disaster. You can't say you drive 40 miles per hour at all times because you strive for consistency. This can kill pedestrians in school zones and kill you on the interstate. We must be flexible to the context of each situation. We must seek congruency, not consistency. Therefore, we will only set the FaultDetail object when a specific message header is present. For our service, we will call this message header "DoesNotHandleFault". Note this isn't "IsSilverlight". This makes our FaultDetail technique reusable for other technologies that do not handle faults.
Enough lecture, back to the lab. First, let's create our FaultDetail object. This can simply be a class with some properties. In this example, I have a Message property as well two constructors, one to accept a message and open to accept an exception:
using System; using System.ServiceModel; //+ namespace General.Service { public class FaultDetail { //- @Message -// public String Message { get; set; } //+ //- @CreateFaultDetail -// public static FaultDetail CreateFaultDetail(Exception exception) { return CreateFaultDetail(exception.Message); } public static FaultDetail CreateFaultDetail(String message) { return new FaultDetail { Message = message }; } } }
At this point, you could take this idea further and iterate through the InnerExceptions of the exception and add nested FaultDetail messages. That's a mission for you, if you choose you accept it.
Next, let's create an interface that will signal to us that a particular service-side data contract can handle a FaultDetail:
namespace General.Service { public interface IContainsFaultDetail { FaultDetail FaultDetail { get; set; } } }
Let's now implement the interface:
using System; using System.Runtime.Serialization; //+ using General.Service; //+ namespace Contact.Service { [DataContract(Namespace = Information.Namespace.Contact)] public class Person : IContainsFaultDetail { //++ //+ more properties here //++ //- @FaultDetail -// [DataMember] public FaultDetail FaultDetail { get; set; } } }
You may be curious to know how this service-side data contract change will affect the rest of the clients which consume this service. It's actually really simple: it doesn't. The client will not break simply because it doesn't know how to deserialize something. Actually, in .NET, if the client-side data contract didn't have data members the service was sending back to it, but implemented System.Runtime.Serialization.IExtensibleDataObject, the data would go into the data contract'sExtensionData property, or as I like to call it "the data member bucket". So, you aren't breaking anything by adding data members to your service-side data contracts. As a side note, this works the other way too. If your client sends more information than the service knows about, the service won't break either and, if its data contract implemented IExtensibleDataObject on a service-side data contract, the data will likewise go into the ExtensionData property. Like I said though, this is in .NET. Silverlight does not support this interface. Of course, Silverlight can still send extra stuff to the service, which will be in .NET.
The last task on the service-side is to add the FaultDetail functionality to our service. We could either add the functional to each and every single operation or we could simply extend the operation behavior that we had already created. I'm voting for the later. Essentially what we are going to do is wrap all the logic in our operation invoker Invoke logic in a try-catch block. Then, if an exception is thrown, we can check to see if the "DoesNotHandleFault" message header is set. If it is, set the FaultDetail on the return object. If it isn't, then simply rethrow the exception.
This all seems pretty simple, right? Well, there are some problems with this. First, you want to make this reusable. Therefore, you cannot assume anything about the operation return type other than the fact that everything inherits from System.Object. You could easily get around this by checking to see if our IContainsFaultDetail interface is on the object. If it is, then you can set the FaultDetail through this interface. You don't even need to know the actual type. However, what if the object is null? Just instantiate it first, right? Again, you don't know the type. Fortunately, however, the wonderful world of reflection is at our disposal.
To get around this problem, we need to first see if there is even a problem at all. This means checking to see if our return object is null. If not, then you're all set to check for the interface and set the FaultDetail through that interface. If it is null, then we need to use reflection to obtain the type of the return object. This is actually hidden in our InnerOperationInvoker which we saved when we instantiated our operation invoker. To be more specific, inside our InnerOperationInvoker is a property named Method. This method is of type System.Reflection.MethodInfo, which, in turn, has a property named ReturnType of type System.Type which is exactly what we need. Then, we use the System.Activator::CreateInstance method to create an instantiated object using the value of ReturnType. At this point we are able to check for our IContainsFaultDetail interface and setting the FaultDetail.
Once more, let's switch from lecture to lab. Below is our new Invoke method for our operation invoker based on the concepts I've just explained:
//- @Invoke -// public Object Invoke(Object instance, Object[] inputs, out Object[] outputs) { outputs = new Object[] { }; MessageHeaders messageHeadersElement = OperationContext.Current.IncomingMessageHeaders; Object returnObject = null; try { //+ authorization Int32 id = messageHeadersElement.FindHeader("UserName", "") + messageHeadersElement.FindHeader("Password", ""); if (id > -1) { String username = messageHeadersElement.GetHeader<String>("UserName", ""); String password = messageHeadersElement.GetHeader<String>("Password", ""); SecurityValidator.Authenticate(username, password); //+ return InnerOperationInvoker.Invoke(instance, inputs, out outputs); } //+ throw new FaultException<InvalidOperationException>(new InvalidOperationException(SecurityValidator.Message.InvalidCredentials), SecurityValidator.Message.InvalidCredentials); } catch (Exception ex) { if (messageHeadersElement.FindHeader("DoesNotHandleFault", "") > -1) { if (returnObject == null) { Type invokerType = InnerOperationInvoker.GetType(); PropertyInfo pi = invokerType.GetProperty("Method"); MethodInfo mi = (MethodInfo)pi.GetValue(InnerOperationInvoker, null); Type returnType = mi.ReturnType; Type interfaceType = returnType.GetInterface("General.Service.IContainsFaultDetail"); if (interfaceType != null) { returnObject = Activator.CreateInstance(returnType); } } if (returnObject != null && returnObject is IContainsFaultDetail) { ((IContainsFaultDetail)returnObject).FaultDetail = FaultDetail.CreateFaultDetail(ex.Message); } } else { throw; } } //+ return returnObject; }
The explanation that I have already provided should help you understand each part of what's going on here. There is, however, one part of this that may cause you problems if you don't implement this technique correctly. Notice the Invoke method accepts an out parameter named outputs. And out parameter is an incredibly powerful C# feature that requires the method on which it is placed to give the parameter a value, which the method caller may then. If your method does not give the parameter a value, your code will not compile. In many cases, setting an out parameter to null will be sufficient. In this case, this will cause a serialization error when the service reply is created. Therefore, in this example, I'm setting the outputs parameter to an empty object array. This will satisfy both the compiler as well as WCF.
At this point, we can move to the client-side. The first thing we need to do is copy our FaultDetail, IContainsFaultDetail, and new Person data contract to our Silverlight project. Note that you do NOT need to do this for any other clients for the reasons I've already already mentioned. The only clients that need these changes are the clients which are going to use the FaultDetail directly.
Next, let's add our "DoesNotHandleFault" message header in our Silverlight client. You can do this in the same place you set your other message headers:
using (OperationContextScope scope = new OperationContextScope((IContextChannel)personService)) { MessageHeaders messageHeadersElement = OperationContext.Current.OutgoingMessageHeaders; messageHeadersElement.Add(MessageHeader.CreateHeader("UserName", "", "JohnDoe")); messageHeadersElement.Add(MessageHeader.CreateHeader("Password", "", "MyPassword12341234")); //+ allows returning of a FaultDetail object messageHeadersElement.Add(MessageHeader.CreateHeader("DoesNotHandleFault", "", true)); //+ personService.BeginGetPersonData("F488D20B-FC27-4631-9FB9-83AF616AB5A6", asyncCallBack, personService); }
Then, in our callback, check for the FaultDetail object:
AsyncCallback asyncCallBack = delegate(IAsyncResult result) { Person person = ((IPersonService)result.AsyncState).EndGetPersonData(result); String text = String.Empty; if (person.FaultDetail != null) { text = person.FaultDetail.Message; } else { text = person.FirstName; } //+ the rest of your callback here };
Now you have the ability to catch faults in Silverlight 2. There's, however, one quite serious problem with this method though. What happens when your operation doesn't return a data transfer object (DTO) like our Person class? There's no setting a FaultDetail on an array, a list, an Int32, or a string.
Though it's generally a redundant and architecturally questionable idea, in these cases you really need to create a wrapper response class. As a service-oriented architect, I only care about the service contracts. If the service contracts have nothing more than a bunch of request response messages, then I'm seriously slowed down. They tell me absolutely nothing. How can I know anything about an operation's response by looking at GetPersonDataResponse? In these situations, it takes me much longer to get at the heart of what's going on because I need to understand with X number of methods and (2 * X) number of custom objects (and look them up on a chart every time I forget what's what!)
Thus, though services are to accept a very specific set of parameters and return a very specific element. For our scenario, however, you need to do this. To create a DTO, just create a class which contains a property for your operation return object and as well as a FaultDetail property. As I've mentioned, this is incredibly redundant as what I've just described is exactly what SOAP envelope does with a fault. But, since Silverlight will never see those messages, we are rather forced to emulate them.
JSON Service Connectivity
If you've been paying attention to the examples thus far, we have only been using the BasicHttpBinding binding. This is because Silverlight doesn't handle bindings like WsHttpBinding, NetTcpBinding, or NetNamedPipeBinding. However, since we're on the web, we might create a Silverlight application to replace an AJAX component which used JSON to communicate to a WebHttpBinding WCF service endpoint. Or, perhaps, you just prefer to use a WebHttpBinding on your WCF service. Therefore, we need the ability 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. If you recall, an async chain is when the callback of one call makes another call and its callback may potentially make another call. Before you can do any of this, though, 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 theEndGetResponse, 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. Here is a complete example of using the WebRequest object in this way:
using System; using System.Collections.Generic; using System.Json; using System.Net; //+ namespace SilverlightClient { 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> Callback; //+ //- $PostJsonData -// private String PostJsonData { get; set; } //+ //- @PostHttpRequest -// public void PostHttpRequest(String endpoint, String methodName) { PostHttpRequest(endpoint, methodName, String.Empty, null); } 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 (System.IO.StreamWriter writer = new System.IO.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)) { OnComplete(new System.IO.StreamReader(response.GetResponseStream()).ReadToEnd()); } } //- #OnComplete -// protected void OnComplete(String data) { if (Callback != null) { JsonValue jsonValue = (JsonValue)JsonObject.Load(new System.IO.StringReader(data))["d"]; Callback(this, new JsonValueEventArgs { JsonValue = jsonValue, JsonType = jsonValue is JsonArray ? JsonType.Array : JsonType.Object }); } } } }
Here you can see the WebRequest setup as well as the async chain. Notice also how this example accepts a System.Collections.Generic.Dictionary<T1, T2> object. Using this we are able to set custom HTTP headers on to our client. Using HTTP headers is an awesome way of communicating with a service. We have already seen that SOAP services use the SOAPAction header to specify what operation to run at the other end. You could do something similar in your JSON services as well.
Normally at this point I would like show you an example of using this client, but we aren't done explaining it. One part of this client example that we haven't discussed is how to deal with the response JSON data. At this point our JSON data is just raw text. Ideally, we would take this raw text, run it through deserialization and be done with things. However, 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'sSystem.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. In this case, 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.
If you notice from the client, when we the last client callback is called, the static System.Json.JsonObject.Load method is called to load a stream or TextReader into a System.Web.JsonValue object. This gives us a representation we can use to access any information we want by accessing the indexer of the object. Before we can access our data, though, 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 as this example shows:
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 andSystem.Json.JsonObject. Unless you know what your service is returning, you need to test which type the JsonObject.Load class created. This is as simple as the following:
JsonType jsonType = value is JsonArray ? JsonType.Array : JsonType.Object;
If you're working with a JsonObject, you may then access any member of the JSON data you want. You do this by reading values from a JsonValue into 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("\"", "");
Since you would be using this regularly, it's a good idea for us to place this in a method (or even in a "Reader" class):
//- $ReadJsonValue -// private string ReadJsonValue(JsonValue jsonValue, String field) { StringBuilder builder = new StringBuilder(); jsonValue[field].Save(new StringWriter(builder)); //+ return builder.ToString().Replace("\"", ""); }
At this point, you have enough information to see how this WcfJsonClient class can be used. Here's how we can use 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) { Write(ReadJsonValue(ea.JsonValue, "State")); }
As I've mentioned, we can work with arrays as well. In .NET though, we really only talk about arrays for value types. If we have a series of reference types, a collection feels much better. Therefore, let's assume that we the following operation to our service:
//- GetPersonList -// [OperationContract] List<Person> GetPersonList( );
What's interesting about this is that System.Collections.Generic.List<T> likes to become an array when going over the wire. When dealing with SOAP services, a List is an array over the wire. The same goes for JSON as well. This is where that JsonArray type comes in to play. This object is incredibly easy to work with. Essentially it's a series of JsonValue objects. Not only that, but JsonArray has an indexer and it implements the IEnumerable<JsonValue> interface, thus allowing us to access it's elements via a foreach loop. In our case, each of these elements will be of the type JsonObject, which we already know how to deal with. Here's a complete example of interacting with the above service operation:
WcfJsonClient wcfJsonClient = new WcfJsonClient(); wcfJsonClient.Callback += delegate(Object sender, WcfJsonClient.JsonValueEventArgs ea) { List<Person> personList = new List<Person>(); JsonArray array = (JsonArray)ea.JsonValue; foreach (JsonValue jsonValue in array) { Person person = new Person { City = ReadJsonValue(jsonValue, "City"), FirstName = ReadJsonValue(jsonValue, "FirstName"), LastName = ReadJsonValue(jsonValue, "LastName"), PostalCode = ReadJsonValue(jsonValue, "PostalCode"), State = ReadJsonValue(jsonValue, "State"), Guid = ReadJsonValue(jsonValue, "Guid"), }; personList.Add(person); //+ Write(person.FirstName); } //+ Write("Person count: " + personList.Count.ToString()); }; wcfJsonClient.PostHttpRequest("http://localhost:1003/Person.svc/json", "GetPersonList");
One aspect of this WcfJsonClient class that you may not have realized is that you could easily make it dual purpose with very few changes. If you added a second callback for XML parsing and change the content type, your "WcfJsonClient" becomes "WcfXmlJsonClient". This WebRequest technique itself can be used for just about anything. You could load CVS data or even access SOAP services manually if you wanted, giving you the ability to interact with WsHttpBinding services. If you know how to communicate with an HTTP service at the wire-level, then the WebRequest can probably make the connection happen.
The only other type of response you would ever really need to parse is a primitive, which is represented by the System.Json.JsonPrimitive, which also inherits from JsonValue. You don't need to do anything special with these at all. Just pull the value from them just as you would a value from JsonObject. In our case, this means running it through the ReadJsonValue method.
Thread Waiting
As we have just seen with our JSON client, many times when working in the Async Pattern, you will have an async chain. 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. In addition, using DevServer profiles, you can load up entire sets of web servers to quickly setup and teardown different development environments.
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 seehttp://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 document:
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. With this information we can create a channel to access our service. Then, with a few changes here and there we can optimize our experience for both security and fault tolerance. Or, in the case of JSON, we can use tools already provided to us to obtain the exact JSON fields we need. Hopefully you understand the often hidden concepts of WCF for .NET and Silverlight much better than you did before.
As a quick closing note, if you like going into technology at this level of depth, but also like thorough explanations, you seriously need to check out Juval Lowy's book,Programming WCF (2nd Edition).