ASP.NET MVC - Set ViewData for masterpage in base controller

Razzie picture Razzie · Jun 10, 2009 · Viewed 13.2k times · Source

I'm using a masterpage in my ASP.NET MVC project. This masterpage expects some ViewData to be present, which displays this on every page.

If I don't set this ViewData key in my controllers, I get an error that it can't find it. However, I don't want to set the ViewData in every controller (I don't want to say ViewData["foo"] = GetFoo(); in every controller).

So, I was thinking of setting this in a base controller, and have every controller inherit from this base controller. In the base controller default constructur, I set the ViewData. I found a similar approach here: http://www.asp.net/learn/MVC/tutorial-13-cs.aspx. So far so good, this works... but the problem is that this data comes from a database somewhere.

Now when I want to Unit Test my controllers, the ones that inherit from the base controller call its default constructor. In the default constructor, I initialize my repository class to get this data from the database. Result: my unit tests fail, since it can't access the data (and I certainly don't want them to access this data).

I also don't want to pass the correct Repository (or DataContext, whatever you name it) class to every controller which in turn pass it to the default controller, which I could then mock with my unit tests. The controllers in turn rely on other repository classes, and I would end up passing multiple parameters to the constructor. Too much work for my feeling, or am I wrong? Is there another solution?

I've tried using StructureMap but in the end I didn't feel like that is going to fix my problem, since every controller will still have to call the base constructor which will initialize the repository class, so I can't mock it.

This is a similar question but I find no satisfactory answer was given. Can I solve this in a neat way, maybe using StructureMap as a solution? Or should I jsut suck it and pass a Repository to every controller and pass it again to the base controller? Again, It feels like so much work for something so simple. Thanks!

Answer

eu-ge-ne picture eu-ge-ne · Jun 10, 2009

I see two options:

First:

Set the ViewData for MasterPage in YourBaseController.OnActionExecuting() or YourBaseController.OnActionExecuted():

public class YourBaseController : Controller
{
    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // Optional: Work only for GET request
        if (filterContext.RequestContext.HttpContext.Request.RequestType != "GET")
            return;

        // Optional: Do not work with AjaxRequests
        if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
            return;

        ...

        filterContext.Controller.ViewData["foo"] = ...
    }
}

Second:

Or create custom filter:

public class DataForMasterPageAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // Optional: Work only for GET request
        if (filterContext.RequestContext.HttpContext.Request.RequestType != "GET")
            return;

        // Optional: Do not work with AjaxRequests
        if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
            return;

        ...

        filterContext.Controller.ViewData["foo"] = ...
    }
}

and then apply to your controllers:

[DataForMasterPage]
public class YourController : YourBaseController
{
    ...
}

I think the second solution is exactly for your case.