0

I have a WPF application using Prism.

The application loads several modules.

Each module connects to one or more WCF Service.

The connection details are in the 'app.config' file of the Module library.

My question is - how do I make the 'Shell' project know about the endpoint configurations that are spread across different 'app.config' files in different assemblies.

Each client in different 'Module' that tries to connect - throws an exception that the 'endpoint' information could not be found...

abatishchev
  • 98,240
  • 88
  • 296
  • 433
John Miner
  • 893
  • 1
  • 15
  • 32

1 Answers1

1

UPDATE:

An alternative potential solution is to create the WCF clients in their own AppDomains.

See here for some ideas:

I guess the question is how to get this working with Prism...the trick might be to have a custom IModuleManager (Prism V4), IModuleLoader (V1) or Catalog to deal with loading your WCF client modules, or perhaps have a wrapper module that in turn loads your WCF clients.


My first attempt at doing something similar to what you are doing was to hack the AppDomain configuration by doing this in my DLL module.

    object o = AppDomain.CurrentDomain.GetData("APP_CONFIG_FILE");

    // See if there is a configuration defined by the Exe host, hosting this DLL.

    Configuration con = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

    // Do we need to override the configuration, to point to our "default" one?

    if (con.HasFile == false)
    {
        string sFullPathToConfig = Assembly.GetExecutingAssembly().Location + ".config";

        AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", sFullPathToConfig);

        ConfigurationManager.RefreshSection("system.serviceModel");
        ConfigurationManager.RefreshSection("system.diagnostics");
    }

I can't remember exactly, but I think I had to NOT define any ServiceModel configuration at all in my main application app.config (otherwise there would be a conflict in trying to replace it)...could be wrong on that...might have been the opposite, or might have been not have any app.config at all ! :o/. It was brittle for some reason....which I can't remember off the top of my head.

I also tried getting access to the ServiceModel configuration from the ConfigurationManager at runtime and trying to modify that by code...but it would have none of it.

Anyhow, I don't think the above will help you as you will be loading multiple modules, so need to load multiple configs.


So anyhow after trying the above I switched to a less brittle method by using a combination of:

ExceptionHandlingProxyBase<T>

from

and

CustomClientChannelFactory<T>

from

I put in some necessary fixes to get the ChannelFactory to work, and I modified it so I could choose if I wanted to override with my own configuration file, and also to support override of the address.

I used this constructor within the ExceptionHandlingProxyBase to create the factory:

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

You can disregard the ExceptionHandlingProxyBase part of this solution...that is just sugar that re-establishes the channel whenever the channel faults, so that you don't have to worry about the state of your proxy.

If you still don't want to use a ChannelFactory, then you could try hacking the ServiceModel configuration in your AppDomain. I tried that, but it seemed to be hard to modify

Here is the ChannelFactory code with the fixes in (stupidly renamed to CustomClientChannel).

/// <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;
bool m_bOverrideConfiguration = false;
Uri m_OverrideAddress = null;

    /// <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))
    {
    m_OverrideAddress = (endpointAddress != null ? endpointAddress.Uri : null);

        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()
    {
    if (string.IsNullOrEmpty(this.configurationPath))
    {
        System.Diagnostics.Debug.WriteLine("Not using overriding config file");

        return base.CreateDescription();
    }

    if (!System.IO.File.Exists(configurationPath))
    {
        System.Diagnostics.Debug.WriteLine("Overriding config file [" + configurationPath + "] doesn't exist");

        return base.CreateDescription();
    }

    m_bOverrideConfiguration = true;

        ServiceEndpoint serviceEndpoint = base.CreateDescription();

        if (endpointConfigurationName != null)
            serviceEndpoint.Name = endpointConfigurationName;

        ExeConfigurationFileMap map = new ExeConfigurationFileMap();
        map.ExeConfigFilename = this.configurationPath;

        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, selectedEndpoint.BindingConfiguration, group);
            }

        if (m_OverrideAddress != null)
        {
            serviceEndpoint.Address = new EndpointAddress(m_OverrideAddress, GetIdentity(selectedEndpoint.Identity), selectedEndpoint.Headers.Headers);
        }
        else
            if (serviceEndpoint.Address == null)
            {
                serviceEndpoint.Address = new EndpointAddress(selectedEndpoint.Address, GetIdentity(selectedEndpoint.Identity), selectedEndpoint.Headers.Headers);
            }

            if (serviceEndpoint.Behaviors.Count == 0 && !string.IsNullOrEmpty(selectedEndpoint.BehaviorConfiguration))
            {
                AddBehaviors(selectedEndpoint.BehaviorConfiguration, serviceEndpoint, group);
            }

            serviceEndpoint.Name = selectedEndpoint.Contract;
        }

        return serviceEndpoint;

    }

    /// <summary>
    /// Configures the binding for the selected endpoint
    /// </summary>
    /// <param name="bindingName"></param>
    /// <param name="group"></param>
    /// <returns></returns>
    private Binding CreateBinding(string bindingName, string bindingConfiguration, ServiceModelSectionGroup group)
    {
    IBindingConfigurationElement be = null;

        BindingCollectionElement bindingElementCollection = group.Bindings[bindingName];

    if (bindingElementCollection.ConfiguredBindings.Count > 0)
    {

        foreach (IBindingConfigurationElement bindingElem in bindingElementCollection.ConfiguredBindings)
        {

            if (string.Compare(bindingElem.Name, bindingConfiguration) == 0)
            {

                be = bindingElem;

                break;

            }

        }

        Binding binding = null;

        if (be != null)
        {

            binding = GetBinding(be);

            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;
    }


    protected override void ApplyConfiguration(string configurationName)
    {
    if (!m_bOverrideConfiguration)
    {
        // This picks up the configuration from the inherited config settings defined
        // by the application i.e. the normal place.

        base.ApplyConfiguration(configurationName);
    }
    }
}
Community
  • 1
  • 1
Colin Smith
  • 12,375
  • 4
  • 39
  • 47
  • thanks, the approach mentioned in your links seems nice, but reading through the comments - it seems as if there are quite a few bugs using that approach. In addition, I do not ALWAYS use the 'Channel Factory', so that method might not suite me always ... – John Miner Jul 31 '12 at 07:26
  • would be happy if you updated your answer with YOUR solution once you find it ... :) – John Miner Jul 31 '12 at 07:26
  • Better solution here: http://stackoverflow.com/questions/5045916/wcf-channelfactory-configuration-outside-of-app-config – Colin Smith Aug 01 '12 at 21:16
  • You mean the 'Use seperate AppDomain' solution ? – John Miner Aug 02 '12 at 07:16
  • 1
    Yeh, use the idea....how to implement with Prism is another question...maybe do your own IModuleLoader (see Prism) https://prism.svn.codeplex.com/svn/V1/spikes/AGCompositeApplicationLibrary/AGComposite/Modularity/ModuleLoader.cs .... or maybe you can get a Module to create a new AppDomain and do a LoadFrom or even start a new Prism Shell/Catalog, etc....just some ideas. – Colin Smith Aug 02 '12 at 08:29