Setting the background in an MVC _Layout.cshtml page - asp.net-mvc

Setting every page's background is rather simple in ASP.Net WebForms where you have access to the Page_Load event in the code-behind of a MasterPage but how is this best done in MVC? After spending several hours researching various alternatives I chose to assign the value to the ViewBag through a "base" controller, derive subsequent controllers from that base and then access that value in _Layout.cshtml.
Here is the base controller, in which I assign a url that points to a specific image:
public class BaseController : Controller
{
public BaseController()
{
ViewBag.url = BingImageLoader.getBingImageUrl();
}
}
The next step is to derive subsequent controllers, in this case the HomeController from that base class:
public class HomeController : BaseController
{
public ActionResult Index()
{
return View();
}
.
.
And finally, use the ViewBag in the head element of _Layout.cshtml to set the background-image style property.
.
.
<style type="text/css">
body {
background-image: url(#ViewBag.url);
background-repeat: no-repeat;
background-size: cover;
}
</style>
</head>
This did accomplish what I set out to do; however, along the way there were a number of alternatives indicated, including using ActionFilters. To be honest, creating a CustomActionFilter and using ActionFilterAttributes and overriding OnActionExecuting seems like overkill but sometimes the simplest way is not always the best.
Ultimately, the question comes down to "Is there a better way?" Are there side-effects from introducing an intermediary? If I override my ViewBag.url in the individual controller methods, the image changes accordingly. So I have yet to find any problems but there may be other issues resulting from this approach.
So again, "Is there a better way"?

One possible problem I can see with this approach is if the developer forgets to subclass hist controller from BaseController.
Using a global action filter would ensure that this will never happen and the property will be always available:
public class BackgroundImageFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
context.Controller.ViewBag.url = BingImageLoader.getBingImageUrl();
}
}
which will be registered only once in your Application_Start:
protected void Application_Start()
{
...
// Register global filter
GlobalFilters.Filters.Add(new BackgroundImageFilterAttribute());
}
If you find this filter approach cumbersome as an alternative I can suggest writing a custom Html helper that could be used in your _Layout.cshtml:
<style type="text/css">
body {
background-image: url(#Html.GetBackgroundImageUrl());
background-repeat: no-repeat;
background-size: cover;
}
</style>
which might be defined as a simple extension method:
public static class HtmlExtensions
{
public static IHtmlString GetBackgroundImageUrl(this HtmlHelper html)
{
string url = BingImageLoader.getBingImageUrl();
return new HtmlString(url);
}
}

Related

ASP.NET MVC: handling logic & variables in the _Layout page

In my _layout.cshtml page, I've got some elements that need to be hidden on some pages. I know the pages on which we won't display some parts. For a single page, I could just do this:
#if (ViewContext.RouteData.Values["action"].ToString() != "LogIn") {
<div> .... <div>
}
But that gets messy and long with multiple pages. Someplace, ideally not in the _Layout page, I could build a list of actions, and if the current action is any of them, set a boolean variable (ShowStuff) to false. Then just do this on _Layout:
#if (ShowStuff== true) {
<div> .... <div>
}
I'm just not sure where would be the best-practice way to examine that list of actions and set the boolean. Can the _Layout page have it's own model and controller like a normal view?
Similarly to MikeSW answer, I'd use an action filter, but I would populate ViewData with a specific ViewModel. When you want to display it simply DisplayFor the value, if it's populated the template is used by whatever type the model is, if it's null nothing is displayed. (examples below from memory, may not be exactly correct.)
public BlahModelAttribute : ActionFilterAttribute
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
BlahModel model = Db.GetModel();
filterContext.Controller.ViewData.Set(model);
}
}
ViewData extensions:
public static ViewDataExtensions
{
private static string GetName<T>()
: where T : class
{
return typeof(T).FullName;
}
public static void Set<T>(this ViewDataDictionary viewData, T value)
: where T : class
{
var name = GetName<T>();
viewData[name] = value;
}
public static T Get<T>(this ViewDataDictionary viewData)
: where T : class
{
var name = GetName<T>();
return viewData[name] as T;
}
}
In your view:
#{var blahModel = ViewData.Get<BlahModel>() }
#Html.DisplayFor(m => blahModel)
If devs would stop looking for the 'best way' for every problem they have, that would be great. No best way here, just opinionated solutions. Here's mine: You can create an action filter [ShowNav] and decorate any controller/action you need. That filter will put a boolean into HttpContext.Items . Create then a HtmlHelper which checks for the boolean. Then in _layout, if (Html.CanShowNavig()) { <nav> } . That's the easiest solution that comes to my mind.

