Using NuGet and WebActivator to Deliver HTTP Modules
Published 12 Nov 2011
Matt Wrock posted a code sample showing how to use the PreApplicationStartMethod (PASM) attribute and DynamicModuleUtility.RegisterModule to deliver HTTP modules easily, without requiring the consuming developing to modify the target app’s web.config. It’s a great technique, and one I’ve used extensively as well, particularly with libraries I distribute via NuGet packages. Matt had one concern though: discovery. He was worried that the consuming developer might have questions about why a behavior is happening (for instance, if the module is significantly changing the app’s rendered markup), but not find any trace of why (becuase the module registration is tucked away in the library’s PASM. It’s a reasonable concern, at least for some heavy-hitting modules and especially if the auto-registered module is just one component of the library (although, I’d caution against that practice). Fortunately, with NuGet and our own David Ebbo’s WebActivator package, there’s a better way.
So, we have two goals. We want the the HTTP module to work after doing nothing more than installing a NuGet package, and we want the consuming developer to easily discover the module’s registration. Like PASM, the WebActivator NuGet package allows you to run code before an app starts. (It also allows you to run code after it starts, and when it shuts down.) The advantage it has over PASM is, as a NuGet package, you can make it dependency of your own package, which means it will be installed into the target app. This, in turn, means you can move the start-up code out of your library and into the target app in a source code file, where the consuming developer can easily discover it. (There are lots of other reasons you might want to use WebActivator, as well, but I’ll stay focused to the matter at hand.)
Using WebActivator to “bootstrap” your library, whether to register an HTTP module or to register MVC routes, is so common we have some guidance and conventions around how to do it.
First, create the folder .\Content\App_Start in your NuGet package. This is where your library’s start-up code is going to go. (I’ll assume you already know how to create a NuGet package, and understand how the Content folder is used; if not, you might want to read more about creating and publishing NuGet packages). Unlike other App_ folders, this one isn’t a special ASP.NET folder. But, by following this convention, consuming developers will have some idea where to look to find your library’s start-up code. Even if they aren’t aware of the convention, the descrptive name will aid discovery.
Second, add the dependency on WebActivator to your package’s .nuspec file:
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>MySuperPackage</id>
etc...
<dependencies>
<dependency id="WebActivator" version="1.5" />
</dependencies>
</metadata>
</package>
Finally, add a source code file named <library>.cs.pp to the App_Start folder, where <library> is the name of your library. The .pp is important, because NuGet is going to transform this file, which will allow you to include the target project’s root namespace (while not critically important, that’s the Visual Studio convention, and some IDE tools get ornery when it’s not followed). To this file, add a WebActivator.PreApplicationStartMethod pointing to a start-up method, and add your start-up code to the start-up method.
When the consuming develper installs the package, the source file will be created in ~/App_Start, and it will behave just as it would have in your library’s PASM. Because it’s in a source code file, it’ll be easy for the consuming developer to find, and they can even quickly comment out if they need to, for instance to debug an issue. (This technique is also great for general configuration of your library, especially if, like me, you think forcing peope to configure apps in XML is rude.)
Here’s a complete example (using code from Matt’s sample):
using System;
using MyLibrary;
[assembly: WebActivator.PreApplicationStartMethod(typeof($rootnamespace$.App_Start.MyLibrary.Loader), "LoadModule")]
namespace $rootnamespace$.App_Start.MyLibrary
{
public class Loader
{
public static void LoadModule()
{
DynamicModuleUtility.RegisterModule(typeof(MyDynamicModule));
}
}
}
References