Monorail Dynamic Actions = Reusable Goodness!
Monorail gives you a nice, easy to use MVC implementation on top of ASP.NET. Dynamic Actions is a feature that allows code reuse across controllers without restricting you to specific inheritence model.
In classic MVC style, Monorail controllers expose actions. Any "public void" method on a controller class is considered an action:
public void Index()
{
...
}
public void NotOpen()
{
...
}
public void OrderClosed()
{
...
}
Sometimes, you may want the same action available on multiple controllers. Since a controller is simply a .NET class, you could use inheritence, but more often than not, that doesn't fit well into your solution.
For my example, I've got a Monorail application I wrote for my daughter's grade school. I used the techniques described here to provide a textbox with AJAX autocomplete used to select a particular guardian (NOTE: we have to be politcally correct here as the person who is the actual guardian may not be the parent. Hence, we use the term "Guardian"). It works great and was easy to set up. The NVelocity template code is simply:
Find by Guardian Name (Last, First): $AjaxHelper.InputTextWithAutoCompletion("parentsName", "FindGuardians.ashx", "%{}", "%{}")
My "FindGuardians" action (method) is pretty simple too:
public void FindGuardians(string parentsName)
{
if (parentsName.Length > 0)
{
Guardian[] g = Guardian.FindAllStartingWith(this.CurrentSchool, parentsName);
PropertyBag["parents"] = g;
}
}
The "CurrentSchool" property is on a base class from which all of my controllers derive from. It lets all of the other controllers know what school the current user is enrolled in. And, as you saw in the previous link, our view for "FindGuardians" that renders the autocomplete list is simple:
<ul>
#foreach($parent in $parents)
<li>$parent.ContactInfo.LastName, $parent.ContactInfo.FirstName</li>
#end
</ul>
So the first time I needed this lookup on a different view, I just did a quick copy paste. C'mon -- a 9-line method and a single view? Just copy it.
Well, as time moved on, I needed this lookup in a number of different controllers. I turned to Monorail's Dynamic Actions and Shared Views to get me some reusable goodness!
A Dynamic Action is an action that is added to the controller during initialization. It's an action on the controller, accessible by a name, but is not one of the "public void" methods compiled with the class. It's a class in itself that implements IDynamicAction. When the action is called, the IDynamicAction.Execute of the dynamic action is called.
Converting the controller code to a dynamic action was pretty straightforward. I had to deal with pulling the "parentsName" out of the request manually since I'm not going through SmartDispatcherController. And I moved the FindGuardians view code to a shared view so it could also be accessed by the dynamic action from anywhere. Here's the final code:
public class FindGuardiansAction : IDynamicAction
{
#region IDynamicAction Members
public object Execute(IEngineContext engineContext, IController controller, IControllerContext controllerContext)
{
ControllerBase baseCtlr = (ControllerBase)controller;
string parentsName = engineContext.Request.Params["parentsName"];
if (parentsName.Length > 0)
{
Guardian[] g = Guardian.FindAllStartingWith(baseCtlr.CurrentSchool, parentsName);
baseCtlr.PropertyBag["parents"] = g;
}
baseCtlr.RenderSharedView("Shared/Lookups/FindGuardians", true);
return null;
}
#endregion
}
One last thing I did was create a static method on the FindGuardiansAction class to make installation easy:
public static void InstallOn(Controller controller)
{
controller.DynamicActions["FindGuardians"] = new FindGuardiansAction(includeStaff);
}
Now when I need the FindGuardians action on a controller, I call the InstallOn during initialization of the controller:
public override void Initialize()
{
base.Initialize();
FindGuardiansAction.InstallOn(this);
}
There's a couple of other AJAX autocomplete lookups I've created using these methods and it makes my life so much easier and the development is so much quicker.