Light up your NuGets with startup code and WebActivator


[Please see the WebActivator wiki for the latest docs]

Wow, it’s hard to believe that it’s been less than a week since NuGet went public.  We were hoping to get noticed, but the attention we got was way beyond my wildest expectations!  The buzz on Twitter has just been phenomenal, and for the most part the feedback has been very positive.  Thank you all for that, this is very encouraging for our little NuGet team 🙂

 

Going beyond assembly references

 

The most common type of NuGet packages bring in assembly references into a project.  It’s super useful, because it saves you from finding it, downloading it, adding the reference, getting dependencies and dealing with versioning.  If NuGet didn’t do any more than that, I think it could be very successful, because that is such a core step that gets repeated manually so many times by so many people.

But it can in fact do a lot more than that!  The first thing that you probably saw already is the config transform support.  I demoed that in my earlier screencast and various people blogged it, with the ELMAH package adding a bunch of things to web.config to make things work.

But not everything that configures libraries goes through web.config.  Thankfully, many libraries come with a code driven way of setting things up (I personally much prefer that over config).  A good example is Louis DeJardin’s Spark View Engine.  In order to use it in an ASP.NET MVC application, you need to create a SparkSettings instance, configure it, feed it to a SparkViewFactory, and then register it as a View Engine with MVC.  Pretty painful steps if you’ve not done it before and just want to get started with Spark.  See Step 5 in this page for an example of what I’m referring to.

So what can NuGet do to make your life easier in these scenarios?  Quite simply, NuGets can include some source code that ends up as part of the project.  For instance, if you install the SparkMvc package, you’ll notice a file named SparkMvc.cs under the App_Start folder, which contains this set up code.  How did the package pull that trick?  Very easy: all it needs to do is include a file named SparkMvc.cs.pp in its Content\App_Start folder.  The reason it has an extra .pp extension is to indicate that it should be preprocessed, which allows it to end up in the correct namespace.  SparkMvc.cs.pp contains (omitting one magic line that I’ll discuss later!):

using System.Web.Mvc;
using Spark;
using Spark.Web.Mvc;

// Secret line goes here! 🙂

namespace $rootnamespace$.App_Start {
    public static class SparkMvc {
        public static void Start() {
            var settings = new SparkSettings();

            // comment this if you want to use Html helpers with the ${} syntax:
            // otherwise you would need to use the <%= %> syntax with anything that outputs html code
            settings.SetAutomaticEncoding(true); 

            settings
                .AddNamespace("System")
                .AddNamespace("System.Collections.Generic")
                .AddNamespace("System.Linq")
                .AddNamespace("System.Web.Mvc")
                .AddNamespace("System.Web.Mvc.Html");
                // Add here more namespaces to your model classes
                // or whatever classes you need to use in the views

            settings
                .AddAssembly("Spark.Web.Mvc")
                .AddAssembly("System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")
                .AddAssembly("System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");

            ViewEngines.Engines.Add(new SparkViewFactory(settings));
        }
    }
}

So the only funny business here is the $rootnamespace$ token, which gets replaced by the namespace of the target project (as in Item Templates).  Other than that, it’s just a static method that contains all the Spark set up logic, which you can of course freely modify once it’s in your app.  It’s just starter code to get you on the right path.

Finally, you’d need to go in your global.asax’s Application_Start and add a line to call this static method.  That works, but that’s kind of a lame manual step that you always have to take after installing the package.  But wait, maybe you don’t have to!

 

Enter WebActivator

 

Luckily, there is a very simple way to avoid forcing the package’s user to manually call the startup code from global.asax.  And this mechanism is not provided by the NuGet engine itself, but comes simply as a result of taking a dependency on a little NuGet called WebActivator.  If you look for at the .nuspec manifest file for SparkMvc, you’ll see something like:

  <dependencies>
    <dependency id="spark" minversion="1.0" />
    <dependency id="WebActivator" version="1.0.0.0" />
  </dependencies>

Note that in addition to having a dependency on Spark core, it has a dependency on the WebActivator NuGet.  So what exactly does that do?  It lets you write the secret magic line that I omitted in the code above:

using System.Web.Mvc;
using Spark;
using Spark.Web.Mvc;

