RoR based MVC Web Frameworks – Friend or Foe?

Ruby on Rails has inspired a whole generation of MVC Web Frameworks. The RoR inspired frameworks follow the same basic design; Controllers whose public methods are web endpoints. So…Do RoR based MVC Web Frameworks encourage good design?

MonoRail and MS MVC are a part of the RoR generation.  

They must. Right? I show, in the code below, that RoR inspired frameworks can actually encourage bad design. I show, in the end, what I think is a better way.

The code presented here is written for MonoRail, but the same problems exist in MS MVC and RoR.  

Let’s start with a simple ProductsController. The index method of this controller is the implementation of http://whocares.com/products/<code&gt;. The code on the end of the URL is the code for a specific product (i.e. nj1287).

public class ProductsController : SmartDispatcherController{   public void Index(string code)   {      Product product = Product.FindByCode(code);      if(product == null)      {         PropertyBag["UnknownSearchTerm"] = code;         RenderView("common/unknown_quick_search");      }      PropertyBag["Product"] = product;      RenderView("display_product");   }}

CategoriesController is our next controller. The index method of this controller is the implementation of http://whocares.com/categories/<name&gt;. The name on the end of the URL is the name for a specific category (i.e. montiors).

public class CategoriesController : SmartDispatcherController{   public void Index(string name)   {      Category cat = Category.FindByName(name);      if(cat == null)      {         PropertyBag["UnknownSearchTerm"] = name;         RenderView("common/unknown_quick_search");      }      PropertyBag["Category"] = cat;      RenderView("display_category");   }}

The index method in both classes are basically the same. We can remove the duplication. Let’s extract an abstract class.

public abstract class SearchableController : SmartDispatcherController{   public void Index(string name)   {      ISearchable item = FindByName(name);      if(item == null)      {         PropertyBag["UnknownSearchTerm"] = name;         RenderView("common/unknown_quick_search");      }      PropertyBag["Item"] = item;      RenderView(IndexView);   }   protected abstract ISearchable FindByName(string name);   protected abstract string IndexView { get; }}public class ProductsController : SearchableController{   protected ISearchable FindByName(string name)   {      return Product.FindByCode(name);   }   protected string IndexView { get { return "display_product"; } }}public class CategoriesController : SearchableController{   protected ISearchable FindByName(string name)   {      return Category.FindByName(name);   }   protected string IndexView { get { return "display_category"; } }}

What do you think about our new DRY code?I don’t like it. Why?

  1. ProductsController and CategoriesController no longer “speak” to me.
  2. Inheritance. I really don’t like the template method pattern because of #1.

Can we fix this?We can use a DynamicAction in MonoRail. Let’s see that code.

Does RoR or MS MVC have a concept of a “DynamicAction”?  

public class IndexAction : IDynamicAction{   private ISearchableRepository repos;   private string indexView;   public IndexAction(      ISearchableRepository repos,      string indexView)   {      this.repos = repos;      this.indexView = indexView;   }   public void Execute(Controller controller)   {      string name = controller.Params["name"];      ISearchable item = repos.FindByName(name);      if(item == null)      {         controller.PropertyBag["UnknownSearchTerm"] = name;         controller.RenderView("common/unknown_quick_search");      }      controller.PropertyBag["Item"] = item;      controller.RenderView(indexView);   }}public class ProductsController : SmartDispatcherController{   public ProductsController()   {      DynamicActions["Index"] = new IndexAction(             new ProductsRepository(), "display_product");   }} public class CategoriesController : SmartDispatcherController{   public CategoriesController()   {      DynamicActions["Index"] = new IndexAction(             new CategoryRepository(), "display_category");   }}

I like this code better. We are no longer using inheritance to reuse code and the code “speaks” better to me. This code has two things that bug me.

  1. The controllers are really not doing much.
  2. DynamicActions are second class citizens in MonoRail.

How do we fix these issues? I don’t know how in the RoR inspired frameworks. I think we need another abstraction. Chris Ortman and I discussed the below code at HDC 07 (so he deserves at least part of the credit). I am not sure he agreed, but maybe this post will change his mind.

