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.

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

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.

WebApplication1_AllFiles

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.
<PropertyGroup>
  <!-- Inject the publication of "secondary" sites into the Windows Azure build/project packaging process. -->
  <CoreBuildDependsOn>
    CleanSecondarySites;
    PublishSecondarySites;
    $(CoreBuildDependsOn)
  </CoreBuildDependsOn>
  <!-- This is the directory within the web application project directory to which the project will be "published" for later packaging by the Azure project. -->
  <SecondarySitePublishDir>azure.publish\</SecondarySitePublishDir>
</PropertyGroup>
<!-- These SecondarySite items represent the collection of sites (other than the web application associated with the role) that need special packaging. -->
<ItemGroup>
  <SecondarySite Include="..\WebApplication1\WebApplication1.csproj" />
  <SecondarySite Include="..\WebApplication2\WebApplication2.csproj" />
</ItemGroup>
<Target Name="CleanSecondarySites">
  <RemoveDir Directories="%(SecondarySite.RootDir)%(Directory)$(SecondarySitePublishDir)" />
</Target>
<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">
<Bindings>
<Binding name="Endpoint1" endpointName="Endpoint2" />
  </Bindings>
</Site>

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.

Pre-Build

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

Post-Build

%WinDir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe "$(ProjectPath)"
/T:PipelinePreDeployCopyAllFilesToOneFolder /P:Configuration=$(ConfigurationName);PreBuildEvent="";PostBuildEvent="";
PackageAsSingleFile=false;_PackageTempDir="$(ProjectDir)..\YOUR-AZURE-PROJECT\Sites\$(ProjectName)"

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\">
<Bindings>
<Binding name="Endpoint1" endpointName="Endpoint2" />
  </Bindings>
</Site>

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.

About these ads
Tagged with: ,
Posted in Windows Azure
26 comments on “Tips for Publishing Multiple Sites in a Web Role
  1. [...] Blog: Tips for publishing multiple sites in a web role by @MichaelCollier (posted Jan. 14) [...]

  2. [...] Blog: Tips for publishing multiple sites in a web role by @MichaelCollier (posted Jan. 14) [...]

  3. [...] web app. No build or config transformations take place. I solved this with a custom build script, but there are other solutions.  The PowerShell build script from below builds the two applications, the startup task dll and [...]

  4. Howdy! It looks as though we both have a interest for the same thing.
    Your blog, “Tips for Publishing Multiple Sites in a Web Role Michael S. Collier’s Blog” and mine are very
    similar. Have you ever thought about authoring a guest post for a
    similar blog? It will unquestionably help gain publicity to your website (my site recieves a lot of targeted traffic).
    If you’re interested, email me at: mitchatwell@t-online.de. Thank you so much

  5. Albert says:

    Excellent article. This addresses an issue I was having with Azure randomly reverting any website publishings (as opposed to azure cloud service publishings). Hopefully the folks over at Microsoft will develop better support around this feature. Hosting multiple websites on a single role is very common.

  6. Matt says:

    Super helpful, thanks Michael. One thing I would add is that when copying/pasting the Post-Build event above (and replacing YOUR-AZURE-PROJECT with your Azure project name), you might get an error when building “Project file not found.”

    This was occurring, because the copy/paste ommitted a crucial space in the MSBUILD.EXE arguments, and it looked like this:

    “$(ProjectPath)”/T:PipelinePreDeployCopyAllFilesToOneFolder

    Ensure you add a space between “$(ProjectPath)” and /T

  7. Matt says:

    One quick question – how do we test this from a staging site?

    Let’s say I’m hosting two sites, called “mysite1.com” and “mysite2.com”.

    Once I deploy my cloud project to Azure, I get a url like: mysites.cloudapp.net

    How do I test the two sites, “mysite1.com” and “mysite2.com”?

    I’ve tried adding the IP of mysites.cloudapp.net to my HOSTS file, along with entries for http://www.mysite1.com and http://www.mysite2.com, but the request eventually times out in IE and I get the “Internet Explorer cannot display the webpage”.

    I’ve even tried it from remote desktoping to the Server 2012 instance in Azure, and changed the host files there, opened IE, and tried to access the sites. Still no luck.

    I’ve confirmed my hosts file matches the hostHeader entries in ServiceDefinition.

    Any thoughts?

    Thanks,
    Matt

  8. Tariq Razzaq says:

    In the first solution, the is missing from the snippet

  9. […] to overcome the issue of secondary sites not publishing I used the solution mentioned in this blog michaelcollier.wordpress.com/2013/01/14/multiple-sites-in-a-web-role/ and it worked fine. Now TFS builds fail with or without this […]

  10. […] to overcome the issue of secondary sites not publishing I used the solution mentioned in this blog michaelcollier.wordpress.com/2013/01/14/multiple-sites-in-a-web-role/ and it worked fine. Now TFS builds fail with or without this […]

  11. Excellent post! We are linking to this great content
    on our website. Keep up the good writing.

  12. [...]to overcome the issue the of secondary sites not publishing i used the solution mentioned in this blog michaelcollier .wordpress.com/2013/01/14/multiple-site-in-a-web-role/ and worked fine.Now TFS builds fail with or without this [.....]

  13. Michael – any thoughts as to why this solution does not work with TFS builds? The solution works great locally, but when I run it through TFS build, the secondary sites don’t appear to be building and/or the paths are all jacked up. C:\a\bin\ServiceDefinition.csdef (0): Cannot find the physical directory ‘C:\Web\MyApp.Web\azure.publish’ for virtual path MyApp.Web/.

  14. Boat says:

    Thank you for the post.

    I change the project file a little bit, so the compile output file will be under WebRole folder like below:

    ..\..\Sites\WebApplication1
    ..\..\Sites\WebApplication2

    CleanSecondarySites;
    PublishSecondarySites;
    $(CoreBuildDependsOn)

    Sites\

    WebApplication1

    WebApplication2

  15. Boat says:

    The config was not formatted correctly above. Try this one:

    I change the project file a little bit, so the compile output file will be under WebRole folder like below:

    ..\..\Sites\WebApplication1
    ..\..\Sites\WebApplication2

    CleanSecondarySites;
    PublishSecondarySites;
    $(CoreBuildDependsOn)

    Sites\

    WebApplication1

    WebApplication2

  16. […] to overcome the issue of secondary sites not publishing I used the solution mentioned in this blog michaelcollier.wordpress.com/2013/01/14/multiple-sites-in-a-web-role/ and it worked fine. Now TFS builds fail with or without this […]

  17. Tom Wilson says:

    @wjchristenson2

    It looks like the compile process is a bit different in TFS build. This probably has to do with the configuration of the Azure Publish Build Definition.

    I was able to get my secondary site to build by creating a dependency from the first site to the second.

    During the TFS build process, the service config file is put in bin, and compiled web projects are put into the directory _PublishedWebsites.

    So you need to update the ServiceDefenition file to to have your physical path be similar to this….

    I’m currently trying to figure out a way to update the servicedefenition file on the fly or update the build process to put the compiled project in the correct location.

  18. plcplc says:

    As a refinement to the selection of project configuration for the secondary site, consider using:

    With this I found that my secondary site was published using the project configuration configured in the solution-level project configuration map.

  19. Car says:

    I used option two and it got stuck in a loop when building, I checked task manager and saw hundreds of msbuild.exe tasks until it crashed my machine. :-( VS2010

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow Michael S. Collier's Blog on WordPress.com
Follow

Get every new post delivered to your Inbox.

Join 1,709 other followers

%d bloggers like this: