Tuesday 19 July 2011

Windows Communication Foundation Architecture Overview


Contents

Introduction
WCF Fundamentals
Code Examples
Summary

Introduction

This document provides a high-level view of the Windows Communication Foundation (WCF) architecture. It is intended to explain key concepts in WCF and how they fit together. There are a few code examples to further illustrate the concepts, but code is not the emphasis of this document.
The rest of this document is organized in two main sections:
  • WCF Fundamentals: Covers key concepts in WCF, terms, and architectural components.
Code Examples: Provides a few short code examples intended to illustrate and reify the concepts covered in WCF Fundamentals.


WCF Fundamentals

A WCF Service is a program that exposes a collection of Endpoints. Each Endpoint is a portal for communicating with the world.
A Client is a program that exchanges messages with one or more Endpoints. A Client may also expose an Endpoint to receive Messages from a Service in a duplex message exchange pattern.
The following sections describe these fundamentals in more detail.

Endpoints

A Service Endpoint has an Address, a Binding, and a Contract.
The Endpoint's Address is a network address where the Endpoint resides. The EndpointAddress class represents a WCF Endpoint Address.
The Endpoint's Binding specifies how the Endpoint communicates with the world including things like transport protocol (e.g., TCP, HTTP), encoding (e.g., text, binary), and security requirements (e.g., SSL, SOAP message security). The Binding class represents a WCF Binding.
The Endpoint's Contract specifies what the Endpoint communicates and is essentially a collection of messages organized in operations that have basic Message Exchange Patterns (MEPs) such as one-way, duplex, and request/reply. The ContractDescription class represents a WCF Contract.
The ServiceEndpoint class represents an Endpoint and has an EndpointAddress, a Binding, and a ContractDescription corresponding to the Endpoint's Address, Binding, and Contract, respectively



EndpointAddress

An EndpointAddress is basically a URI, an Identity, and a collection of optional headers as shown in Figure 2.
An Endpoint's security identity is normally its URI; however, in advanced scenarios the identity can be explicitly set independent of the URI using the Identity address property.
The optional headers are used to provide additional addressing information beyond the Endpoint's URI. For example, address headers are useful for differentiating between multiple Endpoints that share the same address URI.


Bindings

A Binding has a name, a namespace, and a collection of composable binding elements (Figure 3). The Binding's name and namespace uniquely identify it in the service's metadata. Each binding element describes an aspect of how the Endpoint communicates with the world.

For example, Figure 4 shows a binding element collection containing three binding elements. The presence of each binding element describes part of the how of communicating with the Endpoint. The TcpTransportBindingElement indicates that the Endpoint will communicate with the world using TCP as the transport protocol. ReliableSessionBindingElement indicates that the Endpoint uses reliable messaging to provide message delivery assurances. SecurityBindingElement indicates that the Endpoint uses SOAP message security. Each binding element usually has properties that further describe the specifics of the how of communicating with the Endpoint. For example, the ReliableSessionBindingElement has an Assurances property that specifies the required message delivery assurances, such as none, at least once, at most once, or exactly once.

The order and types of binding elements in Bindings are significant: The collection of binding elements is used to build a communications stack ordered according to the order of binding elements in the binding elements collection. The last binding element to be added to the collection corresponds to the bottom component of the communications stack, while the first one corresponds to the top component. Incoming messages flow through the stack from the bottom upwards, while outgoing messages flow from the top downwards. Therefore the order of binding elements in the collection directly affects the order in which communications stack components process messages. Note that WCF provides a set of pre-defined bindings that can be used in the majority of scenarios instead of defining custom bindings.

Contracts

A WCF Contract is a collection of Operations that specifies what the Endpoint communicates to the outside world. Each operation is a simple message exchange, for example one-way or request/reply message exchange.
The ContractDescription class is used to describe WCF Contracts and their operations. Within a ContractDescription, each Contract operation has a corresponding OperationDescription that describes aspects of the operation such as whether the operation is one-way or request/reply. Each OperationDescription also describes the messages that make up the operation using a collection of MessageDescriptions.