[assembly: WebActivator.PreApplicationStartMethod(typeof($rootnamespace$.App_Start.SparkMvc), "Start")]
namespace $rootnamespace$.App_Start { public static class SparkMvc { public static void Start() { // etc...

 

So WebActivator lets you define a PreApplicationStartMethod attribute which is used to mark your method as something that gets called when the Web app starts.  And that’s it!  Now when you install the SparkMvc NuGet in your app, Spark is completely ready to go, and you just need to add your spark views.

Note: the sources for WebActivator are on bitbucket.

 

Hey, this attribute looks familiar!

 

If the PreApplicationStartMethod attribute looks familiar, it’s because ASP.NET 4.0 actually comes with one named exactly the same, and that does the exact same thing (Phil Haack did a nice post on it).  So why the heck aren’t we using it instead of coming up with a new one named the same and that does the same thing?!  The reason is that we were short sighted when we designed this feature in ASP.NET, and only allowed this attribute to be used once per assembly.  This works fine for scenarios where you own the whole assembly, but fails terribly for our NuGet scenario where we want unrelated packages to each come with their startup logic.  ASP.NET fail!  Wait, I was involved in that feature 🙁

So the idea is that we can use WebActivator for now, and then we’ll make the ASP.NET attribute more flexible in a future release, at which point WebActivator will not be needed as a separate thing (that’s just a tentative plan, it’s not settled yet).  But until then, WebActivator does the job nicely, and using it is as easy as adding one dependency line in your .nuspec file, so we’re not in a bad place. 🙂

 

Update (2/15/2011): new switch to have method be called after global.asax App_Start

 

In some scenarios, the pre app start method gets called too early, as it happens before global.asax. In WebActivator 1.3, I added support for a new attribute that makes the method be called after global.asax’s Application_Start. To use this, simply use PostApplicationStartMethod instead of PreApplicationStartMethod, e.g.

 

[assembly: WebActivator.PostApplicationStartMethod(typeof(TestLibrary.MyStartupCode), "CallMeAfterAppStart")]

 

What this does is register a module (using this technique), and then call your method the first time an instance of the module’s Init is called. Note that your method will only be called once even though the module can get instantiated multiple times. So the fact that it uses a module should mostly just be an implementation detail. What matters is that you method will get called later than Application_Start (but still very early in the appdomain start up sequence).

 

Other NuGets that use WebActivator

 

Here is the current list of NuGets that make use of this mechanism:

  • Spark.Web.Mvc3, as discussed here
  • Ninject.MVC3 uses it to register the Ninject DI Container with ASP.NET MVC (new MVC3 feature, see Brad Wilson’s post!)
  • EFCodeFirst.SqlServerCompact uses it to make the EF CTP4 bits use SQLCE 4
  • 51Degrees.mobi
  • AddMvc3ToWebForms
  • AttributeRouting
  • Combres
  • DataAnnotationsExtensions.MVC3
  • entile-server
  • FubuMVC
  • MefContrib.MVC3
  • NuGet.Server
  • RestfulRouting
  • SimpleMembership.Mvc3
  • StructureMap-MVC3
  • VirtualPathTemplates

And I’m sure more will come!  Pretty much any NuGet that needs to execute startup code should make use of this.

 

Feedback welcome

 

WebActivator is still very new, and I’d love to hear feedback about it from NuGet users.  You can leave a comment here, or start a discussion on http://nuget.codeplex.com/.  The latter is probably best as it makes it easier to have a discussion among many folks.

Comments (30)

  1. Mike says:

    Typical Microsoft, great technology, terrible name… keep up the great work, and let's hope the marketing droids come up with a normal name for this one…

  2. Mike says:

    I'm not sure that getting code into my project is such a nice feature.

    And the assembly attribute is indeed fail, very unlike Microsoft. QA missed this? And now we have to live with this? I hope ASP.NET is not getting 'messy'

  3. Paul says:

    It is all good about ASP.NET. What it is missing is good user interface controls. Look at Oracle Application framework and other frameworks, they all have good UI controls mainly a very good Grid control. Instead of spending time on this, why can't microsoft develop something good on UI side?

  4. davidebb says:

    @Mike: the name is just what I came up with after a couple minute of thinking, suggestions are welcome.  Not sure I understand your comment about the assembly attribute; can you be more specific?

    @Paul: how does your comment relate to this post? 🙂

  5. dotDev says:

    Great….

  6. Sayitfast says:

    I love things that make my life easier!

  7. ILog says:

    David, and what if I need more complex Spark registration? What if I want to add this line before registering Spark

       ViewEngines.Engines.Clear();

    and a couple of lines like these

       SparkViewFactory factory = ViewEngines.Engines.OfType<SparkViewFactory>().First();

       factory.Engine.LoadBatchCompilation(Assembly.Load("Precompiled"));

