Model View Action
Adam has an interesting discussion here about handling common actions in MonoRail. This has sparked some discussion in the MonoRail mailing list. I wanted to take the chance to discuss the idea in more detail here.
Basically, he is talking about doing this:
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); } }
And then registering it with the routing engine like this:
new Route("/products/<name>", new IndexAction(new ProductRepository(), "display_product")); new Route("/categories/<name>", new IndexAction(new CategoryRepository(), "display_category"));
Now, accessing "/categories/cars" will give you all the items in the cars category.
On the face of it, it seems like a degenerated controller, no? Why do we need it? We can certainly map more than a single URL to a controller, so can't we solve that problem that way?
Let us stop for a moment and think about the MVC model. Where did it come from? From Smalltalk, when GUI was something brand new & sparkling. It is a design pattern for a connected system. In that case, the concept of a controller made a lot of sense.
But when we are talking about the web? The web is a disconnected world. What is the sense in having a controller there? An Action, or a Command, pattern seems much more logical here, no?
But then we have things that just doesn't fit this model. Consider the example of CRUD on orders. We can have a controller, which will handle all of the logic for this use case in a single location, or we can have four separate classes, each taking care of a single aspect of the use case.
Personally, I would rather have the controller to do the work in this scenario, because this way I have all the information in a single place, and I don't need to hunt multiply classes in order to find it.
But, there are a lot of cases where we do want to have just this single action to happen, or maybe we want to add some common operations to the controller, without having to get in to crazy inheritance schemes.
For this, MonoRail supports the idea of Dynamic Actions, which supports seamless attachment of actions to a controller.
Hammett describe them best:
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 really interesting part in this is that we have both IDynamicAction and IDynamicActionProvider. This means that we get mixin-like capabilities.
Dynamic Actions didn't get all the love they probably deserve, we don't have the SmartDispatcherAction (yet), so if we want to use them, we will need to handle with the raw request data, rather than with the usual niceties that MonoRail provides.
Nevertheless, on a solid base it is easy enough to add.
Now all we need to solve is the ability to route the requests to the correct action, right? This is notepad code, so it is ugly and not something that I would really use, but it does the job:
public class ActionRoutingController : Controller { public delegate IDynamicAction CreateDynamicAction(); public static IDictionary<string, CreateDynamicAction> Routing = new Dictionary<string, CreateDynamicAction>(); protected override void InternalSend(string action, IDictionary actionArgs) { if(Routing.ContainsKey(action) == ) throw new NoActionFoundException(action); Routing[action]().Execute(this); } }
What this means is that you can now do this:
public void AddRoutedActions() { AddRoutedAction("categories", "/categories/<name:string>", delegate { return new IndexAction(new CategoryRepository(), "display_category"); }); AddRoutedAction("products", "/products/<name:string>", delegate { return new IndexAction(new ProductRepository(), "display_product"); }); } public void AddRoutedAction(string action, string url, CreateDynamicAction actionFactory) { RoutingModuleEx.Engine.Add( PatternRule.Build(action, url, typeof(ActionRoutingController), action)); ActionRoutingController.Routing.Add(action, actionFactory); }
And get basically the same result.
Again, all of this is notepad code, just doodling away, but it is nice to see that all the building blocks are there.
Comments
Comment preview