Config Hosting multiple WCF services in one NT service
Hello there,
I've a question about hosting a service in a selfhosting scenario (not IIS or WAS).
For example, I have one WCFService with a config file (web.config). In this config file a system.serviceModel section exists, but also several appSettings.
Hosting in IIS is very simple. I created a svc file and voila, you can call the service. I do not have to place the configsettings of the WCFService into IIS.
Now I turn to host the WCFService in another host, a Windows NT service (or a win/console app).
Why do I have to place all of the WCFService configuration settings in the app.config of the NT Service? Why not just a reference to the physical path of the WCFService.
In the case of three WCFServices hosted by one NT Service, I have to place three web.config contents into the app.config of the NT Service. (Am I wrong? I hope so.)
tnx for reply!
Marijn
回答
This is how app.config files work. If you have 3 services, you will want them defined in the app.config. From an administraiton standpoint if you wanted to configure them, you may not know where to look for teh config files. At least if it is on the config of the exe, it is easy to find and change.
In IIS when you have a different svc file for service and a different web.config because you basically have 3 different wcf service hosts but 3 different service hosting addresses. In the selfhosting solution you are talking about, there will still be 3 service hosts, but one exe housing it.
- You can load and deserialise client config from a single global location using the custom channel factory:
Code Snippetusing System;
using System.Configuration;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
namespace WcfService
{
/// <summary>
/// A custom channel factory which takes in a path to a
/// custom configuration file
/// </summary>
/// <typeparam name="T"></typeparam>
public class CustomChannelFactory<T> : ChannelFactory<T>
{
string _configurationPath;
/// <summary>
/// custom client channel constructor which
/// specifies an external configuration file
/// </summary>
/// <param name="configurationPath"></param>
public CustomChannelFactory(string configurationPath)
: base(typeof(T))
{
_configurationPath = configurationPath;
base.InitializeEndpoint((string)null, null);
}
/// <summary>
/// overrides the CreateDescription() method of the channel factory
/// to apply a new configuration file
/// </summary>
/// <returns></returns>
protected override ServiceEndpoint CreateDescription()
{
ServiceEndpoint serviceEndpoint = base.CreateDescription();
ExeConfigurationFileMap executionFileMap = new ExeConfigurationFileMap();
executionFileMap.ExeConfigFilename = _configurationPath;
System.Configuration.Configuration config = ConfigurationManager.OpenMappedExeConfiguration(executionFileMap, ConfigurationUserLevel.None);
ServiceModelSectionGroup serviceModeGroup = ServiceModelSectionGroup.GetSectionGroup(config);
ChannelEndpointElement selectedEndpoint = null;
foreach (ChannelEndpointElement endpoint in serviceModeGroup.Client.Endpoints)
{
if (endpoint.Contract == serviceEndpoint.Contract.ConfigurationName)
{
selectedEndpoint = endpoint;
break;
}
}
if (selectedEndpoint != null)
{
if (serviceEndpoint.Binding == null)
{
serviceEndpoint.Binding = CreateBinding(selectedEndpoint.Binding, serviceModeGroup);
}
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, serviceModeGroup);
}
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, 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>
/// 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)
{
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;
}
/// <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;
}
}
}
...and call it like this:
CustomChannelFactory<MyService> customChannelFactory = new CustomChannelFactory<MyService>(configFilePath);
...this would obviously be in the client code calling the service - but perhaps you could modify it for the actual hosted service?
すべての返信
This is how app.config files work. If you have 3 services, you will want them defined in the app.config. From an administraiton standpoint if you wanted to configure them, you may not know where to look for teh config files. At least if it is on the config of the exe, it is easy to find and change.
In IIS when you have a different svc file for service and a different web.config because you basically have 3 different wcf service hosts but 3 different service hosting addresses. In the selfhosting solution you are talking about, there will still be 3 service hosts, but one exe housing it.
- You can load and deserialise client config from a single global location using the custom channel factory:
Code Snippetusing System;
using System.Configuration;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
namespace WcfService
{
/// <summary>
/// A custom channel factory which takes in a path to a
/// custom configuration file
/// </summary>
/// <typeparam name="T"></typeparam>
public class CustomChannelFactory<T> : ChannelFactory<T>
{
string _configurationPath;
/// <summary>
/// custom client channel constructor which
/// specifies an external configuration file
/// </summary>
/// <param name="configurationPath"></param>
public CustomChannelFactory(string configurationPath)
: base(typeof(T))
{
_configurationPath = configurationPath;
base.InitializeEndpoint((string)null, null);
}
/// <summary>
/// overrides the CreateDescription() method of the channel factory
/// to apply a new configuration file
/// </summary>
/// <returns></returns>
protected override ServiceEndpoint CreateDescription()
{
ServiceEndpoint serviceEndpoint = base.CreateDescription();
ExeConfigurationFileMap executionFileMap = new ExeConfigurationFileMap();
executionFileMap.ExeConfigFilename = _configurationPath;
System.Configuration.Configuration config = ConfigurationManager.OpenMappedExeConfiguration(executionFileMap, ConfigurationUserLevel.None);
ServiceModelSectionGroup serviceModeGroup = ServiceModelSectionGroup.GetSectionGroup(config);
ChannelEndpointElement selectedEndpoint = null;
foreach (ChannelEndpointElement endpoint in serviceModeGroup.Client.Endpoints)
{
if (endpoint.Contract == serviceEndpoint.Contract.ConfigurationName)
{
selectedEndpoint = endpoint;
break;
}
}
if (selectedEndpoint != null)
{
if (serviceEndpoint.Binding == null)
{
serviceEndpoint.Binding = CreateBinding(selectedEndpoint.Binding, serviceModeGroup);
}
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, serviceModeGroup);
}
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, 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>
/// 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)
{
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;
}
/// <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;
}
}
}
...and call it like this:
CustomChannelFactory<MyService> customChannelFactory = new CustomChannelFactory<MyService>(configFilePath);
...this would obviously be in the client code calling the service - but perhaps you could modify it for the actual hosted service? - Good job. An excellent snippet.
I have used this, but having looked at the ApplyConfiguration code in ChannelFactory, I thought it was a better "fit" to override ApplyConfiguration rather than CreateDescription. I do NOT call base.ApplyConfiguration but use your code instead. I also added a check for configurationpath==null (I had problems with the base ctor calling InitializeEndpoint for some additional ctor's I created, and with CustomDuplexChannelFactory<> I created) and added the wildcard check as-per the base ApplyConfiguration implementation.
protected override void ApplyConfiguration(string configurationName) { if (string.IsNullOrEmpty(_configurationPath)) return; ... bool isWildcard = string.Equals(configurationName, "*", StringComparison.Ordinal); foreach (ChannelEndpointElement endpoint in serviceModeGroup.Client.Endpoints) { if (endpoint.Contract == Endpoint.Contract.ConfigurationName && (endpoint.Name == configurationName || wildcard)) { selectedEndpoint = endpoint; break; } } ... }
I haven't come across any problem so far, but wonder if you can see anything obviously wrong with my modified approach.
Cheerz.

