After a little bit of work with MVC, you get infected with the spirit of clean code
and begin to desire even more ways of eliminating repetition. You’ve got partials
and html helpers. Still you are hungry. SubControllers are the dish that will fill
you up.
Why SubControllers?
There’s a design decision here. The question is why are we designing with subcontrollers?
To understand the rationale, let’s look at the qualities of the various sub-view options.
-
Partial Views – partial views are probably the most useful method for re-using view
output. They are very easy to set up, and are very versatile. However, they don’t
contain logic. They are intended entirely as a slave to the Action Controller. They
get their model from the controller and simply render it. This means that the controller
needs to know and provide everything the partial view needs. Not good for something
like a login status control, which is an separate concern from most controllers.
-
HTML Helpers – HTML helpers get used a lot, and they are very helpful for creating
your own ‘controls’ to use in your pages. They do not, however, support using a view
template. This means you’ve got to create them and test them with tests for emitted
markup. This adds a lot of complexity if you are trying to do something more than
create a html rendering function. Not a good place to insert complex view logic, like
a shopping cart status widget, or anything with a table or list. The HTML helper also
still gets it’s data from the master view/controller, so it fails at separating concerns.
-
Html.RenderAction() – RenderAction is the once and future solution to a number of
problems. Or so I hear. I have trouble getting excited about using it right now. Currently,
it is not baked into MVC, but rather is in the separate ‘MVC Futures’ assembly, where
it is unsupported. Rumor is it is buggy and unsecure. In the future it may be changed
or dropped or renamed or anything. ScottGu has promised in a blog comment that it
is due for inclusion in MVC 2. However, I don’t program to unreleased Microsoft products.
Maybe later, but for now…
-
MvcContrib SubController – Jeff Palermo implemented subcontrollers for MvcContrib
to give us a working solution—now—for reusable view/controller code. Subcontrollers
have their own model, ViewData, are nestable, and can use view templates for their
output. If you have a job that goes beyond a partial view, or a html helper, SubControllers
are a peach.
Setting Up Your Project
1) Add a reference to MvcContrib.
2) Create a class called StructureMapSubControllerBinder. This is only required if
you’re using StructureMap to do your IOC for you. You can use the base SubControllerBinder
from MvcContrib, or create your own version for your IOC tool.
public class StructureMapSubControllerBinder
: SubControllerBinder
{
public override object CreateSubController(Type
destinationType)
{
object instance = ObjectFactory.GetInstance(destinationType);
if (instance == null)
{
throw new InvalidOperationException(destinationType
+ " not registered with StructureMap");
}
return instance;
}
}
3) Make the SubControllerBinder your default binder.
ModelBinders.Binders.DefaultBinder = new StructureMapSubControllerBinder();
4) Add a reference to the MvcContrib namespace to the Pages section of Web.Config.
This will save you putting a lot of namespace tags in your views.
<add namespace="MvcContrib"/>
Creating a SubController
1) Create a ~/Controllers/SubControllers folder. This is completely optional. If you
have a bigger project you might want to make multiple subcontrollers folders for different
areas.
2) Create a SubController class. The action needs to have the ‘same’ name as the class.
It also subclasses from SubController.
using System.Web.Mvc;
using MvcContrib;
namespace NWIS.Business.Web.Controllers.SubControllers
{
public class DemoSubController
: SubController
{
public ViewResult Demo()
{
return View();
}
}
}
3) Create a View subfolder. Using the ‘Add View’ context menu from the controller
won’t work because the ‘sub’ in the class name. Just create the folder yourself. ~/Views/Demo.
4) Create a View. Right click on the ~/Views/Demo folder. Select Add->View. Name
the view ‘Demo’. Make it a partial view (.ascx). Go ahead an add some markup to the
view. Whatever you like.
Using the SubController in your View
1) Add an attribute to your Controller class.
[SubControllerActionToViewDataAttribute]
2) Add the subcontroller as a parameter to the action you want to use it in.
public ViewResult
Index(DemoSubController mySubCont)
3) Place the subcontroller output into your view.
<% ViewData.Get<Action>("mySubCont").Invoke();
%>
The name you use here is the name of the parameter to the controller action.
That should be it. You subcontroller can output into your view, and you can use
it in many, many controllers and actions. You can add dependencies directly to the
subcontroller in it’s constructor. You can also pass information from the action to
the subcontroller using properties.
Resources