A ContractDescription is usually created from an interface or class that defines the Contract using the WCF programming model. This type is annotated with ServiceContractAttribute and its methods that correspond to Endpoint Operations are annotated with OperationContractAttribute. You can also build a ContractDescription by hand without starting with a CLR type annotated with attributes.
A duplex Contract defines two logical sets of operations: A set that the Service exposes for the Client to call and a set that the Client exposes for the Service to call. The programming model for defining a duplex Contract is to split each set in a separate type (each type must be a class or an interface) and annotate the contract that represents the service's operations with ServiceContractAttribute, referencing the contract that defines the client (or callback) operations. In addition, ContractDescription contains a reference to each of the types thereby grouping them into one duplex Contract.
Similar to Bindings, each Contract has a Name and Namespace that uniquely identify it in the Service's metadata.
Each Contract also has a collection of ContractBehaviors that are modules that modify or extend the contract's behavior. The next section covers behaviors in more detail.

Behaviors

Behaviors are types that modify or extend Service or Client functionality. For example, the metadata behavior that ServiceMetadataBehavior implemented controls whether the Service publishes metadata. Similarly, the security behavior controls impersonation and authorization, while the transactions behavior controls enlisting in and auto-completing transactions.
Behaviors also participate in the process of building the channel and can modify that channel based on user-specified settings and/or other aspects of the Service or Channel.
A Service Behavior is a type that implements IServiceBehavior and applies to Services. Similarly, a Channel Behavior is a type that implements IChannelBehavior and applies to Client Channels.

Service and Channel Descriptions

The ServiceDescription class is an in-memory structure that describes a WCF Service including the Endpoints exposed by the Service, the Behaviors applied to the Service, and the type (a class) that implements the Service (see Figure 6). ServiceDescription is used to create metadata, code/config, and channels.
You can build this ServiceDescription object by hand. You can also create it from a type annotated with certain WCF attributes, which is the more common scenario. The code for this type can be written by hand or generated from a WSDL document using a WCF tool called svcutil.exe.
Although ServiceDescription objects can be created and populated explicitly, they are often created behind the scenes as part of running the Service.

Similarly on the client side, a ChannelDescription describes a WCF Client's Channel to a specific Endpoint (Figure 7). The ChannelDescription class has a collection of IchannelBehaviors, which are Behaviors applied to the Channel. It also has a ServiceEndpoint that describes the Endpoint with which the Channel will communicate.
Note that, unlike ServiceDescription, ChannelDescription contains only one ServiceEndpoint that represents the target Endpoint with which the Channel will communicate.

WCF Runtime

The WCF Runtime is the set of objects responsible for sending and receiving messages. For example, things like formatting messages, applying security, and transmitting and receiving messages using various transport protocols, as well as dispatching received messages to the appropriate operation, all fall within the WCF runtime. The following sections explain the key concepts of the WCF runtime.

Message

The WCF Message is the unit of data exchange between a Client and an Endpoint. A Message is essentially an in-memory representation of a SOAP message InfoSet. Note that Message is not tied to text XML. Rather, depending on which encoding mechanism is used, a Message can be serialized using the WCF binary format, text XML, or any other custom format.

Channels

Channels are the core abstraction for sending Messages to and receiving Messages from an Endpoint. Broadly speaking, there are two categories of Channels: Transport Channels handle sending or receiving opaque octet streams using some form of transport protocol such as TCP, UDP, or MSMQ. Protocol Channels, on the other hand, implement a SOAP-based protocol by processing and possibly modifying messages. For example, the security Channel adds and processes SOAP message headers and may modify the body of the message by encrypting it. Channels are composable such that a Channel may be layered on top of another Channel that is in turn layered on top of a third Channel.

EndpointListener

An EndpointListener is the runtime equivalent of a ServiceEndpoint. The EndpointAddress, Contract, and Binding of ServiceEndpoint (representing where, what and how), correspond to the EndpointListener's listening address, message filtering and dispatch, and channel stack, respectively. The EndpointListener contains the Channel stack that is responsible for sending and receiving messages.

ServiceHost and ChannelFactory

The WCF Service runtime is usually created behind the scenes by calling ServiceHost.Open. ServiceHost (Figure 6) drives the creation of a ServiceDescription from on the Service type and populates the ServiceDescription's ServiceEndpoint collection with Endpoints defined in config or code, or both. ServiceHost then uses the ServiceDescription to create the channel stack in the form of an EndpointListener object for each ServiceEndpoint in the ServiceDescription.

