To continue my blogging blast on FubuMVC, it’s time to talk about how fubu is configured. For long time FubuMVC followers, there was a flurry of commits just before Christmas this year to support the new packaging model that greatly changed how you bootstrap a FubuMVC application. I’ll blog about packaging soon, but for right now, let’s catch up on basic bootstrapping.
In contrast to the other .Net web frameworks, FubuMVC has by far and away the simplest runtime model. The real world being imperfect, we achieved that runtime simplicity by relying on the configuration subsystem to determine how the runtime objects would be assembled at application startup time, then to “bake” that object assembly into the underlying IoC container in your application. That code was non-trivial to write.
In the beginning…
At the tail end of 2009 I had a window of opportunity at work to basically reboot FubuMVC from scratch (about the time we moved to GitHub[LINK]). At Dovetail we had already tried a couple iterations of what I now call “proto-fubu” plus the initial versions of FubuMVC that all used something similar to what’s now the “behaviors” runtime model. From that experience we had a pretty good idea of how the runtime model was going to look, but we had struggled with the configuration story.
I had originally instituted a fluent interface to explicitly configure the behavior chains (this request url is handled by this controller action and this view or json), but the fluent interface quickly achieved aluminum wiring status. We knew with the FubuMVC reboot that we wanted to move towards favoring “convention over configuration.”
I started with this vision for FubuMVC configuration and conventions:
- FubuMVC would be able to mostly discover controller actions by scanning one or more assemblies.
- Route url patterns could be determined by a convention
- Views would be “attached” to controller actions by a convention
- FubuMVC would be able to figure out for itself which actions were strictly json services, html snippets, or just returned a string value
- We could happily supplement or override the conventions with explicit configuration.
- Conventions would not be hard coded. Users (us) would be able to create their own conventions.
- One way or another we’d just use an IoC container to do the object assembly, meaning that the whole nested behavior model would need to be translated into the IoC container of your choice.
- Conventions are magic. Magic is confusing. We knew right off the bat that we would need diagnostics baked into the framework to unravel the convention magic.
FubuMVC Configuration at 10,000 Feet
Roughly speaking, the configuration infrastructure is broken up into a few different areas of responsibility:
- A low level configuration object graph (BehaviorGraph) that models all the routes, behaviors, and additional services that FubuMVC needs internally. Think “this url is handled by calling this method on this class, then rendered with this WebForms view.”
- A Domain Specific Language (FubuRegistry) that builds the configuration object graph by using the conventions, policies, and explicit configuration expressed by the DSL.
- A container facility (IContainerFacility) that takes the entire configuration object graph and adds the appropriate configuration to the application’s underlying IoC container.
- Bootstrapping classes (FubuApplication) that govern the entire startup process. Let’s just say that the internals of bootstrapping a FubuMVC application are non-trivial – and much more so after the packaging work. I’ve attempted to make the application bootstrapping as declarative as possible to prevent user mistakes.
FubuMVC incorporates a lot of the architectural lessons that we learned from our involvement with both StructureMap and Fluent NHibernate. The most important lesson is that the configuration should be modeled with a passive data structure, and that data structure should be completely decoupled from the mechanism used to capture configuration. Moreover, the configuration model should be easy to traverse, query, and write.
This architectural strategy has some powerful benefits:
- It enables the framework to mix and match multiple forms of configuration.
- It makes it possible to create all new conventions. In this architecture,
- Conventions can be used to build the first pass of the configuration model, then explicit configuration directives can be applied to the model afterward to override the conventions.
- The passive configuration model can be queried by diagnostic reports
Bootstrapping a Simple Application
Please note that I’m glossing over the new packaging functionality in this post for the sake of length and managing complexity. FubuApplication also “knows” how to invoke the packaging pipeline in the correct order.
Inside the FubuMVC codebase is a simple “Hello World” project that I’m using for this example.
The first thing to do is to create a single top level FubuRegistry for the HelloWorld project like this code.
FubuRegistry is a big topic by itself for another day, but for right now just glance at the class above. Among other things, HelloWorldFubuRegistry is declaring which classes expose actions, how to derive a Route for the controller actions in the BehaviorGraph, and the policies FubuMVC should use to connect WebForms views to controller actions.
Now that we have a FubuRegistry (and we’ve very logically chosen to use StructureMap as our IoC container), we would open the Global.asax file of the Hello World app and write code like this:
FubuApplication .For<HelloWorldFubuRegistry>() .StructureMap(() => new Container()) .Bootstrap(RouteTable.Routes);
FubuApplication “knows” how to do all the bootstrapping activities in the right order, but we have to first tell FubuApplication what the main FubuRegistry is and how to build an IContainerFacility. Please note that we register the container facility in the “StructureMap()” method with a Func (the StructureMap() method actually wraps the Func<IContainer> inside another Func that would return a StructureMapContainerFacility object. Deferred execution FTW!). This has to be done with deferred execution to ensure that your IoC container setup can take advantage of the assemblies discovered dynamically through package loading.
Now that we’ve told FubuApplication about our HelloWorldFubuRegistry and that we’re going to use StructureMap for our container facility, we can call the Bootstrap() method to start up our application. In order, Bootstrap():
- A bunch of package discovery and loading we’ll talk about later…
- Builds an IContainerFacility with the Func<IContainerFacility> we supplied to FubuApplication
- Calls the BuildGraph() method on the HelloWorldFubuRegistry object we supplied to FubuApplication to build a BehaviorGraph of all the actions, views, and route information for our application. HelloWorldFubuRegistry itself is using the conventions we specified to scan the main assembly of HelloWorld in order to build the BehaviorGraph.
- FubuApplication reads BehaviorGraph and registers FubuMVC specific services with the IContainerFacility
- FubuApplication iterates through each “BehaviorChain” in BehaviorGraph and adds uses the IContainerFacility to translate the configuration objects into StructureMap’s internal configuration. If appropriate, FubuApplication also creates and registers an ASP.Net Route object for the behavior chain with a FubuRouteHandler pointing back to the correct behaviors (some BehaviorChain’s represent “partial requests” and would not have a url).
- Some more packaging activation stuff we’ll get into later…
FubuMVC and IoC Freedom
I’ve chosen to use StructureMap for the Hello World application. FubuMVC was purposely built to make the IoC container swappable. StructureMap is the only option in the official core right now, but at least one other team is using Ninject with FubuMVC. The StructureMap() method above is an extension method from the FubuMVC.StructureMap assembly. Despite reports to the contrary, FubuMVC is not tightly coupled to StructureMap.
I think that the configuration subsystem in FubuMVC is what sets it apart from other .Net tools in terms of its capabilities, but I know some of the rest of you are from Missouri and you’ll have to be shown with some examples
But wait, there’s more…
This blogging out FubuMVC before CodeMash thing isn’t happening fast enough because it’s just too big a topic. I need to continue the discussion about FubuMVC configuration with:
- An introduction to the BehaviorGraph
- How FubuMVC allows you to swap out IoC containers
- How FubuRegistry works
- Writing your own conventions
- Packaging! After the support for conventions, I think this is our killer feature.