What the heck is WCF? For a long time, I couldn't tell you exactly
what it was. And honestly, I still don't think I can. WCF is such a
broad concept that it's hard to boil down into a concise explanation.
When I finally started reading about it, and getting beyond the
concepts, things started to clear up. That's what today's tutorial is
about - cutting through all the concepts and just use WCF to build a
basic server and a basic client.
At its core, WCF is a mechanism that facilitates communication between
processes - either on the same machine or separate machines. Part of the
confusion I had when trying to understand WCF was the almost limitless
ways WCF can accomplish this. We're not going to worry about all of that
though. We're going to strip out everything except the bare essentials
required to get a client/server system up and running - and it's
actually very little code.
What we're building today will look a lot like a simple remote procedure
call. We'll be building a server that exposes a function that can be
called by a client. Just to show how easy it is to change the transport
layer, the client will connect to the server through two different
mechanisms - http and named pipe.
The first thing we need to do is define what the client will have access
to. We do this by defining an interface in C# and giving it a few
attributes for WCF. The server will create a class that implements the
interface to actually do the work. The client will just be provided the
interface so it knows which functions are available.
using System;
using System.ServiceModel;
[ServiceContract]
public interface IStringReverser
{
[OperationContract]
string ReverseString(string value);
}
Here's my simple interface. It provides a function that takes a string
and returns a new string with all of the characters reversed. The
ServiceContract attribute tells WCF that this interface can be exposed
for client use. The OperationContract attributes tells WCF that
ReverseString is part of this service contract and can be used by
clients. There are lots of optional settings that can be applied to both
of these attributes, but this is all that's required to get things up
and running. The System.ServiceModel namespace is available by adding
a reference to the System.ServiceModel assembly to your project, which
is available in the default installation of .NET 3.5.
Starting with the server, the first thing we need to do is create a
class that implements this interface and provides the functionality
behind ReverseString .
using System;
using System.ServiceModel;
[ServiceContract]
public interface IStringReverser
{
[OperationContract]
string ReverseString(string value);
}
public class StringReverser : IStringReverser
{
public string ReverseString(string value)
{
char[] retVal = value.ToCharArray();
int idx = 0;
for (int i = value.Length - 1; i >= 0; i--)
retVal[idx++] = value[i];
return new string(retVal);
}
}
Pretty simple, right? There might be more elegant ways to reverse a
string, but we're not here to criticize the implementation of this
function. It's actually not required to use the interface method for
defining service contracts (i.e. you could stick the attributes directly
on the class), but it's the recommended way, and it makes client
applications much easier to implement if they can simply share the same
interfaces.
Surprisingly, there's actually very little code required to make the
server fully functional. We'll begin by creating a ServiceHost , which
is responsible for most of the work behind exposing the service to
clients.
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(
typeof(StringReverser),
new Uri[]{
new Uri("http://localhost:8000"),
new Uri("net.pipe://localhost")
}))
{
}
}
}
The most difficult thing here is the array of Uri objects. These are our
base addresses that clients can use to connect to this WCF server. Like
I said before, we're exposing two ways to connect to this service: http
and named pipe. How the address is formatted depends on the type of
Binding
it represents.
Now that we've got our ServiceHost created, we need to configure some
endpoints. These will actually enable the http and named pipe bindings
and give them the address required by the client.
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(
typeof(StringReverser),
new Uri[]{
new Uri("http://localhost:8000"),
new Uri("net.pipe://localhost")
}))
{
host.AddServiceEndpoint(typeof(IStringReverser),
new BasicHttpBinding(),
"Reverse");
host.AddServiceEndpoint(typeof(IStringReverser),
new NetNamedPipeBinding(),
"PipeReverse");
host.Open();
Console.WriteLine("Service is available. " +
"Press <ENTER> to exit.");
Console.ReadLine();
host.Close();
}
}
}
Here we're adding two endpoints - one for http and one for named pipe.
The address that's passed in is what appears after the base address
specified in the ServiceHost constructor (e.g. for http it would be:
"http://localhost:8000/Reverse" ). We have to specify a base address for
each endpoint we're configuring. So if the net.pipe base address was not
present in the ServiceHost constructor, the server would throw an
exception when it attempted to create the named pipe endpoint. After the
endpoints are configured, we simply call Open on the ServiceHost to
enable it.
Believe or not, that's it for a fully functional WCF server. Below is
all of the code put together.
using System;
using System.ServiceModel;
namespace WCFServer
{
[ServiceContract]
public interface IStringReverser
{
[OperationContract]
string ReverseString(string value);
}
public class StringReverser : IStringReverser
{
public string ReverseString(string value)
{
char[] retVal = value.ToCharArray();
int idx = 0;
for (int i = value.Length - 1; i >= 0; i--)
retVal[idx++] = value[i];
return new string(retVal);
}
}
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(
typeof(StringReverser),
new Uri[]{
new Uri("http://localhost:8000"),
new Uri("net.pipe://localhost")
}))
{
host.AddServiceEndpoint(typeof(IStringReverser),
new BasicHttpBinding(),
"Reverse");
host.AddServiceEndpoint(typeof(IStringReverser),
new NetNamedPipeBinding(),
"PipeReverse");
host.Open();
Console.WriteLine("Service is available. " +
"Press <ENTER> to exit.");
Console.ReadLine();
host.Close();
}
}
}
}
Now we can move on to the client. The first thing we'll need in the
client code is the same interface, with the same attributes, that we
defined in the server. If this were being used in a production
environment, these interfaces would probably be created in a dedicated
library that could be easily distributed. For now, I just copied and
pasted the code into another project.
We have to first establish a channel between the client and a server. A
channel is basically a connection that allows the client and server to
send messages to each other. Fortunately, WCF provides something called
a ChannelFactory that makes creating these very simple.
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
namespace WCFClient
{
[ServiceContract]
public interface IStringReverser
{
[OperationContract]
string ReverseString(string value);
}
class Program
{
static void Main(string[] args)
{
ChannelFactory<IStringReverser> httpFactory =
new ChannelFactory<IStringReverser>(
new BasicHttpBinding(),
new EndpointAddress(
"http://localhost:8000/Reverse"));
ChannelFactory<IStringReverser> pipeFactory =
new ChannelFactory<IStringReverser>(
new NetNamedPipeBinding(),
new EndpointAddress(
"net.pipe://localhost/PipeReverse"));
IStringReverser httpProxy =
httpFactory.CreateChannel();
IStringReverser pipeProxy =
pipeFactory.CreateChannel();
}
}
}
We're building two proxies here - one for http and one for named pipe.
The addresses passed into the ChannelFactory constructor are the same as
those configured on the server. We also have to pass in the specific
bindings we want: BasicHttpBinding and NetNamedPipeBinding. Lastly we
call CreateChannel on each channel factory, which returns an
IStringReverser interface. Now we can call functions on those
interfaces and WCF makes a remote call through the channel to our server
to get the result.
string str = Console.ReadLine();
Console.WriteLine("http: " + httpProxy.ReverseString(str));
Console.WriteLine("pipe: " + pipeProxy.ReverseString(str));
Incredibly, we're now done with the client. Here's the client program in
its entirety:
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
namespace WCFClient
{
[ServiceContract]
public interface IStringReverser
{
[OperationContract]
string ReverseString(string value);
}
class Program
{
static void Main(string[] args)
{
ChannelFactory<IStringReverser> httpFactory =
new ChannelFactory<IStringReverser>(
new BasicHttpBinding(),
new EndpointAddress(
"http://localhost:8000/Reverse"));
ChannelFactory<IStringReverser> pipeFactory =
new ChannelFactory<IStringReverser>(
new NetNamedPipeBinding(),
new EndpointAddress(
"net.pipe://localhost/PipeReverse"));
IStringReverser httpProxy =
httpFactory.CreateChannel();
IStringReverser pipeProxy =
pipeFactory.CreateChannel();
while (true)
{
string str = Console.ReadLine();
Console.WriteLine("http: " +
httpProxy.ReverseString(str));
Console.WriteLine("pipe: " +
pipeProxy.ReverseString(str));
}
}
}
}
It's amazing when you think about the amount of stuff happening behind
the scenes that developers no longer have to worry about. Basically, all
developers have to do is define interfaces and objects and WCF takes
care of the rest. The server side and client side code can be
drastically reduced by using configuration files to replace the setup we
did in code, but for the purpose of understanding, explicitly setting it
is a good way to know what's behind configuration settings.
I think that about wraps it up for this introduction to WCF. There's an
immense amount of configuration and customization supported by WCF, and
volumes could be dedicated to it. Hopefully we'll slowly unravel WCF's
complexity and introduce new concepts in following tutorials. You can
download Visual Studio 2008 solutions for both the client and the server
here.
Source Files:
|