Tips for Publishing Multiple Sites in a Web Role

In November 2010, with SDK 1.3, Microsoft introduced the ability to deploy multiple web applications in a single Windows Azure web role.  This is a great cost savings benefit since you don’t need a new role – essentially a virtual machine – for each web application you want to deploy.

Creating a web role that contains multiple web sites is pretty easy.  Essentially, you need to add multiple <Site> elements to your web role’s ServiceDefinition.csdef file.  Each <Site> element would include a physicalDirectory element that references the location of the web site to be included.

  <Site name="WebRole1" physicalDirectory="..\..\..\WebRole1">
<Binding name="Endpoint1" endpointName="Endpoint1" />
<Site name="WebApplication1" physicalDirectory="..\..\..\WebApplication1\">
<Binding name="Endpoint1" endpointName="Endpoint2" />
<Site name="WebApplication2" physicalDirectory="..\..\..\WebApplication2\">
<Binding name="Endpoint1" endpointName="Endpoint3" />

For additional detailed information on creating a web role with multiple web sites, I suggest following the guidance provided at these excellent resources:

The above resources provide a great starting point.  However, there is a once piece of what I think is important information that is missing.  When Visual Studio and the Windows Azure SDK (via CSPACK) create the cloud deployment package (.cspkg), the content listed at the physicalDirectory location is simply copied into the deployment package.  Meaning, any web applications there are not compiled as part of the process, no .config transformations take place, and any code-behind (.cs) and project (.csproj) files are also copied.


What’s going on? CSPack is the part of the Windows Azure SDK that is responsible for creating the deployment package file (.cspkg).  As CSPack is part of the core Windows Azure SDK, it doesn’t know about Visual Studio projects.  Since it doesn’t know about the Visual Studio projects located at the physicalDirectory location, it can’t do any of the normal Visual Studio build and publish tasks – thus just copying the files from the source physicalDirectory to the destination deployment package.

However, when packaging a single-site web role, CSPack doesn’t rely on the physicalDirectory attribute.  With a single-site web role, the packaging process is able to build, publish, and create the deployment package.

The Workaround

Ideally, each web site should be published prior to packaging in the .cspkg.  Currently there is not a built-in way to do this.  Fortunately we can use MSBuild to automate the build and publish steps.

  1. Open the Windows Azure project’s project file (.ccproj) in an editor.  Since the .ccproj is a MSBuild file, additional data points and build targets can be added here.
  2. Add the following towards the bottom of the .ccproj file.
  <!-- Inject the publication of "secondary" sites into the Windows Azure build/project packaging process. -->
  <!-- This is the directory within the web application project directory to which the project will be "published" for later packaging by the Azure project. -->
<!-- These SecondarySite items represent the collection of sites (other than the web application associated with the role) that need special packaging. -->
  <SecondarySite Include="..\WebApplication1\WebApplication1.csproj" />
  <SecondarySite Include="..\WebApplication2\WebApplication2.csproj" />
<Target Name="CleanSecondarySites">
  <RemoveDir Directories="%(SecondarySite.RootDir)%(Directory)$(SecondarySitePublishDir)" />
<Target Name="PublishSecondarySites" Condition="'$(PackageForComputeEmulator)' == 'true'
                      Or '$(IsExecutingPublishTarget)' == 'true' ">
    Execute the Build (and more importantly the _WPPCopyWebApplication) target to "publish" each secondary web application project.
    Note the setting of the WebProjectOutputDir property; this is where the project will be published to be later picked up by CSPack.
  <MSBuild Projects="%(SecondarySite.Identity)" Targets="Build;_WPPCopyWebApplication" Properties="Configuration=$(Configuration);Platform=$(Platform);WebProjectOutputDir=$(SecondarySitePublishDir)" />

Finally, for each secondary site defined in the .csdef, update the the physicalDirectory attribute to reference the publishing directory, azure.publish\.

<Site name="WebApplication1" physicalDirectory="..\..\..\WebApplication1\azure.publish">
<Binding name="Endpoint1" endpointName="Endpoint2" />

How does this all work?  By adding the CleanSecondarySites and PublishSecondarySites targets to the <CoreBuildDependsOn> element, that is forcing CleanSecondarySites and PublishSecondarySites to happen before any of the default targets included in <CoreBuildDependsOn> (defined in the Microsoft.WindowsAzure.targets file). Thus, the secondary sites are built and published locally before any of the default Windows Azure build targets execute. The IsExecutingPublishTarget condition is needed to ensure PublishSecondarySites happens only when packaging the Windows Azure project (either from Visual Studio or via the command line with MSBuild).

UPDATE (1/15/2013): I’ve added a PackageForComputeEmulator condition to the PublishSecondarySites target. This should ensure the target is also executed when debugging locally via the compute emulator.

I like this approach because it seems like a fairly clean approach and I don’t have to modify build events (keep reading) for each secondary web site I want to include.  I can keep the above build configuration snippet handy and use it in future projects quickly.

Alternative Approach

The above approach relies on modifying the project file to help automate the build and publish of any secondary sites.  An alternative approach would be to leverage build events.  I first learned of this approach from “Joe” in the comments section of Wade Wegner’s blog post.

  1. Select your Windows Azure project, and select Project Dependencies.  Mark the other web apps as project dependencies.build_dependencies
  2. Open the project properties for each web application (not the one used as the Web Role).
  3. For the Build Events, you’ll need to add a pre-build and post-build event.


rmdir "$(ProjectDir)..\YOUR-AZURE-PROJECT\Sites\$(ProjectName)" /S /Q


%WinDir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe "$(ProjectPath)"
/T:PipelinePreDeployCopyAllFilesToOneFolder /P:Configuration=$(ConfigurationName);PreBuildEvent="";PostBuildEvent="";

The pre-build event cleans up any files left around from a previous build.  The post-build event will trigger a local file system Publish action via MSBuild.  The resulting published files going to the “Sites” subdirectory.

Finally, be sure to update the physicalDirectory element in ServiceDefinition.csdef to reference the local publishing subdirectory (notice the ‘Sites’ directory in the updated snippet below).

<Site name="WebApplication1" physicalDirectory="..\..\Sites\WebApplication1\">
<Binding name="Endpoint1" endpointName="Endpoint2" />

When you “Publish” the Windows Azure project, all your web sites will build and publish to a local directory.  All the web sites will have the files you would expect.

Final Helpful Info

When deployed to Windows Azure, the secondary sites referenced in the physicalDirectory attribute are placed in the sitesroot folder of your E or F drive.  The site that is the web role is actually compiled, published and deployed to the approot folder on the E or F drive.  If you want to see this layout locally, first unzip the .cspkg and then unzip the .cssx file (in the extracted .cspkg).  This is the layout that is deployed to Windows Azure.

Special thanks to Paul Yuknewicz and Phil Hoff for their insightful feedback and assistance on this post.