Anyone Using a Custom ViewMasterPage? - asp.net-mvc

Lately when I've been building my asp.net mvc apps I tend to have a number of items that consistently need to be calculated, formatted and configured in my master pages. Some of these items include:
I like to attach specific classed to the body tag like WordPress does to help my CSS out. I usually attach the name of the action, controller, page template, etc.
I like to work with a custom IIdentity in my master pages. This IIdentity includes the users nice Display Name, UserID, UserName, etc.
etc... depending on the project
The way I've gone about accomplishing this has evolved as well. I started out by sending ViewData along with every action result to populate things in the master page. example->
// in the action
ViewData["BodyClasses"] = "index home default";
ViewData["UserData"] = userData;
return View();
// in the master page
<% UserData userData = (UserData)ViewData["UserData"] %>
...
<body class="<%= (string)ViewData["BodyClasses"] %>">
That was horrible. So I started putting some variables at the top of my master pages and populating them based on the objects we have to work with in the master page. example ->
<%# Master Language="C#" Inherits="System.Web.MVC.ViewMasterPage" %>
<% string controller = ViewContext.RouteData.Values["controller"].ToString().ToLower();
string action = ViewContext.RouteData.Values["action"].ToString().ToLower();
CustomIdentity identity = (CustomIdentity)User.Identity %>
...
<body class="no-js <%= controller + " " + action %>">
That's better but I feel like there has to be an easier solution. I started playing around with creating a custom ViewMasterPage and adding some public properties. Is anyone else doing this with the master page or is there another solution to this that I'm completely missing?

I've used just regular content placeholders for body tag id's and classes.
<body id="<asp:ContentPlaceHolder ID="BodyTagId" runat="server" />">
Then use it in the given page that references the master page:
<asp:Content ContentPlaceHolderID="BodyTagId" runat="server">about-page</asp:Content>

Related

MVC Layout with Menu

I'm new to MVC and I'm trying to make use of the Layout Page So far I have the following layout page:
...
<div class="container-full body-content">
<h2>#ViewBag.Title</h2>
<div class="row">
#if (Request.IsAuthenticated)
{
<div class="col-sm-3">
#{Html.RenderAction("MenuPartial", "Layout");}
</div>
<div class="col-sm-9" style="background-color:aqua">
#RenderBody()
</div>
}
else
{
#RenderBody()
}
</div>
...
Is there a way to get MVC to just re-render the render body rather than doing a full post back and re-loading the navigation menu each time a page changes?
First, MVC doesn't do "postbacks". If you click a link, that's just a GET request. If you actually submit a form, it will send the request as a POST, but the destination could be an entirely different view or the same.
Second, taking an action like clicking a link or submitting a form within a web browser by default sends a request to the server and then entirely replaces the user's rendered view with a the response from the server. That necessitates the the server send back a full HTML document, including any associated layout.
If you want to replace just a portion of the HTML page without changing the user's view, that requires using AJAX. You send an AJAX request to request an action that will return a partial view. In other words, it will not utilize the layout you've defined for the web application. You, then, are responsible for replacing the appropriate portion of the DOM client-side with the server's response. There's a plethora of client-side JavaScript libraries that can help you manage this type of workflow, but they're all frameworks in their own right. In other words, they handle routing, models, views, etc. These are what's referred to as SPAs, or Single Page Applications. When you create a SPA, the server is relegated to a pure support role, only providing endpoints allowing you to retrieve or update data. Web Api is a popular choice here exactly for that reason; as all the MVC machinery is unnecessary.
If you're just looking to optimize things by say not having to render the child action that returns the menu, you can employ OutputCache on the child action so that for a period of time, the action will not need to run again, and the HTML output it generated can just be dumped directly into the appropriate place in the layout.

How to hide a content placeholder if it has no children without client code (MVC)

I have a ContentPlaceholder inside of a MasterPageView. All of my other pages come from the same master and I have one page that needs about 70% of the behavior in this master. There is a navigation panel in the master that is spitting out un-necessary html even if left blank by the page. Looks like this:
<div class="span3">
<div class="side_navigation">
<ul>
<asp:ContentPlaceHolder ID="SideNavigation" runat="server" />
</ul>
</div>
</div><%-- /master sub-navigation --%>
I simply want to hide ALL of this markup whenever my placeholder (SideNavigation) has 0 children. I don't want to use javascript. I'd rather do this work on the server and deliver it to the client with less responsibility and markup. I've already tried doing "this.SideNavigation.Controls.Count" but it always ends up being 0. If there was a way I could tie into a loaded event and then test this logic that would be great. I am ok with making a code-behind file for my master, but it would be nice to be able to accomplish my goal in the .master file only.
Let me know what you think.
I would probably recommend using a different master page for the page without the navigation. You can have nested master pages so you don't necessarily need to duplicate code to do this.
However if you do wish to keep it like this, I would personally use a bit of javascript (with jquery) as follows
$(function(){
if($('.span3 .side_navigation ul li').length() == 0){
$('.span3').hide();
}
});
obviously i'd give span3 an ID to make it not hide every span3 but you hopefully get the idea.

