Building a Simple Bottle to Extend FubuMVC

WARNING:  I dashed out this code *fast* just for the blog post.  All the same, the code referenced here is on GitHub.

If you follow FubuMVC at all you’ve surely seen the term “Bottles” being tossed around in regards to extensibility and modularity in FubuMVC applications, but not a lot of detail about what it is or how to use it.  While I think that most people focus on using Bottles as a way to split functional areas of your application into separate projects or even code repositories, you can also use Bottles to create shared infrastructure extensions to FubuMVC — and that’s the topic of this blog post.

I wanna use a DatePicker control for all Date fields!

The easiest example I can think of is to build a Bottle that “drops in” an html convention to make every DateTime property whose name ends in “Date” be edited by the jquery.ui datepicker control, implemented by the new FubuMVC.DatePicker project I threw together specifically for this blog post (as in, just a demonstrator, not testing very hard).  We really do want to make this be completely “drop in” with no other configuration necessary, so the bottle is going to have to contain its own JavaScript assets and have a way to inject both the html convention policies and assets into the base FubuMVC project it gets added to.  So, how do we do that?  Well first, let’s…

Grab Some Dependencies

The FubuMVC.DatePicker depends on a couple other FubuMVC related nugets:

  1. FubuMVC.Core – duh.
  2. FubuMVC.Core.UI – the html convention support for FubuMVC applications among other things
  3. FubuMVC.Core.Assets — the asset pipeline for FubuMVC.  Conveniently enough, this Bottle comes with *a* fairly recent version of jquery just in case the application doesn’t already contain jquery.
  4. FubuMVC.JQueryUI — integrates jquery.ui into a FubuMVC application and also contains a default version of jquery.ui and a jquery.ui theme.  All of the assets can be happily overridden in the application, so don’t think you’re stuck with whatever comes in this Bottle.

Now, we’ll add the…

Client Side Script Support

We’re using the jquery.ui datepicker plugin for the client side date picker support because it’s simple to use.  I’ll do the client side activation with a new file called “DatePickerActivator.js” added at /content/scripts under the root of our FubuMVC project:

DatePickerActivator.js
  1. $(document).ready(function () {
  2.     $(‘.datepicker’).datepicker();
  3. });

The code above isn’t going to help when you’re using client side templates in a “Single Page Application” type approach, but that’s beyond the scope of this post, so we’ll call the code above good enough for now.  Now, DateTimeActivator.js does have some dependencies, so let’s declare those to the asset pipeline with a file called “datepicker.asset.config“:

DatePickerActivator.js requires jquery, jqueryui, jqueryuicss

Couple things to note up above:

  1. Any file named “*.asset.config” is picked up by FubuMVC’s asset pipeline and interpreted as asset configuration
  2. The asset pipeline supports a small textual DSL to express dependencies, asset groups, and other asset rules
  3. “jquery” and “jqueryui” are aliases that the asset pipeline will resolve into an actual asset name.  In the asset pipeline library, there’s another declaration:  “jquery is jquery-1.8.2.min.js” so that asking for “jquery” resolves to “jquery-1.8.2.min.js”  at runtime.  This was done purposely to make it easier to upgrade javascript libraries that embed versions into the file name without breaking the rest of the application.
  4. Anytime we request “DatePickerActivator.js” in a view, the asset pipeline will ensure that jquery and jquery.ui libraries are also added into the page.

Html Convention Support

Next we need to build out the actual html convention policy in the following code:

DatePickerBuilder
  1.     public class DatePickerBuilder : ElementTagBuilder
  2.     {
  3.         public override bool Matches(ElementRequest token)
  4.         {
  5.             return token.Accessor.PropertyType.IsDateTime() && token.Accessor.Name.EndsWith(“Date”);
  6.         }
  7.         public override HtmlTag Build(ElementRequest request)
  8.         {
  9.             // Add the DatePickerActivator.js into the asset pipeline for this page
  10.             request.Get<IAssetRequirements>().Require(“DatePickerActivator.js”);
  11.             string text = null == request.RawValue || DateTime.MinValue.Equals(request.RawValue)
  12.                               ? “”
  13.                               : request.RawValue.As<DateTime>().ToShortDateString();
  14.             return new HtmlTag(“input”).Text(text).Attr(“type”, “text”).AddClass(“datepicker”);
  15.         }
  16.     }

