Controlling ServiceHost Initialization at Run Time

How and Why to Extend ServiceHost and ServiceHostFactory

13 Min Read
ITPro Today logo in a gray background | ITPro Today

This month s column explores how you can extend theServiceHost and ServiceHostFactory types. For more information on WCF, see "Security Practices for WCF" and "Going Mobile with WCF."

At the heart of the WCF hosting model is the ServiceHosttype. When you host services with IIS or WAS, message-based activation takescare of constructing the ServiceHost instance for each service type, on demand.For self-hosted environments (not IIS or WAS), you explicitly write the code toconstruct the ServiceHost and open the communication channel for each endpoint.The process is simple, and usually draws from the service model configurationsection to initialize. However, at times, it is valuable to provide customendpoint and behavior configuration at run time. This is easily done withself-hosting environments because you have direct access to the ServiceHosttype. For IIS and WAS, it requires you to use ServiceHostFactory to gain accessto the ServiceHost type.

In this article, I ll show you how to programmaticallyinitialize the ServiceHost in both scenarios, teach you how to work withServiceHostFactory, and discuss practical reasons why you might want to dothis.

 

A Quick ServiceHost Review

The ServiceHost type is directly associated with aparticular service type, each of its endpoints, and any related behaviorconfiguration. As a CommunicationObject derivative, ServiceHost providesmethods to control when you open and close the communication channel, access tocommunication channel state, and access to events triggered during thelifecycle of the ServiceHost instance.

With self-hosted services, you traditionally create theServiceHost type by associating it with a service type, calling Open, andputting initialization details in the section ofthe application configuration file. For example, the code in Figure 1 willmaintain an instance of the ServiceHost type until you press Enter in a consoleapplication host.

ServiceHost hostServiceA = null;try{ hostServiceA =   new ServiceHost(typeof(BusinessServices.ServiceA)); hostServiceA.Open(); Console.WriteLine("Services are running..."); Console.ReadLine();}finally{ if (hostServiceA != null) {   hostServiceA.Close(); }}

Figure 1: Maintainan instance of the ServiceHost type until you press Enter in a consoleapplication host.

The ServiceHost instance is initialized by the applicationconfiguration shown in Listing One for the type BusinessServices.ServiceA.

The same configuration settings are required when you hostthe service in IIS or WAS, except that the .svc endpoint indicates the servicetype as follows:

<%@ ServiceHost Service="BusinessServices.ServiceA" %> 

In addition, there is no need for the sectionfor the service configuration section because the base address is provided bythe .svc endpoint within the hosting virtual application path.

In both cases, when the ServiceHost instance isconstructed and opened, a collection of channel dispatchers are created, onefor each endpoint, and the service behaviors associated with the service typeinfluence how messages are processed by each endpoint.

 

ServiceHostFactory

If all you are planning to do is load the ServiceHost fromconfiguration as shown earlier, there is no need to change the way the .svcendpoint loads the ServiceHost with IIS and WAS hosting. The code referencedearlier related to self-hosting is taken care of for you. If you want tocustomize the hosting code in any way, a ServiceHostFactory is necessary forIIS and WAS.

Let s start with a simple example. Although VERY rare, itis possible that the ServiceHost can be put into a faulted state. A predictableway to simulate this is with an MSMQ service that intentionally faults a poisonmessage which means the message is returned to the queue (never lost) and,thus, the ServiceHost instance is set to a Faulted state perpetually until thepoison message is dealt with. Poorly configured services can also trigger aFaulted state. Clearly, this is something we would like to know about.

To detect that the ServiceHost has faulted, you can hookthe Faulted event as shown in Figure 2.

