Windows Communication Foundation In-Depth

Creating the Basic WCF Service and Client

Creating basic Windows Communication Foundation services and clients is surprisingly straightforward because of the amount of underlying functionality supplied by the infrastructure. In this section, a simple service (ProperNamesService) and matching client (ProperNamesClient) will be created to demonstrate the basic steps listed in Steps in Creating a Windows Communication Foundation Solution. Both access Windows Communication Foundation through its service model using typed messages and a class-first approach to development. This service is used to store and access formal names of people, products, processes, and technologies to assure the consistency of organizational documentation.

For the full application code for the examples used in this topic, see the WCF Example Code.

Note

The client and service must be consistent with respect to the endpoint's contract, binding, and address used.

Creating a Windows Communication Foundation Service

Commercial development services should be written in a DLL. Initial prototyping and early development can be hosted in a console app, and later deployed in a .svc service.

The following steps represent a basic approach to creating a WCF service. For this example, both the service and client are coded as console executables:

  1. The development project requires a reference to the System.ServiceModel.dll assembly and System.Runtime.Serialization.dll. Similarly, the code may contain using references to the associated System.ServiceModel and child namespaces.

    using System;
    using System.Collections.Generic;
    using System.ServiceModel;
    using System.Configuration;
    
    namespace ProperNamesService
    {
       class Program
       {
          static void Main(string[] args)
          {
            //instantiate server...
          }
       }
    }
    
  2. The initial design calls for four basic operations: adding a proper name (AddProperName), determining if a given name is protected (IsProperName), get total number of names submitted by a particular person (GetTotalByOwner), and get closest matches to a name (MatchClosestName).

  3. The service contract will be represented by the IProperNamesService interface, containing four methods corresponding to the specified operations. A custom data type, ProperNameRecord, is required to hold information associated with a new entry.

    • Apply the ServiceContractAttribute to the interface that contains the methods that will represent the service operations. By default, the name of the contract type will be the interface name.

      Properties of ServiceContractAttribute determine such service contract characteristics as the WSDL name and namespace, whether sessions are supported, and the format of the message. By default, the name of the service will be the class name.

    • Apply the OperationContractAttribute to each method of this interface that will be exposed as a service operation.

      Properties of OperationContractAttribute determine such operation contract characteristics as asynchronous operation, whether the operation is one-way, whether the operation can be used to initiate a session, and so on.

    • For each type of fault detail that can be returned to the client from this service, apply FaultContractAttribute to either the entire interface or the specific method that can potentially send that fault type. Faults are more fully explained in the "Handling Exceptions and Faults" section below.

    namespace ProperNamesService
    {
       //IProperNamesService contract declaration
       [ServiceContract]
       interface IProperNamesService
       {
          [OperationContract]      [FaultContract(typeof(ProperNameRecord))]
          void AddProperName(String properName, int ownerID);
    
          [OperationContract]
          bool IsProperName(String properName);
    
          [OperationContract]
          int GetTotalByOwner(String ownerName);
    
          [OperationContract]
          ProperNameRecord MatchClosestName(String properName);
       }
    }
    

    Note

    Using this approach, the methods that implement the service operations will always have public access because they represent interface implementations. However, member access and Windows Communication Foundation operation publication are unrelated; any method labeled with OperationContractAttribute will be callable by a Windows Communication Foundation client, regardless of its internal visibility. A class can directly declare and implement a service contract without the need for an interface. In this scenario, a published operation can have non-public access specification.

    • Implement a data contract for the data type: class, structure, or enumeration. Apply the DataContractAttribute to the data type declaration and DataMemberAttribute to each data member that is to be serialized. These two attributes have properties that can modify the serialization process, such as changing the associated names member ordering, and whether the member is required. The use of data contracts requires a reference to the System.Runtime.Serialization namespace and the corresponding system.runtime.serialization.dll assembly.

    The following code is added to IProperNamesService.cs:

    [DataContract]
    public struct ProperNameRecord
    {
        [DataMember]
        String properName;
        [DataMember]
        Int32 ownerID;
        [DataMember]
        DateTime entryDateTime;
    
        public ProperNameRecord(String name, Int32 id, DateTime dt)
        {
            properName = name;
            ownerID = id;
            entryDateTime = dt;
        }
    }
    

    Note

    By default, when you apply the DataContractAttribute to a class, the data contract is identified by the class name and the containing namespace.

  4. Implement the service contract by creating a class that implements the interface created in the previous step. Because of the attributes applied in the previous step, the behavior attributes in this step are not absolutely required, but strongly recommended.

    • Optionally, apply the ServiceBehaviorAttribute to the class that contains the methods that will represent the service operations. Properties of ServiceBehaviorAttribute determine service behavioral characteristics (for example, whether to support concurrent transactions, object recycling, or transaction isolation level).

    • Optionally, apply the OperationBehaviorAttribute to each method in this class that will be exposed as a service operation. Properties of OperationBehaviorAttribute determine operation characteristics (for example, how the operation interacts with existing transactions, and whether to use security impersonation).

  5. A simple service implementation (as in this example) is usually contained within a single file (for example, ProperNamesService.cs). For a full listing of the example code, see WCF Example.

    Add the appropriate procedural code to create and run the service in its host. Although the details of this step differ depending on the choice of the host and how a configuration file is used, for this console application host it consists of instantiating a ServiceHost object that is initialized with values from the associated configuration file. The new project file Program.cs is used for this purpose:

    using System;
    using System.ServiceModel;
    using System.Configuration;
    
    namespace NamingServices
    {
       class Program
       {
          static void Main(string[] args)
          {
             Uri baseAddress = new Uri            (ConfigurationManager.AppSettings["baseAddress"]);
             using (ServiceHost sh = new ServiceHost             (typeof(ProperNamesService), baseAddress))
             {
                sh.Open();
                Console.WriteLine("The Proper Names Service is                         running... [Press Enter to close]");
                Console.ReadLine();
                Console.WriteLine("Closing service...");
                sh.Close();
             }
          }
       }
    }
    
  6. Create a configuration file to allow for easy modification of settings. Because this example uses a console app, the configuration file will be named App.config. This file is commonly created by ether modifying an existing configuration file or by using the Configuration Editor Tool (SvcConfigEditor.exe). The excerpt of the configuration file for the example service shown below has two main sections: an <appSettings> section that stores the URI (and perhaps other application-specific settings), and a <serviceModel> section that has values specific to WCF as detailed in "Windows Communication Foundation Configuration Schema" of the Windows SDK.

    <configuration>
      <appSettings>
        <add key="baseAddress"          value="https://localhost:8000/ProperNamesService/" />
       </appSettings>
    
      <system.serviceModel>
        <services>
          <service 
             name="NamingServices.ProperNamesService" 
             behaviorConfiguration="ProperNameBehavior">
             <endpoint address=""
                    binding="basicHttpBinding"
                    bindingConfiguration="Binding1" 
                    contract="NamingServices.IProperNamesService" />
          </service>
        </services>
        <bindings>
          <basicHttpBinding>
             <binding name="Binding1" />
             </basicHttpBinding>
          </bindings>
      </system.serviceModel>
    </configuration> 
    
  7. Build, test and deploy the service.

