An application that consumes one or more WCF services needs to provide the address, binding protocol, and contract (ABC) of each end point in its app.config file. This article shows an easy way to let the application get its WCF client configuration data from anywhere: a flat file, a database, even pigeon post if you like. The approach leverages the native XML configuration schema as well as the proxy generated by Visual Studio.NET. The following is a screenshot from the attached solutin, showing a WPF client that calls the canonical Hello World service synchronously and asynchronously, but using an end point configuration from a local string variable:
Rolling your own WCF custom client
Basically there are two approaches if you want to build a custom WCF client:
- create your own proxy entirely from scratch , or
- extend an existing proxy with a custom channel factory.
Both of these require extensive coding. In this article, I elaborate on the latter approach: I'll build a custom channel factory -derived from ChannelFactory<T>- that reads its WCF client configuration from a custom configuration file. Then I plug this channel factory into the proxy that was generated by Visual Studio.NET.
Oh wait, in the title of this article I mentioned that the configuration data could come from anywhere - not just a file. As it stands, we always need to persist the configuration data in a physical file. Some of the non-virtual members and constructors of the classes in the ChannelFactory<T> hierarchy use the System.Configuration namespace to parse the address, binding, and contract information. The classes in this namespace are specialized in reading configuration sections from physical XML configuration files à la app.config. These classes require a file path to do their work. So there's no way around a config file - at least a temporary one.
Creating a custom channel factory
When you create a custom channel factory, you inherit from ChannelFactory<T> and then override one of the following methods:
- CreateDescription, or
- ApplyConfiguration.
The attached solution at the end of this article uses the latter approach: it overrides ApplyConfiguration.
The code is borrowed from this msdn forum article. It reads the configuration from an arbitrary config file using the very same System.Configuration classes. This is convenient: we can copy/paste the -complex- config file from the project holding the service reference!
Reusing the svcutil proxy
It's perfectly possible to use a custom channel factory and still leverage all the code from the proxy that was generated by Visual Studio.NET through svcutil, e.g. the asynchronous calls and the fault classes. All you need to do is create a child class from the proxy, and override its CreateChannel method.
Here's the architecture:
Here's the CreateChannel method:
/// <summary>
/// Creates a channel that implements the service interface.
/// </summary>
/// <returns>A channel from the end point in the custom configuration file.</returns>
protected override IDummyService CreateChannel()
{
CustomChannelFactory<IDummyService> factory =
new CustomChannelFactory<IDummyService>(
"IDummyServiceBinding", /* or use "*" for the first end point */
this.configurationFilePath);
return factory.CreateChannel();
}
At runtime, the first thing to do is fetching the configuration information from the store of your choice. The attached solution just uses a string variable:
private string endpointConfiguration =
@"
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name='BasicHttpBinding_IDummyService' closeTimeout='00:01:00'
openTimeout='00:01:00' receiveTimeout='00:10:00' sendTimeout='00:01:00'
allowCookies='false' bypassProxyOnLocal='false' hostNameComparisonMode='StrongWildcard'
maxBufferSize='65536' maxBufferPoolSize='524288' maxReceivedMessageSize='65536'
messageEncoding='Text' textEncoding='utf-8' transferMode='Buffered'
useDefaultWebProxy='true'> etc.etc. etc.
Then we need to save this information in a file, preferably in a fast and safe place. The ideal candidate is of course Isolated storage.
Storing configuration files in isolated storage file
The following method saves the configuration information in isolated storage and returns the physical path to that file through a small hack (Reflection):
/// <summary>
/// Saves the configuration information in a file in isolated storage, and returns the path.
/// </summary>
/// <param name="configuration">XML configuration elements.</param>
/// <param name="fileName">Configuration file name</param>
/// <returns>Full path name of the configuration file.</returns>
private string GetConfigurationFilePath(string configuration, string fileName)
{
// Create a file in isolated storage.
IsolatedStorageFile store = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly, null, null);
IsolatedStorageFileStream stream = new IsolatedStorageFileStream(fileName, FileMode.Create, store);
StreamWriter writer = new StreamWriter(stream);
writer.WriteLine(configuration);
writer.Close();
stream.Close();
// Retrieve the actual path of the file (using Reflection).
string proxyConfigurationPath = stream.GetType().GetField("m_FullPath", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(stream).ToString();
return proxyConfigurationPath;
}
That went easy, so where's the catch ?
This solution comes with a small price: the app.config still needs an end point definition. This is because the constructor of ClientBase<T> -the base class of the Visual Studio proxy- parses the config file for a syntactically valid end point: there must be an address (a fake URI will do), a binding protocol (any type will do), and a full contract definition. This end point is never used however, because our child class overrides the creation of the channel. So we can get away with a dummy end point in the standard configuration file - only the contract needs to match:
<configuration>
<system.serviceModel>
<client>
<!-- Look mom: no real configuration. -->
<endpoint
address="http://dummyAddress"
binding="netMsmqBinding"
contract="ServiceReference.IDummyService" />
</client>
</system.serviceModel>
</configuration>
Source code
Here's the sample solution: U2UConsult.WCF.Configuration.Sample.zip (98,53 kb)
Enjoy!