Razor code to programmatically hide global menu items? - asp.net-mvc

I'm an experienced .NET programmer, but I'm new to this whole web programming thing. My ASP.NET MVC website has a global layout that includes some content (menu links at the top of the page) that I want to hide under conditions that are detected dynamically by controller code.
My inclination -- the simple approach that uses the tools I've learned about thus far -- is to shove a Boolean HideGlobal value into the ViewBag, and to put the global markup in _Layout.cshtml that I want to hide inside of an #if (ViewBag.HideGlobal){} block.
I just want to know if this is the "proper" way to do it, or is there some Razor magic that I should be using for reasons that are not yet evident to me?

I dislike using the view model of the action outside of the view returned by the action. Using base view model for this scenario feels very clunky.
I believe it's cleaner and more obvious to just use a separate (child) action that contains the logic for specifying how the global menu should be displayed. This action returns the global menu view. Call that action from your layout page.
Or you can create an action for the entire header where the menu's state is determined -- or do an if/else to render a partial view of the global menu.
The example below encapsulates the needs of a header/global menu and offers a future proof way of changing your header/menu with minimal effect on your code infrastructure (base view model).
~/Controllers/LayoutController.cs
public class LayoutController : Controller
{
[ChildActionOnly]
public ActionResult Header()
{
var model = new HeaderViewModel();
model.ShowGlobalMenu = ShowGobalMenu();
return View(model);
}
}
~/Views/Layout/Header.cshtml
#model HeaderViewModel
#{
Layout = "";
}
<header>
Home
#if(Model.ShowGlobalMenu)
{
<ul>
<li>Link</li>
<li>Link</li>
<li>Link</li>
<li>Link</li>
</ul>
}
</header>
~/Views/Shared/_Layout.cshtml
<html>
<body>
#Html.Action("Header", "Layout")
<p>Stuff</p>
</body>
</body>

What you've described (putting a bool into the ViewBag) will work fine. But personally, I like the strongly-typed model experience, so when I want to have UI logic like what you're describing in the master/layout page (which is pretty much always), I prefer to put those flags into a base model that my other models inherit from.
public class BaseModel
{
public bool HideGlobal { get; set; }
}
And at the top of the _Layout.cshtml page, I specify that I'm expecting a BaseModel:
#model Company.Project.BaseModel
Other views can, of course, require other model types, but if they're using this layout, those model types should derive from BaseModel.
Finally, when I want to check the flag, rather than having a mysterious, undocumented field in the ViewBag, I've got the lovely intellisense and feel-good strongly-typed member of the model:
#if (!Model.HideGlobal)
{
<div>...</div>
}
Edit: I should probably add that it's often nice to also have a base controller whose job it is to populate the fields in BaseModel. Mine usually look like this:
public class BaseController : Controller
{
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
var result = filterContext.Result as ViewResultBase;
if (result != null)
{
var baseModel = result.Model as BaseModel;
if (baseModel != null)
{
//Set HideGlobal and other BaseModel properties here
}
}
}
}
First answer here, be gentle :-)

This type of thing is usually based on user permissions which would be much better in an Action Filter instead of the base controller.
Yes you would have to use the ViewData or ViewBag but it's really not part of the view/model it's higher up in your _layout.cshtml
Action Filter
public class UserAccessAttribute : ActionFilterAttribute
{
public override void OnActionExecuting( ActionExecutingContext filterContext ) {
var hasAccessToMenu = /* Code */;
filterContext.Controller.ViewData["GlobalVisible"] = hasAccessToMenu;
}
}
View (_layout.cshtml)
#if(ViewData["GlobalVisible"])
{
<ul>
<li>Link</li>
<li>Link</li>
<li>Link</li>
<li>Link</li>
</ul>
}
This gives you the advantage of separating this logic out of the controller and because it's global that's important.
Also I've used ViewData in my example but using ViewBag is just as good.

Related

How to manage models that are being displayed in a shared layout?

