FubuToDo – Part 1: Conventions, Opinions, and Bootstrapping
Posted in: Examples, Software Development
With the recent activity from the FubuMVC team, I thought that I would help others get started by providing some samples. Tim Tyrrell started us off with his two posts:
- “Hello World” with FubuMVC (Super Quick Start)
- “Hello World” with FubuMVC (without the training wheels)
Building off of Tim’s directions, I created a simple but more involved application to show some jQuery magic and really just get started with the framework. I’ve broken down this series into the following:
- Part 1: Conventions, Opinions and Bootstrapping
- Part 2: Forms, Controllers, Views, and jQuery
- Part 3: Persistence
Overview
I wanted to create a simple “To Do” application using FubuMVC. I threw together some quick css and figured I’d make something similar to figure 1.1.:
Figure 1.1 – Wireframe
One important thing to note is that I built this application using a very specific approach: don’t think too much. A lot of the refactoring that you’ll see me do came to mind before I wrote the original pieces (and I’m sure you’ll see it too) but I decided to just stay the course and see what Fubu can do.
Each “Part” in this series has a corresponding tag in the repository I used which I’ll link to at the end of each article.
Getting Started
I started with the tests for my home controller. I wasn’t too sure how I wanted everything to work just yet so I came up with the following cases to test against:
- Given some input model (“IndexInputModel”), the Home controller should return a instance of “IndexViewModel” and it should not be null.
- When the home controller creates the “IndexViewModel”, it should set the “Items” property.
Let’s take a look at those tests:
Test 1
/// <summary> /// Given some input model ("IndexInputModel"), the Home controller should return a instance of "IndexViewModel" and it should not be null. /// </summary> [Test] public void Index_Sets_ViewModel() { IToDoItemRepository itemRepository = _mockRepository.DynamicMock<IToDoItemRepository>(); HomeController controller = new HomeController(itemRepository); IndexViewModel viewModel = controller.Index(new IndexInputModel()); Assert.IsNotNull(viewModel); }
Test 2
/// <summary> /// When the home controller creates the "IndexViewModel", it should set the "Items" property. /// </summary> public void Index_Sets_ViewModel_Items() { IToDoItemRepository itemRepository = _mockRepository.DynamicMock<IToDoItemRepository>(); HomeController controller = new HomeController(itemRepository); using(_mockRepository.Ordered()) { itemRepository.Expect(r => r.GetAll()).Return(new List<ToDoItem>()); } _mockRepository.ReplayAll(); IndexViewModel viewModel = controller.Index(new IndexInputModel()); Assert.IsNotNull(viewModel.Items); }
Simple, right?
Now, let’s take a look at the other classes:
IndexInputModel
/// <summary> /// Provides an input model for the home index view. /// </summary> public class IndexInputModel { }
IndexViewModel
/// <summary> /// Provides a model for the home index view. /// </summary> public class IndexViewModel { /// <summary> /// Gets or sets the associated collection of <see cref="ToDoItem"/> entities. /// </summary> public IEnumerable<ToDoItem> Items { get; set; } }
ToDoItem
/// <summary> /// Represents a to-do item. /// </summary> public class ToDoItem { /// <summary> /// Gets or sets the unique identifier of the item. /// </summary> public virtual int ItemId { get; set; } /// <summary> /// Gets or sets a description of the item. /// </summary> public virtual string Description { get; set; } /// <summary> /// Gets or sets a flag indicating whether the item has been completed. /// </summary> public virtual bool IsComplete { get; set; } }
HomeController
/// <summary> /// Provides a controlling mechanism for the home area. /// </summary> public class HomeController { private readonly IToDoItemRepository _itemRepository; /// <summary> /// Initializes a new instance of the <see cref="HomeController"/> class with the specified dependency. /// </summary> /// <param name="itemRepository">The item repository.</param> public HomeController(IToDoItemRepository itemRepository) { _itemRepository = itemRepository; } /// <summary> /// Prepares the index view model. /// </summary> /// <param name="model">The input model.</param> /// <returns></returns> public IndexViewModel Index(IndexInputModel model) { return new IndexViewModel { Items = _itemRepository.GetAll() }; } }
Piecing it together
Ok, so now I had a working controller. What I needed to do next was move my classes around, create a view, and then wire everything up. I went with three projects:
- FubuToDo
- Domain
- Repositories
- Infrastructure code will go here too
- FubuToDo.Tests
- FubuToDo.Web
- Controllers – Folder for each
- Common – Master pages
- Images
- Styles
- Infrastructure – Bootstrapping, registries
I took up some habits from the Dovetail guys and organized my views, models, and controllers in the same folder. Fubu uses the notion of “one model in, one model out” and the controllers are intended to be very light. Given those conventions, it’s pretty safe to assume that your folders won’t blow up with a ton of files and it’s just easier to find everything. I ended up with this:
Figure 1.2 – Solution structure
The Index view doesn’t have anything fancy. The code-behind class inherits from FubuPage<IndexViewModel> and here’s the code for rendering the items:
<div id="blog-entry-container"> <h2>To-Do List</h2> <div id="Item-List"> <% if(Model.Items.Any()) { %> <ul class="item-list"> <% foreach (var item in Model.Items) { %> <li id="<%= item.ItemId %>" class="item"> <div class="row no-label"> <input type="checkbox" name="Item-<%= item.ItemId %>" <%= item.IsComplete ? "checked=\"checked\"" : string.Empty %> /> <label class="checkbox"><%= item.Description %></label> </div> <br class="cboth" /> </li> <% } %> </ul> <% } else { %> <span>No items.</span> <% } %> </div> </div>
Getting it to run
We’re almost there, I promise. We’ve got everything working. Now we just need to tell Fubu what to do with it all. Let’s take a look at the Global.asax.cs file:
Global.asax.cs
/// <summary> /// Provides a global application class for the to-do list application. /// </summary> public class Global : HttpApplication { /// <summary> /// Called by the ASP.NET Framework on application start. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void Application_Start(object sender, EventArgs e) { var routes = RouteTable.Routes; FubuStructureMapBootstrapper.Bootstrap(routes); } }
We’re deferring all of the initialization and registry calls to our FubuStructureMapBootstrapper class. It’s got a method called “Bootstrap” that just takes in our route collection. Let’s take a look under the hood (if you’ve read Tim’s post, this should look extremely familiar):
FubuStructureMapBoostrapper
/// <summary> /// Simple StructureMap bootstrapper. /// </summary> public class FubuStructureMapBootstrapper : IBootstrapper { private readonly RouteCollection _routes; /// <summary> /// Initializes a new instance of the <see cref="FubuStructureMapBootstrapper"/> class. /// </summary> /// <param name="routes"></param> public FubuStructureMapBootstrapper(RouteCollection routes) { _routes = routes; } /// <summary> /// Initializes the bootstrapping process. /// </summary> public void BootstrapStructureMap() { UrlContext.Reset(); ObjectFactory.Initialize(x => x.For<IToDoItemRepository>().Use<FakeToDoItemRepository>()); Bootstrap(ObjectFactory.Container, _routes); } /// <summary> /// Bootstraps the application with the specified container. /// </summary> /// <param name="container"></param> /// <param name="routes"></param> public static void Bootstrap(IContainer container, RouteCollection routes) { var bootstrapper = new StructureMapBootstrapper(container, new FubuToDoRegistry()); bootstrapper.Bootstrap(routes); } /// <summary> /// Bootstraps the application for the specified routes. /// </summary> /// <param name="routes"></param> public static void Bootstrap(RouteCollection routes) { new FubuStructureMapBootstrapper(routes).BootstrapStructureMap(); } }
There are two things to note here: 1) The FakeToDoItemRepository and 2) the FubuToDoStructureMapRegistry classes.
The FakeToDoItemRepository class simply returns a hard-coded List<ToDoItem> full of 3 to-do items. The FubuToDoRegistry class has all of my registry calls which tell Fubu what conventions I would like for my app:
FubuToDoRegistry
/// <summary> /// Simple registry. /// </summary> public class FubuToDoRegistry : FubuRegistry { /// <summary> /// Initializes a new instance of the <see cref="FubuToDoRegistry"/> class. /// </summary> public FubuToDoRegistry() { IncludeDiagnostics(true); Applies.ToThisAssembly(); Actions .IncludeTypesNamed(x => x.EndsWith("Controller")); Routes .IgnoreControllerNamespaceEntirely(); Views.TryToAttach(x => { x.by_ViewModel_and_Namespace_and_MethodName(); x.by_ViewModel_and_Namespace(); x.by_ViewModel(); }); } }
I have used the same conventions that Tim did in his example. He’s got a great explanation on what these do so I strongly recommend reading his post.
Running the application
At the time of this writing, there is no way to specify your default route in your registry. To get around this for now, I created a Default.aspx in the root and did a Response.Redirect(“~/Home/Index”) to force it to my default action. Once that’s in place, hit F5 and you should see this:
Figure 1.3 – End result
That’s it! We’re all done. In my next part I’ll talk about wiring up forms to your controllers and leverage the UnitOfWork pattern to get the items saving.
Source code
You can download the source for this part at: http://svn.joshua-arnold.com/fubutodo/tags/part1
Return to: FubuToDo – Part 1: Conventions, Opinions, and Bootstrapping
Social Web