The code below is not apart of any framework. It is only an idea.  

public class IndexAction : SmartDispatcherAction{   private ISearchableRepository repos;   private string indexView;   public IndexAction(      ISearchableRepository repos,      string indexView)   {      this.repos = repos;      this.indexView = indexView;   }   public void Execute(string name)   {      ISearchable item = repos.FindByName(name);      if(item == null)      {         PropertyBag["UnknownSearchTerm"] = name;         RenderView("common/unknown_quick_search");      }      PropertyBag["Item"] = item;      RenderView(indexView);   }}

IndexAction is now a first class citizen of the framework. Actions have all the benefits that controllers have in MonoRail (parameter binding, …).How do we hook this action to a URL?

new Route("/products/<name>",    new IndexAction(new ProductRepository(), "display_product"));new Route("/categories/<name>",    new IndexAction(new CategoryRepository(), "display_category"));

Routing engines allow us to remove the “controller” completely. We don’t need a “controller” (at least not in the RoR framework form). What were our controllers doing? They were really just organizing our actions. Routing engines allow us to organize our actions without the “controller”.What do you think? I would really like some feedback on this topic. Does anyone else feel this pain with these frameworks?

Advertisements

MonoRail – Beginner FAQ

I am giving a MonoRail talk, at HDC 07, on Thursday morning.What questions do you think I should be prepared to answer?Here is a list of questions, and answers, that I typically get, and give, about MonoRail.Why would I use (care about) MonoRail?

  • Separation of concerns makes your application easier to test.
  • Microsoft will soon be releasing MS MVC. It is very similar to MonoRail.
  • It works well on teams where the designers and developers are not the same people.
  • Your team does not come from the MS world. MonoRail is closer to the framework designs from other languages (Java, Ruby).

Why would I not use MonoRail?

  • Your project depends too much on 3rd party controls.
  • Your teams skill set is totally centered around WebForms.
  • If your only experience with developing web sites is with WebForms, then you will have to learn HTTP.

What kinds of projects work best with MonoRail?

  • I would choose MonoRail for all projects, except for simple reporting web applications.

Do you have any other questions that you think I should be prepared to answer? Do you have anything to add to the current set of answers?

MS MVC – A MonoRail Perspective

Scott Guthrie gave us our first look at the the new Microsoft MVC framework at ALT.NET. I took special care, while watching the video, to answer one question.What, if anything, can MonoRail learn from MS MVC?

  1. Routing – RoR and MS MVC treat Routing as a first class citizen. Routing in MonoRail is an after thought and it shows.Why is first class Routing a big deal?
    • DRY – Tight integration between the routing engine and URL generation allows URLs to refactored easily and safely.
    • Testing – Testing routes, in MonoRail, requires an end-to-end test. If routes were first class objects, then routes could be tested in isolation.
  2. Typed PropertyBag – Typed PropertyBag is not the default behavior in MS MVC, but it is supported.Why is a Typed PropertyBag important?Contract – A Typed PropertyBag communicates the contract between the controller and view. Without this contract, refactoring anything in the view or the controller is difficult and error prone. We enforced a contract by implementing Advocates, but it would be nice if it were supported by MonoRail.

What are your thoughts?

Great Query DSL

Update: Ayenda replied with the following

This is Rhino Commons repository + NHibernate Query Generator code.

Ayende posted an example of a great query DSL. Is this real? If so, where can I download this framework?

ICollection<Event> eventsInTwoDays = Repository<Event>.FindAll(        Where.Event.ScheduledDate == DateTime.Today.AddDays(2) &&        Where.Event.Participants.With(FetchMode.Join)    );

Flattering

I want to thank the person that added my blog as a reference for MonoRail on Wikipedia. I am flattered.Thanks.

No Dots

On my last project, James and I started the slogan “No Dots”. “No Dots” reminds us that templates/views are “documents with holes” and should contain a very minimum amount of logic.