I'm working on a new MVC/Razor website, and we've gotten to the point where we need to put in a real menu structure. Currently, our menus are hardcoded into the global shared layout, we need to populate the menus from code, so that we can determine, after login, which menus will be displayed for a user.
We'll want the menus to appear on pretty much every page, except for login and a few other special cases. So we'll continue to one them to be rendered in the shared layout.
It looks simple enough to create an #Html.DisplayFor template that would render a menu item. The question is, how to get data to it?
The shared layout is used with quite a number of views, handling a number of different models, being loaded from a number of controllers.
Adding a List member to each model, and then populating it in each controller, seems tedious, bothersome, and error prone.
Or we could skip adding the collection to each model, and instead have each controller stick it in the ViewBag. That doesn't seem all that great, either.
The only possibility I've been able to dream up, to keep from having to repeat this for every controller, is to define a common base class, derived from Controller, that all of the controllers that use the shared layout could derive from, in turn, that would stuff the MenuItem collection into the ViewBag.
But I'm wondering if I'm missing something. Is there some preferred way of dealing with this situation?
Shared/base view models is a way to go as you mentioned, but in my opinion its not very "single responsibility". Having to inherit from a base model and add menu items on each page is tedious as you mentioned.
If I was you I would use:
#Html.Action("action", "controller")
https://msdn.microsoft.com/en-us/library/ee703457.aspx
This would call an action method, and then you can bind a specific menu model and also return a specific partial view that would be rendered in the view that called it
#Html.Action("TopMenu", "Controller")
[ChildActionOnly]
public PartialViewResult TopMenu()
{
return PartialView(new MenuModel());
}
Create a TopMenu.cshtml
You can even go as far as passing in values into the controller action to modify the output model/view.
You can call #Html.Action from your layout/shared view.
EDIT
Added [ChildActionOnly] as highlighted in comment as this prevents access to the action unless it was called from a parent action and should be used here
If I understand your question correctly...
I prefer to have a MasterViewModel that every ViewModel for every page inherits from. The MasterViewModel holds things common to every page, like the user's name, navigation rules, etc. The shared layout then uses #model MasterViewModel, which is populated by an ActionFilterAttribute.
public class MasterViewModel
{
public IEnumerable<string> NavItems {get; set;}
}
public class HomeIndexViewModel : MasterViewModel
{
// stuff for the home view
}
I then have a MasterViewModelAttribute:
public class MasterViewModelAttribute : ActionFilterAttribute
{
[Inject]
public IDatabase Database { get; set; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
ViewResultBase viewResult = filterContext.Result as ViewResultBase;
// not a view result
if (viewResult == null)
{
return;
}
MasterViewModel model = viewResult.ViewData.Model as MasterViewModel;
// not a MasterViewModel view model
if (model == null)
{
return;
}
model.NavItems = // set your nav items
}
}
Then, every controller derives from a base controller, which implements the attribute:
[MasterViewModel]
public class BaseController : Controller
{
}
And for each controller:
public class HomeController : BaseController
{
public ActionResult Index()
{
return View(new HomeIndexViewModel());
}
}
And finally, the layout:
#model MasterViewModel
// omitted
<div class="nav">
#foreach (var item in Model.NavItems)
{
<a></a>
}
</div>
The thing I like about the pattern is that you still get strong type-checking by using ViewModels (as opposed to the ViewBag), and you don't have to deal with all the extra nav stuff when you want to unit test an action, for example.
This also has an advantage over using partial views and #Html.Action() in that you don't incur a second request to the server for every page load.

In ASP.Net MVC, how can you manage dependencies between views and layout pages?

We're working with ASP.Net MVC and Google Publisher Tags (GPT).
GPT requires that you create javascript "slots" in the <head> and some html+script that goes into the <body>. The key dependency here is that in both places is an id that must match. So the head will contain some Javascript that includes something like:
<head>
<script>
...
DefineSlot('div-gpt-ad-123456789-0', 'foo', 'bar')
...
</script></head>
and
<body>
...
<div id='div-gpt-ad-123456789-0'>
<script>
...
Display('div-gpt-ad-123456789-0')
...
</script>
</div>
...
How can we manage the dependencies between these 2 pieces code? The critical piece is that the id of both parts must match.
We want to use MVC to create these pieces of code dynamically. So in any view, partial or layout, I will be able to add a helper call that might look like:
#Html.CreateAd(size, "foo", "bar")
#Html.CreateAd can be called anywhere in a view, partial view, layout, or nested layout.
How do you use ASP.Net MVC to program the code that goes into <head>?
Any suggestions are appreciated. I'm just looking for direction, not a full blown solution.
Many Thanks.
You have a few different ways to do this.
You can add the id's to the ViewData or a base viewmodel.
Then OnActionExecuting or OnActionExecuted in a base controller or via actionfilters, you can add your data to whichever place you prefer to. If you need examples for this, please leave a comment on this answer.
Then, your helpers (one for each section) you can read from one of the 2 sources you decided on. I have gone both routes. If all of your pages are going to have an ad, then I would lean towards the base ViewModel. If it's more of a rare occurrence ViewData would be more appropriate.
To access the viewdata within an htmlHelper Extension method:
public static class HtmlExtension
{
public static MvcHtmlString RenderAdHead(this HtmlHelper h)
{
h.ViewContext.ViewData.Model // a test and cast here
h.ViewContext.ViewData["AdIdentifier"] // test for null and cast here
string tags = String.Empty;
//build up string to resemble your script/html tags using either of the
//sources above, so long as either source is not empty.
return new HtmlMvcString(tags);
}
}
And some code for a filter:
public class AdvertisingFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
List<String> idList = null; // list of string might not be adequate. depends on your implementation
//read advertising ids from your datastore or wherever you have.
filterContext.Controller.ViewData["advertisingFilter"] = idList;
}
}
The basecontroller is pretty much the same, instead you have the controllercontext directly. You just have to be sure all your controllers inherit from them.