Creating a Windows Communication Foundation Client

To create a WCF client:

  1. Choose a hosting scheme for the client. A console application project is again chosen for its simplicity.

    using System;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    
    namespace ProperNamesClient
    {
        class Program
        {
            static void Main(string[] args)
           {
                Console.WriteLine("Starting client...");
                // Create a proxy and invoke oeprations... 
           }
       }
    }
    
  2. Generate the proxy class, contract interface, and configuration file for the client by running the Service Model Metadata Utility Tool (SvcUtil.exe). In this example, the following command line was run to generate the files output.config and output.cs:

    SvcUtil https://localhost:8000/ProperNamesService 
    

    In this example, the files were renamed to ProperNamesClient.cs and App.config, respectively, then added to the project. The former defines the following programming constructs:

    • The structure ProperNameRecord which represents the data contract.

    • The interface IProperNamesService which represents the service contract.

    • The class ProperNamesServiceProxy which implements the service contract and acts as a local proxy for the service.

    • The interface IProperNamesServiceChannel which represents the channel used to communicate to the service. This type is typically less used by the service model programmer.

    SvcUtil has many options which are documented in the Windows SDK. For example, the /async switch is used to generate asynchronous begin/end versions of the proxy and contract classes.

Important

If the service is modified in a way that substantially affects the address, binding or contract of its endpoints, then all clients must be updated by either regenerating these two files or updating their contents.

