Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

WCF: A few tips

4.61/5 (66 votes)
2 Dec 2010CPOL6 min read 1   702  
A few tips when working with WCF.

Introduction

I am by no means a WCF expert, but I have been using it a bit at work of late, and I have come across a few issues that I thought may be worth mentioning in as a top-tips style article.

So that is what this article will be.

This article assumes you know a little bit about WCF, and that you know enough to edit config files.

Please make sure to read the "Running the code" section before you try to run it.

Tip 1: Debugging WCF

One of the first things that you will most likely want to do is call a WCF Service and debug it. Now when I first started creating WCF Services at my company, I created IIS web hosted services. While this may be a great end solution, it doesn't really offer the opportunity to be debugged that easily. So my advice would be to create a Console based implementation of your WCF Service and use this for debugging purposes, and then when you are happy, you can push the tested code back into an IIS web based service.

The attached demo app uses a Console hosted (self hosted) WCF Service, which is hosted as follows:

C#
using System;
using System.ServiceModel;

namespace WcfServiceLibrary1
{
    class Program
    {
        static void Main(string[] args)
        {

            Console.WriteLine("----------------------------" + 
                    "-----------------------------------------");
            Console.WriteLine("This is the WCF Service " + 
                    "hosting within a console application");
            Console.WriteLine("The service can also be hosted with a web browser");
            Console.WriteLine("");
            Console.WriteLine("Initializing WCF Service...");

            // The service configuration is loaded from app.config
            using (ServiceHost host = new ServiceHost(typeof(Service)))
            {
                host.Open();

                Console.WriteLine("WCF Service is ready for requests." +  
                "Press any key to close the service.");
                Console.WriteLine();
                Console.Read();

                Console.WriteLine("Closing service...");
            }
        }
    }
}

So this is enough to host the WCF Service.

Tip 2: Adding a Service Reference

If we consider how the attached demo app WCF Service is configured at the server end:

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="InitialTransactionValue" value="12" />
  </appSettings>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="MetadataBehavior">
          <serviceMetadata httpGetEnabled="true" 
                           httpGetUrl="http://localhost:8001/ServiceHost/mex" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service behaviorConfiguration="MetadataBehavior" 
               name="WcfServiceLibrary1.Service">
        <endpoint
          address="service"
          binding="netTcpBinding"
          contract="WcfServiceLibrary1.IService"
          name="TcpBinding" />
        <endpoint
          address="service"
          binding="wsDualHttpBinding"
          contract="WcfServiceLibrary1.IService"
          name="HttpBinding" />
        <endpoint
          address="mex"
          binding="mexHttpBinding"
          contract="IMetadataExchange"
          name="MexBinding"/>
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:8000/ServiceHost/" />
            <add baseAddress="http://localhost:8001/ServiceHost/" />
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>

It can be seen that there are three possible end points:

  • netTcpBinding
  • HttpBinding
  • MexBinding

and two possible host addresses:

  • net.tcp://localhost:8000/ServiceHost/
  • http://localhost:8001/ServiceHost/

My personal experience was that, I could never find the running service if I tried to add a service reference using the "tcp" host address. I always had to use the "http" host address to find the running service. This is shown below:

Image 1

So this failed where I tried to use "tcp" host address. But if I used the "http" host address, all is cool.

Image 2

So I would recommend always creating an HTTP binding to allow you to gain access to the service even if you don't end up using the HTTP binding.

Amendment

One of the readers of this article, Benoît Dion, suggested that I could fix this with altering the mexBinding end point in the service end.

So this is what I did with this small App.Config change at the service end:

XML
<!--<endpoint
  address="mex"
  binding="mexHttpBinding"
  contract="IMetadataExchange"
  name="MexBinding"/>-->
<endpoint 
  address="mex" 
  binding="mexTcpBinding" 
  contract="IMetadataExchange" />

And I am pleased to inform that this now allows you to add the service reference using the TCP address: net.tcp://localhost:8000/ServiceHost/.

So thanks Benoît.

Tip 3: Service Configuration

In the attached demo app, the WCF Service definition looks like the following:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Net.Security;

namespace WcfServiceLibrary1
{
    /// <summary>
    /// The service contract
    /// </summary>
    [ServiceContract(
    Name = "IService",
    Namespace = "WcfServiceLibrary1.Service")]
    public interface IService
    {
        [OperationContract]
        [ReferencePreservingDataContractFormat] 
        List<Person> GetPeople();

        [OperationContract]
        List<Person> GetPeopleWithOutCicularReferencing();
    }

    //A simple DataContract serializable class
    [DataContract]
    public class Person
    {
        int age = 0;
        string name = string.Empty;
        List<Person> children = new List<Person>();
        Person parent = null;

        [DataMember]
        public int Age
        {
            get { return age; }
            set { age = value; }
        }

        [DataMember]
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        [DataMember]
        public List<Person> Children
        {
            get { return children; }
            set { children = value; }
        }

        [DataMember]
        public Person Parent
        {
            get { return parent; }
            set { parent = value; }
        }
    }
}

It can be seen that the service contract operations (methods) return a generic List<T>, which is a personal choice. But what you must make sure is that the proxy (client) for the WCF Service actually returns the same sort of objects. This is achieved by configuring the WCF Service reference when you add it.

To add a reference, you must ensure that you have access to the running WCF Service host (i.e., the actual service); in the attached demo, this is the Console app.

So, providing you have access to the actual WCF Service, you should ensure that you configure the service to use the same types as the operation contracts that were defined within the service contract interface. Which in the case shown above was the generic List<T>, so we can change this in the dropdown combo boxes provided within the Data Type section of the Service Reference Settings dialog.

