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:
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...");
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:
="1.0"="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:
So this failed where I tried to use "tcp" host address. But if I used the "http" host address, all is cool.
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:
<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:
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
{
[ServiceContract(
Name = "IService",
Namespace = "WcfServiceLibrary1.Service")]
public interface IService
{
[OperationContract]
[ReferencePreservingDataContractFormat]
List<Person> GetPeople();
[OperationContract]
List<Person> GetPeopleWithOutCicularReferencing();
}
[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.
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.
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:
So I changed some of these parameters to improve the service throughput:
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
.
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
{
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:
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
{
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 ,
false,
true,
null);
}
#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:
[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.
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:
<userPrincipalName value="XXXXXX" />
to be something like:
<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.