Selecting a Stylesheet at runtime












You can adjust the appearance of your web form with a CSS (cascading style sheet). In the format menu of the VS.NET web form designer is document styles. Here you select a css file or you can build a style on the spot. The (link to the) style will be embedded in the header of the webform. Take this example :

<HTML>
    <HEAD>
        <title>BaseForm1</title>
        <meta name=”GENERATOR” Content=”Microsoft Visual Studio .NET 7.1″>
        <meta name=”CODE_LANGUAGE” Content=”C#”>
        <meta name=”vs_defaultClientScript” content=”JavaScript”>
        <meta name=”vs_targetSchema” content=”http://schemas.microsoft.com/intellisense/ie5″>
        <LINK href=”../StyleSheetMOC.css” type=”text/css” rel=”stylesheet”>
   
</HEAD>
    <body MS_POSITIONING=”GridLayout”>
        <form id=”Form1″ method=”post” runat=”server”>


To give all the forms in your app the same appearance it would make sense to set the stylesheet in the base webform class of your app. The actual webforms inherit from this baseform and should inherit the stylesheet as well. Alas, this does not work. When inheriting from a webform you do not inherit the HTML in the header. And what is worse, it is next to impossible to influence the content of the header of a page being rendered.


The good thing is that the link to the stylesheet does not have to be in the header. Googling around I found several solutions. One of them was to start the response with the link. Which worked until the page contained a validator control. The other one was to finish the response with a link. This worked and I posted the first version of this post.


In a response on that Ryan Greg pointed to the importance of the place in the document where the link should be injected. To influence the injection place I used the Controls.AddAt() method in a second version to insert a literal control containing the link. The first parameter of this method is the desired location of the control in the the tree of the page. Passing a 0 inserts the link as first line of the response. That line is reserved for the doctype. If your page has a validator control, the script of that control will fail to load. Passing in a 1 inserts the link as first markup in the body.


But there is a big drawback to using the Controls.AddAt()  method. It might (will allways ?) ruin the viewstate of the page, making the baseform pretty unusable. As an alternative I used the RegisterClientScriptBlock method in my third attempt. This method will insert any script, or a link to a style sheet, in the top of the rendered page before the rendered controls. Which is a nice location.


The link to the style sheet is in the web.config and read from the AppSettings. This location is the location as seen by the browser, something like


<add key=”StyleSheet” value =”http://yourhost/su/moc/StyleSheetMOC.css”></add>


protected override void OnLoad(EventArgs e)
{
    base.OnLoad (e);
    RegisterClientScriptBlock(“MyCSS”, string.Format(“<link rel=’stylesheet’ type=’text/css’ href='{0}’></link>”));
}


As a result every page which inherits from the baseform contains a link to the stylesheet, its controls can successfully use the styles defined, and the viewstate of the pages keeps running as well.


Blog on, Peter


This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Peter’s Gekko

    Why Page_load ?

  • Peter van Ooijen

    That is a point ! I havn’t tried that scenario but I would by no means guarantee that the stylesheet would be corrrectly rendered. I have updated the post, thanks.

  • Ryan Gregg

    Do you run into any problems on slower links where the page is rendered before the CSS is applied, and then re-rendered once the CSS link is downloaded?

  • Peter van Ooijen

    hi Vijay, I’m not sure if I can follow you. What I needed was a piece of code (not markup) which injects the link when the (base-)page is rendered. The value could indeed be based on a profile as well, my example of a web.config is just simple. Main pont is that I get the link for free in any page which descends from the basepage.

    Could you eleborate a little on your solution ?

  • Vijay

    Peter you don’t have to do this. You can instead assign the Link tag an ID and a runat=server attribute and use HtmlConytol.GenericControl with the same ID in the codebehind to set the value dynamically. That is pretty easy. I have done this in a project before and you can change the CSS source depending on the user profile, preference and whatever constraint you have. In fact this is a beautiful way to skin ASP.NET apps without using the Whidbey features 😉