IIS 7.0

Extend Your WCF Services Beyond HTTP With WAS

Dominick Baier and Christian Weyer and Steve Maine

Code download available at:WAS2007_09.exe(178 KB)

This article is based on a prerelease version of Windows Server 2008. All information herein is subject to change.

This article discusses:

  • The architectures and process models of IIS 6.0 and IIS 7.0
  • How Web services were hosted in IIS 6.0
  • Hosting robust WCF services with IIS 7.0
  • How Windows Process Activation Service (WAS) works
  • Support for non-HTTP protocols
This article uses the following technologies:
IIS, WAS

Contents

The IIS 6.0 Architecture and Process Model
IIS 7.0 and WAS
Inside WAS
Configuration and Multi-Protocol Addressing
How Listeners Know to Listen
Message-Based Activation over Non-HTTP Protocols
Worker Process Initialization
Getting Data from the Listener to the Worker
Hosting a WCF Service in WAS
Lifetime Management of WAS-Hosted Services
Automating Setup of WAS-hosted Services
Extending WAS

All the talk about service-oriented applications we've been hearing for the past few years has given birth to real frameworks, runtimes, and other bona fide tools for designing, building, and deploying service-oriented connected systems. A good example of this is Windows® Communication Foundation (WCF), which allows you to create services and service consumers in managed code.

One strength of WCF is that you can host WCF-based services in any Windows process, including a console application, a Windows Forms app, or a Windows Presentation Foundation (WPF) UI application. You can even self-host WCF services in long-running Windows NT® services that run in the background working on behalf of a configured identity. WCF services with HTTP-based endpoints can also be hosted inside IIS, much like the traditional Web services as implemented by ASP.NET and ASMX.

If you want to provide robust WCF services through IIS 7.0, it is essential that you understand a new IIS feature called the Windows Process Activation Service (WAS). WAS is a fundamental underlying component of IIS 7.0 that makes it possible to host WCF services beyond HTTP and without having to install the whole IIS package.

Before we dive into WAS and show you how to use it in your own applications, let's take a look at how IIS 6.0 hosting works and why the new process in IIS 7.0 is better.

The IIS 6.0 Architecture and Process Model

Understanding the new process activation mechanism for Windows Vista® and Windows Server® 2008 requires a familiarity with the IIS 6.0 architecture and process model. The architecture for Windows Server 2003 and IIS 6.0 is divided into two basic parts—a listener process and a set of worker processes. The IIS 6.0 listener process is implemented in the w3svc service, which is a long-running Windows NT service activated by the Windows Service Control Manager (SCM). The listener process waits for messages to arrive via HTTP and then dispatches them to the appropriate worker process (w3wp.exe), which hosts the application code that will finally process the request.

When a request arrives on the network, it is processed by the kernel-mode HTTP stack (http.sys) and delivered to the listener process. The w3svc process then looks at the request URI and uses it to map the request to a specific IIS application living inside a particular IIS application pool that is, in turn, hosted inside an instance of the worker process (w3wp), as you see in Figure 1. The mapping between the request URI and the application is based on configuration information stored in the IIS metabase, which can be found in metabase.xml and mbschema.xml, respectively, both of which are stored in the %windir%\system32\inetsrv folder. Once w3svc has determined the destination application, it can look it up in some internal data structures to resolve the destination worker process and application pool.

Figure 1 IIS 6.0 Basic Architecture

Figure 1** IIS 6.0 Basic Architecture **(Click the image for a larger view)

Depending on the state of the targeted IIS server, one of two things can happen at this point. If the worker process and application pool are already running because a previous request has been received for that pool, then no activation is necessary. Therefore, the request is simply dispatched to the waiting worker process. If no worker process exists to handle the current request, the listener process must create a new instance of w3wp before dispatching the request.

The IIS 6.0 worker process is a lightweight executable. When it starts up in response to an activation request, the first thing it does is load a simple unmanaged shim DLL (w3wphost.dll) that enables w3svc to communicate with the worker process. This shim is also responsible for loading aspnet_isapi.dll, which implements the interface between the managed components of ASP.NET and the unmanaged components of IIS.

