Windows® Communication
Foundation (WCF), formerly code-named "Indigo," is about to radically
change the face of distributed programming for developers using the
Microsoft® .NET Framework. WCF unifies
the existing suite of .NET distributed technologies into a single
programming model that improves the overall developer experience through
a consistent architecture, new levels of functionality and
interoperability, and all the extensibility points you could want. This
article introduces you to WCF programming and shows you how to get
started.
As
its name suggests, WCF provides the .NET Framework with a basis for
writing code to communicate across components, applications, and
systems. WCF was designed according to the tenets of service
orientation. A service is a piece of code you interact with through
messages. Services are passive. They wait for incoming messages before
doing any work. Clients are the initiators. Clients send messages to
services to request work.
Figure 1 Services and Endpoints
Services
expose one or more endpoints where messages can be sent. Each endpoint
consists of an address, a binding, and a contract (see Figure 1).
The address specifies where to send messages. The binding describes how
to send messages. And the contract describes what the messages contain.
Clients need to know this information before they can access a service.
Services
can package up endpoint descriptions to share with clients, typically
by using Web Services Description Language (WSDL). Then clients can use
the provided service description to generate code within their
environment capable of sending and receiving the proper messages (see Figure 2).
Figure 2 Sharing Endpoint Descriptions
Windows
Communication Foundation provides a new library of classes found in the
System.ServiceModel namespace that bring these service-oriented
concepts to life. This is what's typically referred to as the WCF
programming model.
WCF Programming Model
With
WCF, you're either writing services that expose endpoints or you're
writing clients that interact with endpoints. Hence, endpoints are
central to the WCF programming model and infrastructure. WCF models
endpoints with the .NET classes and interfaces shown in Figure 3. This mapping is true whether you're writing WCF clients or services.
Figure 3 WCF Classes and Interfaces
Element | Class or Interface |
---|---|
Endpoint | System.ServiceModel.ServiceEndpoint |
Address | System.Uri |
Binding | System.ServiceModel.Binding |
Contract | Interfaces annotated with System.ServiceModel attributes |
When
building a WCF service, you typically start by defining a .NET
interface definition to serve as the service contract. Then you
implement the service contract in a .NET class, known as the service
type, and configure its behavior. Next, you define the endpoints the
service will expose, specifying the address, binding, and contract for
each one. Finally, you host the service type in an application using the
WCF hosting infrastructure. Once the service type is hosted, clients
can retrieve its endpoint descriptions and begin integrating with it.
When
building a WCF client, you first need the description of the target
endpoint you want to access. The endpoint description can be used to
dynamically create a typed proxy. WCF provides a tool named SvcUtil.exe
for automating this process. Then you can write code against the typed
proxy to access the service by sending the appropriate messages to the
target endpoint.
Service Contracts and Dispatch Behavior
You
model service contracts in .NET using traditional C# interface
definitions. You can use any .NET interface as a starting point, such as
the one shown here:
To make this a WCF service contract, you must annotate the interface
itself with [ServiceContract] and each operation you want to expose with
[OperationContract]:
namespace ServiceLibrary
{
public interface IEchoService
{
string Echo(string msg);
}
}
using System.ServiceModel; namespace ServiceLibrary { [ServiceContract(Namespace="http://example.org/echo/")] public interface IEchoService { [OperationContract] string Echo(string msg); } }
These
attributes influence the mapping between the worlds of .NET and SOAP.
WCF uses the information found in the service contract to perform
dispatching and serialization. Dispatching is the process of deciding
which method to call for an incoming SOAP message. Serialization is the
process of mapping between the data found in a SOAP message and the
corresponding .NET objects used in the method invocation. This mapping
is controlled by an operation's data contract.
WCF
dispatches based on the message action. Each method in a service
contract is automatically assigned an action value based on the service
namespace and method name. For example, the default action for the Echo
method just shown is http://example.org/echo/Echo. You can customize the
action value for each method using [OperationContract]. A value of *
can be used for any action when a specific match doesn't exist.
In
the following example, WCF will dispatch messages with an action of
urn:echo:string to the Echo method. Messages with any other action are
dispatched to the EchoMessage method:
[ServiceContract(Namespace="http://example.org/echo/")]
public interface IEchoService
{
[OperationContract(Action="urn:echo:string")]
string Echo(string msg);
[OperationContract(Action="*")]
Message EchoMessage(Message msg);
}
Data Contracts
Once
the target method has been determined based on the action, WCF relies
on the method's data contract to perform serialization. The data
contract is defined by the types used in the method signature. In the
previous example, EchoMessage is generic and could be used to process a
variety of incoming SOAP messages. Therefore I've used Message to model
the input and output. Message is a special type used to represent all
messages flowing through WCF. When using Message, WCF does not perform
type-based serialization. Instead, it just gives you direct access to
what's found in the SOAP message.
When
Message is not used, WCF performs serialization to map between the data
found in the SOAP message and the corresponding .NET objects needed to
invoke the method. For example, in the case of Echo, WCF maps the SOAP
payload to a .NET string. WCF provides an implicit mapping for all .NET
primitive types (string, int, double, and so on).
The
way WCF serializes .NET classes depends on the serialization engine in
use. The default serialization engine is known as DataContract, a
simplified version of XmlSerializer, the default serialization engine
used in ASMX today. DataContract defines attributes for annotating class
definitions to influence the serialization process. Here's an example
of it in use:
With DataContract, only fields marked with DataMember will be
serialized. And you can serialize private fields. This is much different
from the way XmlSerializer works. Now you can use Person in an
operation contract:
When EchoPerson is invoked, WCF will serialize Person instances
according to the DataContract attributes specified on the Person class.
DataContract produces a very simple XML structure—a sequence of
elements. You can control the name of each element, the order, and
whether a particular element is required, but that's about it.
[DataContract(Namespace="http://example.org/person")]
public class Person
{
[DataMember(Name="first", Order=0)]
public string First;
[DataMember(Name="last", Order=1)]
public string Last;
[DataMember(IsRequired=false, Order=2)]
private string id;
...
}
[ServiceContract(Namespace="http://example.org/echo/")] public interface IEchoService { ... // previous methods omitted [OperationContract] Person EchoPerson(Person p); }
If
you need to do anything more sophisticated, WCF lets you fall back to
using XmlSerializer. You indicate your desire to use XmlSerializer by
annotating the interface with [XmlSerializerFormat]:
This tells WCF to use XmlSerializer for all types in the
contract, according to the XmlSerializer defaults and the various
System.Xml.Serialization customization attributes. This makes it much
easier to move existing ASMX services forward to WCF.
[ServiceContract]
[XmlSerializerFormat]
public interface IEchoService { ... }
WCF
also supports serializing types marked with [Serializable], which
allows .NET remoting types to work with WCF without change. WCF provides
an implicit mapping for [Serializable] types where all public/private
fields are automatically serialized.
Message and Service Contracts
All
of the examples up to this point have relied on WCF to automatically
map the method signature to the SOAP message. The parameter list and
return type are always mapped to the SOAP body for the request and
response, respectively. If you need to support headers, you can write
another class that models the structure of the entire SOAP envelope for
the particular operation, specifying which fields map to headers versus
the body. You define this mapping with the [MessageContract] attributes:
[MessageContract]
public class EchoPersonMessage
{
[MessageBody]
public Person Person;
[MessageHeader]
public Authorization Authorization;
}
In
this example, the Person field is mapped to the SOAP body while the
Authorization field is mapped to a SOAP header. Now you can use
EchoPersonMessage in an operation contract:
Using MessageContract is a more advanced technique that is only necessary when you need direct control over the SOAP contract.
[ServiceContract]
public interface IEchoService
{
... // previous methods omitted
[OperationContract]
void EchoPerson(EchoPersonMessage msg);
}
Implementing Service Contracts
Now you can implement the service contract by simply implementing the .NET interface in a class:
By doing this, EchoService is guaranteed to support the service contract
defined by IEchoService. When using an interface to define the service
contract, you don't need any contract-related attributes on the class
definition. However, you may want to use [ServiceBehavior] to influence
its local behavior:
using System.ServiceModel;
namespace ServiceLibrary
{
public class EchoService : IEchoService
{
public string Echo(string msg)
{
return msg;
}
... // remaining methods omitted
}
}
using System.ServiceModel; namespace ServiceLibrary { ... // interface definition omitted [ServiceBehavior( InstanceContextMode=InstanceContextMode.Single, ConcurrencyMode=ConcurrencyMode.Multiple)] public class EchoService : IEchoService { ...
This
particular example tells WCF to manage a singleton instance of the
service type and to allow multithreaded access to the instance. There is
also an [OperationBehavior] attribute for controlling operation-level
behavior.
Behaviors
influence processing within the host, but have no impact on the service
contract whatsoever. Behaviors are one of the primary WCF extensibility
points. Any class that implements IServiceBehavior can be applied to a
service through the use of a custom attribute or configuration element.
Hosting the Service and Defining Endpoints
To
use EchoService, you need to host it in a .NET-based application. The
ServiceHost class gives you direct control over the WCF hosting
infrastructure. You instantiate ServiceHost based on a particular
service type. The following code shows how to do this in a console
application:
using System;
using System.ServiceModel;
using ServiceLibrary;
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(
typeof(EchoService), new Uri("http://localhost:8080/echo")))
{
...
In
addition to specifying the service type, you also specify the base
addresses for the different transports you plan to use. In this example,
I've specified a base address of http://localhost:8080/echo. This will
be used as the base for any relative HTTP addresses I might specify when
adding endpoints. The base HTTP address is also used as the default for
retrieving the service description.
You
then add the service endpoints. Again, a service may have one or more
endpoints and each endpoint consists of an address, a binding, and a
contract. You provide this information to your ServiceHost by calling
AddServiceEndpoint. This is where you specify the service contract you
defined earlier (IEchoService). For the binding, you typically choose
from one of the many predefined bindings that ship with WCF and an
appropriate address given the binding's transport. Here's an example:
host.AddServiceEndpoint(typeof(IEchoService),
new BasicHttpBinding(), "svc");
host.AddServiceEndpoint(typeof(IEchoService),
new NetTcpBinding(), "net.tcp://localhost:8081/echo/svc");
In
this particular example, I've specified IEchoService as the contract
for both endpoints, but with each endpoint using a different binding and
address. The first endpoint uses the BasicHttpBinding and a relative
HTTP address of svc (which would make its absolute address
http://localhost:8080/echo/svc). The second endpoint uses the
NetTcpBinding and an address of net.tcp://localhost:8081/echo/svc.
Therefore, this service allows consumers to communicate with it over
either HTTP or TCP using different WS-* protocols, as defined by each
binding.
Choosing and Customizing Bindings
The
WCF infrastructure relies heavily on endpoint definitions to control
how messages are processed. In fact, WCF uses the endpoint definitions
to dynamically build the appropriate message processing runtime within
hosts and clients. The binding has the most significant influence on
this process.
The
binding controls three aspects of message communication: the suite of
WS-* protocols, including WS-Security, WS-ReliableMessaging, and so on;
the message encoding, such as XML 1.0, Message Transmission Optimization
Mechanism (MTOM), and binary; and the transport protocol, including
HTTP, TCP, and Microsoft Message Queuing (MSMQ). A given binding
specifies one message encoding and one transport, but it can specify
numerous WS-* protocols. Given that, there are an overwhelming number of
permutations that could be used. To simplify things, WCF provides a set
of predefined bindings that fit the most common use cases. Figure 4 lists some of these and describes what they embody.
Figure 4 Predefined WCF Bindings
Class Name | Element Name | Transport | Encoding | WS-* Protocols |
---|---|---|---|---|
BasicHttpBinding | basicHttpBinding | HTTP | XML 1.0 | WS-I Basic Profile 1.1 |
WSHttpBinding | wsHttpBinding | HTTP | XML 1.0 | Message security, reliable sessions, and transactions |
WSDualHttpBinding | wsDualHttpBinding | HTTP | XML 1.0 | Message security, reliable sessions, and transactions |
NetTcpBinding | netTcpBinding | TCP | Binary | Transport security, reliable sessions, and transactions |
NetNamedPipeBinding | netNamedPipeBinding | Named Pipes | Binary | Transport security, reliable sessions, and transactions |
NetMsmqBinding | netMsmqBinding | MSMQ | Binary | Transport security and queue transactions |
Developers
who care primarily about interoperability and don't need WS-*
functionality will typically use BasicHttpBinding. Developers who care
about interoperability, but also need message-based security, reliable
messaging, and transactions will typically choose WSHttpBinding.
Developers using WCF on both sides (client and service) can choose from
NetTcpBinding and NetNamedPipeBinding, which provide significant
optimizations. Those building asynchronous systems can choose
NetMsmqBinding. There are also a few bindings that aren't listed here,
such as MsmqIntegrationBinding for integrating with existing MSMQ
applications, NetPeerTcpBinding for building peer-to-peer services, and
WSFederationBinding for federated security. A service can support any
combination of these bindings.
Once
you choose a binding and instantiate it, you can customize some of its
characteristics through the properties exposed on the binding class. The
following example shows how to enable transport-based security on the
BasicHttpBinding:
If the predefined bindings and their customizations still don't fit your
needs, you can also write a custom binding by implementing a class that
derives from Binding.
BasicHttpBinding b = new BasicHttpBinding();
b.Security.Mode = BasicHttpSecurityMode.Transport;
b.Security.Transport.ClientCredentialType =
HttpClientCredentialType.Basic;
host.AddServiceEndpoint(typeof(IEchoService), b, "svc");
Opening the Host
Now
that you've configured the host with endpoint information, WCF can
build the runtime needed to support your configuration. This occurs when
you call Open on a particular ServiceHost:
Once the call to Open returns, the WCF runtime is built and ready to
receive messages at the addresses specified by each endpoint. During
this process, WCF generates an endpoint listener for each supplied
endpoint and the code needed to support the WS-* protocols specified by
the binding. (An endpoint listener is the piece of code that actually
listens for incoming messages using the specified transport and
address.)
host.Open();
You
can retrieve information about the service at run time through the
object model exposed by ServiceHost. This allows you to retrieve
anything you might want to know about the initialized service, such as
what endpoints it exposes and what endpoint listeners are currently
active.
Figure 5 shows a complete example of the console application that hosts EchoService. Figure 6
shows the output. Notice there are two additional endpoint listener
addresses that I didn't specify in a ServiceEndpoint. WCF automatically
provided these using the base HTTP address for retrieving the service
description.
Figure 5 Console App Hosting EchoService
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(
typeof(EchoService), new Uri("http://localhost:8080/echo")))
{
// define the service endpoints
host.AddServiceEndpoint(typeof(IEchoService),
new BasicHttpBinding(), "svc");
host.AddServiceEndpoint(typeof(IEchoService),
new NetTcpBinding(), "net.tcp://localhost:8081/echo/svc");
host.Open();
Console.WriteLine(
"{0} is open and has the following endpoints:\n",
host.Description.ServiceType);
int i=1;
foreach (ServiceEndpoint end in host.Description.Endpoints)
{
Console.WriteLine("Endpoint #{0}", i++);
Console.WriteLine("Address: {0}",
end.Address.Uri.AbsoluteUri);
Console.WriteLine("Binding: {0}",
end.Binding.Name);
Console.WriteLine("Contract: {0}\n",
end.Contract.Name);
}
Console.WriteLine(
"The following EndpointListeners are active:\n");
foreach (EndpointListener l in host.EndpointListeners)
Console.WriteLine(l.Listener.Uri.AbsoluteUri);
// keep the process alive
Console.ReadLine();
}
}
}
Figure 6 Output of Console App
If you browse to http://localhost:8080/echo, you'll see the default WCF help page (see Figure 7)
and if you browse to http://localhost:8080/echo?wsdl, you'll see the
generated WSDL definition. The other endpoint listener
(http://localhost:8080/echo/mex) knows how to speak WS-MetadataExchange.
It's important to note that this sample is not using IIS in any way.
WCF provides built-in integration with httpsys, which allows any
application to automatically become an HTTP listener.
Figure 7 EchoService Help Page
Configuring Service Endpoints
Hardcoding
endpoint information into the host application is not ideal since
endpoint details may change over time. It's not hard to imagine how you
could store the endpoint information in a configuration file or database
and read it while initializing ServiceHost. Since this is a common
need, WCF automatically provides this functionality through the
predefined <system.serviceModel> configuration section.
The following configuration file defines the same two endpoints that were specified by calling AddServiceEndpoint:
If you add this configuration file to the host application shown in Figure 5
and comment out the calls to AddServiceEndpoint, you'll see the same
results in the output. This increases deployment flexibility since the
communication details are completely factored out of the compiled code.
You can also configure bindings within <system.serviceModel>. Figure 8 shows how to configure basicHttpBinding to use transport security like I had done in code.
<configuration>
<system.serviceModel>
<services>
<service type="ServiceLibrary.EchoService">
<endpoint address="svc" binding="basicHttpBinding"
contract="ServiceLibrary.IEchoService"/>
<endpoint address="net.tcp://localhost:8081/echo/svc"
binding="netTcpBinding"
contract="ServiceLibrary.IEchoService"/>
</service>
</services>
</system.serviceModel>
</configuration>
Figure 8 Configuring Transport Security
<configuration>
<system.serviceModel>
<services>
<service type="ServiceLibrary.EchoService">
<endpoint
address="https://localhost:8082/echo/svc"
binding="basicHttpBinding"
bindingConfiguration="MyBindingConfiguration"
contract="ServiceLibrary.IEchoService"/>
...
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="MyBindingConfiguration">
<security mode="Transport">
<transport clientCredentialType="Basic" />
</security>
</binding>
</basicHttpBinding>
</bindings>
</system.serviceModel>
</configuration>
You
can even define new custom bindings from scratch using the
<customBinding> element, which would be equivalent to deriving a
new class from Binding. When it comes to configuring endpoints,
bindings, and even behaviors, anything you can do in code, you can also
do through configuration.
Figure 9 Using the GUI to Define an Endpoint
Although IntelliSense® for the configuration file works well in Visual Studio® 2005, some developers still prefer to avoid XML altogether. The WinFX®
SDK includes a tool called SvcConfigEditor, which provides a graphical
interface for working with the various <system.serviceModel>
settings. Figure 9 shows how to configure endpoints using this tool.
Using Activation Services
Although
I've been experimenting with console applications, ServiceHost makes it
easy to host WCF services in a variety of applications, including
Windows Forms applications and managed Windows Services. In all of these
application scenarios, you're responsible for managing the process
hosting WCF. This is known as self-hosting in WCF terms.
In
addition to self-hosting, WCF also makes it possible to activate
services by using IIS along with traditional Web hosting techniques. You
can accomplish this by using a .svc file (similar to .asmx) that
specifies the service type to host:
You place this file in a virtual directory and deploy your service type
to its \bin directory or the Global Assembly Cache (GAC). When using
this technique you specify the service endpoint in the web.config, but
you don't need to specify the address since it's implied by the location
of the .svc file.
<%@Service class="ServiceLibrary.EchoService" %>
<configuration> <system.serviceModel> <services> <service type="ServiceLibrary.EchoService"> <endpoint address="" binding="basicHttpBinding" contract="ServiceLibrary.IEchoService"/> </service> ...
Now
if you browse to the .svc file, you'll notice the help page is
displayed, showing that a ServiceHost was automatically created within
an ASP.NET application domain. This is accomplished by an HTTP module
that filters incoming .svc requests and automatically builds and opens
the appropriate ServiceHost when needed.
If
you install the WinFX extensions for Visual Studio 2005, you'll find a
new Web Site project template called Indigo Service (this name will
change). When you create a new Web site based on this template, it
automatically gives you the .svc file along with the corresponding
implementation class (found in App_Code). It also comes with a default
endpoint configuration in web.config.
On
IIS versions 5.1 and 6.0 the WCF activation model is tied to the
ASP.NET pipeline, and therefore to HTTP. IIS 7.0, which is planned for
release with Windows Vista™, introduces
a transport-neutral activation mechanism known as Windows Activation
Services (WAS). With WAS, you'll be able to leverage .svc-like
activation over any transport.
Programming Clients
Programming
WCF clients involves sending messages to a service endpoint according
to its address, binding, and contract. To accomplish this, you first
create a ServiceEndpoint representing the target endpoint. The following
example shows how to create a ServiceEndpoint, based on the service's
HTTP endpoint, in a client console application:
This code assumes that the client has access to the same IEchoService
interface definition that the service used and that the client knows
what binding and address to use. Then you can create a ChannelFactory
based on the ServiceEndpoint:
ChannelFactory creates typed proxies. In this case, it creates proxies
of type IEchoService, which you can use to invoke the operations defined
by the contract:
using System;
using System.ServiceModel;
using ServiceLibrary;
class Program
{
static void Main(string[] args)
{
ServiceEndpoint httpEndpoint = new ServiceEndpoint(
ContractDescription.GetContract(typeof(IEchoService)),
new BasicHttpBinding(), new EndpointAddress(
"http://localhost:8080/echo/svc");
ChannelFactory<IEchoService> factory = new ChannelFactory<IEchoService>(httpEndpoint);
IEchoService svc = factory.CreateChannel(); Console.WriteLine(svc.Echo("Hello, world"));
If
the service requires a customized binding, you will need to create the
binding and customize it before creating ServiceEndpoint. If you want to
access the TCP endpoint, simply create another ServiceEndpoint
specifying the TCP endpoint details and supply it to ChannelFactory
instead of the HTTP endpoint. Everything else would work the same.
The
proxy shields you from the different addresses and bindings in use,
allowing you to write application code against the service contract. Figure 10 shows the complete client console application.
Figure 10 EchoService Client App
using System;
using System.ServiceModel;
using ServiceLibrary;
class Program
{
static void Main(string[] args)
{
try
{
// define service endpoints on client
ServiceEndpoint httpEndpoint = new ServiceEndpoint(
ContractDescription.GetContract(
typeof(IEchoService)),new BasicHttpBinding(),
new EndpointAddress("http://localhost:8080/echo/svc"));
ServiceEndpoint tcpEndpoint= new ServiceEndpoint(
ContractDescription.GetContract(
typeof(IEchoService)),
new NetTcpBinding(), new EndpointAddress(
"net.tcp://localhost:8081/echo/svc"));
IEchoService svc = null;
// create channel factory based on HTTP endpoint
using (ChannelFactory<IEchoService> httpFactory =
new ChannelFactory<IEchoService>(httpEndpoint))
{
// create channel proxy for endpoint
svc = httpFactory.CreateChannel();
// invoke service operation
Console.WriteLine("Invoking HTTP endpoint: {0}",
svc.Echo("Hello, world"));
}
// create channel factory based on TCP endpoint
using (ChannelFactory<IEchoService> tcpFactory =
new ChannelFactory<IEchoService>(tcpEndpoint))
{
// create channel proxy for endpoint
svc = tcpFactory.CreateChannel();
// invoke service operation
Console.WriteLine("Invoking TCP endpoint: {0}",
svc.Echo("Hello, world"));
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
Configuring Client Endpoints
Hardcoding
endpoint information into client code is also less than ideal. So WCF
provides a similar configuration mechanism for specifying client
endpoints within <system.serviceModel>. The following application
configuration file specifies the same client endpoint details used in
the code:
<configuration>
<system.serviceModel>
<client>
<endpoint name="httpEndpoint"
address="http://localhost:8080/echo/svc"
binding="basicHttpBinding"
contract="ServiceLibrary.IEchoService"/>
<endpoint name="tcpEndpoint"
address="net.tcp://localhost:8081/echo/svc"
binding="netTcpBinding"
contract="ServiceLibrary.IEchoService"/>
</client>
</system.serviceModel>
</configuration>
Notice
that I've given each client endpoint a name. You can use this name in
your code when creating a ChannelFactory, as illustrated in the
following code:
Now when you want to create a ChannelFactory for the TCP endpoint, you
can simply change the configuration name to tcpEndpoint. You can also
configure bindings in the client configuration file just like I did in
the service configuration file.
using (ChannelFactory<IEchoService> httpFactory =
new ChannelFactory<IEchoService>("httpEndpoint"))
{
svc = httpFactory.CreateChannel();
Console.WriteLine("Invoking HTTP endpoint: {0}",
svc.Echo("Hello, world"));
}
If you add this configuration file to the client console application shown in Figure 10,
comment out the code to create the ServiceEndpoint objects, and specify
the endpoint names when constructing each ChannelFactory, the result
will be the same.
Generating Client Proxies
The
preceding examples assumed the client had access to the same interface
definition used to implement the service. This is common when .NET is
used to implement both sides (in traditional .NET remoting scenarios),
but that's not always the case, and it's never the case when the service
wasn't implemented with the .NET Framework. The preceding examples also
assumed the client knew the precise endpoint details in advance, which
isn't always common either.
When
clients don't have access to such information, they can discover it
using the WS-MetadataExchange specification. Services share endpoint
descriptions with clients through WSDL definitions. Most service
platforms provide tools for generating client-side code from WSDL
definitions. The WinFX SDK provides SvcUtil.exe for this purpose.
When
executing SvcUtil.exe, you specify the URL of the WSDL definition and
it will generate the necessary .NET code and configuration elements that
define the endpoints and bindings used by the service endpoints. Figure 11
shows the output of running SvcUtil.exe against
http://localhost:8080/echo. Notice that it generates two files:
EchoService.cs and output.config.
Figure 11 SvcUtil.exe Output
EchoService.cs
contains a .NET interface definition equivalent to the one the service
implemented. Output.config contains the client endpoint information
needed to access the service (similar to what I just wrote manually).
You'll need to merge the contents of output.config with your application
configuration file manually. With this in place, you can write client
code using the same techniques shown in the previous examples, only
using the generated interface and configuration file.
In
general, the WCF client programming model is explicit about the service
boundary. You create a typed channel against a specific service
endpoint and then send messages through it. Although this helps
emphasize the tenets of service orientation, it can be tedious to work
at this level all the time. To help you, SvcUtil.exe also generates a
proxy class that completely hides the ChannelFactory and ServiceEndpoint
creation details. If you open EchoService.cs, you'll find the following
class definition:
public partial class EchoServiceProxy :
System.ServiceModel.ClientBase<IEchoService>, IEchoService
{
public EchoServiceProxy()
{
}
public EchoServiceProxy(string endpointConfigurationName) :
base(endpointConfigurationName)
{
}
...
This
proxy class can simplify your client application code. All you have to
do is instantiate the proxy, specify the name of the endpoint
configuration, and make method calls. Here's an example:
The generated configuration file (output.config) defines the IEchoService and IEchoService1 endpoint configurations.
using (EchoServiceProxy proxy = new EchoServiceProxy("IEchoService"))
{
// invoke service operation
Console.WriteLine("Invoking HTTP endpoint: {0}",
proxy.Echo("Hello, world"));
}
using (EchoServiceProxy proxy = new EchoServiceProxy("IEchoService1"))
{
// invoke service operation
Console.WriteLine("Invoking TCP endpoint: {0}",
proxy.Echo("Hello, world"));
}
Logging Messages
As
you begin working with the WCF programming model, you may find it
useful to trace the SOAP messages traveling between clients and
services. Windows Communication Foundation provides built-in support for
message logging, which can be turned on through your application
configuration file.
Figure 12 SvcTraceViewer.exe
SvcConfigEditor
provides a Diagnostics tab where you can enable the messaging logging
features. Once you enable messaging logging and rerun the app, you'll
see the trace file appear in the specified location. The WinFX SDK also
provides SvcTraceViewer.exe for viewing the information in a trace file
(see Figure 12).
Conclusion
WCF
is a new step in distributed programming for developers using the .NET
Framework. If you currently build systems using any of today's .NET
distributed technologies, it's time to start paying attention to WCF and
the future it holds. It's only a matter of time before all
.NET-targeted code related to communications will be written using WCF.
Reference:
No comments:
Post a Comment