MVC Script & Css Helpers

Posted by Matthew Osborn on October 13, 2009

During development of MVC 2 Preview 2 I was lucky enough (I’m on the QA team after all) to get to code and check in a feature that I wanted to MVC Futures. In all actuality it is two separate “features” that basically do the same thing. Okay, I think I might be blowing it out of proportion they are basically two new HTML helpers “Script” and “Css

###The Problem:

When you add links to to files (aka Scripts and CSS) the URLs are normally relative to the current page’s URL. This means that if you have “../../Scripts/jquery-1.3.2.js” it means go up two segments then down to the scripts segment. This works great but when you start using routing the pattern starts to fall apart. By this I mean that because routes change often and don’t map nicely to folders going up two segments and down one might not get you to the right folder on all your pages. To get around this most people use app rooted paths “~/Script/jquery-1.3.2.js”. The “~” basically means go to the app root and then go down. However, this is not a concept that browsers understand “~” is handled by the web framework in this case ASP.NET and ends up getting converted to the proper relative path before being sent to the client. Like I just said HTML has no concept of what “~” means when you use it in the href or src of and HTML element “most” of the time the framework detects that and converts it for you. For instance ASP.NET will convert Link tags if they are in the head tag with the attribute “runat” set to “server”. However other tags like Script tags will not have their paths converted. So to get around this it is best practice to use the “Content” method hanging off the “URL” helper which will convert an app rooted path to the correct path.

  1. <head runat="server">
  2.     <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
  3.     <link href="~/Content/site.css" rel="stylesheet" type="text/css" />
  4.     <script src="<%= Url.Content("~/Scripts/jquery-1.3.2.js")%>" type="text/javascript"></script>
  5. </head>

###The Solution:

So you can see for yourself how this can make your code look ugly and then you have that “runat” attribute on the head tag that is some kind of left over from your web forms programming days. I soon grew very tried of copy and pasting the same tag over and over for each script and just changing the name. so I figured that the ASP.NET MVC framework had helpers for everything else so why not Scripts and CSS? So after a little bit of clever coding there is now a Script helper and a CSS helper.

There are a couple of ways to use them first and the most simple to to just give it a file name or a folder that is inside the default location. For Scripts that is “~/Scripts” and for CSS that is “~/Content”.

  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>

Basically there is some very simple logic that says if the path doesn’t start with “~”, “../”, “HTTP”, or “HTTPS” it is a file name or a path that is under the default folder for each type. That leads me to my next point which is that if the path you give the helper looks like you are trying to give it a full path (aka the strings mentioned above) then it will still create the nice tag for you but wont add to the file path. This means that you can use this with the New AJAX CDN and still have all your script tags look the same at that top of the page. Hey call me vain but I like to have pretty code.

  1. <head>
  2.     <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
  3.     <%= Html.Css("~/myThemes/BlueTheme/site.css") %>
  4.     <%= Html.Script("http://ajax.microsoft.com/ajax/jquery/jquery-1.3.2.js") %>
  5. </head>

However there is an extra overload that the CSS helper that allows you to pass a media type. For those unfamiliar with CSS every time you link to a StyleSheet you can add the media type attribute that allows you to specify when to apply that style. The most common use of this is to define a “Print” style so when a user goes to print out part of your page the website will optimize itself for printing. If you choose not to specify a media type that style will be used for all the media that doesn’t have a StyleSheet defined for it (no media type is the helpers default).

  1. <head>
  2.     <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
  3.     <%= Html.Css("~/myThemes/BlueTheme/site.css", "screen") %>
  4.     <%= Html.Css("~/myThemes/BlueTheme/print.css", "print") %>
  5. </head>

Also as I mentioned before notice that you no longer need to include the “runat” attribute on the head tag. This isn’t anything special but hey it looks cleaner.

###The drawback: Okay so this solution isn’t perfect, sorry I know I got you all excited and now I’m letting you down. The problem is that Visual Studio does not know to parse these new helpers when it updates intellisense. So yes that means not intellisense, sorry! However if you followed best practice and used the URL helpers you wouldn’t have got intellisense either. Previously I found myself not using the URL helper when I was developing and then just changing it to use the URL helper after I was done and I do the same thing with the new helpers.

###Conclusion:

Like I said its not perfect but its a small improvement over the way things were done before. I would love to continue to improve these and maybe even bring them into the main framework if there is enough requests for them so please let me know what you think! But be nice this is my first try at coding a “feature”. You can download MVC Futures here.