Skip navigation.

Remember To Label Code in SourceSafe And Do It OftenAll recent postsHow Much Room For Error Have You Built Into Your Business?

How to Link Stylesheets from a Master Page

First off, big thanks to Scott Allen for his coverage of master pages in ASP.NET 2.0. Scott nicely summarized some common gotchas that would’ve cost me time if I wasn’t aware of them.

One of the dilemmas master pages present is how to link to images, web pages, stylesheets, etc. Do you use a relative URL? Or an absolute one? Do you give, say, an image a runat="server" attribute and throw a tilde (~) in to have it resolved to the project root? There’s no easy answer.

HtmlHead Is Lacking

In 2.0 you may access the <head> element of a page server-side (provided you decorated it with runat="server"). The runtime treats it as an instance of the HtmlHead control.

What jumped out at me was its scarce list of useful methods and properties. Yes, there’s the much coveted Title attribute, but besides that… How do I register an external JavaScript file? Or a stylesheet? Or a meta tag. Granted, meta tags are for the most part only “hints” to browsers and therefore aren’t that useful, but still.

For example, I like to @import some of the stylesheets, and <link> the rest:

<style type='text/css' media='screen,projection'>
@import '/styles/layout.css';
</style>
<link rel='stylesheet' type='text/css' 
     media='print' href='/styles/print.css' />

You might want to do this to cater advanced styles to modern browsers, and older styles to legacy browsers. This way every browser gets what it can handle. Or, this way you may let owners of crappy browsers know it’s time to upgrade.

The HtmlHead control doesn’t have the luxury of doing much besides setting the page title and registering page-scope CSS rules. It’s a shame.

Some hacks, published online, suggest creating an HtmlLink control, setting its src and rel properties, adding it to the Controls collection, or resorting to even funkier logic with AddParsedSubObject(). All this to register a stylesheet and only a <link> one?! Maybe we should learn a thing or two from the Ruby on Rails folks. At least Rails was written by developers for developers.

My Hack

I wrote a base class for for my master pages. It simply exposes a property with the base URL of my project. If your project can switch between HTTP and HTTPS, you might want to add another private property for that instead of hardcoding “http://”.

When it comes to creating a new master page, I derive from my base class and plug the BaseURL property as follows:

<style type='text/css' media='screen,projection'>
@import '<%= BaseURL %>/styles/layout.css';
</style>
<link rel='stylesheet' type='text/css' 
    media='print' href='<%= BaseURL %>/styles/print.css' />

A master page is a good old combination of HTML markup and code-behind, which is why you can reference its properties.

Comments

Comment permalink 1 scottgu |
Sue has a good list of samples showing common head usage here: http://www.edream.org/BlogArticle.aspx?RecordID=112

Hope this helps,

Scott
Comment permalink 2 Tim Murphy |
You might want to check out this post on my old blog as an alternate approach.

http://twmurph.blogspot.com/2006/01/master-pages-and-themes.html
Comment permalink 3 Milan Negovan |
Scott, that's my point exactly: "Page.Header.Controls.Add" is an unnatural notation. For example, which line of code is more natural?

if (string.Contains ("something")) {}
or
if (string.IndexOf ("something") != -1) {}

The first one, of course. The HtmlHead control makes you think in terms of control trees and such. That's not how you think when you lay out web forms. That's why I said HtmlHead was lacking meaningful functionality.
Comment permalink 4 Karl |
This is a problem I have been trying to solve for a long time. Thanks for writing about this.
Comment permalink 5 Scott |
Thanks for the compliment. I actually started working on another master page article this weekend that covers a lot of tips, tricks, and tracks. One thing I've discovered is that ContentPlaceHolders work well inside the tag (unfortunately, VS2005 validation flags the control with an error, but everything works). In any case, this article keeps growing and should provide a couple tips on stuff.
Comment permalink 6 scottgu |
Ahh...but you forget magic URL rebasing is built-into the header control, which means you don't need the BaseUrl property above for the link element at all (or any code for that matter).