How to dynamically change Themes and _Layout in ASP.Net MVC4

I want to be able to change the _Layout.cshtml view based on a setting in my database.
I understand that it is probably done in the _ViewStart.cshml view.
I am using EF 4.2 and want to adapt a solution that will not break any design pattern.
Not sure how to go about doing this in MVC.
In web forms, I could easily do this in the code-behind for the masterpage.
I am doing something like this in my base controller:
public abstract class BaseController : Controller
{
private IUserRepository _userRepository;
protected BaseController()
: this(
new UserRepository())
{
}
public BaseController(IUserRepository userRepository)
{
_userRepository = userRepository;
}
I have looked at FunnelWeb source as well but I am not quite getting how they are injecting things..
Add this code to in the RegisterBundles method of the BundleConfig class. Note that I am creating a separate bundle for each css so that I don't render each css to the client. I can pick which bundle I want to render in the HEAD section of the shared _Layout.cshtml view.
bundles.Add(new StyleBundle("~/Content/Ceruleancss").Include(
"~/Content/bootstrapCerulean.min.css",
"~/Content/site.css"));
bundles.Add(new StyleBundle("~/Content/defaultcss").Include(
"~/Content/bootstrap.min.css",
"~/Content/site.css"));
Then put some logic in the shared_Layout.cshtml to render the appropriate bundle. Since this layout view fires for every page, this is a good place to put it.
I think this approach could be used for branding if you support multiple corps for your app. It could also be used to provide a custom style by user I suppose.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>#ViewBag.Title - Contoso University</title>
#{
if (HttpContext.Current.User.Identity.Name == "MARK")
{
#Styles.Render("~/Content/defaultcss");
}
else
{
#Styles.Render("~/Content/Ceruleancss");
}
}
Old Question but for anyone coming across this question here is a nice solution using Action Filters Attributes
public class LoadUserLayoutAttribute : ActionFilterAttribute
{
private readonly string _layoutName;
public LoadUserLayoutAttribute()
{
_layoutName = MethodToGetLayoutNameFromDB();
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
var result = filterContext.Result as ViewResult;
if (result != null)
{
result.MasterName = _layoutName;
}
}
}
and then, you can add an attribute to your base controller (or action) with this custom attribute:
[LoadUserLayout]
public abstract class BaseController : Controller
{
...
}

Conditionally including stylesheets in the layout depending on the controller's name

