How to create a strongly typed master page using a base controller in ASP.NET MVC
Asked Answered
R

2

20

Following the NerdDinners example, I am interested in creating a strongly typed Master Page. In order to achieve this, I use a base controller which retrieves the data for the master page. All other controllers inherit this class. Similarly, I have ViewModels for the master page and any other views. The view ViewModel classes inherit from the master page's ViewModel.

Question

How should a child controller ensure that the master page's data is passed to the View without setting the properties of its ViewModel that pertain to the master page itself?

My the master page will display a number of buttons, which are determined in an XML file, hence the Buttons class that I am populating.

MasterPage ViewModel Code Snippet

using System.Collections.Generic;

namespace Site1.Models
{
    public class MasterViewModel
    {
        public List<Button> Buttons{set; get;}
    }
}

View ViewModel

namespace Site1.Models
{
    public class View1ViewModel : MasterViewModel
    {
        public SomeDataClass SomeData { get; set; }
    }
}

Base Controller

using System.Collections.Generic;
using System.Web.Mvc;
using Site1.Models;

namespace Site1.Controllers
{
    public abstract class BaseController : Controller
    {
        protected MasterViewModel model = new MasterViewModel();

        public BaseController()
        {
            model.Buttons = new List<Button>();
            //populate the button classes (doesn't matter how)
            PopulateButtons(model.Buttons);
        }
    }
}

View's controller:

using System.Web.Mvc;

namespace Site1.Controllers
{
    public class View1Controller : BaseController
    {
        public ActionResult Index()
        {
            Models.View1ViewModel viewModel = new Models.View1ViewModel();
            SomeDataClass viewData = new SomeDataClass()
            //populate data class (doesn't matter how)
            PopulateDataClass(viewData);
            viewModel.SomeData = viewData;
            //I WANT TO ELIMINATE THE FOLLOWING LINE!
            viewModel.Buttons = model.Buttons;
            return View("Index", viewModel);
        }
    }
}

The master page inherits System.Web.Mvc.ViewMasterPage<Site1.Models.MasterViewModel>.

The view inherits System.Web.Mvc.ViewMasterPage<Site1.Models.View1ViewModel>.

Rexford answered 20/4, 2009 at 13:25 Comment(1)
Moved Solution out of question into accepted solution where it belongs. This cleanly separates the Question/Answer model that Stack Overflow uses.Semibreve
T
17

You could create an after action executed filter which looks for a model of that type and sets the properties accordingly, perhaps by calling a base controller function. You would then put the filter on the base class, and all actions would see it automatically.

The action filter attribute gets the controller's ViewModel, and passes it to the controller's SetModel function:

using System.Web.Mvc;
using Site1.Controllers;

namespace Site1.Models
{
    public class MasterAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            base.OnActionExecuted(filterContext);

            MasterViewModel viewModel = (MasterViewModel)((ViewResultBase)filterContext.Result).ViewData.Model;

            BaseController controller = (BaseController)filterContext.Controller;
            controller.SetModel(viewModel);
        }
    }
}

This function is added to the BaseController:

public void SetModel(MasterViewModel childViewModel)
{
    childViewModel.Buttons = model.Buttons;
}
Tillio answered 20/4, 2009 at 15:31 Comment(4)
Before I try this out, it seems a suprisingly complicated solution for what is a very common scenario. Or do people not strongly type their master pages?Rexford
I have not seen many strongly typed master pages. However, you would have the same issue of having to set common properties with ViewDataDictionary. It turns out, though, that creating an action filter is really, really easy. I think you're overestimating the complication.Tillio
Ah, I see you've updated the question with an implementation of this. Simple, wasn't it!Tillio
Thanks for this. Most excellent. I needed to render a couple of things differently on the master page, and this tip got me there.Wanda
M
6

Rather than creating an attribute, why not just override Controller.OnActionExecuted and put the initialization code there? Seems a bit simpler.

Motheaten answered 27/4, 2009 at 4:40 Comment(2)
Here's a consideration from MSDN: "If this method is overridden in a derived Controller class, it will be called for every action method in the class. For more flexibility, derive a class from ActionFilterAttribute and override this method in the derived ActionFilterAttribute class."Incongruity
Link to MSDN documentation for Controller.OnActionExecuted containing the quote Aaron references in above comment.Bottleneck

© 2022 - 2024 — McMap. All rights reserved.