Introduction
I’m impressed! And that’s not an easy thing to do. I’ve been playing with the CTP (February 2006) release for a while now, and believe it or not, I think we may actually have something here. I have not had the time to explore the other ‘Foundation’ members (of WinFX), but from what I’ve seen of the Windows Communication Foundation (WCF), it really is a very nice package. Yes, I know that each time there is a new technology released, there is always some neat features. But in the past, I always thought that we were paying more (in complexity) than what we were getting (in functionality), can you spell COM? This time, it feels like we are getting the whole thing for free. It’s possible that it may be just my naïve perspective, but hey it’s all about me anyway.
I like to preface my articles with a disclaimer. The content in this article is simply my impressions and interpretation, and it should be read in that context. Even though the prose may at times seem like I know what I’m talking about, any similarity to actual facts may simply be coincidence. Enjoy the journey.
Why can’t we just talk?
The minute we stepped out of the DOS box, we realized that we were not alone any more, and we needed to learn to get along and communicate with the other inhabitants of the virtual world. DDE, OLE, COM, DCOM, and Remoting have been some of the attempts at providing mechanisms for two applications to be able to talk to each other. Remember how OLE and COM were described when first introduced? As the ‘foundation for all future products’. With hindsight, we can see that they were really just baby steps. Each one solved only a small part of the whole problem. So if they were baby steps, then WCF is certainly a giant leap. WCF provides a complete solution to the communication problem. And it does it with elegance and simplicity. Can you tell that I’m just a little enthusiastic?
Whether your requirement is to communicate with another module on the same machine, or another module implemented in a different language, or you need to communicate with a module that’s on a machine on the other side of the world, or you want to communicate with a module running on a different platform, or even communicate with a module that’s not even running!, yup, you can do it under WCF.
In my opinion, the beauty of WCF is that it is an ‘all-inclusive’ solution. It is the first one to provide a complete end-to-end solution covering the scope and depth of the problem. It is also the simplest from a programmer's point of view; you are always just making a method call. Yeah, I am sure that there is quite a bit of magic going on under the covers to support that simplicity of use.
Now, I have not explored every nook and cranny, or option, or possibility, but from what I’ve seen, it’s an excellent solution. At least for me, and as I said before…
What is a service?
Let’s see how much trouble I can get myself into here. I think that, in its most elemental form, a service is simply some functionality (code) running in some external process that is made available to other processes in a standard way. That’s pretty much the crux of it, except that in a standard way also encompasses platform and language neutrality.
So, by the above definition, a service really has two parts. First is the code that must be running somewhere in order to provide some functionality to clients. And second, there must be some generic mechanism that can be used by any process, regardless of the platform, language, or locality, that makes the service accessible. That generic mechanism has turned out to be XML and SOAP. Of course, there are some additional facilities required in order for a client to be able to know (or discover) what functionality the service makes available. But I think of those as supporting technologies.
There is also some glue that is required in order to tie the two parts of a service together. That glue is the code that will support the communication medium (transport) that is being used by the service and the client to talk to each other. Being lazy…I mean smart, we’ve come up with some generic glue also. This way, each service implementation does not have to re-invent the wheel. For Web Services, the generic glue is a Web Server. So, a Web Server provides a hosting environment for services that use HTTP as their transport mechanism. I would also like to suggest that Web Services are a special implementation of a service as defined above.
Here are the things that we will be examining in the rest of this article. How do you define a service? How do you implement a service? How do you host a service? How do you access and use a service? Once we have the basics nailed down, we’ll look at some of the more complex communication options that WCF facilitates.
So what is WCF?
Here is what WCF is for me:
WCF is an inter-application communication platform. It provides a common API that hides the underlying communication medium. It can be platform neutral, and it provides for locality indifference. Essentially, under WCF, I, as a programmer, do not need to know or care where the other end is or how the actual communication is taking place. To me, that is beautiful!
WCF is services based technology. It has, as its roots, Web Services, and thus XML and SOAP are its core technologies. WCF took the concept of Web Services and super-charged it. Much of the look and feel of WCF behaves like traditional Web Services. In fact, I like to think of WCF services as Web Services on steroids. You define WCF services much like Web Services. You can interrogate a known service for its methods with the same protocols that are available for Web Services. And you have very similar infrastructure requirements as are for Web Services. The main difference is that WCF has expanded the transports that are available to include TCP/IP, IPC (named pipes), and message queue-ing, in addition to HTTP. I think the focus of Web Services is to solve the interoperability problem, and the focus of WCF is to solve the much broader communication problem. And it has done this while still maintaining a uniform API as well providing more efficient mechanisms.
The most important feature from a developer perspective is that you don’t have to be concerned with what or how you are communicating. The code is the same, no matter what the final transport mechanism or locality of service might be.
Service Contracts-exposing functionality
Our WCF journey starts with how services define the functionality that they expose. Much of the infrastructure required to implement services under WCF is specified using declarative programming. That means, using attributes to specify functionality. The following shows how to declare an interface that will be exposed as a service:
[ServiceContract]
public interface ILocalTime
{
[OperationContract]
string GetLocalTime();
}
public class LocalTimeService : ILocalTime
{
...
}
The ServiceContract
attribute specifies that the interface defines the functionality of a service. OperationContract
is used to decorate each method that is to be exposed as part of the service. That is all that is required to create a WCF service. Just slightly more is required to actually deploy the service, which we’ll cover later on.
By the way, you don’t have to use interfaces when implementing a service, just like you don’t have to use an interface to define a class. You do have to specify what you want exposed through a service, explicitly. You can define anything else you want or need as part of the interface, but only methods, and only methods that get decorated with [OperationContract]
, will be exposed by the service.
Data Contracts-exposing data types
WCF also allows you to expose custom data types so that you are not restricted to simple data types of the CLR. These are simple structs with no methods associated with it. This can be a little confusing sometimes, because the same syntax is used for both services as well as for CLR definitions. Here’s an example of a DataContract
that we will use.
[DataContract]
public class SensorTemp
{
[DataMember]
public int probeID;
[DataMember]
public int temp;
}
DataContract
specifies the data type that you are exposing and, DataMember
specifies the members that are part of the data type. As is the case with ServiceContract
, you have to explicitly declare which members are to be exposed to external clients, using DataMember
. What that means is that you can include anything else that you may want (or need) as part of the class definition, but only the members decorated with DataMember
will be visible to clients.
Coding options in WCF
As we saw above, one of the options available to specify functionality under WCF is to use attributes. Attributes are translated by the compiler to generate much of the infrastructure required by WCF in order for us to create and use services.
The second way you can specify many of the options is through configuration files. This allows you to make changes without having to re-compile. Many of the WCF classes will automatically use default values from the config file. Here’s an example of an endpoint specified using config data (endpoints will be described shortly). First, the config file, then the code statement referencing the config file data:
<endpoint name ="LocalTimeService"
address="net.pipe://localhost/LocalTimeService"
binding="netNamedPipeBinding"
contract="ILocalTime" />
LocalTimeProxy proxy = new LocalTimeProxy("LocalTimeService");
Finally, the third way of coding functionality is, of course, programmatically. Many of the things that you can do via attributes or config files can also be done programmatically. Here is the previous endpoint, defined programmatically:
Uri baseAddress = new Uri(ConfigurationManager.AppSettings["basePipeTimeService"]);
baseAddress = new Uri(ConfigurationManager.AppSettings["basePipeTimeService"]);
serviceHost.AddServiceEndpoint(typeof(ILocalTime),
new NetNamedPipeBinding(), baseAddress);
Endpoints
Endpoints are the ‘identity’ of a service. They define all the information that we need in order to establish and communicate successfully with a service. Endpoints are made up of three pieces of information: Address, Binding, and Contract. The address is obviously the location of the service, such as ‘net.pipe://localhost/LocalTimeService’. The binding specifies security options, encoding options, and transport options, which means a lot of options! Luckily, there is a collection of pre-defined bindings provided with WCF that we can use to make our life simpler. And finally, the contract is the actual interface that the service implements.
Implementing a WCF Service
So, a service is nothing more than a regular class that gets decorated with some special attributes. The attributes are then translated by the compiler to generate the special infrastructure code required to expose the class as a service to the world. In the following code, we first define an interface that has one method that returns the local time of where the service has been deployed. The LocalTimeService
class then implements the interface, and thus exposes the functionality to the world, or at least to whomever is interested.
[ServiceContract]
public interface ILocalTime
{
[OperationContract]
string GetLocalTime();
}
[ServiceBehavior(InstanceContextMode =
InstanceContextMode.PerCall)]
public class LocalTimeService : ILocalTime
{
public string GetLocalTime()
{
return DateTime.Now.ToShortTimeString();
}
}
That’s all that’s needed to create a WCF service. If you compile the above code into a DLL (a library), you will have created a service. Of course, there is a little more needed in order to have something that’s useable. We need two other pieces in order to complete the service. We need something that will be able to load the service DLL when a client requests the functionality of the service. And we need something that will be able to listen on a communication port, and look through everything that is being received to see if it matches what we are responsible for, our service contract.
Deploying a WCF Service
There are a number of ways to deploy our service. First, if we implement our service to support HTTP, then we could deploy our service just like a regular Web Service, using IIS. If we want to support some of the other transports, then we could deploy the service using Windows Activation Service (WAS), which is an enhancement available in IIS 7.0. If either of these is not suitable or we want more control over the service, then the other solution is to build our own hosting environment by using ServiceHost
. ServiceHost
is a class available in WCF to host services, almost like a mini IIS, just for our service. We can implement ServiceHost
in any housing available under Windows, in a console app, a Windows executable, or a Windows service (formerly NT service).
ServiceHost
will listen on whatever channel we specify for our service, using whatever protocol we specify, and call our service whenever a client request for our specific service comes in. That’s a lot of bang for just a couple of lines of code. All that we need to do is tell ServiceHost
the endpoint that it is responsible for and the class that it should instantiate when a matching message is received. Here’s the code that’s required to host the LocalTimeService
in a console app:
class TimeService
{
static void Main(string[] args)
{
Uri baseAddress = new Uri(
ConfigurationManager.AppSettings["basePipeTimeService"]);
ServiceHost serviceHost = new ServiceHost(typeof(LocalTimeService),
baseAddress);
serviceHost.Open();
Console.WriteLine("Service is running....press any key to terminate.");
Console.ReadKey();
serviceHost.Close();
}
}
You can now compile the service. However, if you try to run it, you’ll get an error message indicating that you haven’t provided ServiceHost
with an endpoint. As we saw above, you can specify endpoints either programmatically, or by using the configuration file. The nice thing about using configuration is that you can change it at any time and you don’t have to recompile. As we’ll see later, you can specify multiple endpoints for a service, depending on the clients that you want to support. And if at some point later, you decide to not support a specific transport, you just have to edit the configuration file.
Here’s the config file that we’ll need for the LocalTimeService
:
<configuration>
<appSettings>
<add key="basePipeTimeService"
value="net.pipe://localhost/LocalTimeService" />
</appSettings>
<system.serviceModel>
<services>
<service name="LocalTimeService">
<endpoint
address=""
binding="netNamedPipeBinding"
contract="ILocalTime"
/>
</service>
</services>
</system.serviceModel>
</configuration>
Let’s examine the entries in the config file. You should note that there could be as many entries as needed. For example, there could be several endpoints that the service supports (different transports). There is also only one service being specified in this example, but there could be several services provided in the same housing. You can see that the endpoint has three properties: address, binding, and contract. The binding indicated is referencing the standard netNamedPipeBinding
provided in WCF. There are various default binding classes provided for each transport. You can see the options for each in the docs.
I will say here that you too will encounter the “zero application (non-infrastructure) endpoints” exception at some point. There won’t be too many clues as to what exactly is not matching up, so you’ll have to scrutinize the text. Make sure that you have the correct namespaces specified.
Now you can execute the application, and the service will be available to any client that knows how to communicate with it.
Proxies
Just by saying we want to go to New York and we are going to go by car, does not get us there. We need a car to actually get us there. Having a completely defined endpoint is not enough. We need something (code) that will actually take the endpoint as a parameter and allow us to do what we want to do, call a method. And that something is a proxy.
As was the case in the past with COM, proxies take care of all the low level plumbing (serializing and packaging our parameters) so that we just need to make the call. We don’t care how they are forced through the ‘spigot’ or how they are pulled out on the other side.
And as we’ve also had in the past, there is a utility that will create the proxies for us. However, this is one area of WCF that needs some improvement. And I’m guessing this functionality will become incorporated into Visual Studio in future releases. At least, I would hope so. To create a proxy, you need to use the command line utility, svcutil, which has a number of switches that are not all or well documented. But hey, I’m not complaining, it’s a small inconvenience for a whole lot of major improvements. And it’s still only Beta.
So, you run svcutil against your service DLL and bam! You got your proxy class. There are other options, like if the service has a MEX endpoint, you can direct it to the service, and it will extract the service information dynamically from the service. This is essentially the same functionality provided through Studio when creating a Web Service, and we use the ‘Add Web Reference’ dialog. What I really want is for Visual Studio to automatically generate the proxies since it has all the information in the source files to begin with! But as I said, I’m not complaining. ;)
Currently then, creating the proxy is a two step process. First, you run svcutil against your service DLL, which will create the schema (XSD) and WSDL files. Then, you invoke svcutilagain, but this time, you run it against the output it just created (*.xsd, *.wsdl).
The svcutil will generate ‘output.cs’ as the default file name, unless you specify otherwise, I normally just rename it. There are also options to just generate DataContracts
or just ServiceContracts
, and also an option to generate a client config file. Here’s the proxy file for the LocalTimeService
, with some portions edited for readability. There’s not much there, since all the magic occurs in ClientBase
.
[ServiceContract]
public interface ILocalTime
{
[OperationContract]
string GetLocalTime();
}
public interface ILocalTimeChannel : ILocalTime,
System.ServiceModel.IClientChannel
{
}
public partial class LocalTimeProxy :
System.ServiceModel.ClientBase<ILocalTime>, ILocalTime
{
public LocalTimeProxy()
{
}
public string GetLocalTime()
{
return base.InnerProxy.GetLocalTime();
}
}
Consuming a WCF Service
So now, the next logical step is to build a client that knows how to consume the service that is being provided. The client code only needs two things: the proxy that allows it to communicate with the service, and the endpoint to the service. Here’s one version of a client that consumes the LocalTimeService
:
Collapse
class Client
{
public bool keepClocking = true;
LocalTimeProxy proxy = null;
public Client()
{
proxy = new LocalTimeProxy();
}
public void ClockingThread()
{
while (keepClocking)
{
Console.WriteLine(proxy.GetLocalTime());
Thread.Sleep(1000);
}
proxy.Close();
}
static void Main(string[] args)
{
Client client = new Client();
Thread thread = new Thread(new ThreadStart(client.ClockingThread));
thread.Start();
Console.WriteLine("Service is running....press" +
" any key to terminate.");
Console.ReadKey();
client.keepClocking = false;
Thread.Sleep(2000);
}
}
Not much to this client. All that it's doing is making a call to the service method GetLocalTime()
, once a second. As you can see, the client code has no indication as to what or where the other end of the method call is. Nor what mechanism is actually being used to make the connection. It is just a simple class method call! As we look at other examples, you'll keep seeing the simplicity of coding that is provided under WCF. And here is the config file that specifies the endpoint to the service which is required by the client.
<configuration>
<system.serviceModel>
<client>
<endpoint name ="LocalTimeService"
address="net.pipe://localhost/LocalTimeService"
binding="netNamedPipeBinding"
contract="ILocalTime" />
</client>
</system.serviceModel>
</configuration>
Compile and run the client. Start several instances. Just make sure that the service is started before you start the clients, otherwise nobody will be listening.
That’s it for the basics in getting services listening and consuming services. In Part 2, we'll build some examples that will demonstrate WCF's support for the various communication patterns. The download includes all of the source code for the sample applications described in the articles.
Al Alberto
|
Click here to view Al Alberto's online profile.
|