»
S
I
D
E
B
A
R
«
Adding MvcContrib SubControllers to your ASP.NET MVC Project
Aug 3rd, 2009 by Josh Rivers

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

Adding MVC to an existing ASP.NET Application
Jul 19th, 2009 by Josh Rivers

ASP.NET MVC Has added a very useful new programming model to developing websites in
.NET. There is hype and debate all throughout the interwebs on how great MVC is. And
it all can be yours, with nothing but a ‘File->New’ in Visual Studio….unless you
already have an ASP.NET application. In which case, you need to do a bit more work.

  1. Add project references to:

    System.Web.Abstractions

    System.Web.Mvc

    System.Web.Routing

    image
  2. Create /Controllers, /Views, and /Views/Shared folders in your project

    image
  3. Update web.config

    <?xml version="1.0" encoding="utf-8" ?>
    
    <configuration>
    
    <system.web>
    
    <pages>
    
    <namespaces>
    
    <add namespace="System.Web.Mvc"/>
    
    <add namespace="System.Web.Mvc.Ajax"/>
    
    <add namespace="System.Web.Mvc.Html" />
    
    <add namespace="System.Web.Routing"/>
    
    <add namespace="System.Linq"/>
    
    <add namespace="System.Collections.Generic"/>
    
    </namespaces>
    
    </pages>
    
    <compilation>
    
    <assemblies>
    
    <add assembly="System.Core,
    Version=3.5.0.0,
    Culture=neutral,
    PublicKeyToken=B77A5C561934E089"/>
    
    <add assembly="System.Web.Mvc,
    Version=1.0.0.0,
    Culture=neutral,
    PublicKeyToken=31bf3856ad364e35" />
    
    <add assembly="System.Web.Abstractions,
    Version=3.5.0.0,
    Culture=neutral,
    PublicKeyToken=31BF3856AD364E35"/>
    
    <add assembly="System.Web.Routing,
    Version=3.5.0.0,
    Culture=neutral,
    PublicKeyToken=31BF3856AD364E35"/>
    
    </assemblies>
    
    </compilation>
    
    <httpModules>
    
    <add name="UrlRoutingModule"
    
    type="System.Web.Routing.UrlRoutingModule,
    System.Web.Routing,
    Version=3.5.0.0,
    Culture=neutral,
    PublicKeyToken=31BF3856AD364E35" />
    
    </httpModules>
    
    </system.web>
    
    <system.webServer>
    
    <validation validateIntegratedModeConfiguration="false"/>
    
    <modules runAllManagedModulesForAllRequests="true">
    
    <remove name="ScriptModule" />
    
    <remove name="UrlRoutingModule" />
    
    <add name="ScriptModule" preCondition="managedHandler"
    
    type="System.Web.Handlers.ScriptModule,
    
    System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
    
    PublicKeyToken=31BF3856AD364E35"/>
    
    <add name="UrlRoutingModule"
    
    type="System.Web.Routing.UrlRoutingModule,
    System.Web.Routing,
    
    Version=3.5.0.0, Culture=neutral,
    
    PublicKeyToken=31BF3856AD364E35" />
    
    </modules>
    
    <handlers>
    
    <remove name="WebServiceHandlerFactory-Integrated"/>
    
    <remove name="ScriptHandlerFactory" />
    
    <remove name="ScriptHandlerFactoryAppServices" />
    
    <remove name="ScriptResource" />
    
    <remove name="MvcHttpHandler" />
    
    <remove name="UrlRoutingHandler" />
    
    <add name="ScriptHandlerFactory" verb="*" path="*.asmx"
    
    preCondition="integratedMode"
    
    type="System.Web.Script.Services.ScriptHandlerFactory,
    
    System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
    
    PublicKeyToken=31BF3856AD364E35"/>
    
    <add name="ScriptHandlerFactoryAppServices" verb="*"
    
    path="*_AppService.axd" preCondition="integratedMode"
    
    type="System.Web.Script.Services.ScriptHandlerFactory,
    
    System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
    
    PublicKeyToken=31BF3856AD364E35"/>
    
    <add name="ScriptResource" preCondition="integratedMode"
    
    verb="GET,HEAD" path="ScriptResource.axd"
    
    type="System.Web.Handlers.ScriptResourceHandler,
    
    System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
    
    PublicKeyToken=31BF3856AD364E35" />
    
    <add name="MvcHttpHandler" preCondition="integratedMode"
    
    verb="*" path="*.mvc" type="System.Web.Mvc.MvcHttpHandler,
    
    System.Web.Mvc, Version=1.0.0.0, Culture=neutral,
    
    PublicKeyToken=31BF3856AD364E35"/>
    
    <add name="UrlRoutingHandler"
    
    preCondition="integratedMode" verb="*" path="UrlRouting.axd"
    
    type="System.Web.HttpForbiddenHandler,
    System.Web,
    
    Version=2.0.0.0, Culture=neutral,
    
    PublicKeyToken=b03f5f7f11d50a3a" />
    
    </handlers>
    
    </system.webServer>
    
    </configuration>
  4. Add the following to Global.asax.cs (create a Global.asax if you don’t already have
    one)

    public static void RegisterRoutes(RouteCollection
    routes)
    {
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.IgnoreRoute("{resource}.aspx/{*pathInfo}");
    
    routes.MapRoute(
    "Default", //
    Route name
    
    "{controller}/{action}/{id}", //
    URL with parameters
    
    new { controller = "Home",
    action = "Index", id = "" } //
    Parameter defaults
    
    );
    
    }
    
    protected void Application_Start()
    {
    RegisterRoutes(RouteTable.Routes);
    }
    

    The above is the “standard” routing for a MVC site. It will work, but it might cause
    you trouble if you want the root of your website to still go to a ‘default.aspx’.
    Try this line instead:

    routes.MapRoute(
    "Default",
    
    "MVC/{controller}/{action}/{id}",
    
    new { controller = "Home",
    action = "Index", id = "" } 
  5. Edit your .csproj file by hand

    <ProjectTypeGuids>{603c0e0b-db56-11dc-be95-000d561079b0};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
    
    

    This will tell Visual Studio to act like this is an MVC project. You will get
    the context menu items for MVC in your Controllers and Views directories.

    Note: I’ve had trouble at this point with some Visual Studio installs.
    If you’re having trouble with “The project type is not supported by this installation.”
    messages here, it may be time for a clean install in a fresh VM.
  6. Set Up IIS. If you’re using IIS7, the System.Webserver settings in your Web.Config
    file should have taken care of this step. If you’re going to need to map the wildcard
    URL to aspnet_isapi.dll in order to get all of the MVC routing magic to work.

    image
  7. You might want to copy of the /Views/web.config file from an existing MVC project
    to prevent your views from being viewed directly, rather than through their controllers.
  8. You might also wish to create a /Scripts folder and copy over the contents of
    the /Scripts folder from native MVC project. It has the jquery and MS AJAX javascripts
    that you may be looking for if you’re following a MVC book or tutorial.

Boy howdy. That was a good chunk of work. But now you have the infinite pleasure of
developing in MVC, and your old web site should continue to work under you. Delicious
incremental development goodness.

Resources

»  Substance: WordPress   »  Style: Ahren Ahimsa