Aspnet_isapi.dll is responsible for loading the common language runtime (CLR) into the worker process and creating a default application domain, which is where the managed hosting components of ASP.NET will live. These managed hosting components are responsible for creating additional application domains (one per IIS application) on demand and routing requests to them based on the request's URL.

IIS 7.0 and WAS

If you understand the IIS 6.0 process model, you'll understand IIS 7.0 and WAS. All of the major architectural components of IIS 6.0 (listeners, workers, and application managers) are also present in WAS. The difference is that the IIS 7.0 and WAS implementation supports non-HTTP scenarios, which may work well for your service-based application architectures.

Manageability, reliability, and scalability are exactly the same with WAS as they were for IIS 6.0 on Windows Server 2003. But now, all of the great IIS features that address these issues (on-demand activation, process health monitoring, enterprise-class manageability, rapid failure protection) that were available only to HTTP-based apps in IIS 6.0 will now be available to non-HTTP-based applications and services as well.

Inside WAS

In IIS 6.0, the w3svc service really did double duty. It acted as the HTTP listener because it registered with http.sys and was the direct recipient of incoming HTTP traffic. It also owned the process activation components responsible for starting new instances of w3wp and dispatching requests appropriately. In IIS 7.0, these two responsibilities have been refactored into separate Windows NT services. The w3svc process retains its role as the HTTP listener, but the components responsible for configuration and process activation have been factored into WAS, which has three parts: the configuration manager, the process manager, and the unmanaged listener adapter interface.

The configuration manager reads application and application pool configuration from applicationhost.config (the IIS 7.0 replacement for the metabase). The process manager maps application pools to existing worker processes and is the one responsible for spawning new instances of w3wp to host new application pools in response to activation requests. The unmanaged listener adapter interface defines how external listeners communicate activation requests they receive to WAS.

The w3svc service owns the communication with kernel-level http.sys and communicates HTTP activation requests to WAS across the listener adapter interface.

WCF uses the listener adapter interface to communicate activation requests that are received over the supported non-HTTP protocols (namely TCP, Named Pipes, and MSMQ). The WCF plumbing that actually receives requests over non-HTTP protocols is hosted inside of SMSvcHost.exe, which hosts the following four long-running Windows NT services: NetTcpPortSharing, NetTcpActivator, NetPipeActivator, and NetMsmqActivator (see Figure 2).

Figure 2 Basic Architecture of IIS 7.0 and WAS

Figure 2** Basic Architecture of IIS 7.0 and WAS **(Click the image for a larger view)

NetTcpPortSharing is the WCF TCP port sharing service. It implements a centralized TCP listener so that multiple processes can listen on the same TCP port. This service is available even if IIS 7.0 is not installed.

NetTcpActivator is the WCF TCP Activation Service. It communicates TCP activation requests to WAS.

NetPipeActivator is the WCF-named pipe activation service which communicates named pipe activation requests to WAS.

NetMsmqActivator is the WCF MSMQ activation service; it communicates MSMQ activation requests to WAS.

Although these services all live in the same binary, they are separate Windows NT services and can be stopped and started individually to reduce both attack surface and overhead. They are all examples of listener services and all behave in a similar manner. For that reason, we will focus on TCP activation, which can also serve as an example for named pipes and MSMQ. The only difference is the particular type of network resource in use in each case.

Configuration and Multi-Protocol Addressing

