app.config for a part

Mar 18, 2009 at 4:49 AM
Hi,

Sorry if this is a newbie question.

I've created a part which is a client to a WCF service. All of the configuration for the service (bindings, endpoints etc) is stored in the app.config file for that part.

This works fine when the part is run as a standalone application. However, when I use the part actually as a part (i.e. plugged into an MEF-enabled application), the call to the WCF service fails. I'm pretty sure this is because the part is looking for the service configuration details, but is picking up the app.config file for the MEF host application.

I have temporarily worked around this by copying the configuration details from the part's app.config file, into the MEF host application's app.config file - but that kind of defeats the purpose of using MEF (in that the MEF host should be completely unaware of the part, until runtime).

Any suggestions are welcome!

Cheers,

Darren.
Apr 2, 2009 at 6:22 PM
You can put your service config in a separate file; Here's the code I used. I found it by trawling the Internet (as usual:-))

You need to find a way of passing in the location of the file; 'ConfigurationPath' but I'm sure you can do that.

namespace MyCustom.Service

{

     /// <summary>

    /// Custom client channel. Allows to specify a different configuration file

    /// </summary>

    /// <typeparam name="T"></typeparam>

    public class CustomClientChannel<T> : ChannelFactory<T>

    {

        string configurationPath;

        string endpointConfigurationName;

 

        /// <summary>

        /// Constructor

        /// </summary>

        /// <param name="configurationPath"></param>

        public CustomClientChannel(string configurationPath)

            : base(typeof(T))

        {

            this.configurationPath = configurationPath;

            base.InitializeEndpoint((string)null, null);

        }

 

        /// <summary>

        /// Constructor

        /// </summary>

        /// <param name="binding"></param>

        /// <param name="configurationPath"></param>

        public CustomClientChannel(Binding binding, string configurationPath)

            : this(binding, (EndpointAddress)null, configurationPath)

        {

        }

 

        /// <summary>

        /// Constructor

        /// </summary>

        /// <param name="serviceEndpoint"></param>

        /// <param name="configurationPath"></param>

        public CustomClientChannel(ServiceEndpoint serviceEndpoint, string configurationPath)

            : base(typeof(T))

        {

            this.configurationPath = configurationPath;

            base.InitializeEndpoint(serviceEndpoint);

        }

 

        /// <summary>

        /// Constructor

        /// </summary>

        /// <param name="endpointConfigurationName"></param>

        /// <param name="configurationPath"></param>

        public CustomClientChannel(string endpointConfigurationName, string configurationPath)

            : this(endpointConfigurationName, null, configurationPath)

        {

        }

 

        /// <summary>

        /// Constructor

        /// </summary>

        /// <param name="binding"></param>

        /// <param name="endpointAddress"></param>

        /// <param name="configurationPath"></param>

        public CustomClientChannel(Binding binding, EndpointAddress endpointAddress, string configurationPath)

            : base(typeof(T))

        {

            this.configurationPath = configurationPath;

            base.InitializeEndpoint(binding, endpointAddress);

        }

 

        /// <summary>

        /// Constructor

        /// </summary>

        /// <param name="binding"></param>

        /// <param name="remoteAddress"></param>

        /// <param name="configurationPath"></param>

        public CustomClientChannel(Binding binding, string remoteAddress, string configurationPath)

            : this(binding, new EndpointAddress(remoteAddress), configurationPath)

        {

        }

 

        /// <summary>

        /// Constructor

        /// </summary>

        /// <param name="endpointConfigurationName"></param>

        /// <param name="endpointAddress"></param>

        /// <param name="configurationPath"></param>

        public CustomClientChannel(string endpointConfigurationName, EndpointAddress endpointAddress, string configurationPath)

            : base(typeof(T))

        {

            this.configurationPath = configurationPath;

            this.endpointConfigurationName = endpointConfigurationName;

            base.InitializeEndpoint(endpointConfigurationName, endpointAddress);

        }

 

        /// <summary>

        /// Loads the serviceEndpoint description from the specified configuration file

        /// </summary>

        /// <returns></returns>

        protected override ServiceEndpoint CreateDescription()