Reusable Content Box Data In ASP.NET MVC?

If I create a PartialView for a box that holds a header, image and content what is the best way to store the content without using a database?
Example: TurboTax
I doubt the content for the boxes in the sidebar are stored in a database but to make reusable code it would be beneficial to create the structure in a PartialView and populate the content areas. I can create a PartialView and pass a Model from the parent Controller to the PartialView but then I would be stuck copying and pasting that same content if I wanted to use the same box on another page.
For fixed content you might want to think about using XML+XSLT or even HTML snippets in the file system and simply rendering them. An HtmlHelper method might make more sense for this than a partial view - Html.RenderXml() or Html.Include(). The only real difference between these and partial views is that the view engine isn't invoked since there aren't any substitutions. I do this sort of thing with my privacy policy and terms and conditions. I'd certainly consider keeping these cached.
If these really are templates and you are just substituting content, then I think the partial view works well and I would consider putting the data in a database, again, maybe using caching if I found that performance suffered. You could use this in combination with the former -- say keep your images/xml in the file system and a pointer to them in the database so you know which ones to pick in the partial.
Passing data to partial view that is used in many places can be done in many ways:
Create base model class for all your models. In base class define PartialModel property which will be holding model for partial view (there may be many of them if use have many partial views). Now you can populate the PartialModel property in controller action, but to make code more reusable you can create your own Action Filter which will insert the partial view data just after the action method is executed (but before the model is passed to the view)
public class PartialViewModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
BaseViewModel model;
if (filterContext.Controller.ViewData.Model == null)
{
model = new BaseViewModel();
filterContext.Controller.ViewData.Model = model;
}
else
{
model = filterContext.Controller.ViewData.Model as BaseViewModel;
}
model.PartialModel = new PartialModel(...) // Partial model initialization
base.OnActionExecuted(filterContext);
}
}
Then you can use it like:
[PartialViewModel]
public ActionResult Index()
{
//...
}
Another option: you can create BaseController class for all your controllers and create PartialModel on base controller initialization. Then PartialModel can be stored in ViewData[] dictionary. Because using ViewData dictionary in views is bad, create extension method on HtmlHelper like:
public static PartialModel GetPartialModel(this HtmlHelper helper)
{
return helper.viewContext.ViewData["PartialModel"] as PartialModel
}
So you could obtaint the model this way:
<% Html.RenderPartial("MyPartial", Html.GetPartialModel()); %>

Accessing Application Settings in ASP.NET MVC view

I'm trying to build a global menu into my ASP.NET MVC site.master, and I was wondering how I could go about accessing the Application Settings property from the site.master markup? Previously I probably would have instantiated a config object from my site.master's code-behind and then set a public property. But now I'm scratching my head...must need more coffee.
UPDATED with answer code
Added a string setting to the application propererties called baseurl and gave it a value of "http://mysite.com"
Made a model class of GlobalMenu.cs
public class GlobalMenu
{
private string _baseurl;
public string baseurl
{
get { return _baseurl; }
set
{
_baseurl = value;
}
}
}
Created a base controller class named BaseController and inherited from Controller, and overroad OnActionExecuted thusly:
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
string baseurl = Properties.Settings.Default.baseurl;
GlobalMenu menumodel = new GlobalMenu();
menumodel.baseurl = baseurl;
ViewData["menudata"] = menumodel;
base.OnActionExecuted(filterContext);
}
Created a partial view called ViewGlobalMenu in the Shared folder that was strongly typed to GlobalMenu that looks like this...but with more stuff obviously:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MyApp.Web.Models.GlobalMenu>" %>
Finally in Site.Master I added this to where I wanted the menu to show:
<%Html.RenderPartial("ViewGlobalMenu", (MyApp.Web.Models.GlobalMenu)ViewData["menudata"]); %>
Here's the strategy that I would probably use. Create a base controller from which your other controller's will derive and have it derive from Controller. Override the ActionExecuted method in the base controller and have it access the application settings (and probably cache them). Generate ViewData for your menu as strongly-typed menu model class assigned to a particular key in the ViewData. You only need to provide the model to actions that are returning a ViewResult (and, perhaps, PartialViewResults).
Create a strongly-typed partial view that implements the global menu markup using the menu model class. Include this in the MasterPage definition via RenderPartial. Pass the ViewData item corresponding to the key as the Model to the partial view so that you can use the model's properties in your menu.
This should do the trick from within the View -->
#System.Configuration.ConfigurationManager.AppSettings["AppSetting"]