In order for an application to be activated by WAS, it first needs to be configured. Each application hosted in WAS has a corresponding configuration entry located in %windir%\system32\inetsrv\config\applicationhost.config. This information includes items like the application pool that will host the application and a URL fragment that uniquely identifies it. We'll present an example of this later. Much like IIS 6.0, each application is associated with both an application pool and a site. Each site has a set of address bindings for the network protocols it supports. For example, an administrator might configure the default site to bind to HTTP on port 80 and TCP on port 7777. This site might host two applications (listening on /Foo and /Bar respectively) that live in separate application pools. In this configuration, the /Foo application would listen on https://myserver.com/Foo as well as net.tcp://myserver.com:2323/Foo, while /Bar would listen on https://myserver.com/Bar and net.tcp://myserver.com:2323/Bar. Regardless of how they were received—since each application pool gets its own worker process—all requests for /Foo would go to worker process 1, while everything destined for /Bar would go to worker process 2. In general, an application's site governs the set of network addresses associated with the application while its application pool governs the worker process instance that will host it. Note that the one worker process per application pool rule only holds true if you're not using Web gardens. In the Web garden case, each application pool maps to its own set of worker processes, up to the maximum number of worker processes specified in the application pool's configuration.

How Listeners Know to Listen

A listener needs to receive messages. For this, it needs to open a socket (or a pipe handle, or start an MSMQ read, and so on). However, in order to receive the proper messages, it needs to obtain the necessary addressing information from WAS. This is accomplished during listener startup. The protocol's listener adapter calls a function on the WAS listener adapter interface and essentially says, "I am now listening on the net.tcp protocol; please use this set of callback functions I'm handing you to tell me what I need to know." In response, WAS will call back with any configuration it has for applications that are set up to accept messages over the protocol in question. For the example above, the TCP listener would be informed that there were two applications (*:7777/Foo and *:7777/Bar) configured to use TCP. WAS also assigns to each application a unique listener channel ID used for associating requests with their destination applications.

The listener process uses the configuration information provided by WAS to build up a routing table, which it will use to map incoming requests to listener channel IDs as they arrive. The mechanics of this mapping is an implementation detail of the underlying protocol the listener is supporting. The important thing is that each listener service must be able to look at an incoming message, say, "Ah—this is destined for listener channel x," and dispatch the request to WAS accordingly. It happens that WCF uses URIs to indicate the destination of messages that arrive over TCP/MSMQ/named pipes, but other protocols could conceivably implement this mapping in whatever way is appropriate for them.

Once the listener service has connected to WAS and received configuration information, it can open its network resource and begin listening for messages. For TCP, this causes NetTcpActivator to trigger a socket to open and an asynchronous call to Socket.Accept to be made, at which point the listener essentially goes to sleep until a message arrives.

Message-Based Activation over Non-HTTP Protocols

The listener process wakes up and starts reading bytes when they arrive at the waiting socket. At this point, the goal is to read just enough information to determine the eventual destination of the forthcoming message and then pause while the listener calls back to WAS and spawns the worker process. For TCP, the destination address is determined by reading information out of the framing protocol that surrounds the SOAP message. Once the listener has the destination URI, it uses that URI as an index into its internal routing table and resolves the URI into the corresponding listener channel ID assigned to that address by WAS.

The listener service then asks WAS to activate a worker process to handle the pending request by calling the unmanaged WebhostOpenListenerChannelInstance method on the listener adapter interface. In addition to other parameters, WebhostOpenListenerChannelInstance takes a listener channel ID as well as a blob of data (represented as a byte array) that will be used to initialize the receiving side of the plumbing in the activated process. This blob is of no interest to WAS as it's merely a convenience that allows each protocol listener implementation to initialize its newly activated components.

When WAS receives the call to WebhostOpenListenerChannelInstance, the process manager part of WAS spawns a new instance of the worker process. The worker process then needs to do some initialization itself before it can process messages received by the listener. During this time the listener may continue to receive data from the network; however it cannot dispatch the message to the worker until it receives notification that worker process initialization is complete.

Worker Process Initialization

There are several important components that are active inside the worker process, as you can see in Figure 3. The architecture of a worker process is shown in Figure 4.

Figure 3 Important Components in the Worker Process

Component Description
Process Host Loads the CLR into the worker process and initializes the default application domain.
Application Manager Creates unique application domains in response to application-level activation requests and manages their lifetime.
Process Protocol Handlers Implement protocol-specific process initialization logic.
Application Domain Protocol Handlers Reside in the activated application domain and perform protocol-specific application domain initialization.

