views:

3094

answers:

1

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!

+22  A: 

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.

eu-ge-ne
hmm, that looks promising. I'm at home now so will test this tomorrow, though I do feel the second one is going to work (keeping my fingers crossed, already spent too many hours of trying all sorts of things).I like the AOP style of it.
Razzie
I'm using the second solution in my projects and it works smoothly
eu-ge-ne
This did it for me. Thanks!
Razzie
@eu-ge-ne: I never understood quite how (or when) to use ActionFilters until I saw your code snippet. I can imagine many uses for them now. :) I've switched my implementation from using a similar architecture to your 'First' approach, and I'm now using the ActionFilter approach. It's working great and cleaned up a lot of other slop in my base controller along the way. Thanks for the great answer!
Luc
Is it possible to set the attribute on an action level or would I have to implement the filter otherwise?
Nick Masao
Yes, you can set the attribute on controller level or action level
eu-ge-ne
Finally tried it and it works. Cheers
Nick Masao
Is there a way or passing the information back to the view as a ViewModel in some way given the controller may already be returning it's own ViewModel? I try and avoid using ViewData[] and I'm wondering if there is a tidier way of dealing with this is your passing back a few values?
Andi