Please excuse the long post. There is a question, just bear with me.
A Little Context
We have a site which is required to adapt considerably based on a variety of user settings, the group the user belongs to, where they come from and other things. We used to include the relevant bits on the model for the page, so if the page had a table which would show if the user was over a certain age, then on the model we would do something like:
//model
public PageModel
{
public bool ShowTable {get;set;}
}
//controller
public PageController
{
public ActionResult ShowPage()
{
var model = new PageModel() {
ShowTable = User.Age > 21
};
return View(model);
}
}
//view
@if(Model.ShowTable)
{
<table>Some Html here</table>
}
This quickly became very complicated to know what we should be showing which users. To try and address this problem, we centralised all the logic about when a particular thing should be shown or hidden. We called this class UserConfiguration
and it (mostly) just contained a series of functions returning booleans indicating what should be shown. This allowed us to set up a series of Specs and Tests of what a user should be shown. This UserConfigratuion
was then put on a base class, which all page models were required to inherit from, so what we have currently is something like this:
//UserConfiguration
public UserConfiguration
{
private readonly User _user;
public UserConfiguration(User user) {
_user = user
}
public bool ShowTable() {
return _user.Age > 21;
}
}
//model base
public ModelBase
{
public UserConfiguration {get;set;}
}
//model
public PageModel : ModelBase
{
// whatever data is needed for the page
}
//controller
public PageController
{
public ActionResult ShowPage()
{
var userConfiguration = new UserConfiguration(User);
var model = new PageModel {
UserConfiguration = userConfiguration
};
return View(model);
}
}
//view
@if(Model.UserConfiguration.ShowTable())
{
<table>Some Html here</table>
}
This has helped, mostly because it allowed us to create a series of tests of what a user should and should not see etc. However, it is not a very clean solution, having to put together this additional class and include it on the model. It also has ramifications for rendering Partial Views. If the model has a property IEnumerable<Foo> Foos
on it, that we want to render in a partial, but that partial also relies on the user configuration, we have a problem. You can't just pass the foos to the Partial as the model, because then the partial doesn't have access to the UserConfiguration
. So what would be the best way to access this information. The way I see it, in the context of asp.net MVC there are 4 ways available:
1) Have a new model for the partial e.g.
// parent view
@{
var foosModel = new foosModel {
Foos = Model.Foos,
UserConfiguration = Model.UserConfiguration
}
}
@Html.RenderPartial("FooList", foosModel)
// child partial view
@if(Model.UserConfiguration.ShowTable) {
foreach(var foo in Model.Foos) {
//Some HTML
}
}
This is the probably the "most pure" solution, adhering best to the principles of MVC, but involves a lot of (arguably unnecessary) models, causing project bloat.
2) Expose the UserConfiguration through the ViewData. e.g :
// parent view
@Html.RenderPartial("FooList", Model.Foos, new ViewDataDictionary { { "UserConfiguration", Model.UserConfiguration } })
// child partial view
@{
var userConfig = (UserConfiguration)ViewData["UserConfiguration"];
}
@if(userConfig.ShowTable) {
foreach(var foo in Model) {
//Some HTML
}
}
I don't really like this because it isn't type safe and relies on magic strings to get it from the ViewData.
3) Put the UserConfiguration in the ViewBag. Same issues as above really
4) Modify the page model, and expose the UserConfiguration via a property of the page itself, as per http://haacked.com/archive/2011/02/21/changing-base-type-of-a-razor-view.aspx/
I feel since the UserConfiguration is ambient contextual information it makes sense to expose it via the class as in option 4 above. Is there generally accepted best practice in MVC for exposing this kind of data? Has anyone tried anything like option 4 in the past and were there any 'gotcha's'
tl;dr: What is the best way of exposing contextual information to the views on your site, in MVC in general or asp.net MVC in particular?