Introduction
Microsoft WCF Framework (System.ServiceModel
) provides several extension points from where the developer can extend the framework when it is required. In this Part I article, I'm going to cover two perspectives of extending WCF framework:
- Let's think about a WPF desktop application or a WPF browser application or a windows desktop application or a .NET web application, built on service oriented architecture and all the back end services are WCF services. Now for several reasons, you may need to change the server for your service which in turn changes your service URL or you may need to change the configuration of service bindings/behaviors. If you change any WCF configuration at the server, you need to provide the same bindings/behaviors configuration to the clients also to work the client-service interaction properly. And this is very difficult in typical clients like XBAP (WPF Browser application) clients. My ExtendingWCFPartI.WcfExtentions.dll component provides a solution to this. You can have an external configuration file (e.g. by downloading it from the server before the client application starts up) where service endpoints are configured properly and use that external configuration file to create the WCF service proxy instances.
- In many cases, we want to get exactly the same exception thrown from WCF service at the WCF client. ExtendingWCFPartI.WcfExtentions.dll component also provides this.
Background
Extending ChannelFactory<T>
This extension is to serve the first introduction item. In general, System.ServiceModel.ChannelFactory<T>
provides us the option to get a proxy instance programmatically instead of using the proxy generated through svcutil.exe (.NET Framework tool). I've extended this class to write my own ExtendingWCFPartI.WcfExtentions.ExtendedChannelFactory<T>
class to provide the external configuration file functionality. The constructor argument "configurationPath
" should contain the full path of the external configuration file. Then I override the ChannelFactory<T>.CreateDescription()
method to apply the external configuration file. The external configuration file looks exactly the same as conventional client's app.config or web.config file. For a sample external configuration, you can look at the ExtendingWCFPartI.Client.WcfClientConfiguration.xml file.
Extending WCF Service and WCF Client Behavior
To provide the second introduction item, we need to extend the behavior of service, endpoint and contract.
public class ExtendedServiceErrorHandler:IErrorHandler
{
#region IErrorHandler Members
bool IErrorHandler.HandleError(Exception error)
{
if (error is FaultException)
{
return false;
}
else
{
return true;
}
}
void IErrorHandler.ProvideFault(Exception error,
MessageVersion version,
ref Message fault)
{
if (error is FaultException)
{
}
else
{
MessageFault messageFault = MessageFault.CreateFault(
new FaultCode("Sender"),
new FaultReason(error.Message),
error,
new NetDataContractSerializer());
fault = Message.CreateMessage(version, messageFault, null);
}
}
#endregion
}
ExtendedServiceErrorHandler
should be injected into ChannelDispatcher's error handler list. Then the WCF Framework will use our implemented IErrorHandler.ProvideFault
method when any exception is thrown from the service. Inside the method implementation, I used the NetDataContractSerializer
to serialize the MessageFault
which will be transmitted to the client.
public class ExtendedClientMessageInspector:IClientMessageInspector
{
#region Methods
private static object ReadFaultDetail(Message reply)
{
const string detailElementName = "Detail";
using (var reader = reply.GetReaderAtBodyContents())
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element &&
reader.LocalName == detailElementName)
{
break;
}
}
if (reader.NodeType != XmlNodeType.Element ||
reader.LocalName != detailElementName)
{
return null;
}
if (!reader.Read())
{
return null;
}
var serializer = new NetDataContractSerializer();
try
{
return serializer.ReadObject(reader);
}
catch (FileNotFoundException)
{
return null;
}
}
}
#endregion
#region IClientMessageInspector Members
void IClientMessageInspector.AfterReceiveReply
(ref Message reply, object correlationState)
{
if (reply.IsFault)
{
var buffer = reply.CreateBufferedCopy(Int32.MaxValue);
var copy = buffer.CreateMessage();
reply = buffer.CreateMessage();
var faultDetail = ReadFaultDetail(copy);
var exception = faultDetail as Exception;
if (exception != null)
{
throw exception;
}
}
}
object IClientMessageInspector.BeforeSendRequest
(ref System.ServiceModel.Channels.Message request,
System.ServiceModel.IClientChannel channel)
{
return null;
}
#endregion
}
Now we need to extend the IClientMessageInspector
to get back the same exception at the client end. At IClientMessageInspector.AfterReceiveReply
method implementation, NetDataContractSerializer
has been used to deserialize the fault exception and get the actual exception back again.
public class ExtendedServiceBehavior:Attribute,
IServiceBehavior, IEndpointBehavior, IContractBehavior
{
private void ApplyDispatchBehavior(ChannelDispatcher dispatcher)
{
foreach (IErrorHandler errorHandler in dispatcher.ErrorHandlers)
{
if (errorHandler is ExtendedServiceErrorHandler)
{
return;
}
}
dispatcher.ErrorHandlers.Add(new ExtendedServiceErrorHandler());
}
private void ApplyClientBehavior(ClientRuntime runtime)
{
foreach (IClientMessageInspector messageInspector in runtime.MessageInspectors)
{
if (messageInspector is ExtendedClientMessageInspector)
{
return;
}
}
runtime.MessageInspectors.Add(new ExtendedClientMessageInspector());
}
}
To tell the .NET runtime to use my ExtendedServiceErrorHandler
at Service end error handling and ExtendedClientMessageInspector
at client error handling, we need this ExtendedServiceBehavior
class which implemented service, endpoint and contract behavior. All behaviors have an AddBindingParameters
method, an ApplyDispatchBehavior
method, a Validate
method, and an ApplyClientBehavior
method with one exception: Because IServiceBehavior
cannot execute in a client, it does not implement ApplyClientBehavior
. For more details, you can read the MSDN documentation.
Using the Code
For simplicity, I've hosted the WCF service at the console. To run the sample code and see it working, follow the steps below:
- After opening the "
ExtendingWCFPartI
" application with VS, right click on the ExtendingWCFPartI.Service
project, then click Debug -- > Start new instance. Doing so, you have ensured that the service is running at console host. - Then run the
ExtendingWCFPartI.Client
project under debug mode.
ExtendingWCFPartI.WcfExtentions.dll is the reusable component which is going to be used at both WCF client and WCF service. Also make sure that you wrote the custom exceptions in a common assembly which will be used at both WCF client and WCF service.
Using the Code at WCF Service
At first let me show you how we can use this component to host our WCF services. You can host the WCF services at IIS or at Windows Service or at console, no matter where you host it, add the following section at your app.config or web.config ServiceModel
section. Add a reference to the ExtendingWCFPartI.WcfExtentions.dll to your service host application.
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="ExtendedServiceBehavior"
type="ExtendingWCFPartI.WcfExtentions.ServiceBehaviorExtension,
ExtendingWCFPartI.WcfExtentions, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
</system.serviceModel>
ExtendingWCFPartI.WcfExtentions.ServiceBehaviorExtension
has been used to tell WCF framework to use ExtendedServiceBehavior
at its CreateBehavior()
method.
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="CustomServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceThrottling maxConcurrentCalls="16" maxConcurrentSessions="16" />
<ExtendedServiceBehavior />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Now create a service behavior like the example above. <ExtendedServiceBehavior>
ensures that this behavior configuration will use the ExtendedServiceBehavior
.
<system.serviceModel>
<services>
<service behaviorConfiguration="CustomServiceBehavior"
name="ExtendingWCFPartI.Service.CustomerService">
...
...
Your service endpoint's configuration goes here.
</service>
</services>
</system.serviceModel>
That's all you need to do to host your WCF service which will have the two functionalities explained in the Introduction.
Using the Code at WCF Client
Add a reference to the ExtendingWCFPartI.WcfExtentions.dll at your client application. Now create the external configuration file. It may look like the following:
="1.0"="utf-8"
<configuration>
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="ExtendedServiceEndpointBehavior"
type="ExtendingWCFPartI.WcfExtentions.ServiceBehaviorExtension,
ExtendingWCFPartI.WcfExtentions, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
<bindings>
.. tips: provide the same binding which has been used at service
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="CustomServiceEndpointBehavior">
<ExtendedServiceEndpointBehavior />
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="http://localhost:8036/Services/CustomerService"
binding="wsHttpBinding" bindingConfiguration="CustomWSHttpBinding"
behaviorConfiguration="CustomServiceEndpointBehavior"
contract="ExtendingWCFPartI.Common.Services.ICustomerService"
name="CustomerServiceEndPoint" />
</client>
</system.serviceModel>
</configuration>
Now save this external configuration file at any location of your hard drive with .xml or .config extension, e.g. "C:\Temp\MyAppServices.xml".
I've provided a very simple interface ExtendingWCFPartI.WcfExtentions.WcfClientHelper
to get the instance of proxy.
public static T GetProxy<T>(string externalConfigPath)
{
var channelFactory = new ExtendedChannelFactory<T>(externalConfigPath);
channelFactory.Open();
return channelFactory.CreateChannel();
}
At the client code, use this interface to call your service method:
var externalConfigPath = @"C:\Temp\MyAppServices.xml";
var proxy = WcfClientHelper.GetProxy<IMyWCFService>(externalConfigPath);
proxy.CallServiceMethod();
Points of Interest
There are many more extension points at WCF framework which are very interesting and useful. I'll try my best to cover few more examples later in my next article.
In brief, I found the WCF framework so well designed that programmers have almost all the options to do whatever they want.
History
- 7th November, 2009: Initial version