        {

            ServiceEndpoint serviceEndpoint = base.CreateDescription();

 

            if (endpointConfigurationName != null)

                serviceEndpoint.Name = endpointConfigurationName;

 

            string configFilename = System.IO.Path.Combine(physicalPath, this.configurationPath);

            if (!File.Exists(configFilename))

            {

                LogEventData("Config File not found:" + configFilename, EventLogEntryType.Warning);

                return null;

            }

            string logMsg = "Using " + configFilename;

            LogEventData(logMsg, EventLogEntryType.Information);

 

            ExeConfigurationFileMap map = new ExeConfigurationFileMap();

            map.ExeConfigFilename = configFilename;

 

            Configuration config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);

            ServiceModelSectionGroup group = ServiceModelSectionGroup.GetSectionGroup(config);

            ChannelEndpointElement selectedEndpoint = null;

 

            foreach (ChannelEndpointElement endpoint in group.Client.Endpoints)

            {

                if (endpoint.Contract == serviceEndpoint.Contract.ConfigurationName &&

                    (this.endpointConfigurationName == null || this.endpointConfigurationName == endpoint.Name))

                {

                    selectedEndpoint = endpoint;

                    break;

                }

            }

 

            if (selectedEndpoint != null)

            {

                if (serviceEndpoint.Binding == null)

                {

                    serviceEndpoint.Binding = CreateBinding(selectedEndpoint.Binding, group);

                }

 

                if (serviceEndpoint.Address == null)

                {

                    serviceEndpoint.Address = new EndpointAddress(selectedEndpoint.Address, GetIdentity(selectedEndpoint.Identity), selectedEndpoint.Headers.Headers);

                }

 

                if (serviceEndpoint.Behaviors.Count == 0 && selectedEndpoint.BehaviorConfiguration != null)

                {

                    AddBehaviors(selectedEndpoint.BehaviorConfiguration, serviceEndpoint, group);

                }

                serviceEndpoint.Name = selectedEndpoint.Contract;

            }

            return serviceEndpoint;

        }

 

        private string _physicalPath = null;

        private string physicalPath

        {

            get

            {

                if (String.IsNullOrEmpty(_physicalPath))

                {

                    Settings settings = new Settings();

                    _physicalPath = settings.WCFConfigPath;

                    if (String.IsNullOrEmpty(_physicalPath))

                    {

                        // if hosted in IIS

                        _physicalPath = System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath;

                        if (String.IsNullOrEmpty(_physicalPath))

                        {

                            // for hosting outside of IIS

                            _physicalPath = System.IO.Directory.GetCurrentDirectory();

                        }

                    }

                }

                return _physicalPath;

            }

        }

 

        /// <summary>

        /// Configures the binding for the selected endpoint

        /// </summary>

        /// <param name="bindingName"></param>

        /// <param name="group"></param>

        /// <returns></returns>

        private Binding CreateBinding(string bindingName, ServiceModelSectionGroup group)

        {

            BindingCollectionElement bindingElementCollection = group.Bindings[bindingName];

            if (bindingElementCollection.ConfiguredBindings.Count > 0)

            {

                IBindingConfigurationElement be = bindingElementCollection.ConfiguredBindings[0];

 

                Binding binding = GetBinding(be);

                if (be != null)

                {

                    be.ApplyConfiguration(binding);

                }

                return binding;

            }

            return null;

        }

 

        /// <summary>

        /// Helper method to create the right binding depending on the configuration element

        /// </summary>

        /// <param name="configurationElement"></param>

        /// <returns></returns>

        private Binding GetBinding(IBindingConfigurationElement configurationElement)

        {

            if (configurationElement is CustomBindingElement)

                return new CustomBinding();

            else if (configurationElement is BasicHttpBindingElement)

                return new BasicHttpBinding();

            else if (configurationElement is NetMsmqBindingElement)

                return new NetMsmqBinding();

            else if (configurationElement is NetNamedPipeBindingElement)

                return new NetNamedPipeBinding();

            else if (configurationElement is NetPeerTcpBindingElement)

                return new NetPeerTcpBinding();

            else if (configurationElement is NetTcpBindingElement)

                return new NetTcpBinding();

            else if (configurationElement is WSDualHttpBindingElement)

                return new WSDualHttpBinding();

            else if (configurationElement is WSHttpBindingElement)

                return new WSHttpBinding();

            else if (configurationElement is WSFederationHttpBindingElement)

                return new WSFederationHttpBinding();

 

            return null;

        }

 

        /// <summary>

        /// Adds the configured behavior to the selected endpoint

        /// </summary>