Similarly, on the client side, the Client runtime is created by a ChannelFactory, which is the Client's equivalent of ServiceHost.
ChannelFactory drives the creation of a ChannelDescription based on a Contract type, a Binding, and an EndpointAddress. It then uses this ChannelDescription to create the Client's channel stack.
Unlike the Service runtime, the Client runtime does not contain EndpointListeners because a Client always initiates connection to the Service, so there is no need to "listen" for incoming connections.

Code Examples

This section provides code examples that show how Services and Clients are built. These examples are intended to reify the above concepts and not to teach WCF programming.

Defining and Implementing a Contract

As mentioned above, the easiest way to define a contract is creating an interface or a class and annotating it with ServiceContractAttribute, allowing the system to easily create from it a ContractDescription.
When using interfaces or classes to define contracts, each interface or class method that is a member of the contract must be annotated with OperationContractAttribute. For example:
using System.ServiceModel;
 
//a WCF contract defined using an interface
[ServiceContract]
public interface IMath
{
    [OperationContract]
    int Add(int x, int y);
}
Implementing the contract in this case is simply a matter of creating a class that implements IMath. That class becomes the WCF Service class. For example:
//the service class implements the interface
public class MathService : IMath
{
    public int Add(int x, int y)
    { return x + y; }
}

Defining Endpoints and Starting the Service

Endpoints can be defined in code or in config. In the example below, the DefineEndpointImperatively method shows the easiest way to define Endpoints in code and start the service.
DefineEndpointInConfig method shows the equivalent endpoint defined in config (config example follows the code below).
public class WCFServiceApp
{
    public void DefineEndpointImperatively()
    {
        //create a service host for MathService
        ServiceHost sh = new ServiceHost(typeof(MathService));
 
        //use the AddEndpoint helper method to
        //create the ServiceEndpoint and add it 
        //to the ServiceDescription
        sh.AddServiceEndpoint(
          typeof(IMath), //contract type
          new WSHttpBinding(), //one of the built-in bindings
          "http://localhost/MathService/Ep1"); //the endpoint's address
 
        //create and open the service runtime
        sh.Open();
 
    }
 
    public void DefineEndpointInConfig()
    {
        //create a service host for MathService
        ServiceHost sh = new ServiceHost (typeof(MathService));
 
        //create and open the service runtime
        sh.Open();
 
    }
}
<!-- configuration file used by above code -->
<configuration 
   xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
   <system.serviceModel>
      <services>
                 <!-- service element references the service type -->
         <service type="MathService">
            <!-- endpoint element defines the ABC's of the endpoint -->
              <endpoint 
            address="http://localhost/MathService/Ep1"
            binding="wsHttpBinding"
              contract="IMath"/>
         </service>
      </services>
   </system.serviceModel>
</configuration>

Sending Messages to an Endpoint

The code below shows two ways to send a message to the IMath endpoint. SendMessageToEndpoint hides the Channel creation, which happens behind the scenes while the SendMessageToEndpointUsingChannel example does it explicitly.
The first example in SendMessageToEndpoint uses a tool named svcutil.exe and the Service's metadata to generate a Contract (IMath in this example), a proxy class (MathProxy in this example) that implements the Contract, and associated config (not shown here). Again, the Contract defined by IMath specifies the what (i.e., the operations that can be performed), while the generated config contains a Binding (the how) and an address (the where).
Using this proxy class is simply a matter of instantiating it and calling the Add method. Behind the scenes, the proxy class will create a Channel and use that to communicate with the Endpoint.
The second example in SendMessageToEndpointsUsingChannel below shows communicating with an Endpoint using ChannelFactory directly. In this example, instead of using a proxy class and config, a Channel is created directly using ChannelFactory<IMath>.CreateChannel. Also, instead of using config to define the Endpoint's address and Binding, the ChannelFactory<IMath> constructor takes those two pieces of information as parameters. The third piece of information required to define an Endpoint, namely the Contract, is passed in as the type T.
using System.ServiceModel;
//this contract is generated by svcutil.exe
//from the service's metadata
public interface IMath
{
    [OperationContract]
    public int Add(int x, int y)
    { return x + y; }
}
 
 
//this class is generated by svcutil.exe
//from the service's metadata
//generated config is not shown here
public class MathProxy : IMath
{
    ...
}
 