Figure 4 Worker Process Initialization Architecture

Figure 4** Worker Process Initialization Architecture **(Click the image for a larger view)

Since IIS does not implement any managed code components, the job of loading the CLR into the worker process falls to the ASP.NET process host. WAS creates a process host inside the worker process by calling an API exposed by aspnet_isapi.dll. WAS passes this function a callback interface that the managed process host can use for subsequent communication with WAS. The newly created process host is then returned to WAS, allowing for two-way communication between WAS and the worker process.

WAS uses the instance of the process host it obtained from the worker process to start the protocol-specific initialization routines. The activation system StartProcessProtocolListenerChannel on the process host, passing it a protocol key (such as "net.tcp") and a callback interface that the protocol-specific handlers can use to talk back to WAS.

When the process host receives a request to start a new process protocol listener channel inside the worker process, the first thing it does is look up the protocol key in the configuration. Users of WAS are required to register this prior to starting their listeners. For example, WCF registers both a process protocol handler and an application domain protocol handler for net.tcp. The configuration data shown in Figure 5 can be found in the master web.config file in %windir%\Microsoft.NET\Framework\v2.0.50727\CONFIG.

Figure 5 Configuration for net.tcp Protocol Handlers

<system.web> <protocols> <add name="net.tcp" processHandlerType= "System.ServiceModel.WasHosting.TcpProcessProtocolHandler, System.ServiceModel.WasHosting, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" appDomainHandlerType= "System.ServiceModel.WasHosting.TcpAppDomainProtocolHandler, System.ServiceModel.WasHosting, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="false" /> </protocols> </system.web>

Once the process host has resolved the protocol key in configuration, it creates a new instance of the configured process protocol handler type in the default application domain and calls StartListenerChannel on it. This method takes two separate callback interfaces. One allows communication with WAS (it is, in fact, the same WAS callback object as passed to the process host). The other one allows communication with the process host directly (see Figure 6).

Figure 6 Registering Protocol Handlers

Figure 6** Registering Protocol Handlers **(Click the image for a larger view)

A process protocol handler (PPH) has a large degree of control over the hosting model for a particular protocol. For example, the PPH could simply stop at this point in the initialization routine and handle all subsequent requests in the default application domain. However, WCF has chosen to retain the IIS 6.0 application pool model, which activates each application in its own application domain so that each one can be independently monitored and recycled. As such, the WCF PPHs need to collaborate with the ASP.NET application manager, which governs applications and application domain lifetime.

The process host provides the PPH with access to the application manager's functionality through the IAdphManager interface it passes to StartListenerChannel. In order to determine the destination application for the activation request, the PPH calls back to WAS and asks for the data blob that the listener provided when it originally called WebhostOpenListenerChannelInstance. After doing some twiddling on the returned byte array, the WCF PPH has access to the application key that uniquely identifies the destination application (for example, /w3svc/1/Foo) because, by convention, the WCF listener includes the application key in the data blob it sends to the listener channel. WAS places no restrictions on the data passed in the blob—from the WAS perspective, it's merely an opaque byte array. It can then pass the application key, the protocol key, and the listener callback interface to IAdphManager.StartAppDomainProtocolListenerChannel to activate the application's application domain.

In general, the application manager uses the application key as an index into a table of all application domains. When it receives a call to StartAppDomainProtocolListener, it first consults this table to see if the application has already been activated by a previous request that arrived over a different protocol. Finding none, it uses the protocol key to look up the protocol-specific application domain protocol handler type in much the same way as the process host used this key to determine the process protocol handler type. Once the application domain protocol handler type is resolved, the application manager creates a new instance of it in the target application domain. Finally, the application manager calls AppDomainProtocolHandler.StartListenerChannel, again passing the WAS callback interface so the application domain can communicate with WAS if necessary.

Once the application domain protocol handler is instantiated, the internals of the worker process look like Figure 7. At this point, WAS considers the target application to be fully activated and has no further involvement in the process until the application shuts down.

Figure 7 Application Domain Protocol Handler Initialization

