I have been trying to organise my views to optimize load times by having css in the head and scripts at the end of the body.
To this end I have created masterpages that have 3 content placeholders: styles, maincontent and scripts.
With the (achieved) aim that when I render a single view with a master this neatly can collate all the shared components and specific page components into their respective places in the rendered page. The difficultly comes when I try rendering a master-detail model.
Consider the following:
I have a Master-Detail model say Order-OrderItems and views for the Order and views for the OrderItems. In an effort to be DRY and not to repeat myself (doh! I just did right then :) ) I would like to partially render the OrderItems list view in the Order header view and still achieve the correct element layout i.e: have the any specific css be rendered into the content placeholder in the head, content into the main content placeholder and scripts into my scripts content placeholder at the bottom of the body.
I have a nasty hack at the moment but wondered if there is a cleaner way of doing this.
My current hack is the equivalent of a sql cross join with a where clause in that I do a partial render of the orderitems view in each contentplaceholder of the order view and inject the name of that placeholder into the viewdata. In the orderitems view if the placeholder name does not equal the target placeholder, I return and there by only the css get rendered into the css place holder etc. To re-phrase it, I render the child three times in the parent, while in the child I return a different part of the content for each rendering.
Smells funny and not very elegant as the child has to know that it is being rendered etc.
My next thought is to try intercepting a copy of the ViewPage and taking control of the rendering using using SetRenderMethodDelegate by only calling render on the target contentplaceholder control but this is getting very tangled at this point.
Can anyone suggest a better way that maintains code re-use and optimal html output?
Some example code:
<%@ Master Language="C#" ... %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title>Master-Detail</title>
<!--common styles-->
<link type="text/css" href="my.css" rel="stylesheet" />
<asp:ContentPlaceHolder ID="Styles" runat="server" ></asp:ContentPlaceHolder>
</head>
<body>
<!--common markup-->
<h2>Master Markup</h2>
<asp:ContentPlaceHolder ID="mainContent" runat="server"></asp:ContentPlaceHolder>
<!--common scripts-->
<script type="text/javascript" src="/scripts/my.js"></script>
<asp:ContentPlaceHolder ID="Scripts" runat="server"></asp:ContentPlaceHolder>
</body>
</html>
Order View
<%@ Page Title="Order" Language="C#" MasterPageFile="~/Views/Shared/my.Master" ... %>
<asp:Content ContentPlaceHolderID="Styles" runat="server">
<style type="text/css">
/*order styles*/
</style>
<%
var partialContext = this.Html.GetViewContext("OrderDetails", "List", this.ViewData.Model.Items, this.ViewData);
partialContext.RenderContentPlaceHolder("Styles");
%>
</asp:Content>
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
<h3>Order Markup</h3>
<%
var partialContext = this.Html.GetViewContext("OrderDetails", "List", this.ViewData.Model.Items, this.ViewData);
partialContext.RenderContentPlaceHolder("MainContent");
%>
</asp:Content>
<asp:Content ContentPlaceHolderID="Scripts" runat="server">
<script type="text/javascript">
/*order specific script*/
</script>
<%
var partialContext = this.Html.GetViewContext("OrderDetails", "List", this.ViewData.Model.Items, this.ViewData);
partialContext.RenderContentPlaceHolder("Scripts");
%>
</asp:Content>
Order Items View:
<%@ Page Title="OrderItems" Language="C#" MasterPageFile="my.aspx" ... %>
<asp:Content ID="Content2" ContentPlaceHolderID="Styles" runat="server">
<% if (ViewData["placeHolderName"] != null && ViewData["placeHolderName"].ToString().ToLower() != "styles")
return; %>
<style type="text/css">
/*OrderItems style*/
</style>
</asp:Content>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
<% if (ViewData["placeHolderName"] != null && ViewData["placeHolderName"].ToString().ToLower() != "maincontent")
return; %>
<h4>Order Items MarkUp</h4>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="Scripts" runat="server">
<% if (ViewData["placeHolderName"] != null && ViewData["placeHolderName"].ToString().ToLower() != "scripts")
return; %>
<script type="text/javascript">
/*Order Items script*/
</script>
</asp:Content>
Where there are a couple of extension methods:
public static ViewContext GetViewContext(this HtmlHelper htmlHelper, String controller, String viewName, Object model, ViewDataDictionary viewData)
{
var routeData = new System.Web.Routing.RouteData();
routeData.Values["controller"] = controller;
var controllerContext = new ControllerContext(htmlHelper.ViewContext.HttpContext, routeData, htmlHelper.ViewContext.Controller);
var viewEngineResult = ViewEngines.Engines.FindView(controllerContext, viewName, "Partial");
var viewData2 = new ViewDataDictionary(viewData);
viewData2.Model = model;
var viewContext = new ViewContext(controllerContext, viewEngineResult.View, viewData2, htmlHelper.ViewContext.TempData, htmlHelper.ViewContext.Writer);
return viewContext;
}
public static void RenderContentPlaceHolder(this ViewContext context, String placeHolderName)
{
context.ViewData["placeHolderName"] = placeHolderName;
context.View.Render(context, context.Writer);
context.ViewData.Remove("placeHolderName");
}