Thursday, 4 February 2016

Creating WCF Client Channels from Configuration

I'm going to write today about WCF: specifically about how to create client channel instances when your config is held somewhere other than the app.config or web.config file of your application. I should point out that I'm not a huge WCF fan, and try not to use it where possible, but sometimes you have no choice.

I've recently been working on a project where some parts of the application don't have access to the web.config file, so their configuration had to be held in a database (there are very good reasons for this, but I won't go into them).

So rather than have the client config in the web.config file and use the generated client proxy classes to make calls to the service, the solution I used was to use the ConfigurationChannelFactory<T> class provided in the System.ServiceModel.Configuration namespace. I created a WcfConfiguration class (more on this later) and used it to create the channel factory as follows:
private static ConfigurationChannelFactory<TChannel> CreateChannelFactory(WcfConfiguration wcfConfiguration)
{
    return new ConfigurationChannelFactory<TChannel>(
        wcfConfiguration.EndpointConfiguration.Name,
        wcfConfiguration.Config,
        new EndpointAddress(wcfConfiguration.EndpointConfiguration.Address));
}

The ConfigurationChannelFactory<T> object needs various configuration in the form of a System.Configuration.Configuration object, and also some endpoint configuration information. One big restriction of the System.Configuration.Configuration class is that, AFAIK, it can only be instantiated from a config file on the file system. So what I did was take the <system.serviceModel> configuration section that was stored in the database as a string and write this out to a file, to then be used to create the Configuration object. A bit roundabout but there you go!!
private System.Configuration.Configuration CreateTempConfigFile(string filename, string rawConfig)
{
    var configFilename = $"{filename}.config";
    var configFilepath = Path.Combine(_configDirectory, configFilename);
    File.WriteAllText(configFilepath, string.Format(ConfigFormat, rawConfig));
    
    var virtualDirectoryMapping = new VirtualDirectoryMapping(_configDirectory, false, configFilename);
    var fileMap = new WebConfigurationFileMap();
    fileMap.VirtualDirectories.Add(VirtualDirectoryName, virtualDirectoryMapping);
    var webSiteName = HostingEnvironment.SiteName;
    var configuration = WebConfigurationManager.OpenMappedWebConfiguration(fileMap, VirtualDirectoryName, webSiteName);

    return configuration;
}

The System.Configuration.Configuration object can now be used to create an instance of my WcfConfiguration class, which is just a data bucket for the objects it's given:
private WcfConfiguration CreateWcfConfiguration(System.Configuration.Configuration configuration, string rawConfig)
{
    var serviceModelSectionGroup = ServiceModelSectionGroup.GetSectionGroup(configuration);
    if (serviceModelSectionGroup == null)
    {
     throw new System.Configuration.ConfigurationErrorsException("The WCF client configuration does not contain a 'system.serviceModel' section.");
    }
    if (serviceModelSectionGroup.Client == null)
    {
     throw new System.Configuration.ConfigurationErrorsException("The WCF client configuration does not contain a 'client' section.");
    }
    if (serviceModelSectionGroup.Client.Endpoints == null || serviceModelSectionGroup.Client.Endpoints.Count == 0)
    {
     throw new System.Configuration.ConfigurationErrorsException("The WCF client configuration does not contain any endpoints in the 'client' section.");
    }

    var endpointConfig = serviceModelSectionGroup.Client.Endpoints[0];
    var wcfConfiguration = new WcfConfiguration(rawConfig, configuration, endpointConfig)

    return wcfConfiguration;
}

The ConfigurationChannelFactory<T> is created as shown above and cached. The cached object is used to create any channels that are needed. This means that, although it's a pain having to write out a physical config file, at least it only needs to be done once.

If anyone has a better solution to this problem, or ideas on how this can be improved, then please let me know!

No comments:

Post a Comment