Partager via


Hosting Services with WAS and IIS on Windows Azure

Many developers choose to use service oriented techniques to break large systems into smaller, loosely coupled services. Frequently, each service will be hosted on a different machine and use WS-* protocols for standards-based communication. However there can be times when different hosting and communication approaches make more sense. For example, if you control a service and all its clients, you may be able to get better performance using binary messages sent over TCP. And in some cases you may choose to host multiple services on the same machine and use an optimised local communication mechanism such as named pipes, while maintaining the logical separation between your services.

Microsoft’s application platform has long made it easy to support all of these scenarios with few or no changes to your application code. Windows Communication Foundation (WCF) provides a unified programming model over multiple messaging protocols, while Windows Process Activation Services (WAS) allows you to host your services in IIS and have them “woken up” by incoming messages sent over non-HTTP transports, including TCP and named pipes.

However if you’ve decided to leverage Windows Azure to deploy your applications, it isn’t obvious how you can take advantage of WAS. But the good news is that with a little help from PowerShell, it’s entirely possible to get this working. The key steps that you’ll need to script are:

  • Enable and start the WAS activation service for your chosen protocol
  • Add a binding for your chosen protocol to your IIS web site
  • Enable the chosen protocol on your web sites or applications.

This blog describes a simple application consisting of a single web role containing a web site (the client) and a virtual application hosting a WCF service. Since both the client and the service are hosted on the same machine, we’ll use WCF’s NetNamedPipeBinding for optimised communication, although the same basic approach also works for the NetTcpBinding. (Note that MSMQ is not supported on Windows Azure, so you cannot use those bindings). I’ve included the code for the most important parts of the solution in the post, or you can download the entire sample here.

WCF Configuration

There’s nothing Azure-specific about the WCF configuration, but I’ll show some of it for completeness. I’ve built a simple service and have configured a single endpoint with the NetNamedPipeBinding and a net.pipe:// URL.

web.config (Service):

<system.serviceModel>

  <services>

    <service name="WcfService1.Service1">

      <endpoint binding="netNamedPipeBinding"

                address="net.pipe://localhost/WcfService1/service1.svc"

                contract="WcfService1.IService1"/>

    </service>

  </services>

</system.serviceModel>

The client’s WCF configuration looks much the same, using the matching ABCs (Address, Binding and Contract).

Windows Azure Startup Task

When you want to perform some scripting at the time your Windows Azure instances start, normally you’ll do all of your work in a script defined in the Service Definition file’s <Startup> element. However tasks defined here execute before the fabric has configured IIS, so it turns out this is not a good place to make most of the required changes for WAS. So all we’ll do in the startup task is configure PowerShell so we can run our script later in the startup sequence:

ServiceDefinition.csdef:

<ServiceDefinition name="WasInAzure" xmlns="https://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">

  <WebRole name="WebRole1">

    ...

    < Startup >

      < TaskcommandLine = "setup\startup.cmd"executionContext="elevated" />

    </ Startup >

  </WebRole>

</ServiceDefinition>

Startup.cmd:

powershell -command "set-executionpolicy Unrestricted" >> out.txt

Windows Azure Role OnStart

The OnStart method of your WebRole class executes after IIS has been set up by the fabric, so at this time we’re free to run scripts that make the required configuration changes. The first thing to note is that normally the OnStart method does not run with administrator privileges—we’ll need to change this for our script to work.

ServiceDefinition.csdef:

<ServiceDefinition name="WasInAzure" xmlns="https://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">

  <WebRole name="WebRole1">

    ...

    < RuntimeexecutionContext = "elevated" />

  </WebRole>

</ServiceDefinition>

Now, we can add the code that kicks off our PowerShell script when the role starts:

WebRole.cs:

public class WebRole : RoleEntryPoint

{

    public override bool OnStart()

    {

        var startInfo = new ProcessStartInfo()

        {

            FileName = "powershell.exe",

Arguments = @".\setup\rolestart.ps1",

            RedirectStandardOutput = true,

            UseShellExecute=false,

        };

 

        var writer = new StreamWriter("out.txt");

 

        var process = Process.Start(startInfo);

        process.WaitForExit();

        writer.Write(process.StandardOutput.ReadToEnd());

        writer.Close();

 

        return base.OnStart();

    }

}

Finally, we come to the PowerShell script that starts the listener service and configures the bindings. There are a few things to note about this script:

  • If you’re using TCP instead of Named Pipes, change all references to net.pipe to net.tcp,change the bindings to include your chosen port (e.g. “808:*”). You’ll also need to enable and both the NetTcpPortSharing and NetTcpActivator services.
  • The script makes some assumptions on the name of your role and also updates the protocols on all IIS virtual applications. Depending on how you’ve set up your roles and sites you may need to change this.
  • There are some differences in how you access the IIS PowerShell cmdlets between IIS 7.0 and IIS 7.5. My script works for IIS 7.5, which is included in Windows Server 2008 R2 and the “2.x” family of the Windows Azure OS. To get this version, make sure you set osFamily="2" in the root node of your ServiceConfiguration.cscfg file. Alternatively if you want to use IIS 7.0 (Azure OS “1.x”) you can modify your PowerShell script to make this work.

RoleStart.ps1:

write-host "Begin RoleStart.ps1"

import-module WebAdministration

 

# Starting the net.pipe service

$listenerService = Get-WmiObject win32_service -filter "name='NetPipeActivator'"

$listenerService.ChangeStartMode("Manual")

$listenerService.StartService()

 

# Enable net.pipe bindings

$WebRoleSite = (Get-WebSite "*webrole*").Name

Get-WebApplication -Site $WebRoleSite | Foreach-Object { $site = "IIS:/Sites/$WebRoleSite" + $_.path; Set-ItemProperty $site -Name EnabledProtocols 'http,net.pipe'}

New-ItemProperty "IIS:/Sites/$WebRoleSite" -name bindings -value @{protocol="net.pipe";bindingInformation="*"}

 

write-host "End RoleStart.ps1"

Summary

With the right elements in your Windows Azure Service Definition and Service Configuration files, most common configuration tasks can be achieved without any coding or scripting. However there are some useful capabilities that you may be used to from on-premises Windows installations that are not currently exposed in this way. Fortunately, if you’re familiar with scripting techniques and the Windows Azure startup sequence you can normally code your way around these problems. Windows Process Activation Services is one example of a useful feature that’s not obviously available in Windows Azure, but can be made to work with a little perseverance.