public class WCFClientApp
{
    public void SendMessageToEndpoint()
    {
        //this uses a proxy class that was
        //created by svcutil.exe from the service's metadata
        MathProxy proxy = new MathProxy();
 
        int result = proxy.Add(35, 7);
    }
    public void SendMessageToEndpointUsingChannel()
    {
        //this uses ChannelFactory to create the channel
        //you must specify the address, the binding and 
        //the contract type (IMath)
        ChannelFactory<IMath> factory=new ChannelFactory<IMath>(
            new WSHttpBinding(),
            new EndpointAddress("http://localhost/MathService/Ep1"));
        IMath channel=factory.CreateChannel();
        int result=channel.Add(35,7);
        factory.Close();
 
    }
}

Defining a Custom Behavior

Defining a custom Behavior is a matter of implementing IServiceBehavior (or IChannelBehavior for client-side behaviors). The code below shows an example behavior that implements IServiceBehavior. In IServiceBehavior.ApplyBehavior, the code inspects the ServiceDescription and writes out the Address, Binding, and Contract of each ServiceEndpoint, as well as the name of each Behavior in the ServiceDescription.
This particular behavior is also an attribute (inherits from System.Attribute), making it possible to apply declaratively as will be shown below. However, behaviors are not required to be attributes.
[AttributeUsageAttribute(
         AttributeTargets.Class,
         AllowMultiple=false,
         Inherited=false)]
public class InspectorBehavior : System.Attribute, 
                                 System.ServiceModel.IServiceBehavior
{
   public void ApplyBehavior(
       ServiceDescription description,
       Collection<DispatchBehavior> behaviors)
   {
       Console.WriteLine("-------- Endpoints ---------");
       foreach (ServiceEndpoint endpoint in description.Endpoints)
       {
           Console.WriteLine("--> Endpoint");
           Console.WriteLine("Endpoint Address: {0}", 
                             endpoint.Address);
           Console.WriteLine("Endpoint Binding: {0}", 
                             endpoint.Binding.GetType().Name);
           Console.WriteLine("Endpoint Contract: {0}", 
                              endpoint.Contract.ContractType.Name);
           Console.WriteLine();
       }
       Console.WriteLine("-------- Service Behaviors --------");
       foreach (IServiceBehavior behavior in description.Behaviors)
       {
           Console.WriteLine("--> Behavior");
           Console.WriteLine("Behavior: {0}", behavior.GetType().Name);
           Console.WriteLine();
       }
   }
}

Applying a Custom Behavior

All behaviors can be applied imperatively by adding an instance of the behavior to the ServiceDescription (or the ChannelDescription on the client side). For example, to apply the InspectorBehavior imperatively you would write:
ServiceHost sh = new ServiceHost(typeof(MathService));
sh.AddServiceEndpoint(
       typeof(IMath), 
        new WSHttpBinding(),
       "http://localhost/MathService/Ep1"); 
//Add the behavior imperatively
InspectorBehavior behavior = new InspectorBehavior();
sh.Description.Behaviors.Add(behavior);
sh.Open();
Additionally, behaviors that inherit from System.Attribute may be applied declaratively to the service. For example, because InspectorBehavior inherits from System.Attribute, it can be applied declaratively like this:
[InspectorBehavior]
public class MathService : IMath
{
   public int Add(int x, int y)
   { return x + y; }
}

Summary

WCF Services expose a collection of Endpoints where each Endpoint is a portal for communicating with the world. Each Endpoint has an Address, a Binding, and a Contract (ABC). The Address is where the Endpoint resides, the Binding is how the Endpoint communicates, and the Contract is what the Endpoint communicates.
On the Service, a ServiceDescription holds the collection of ServiceEndpoints each describing an Endpoint that the Service exposes. From this description, ServiceHost creates a runtime that contains an EndpointListener for each ServiceEndpoint in the ServiceDescription. The Endpoint's address, Binding, and Contract (representing the where, what, and how) correspond to the EndpointListener's listening address, message filtering and dispatch, and channel stack, respectively.
Similarly, on the Client, a ChannelDescription holds the one ServiceEndpoint with which the Client communicates. From this ChannelDescription, ChannelFactory creates the channel stack that can communicate with the Service's Endpoint.

No comments:

Post a Comment