I am learning the ASP.NET MVC 3 Framework. In my layout page (_Layout.cshtml), I would like to conditionally include some CSS stylesheets depending on the name of the controller. How do I do that?
You could obtain the current controller name using the following property:
ViewContext.RouteData.GetRequiredString("controller")
So based on its value you could include or not the stylesheet:
#if (ViewContext.RouteData.GetRequiredString("controller") == "somecontrollername")
{
<link href="#Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
}
Or use a custom helper:
public static class CssExtensions
{
public static IHtmlString MyCss(this HtmlHelper html)
{
var currentController = html.ViewContext.RouteData.GetRequiredString("controller");
if (currentController != "somecontrollername")
{
return MvcHtmlString.Empty;
}
var urlHelper = new UrlHelper(html.ViewContext.RequestContext);
var link = new TagBuilder("link");
link.Attributes["rel"] = "stylesheet";
link.Attributes["type"] = "text/css";
link.Attributes["href"] = urlHelper.Content("~/Content/Site.css");
return MvcHtmlString.Create(link.ToString(TagRenderMode.SelfClosing));
}
}
and in layout simply:
#Html.MyCss()
I would use different approach. Define base controller instead and define method SetStyleSheet like:
public abstract class BaseController : Controller
{
protected override void Intialize(RequestContext requestContext)
{
base.Initialize(requestContext);
SetStyleSheet();
}
protected virtual void SetStyleSheet()
{ }
}
In derived classes you can override SetStyleSheet to set something like ViewData["styleSheet"] and use it for example in your master page (_Layout.cshtml).
Darin definitely answered your questions but an alternative would be use the controllers name as the id of some HTML element on your page, which would give you the flexibility of customizing controller-level views but keep your CSS in one file.
<body id="<%=ViewContext.RouteData.GetRequiredString("controller").ToLower() %>">
... content here
</body>
I did another extension method for ControllerContext because ViewContext is aleady derived from it and you can call your method directly.
For example :
public static class ControllerContextExtensions
{
public static string GetControllerName(this ControllerContext helper)
{
if (helper.Controller == null)
{
return string.Empty;
}
string[] fullControllerNames = helper.Controller.ToString().Split('.');
return fullControllerNames[fullControllerNames.Length-1].Replace("Controller",string.Empty);
}
}
And to use this in your _Layout :
#if(ViewContext.GetControllerName() == "MyControllerName")
{
//load my css here
}
You could also pass in your controller name as parameter and make this extension method return a bool.

Help with ViewData asp.net mvc

In my master page I have a menu that uses jquery ui accordion.
What is the best way to specify which item should be active?? the only thing I can think of is
$(document).ready(function() {
$("#accordion").accordion({
collapsible: true,
autoHeight: false,
active:<%=ViewData["active"] %>
});
})
But it seems a little repetitive having to set ViewData["active"] everytime a View is called throughout my whole app... what do you think?
Have you considered putting the accordian in a PartialView? Then you place your code within the PartialView and away from the Master page.
Also you can have a base controller which can set the ViewData for you so now it's in one place.
Edit
It's the same as any other base class. Your controller inherits from Controller so your base class will need to do the same;
using System.Web.Mvc;
namespace MyAppControllers
{
public class ControllerBase : Controller
{
protected override void Execute(System.Web.Routing.RequestContext requestContext)
{
ViewData["ApplicationName"] = CacheHelper.Get().Name;
base.Execute(requestContext);
}
}
}
Now set your controller class to inherit from the ControllerBase class;
public class HomeController : ControllerBase
I've also implemented a CacheHelper but you can obviously use your own flavour of storing the current value.

ASP.NET MVC: Page-specific logic

There has been many discussion on ASP.NET MVC and Codebehind-files, mostly where it has been pointed out that these Codebehind-files are evil.
So my question is, how do you handle page-specific logic?
What we don't want here is spaghetti-code in the inline-code and we don't want page-specific code scattered throughout helper-classes or on top of the HTML-helper-class.
An example would be:
<% for(int i = 0; i < companyList.Count; i++) { %>
RenderCompanyNameWithRightCapsIfNotEmpty(company, i)
<% } %>
With accompanying codebehind:
private string RenderCompanyNameWithRightCapsIfNotEmpty(string company, index)
{
if (index == 0) {
return string.Format("<div class=\"first\">{0}</div>", company);
}
// Add more conditional code here
// - page specific HTML, like render a certain icon
string divClass = (index % 2 == 0) ? "normal" : "alternate";
return string.Format("<div class=\"{1}\">{0}</div>", company, divClass);
}
This will only be used on one page and is most likely subject to change.
Update: A couple approaches I thought about where these:
1) Inline codebehind on page - with simple methods that returns strings.
<script runat="server">
private string RenderCompanyHtml(string companyName) ...
<script>
2) Putting a method which returns a string in the Controller. But that would be putting View-logic into the Controller.
public class SomeController : Controller
{
[NonAction]
private static string RenderCompanyHtml(string companyName) ...
public ActionResult Index() ...
}
You should put that code in the controlleraction where you prepare the viewdata.
I usually make a region "helper methods" in my controller class with a few [NonAction] methods to keep things clean.
So my (simplified) controller would look like this:
public class SomeController : Controller
{
#region Helper methods
[NonAction]
private static string CompanyNameWithRightCapsIfNotEmpty(string company)
{
if (string.IsNullOrEmpty(company)) {
return company;
}
return UpperCaseSpecificWords(company);
}
#endregion
public ActionResult Companies()
{
var companies = GetCompanies();
var companyNames = companies.Select(c => CompanyNameWithRightCapsIfNotEmpty(c.Name));
ViewData["companyNames"] = companyNames;
return view();
}
}
Helper methods are one good way of handling page specific code, but I think it is a;ways preferable to get your model to show the data you need.
If you're going to go for the helper option, you would be better served by making the operation it perfroms a bit less page specific. If your method RenderCompanyNameWithRightCapsIfNotEmpty has to be so specific it would be better if your model provided it. One way would be to have the model provide a list with the text already formatted, and expose it as a public property (say an IEnumerable of formatted company names).
Use Html Helpers.
Like so create the helper methods in a static class:
public static string Label(this HtmlHelper helper, string target, string text)
{
return String.Format("<label for='{0}'>{1}</label>", target, text);
}
.. then use in your view:
<span><% =Html.Label("FinishDateTime.LocalDatetime", "Finish Time:")%><br />
You could create a helper method called maybe RenderCompanyName(string[] companies) that checked for nulls, did the caps manipulation and rendered the html in between - all in the same helper if you like.
Also: controller action methods should be light - ie. only getting the data and returning views. You should delegate things like manipulation of data for presentation to views and Html helpers.
EDIT: Here is a helper that you might be after:
This helper renders an IList<> to html in the form of an unordered list <ul>...</ul>. The useful thing about it is that it gives you control over how the list is rendered thru css AND it allows you to render additional html/content for each item. Take a look - this is the helper:
public static string UnorderedList<TItem>(this HtmlHelper helper,
IList<TItem> items, Func<TItem, string> renderItemHtml,
string ulID, string ulClass, string liClass)
{
StringBuilder sb = new StringBuilder();
// header
if (!ulID.IsNullOrTrimEmpty()) sb.AppendFormat("<ul id='{0}'", helper.Encode(ulID.Trim()));
else sb.AppendFormat("<ul");
if (!ulClass.IsNullOrTrimEmpty()) sb.AppendFormat(" class='{0}'>", helper.Encode(ulClass.Trim()));
else sb.AppendFormat(">");
// items
foreach (TItem i in items)
{
if (!liClass.IsNullOrTrimEmpty())
sb.AppendFormat("<li class='{0}'>{1}</li>", helper.Encode(liClass.Trim()),
renderItemHtml(i));
else
sb.AppendFormat("<li>{0}</li>", renderItemHtml(i));
}
// footer
sb.AppendFormat("</ul>");
return sb.ToString();
}
..using it is easy. here is a simple example to render a list of tags:
<div id="tags">
<h2>Tags</h2>
<%=Html.UnorderedList<Tag>(Model.Tags.Tags,tag=>
{
return tag.Name;
},null,null,null) %>
</div>
..you can see in my usage example that i have chosen not to specify any css or id attribute and i simply return the name of the Tag item thru the use of the anonymous delegate. Anonymous delegates are way easy to use.. in your case maybe something like this would work:
<div id="tags">
<h2>Tags</h2>
<%=Html.UnorderedList<string>(ViewData["companies"],company=>
{
if (someCondition) return company.ToUpper();
else return company;
},null,null,null) %>
</div>
.. ViewData["companies"] is an IList<string> for simplicity.

Resources