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

1

28

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!

Kush answered 10/6, 2009 at 15:52 Comment(0)
B
45

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.

Barcus answered 10/6, 2009 at 16:20 Comment(6)
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.Kush
I'm using the second solution in my projects and it works smoothlyBarcus
@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!Polyploid
Is it possible to set the attribute on an action level or would I have to implement the filter otherwise?Frawley
Yes, you can set the attribute on controller level or action levelBarcus
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?Anemometry

© 2022 - 2024 — McMap. All rights reserved.