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