It’s not a lot of code, but it’s enough to declare when the new html convention applies (properties of type DateTime that end in “Date”), and how to build the html tag for the editor.  The only fancy thing is that this code can inject the script requirements into the asset pipeline.  It’s important to note here that the call to IAssetRequirements.Require(asset name) above does not write out a script tag right there and then, it simply tells the asset pipeline that “DatePickerActivator.js” and all its dependencies (and their dependencies) are required on this page.  Somewhere in the view (typically in the footer), there’s a single call to write the script tags that will emit script tags for all of the pending script dependencies (the asset pipeline can optionally do script compression and combinations with the assets injected by html conventions to avoid making extra http requests).

You do not have to use FubuMVC’s asset pipeline with a FubuMVC application, but it is necessary to make these types of Bottle extensibility mechanisms work.

Registering the new Html Convention

When a FubuMVC application spins up, it searches through all the assemblies loaded by Bottles looking for any concrete class that implements the IFubuRegistryExtension interface, creates an instance of that type, and applies the configuration in that IFubuRegistryExtension to the FubuMVC application spinning up.  In order to apply the html convention class that we built above, we need to add a new IFubuRegistryExtension class to our new assembly:

Code Snippet
  1.     public class DatePickerRegistryExtension : IFubuRegistryExtension
  2.     {
  3.         public void Configure(FubuRegistry registry)
  4.         {
  5.             registry.Import<HtmlConventionRegistry>(x => {
  6.                 x.Editors.Add(new DatePickerBuilder());
  7.             });
  8.         }
  9.     }

Bottle-ize the Assembly

Bottles doesn’t go around willy nilly loading every assembly it finds in the application as a Bottle, so we have to do something to mark our assembly as appropriate for Bottles to load it into FubuMVC applications.  The easiest and most common way is to just add the [FubuModule] marker attribute at the assembly level like this code below:

Code Snippet
  1. using System.Reflection;
  2. using FubuMVC.Core;
  3. [assembly: AssemblyTitle(“FubuMVC.DatePicker”)]
  4. [assembly: FubuModule]

Modifying the Build Script to Embed Content in the Bottle Assembly

Lastly, we need to embed any kind of content (JavaScript files, stylesheets, CoffeeScript, Spark or Razor views, asset config files) that isn’t part of the C# code into the Bottle assembly.  Bottles does this by sweeping the project for all files that match a set criteria (the out of the box criteria is anything that isn’t C# code or related to Visual Studio), and making a single zip file called “pak-WebContent.zip” and embedding that into the assembly.  There’s no need to make every single non-C# file an embedded resource, but we do need to call the Bottles functionality to “bottle up” the contents when you rebuild the project.

There’s an executable called “bottles.exe” in the tools folder of the Bottles nuget that you can use to “bottle up” an assembly.  I usually add the call to Bottles directly into the compile step of one or our rake scripts like so:

desc "Compiles the app"
task :compile => [:restore_if_missing, :clean, :version] do
  bottles("assembly-pak src/FubuMVC.DatePicker -p FubuMVC.DatePicker.csproj")

  MSBuildRunner.compile :compilemode => COMPILE_TARGET, :solutionfile => 'src/FubuMVC.DatePicker.sln', :clrversion => CLR_TOOLS_VERSION

  target = COMPILE_TARGET.downcase
end

def self.bottles(args)
  bottles = Platform.runtime(Nuget.tool("Bottles", "BottleRunner.exe"))
  sh "#{bottles} #{args}"
end