Just write:



within the head section of your .master page.

The < head > server control will then update the path to always be relative to the master page location regardless of what sub-directory a page based on the master page is. For example, if you put a page1.aspx page within a "NewFolder" subdirectory in your site, the control would automaticlaly adjust the reference to the stylesheet to be "../Stylesheet.css" for you. If you had a page2.aspx in the same directory as Site.Master, then it would be declared as just "StyleSheet.css".

This saves you from having to write any code at all.... ;-)

Scott
Comment permalink 7 scottgu |
Rats - my comment above was html encoded. Put the appropriate html brackets around this to have it show up:

< link href="StyleSheet.css" rel="stylesheet" type="text/css" >
Comment permalink 8 Milan Negovan |
Aha! "Relative to the master page location" was the missing bit. Thanks, Scott!
Comment permalink 9 Dave Griffiths |
Why not use ResolveUrl() and get the best of both worlds (late/dynamic resolution, without a server control)?

@import '< %= ResolveUrl("~/styles/layout.css") % >'

ResolveUrl is a method on all controls and pages, and has 2 flavours, whether you use Me.Page.ResolveUrl or Me.ResolveUrl (or simply ResolveUrl) determines where the resolution is relative to (i.e. in both cases it's relative to the relevant controls Template source folder), so Me.ResolveUrl() is relative to the control or page ASPX/ASCX folder, and for Me.Page.ResolveUrl() is relative to the containing ASPX pages folder. It's useful for embedding in ASCX's that need to access a resource relative to the location of the containing page. Using the tilde (or an absolute path) makes both versions act the same ...
Comment permalink 10 Taco Oosterkamp |
Milan, thanks for this article. It helped me fix a problem with including a reference to a JavaScript file. I now use a combination of your BaseURL code and the code of Sue that Scott pointed to.

Scott, you gave an example of linking to a css file. That was no problem for me using themes, but do you know of a way to do the same thing for a JavaScript file. It looks quite a bit cleaner than having to write C# code, but I need an example to get this working, I'm afraid.

Thanks!
Taco Oosterkamp
Comment permalink 11 Milan Negovan |
Dave, that's a great idea! It's a pity that classes and methods which process URIs are scattered all over .NET.
Comment permalink 12 franco |
can you suggest validating web controls vs2003 using external javascript validation file.i have no clue and i am goin to use it for the first time
Comment permalink 13 Milan Negovan |
Franco, I honestly don't know what external validation file you mean.
Comment permalink 14 franco |
hi,
i want to validate for eg email,url entered by the person in web control on mine form.this validations are required on multiple pages.so i thought of using a single javascript file .
Comment permalink 15 bobC |
I I have a root site with child sites. Each child site is based on a master. Each child site needs a unique logo, but when the image tag is resolved, it is always based on the location of the master page, at the root, so images always come from the root instead of the sub-site images folder.

How can I use a master page for sub-sites, and pull images from the sub-site Images folder?

RootSite
Images
SubSite
Images

SubSite
Images
Comment permalink 16 groker |
Another method:
The advantage of this method is that you set the master page up as a you would any html page and thus see the styles in the designer.


just add the following to the master page or master page base:

protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
JSManager.MaintainScrollPosition(Page);
if (!IsPostBack)
{
foreach (Control control in Page.Header.Controls)
{
WebControl webControl = control as WebControl;
if (webControl != null && webControl.Attributes["href"] != null)
webControl.Attributes["href"] = ResolveUrl(webControl.Attributes["href"]);
}
}
}
Comment permalink 17 groker |
sorry, take the line "JSManager.MaintainScrollPosition(Page);" from the above example
Comment permalink 18 Mike Flynn |
This is a good example but it does not cover https and uses procedures. Here is what I use.

