Understanding WCF
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-side channel 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 the System.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 the System.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 the System.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.SerializableAttribute attribute, 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 keep performance, 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 of binding 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