Figure 7** Application Domain Protocol Handler Initialization **(Click the image for a larger view)

Getting Data from the Listener to the Worker

Activating worker processes and application domains is the first step towards a larger goal: actually delivering the message to the application. WAS, since it is an activation service and not a message delivery service, does not participate in this activity. Instead, each protocol implementation is free to implement communication between the listener and the worker in whatever way it sees fit. This architecture allows for various protocol-specific optimizations and for each implementation to exploit the nature of the underlying protocol. For example, the WCF MSMQ activation service simply passes the name of the MSMQ queue in the data blob, allowing the activated application to read directly from the data source and bypass the listener completely.

Hosting a WCF Service in WAS

Now let's get our hands dirty and actually host a WCF service in WAS. First we will set up an HTTP-based service, which is not much different from what we'd do in IIS 6.0. Later, we will add support for TCP, named pipes, and MSMQ and take a closer look at the available configuration options (see Figure 8 for the source code of the simple service).

Figure 8 Sample WCF Service

[ServiceContract(Name = "WasDemoServiceContract", Namespace = "urn:msdnmag")] public interface IDemoService { [OperationContract(Name="Ping", Action="Ping", ReplyAction="PingReply")] string Ping(string data); } [ServiceBehavior( Name="WasDemoService", Namespace="urn:msdnmag", InstanceContextMode=InstanceContextMode.PerCall, ConcurrencyMode=ConcurrencyMode.Single)] public class DemoService : IDemoService { public string Ping(string data) { return "pinged with: " + data; } }

The first step is to create a new application in WAS—you can do that either using the IIS 7.0 management tool or with Visual Studio®. The virtual directory has to contain three things: a service (either compiled in the /bin directory or as source code in /App_Code folder), a .svc service endpoint, and a configuration file.

The .svc file makes the connection between a URI and a service implementation (very similar to .asmx files). The @ServiceHost directive inside the .svc file tells the WCF plumbing to create a ServiceHost instance for the type specified in the Service attribute:

<% @ServiceHost Service="DemoService" %>

Configuration information for WAS is stored in a web.config file. The entire WCF configuration takes place in the system.serviceModel section. One thing to note here is that you do not specify a base address when hosting in WAS since that address is determined by the Web site and virtual directory system (see Figure 9 for the basic configuration file). The configured service behavior enables metadata publishing and you can directly point tools like svcutil.exe or the "Add Service Reference" dialog in Visual Studio to the .svc file to generate a client-side proxy.

Figure 9 Basic Configuration for the Sample Service

<configuration> <system.serviceModel> <services> <service name="DemoService" behaviorConfiguration="Behavior"> <endpoint address="" binding="wsHttpBinding" bindingNamespace="urn:msdnmag" contract="IDemoService" /> </service> </services> ... <behaviors> <serviceBehaviors> <behavior name="Behavior"> <serviceMetadata httpGetEnabled="true" /> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>

WCF endpoints that use the HTTP transport traverse the common IIS processing pipeline. If you've worked with ASP.NET Web services in the past this may look very familiar, but internally it works a little bit differently. There are actually two modes of operation for HTTP-based endpoints: ASP.NET compatibility and no compatibility. By default ASP.NET compatibility is turned off, which means that HttpContext.Current is null and that the actual activation of the endpoint does not happen in an HttpHandler but in an HttpModule that hands off the request to the ServiceHost in the ASP.NET PostAuthenticateRequest pipeline event. This has some implications; first of all, if you are used to the ASP.NET extensibility model, you will only receive pipeline notifications up to PostAuthenticateRequest. From there on, processing will jump directly to the EndRequest event. Also, WCF services will not be able to access any ASP.NET host features such as session state. This is intentional. Since the WCF service host has transport-independent equivalents of all these features (sessions, authentication, and so forth), you should not attempt to tie your service to a specific hosting environment.

If you absolutely need ASP.NET compatibility (to share session state between the service and an ASP.NET application hosted in the same application domain, for example), you can turn it on using a configuration switch:

<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />

Furthermore, you have to specifically enable your service for ASP.NET compatibility:

[AspNetCompatibilityRequirements( RequirementsMode=AspNetCompatibilityRequirementsMode.Required)] class DemoService : IDemoService { ... }

These two switches in combination change the way the internal processing of service requests work. Once they are used, the requests will get dispatched to the WCF runtime using an HttpHandler and the whole processing pipeline behaves very much like ASP.NET Web services. Furthermore the current HttpContext is available and can be used as usual. WCF services running in ASP.NET compatibility mode support HTTP endpoints only.

Now that you've seen how to host an HTTP-based service in WAS, let's add non-HTTP endpoints to the service. As explained earlier, the protocol listeners are in charge of opening the actual transport and dispatching the connection to the application domain running the service. These protocol listeners are Windows NT services. When you open services.msc you will find a Net.tcp Listener Adapter as well as listeners for named pipes and MSMQ. These, along with the port sharing service for TCP, have to be up and running for successful non-HTTP activation.

The host name and port configuration for the listener adapters can be found in the WAS configuration file applicationHost.config. You can either edit the settings manually, use the appcmd.exe command-line tool, or use the IIS 7.0 configuration GUI. These settings, called bindings, are done at the site level (see Figure 10). (Note that there are some differences between the versions of WAS that ship with Windows Vista and Windows Server 2008. In Windows Vista, the bindings are not pre-configured and there is currently no GUI to add them graphically. Later you'll see the necessary appcmd.exe commands. On Windows Server 2008 the bindings are configured by default.)

Figure 10 IIS 6.0 Basic Architecture

Figure 10** IIS 6.0 Basic Architecture **(Click the image for a larger view)

Figure 10 IIS 6.0 Basic Architecture

Figure 10** IIS 6.0 Basic Architecture **

<bindings> <binding protocol="https" bindingInformation="*:443:" /> <binding protocol="http" bindingInformation="*:80:" /> <binding protocol="net.tcp" bindingInformation="808:*" /> <binding protocol="net.pipe" bindingInformation="*" /> <binding protocol="net.msmq" bindingInformation="localhost" /> <binding protocol="msmq.formatname" bindingInformation="localhost" /> </bindings>

Each protocol has associated protocol-specific configuration settings in the bindingInformation attribute. For example, bindingInformation for the TCP binding specifies the port as well as on which host name the listener should accept connections (the * specifies that all host names are valid).

The last step is to enable the protocols of choice in the application that hosts the service. As of Windows Server 2008 Beta 3, there is no GUI for this; you have to manually edit applicationHost.config. Later we'll look at how to do this either using a tool or programmatically. Search for the application element in the configuration file and add an enabledProtocols attribute, like so:

<application path="/WasDemo" enabledProtocols="http,net.tcp,net.pipe,net.msmq"> <virtualDirectory path="/" physicalPath="C:\MSDN\WasDemo\web" />

After that you simply have to add additional endpoints to your WCF service configuration to make them reachable over the above protocols:

<endpoint address="" binding="netTcpBinding" bindingNamespace="urn:msdnmag" contract="IDemoService" /> <endpoint address="" binding="netNamedPipeBinding" bindingNamespace="urn:msdnmag" contract="IDemoService" />

When you generate the client proxy from metadata, the configuration file will include a client element for each endpoint. In the client code you can specify which transport to use by passing the endpoint name to the proxy constructor, like this:

WasDemoServiceContractClient proxyTcp = new WasDemoServiceContractClient( "NetTcpBinding_WasDemoServiceContract"); Console.WriteLine(proxyTcp.Ping("Hello"));

Adding an MSMQ endpoint to the service requires some additional steps. First you have to create a queue using the Message Queuing MMC snap-in (or by using any of the available programmatic approaches). Furthermore, you have to set access control lists (ACLs) on the queue to allow the worker process (NETWORK SERVICE, by default) to read and peek from the queue.

It's important to note that the activation of MSMQ endpoints only works correctly if the queue has the same name as the .svc file (minus the machine name). That means that if your service endpoint is /server/app/service.svc, the queue name must be app/service.svc.

In the service configuration you have to specify the name of the queue the service should listen on:

<endpoint address="net.msmq://localhost/private/wasdemo/service.svc" binding="netMsmqBinding" bindingNamespace="urn:msdnmag" contract="IServiceMsmq" />

Non-HTTP endpoints do not pass the IIS processing pipeline and will get routed directly to the WCF runtime. This means that you can't use an HttpModule to pre- or post-process requests. In addition, the Application_Start and Application_End of the HttpApplication class (global.asax) don't fire. So if you want to run startup or cleanup code for such services, you have to use the events of the ServiceHost class. We'll take a closer look at how that works in the next section.

Lifetime Management of WAS-Hosted Services

When you self-host a service (in a Windows NT service, for example) the lifetime of that service is deterministic. It is running after calling Open and it will shut down after calling Close (ignoring catastrophic failures for now). Additionally, Windows NT services usually start at boot time. This is different when hosting in WAS.

First of all, WAS does demand activation (as you know, the "A" in "WAS" stands for activation). This means that the application domain hosting a service only gets created when a request message comes in. Application domains shut down again after a configurable idle period. There are also several reasons why WAS or the ASP.NET runtime may decide to recycle the application domain or even the whole worker process.

First of all, the application pool settings in WAS specify regular recycles of the worker process—by default it is every 29 hours (please note that this prime number was chosen intentionally so that the chance that recycling will actually happen at the same time of day will be minimized).

There are several other reasons why an application domain may recycle. Configuration settings in machine.config, web.config, or applicationHost.config affecting this application domain may have changed. Also the /bin or /App_Code directory or its contents might have been modified, the number of re-compilations (for .aspx, .asmx, .svc, and so on) exceeds the limit specified by the <compilation numRecompilesBeforeAppRestart /> setting in machine.config or web.config (by default this is set to 15), or the physical path of the virtual directory is modified. Finally, the code access security (CAS) policy might have been modified or the application sub-directories might have been deleted. When this happens, you lose all in-memory state including sessions or instance and static variables. That means in essence that out-of-the-box WAS hosting is not something that is really suited for sessionful or singleton services. It is more suitable for stateless per-call services.

The WCF ServiceHost type features two events called Opening and Closing. They are the only proper ways to execute code at service startup and shutdown. The problem is that you have to wire up the handlers for these events between creating a new instance of the ServiceHost and calling Open on it. This is not possible when using the @ServiceHost directive in .svc files as described above. A viable option in this case is to host a custom service host factory. This gives you more control and allows you to handle the aforementioned events.

To do this you have to derive from a class named ServiceHostFactoryBase and implement the CreateServiceHost method. This method receives the service type name and the base addresses from the hosting environment and returns an instance of ServiceHostBase. Now it is your responsibility to create the appropriate ServiceHost, configure it according to your needs, and return it to the WCF runtime. Doing this then allows you to programmatically access the extensibility model of WCF and wire up event handlers (see Figure 11).

Figure 11 Using CreateServiceHost

public class HostFactory : ServiceHostFactoryBase { public override ServiceHostBase CreateServiceHost( string constructorString, Uri[] baseAddresses) { Type service = Type.GetType(constructorString); ServiceHost host = new ServiceHost(service, baseAddresses); // hook up event handlers host.Opening += OnOpening; host.Closing += OnClosing; return host; } }

To make the connection between the already-existing .svc endpoint and the custom factory, you have to add the Factory attribute to the @ServiceHost directive:

<%@ServiceHost Service="Service" Factory="HostFactory" %>

Automating Setup of WAS-hosted Services

As we've discussed, there are several steps necessary to set up a WAS-hosted WCF service:

  1. Setting up the physical directory (and ACLs if necessary).
  2. Setting up the application and virtual directory in WAS.
  3. Configuring the enabled protocols.
  4. Creating the queue and setting ACLs (if MSMQ is used).

WAS offers various interfaces for configuration including Windows Management Instrumentation (WMI), a command-line tool (appcmd.exe), and a brand-new managed API. While WMI and appcmd.exe are ideal for scripting and batch files, the new managed API gives you easy access to configuration for more complex deployment scenarios like MSI packages. See Figure 12 for some appcmd.exe examples.

Figure 12 Using appcmd.exe to Set Up a WAS Application

REM adding bindings to the default web site appcmd.exe set site "Default Web Site" – +bindings.[protocol='net.tcp',bindingInformation='808:*'] appcmd.exe set site "Default Web Site" – +bindings.[protocol='net.pipe',bindingInformation='*'] appcmd.exe set site "Default Web Site" – +bindings.[protocol='net.msmq',bindingInformation='localhost'] REM add a new application appcmd add app /site.name:"Default Web Site" /path:/WasDemo /physicalpath:c:\etc\WasDemo REM enabled protocols for application appcmd.exe set app "Default Web Site/WasDemo" /enabledProtocols:http,net.pipe,net.tcp,net.msmq

The managed configuration API can be found in the new Microsoft.Web.Administration.dll assembly and is centered around the ServerManager class. From there you will find a complete object model covering application pools, worker processes, sites, applications and configuration files. Setting up an application using that API is a matter of a few lines of code, as you can see in Figure 13. Also take a look at Figure 14 for a little helper method that sets up a queue and the minimum ACLs for NETWORK SERVICE account.

Figure 14 Creating Queues and Setting ACLs

private static void SetupMsmq(string queuename) { // create queue – in this case a transactional queue is created MessageQueue queue = MessageQueue.Create(queuename, true); queue.Label = queuename; // set ACL queue.SetPermissions(GetAccountName( WellKnownSidType.NetworkServiceSid), MessageQueueAccessRights.ReceiveMessage); } private static string GetAccountName(WellKnownSidType wellKnownSid) { NTAccount account = (NTAccount) new SecurityIdentifier(wellKnownSid, null).Translate(typeof(NTAccount)); return account.Value; }

Figure 13 Using the Managed Configuration API

private static void SetupWasApp(string virtualPath, string physicalPath, string enabledProtocols) { ServerManager manager = new ServerManager(); // creating the application manager.Sites[0].Applications.Add(virtualPath, physicalPath); manager.CommitChanges(); // setting up the enabled protocols Application wasApp = manager.Sites[0].Applications[virtualPath]; wasApp.EnabledProtocols = enabledProtocols; manager.CommitChanges(); }

Extending WAS

WAS is extensible, meaning you can write your own support for custom WCF transport channels. The Windows SDK contains a sample for a User Datagram Protocol (UDP)-based WAS activation extension, but extending WAS to add your own features is no simple task. It involves implementing custom process and application domain protocol handlers as well as creating code for listening to receive messages on the respective and targeted transport mechanism. An in-depth article on that topic alone would be needed to explain it sufficiently.

There is one very essential point we must mention, however. The maximum request execution limit in WAS has different values depending on the OS in question. For Windows Server 2008 it is unlimited, but for Windows Vista Ultimate and Business it is a 10-connections limit. The home-targeted versions of Windows Vista, namely Windows Vista Home Basic and Home Premium, as well as Windows Vista Starter, have a request execution limit of 3. Keep this in mind as you plan your implementation.

Dominick Baier is the security consultant at thinktecture (thinktecture.com) in Germany. In addition he is the security and WCF curriculum lead at DevelopMentor (develop.com), developer security MVP, and the author of Developing More Secure Microsoft ASP.NET 2.0 Applications (Microsoft Press, 2006). You can find his blog at leastprivilege.com.

Christian Weyer is co-founder and principal architect of thinktecture and has been modeling and implementing distributed applications with Java, COM, DCOM, COM+, Web Services, WCF, and other technologies. Get in touch with him at thinktecture.com.

Steve Maine is a Senior Program Manager on the Connected Framework team at Microsoft. He's currently working on many Web-focused programming model features for the upcoming release of WCF in the .NET Framework 3.5. He blogs at hyperthink.net/blog.