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
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
.
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.
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.
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.
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
.
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.
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 the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.