ServiceHost

Regardless of the hosting environment, the service model provides a consistent runtime experience for host initialization, operation calls, and message processing. The ServiceHost type, part of the System.ServiceModel namespace, is the centerpiece of this hosting story. All WCF services must be associated with a ServiceHost instance to be accessible at runtime. Figure 4-1 illustrates the allocation of ServiceHost and associated service types for Windows service and IIS hosting.

Each ServiceHost is always associated with a particular service type

Figure 4-1. Each ServiceHost is always associated with a particular service type

Each ServiceHost instance is initialized with information about the service type, one or more service endpoints, optional base addresses, and behaviors that govern how the service model processes requests to the service. In this section, I’ll explain the mechanics of the ServiceHost.

Initializing the ServiceHost

Example 4-1 illustrates a simple example of a console host application initializing the ServiceHost programmatically. In fact, this is the entire listing for the host application. When the application is launched, the ServiceHost is constructed with the service type, HelloIndigo.HelloIndigoService. A single endpoint is created, exposing its operations from the HelloIndigo.IHelloIndigoService service contract over NetTcpBinding. Recall from the introduction to these concepts in Chapter 1 that when a complete URI is provided for the endpoint address, you needn’t provide any base addresses to the ServiceHost constructor.

Example 4-1. Initializing the ServiceHost programmatically

