Refactoring the ASP.NET MVC project template – Part 4

I wasn’t planning on continuing this series until I’d had a chance to play with the new RC1 release of the MVC framework, but Phil Haack has a post up that I’d like to touch on. To summarize with the latest release the MVC team has added a ContentPlaceHolder to the head section of the Site.Master file. This is a good thing and has been recommended by others as an excellent way to control the page title, scripts and css files from within your views. The issue is that the head tag has the runat=”server” attribute. So while the page is being rendered by the server the view engine parses this tag and runs some asp.net code to “help” you.  In this case one thing it helps you with is adding a title tag if you didn’t provide one. If you construct your head section improperly you end up getting an error :


The Controls collection cannot be modified because the control contains code blocks (i.e. <% … %>).

Looking around the closest reason I can find for why the head tag has the runat=”server” attribute is a comment from Phil to this answer on StackOverflow.com where he states:

the head runat="server" (aka HeaderControl) makes it easy to reference .css and .js files in the head section. It will rewrite the Href value.

Ok valid point, but I’m not sure its needed just to do that.

First, outside of ContentPlaceHolder tags in the masterpage anywhere else I see runat=”server” it’s considered code smell. There are just far too many ways that server/user controls can mess with the final rendered page by adding viewstate, mangled id’s and a host other issues. There is too much black magic going on for my tastes. Better to use Html helper methods to do the magic and display that methods results using <%= %> code blocks. This way we know that the method can only output its results in a specific location in the view and if we find an issue with that output we know what code is responsible for it. Controls have access to the entire page and can do things that I may or may not want them to do and those changes can be very difficult to trace back to the code responsible.  ( Yes I know there are ways to get access to the Page instance from helpers but I’ve got a solution to that too: DON’T DO IT! :-)

So what we need are some helpers for linking in our javascript and stylesheets. The MvcContrib project has helpers to do this but the method names are inconsistent and I wanted some more control over the conventions used.

   1: public static class IncludeExtensions {
   2:     public static string IncludeCSS(this HtmlHelper html, string cssFile)
   3:     {
   4:         string cssPath = cssFile.Contains("~") ? cssFile : ("~/content/css/" + cssFile);
   5:         string url = html.ResolveUrl(cssPath);
   6:         return string.Format("<link type=\"text/css\" rel=\"stylesheet\" href=\"{0}\" />\n", url);
   7:     }
   8:  
   9:     public static string IncludeJS(this HtmlHelper html, string jsFile)
  10:     {
  11:         string jsPath = jsFile.Contains("~") ? jsFile : ("~/content/js/" + jsFile);
  12:         string url = html.ResolveUrl(jsPath);
  13:         return string.Format("<script type=\"text/javascript\" src=\"{0}\" ></script>\n", url);
  14:     }
  15:  
  16:     public static string ResolveUrl(this HtmlHelper html, string relativeUrl)
  17:     {
  18:         if(relativeUrl == null) { return null; }
  19:         if(!relativeUrl.StartsWith("~")) { return relativeUrl; }
  20:         return (html.ViewContext.HttpContext.Request.ApplicationPath + relativeUrl.Substring(1)).Replace("//", "/");
  21:     }
  22: }

and now I can have the following in my site.master file:

   1: <head>
   2:     <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
   3:     <%= Html.IncludeCSS("site.css") %>
   4:     <%= Html.IncludeJS("jquery-1.2.6.js") %>
   5:     <asp:ContentPlaceHolder ID="HeaderContent" runat="server" />
   6: </head>

Here is the head section of my login view:

   1: <asp:Content ContentPlaceHolderID="HeaderContent" runat="server">
   2:     <title>Login</title>
   3:     <script type="text/javascript">
   4:         $(document).ready(function() {
   5:             $("input#username").focus();
   6:         });
   7:     </script>
   8: </asp:Content>

No runat attributes, mystery errors or work arounds. Just clean easy to follow markup.

Please let me know what you think.

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • DotNetKicks
  • DZone
  • LinkedIn
  • Ping.fm
  • Reddit
  • StumbleUpon
  • Technorati
  • Twitter
kick it on DotNetKicks.com
Wednesday, January 28th, 2009 Coding

3 Comments to Refactoring the ASP.NET MVC project template – Part 4

  • TheDeeno says:

    I like it. I’ll be replacing my templates with this. Keep up the good work.

  • TheDeeno says:

    Another note. Unless your referencing line numbers explicitly can you post the code without them? It makes it much more difficult to copy paste

  • iLude says:

    Ya I did realize that. In future posts that was my plan, but thanks for the note!