Warning

It is considered poor practice to share the interface (which represents the service contract created for the server in step 3 above) with the client for several reasons. Chief among these reasons is that it leads to tightly coupling the code base of the client to the service, and the interface will not capture the endpoint characteristics specified in the service's configuration filed.

  1. Access the Windows Communication Foundation service operations where appropriate by calling the methods of the proxy object. From a syntax standpoint, this invocation is identical to a local method call. From a reliability standpoint, all operation calls should be contained within exception-handling and using(…) blocks because failures, for example timeouts, are much more likely to occur.

    using (ProperNamesServiceProxy proxy = new         ProperNamesServiceProxy())
    {
       try
       {
          String bd = "Bob Dobbs";
          //Use the proxy to call service operations.
          proxy.AddProperName(bd, 1234);
          if (proxy.IsProperName("bob dobbs "))
             Console.WriteLine(bd + " was registered properly.");
          Console.WriteLine("Employee 1234 has registered " +
               proxy.GetTotalByOwner(1234) + " proper names.");
       }
       catch (Exception e)
       {
          Console.WriteLine("Exception thrown: " + e.Message);
       }
    }
    
  2. If required, create and customize a configuration file for the client. For more information, see "Configuration Files" in this document.

  3. Build, test, and deploy the client. If the corresponding service is running, the client should be able to invoke service operations transparently.

    The output of the client excerpt shown above would be:

    Starting client...
    Bob Dobbs was registered properly.
    Employee 1234 has registered 1 proper name.
    

Handling Exceptions and Faults

Clients and services should trap and handle all exceptions locally whenever possible. However, when a service operation fails because of irreconcilable logical inconsistency, this state needs to be propagated back to the client in the form of a SOAP fault message.

Note

Some failures should not be communicated back to the client (for example, security issues). In general, the service implementation gets to decide whether it wants to send a fault to the client. By default, unhandled .NET exceptions are not sent back to the client.

Typically, fault handling is accomplished in three steps:

  1. In the service contract, associate operations with the specific faults that they can report by annotating with FaultContractAttribute. For example, the following indicates that the AddProperName operation can report an out-of-memory fault:

    [OperationContract]
    [FaultContract(typeof(ProperNameRecord))]
    void AddProperName(String properName, int ownerID);
    
  2. In the service, at the place where the corresponding exceptional condition is determined, create a corresponding WCF fault exception (FaultException or derived type) and throw it. If these exceptions are not handled locally, the Windows Communication Foundation infrastructure automatically processes fault exceptions as SOAP fault messages and sends them to the client.

    if (ownerID < 0)
       throw new FaultException<ProperNameRecord>(pnr,           "Invalid owner ID");
    if (IsProperName(properName))
       throw new FaultException<ProperNameRecord>(pnr,           "Duplicate Name");
    
  3. At the invoking client, the SOAP fault message is automatically converted to its corresponding FaultException. The client can catch these specific exceptions as it deems necessary. An uncaught FaultException, like any other, will cause the client to terminate.

    try
    {
       //...
       proxy.AddProperName(bd, 1234);
       //...
    }
    catch (FaultException<NamingServices.ProperNameRecord> e)
    {
       Console.WriteLine("Name registration problem: " + e.Message);
    }
    catch (FaultException e)
    {
       Console.WriteLine("General FaultException: " +          e.GetType().Name + " - " + e.Message);
    }
    catch (Exception e)
    {
       Console.WriteLine("Exception thrown: " + e.Message);
    }