That’s actually all of the code, except for a nuspec file to package this up for nuget.  And tests, which don’t exist right now:(.

Consuming this Bottle

Now it’s time to use our new Bottle and apply the html convention for date properties.  You’re really got just a couple steps:

  1. Add an assembly reference in your main application to the new FubuMVC.DatePicker library through nuget or however you want to do that
  2. Do make sure that you are calling “WriteScriptTags()” somewhere at the end of your views so that the asset pipeline writes out the script files declared by the new html convention class.  We typically do this by putting that call in the main application Spark layout file.
  3. You might have to manually declare the “jqueyruicss” asset in the head of your view to make sure the “jquery-ui.css” file is present in any view that uses the new datepicker convention.  The asset pipeline handles the scripts pretty well, but the CSS files are a little trickier because they usually get written into the page before the html convention even fires
  4. Use the html convention on a page with the “InputFor” extension method like this in Spark: !{this.InputFor(x => x.SomeDate)} or <Input name=”SomeDate” /> if you’re using Spark bindings (not standardized in the FubuMVC.Spark nuget yet, but will be soon).

Stuff I think could be better

  • The javascript activation could stand to be standardized in a way that is more conducive to using the html conventions with backbone, knockout, or something of that ilk.  I’m hoping that my colleague Bob Pace will add in his module/activator magic he uses in his projects as a standard trick in FubuMVC.
  • We need to get a FubuMVC.QuickStart nuget going that bootstraps more of the asset pipeline setup and layout files to get users going faster
  • I’d like to see some enhancements to our “fubu.exe” tool to deal with more of the repetitive Bottle project setup.
  • I’m not a big fan of the way we smuggle the Bottles executable through nuget today.  It leaves us with the ugly hack in the rake scripts to magically find where nuget decided to put the bottles executable.  I know how we’re going to beat this inside the fubu project ecosystem with our ripple tool, but I’m not sure how best to do this for folks not using our build tools.  I’m not willing to accept that Bottles has to be installed on a user box before running the build file.
  • Today, FubuMVC has a limitation that asset files have to be under the /content directory for the asset pipeline.  We’re absolutely committed to changing this in the near term, but it won’t happen before our 1.0 release.  I get aggravated every time I hear somebody say that FubuMVC is just an attempt to copy Ruby on Rails.  Ironically enough, this asset file limitation is the only single thing in FubuMVC that was copied directly from RoR — and every body, including me, hates it.

What can I do with Bottles to extend FubuMVC?

This is bravado until we have enough documentation and samples to prove it, but I think that FubuMVC has the best story for extensibility and modularity in the entire .Net Web Framework universe — and honestly, I don’t think it’s even close.

So what sorts of extensibility things can you do with the FubuMVC/Bottles combination?  The short answer is every single thing that you can do with a FubuMVC application can be externalized into a Bottle and added into the base application without any seams (except for content extensions in existing views) or special handling in the base application.

6 thoughts on “Building a Simple Bottle to Extend FubuMVC

  1. It seems like Bottles are working around a problem inherent to creating HTML UI components that’s more elegantly solved by using MVC on the client side via Angular.JS or similar. Why muddy up the waters with creating an AOP way to enforce convention that rightly lives in the presentation layer alone?

    1. From what I can tell, this is much more than enforcing just conventions. Bottles allows you to add/replace just about *anything* from the host site. Think of a base website that processes orders a fairly generic way; this base website is your base product you resell to customers perhaps. Then, after you’ve sold quite a few of this product to clients that are happy with how it works, along comes the next client that wants to change how orders are processed. When you deploy your product for this client, you can drop in a Bottle that changes how orders are processed to fit *their* needs/wants. You just customized the product you offer without messing up the base code. You’ll likely even name your Bottle after the client.

    2. As Matt said, there’s two different things in this post:

      1.) Bottles — our infrastructure for modularizing FubuMVC applications and creating reusable assets
      2.) Html Conventions — the part you’re objecting to.

      I’m happy to disagree with you and say it’s almost a preference thing. I’m not particularly going to buy the argument that having to write more repetitive code in the client JS and Html markup is more “elegant” at face value.

  2. This is great stuff!

    I intend to use Bottles mid-2013 for a project I’m working on now as the means to swap out the visual theme, site name, select view content, DB content, and certain business rules, simply based on the domain name used to access the base website.

    Some of the view content will be small Spark partial view replacements; others will be entire main view replacements. The visual theme will be cake by overriding site styles. The DB content and business rules will be service replacements (e.g., replace the service for IOrderProcessor with this new SuperAwesomeOrderProcessor implementation).

    All made possible by FubuMVC and Bottles! What’s not to love?

  3. I was loving this article until I got to: “You might have to manually declare the “jqueyruicss” asset in the head of your view”. Eeek! Doesn’t this defeat the point, somewhat? Any plans to improve the Asset pipeline’s handling of CSS dependencies?

Leave a comment