I amended the slogan to be: “Zero or One Dot”. You will see why shortly. The amended version was an artifact of using NVelocity. 

The example below is the reason behind the slogan.

Note: This example is in NVelocity, but it really applies to any template engine. 

#if(person.Age > 10)<div class="discount">Team</div>#end#if(person.Age > 20)<div class="discount">Network</div>#end#if(person.Age > 30)<div class="discount">World</div>#end

Is this code bad? Not really. The logic in the view is minimal.I say this view might be acceptable in a small project. The problem occurs when a project gets large. Why? Testing any logic in the view is hard and therefore does not get tested throughly. This under-tested logic is a breading ground for new bugs.James and I created a solution to this problem that James coined as the Advocate pattern.

Does this “pattern” have another name? 

What is an Advocate?The advocate is a “plain old object”, living outside the view, that answers questions about a model or models for a specific view.Enough with the definition, we want to see the code.PersonAdvocate

public class PersonAdvocate{    private Person person;    public PersonAdvocate(Person person)    {        this.person = person;    }    public void IList<Discount> Discounts    {        get        {            IList<Discount> discounts = new List<Discount>();            if(person.Age > 10) discounts.Add(new Discount("Team"));            if(person.Age > 20) discounts.Add(new Discount("Network));            if(person.Age > 30) discounts.Add(new Discount("World"));        }    }}

NVelocity Template

#foreach($Discount in $PersonAdvocate.Discounts)<div class="discount">$Discount.Name</div>#end

The above template code is an example of “One Dot”. Allowing “One Dot” means that only one object needs to be put into the template’s context. Sticking to “No Dots’ would force one object in the context per hole in the template. I am willing to allow “One Dot”, but no more.

The new version of the template is less complicated and more resistant to change. The decisions about what discounts to display are encapsulated in PersonAdvocate. Moving the logic from the template to the advocate made it easier to test and refactor. Advocates allowed us to fix buggy view logic that finally stayed fixed.

MonoRail: Dynamic Actions; The better way to implement actions?

We are finishing up our eight month development effort on sothebyshomes.com. Knowing what we do now, “What would we do different?”.I think most of my team would say; Dynamic Actions. What are Dynamic Actions?

Dynamic Actions are a way to associate code with a name in runtime.

What exactly does that mean? An example might help explain.

public ListingController(){    DynamicActions["ImageSlideShow"] =        new ImageSlideShowAction(new ListingContext());    DynamicActions["FullSizeImageSlideShow"] =        new FullSizeImageSlideShowAction(repository);}

After these actions are added, listing/imageslideshow.rails and listing/fullsizeimageslideshow.rails are mapped to the actions defined in ImageSlideShowAction and FullSizeImageSlideShowAction respectively.How does one implement a Dynamic Action?

public class FullSizeImageSlideShowAction : IDynamicAction{    public void Execute(Controller controller)    {        string identifier = controller.Params["identifier"];        Listing listing = FindByListingNumber(identifier);        controller.PropertyBag["Listing"] = listing;        controller.RenderSharedView("fullsizeimageslideshow");    }}

Why would we want to use Dynamic Actions? I will let Hammett answer this question.

DynamicActions offers a way to have an action that is not a method in the controller. This way you can “reuse an action” in several controllers, even among projects, without the need to create a complex controller class hierarchy.

The most important part of Hammett’s answer is “reuse an action … without the need to create a complex controller class hierarchy”. In the beginning or our project, we use inheritance to “reuse actions”. This turned out to be a bad decision, for all the obvious reasons. At the time, we overlooked Dynamic Actions; I think partially because the Dynamic Action documentation was buried in the advanced section of the MonoRail documentation.Our code has two main use cases that are shared among eight different domain models. We currently use one base controller to implement the use cases with hooks to allow the subclasses to customize its behavior. This has made (as you can imagine) unclear and hard to maintain code. I would never again use inheritance as a way to share actions across controllers.Has anyone else overlooked Dynamic Actions?Any advice on implementing Dynamic Actions? Any pitfalls?