After a little bit of work with MVC, you get infected with the spirit of clean codeand begin to desire even more ways of eliminating repetition. You’ve got partialsand html helpers. Still you are hungry. SubControllers are the dish that will fillyou 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 viewoutput. They are very easy to set up, and are very versatile. However, they don’tcontain logic. They are intended entirely as a slave to the Action Controller. Theyget their model from the controller and simply render it. This means that the controllerneeds to know and provide everything the partial view needs. Not good for somethinglike 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 creatingyour own ‘controls’ to use in your pages. They do not, however, support using a viewtemplate. This means you’ve got to create them and test them with tests for emittedmarkup. This adds a lot of complexity if you are trying to do something more thancreate a html rendering function. Not a good place to insert complex view logic, likea shopping cart status widget, or anything with a table or list. The HTML helper alsostill 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 ofproblems. 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, whereit is unsupported. Rumor is it is buggy and unsecure. In the future it may be changedor dropped or renamed or anything. ScottGu has promised in a blog comment that itis 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 MvcContribto give us a working solution—now—for reusable view/controller code. Subcontrollershave their own model, ViewData, are nestable, and can use view templates for theiroutput. If you have a job that goes beyond a partial view, or a html helper, SubControllersare a peach.
Setting Up Your Project
1) Add a reference to MvcContrib.
2) Create a class called StructureMapSubControllerBinder. This is only required ifyou’re using StructureMap to do your IOC for you. You can use the base SubControllerBinderfrom MvcContrib, or create your own version for your IOC tool.
public class StructureMapSubControllerBinder: SubControllerBinder{public override object CreateSubController(TypedestinationType){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();
<add namespace="MvcContrib"/>
Creating a SubController
1) Create a ~/Controllers/SubControllers folder. This is completely optional. If youhave a bigger project you might want to make multiple subcontrollers folders for differentareas.
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 controllerwon’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. Namethe view ‘Demo’. Make it a partial view (.ascx). Go ahead an add some markup to theview. 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 ViewResultIndex(DemoSubController mySubCont)
3) Place the subcontroller output into your view.
<% ViewData.Get<Action>("mySubCont").Invoke();%>