How to show different view sections in an object oriented way

I'm developing a website in ASP.NET MVC where I would like to show different sections of a view for users with different security levels. In essence, the views are the same, but users with higher levels of security must be able to see sections that shouldn't be seen by users with security levels above of, for example, administrators.
I don't know how to do this in an object oriented way. I think this can be done with inheritance, but I don't know how to implement inheritance in the view.
Also, I know I can code a lots of ifs, doing something like
<% if (User has some security level) { %>
<span>show this info</span>
<% } %>
but this doesn't smell well. The fact is that I don't know how to use object oriented principles or design for this task.
I think this is a common task, so I think there is a pattern to accomplish this task. For example, Stackoverflow does this when shows some options (edit, delete, etc) for the user who posted a question (or answer or comment) and hides the same options to everybody else.
Depending on the complexity of what you are doing, the if statement route may be sufficient. If not, then you could look at using partial views and write an HtmlHelper extension that allow you to render a partial based on a particular role. It might look something like this:
<% Html.RenderPartialWithRole( "AdminSection",
Model,
ViewData,
User,
"Administrator",
null ) %>
public static void RenderPartialWithRole( this HtmlHelper helper,
string partialName,
object model,
ViewDataDictionary viewData,
IPrincipal user,
string role,
object htmlAttributes )
{
if (user != null && !string.IsNullOrEmpty(role) && user.IsInRole(role))
{
helper.RenderPartial( partialName, model, viewData, htmlAttributes );
}
}
Using a strongly typed view, have a Boolean property on your model like ShowSection (one for each section, logic to set in your controllor). Have the sections in divtags with good Ids. Then use JavaScript or jquery to set the div tags display style based on the Boolean property.
You could have all the sections in partials, with views built to include the Sections available to the different permission levels. So one for admins, one for every level. Then your Controller has the logic to decide which view to use. So any OO part would be in the controller, not the view.
This isn't an object-oriented approach, but it's related. The interesting part of this question is how to get ride of the if-statements. The usual method to get rid of if's or case's is to use a lookup table of criteria and effects. There are other techniques that use this same idea, such as data-directed programming (http://en.wikipedia.org/wiki/Data-directed___programming) and dispatch tables (http://en.wikipedia.org/wiki/Dispatch_table). Many language implementations use type dispatch tables to implement virtual method calls.
Assuming partial views are OK for this problem, the lookup table could be a list of pairs. The first element of the pair is a role name to check against the current user. The second element of the pair is the name of teh partial view to render when the role check succeeds.
We initialize the table in the controller (or whereever) and assign it to ViewData, then use Html.RenderViewByRole to choose and render the correct partial view:
<% Html.RenderPartialByRole(User, (List<Dispatch>)ViewData["rolePartial"]); %>
public static class MyHelper {
public static void RenderPartialByRole(this HtmlHelper helper, IPrincipal user, List<Dispatch> rolePartial) {
foreach (Dispatch d in rolePartial) {
if (d.CheckRole(user)) {
helper.RenderPartial(d.PartialName);
break;
}
}
}
}
public class Dispatch {
string _roleName;
string _partialName;
public Dispatch(string roleName, string partialName) {
_roleName = roleName;
_partialName = partialName;
}
public bool CheckRole(IPrincipal user) {
return user.IsInRole(_roleName);
}
public string PartialName {
get { return _partialName; }
}
}
public class HomeController : Controller {
List<Dispatch> rolePartial = new List<Dispatch>();
private void InitTable() {
rolePartial.Add(new Dispatch("admin", "adminPartial"));
rolePartial.Add(new Dispatch("report", "reportPartial"));
rolePartial.Add(new Dispatch("guest", "guestPartial"));
}
public HomeController() {
InitTable();
}
public ActionResult Index() {
ViewData["rolePartial"] = rolePartial;
return View();
}
}

Resources