public static string RootUrl
{
get
{
HttpContext context = HttpContext.Current;
string executionPath = context.Request.ApplicationPath;
return string.Format("{0}://{1}{2}", context.Request.Url.Scheme,
context.Request.Url.Authority,
executionPath.Length == 1 ? string.Empty : executionPath);
}
}
Comment permalink 19 mikail |
hi
while i am using this code


i got an error. like
"Cannot switch views: Validation (Internet Explorer 6): Element 'link' cannot be nested within element 'td'."
i coulndt solve this.
there is any one to known reason?
thanks
Comment permalink 20 mikail |
code is here
link rel="stylesheet" href="../js/cbcscbinsmenu.css" type="text/css"
Comment permalink 21 Asbjørn Ulsberg |
What I do to solve this, is replace all instances of "~/" in the code with Request.ApplicationPath (after some validation and preparation) in a base class' Render() method. Since "~/" is used elsewhere in ASP.NET and is automatically replaced on all Server Controls, I think this is a pretty neat solution.
Comment permalink 22 Nathanael Jones |
I wrote a test suite to figure out exactly how broken 'link', 'meta', and 'script' references are when used with master pages.

Play around with it:
http://nathanaeljones.com/tests/contentplaceholder/

I blogged about the core issue and how to solve it application-wide:
http://nathanaeljones.com/11011_Referencing_stylesheets_scripts_from_content_pages
Comment permalink 23 afterburn |
Something i found is ASP.net may try to rewrite some URL's based on the actual url selected instead of the root of the site.

I had to use a ControlAdapter to over ride the form rendering. But that only solved URL's to the page. That didn't fix images and css that try the same thing but couldn't figure out how to get the CSS in the theme to not do ../../../../style.css ; when asp.net actually sees this, it throws an error because the path is actually outside of the website. something about "../" is dangerous and can not go past the root of the web, when in actually it would have only rendered the browser correctly mapping the css theme based on the URL;

Wasn't able to resolve that. So all my themes only contain skins and css is a root directory using a virtual path "/css/style.css" instead. buggy.. found another nice bug that results in iis crashing but no where at MS to report stupid things like that.
Comment permalink 24 Cookston |
Hi,

I tried this in a nested master page, the <%= BaseURL %> was evaluated correctly in the nested page, but not the parent master page.

Both master pages inherit from the base class.

It appears as MasterPages/%3C%25=%20BaseURL%20%25%3E in the resulting HTML suggesting that this method cannot be used with nested master pages because all href's are prepended with the MastePage's folder.

My parent master has this:

head runat="server">
title>
link rel="stylesheet" type="text/css" media="screen" href="<%= BaseURL %>/lib/css/mycss.css" />
asp:ContentPlaceHolder ID="ContentPlaceHolderHead" runat="server">
/asp:ContentPlaceHolder>
/head>


The nested master page has this:

asp:Content ID="ContentHead" ContentPlaceHolderID="ContentPlaceHolderHead" runat="Server">
link rel="stylesheet" type="text/css" media="screen" href="<%= BaseURL %>/lib/css/anothercssofmine.css" />
asp:ContentPlaceHolder ID="ContentPlaceHolderHead" runat="server">
/asp:ContentPlaceHolder>
/asp:Content>

(First Left angled bracket removed on each line so this can be posted)

The nested part of the page is evaluates <%= BaseURL %> correctly, but the parent part gets MasterPages/%3C%25=%20BaseURL%20%25%3E

Also, I find that with this method the Design editor mode does not render using the stylesheet when using <%= BaseURL %>

Any ideas?

HL

Emails and Notifications

Would you like to be notified when somebody responds to this post?  Would you like to have these comments emailed to you?

TrackBacks

Sorry, TrackBacks are not allowed.

Submit your comment

Please enter only text since all HTML tags except hyperlinks will be stripped. Hyperlinks will become live links. Any comments with flaming or offensive language will be deleted. Be courteous to other posters. Thank you.

Your name (required):
Your email (optional):
Your site's URL (optional):
Enter this number
Type in the number above:
Comment (required):