static void Main(string[] args)
{
  using (ServiceHost host = new
ServiceHost(typeof(HelloIndigo.HelloIndigoService)))
  {
    host.AddServiceEndpoint(typeof(HelloIndigo.IHelloIndigoService)
, new NetTcpBinding( ), "net.tcp://localhost:9000/HelloIndigo");
    host.Open( );

    Console.WriteLine("Press <Enter> to terminate the Host
application.");
    Console.WriteLine( );
    Console.ReadLine( );
  }
}

The Open( ) method initializes the ServiceHost instance and begins listening for messages at each configured endpoint. Even though the Console.ReadLine( ) statement blocks the console application to keep the process alive, incoming requests are processed on their own threads taken from the thread pool. When the console is closed, the using statement shown in Example 4-1 disposes of the ServiceHost instance calling its Close( ) method.

Tip

In this example, only one service is exposed by the host application. To expose multiple services, multiple ServiceHost instances can be opened within the same host process as illustrated by the sample in the directory <YourLearningWCFPath>\Samples\Hosting\MultipleServiceHost.

Declarative Configuration

The ServiceHost will normally be initialized declaratively via application configuration. The <system.serviceModel> section may have one or more services described in the <services> section. Here is an example of declarative service configuration:

<service name="HelloIndigo.HelloIndigoService"
behaviorConfiguration="serviceBehavior">

  <endpoint address=
"net.tcp://localhost:9000/HelloIndigoService" binding="netTcpBinding"
contract="HelloIndigo.IHelloIndigoService" />
</service>

As with programmatic initialization, you must specify the service type and one or more endpoints (explained in Chapter 1). This example illustrates how to configure a single endpoint similar to the code in Example 4-1.

When the ServiceHost instance is constructed, it looks for a <service> section matching its service type, and initializes itself from those settings. Initializing the ServiceHost declaratively removes hardcoded base addresses and endpoints from the code, as shown in Example 4-2.

Example 4-2. Initializing the ServiceHost declaratively

using (ServiceHost host = new ServiceHost(typeof(HelloIndigo.HelloIndigoService)))
{
  host.Open( );

  Console.WriteLine("Press <Enter> to terminate the Host application.");
  Console.WriteLine( );
  Console.ReadLine( );
}

Tip

Managing deployments to staging, test, and production servers throughout the development cycle is much easier when you use declarative configuration. Deployment scripts can be used to modify endpoint addresses and port assignments to automate the process. The downside can be that any text editor may be used to modify configuration settings, which makes it difficult to prevent unwanted changes.

Base Addresses

If you specify a fully qualified URI for each service endpoint, a base address is not required to initialize the ServiceHost. If you provide base addresses to the ServiceHost constructor, you can optionally provide a relative URI to the endpoint address, as shown in Example 4-3.

Example 4-3. Initializing the ServiceHost with a base address and relative endpoint

using (ServiceHost host = new ServiceHost(typeof(HelloIndigo.HelloIndigoService),new Uri("net.tcp://localhost:9000")))
{
  host.AddServiceEndpoint(typeof(HelloIndigo.IHelloIndigoService),
new NetTcpBinding( ), "HelloIndigo");
  host.Open( );

  Console.WriteLine("Press <Enter> to terminate the Host application.");
  Console.WriteLine( );
  Console.ReadLine( );
}

Base addresses and relative endpoints can also be provided declaratively in the <host> section of the service configuration, as shown in Example 4-4.

Example 4-4. Declarative configuration of base addresses and relative endpoints

<service name="HelloIndigo.HelloIndigoService"
behaviorConfiguration="serviceBehavior">
  <host>
    <baseAddresses>
      <add baseAddress="http://localhost:8000"/>
      <add baseAddress="net.tcp://localhost:9000"/>
    </baseAddresses>
  </host>
  <endpoint binding="netTcpBinding" contract="HelloIndigo.IHelloIndigoService" />
  <endpoint  address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>

Endpoints without a complete URI will be addressed relative to the base address matching their binding protocol. For example, the NetTcpBinding endpoint shown in Example 4-4 uses the net.tcp base address, while the metadata exchange endpoint relies on the http base address. If you omit the address altogether from the <endpoint> configuration, the base address for the endpoint’s protocol is assumed to be the endpoint address.

Tip

You should use base addresses and relative endpoints whenever possible so that only the base address need be modified when services are deployed to multiple machines.

ServiceHost and ServiceDescription

Chapter 2 explains the concept of a service description, which incorporates information about the service, its endpoints, and any behaviors that are attached to the service. The service description is represented by the ServiceDescription type, which is ultimately used to generate the WSDL document for the service and to support interactive metadata exchange (discussed in Chapter 1 and Chapter 2).

The ServiceHost is responsible for generating this service description for its service. ServiceHost has a Description property that can be used to access the ServiceDescription instance. The instance is generated when the Open( ) method is called, at which time the description is generated by using reflection on the service type, its contracts, and relevant service behaviors.

Under most circumstances, you will not interact directly with the process of generating the ServiceDescription. For advanced scenarios, however, you can control how this ServiceDescription is generated by overriding the CreateDescription( ) method in a class that extends the ServiceHost.

Closing the ServiceHost

Once you have initialized and opened the ServiceHost instance, channel listeners are created for each endpoint to process requests to each endpoint (discussed in Chapter 2). If the host process is shut down, each of its ServiceHost instances will be closed either explicitly with a call to its Close( ) or Dispose( ) method, or implicitly by the service model when the type is disposed. While the ServiceHost is closing, new requests to that particular ServiceHost instance are rejected, but any requests that are already in queue will be completed. Although a rare occurrence, it is possible for the ServiceHost to be put into a faulted state. For example, if there are errors in the service model configuration. If the ServiceHost State property is set to CommunicationState.Faulted, when Close() is called an exception will be thrown. For this reason, it is best to check the State property of the ServiceHost instance and call Abort() if it is in a faulted state. This technique was illustrated in Chapter 1.

Communication Events

ServiceHost inherits ServiceHostBase, which inherits CommunicationObject. CommunicationObject is a common base type for many objects participating in the communication channel, providing a consistent state machine. This type exposes events including Opening, Opened, Closing, Closed, and Faulted. To be notified of state changes in the channel stack for each hosted service, you can hook these communication events.

Closing and Faulted can be particularly useful for writing event logs or notifying administrators when the communication channel for your service is closing or has encountered a problem. Just add these event handlers to the ServiceHost instance before you open the communication channel, as shown here:

using (ServiceHost host = new ServiceHost(typeof(HelloIndigo.HelloIndigoService)))
{
  host.Closed += new EventHandler(host_Closed);
  host.Closing += new EventHandler(host_Closing);
  host.Faulted += new EventHandler(host_Faulted);
  host.Open( );

  // other code
}

The ServiceHost also has a State property based on the CommunicationState enumeration. You can use this property to detect the following states: Created, Opening, Opened, Closing, Closed, or Faulted.

Warning

Client-side proxies also inherit CommunicationObject. After each call, clients should check the proxy’s state to ensure that the communication channel has not faulted.

Get Learning WCF now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.