Image 3

If you do not have a Service Reference and are in the process of adding it, this Service Reference Settings dialog is accessable from the Advanced button of the Add Service Reference dialog.

Image 4

Tip 4: Service Parameters

When the WCF Service was eventually added, it was found that some of the default parameters just were not big enough. Shown below is the before and after App.Config for the client:

Image 5

So I changed some of these parameters to improve the service throughput:

Image 6

Tip 5: Circular References

As part of something we were doing, I needed to have a circular reference; you know when object A holds a reference to object B, and object B holds a reference to object A.

What was found was that the default DataContractSerializer couldn't be to set to deal with serializing a cyclic object graph in a config file, even though it is totally capable of doing this task. It appeared that some of the properties of the DataContractSerializer can only be set via a constructor call, not via config files. So I did a bit of research into this and found some interesting code that allowed you to create a specialized DataContractSerializer that would allow cyclic object graphs to be serialized. The code that I found allows to adorn your service interface operation contracts with a custom attribute called ReferencePreservingDataContractFormatAttribute.

C#
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Runtime.Serialization;
using System.ServiceModel.Description;

namespace WcfServiceLibrary1
{
    /// <summary>
    /// Allows us to adorn the IService contract with this
    /// attribute to indicate that a specialized DataContractSerializer
    /// should be used that has preserveObjectReferences set true
    /// </summary>
    public class ReferencePreservingDataContractFormatAttribute 
        : Attribute, IOperationBehavior
    {
        #region IOperationBehavior Members
        public void AddBindingParameters(OperationDescription description,
            BindingParameterCollection parameters)
        {
        }

        public void ApplyClientBehavior(OperationDescription description,
            System.ServiceModel.Dispatcher.ClientOperation proxy)
        {
            IOperationBehavior innerBehavior =
              new ReferencePreservingDataContractSerializerOperationBehavior(description);
            innerBehavior.ApplyClientBehavior(description, proxy);
        }

        public void ApplyDispatchBehavior(OperationDescription description,
            System.ServiceModel.Dispatcher.DispatchOperation dispatch)
        {
            IOperationBehavior innerBehavior =
              new ReferencePreservingDataContractSerializerOperationBehavior(description);
            innerBehavior.ApplyDispatchBehavior(description, dispatch);
        }

        public void Validate(OperationDescription description)
        {
        }

        #endregion
    }
}

Which in turns create a new ReferencePreservingDataContractSerializerOperationBehavior object that inherits from IOperationBehavior. It is this class that is responsible for creating the actual DataContractSerializer that allows serializing a cyclic object graph. The code for this class is as follows:

C#
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Runtime.Serialization;
using System.ServiceModel.Description;

namespace WcfServiceLibrary1
{
    /// <summary>
    /// A specialized DataContractSerializer that has 
    /// preserveObjectReferences set true, which allows for
    /// circular references to be serialized
    /// </summary>
    public class ReferencePreservingDataContractSerializerOperationBehavior :
        DataContractSerializerOperationBehavior
    {
        #region Ctor
        public ReferencePreservingDataContractSerializerOperationBehavior(
            OperationDescription operationDescription)
            : base(operationDescription) { }
        #endregion

        #region Public Methods

        public override XmlObjectSerializer CreateSerializer(Type type,
               XmlDictionaryString name, XmlDictionaryString ns, 
               IList<Type> knownTypes)
        {
            return new DataContractSerializer(type, name, ns, knownTypes,
                2147483646 /*maxItemsInObjectGraph*/,
                false/*ignoreExtensionDataObject*/,
                true/*preserveObjectReferences*/,
                null/*dataContractSurrogate*/);
        }
        #endregion
    }
}

The important part is the constructor of the DataContractSerializer where we set the preserveObjectReferences value. This is the all important part.

The importance of this class may become clearer if we see an example screenshot from the attached demo where the following applies:

  • The first WCF call uses ReferencePreservingDataContractFormatAttribute, so is safe to return circular references.
  • The second WCF call uses the standard DataContractSerializer, so causes a communication Exception.

If we just remind ourselves what the service contract looks like:

C#
/// <summary>
/// The service contract
/// </summary>
[ServiceContract(
Name = "IService",
Namespace = "WcfServiceLibrary1.Service")]
public interface IService
{
    [OperationContract]
    [ReferencePreservingDataContractFormat] 
    List<Person> GetPeople();

    [OperationContract]
    List<Person> GetPeopleWithOutCicularReferencing();

}

We can see that the first call worked just fine, as we used ReferencePreservingDataContractFormatAttribute, so is safe to return circular references.

But the next call failed completely, and resulted in a communication Exception. This is because the standard DataContractSerializer doesn't have the preserveObjectReferences parameter turned on.

Image 7

I can take no resposibility for creating either the ReferencePreservingDataContractFormatAttribute or the ReferencePreservingDataContractSerializerOperationBehavior code. This is down to Sowmy Srinivasan, whose blog I found it on. I totally get what it's about though, as I hope you do now. You can read more at Sowmy Srinivasan's blog.

Wrap Up

I am fully aware that these tips may not be suitable for everyone's WCF Services, but they are things that I found helped my own code; as such, I thought it may be worth sharing what I found. I hope at least one of these tips is useful to you.

Running the Code

You will need to change the App.Config in the ServiceClientTestApp project to use your own PC user. So you will need to change the following line:

XML
<userPrincipalName value="XXXXXX" />

to be something like:

XML
<userPrincipalName value="YOUR_PC_NAME\YOUR_USER"/>

If you don't change this, the ServiceClientTestApp project will not work.

We're Done

If you liked this article and found it useful, please leave a vote/comment.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)