    after ? How can I do this?

  8. awesome says:

    just had  a look at the source, this nupack is beautiful in its simplicty

  9. davidebb says:

    @ILog: the idea is that you can just modify that file to fit your needs.  It's only meant to be a starting point so you don't start completely from scratch with the registration.

  10. ILog says:

    David, thanks for the explanation. Can we extend the idea a little bit? Can I add something to my own Spark project template that will call NuGet and will bring me reference assemblies with or without WebActivator execution?

  11. davidebb says:

    @ILog: If you're goal is just to bring in assembly references, then WebActivator would not come in the picture.  Could you start a thread on http://nuget.codeplex.com/ to discuss in more details?

  12. Rick Strahl says:

    So this a maybe obvious question, but: How is this WebActivator attribute getting fired? And do you have access to the HttpApplication instance at that point to add event handlers/modules at that time?

  13. davidebb says:

    It gets fired by itself using the new PreApplicationStartMethod attribute (see haacked.com/…/three-hidden-extensibility-gems-in-asp-net-4.aspx).

    It does not have access to an app instance, I think there is some way to register modules from there.  It's via an undiscoverable API, but I can dig it up for you if that's what you're after.  Note that it comes with MVC3.

  14. davidebb says:

    @Rick: the API is in Microsoft.Web.Infrastructure.dll, and is Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule().

    We'll make that less hidden in future versions of the framework, but it can be helpful for now.

  15. Buu Nguyen says:

    Nice post.  My project needs to register a custom route to RouteTable.Routes.  I've tried the pre-start approach but seems like the Routes object hasn't been created in that event (NullReferenceError).  Is there any way to automate route registration via NuGet?

  16. Buu Nguyen says:

    Sorry, please disregard my previous comment.  The Routes object isn't null.  So this approach should work just fine to register route.

  17. Dan Ryan says:

    Hello David – I'm trying to create a nuget package with dependencies; however, I'm not quite 'getting' the include-dependency process.  Is it necessary to include the dependencies in the lib directory?  I assumed that by adding a dependency, the nuget package installer would resolve the dependency from the nuget feed; however, I receive the following error: Unable to resolve dependency 'xyx'  Have any pointers or links?  I cannot seem to find any related post online.  thanks…

  18. davidebb says:

    @Dan: it's best to post such nuget questions on http://nuget.codeplex.com/ so it gets a bigger audience.

  19. yysun says:

    Hi,

    I use WebMatrix and have the start function is a cs file in the App_Code folder. It dose not seem to work.

    Does WebActivator support ASP.NET Web Pages sites?

    Thanks,

    yysun

  20. davidebb says:

    @yysun: I made a new 1.3 version which work for App_Code. Make sure you use the new  PostApplicationStartMethod instead of PreApplicationStartMethod

  21. Steve says:

    Isn't this something that could be using MEF ?  Meaning, couldn't the webactivator be built on MEF and we are simply plugging in ?

  22. davidebb says:

    @Steve: I actually started really early on with MEF (see first commit on bitbucket.org/…/webactivator), but then moved away from it, in part to match the existing System.Web pattern.

  23. Andrei Rinea says:

    Hello David,

    What if I have an exception in a PreApplicationStartMethod and I want to log it? I need to Server.MapPath the file path but HttpContext.Current is null..

    Any ideas?

    Thanks.

  24. davidebb says:

    @Andrei: try System.Web.Hosting.HostingEnvironment.MapPath() instead

  25. Andrei Rinea says:

    @David : THANKS! It worked.

  26. Zoltan Juhasz says:

    Dear David,

    Why hasn't got WebActivator a strong name? I cannot add strong name to my web site too.

    Thanks.

  27. davidebb says:

    @Zoltan: make sure you use the WebActivatorEx package, which is strong named. I'm surprised you're even finding the old one, since it's been hidden for a long time. BTW, please use github.com/…/WebActivator for WebActivator communication instead of this post. Thanks!

  28. apu mridha says:

    Thanks for sharing this amazing and informative article … enjoyed every bit of it ..  🙂

    Apu

  29. Aamol Gote says:

    I had question around this PreApplicationStartMethod attribute which as u mentioned is leveraged by WebActivatorEx. If I have an assembly which needs to be put in GAC and that assembly leverages PreApplicationStartMethod  to execute some code before app start. I see that when I reference the assembly it does not invoke the required code, when I bin deply the same assembly it works fine. Any thoughts.

  30. davidebb says:

    @Aamol: being discussed on github.com/…/22