Sdílet prostřednictvím


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 https://nuget.codeplex.com/.  The latter is probably best as it makes it easier to have a discussion among many folks.

Comments

  • Anonymous
    October 12, 2010
    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...

  • Anonymous
    October 12, 2010
    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'

  • Anonymous
    October 12, 2010
    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?

  • Anonymous
    October 12, 2010
    @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? :)

  • Anonymous
    October 13, 2010
    Great....

  • Anonymous
    October 14, 2010
    I love things that make my life easier!

  • Anonymous
    November 24, 2010
    The comment has been removed

  • Anonymous
    November 24, 2010
    just had  a look at the source, this nupack is beautiful in its simplicty

  • Anonymous
    November 25, 2010
    @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.

  • Anonymous
    November 25, 2010
    The comment has been removed

  • Anonymous
    November 29, 2010
    @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?

  • Anonymous
    December 22, 2010
    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?

  • Anonymous
    December 22, 2010
    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.

  • Anonymous
    December 22, 2010
    @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.

  • Anonymous
    January 26, 2011
    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?

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

  • Anonymous
    February 15, 2011
    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...

  • Anonymous
    February 15, 2011
    @Dan: it's best to post such nuget questions on http://nuget.codeplex.com/ so it gets a bigger audience.

  • Anonymous
    February 24, 2011
    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

  • Anonymous
    February 24, 2011
    @yysun: I made a new 1.3 version which work for App_Code. Make sure you use the new  PostApplicationStartMethod instead of PreApplicationStartMethod

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

  • Anonymous
    April 22, 2011
    @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.

  • Anonymous
    May 03, 2011
    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.

  • Anonymous
    May 03, 2011
    @Andrei: try System.Web.Hosting.HostingEnvironment.MapPath() instead

  • Anonymous
    May 03, 2011
    @David : THANKS! It worked.

  • Anonymous
    September 25, 2013
    Dear David, Why hasn't got WebActivator a strong name? I cannot add strong name to my web site too. Thanks.

  • Anonymous
    September 25, 2013
    @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!

  • Anonymous
    May 10, 2014
    Thanks for sharing this amazing and informative article ... enjoyed every bit of it ..  :) Apu

  • Anonymous
    July 31, 2014
    The comment has been removed

  • Anonymous
    July 31, 2014
    @Aamol: being discussed on github.com/.../22