Philip Hendry's Blog

WCF Overview

Tools

Introduction

Windows Communication Foundation is part of the Microsoft .Net Framework V3.0 and is part of Vista and Windows Server 2008. It’s Microsoft’s platform for SOA and unifies ASMX, .NET Remoting and Enterprise Services stacks. Because there’s a single API it makes it easier to build for a multitude of different circumstances.

SOA simply defines a contract for accessing a particular service and makes it available via some means of communication protocol. WCF abstracts away from particular protocols.

WCF is interoperable since it supports core Web services standards and supports integration since it integrates with earlier technologies such as COM, Enterprise Services and MSMQ.

Connectivity

Services can be deployed inside a company and connect to TCP/IP endpoints and authenticate to an Active Directory. However, a more common scenario is to support Internet clients either using a Basic Profile for legacy support where credentials are secured through a HTTPS link (BasicHttpBinding), or using WS* across HTTP (WSHttpBinding) where the message itself is encrypted.

Another option is to deploy into a business partner or for cross-machine communication. In this scenario we may be using a certificate for authentication to a WSHttpBinding.

Basics

A Service must be hosted by a ServiceHost which must specify one or more Endpoints for clients to connect to. A Client must have a Proxy that has an Endpoint that will connect to the service. The service also defines Behaviours that determine, for example, instancing behaviour.

Example

When a ‘WCF Service’ item is added to a project the following are automatically added :

Hosting

Hosting relies on the creation of a ServiceHost, passing the type that implements the service contract, then adding an endpoint to it which declares three things:

WCF Client

For the client to consume the service it must:

Here’s the relationship between these objects in a diagram:

image

Web Service Description Language

A WSDL provides:

The WSDL can also be used using svcutil.exe to generate the proxy code on the client. Or live meta data exchange can be employed using WS-MetadataExchange _and _svcutil.exe used again.

Declarative Configuration

A configuration file can be used to define the WCF endpoints. The following example defines two endpoints. The first is in addition to the programmatic endpoint defined in the code above. These second provides the metadata exchange and defines its address as a relative address which must be combined with a baseAddress to form the full path:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="HelloWorld.HelloWorldService"
               behaviorConfiguration="serviceBehaviour">
        <endpoint binding="basicHttpBinding"
                  contract="HelloWorld.IHelloWorldService"
                  address="http://localhost:8000/HelloWorld" />
        <endpoint binding="mexHttpBinding"
                  contract="IMetadataExchange"
                  address="mex" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8000"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="ServiceBehaviour">
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Contracts

There are three types of contracts:

Service Contracts

Contracts define the operations and parameter types that are defined on a service. Endpoints can only be associated to a single contract. To define a class and operation must be attributed. It is preferable to attribute an interface then it can be implemented multiple times:

[ServiceContract(Namespace="http://www.philiphendry.me.uk/samples/HelloWorld/2008/03")]
public interface IHelloWorldService
{
    [OperationContract]
    string TheMessage(string message);
}

It is a good idea to add the Name parameter to the attributes so that the operation or class name can be changed without affecting the contract. The following example demonstrates this including naming operation parameters and return types:

[ServiceContract(
    Name="HelloWorldService",
    Namespace="http://www.philiphendry.me.uk/samples/HelloWorld/2008/03")]
public interface IHelloWorldService
{
    [OperationContract(
        Name="TheMessage",
        Action = "http://www.philiphendry.me.uk/samples/HelloWorld/2008/03/HelloWorldService/TheMessage",
        ReplyAction = "http://www.philiphendry.me.uk/samples/HelloWorld/2008/03/HelloWorldService/TheMessageResponse")]
    [return: MessageParameter(Name="ReturnString")]
    string TheMessage(
        [MessageParameter(Name="Message")]
        string message);
}

ServiceContract Named Parameters

OperationContract Named Parameters

_IsOneWay=[true false]_ can be specified on the operation to make the method more of a fire-and-forget option similar to MSMQ.

_FaultContract(typeof()]_ can be specified to abstract clients away from the details of how a service is written since stack traces in errors specify internal details. The client errors specified in the contract can then be thrown with the following code:

throw new FaultException<MyClientException>(new MyClientException("Custom error"));

Complex Types

The DataContractSerializer is the default WCF serializer and uses the WSDL to examine the contents of the message and converts between the message XML and .Net CLR data types. It can handle:

SerializableAttribute

Any type with SerializableAttribute will be converted and everything will be serialized including all fields regardless of accessibility. There is no control over naming conventions or data types.

Data Contracts

The DataContractAttribute can be applied to map between a CLR type and the schema. The DataMemberAttribute must be applied to fields or properties to have them opt-in to serialization.

If an operation includes a parameters or return value that is of a complex type but that isn’t serializable then when the service is hosted, the Open() will fail with the error below that indicates that the complex type is not serializable.

image_thumb[3]

For example, the following MessageItem complex type is decorated with the DataContract and DataMember attributes and creates a Nested WSDL which contains the definition. The name in the contract will default to the name of the decorated field or property but can be defined by adding the Name named parameter to the attribute.

[DataContract]
public class MessageItem
{
    [DataMember]
    public string TheMessage { get; private set; }

    public string Category { get; private set; }
}

Known Types

If a complex type being used as a parameter or return value can be one of a number of derived types, then the KnownTypeAttribute can be applied to the base class to define other types that can be used by the client in conversations with the server. This attribute will have the server generate the contract and WSDL with the extra types included:

[DataContract(Namespace="http://www.philiphendry.me.uk/samples/HelloWorld/2008/03")]
[KnownType(typeof(ComplexMessageItem))]
[KnownType(typeof(SimpleMessageItem))]
public class MessageItem
{
    [DataMember]
    public string TheMessage { get; private set; }

    public string Category { get; private set; }
}

[DataContract(Namespace="http://www.philiphendry.me.uk/samples/HelloWorld/2008/03")]
public class ComplexMessageItem : MessageItem
{
}

[DataContract(Namespace="http://www.philiphendry.me.uk/samples/HelloWorld/2008/03")]
public class SimpleMessageItem : MessageItem
{
}

Service Known Type

The ServiceKnownType attribute can be added to the service contract instead of the actual type since adding to the actual type means that every contract would have to allow all the defined types.

[ServiceContract(Namespace="http://www.philiphendry.me.uk/samples/HelloWorld/2008/03")]
[ServiceKnownType(typeof(ComplexMessageItem))]
public interface IHelloWorldService
{
    [OperationContract]
    string TheMessage(MessageItem message);
}

Declared Type

Types can also be added to the the configuration file which allows for implementation configuration changes. This can also be adapted to offer dynamically changing types at runtime if required.

<system.runtime.serialization>
  <dataContractSerializer>
    <declaredTypes>
      <add type="HelloWorld.MessageItem, HelloWorld, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
        <knownType type="HelloWorld.ComplexMessageItem, HelloWorld, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
        <knownType type="HelloWorld.SimpleMessageItem, HelloWorld, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      </add>
    </declaredTypes>
  </dataContractSerializer>
</system.runtime.serialization>

IXmlSerializable

Is the best way to support a given contract from a object that is very different in structure - the mapping can be controlled explicitly.

[XmlSchemaProvider("GetSchema")]
public class MessageItemSerializer : IXmlSerializable
{
    public static XmlQualifiedName GetSchema(XmlSchemaSet schemaSet)
    {
        string schemaString = String.Format("<xs:schema xmlns:tns='{0}' xmlns:xs='http://www.w3.org/2001/XMLSchema' ..... </xs:schema>", ns);

        XmlSchema schema = XmlSchema.Read(new StringReader(schemaString), null);
        schemaSet.XmlResolver = new XmlUrlResolver();
        schemaSet.Add(schema);

        return new XmlQualifiedName("MessageItem", ns);
    }

    public System.Xml.Schema.XmlSchema GetSchema()
    {
        throw new NotImplementedException("Not implemented - the XMmlSchemaProvider attribute specifies" +
            "calling the static GetSchema method instead");
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        MessageItem = new MessageItem();
        while (reader.IsStartElement())
        {
            if (reader.IsStartElement("TheMessage"))
            {
                reader.MoveToContent();
                MessageItem.TheMessage = reader.ReadString();
                reader.MoveToContent();
                reader.ReadEndElement();
            }
        }
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        throw new NotImplementedException();
    }

    public MessageItem MessageItem { get; private set; }
}

Message Contracts

Message contracts are particularly useful for adding custom headers or controlling protection level by only encrypting certain sections, for example. The message itself is sent as a SOAP method and therefore the SOAP body carries the payload to be given to the client whilst the rest of the message is used for containing, describing and transporting the body. In particular it may be that the header is used by routers to transfer messages to particular servers (load balancing for example) and therefore data marked with MessageHeader will be contained in the header where it will be visible.

[MessageContract]
public class CustomClass
{
    [MessageHeader]
    public string operation;

    [MessageBodyMember]
    public string data;
}

Bindings

Bindings describe how messages are transported including:

**Binding** **Interop** **Security** **Session** **Transx** **Duplex** **Stream**
**BasicHttpBinding** BP T
**WsHttpBinding** WS TS y y
**WsDualHttpBinding** WS TS y y y
**NetTcpBinding** TS y y y o
**NetNamePipesBinding** TS y y y o
**NetMsmqBinding** TS y y
**NetPeerTcpBinding** TS y

T = Transport Security, S = WS-Security, O = One-Way only

Bindings are specified in the configuration (although code is possible too) and allow changes without having to recompile code. The following example shows how the endpoint is named Binding1 and then the binding properties are modified in the bindings section:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="HelloWorld.HelloWorldService"
               behaviorConfiguration="serviceBehaviour">
        <endpoint bindingConfiguration="Binding1"
                  binding="basicHttpBinding"
                  contract="HelloWorld.IHelloWorldService"
                  address="http://localhost:8000/HelloWorld" />
      </service>
    </services>
    <bindings>
      <basicHttpBinding>
        <binding name="Binding1" 
                 hostNameComparisonMode="StrongWildcard"
                 sendTimeout="00:10:00"
                 maxReceivedMessageSize="65536"
                 messageEncoding="Text"
                 textEncoding="utf-8" />
      </basicHttpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Behaviours

Behaviours are concerned with system semantics such as:

Instancing

There are a number of instancing behaviours that are available :

This can be specified either in code:

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class HelloWorldService : IHelloWorldService
{
}

Or in config:

<system.serviceModel>
  <services>
    <service name="HelloWorld.HelloWorldService"
             behaviorConfiguration="serviceBehaviour">
        <!-- endpoints -->      
    </service>
  </services>
  <behaviors>
    <serviceBehaviors>
      <behavior name="serviceBehaviour">
        <serviceThrottling maxConcurrentCalls="10"
                           maxConcurrentInstances="10"/>
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>