|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
IntroductionI 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 an 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. Tip1 : Debugging WCFOne 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...");
// 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. Tip2 : Adding a Service ReferenceIf we consider how the attached demo app WCF service is configured at the server end. <?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 3 possible end points
and 2 possible host addresses
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 a http binding to allow you to gain access to the service eevn if you don't end up using the http binding. AmendmentOne 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="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. Tip3 : Service ConfigurationIn 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
{
/// <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 generic To add a reference you must ensure that you have access to the running WCF service host (ie 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 generic
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.
Tip4 : Service ParametersWhen 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
Tip5 : Circular ReferencesAs 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 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 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, string name,
string ns, IList<Type> knownTypes)
{
return CreateDataContractSerializer(type, name, ns, knownTypes);
}
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
#region Private Methods
private static XmlObjectSerializer CreateDataContractSerializer(Type type,
string name, string ns, IList<Type> knownTypes)
{
return CreateDataContractSerializer(type, name, ns, knownTypes);
}
#endregion
}
}
The important part is the constructor of the The importance of this class may become clearer if we see an example screen shots from the attached demo where the following applies:
If we just remind ourselves what the service contract looks like /// <summary>
/// The service contract
/// </summary>
[ServiceContract(
Name = "IService",
Namespace = "WcfServiceLibrary1.Service")]
public interface IService
{
[OperationContract]
[ReferencePreservingDataContractFormat]
List<Person> GetPeople();
[OperationContract]
List<Person> GetPeopleWithOutCicularReferencing();
}
So we can see that the 1st call worked just fine, as we used the But the next call failed completely, and resulted in a communication Exception. This is because the standard
I can take no resposibility for creating either the Wrap UpI am fully aware that these tips may not be suitable for everyones 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 CodeYou 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 lines <userPrincipalName value="XXXXXX" /> To be something like <userPrincipalName value="YOUR_PC_NAME\YOUR_USER"/> If you dont change this, the ServiceClientTestApp project will not work. We're DoneIf you liked this article and found it useful please leave a vote/comment.
|
||||||||||||||||||||||