ServiceHost hostServiceA = null;try{ hostServiceA =   new ServiceHost(typeof(BusinessServices.ServiceA)); hostServiceA.Faulted +=   new EventHandler(hostServiceA_Faulted); hostServiceA.Open(); Console.WriteLine("Services are running..."); Console.ReadLine();}finally{ if (hostServiceA != null && hostServiceA.State!=      CommunicationState.Faulted) {   hostServiceA.Close(); }}static void hostServiceA_Faulted(object sender, EventArgs e){ // Notify administrator, log exception}

Figure 2: Hook theFaulted event to detect that the ServiceHost has faulted.

In addition to logging the fault when it takes place, toclose the ServiceHost you must check first if the communication state isFaulted; otherwise, Close will throw an exception.

To make this code available to a .svc endpoint, we mustuse a ServiceHostFactory instance to create the ServiceHost instance. First,associate the factory with the @ServiceHost endpoint:

<%@ ServiceHost Service="BusinessServices.ServiceA" Factory="ServiceHostExtensions.ServiceHostFactoryEx" %> 

The implementation for ServiceHostFactoryEx is verysimple. Derive a type from ServiceHostFactory and construct the ServiceHosttype of your choice when requests arrive to the factory. Requests sent to the.svc endpoint will forward to ServiceHostFactoryEx sending the service typename along with it. ServiceHostFactoryEx is shown in Figure 3.

public class ServiceHostFactoryEx : ServiceHostFactory{ public ServiceHostFactoryEx() { } public override System.ServiceModel.ServiceHostBase  CreateServiceHost(string constructorString, Uri[]   baseAddresses) {   Type t = Type.GetType(string.Format("{0}, {1}",     constructorString, constructorString.Substring(0,     constructorString.IndexOf("."))));      ServiceHost host = new ServiceHost(t, baseAddresses);      host.Faulted += new EventHandler(host_Faulted);      return host; } protected override System.ServiceModel.ServiceHost   CreateServiceHost(Type serviceType, Uri[] baseAddresses) {      ServiceHost host = new ServiceHost(serviceType,        baseAddresses);      host.Faulted += new EventHandler(host_Faulted);      return host; }}

Figure 3: ServiceHostFactoryEximplementation.

The first constructor is always called from IIS and WAS,as it cannot be a singleton service in this hosting environment. The only trickhere is that you have to make assumptions about the assembly name that owns theservice type, as you are only provided with the fully qualified namespace andtype name. What I usually do is make sure my assemblies are named for thenamespace; thus, I can make that assumption in this code:

Type t = Type.GetType(string.Format("{0}, {1}", constructorString, constructorString.Substring(0, constructorString.IndexOf(".")))); 

The constructorString parameter is the service type name;I extract the namespace from there to build the fully qualified type name. Withthis I can construct the ServiceHost type, hook the Faulted event, and returnthe new instance to the runtime:

ServiceHost host = new ServiceHost(t, baseAddresses);host.Faulted += new EventHandler(host_Faulted);return host; 

If you want more control over the ServiceHost constructionprocess, you should also extend the ServiceHost type.

 

Controlling Service Behaviors at Run Time

If you look at the section from ListingOne, you ll see that some service behaviors have been explicitly associatedwith the service. Behaviors affect how authentication and authorization areexecuted, as well as debugging and metadata support, throttling requests to theservice type, and a number of other features. In configuration, you associatebehaviors with a service using the behaviorConfiguration property of the type:

 

You also can add them programmatically to the ServiceHostat run time. Service behaviors are represented at run time as types thatimplement IServiceBehavior. Common behaviors include:

  • ServiceCredentials

  • ServiceAuthorizationBehavior

  • ServiceDebugBehavior

  • ServiceMetadataBehavior

  • ServiceThrottlingBehavior

  • ServiceSecurityAuditBehavior

  • AspNetCompatibilityRequirementsAttribute

Some new behaviors have also been added to .NET 3.5 (to bediscussed in future articles):

  • DurableServiceAttribute

  • PersistenceProviderBehavior

  • WorkflowRuntimeBehavior

The point is, you can construct these behaviors at run time,then initialize the ServiceHost instance with the required settings.

Why set behaviors at run time? Perhaps for any of thefollowing reasons:

  • To ensure certain fixed behavior settings are always reflected at run time, without leaving them readily accessible in the configuration file.

  • To provide good defaults when no configuration settings are present, and use configuration settings if present.

  • To set behavior settings from a global configuration store, perhaps a configuration service or database, so services hosted on different machines or in different hosting environments can share common settings, and to control configuration settings for all services from a central location.

The behavior configuration for ServiceA shown in ListingOne includes the following settings:

  • Exceptions are not reported as faults.

  • Metadata exchange is enabled, browsing is disabled.

  • UserName credentials are authenticated and authorized by the ASP.NET provider model.

  • Throttling settings support 30 concurrent requests (instead of 16) and 1,000 concurrent sessions (instead of 10).

To handle this at run time, initialize the ServiceHostinstance before you call Open. Error handlers, another type of behavior thatcan be set only at run time, must be initialized after the channel dispatchershave been created (in other words, after you call Open). Initialization wouldlook something like this:

ConfigureServiceBehaviors(hostServiceA);hostServiceA.Open();AddErrorHandler(hostServiceA); 

ConfigureServiceBehaviors and AddErrorHandler are shown inFigure 5. The ServiceHost type provides direct access toServiceAuthorizationBehavior and ServiceCredentials via its Authorization andCredentials properties, thus setting values for those behaviors is a simpleproperty initialization instruction. ServiceThrottlingBehavior,ServiceDebugBehavior, and ServiceMetadataBehavior must be accessed via the Findmethod of the Behaviors collection. In Figure 4, the ServiceThrottlingBehavioris only initialized with values if there is not a configuration behavioralready present. The other two behaviors are always added to the ServiceHost,but the values for IncludeExceptionDetailsInFaults and HttpGetEnabled areinitialized to true or false depending if the code is compiled for debug ornot.

private static void ConfigureServiceBehaviors(ServiceHost host){ host.Authorization.ImpersonateCallerForAllOperations = false; host.Authorization.PrincipalPermissionMode =   PrincipalPermissionMode.UseAspNetRoles; host.Credentials.UserNameAuthentication.  UserNamePasswordValidationMode =   UserNamePasswordValidationMode.MembershipProvider; host.Credentials.ServiceCertificate.SetCertificate(  StoreLocation.LocalMachine, StoreName.My,   X509FindType.FindBySubjectName, "RPKey"); ServiceThrottlingBehavior throttleBehavior = host.Description.Behaviors.Find(); if (throttleBehavior == null) {      throttleBehavior = new ServiceThrottlingBehavior();     throttleBehavior.MaxConcurrentCalls = 30;     throttleBehavior.MaxConcurrentSessions = 1000;     host.Description.Behaviors.Add(throttleBehavior); }   bool debugMode =#if DEBUGtrue;#elsefalse;#endif ServiceDebugBehavior debugBehavior =  host.Description.Behaviors.Find(); if (debugBehavior == null) {     debugBehavior = new ServiceDebugBehavior();     host.Description.Behaviors.Add(debugBehavior); }   debugBehavior.IncludeExceptionDetailInFaults = debugMode; ServiceMetadataBehavior mexBehavior = host.  Description.Behaviors.Find(); if (mexBehavior == null) {     mexBehavior = new ServiceMetadataBehavior();     host.Description.Behaviors.Add(mexBehavior); } mexBehavior.HttpGetEnabled = debugMode;}private static void AddErrorHandler(ServiceHost host){ foreach (ChannelDispatcher dispatcher in   host.ChannelDispatchers) {     foreach (EndpointDispatcher epDispatcher in       dispatcher.Endpoints)     {         if ((epDispatcher.ContractName !=           "IMetadataExchange" && epDispatcher.ContractName          != "IHttpGetHelpPageAndMetadataContract"))             dispatcher.ErrorHandlers.Add(             new ConvertToFaultErrorHandler());     } }}

Figure 4: Code toinitialize behaviors for the ServiceHost instance.

Error handlers are types that implement IErrorHandler, andmust be added to each endpoint exposed by the ServiceHost instance after thehost is open. You can add error handlers using a custom attribute that youapply directly to the service type:

[ConvertToFaultErrorHandler]public class ServiceA : IServiceA

If you want to add the same error handler to all services,and prefer to control that at the host, Figure 4 illustrates how to do thisafter the host is opened. In fact, this listing also illustrates how to add theerror handler to non-metadata endpoints only.

To summarize, the configuration in Figure 4 adds somevalue to the configuration file results with the following results:

  • Exceptions are reported as faults only when the code is compiled in debug.

  • Metadata exchange is always enabled, but browsing is enabled only when compiled for debug.

  • UserName credentials are always authenticated and authorized by the ASP.NET provider model.

  • Throttling settings default to the configuration settings unless the configuration section is present, which can provide overrides.

  • An error handler to convert non-CommunicationException exceptions to faults is always added to non-metadata endpoints.

Adding Service Endpoints at Run Time

Aside from working with service behaviors at run time, forthese reasons you may also want to configure endpoints at run time:

  • To initialize endpoints from a global configuration store, perhaps a configuration service or database. Run time configuration services can be useful for dynamic discovery and to provide a central place to view and control service configuration.

  • To hard-code relative endpoint configuration that is always fixed, while allowing base addresses to be changed in configuration for flexibility. This can be useful if you deploy services to customers and don t want them to have access to non-required configuration settings.

To initialize service endpoints at run time, simply callAddServiceEndpoint on the ServiceHost instance, as shown in Figure 5.

private void ConfigureServiceEndpoints(ServiceHost host){ XmlDictionaryReaderQuotas readerQuotas =  new XmlDictionaryReaderQuotas(); readerQuotas.MaxArrayLength = 200000; readerQuotas.MaxStringContentLength = 200000; BasicHttpBinding basicHttp = new BasicHttpBinding(); basicHttp.MaxReceivedMessageSize=200000; basicHttp.MaxBufferSize = 200000; basicHttp.ReaderQuotas = readerQuotas; WSHttpBinding wsHttpCommon = new WSHttpBinding(); wsHttpCommon.MaxReceivedMessageSize=200000; wsHttpCommon.ReaderQuotas = readerQuotas; wsHttpCommon.Security.Message.ClientCredentialType    = MessageCredentialType.UserName; wsHttpCommon.Security.Message.EstablishSecurityContext   = false; wsHttpCommon.Security.Message.NegotiateServiceCredential   = false; WSHttpBinding wsHttpSession =   new WSHttpBinding(SecurityMode.Message, true); wsHttpSession.MaxReceivedMessageSize = 200000; wsHttpSession.ReaderQuotas = readerQuotas; wsHttpSession.Security.Message.ClientCredentialType   = MessageCredentialType.UserName; wsHttpSession.Security.Message.EstablishSecurityContext   = true; wsHttpSession.Security.Message.NegotiateServiceCredential   = false; host.AddServiceEndpoint(typeof(BusinessServices.IServiceA),   basicHttp, "ServiceA/Soap11"); host.AddServiceEndpoint(typeof(BusinessServices.IServiceA),   wsHttpCommon, "ServiceA/Soap12"); host.AddServiceEndpoint(typeof(BusinessServices.IServiceA),   wsHttpSession, "ServiceA/Soap12Session");}Extending the ServiceHost

Figure 5: Addingservice endpoints at run time.

As explained earlier, when you host with IIS and WAS, the.svc endpoint will invoke a ServiceHostFactory of your choice if you need tocontrol ServiceHost initialization. You can hook the Faulted event, initializeservice behaviors, and initialize endpoints before returning the ServiceHosttype to the runtime. You can t add the error handler, however, until after theServiceHost is opened. To do this in a central place, you would have tocustomize the ServiceHost type. Furthermore, you might want to encapsulate someof the common behavior and endpoint configuration requirements in a ServiceHosttype rather than cluttering the factory.

Figure 7 illustrates a customized ServiceHost type withthe following overrides:

  • ApplyConfiguration supplies code to configure the service behaviors before applying the final configuration settings for the service.

  • InitializeRuntime supplies code to add endpoints before initializing the runtime, and code to add the error handler after the runtime is initialized.

  • OnFaulted is overridden to handle the rare event of a Faulted ServiceHost.

public class ServiceHostEx: ServiceHost{ private Type m_serviceType; public ServiceHostEx(object singletonInstance, params Uri[]   baseAddresses): base(singletonInstance, baseAddresses) { } public ServiceHostEx(Type serviceType, params Uri[]   baseAddresses) : base(serviceType, baseAddresses) {   m_serviceType = serviceType; } protected override void ApplyConfiguration() {   ConfigureServiceBehaviors();   base.ApplyConfiguration(); } protected override void InitializeRuntime() {   ConfigureMetadataEndpoints();   ConfigureServiceEndpoints();   base.InitializeRuntime();   this.AddErrorHandler(false); } protected override void OnFaulted() {   base.OnFaulted(); // TODO: notify administrator } private void ConfigureServiceBehaviors() {...} private void ConfigureMetadataEndpoints() {...} private void ConfigureServiceEndpoints() {...}}

Figure 7:Implementation of ServiceHostEx.

You can construct ServiceHostEx directly if you areself-hosting, but for IIS and WAS you would use the ServiceHostFactory toconstruct the ServiceHostEx instance. Now, all of your customizations of theServiceHost initialization process are encapsulated into the custom type and areavailable to any hosting environment.

 

Conclusion

Customizing the initialization of the ServiceHost at run timecan be useful to your production deployments. Although rare, it is probablyalways a good idea to hook the Faulted event of the ServiceHost instance so youcan report this exception to administrators and react accordingly. More likely,you ll benefit from the ability to enforce certain behavior policies andpossibly configure service endpoints at run time. The sample code for thisarticle includes examples related to this discussion for self-hosting andIIS/WAS. Enjoy!

 

Download the samplesfor this article at http://www.dasblonde.net/downloads/aspprofeb08.zip.

 

MicheleLeroux Bustamante is Chief Architect of IDesign Inc.,Microsoft Regional Director for San Diego,and Microsoft MVP for Connected Systems. At IDesign, Michele provides training,mentoring, and high-end architecture consulting services focusing on Webservices, scalable and secure architecture design for .NET, federated securityscenarios, Web services, interoperability, and globalization architecture. Sheis a member of the International .NET Speakers Association (INETA), a frequentconference presenter, conference chair for SD West, and is frequently publishedin several major technology journals. Michele also is on the board of directorsfor IASA (International Association of Software Architects), and a ProgramAdvisor to UCSD Extension. Her latest book is LearningWCF (O Reilly, 2007); see her book blog at http://www.thatindigogirl.com. Reachher at mailto:[email protected], or visit http://www.idesign.net and her main blog at http://www.dasblonde.net.

Begin Listing One

                                                                                                                                                                                                                                                                                                                                                                         

 

 

Read more about:

Microsoft
Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like