How to structure a websitewhen using boxes

I am designing a page which needs to have boxes (partial views) on both the left and the right side of the main content. The boxes will contain extra information just like on SO. I would like to group the pages, so certain pages will have some boxes, and pages of another group will have other boxes.
I am though in doubt how this is best accomplished in MVC.NET.
I see two options:
1) On every page I include the partial controls I need.
2) Create sub-master pages where I can assign a page to, and which can control which partial views should be shown.
Are there any other ways of doing this, which would be smarter or easier to implement?
Boxes on SO pages are strictly related to content of the page, so I would create one master page with 3 content placeholders (Site.Master):
<asp:ContentPlaceHolder ID="LeftContent" runat="server" />
<asp:ContentPlaceHolder ID="MainContent" runat="server" />
<asp:ContentPlaceHolder ID="RightContent" runat="server" />
If you had some range of functionalities, that have the same boxes, I would create master page for them and place something like (Controller.Master):
<asp:Content runat="server" ContentPlaceHolderID="RightContent">
<% Html.RenderPartial("RelatedTagsInfo", ViewData['RelatedTagsInfoModel']) %>
<% Html.RenderPartial("RelatedQuestionsInfo", ViewData['RelatedQuestionsModel']) %>
<asp:ContentPlaceHolder ID="RightContentSubcontent" runat="server" />
</asp:Content>
Actions using Controller.Master would place additional boxes in RightContentSubcontent.
If boxes are really independent of current page, you can also use RenderAction:
<asp:Content runat="server" ContentPlaceHolderID="RightContent">
<% Html.RenderAction("RelatedTagsInfo", "Question", new { id = Model.Id} ) %>
<% Html.RenderAction("RelatedQuestionsInfo", "Question", new { id = Model.Id} ) %>
<asp:ContentPlaceHolder ID="RightContentSubcontent" runat="server" />
</asp:Content>
RenderAction creates new request to render part of the page. It may be easier to use, but I would use RenderAction for boxes, that do not share any information with the main view (for example if you wanted to display "Clock" box, "Latest downloads" box or "Login" box).
There are many ways to put content into sidebars on pages...
Build different master pages, each containing a different subset of sidebar content. I would use RenderAction for this and not RenderPartial, that way you do not have to worry about having each ActionResult in your MVC Solution be responsible for sending content to these partials.
set up a single RenderAction in the Master page and in the ActionResult for the RenderAction, have it inspect the calling Controller and/or the ActionResult (all contained in the HttpContext). Based on that you can send back content conditionally, and render it in the partial view conditionally.

Asp.Net MVC - RenderPartial - Create in a List view

I got a page that lists all my articles (Articles/List.aspx).
I also got a control that create article (Article/Create.ascx).
I will like that my List.aspx page that's render the Create.ascx to be able to create article.
I know that in MVC, the preferred approach is one page by action. But in this case I need to do that. It's a design issue and how the client want the Web site to work.
So for now, I got the following code in List.aspx :
<% Html.RenderPartial("Create", new Domain.Models.Article()); %>
That render correctly. But when I hit the create button, it's doesn't go in the Create[post] method of my ArticleController.
Any idea why and how I could resolve that issue ?
If you have problems with the button, it's not going to have anything to do with how you're rendering the user control. We need to see the form markup that the button is inside, that will show what the problem is most likely.
But just for reference, you're probably looking to do something like this:
<% using (Html.BeginForm("Create",
ViewContext.RouteData.Values["Controller"].ToString())) { %>
your control markup here
<% } %>

Script & CSS Registration Helper in ASP.NET MVC?

