Fri, 08 Oct 2010

Deploying WebMatrix Applications to Windows Azure

In this week’s Cloud Cover episode, Ryan and I deploy an application to Windows Azure that was built using the new WebMatrix beta. For the most part, the process is what I described in my last post, “Building, Running, and Packaging Windows Azure Applications From the Command Line.” In this post, I’ll describe the few extra things I needed to do to get things to work.

For those who did watch the show, you’ll remember that I didn’t have a working example after upgrading to WebMatrix Beta 2, and I wasn’t sure what was broken. It turned out to simply be a mistake on my part. I had left an old binary (from the Beta 1 bits) in the bin folder of my application, and that mismatch was what was breaking the application. The steps I’ll describe below do work for both Beta 1 and Beta 2 applications.

Adding the right assemblies to the bin folder

One common cause of deployment errors in Windows Azure is a mismatch between the .NET assemblies in the Global Assembly Cache (GAC) in your development environment versus the assemblies in Windows Azure. When you develop with Visual Studio, you need to remember to mark any assembly as “Copy Local” if it’s not part of a standard .NET installation (like what we have in the cloud).

When building an application outside of Visual Studio, such as with the WebMatrix tools, you’ll need to make sure that the bin folder of your application contains those assemblies you would have marked “Copy Local” in Visual Studio.

I couldn’t find a list of assemblies I needed for WebMatrix applications, so I did something creative to find out. Once I had my application built, I used WebMatrix’s publish feature to publish it to my local machine. The two options for publishing are Web Deploy and FTP Publishing. I used the latter against an FTP server running on localhost.

Looking at what was uploaded by the FTP process, I found the following assemblies in the bin folder:

  • AdminPackage.dll
  • Microsoft.Web.Infrastructure.dll
  • Microsoft.WindowsAzure.StorageClient.dll (this one was already there before the publish, because I added it when I used Windows Azure blobs in the application)
  • NuPack.Core.dll
  • System.Web.Helpers.dll
  • System.Web.Razor.dll
  • System.Web.WebPages.Deployment.dll
  • System.Web.WebPages.dll
  • System.Web.WebPages.Razor.dll
  • WebMatrix.Data.dll

To that list, I also added System.Web.Mvc.dll, which it seems WebMatrix relies on but doesn’t publish with your application.

Making sure ASP.NET handles all URLs

Per the instructions in the WebMatrix Beta 2 Release Readme, I created a web.config file to make sure that ASP.NET saw all web requsets (and not just those to known extensions like .aspx). While I was there, I also made index.cshtml the default document. Here’s the web.config I ended up with:


<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <defaultDocument enabled="true">
        <files>
                <clear />
                <add value="index.cshtml" />
            </files>
        </defaultDocument>
        <modules runAllManagedModulesForAllRequests="true" />
    </system.webServer>
</configuration>

Using the .NET 4 runtime

By default, Windows Azure runs your application using .NET 3.5 SP1 (which was the version available when Windows Azure was first released). To use a different runtime, you need to add a parameter when creating your application package. Visual Studio does this for you automatically if your application targets .NET 4, but when you’re packaging using the command line tools, you need to do this yourself.

Because the WebMatrix libraries require .NET 4, I needed to be careful with how I packaged the application to make sure I got the right runtime version. The way you specify that you want the .NET 4 runtime is by creating a file that contains the string TargetFrameworkVersion=v4.0 and using the rolePropertiesFile parameter to tell cspack to apply that setting to your role. My final cspack command looked like this:

cspack ServiceDefinition.csdef /role:helloworld;helloworld /rolePropertiesFile:helloworld;roleproperties.txt
/out:HelloRazor.cspkg

Packaging and deploying to the cloud

With a correct bin folder and web.config, I was able to deploy to the cloud using the SDK command line tools as described in my previous post.

Bonus: a utility to copy assemblies from the GAC

Now that I know what assemblies are required, I’d rather not have to publish the application locally each time to get the bin folder right, so I wrote a small application to fetch assemblies from the GAC and copy them locally for me. Just compile the following code and run it with the target directory (usually bin) and a list of assemblies:

using System;
using System.Reflection;
using System.Linq;
using System.IO;

public class CopyAssembly
{
    public static void Main(string[] argv)
    {
        if (argv.Length < 2)
        {
            Console.WriteLine("Usage: CopyAssembly <target-directory> <assembly-name-1> <assembly-name-2> ...");
            Console.WriteLine("CopyAssembly will attempt to load each of the assemblies (from the GAC or");
            Console.WriteLine("  elsewhere) and copy them to the target directory.  It will not overwrite");
            Console.WriteLine("  existing files.");
            return;
        }

        Directory.CreateDirectory(argv[0]);

        foreach (var assemblyName in argv.Skip(1))
        {
            var assembly = Assembly.LoadWithPartialName(assemblyName);
            if (assembly != null)
            {
                var fileInfo = new FileInfo(assembly.Location);
                try
                {
                    File.Copy(assembly.Location,
                        Path.Combine(argv[0], fileInfo.Directory.GetFiles(fileInfo.Name)[0].Name));
                    Console.WriteLine("Found and copied {0}.", assemblyName);
                }
                catch (IOException e)
                {
                    Console.WriteLine(e.Message);
                }
            }
            else
            {
                Console.WriteLine("Unable to locate assembly {0}.", assemblyName);
            }
        }
    }
}

More to come

You can probably tell from this week’s Cloud Cover episode that I’m quite impressed with WebMatrix and the new Razor syntax for ASP.NET Web Pages. I’m sure I’ll be building some demos using these new technologies, along with ASP.NET MVC 3 (which was just released in beta form along with WebMatrix Beta 2).

Let me know what specific topics you’d like to see covered that integrate WebMatrix and Windows Azure. My contact information is on the right side of my blog, so send me an email or a tweet.