        /// <param name="behaviorConfiguration"></param>

        /// <param name="serviceEndpoint"></param>

        /// <param name="group"></param>

        private void AddBehaviors(string behaviorConfiguration, ServiceEndpoint serviceEndpoint, ServiceModelSectionGroup group)

        {

            if (group.Behaviors.EndpointBehaviors.Count == 0)

                return;

 

            EndpointBehaviorElement behaviorElement = group.Behaviors.EndpointBehaviors[behaviorConfiguration];

            for (int i = 0; i < behaviorElement.Count; i++)

            {

                BehaviorExtensionElement behaviorExtension = behaviorElement[i];

                object extension = behaviorExtension.GetType().InvokeMember("CreateBehavior",

                    BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,

                    null, behaviorExtension, null);

                if (extension != null)

                {

                    serviceEndpoint.Behaviors.Add((IEndpointBehavior)extension);

                }

            }

        }

 

        /// <summary>

        /// Gets the endpoint identity from the configuration file

        /// </summary>

        /// <param name="element"></param>

        /// <returns></returns>

        private EndpointIdentity GetIdentity(IdentityElement element)

        {

            EndpointIdentity identity = null;

            PropertyInformationCollection properties = element.ElementInformation.Properties;

            if (properties["userPrincipalName"].ValueOrigin != PropertyValueOrigin.Default)

            {

                return EndpointIdentity.CreateUpnIdentity(element.UserPrincipalName.Value);

            }

            if (properties["servicePrincipalName"].ValueOrigin != PropertyValueOrigin.Default)

            {

                return EndpointIdentity.CreateSpnIdentity(element.ServicePrincipalName.Value);

            }

            if (properties["dns"].ValueOrigin != PropertyValueOrigin.Default)

            {

                return EndpointIdentity.CreateDnsIdentity(element.Dns.Value);

            }

            if (properties["rsa"].ValueOrigin != PropertyValueOrigin.Default)

            {

                return EndpointIdentity.CreateRsaIdentity(element.Rsa.Value);

            }

            if (properties["certificate"].ValueOrigin != PropertyValueOrigin.Default)

            {

                X509Certificate2Collection supportingCertificates = new X509Certificate2Collection();

                supportingCertificates.Import(Convert.FromBase64String(element.Certificate.EncodedValue));

                if (supportingCertificates.Count == 0)

                {

                    throw new InvalidOperationException("UnableToLoadCertificateIdentity");

                }

                X509Certificate2 primaryCertificate = supportingCertificates[0];

                supportingCertificates.RemoveAt(0);

                return EndpointIdentity.CreateX509CertificateIdentity(primaryCertificate, supportingCertificates);

            }

            return identity;

        }

    }

}

Apr 2, 2009 at 8:27 PM
Nice, thanks for taking the time Robert.

That looks like it will work for my specific scenario, where I've got a WCF service in my part. I wonder if there is a more generic solution though - what if I had other config data in my part's app.config file?

It would be nice if MEF could somehow intercept all calls to app.config for a part, and redirect them to that part's app.config file. I'm new to dotNet so have no idea if this is even remotely possible. 

Darren.
Apr 3, 2009 at 5:41 AM
 Hi Darren

I've checked with folks on the WCF team and unfortunately it seems like there's no easy way to do this. The best solution I heard, is to set up a separate app domain and run the service from there, as each app domain can have it's own config which you could programatically modify based on imports from MEF. You might also look into integrating MEF with System.Addin to get what you want. Kent Boogaart has a great post on his blog about how to integrate the two technologies, here http://kentb.blogspot.com/2009/02/maf-and-mef.html

HTH Glenn
Apr 3, 2009 at 10:02 AM
Thanks Glenn.

I had hoped to avoid the intricacies of MAF, and really had wanted to have the plug-ins running in the same appdomain as the main app.

Doing some more googling, it seems that having separate app.config files for DLL's has been on many people's wishlists for years. No chance you can pull some strings & get that done when MEF makes its way into the System namespace? :)

I think I'll end up either hand-merging the app.config files, or hard-coding the plugin's settings.

Cheers,

Darren.
Apr 3, 2009 at 11:09 AM
Darren

Actually my team (FXCore which MEF is a part of) owns the config story for the framework going forward. So we will certainly keep this feedback in mind. It won't make it in .NET 4.0 though :-(

Thanks
Glenn