I have tried to use ASP.NET MVC for a while, then I face a problem that I don't want to include all of my js and css in master page. But how can I register it in head of master page from my specific view?
The default master page template includes a Content PlaceHolder for the head. If it doesn't you can easily add one:
<head runat="server">
<title></title>
<asp:ContentPlaceHolder ID="head" runat="server" />
</head>
Your views can then put anything they want in the head:
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
<script src="Scripts/myScripts.js" type="text/javascript"></script>
<link href="Styles/myStyles.css" rel="stylesheet" type="text/css" />
</asp:Content>
It doesn't look like there's a simple option 'built-in' to the ASP.NET MVC framework just yet. If you are using a user control (.ascx), which you may be if you are creating self-contained controls which also want to manage their own JavaScript requirements, then you can't even use the placeholders to help you out.
In the end I created a helper class and in it there are a couple of methods:
private static SortedList<int, string> GetRegisteredScriptIncludes()
{
var registeredScriptIncludes = System.Web.HttpContext.Current.Items["RegisteredScriptIncludes"] as SortedList<int, string>;
if (registeredScriptIncludes == null)
{
registeredScriptIncludes = new SortedList<int, string>();
System.Web.HttpContext.Current.Items["RegisteredScriptIncludes"] = registeredScriptIncludes;
}
return registeredScriptIncludes;
}
public static void RegisterScriptInclude(this HtmlHelper htmlhelper, string script)
{
var registeredScriptIncludes = GetRegisteredScriptIncludes();
if (!registeredScriptIncludes.ContainsValue(script))
{
registeredScriptIncludes.Add(registeredScriptIncludes.Count, script);
}
}
public static string RenderScripts(this HtmlHelper htmlhelper)
{
var registeredScriptIncludes = GetRegisteredScriptIncludes();
var scripts = new StringBuilder();
foreach (string script in registeredScriptIncludes.Values)
{
scripts.AppendLine("<script src='" + script + "' type='text/javascript'></script>");
}
return scripts.ToString();
}
That's a basic form of it anyway to try and show the way it works. It could be enhanced in many ways, but at the moment it just filters out duplicate script insert requests for you. Whenever you want to add a new script in the ascx (or aspx for that matter) you can do it this way:
<%
Html.RegisterScriptInclude(Url.Content("~/Scripts/MapLayers/MapLayer.js"));
Html.RegisterScriptInclude(Url.Content("~/Scripts/MapLayers/Vehicles.js"));
%>
Then you need to remember to output them once you're done. This is achieved by making the following call at the place in your page where you want to output the script tags:
<%=Html.RenderScripts() %>
Seems to work so far for me. I did half expect to have rendering issues depending at what point RenderScripts was called, especially if not all of the RegisterScriptIncludes had been called yet, but so far it seems to do the job. If you render the scripts last then you should have no problems.
#Jason: WARNING, you shouldn't be using static variables like this... In a web context static variables are shared across all users and all page requests. I am amazed that you haven't run into trouble with your code. The principle is fine but the code is wrong and will give you trouble. In this case you should be using System.Web.HttpContext.Current.Items. See http://www.hanselman.com/blog/ATaleOfTwoTechniquesTheThreadStaticAttributeAndSystemWebHttpContextCurrentItems.aspx for more.
Here is a solution similar to the one Jason gave, but takes vdh_ant's comments into consideration:
http://frugalcoder.us/post/2009/06/29/Handling-Scripts-in-ASPNet-MVC.aspx
technically you should be putting all your js at the bottom of the page for the best performance.
I think the only way you could do this though would be to include the javascript in the VIewData and have the ViewData displayed on the masterpage (not a great solution).
MVC Futures now has built in helpers for this...
1.<head>
2. <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
3. <%= Html.Css("BlueTheme/site.css") %>
4. <%= Html.Script("jquery-1.3.2.js") %>
5.</head>
More information here: http://blog.osbornm.com/archive/2009/10/12/mvc-script-css-helpers.aspx
Mathew,
I took a look at your blog on Html.Css and Html.Script helpers. I don't mean to be critical but I do not see any mention in the blog about how the Css and Script helpers would address the problem discussed here. The problem here is one of needing to "registered" script references at any point during the rendering process, and possibly multiple times (in the case of an template or partial view that is used several times and registers its own scripts), and then outputting the aggregate results, sans duplicates, in a single location.
If your solution addresses this, please correct me with some clarification.
--Regards,
Ken
I wrote just such a manager for MVC, and wrote about it on my blog:
"JavascriptHelper–Managing JS files for ASP.NET MVC"
UPDATE: I added bundling:
"JavascriptHelper:Managing JS files for ASP.NET MVC (With Bundling)"

Resources