Tue, 20 Apr 2010

Build Your Own Web Role: Running Hosted Web Core in Windows Azure

In this post, I’ll show how you can run your own instance of Hosted Web Core (HWC) in Windows Azure. This will give you the basic functionality of a web role, but with the flexibility to tweak advanced IIS settings, run multiple applications and virtual directories, serve web content off of a Windows Azure Drive, and more.

If you want to try this out yourself, you can download the full Visual Studio 2010 solution from Code Gallery.

The code I share in this blog post will work today, but I should point out that Windows Azure will provide the full power of IIS in the future, without the need for you to manage your own Hosted Web Core process.

I should also point out that by running HWC yourself, you’re taking responsibility for a lot of things that are completely abstracted and automated away in the existing Windows Azure web role. Use this code only if you’re comfortable managing your own IIS configuration files and debugging HWC errors.

What is Hosted Web Core?

IIS Hosted Web Core is what Windows Azure uses to power web roles. It runs web applications using the same pipeline as IIS, but with a few restrictions. Specifically, Hosted Web Core can only run a single application pool and a single process. It also does not include the Windows Process Activation Service (WAS). If you’re interested in learning more about using Hosted Web Core, I recommend blog posts by CarlosAg and Kanwaljeet Singla, in addition to the MSDN documentation.

The Basic Technique

As I discussed in my post “Using Other Web Servers on Windows Azure,” the flexibility of worker roles with endpoints means you can run all sorts of servers in Windows Azure. The technique I described there has been used to run Apache, Tomcat, Jetty, Mongrel, WEBrick, SMTP servers, FTP servers, and more. Running Hosted Web Core is no different, but because it’s already installed on Windows Azure VMs, there’s no need to upload it with your package.

The only difficult part is the HWC configuration, some of which needs to be done at runtime.

Constructing applicationHost.config and web.config

To launch Hosted Web Core, we need to pass two paths to WebCoreActivate: the path to applicationHost.config and the path to the root web.config. Many of the settings in these configuration files are fixed, but some will need to change based on information available at runtime. For example, the port I need to bind to and the location of my web content won’t be known until runtime.

I decided to implement a very simple parameterization scheme. Throughout applicationHost.config and web.config, I used parameters like {approot}, which get replaced by simple string substitution at runtime, based on a NameValueCollection of parameters and values.

Below is the code that writes out the parameterized configuration files. This code runs in OnStart():

var parameters = new NameValueCollection();
// "DiagnosticStore" is automatically inserted and is where
// the diagnostic monitor will look for these
var diagnosticStore =
  RoleEnvironment.GetLocalResource("DiagnosticStore")
  .RootPath;
parameters["LogFilesDirectory"] =
  Path.Combine(diagnosticStore, "LogFiles");
parameters["FailedReqLogFilesDirectory"] =
  Path.Combine(diagnosticStore, "FailedReqLogFiles");
parameters["iisCompressionCacheDirectory"] =
  RoleEnvironment.GetLocalResource("iisCompressionCache")
  .RootPath;
var approot =
  Environment.GetEnvironmentVariable("RoleRoot")
  + @"\approot";
parameters["approot"] = approot;
var endpoint =
  RoleEnvironment.CurrentRoleInstance
  .InstanceEndpoints["HttpIn"].IPEndpoint;
parameters["address"] = endpoint.Address.ToString();
parameters["port"] = endpoint.Port.ToString();
parameters["aspNetTempDirectory"] =
  RoleEnvironment.GetLocalResource("aspNetTemp")
  .RootPath;
parameters["applicationPoolName"] =
  "{" + Guid.NewGuid().ToString() + "}";
parameters["machineKeyElement"] = RoleEnvironment
  .GetConfigurationSettingValue("machineKeyElement");

string configPath = RoleEnvironment
  .GetLocalResource("Config").RootPath;

// substitutes in the above parameters
HWCConfig.WriteConfigFile(
    parameters,
    Path.Combine(approot, "applicationHost.config"),
    Path.Combine(configPath, "applicationHost.config"));

// substitutes in the above parameters
HWCConfig.WriteConfigFile(
    parameters,
    Path.Combine(approot, "web.config"),
    Path.Combine(configPath, "web.config"));

Making sure you have a working applicationHost.config and web.config is probably the hardest part of building this HWC worker role. As a starting point for my configuration files, I took the actual files from a web role. (I wrote some code to hunt around on the VM’s local storage and found the ones in use.) From there, all I did was fix some hardcoded paths (substituted %windir% for d:\windows in a number of places) and comment out a couple lines that initialize the Windows Azure runtime. (This has already done by the time my code runs in the worker role.)

Once the configuration files are written, we simply need to launch Hosted Web Core. This involves a little bit of P/Invoke, since HWC is a native DLL. Read HWCPInterop.cs in the sample code for the details.

Using the Code

To use the sample code, you need to do only two things:

  1. Put your web content under the Websites directory in the worker role project. You may have to unload the worker role project and reload it again to pick up the changes. (See the “Tip of the Week” at the end of Cloud Cover Episode 3 for why that is.)
  2. Fill in the machineKeyElement configuration setting in ServiceConfiguration.cscfg (or just double-click the role in Visual Studio). This is necessary if you’re going to be using ASP.NET Web Forms to avoid view state validation errors. The value should be a complete XML element like the one you can get via the “ASP.NET machineKey Generator” tool. Visual Studio will properly escape the XML for you, but if you edit the configuration file by hand, be sure to escape the quotes and angle brackets.

Where Are We Now?

With the full sample code from Code Gallery, you should be able to mimic the functionality of a web role with no real difference. The whole point of this exercise, though, is to enable some additional scenarios. In future blog posts, I’ll explain how to use this code to do the following:

Download the Code

You can download the full Visual Studio 2010 solution from Code Gallery.