Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

ASP.NET and Styles & CSS Embedding


:P
On this page:

One thing that really bugs me about ASP.NET is the way it deals with header management especially when working with Themes. One of the big problems I see is that ASP.NET embeds the Themes style sheet at the bottom of the header list. If you happen to be using Master Pages which can have a header content section or your are adding additional CSS or style tags to your header the Themes style sheet will always end up on the bottom of the header list:

<%@Page Language="C#" 
         MasterPageFile="~/TimeTrakkerMaster.Master" 
         AutoEventWireup="false" 
         CodeBehind="OpenEntries.aspx.cs" Inherits="TimeTrakkerWeb.OpenEntries" 
         Title="Open Entries - Time Trakker" %>
<asp:Content ID="Head" ContentPlaceHolderID="head" runat="server">
<style type="text/css">
    #itemtemplate
    {
        border-bottom: dashed 1px teal;
        padding: 9px;
        padding-left: 30px;
    }
    #itemtemplate:hover
    {
        background: url(images/lightorangegradient.png);
        cursor: pointer;
    }
    #itemtemplate b
    {
        color: Navy;
    }
    #itemtemplate a
    {
        text-decoration: none;
    }
    #itemtemplate a:visited
    {
        color: Navy;
    }
    #clockimg
    {
        width: 16px;
        height: 16px;
        margin-right: 15px;
        margin-bottom: 5px;
        background-image: url(images/punchout.gif);
        float: left;
    }
</style>
</asp:Content>
<asp:Content ID="Content" ContentPlaceHolderID="Content" runat="server">
...
</asp:Content>

And I render that I get:

<html xmlns="http://www.w3.org/1999/xhtml" >
<head><title>
    Open Entries - Time Trakker
</title>
    <style type="text/css">
    #pagecontainer 
    { text-align:left; width: 850px; border: solid 1px silver; border-right: solid 2px silver;border-bottom: solid 2px silver; background: white; position:relative; }    
    </style>
    
<style type="text/css">
    #itemtemplate
    {
        border-bottom: dashed 1px teal;
        padding: 9px;
        padding-left: 30px;
    }
    #itemtemplate:hover
    {
        background: url(images/lightorangegradient.png);
        cursor: pointer;
    }
    #itemtemplate b
    {
        color: Navy;
    }
    #itemtemplate a
    {
        text-decoration: none;
    }
    #itemtemplate a:visited
    {
        color: Navy;
    }
    #clockimg
    {
        width: 16px;
        height: 16px;
        margin-right: 15px;
        margin-bottom: 5px;
        background-image: url(images/punchout.gif);
        float: left;
    }
</style>
<link href="App_Themes/Standard/Standard.css" type="text/css" rel="stylesheet" />
</
head>

Notice that the style sheet I defined in the master content page as well as in the master page (#pagecontent) end up above the Standard.css themes file.

The same is true if you attempt to add any controls to the Page.Header controls collection - whatever you do there ends up BEFORE the themes style sheet as far as I can tell or if you add styles manually to the header section.

This means you effectively can't override the styles defined in the themes file which is uhm problematic in many situations. This bit me today once again as I was overriding one of the styles in the themes css and found that I couldn't override the class.

There's a way to work around this but it's not XHTML compliant - you can embed styles and style sheet references into the page's content which at least guarantees that the styles are rendered after the theme's CSS.

Loading a CSS stylesheet from a Control

Another related issue I ran into today as I was building a small wrapper control around the jQuery Calendar control is how do you effectively load a CSS style sheet from a custom control, without actually rendering the style sheet multiple times?

The problem here is that unlike Script references which have  a clear API in the Page.ClientScript/ScriptManager which help to make sure you don't load multiple references to the same script files, there's no corresponding API for CSS files. However, you can fake out ASP.NET by using the ClientScript/ScriptManager for this anyway:

string css = @"<link href=""" + this.ResolveUrl("scripts/jquery-calendar.css") +
             @""" type=""text/css"" rel=""stylesheet"" />";
ScriptManager.RegisterClientScriptBlock(this.Page, typeof(ControlResources), "_calcss", css, false);

You can also use Page.ClientScript.RegisterClientScriptBlock. Basically you can use this API to inject anything into the top of the page, just after the FORM tag as long as you pass the last parameter as false, which indicates that you're providing your own <script> tags - or that you are basically handling the full markup. The flag is there for compatibility with .NET 1.1 which requires that you add the script tags, but you can use this now for injecting code into the page. This isn't ideal for CSS in all cases though: if your CSS needs to modify the body tag then this might not work correctly and as mentioned this is not XHTML compliant.

Posted in ASP.NET  

The Voices of Reason


 

Rainer
September 24, 2007

# re: ASP.NET and Styles &amp; CSS Embedding

Hi Rick,

Have you tried StylesheetTheme instead of Theme in web.config? StylesheetTheme is assigned early on in the process, Theme, however, comes last.


"Individual adjustments are sometimes necessary and you wouldn't want to be restricted by a theme once it has been set. The answer is that all templates permit overriding of the values defined in a theme, which means that in your page code you can assign any visual properties at will and rest assured they'll be taken into account when the page gets rendered to the browser."
From MSDN2 Introduction to the ASP.NET Master Pages Template Set > Themes versus StylesheetThemes

Andrew
September 24, 2007

# re: ASP.NET and Styles &amp; CSS Embedding

Rick,

I dynamically inject CSS using the this.page.header.controls.add(); on the master page to do some browser type hacking. The control you add is an HtmlLink with the attributes of a CSS file.

I wonder if you could change that to .AddAt(); and put that on the page load of your child pages to correct your CSS?

-A

Rick Strahl
September 24, 2007

# re: ASP.NET and Styles &amp; CSS Embedding

are injected at the end always. IOW, it's not in the header collection when you add it yet, so even if you add your's at the bottom (which is the default anyway) it still ends up before the themed CSS file.

@Rainer - YES! Looks like StyleSheetThemet does the right thing with the style sheet's getting dumped right at the top of the header tag! Never occurred to me that this would be the case. Awesome.

Expirementing around with both Theme and StyleSheetScheme I can see that if you specify both though you get the CSS injected twice which is kinda lame. You'd figure that one would override the other.

Doesn't matter I suppose since I only ever use styles in themes anyway... Thanks!

Ben Williams
September 25, 2007

# re: ASP.NET and Styles &amp; CSS Embedding

This was the first thing i butted up against in 2.0- No control over how you stylsheest are introduced to the page, in order to order multiple stylesheets within a them you have to alphabetise them etc etc.

I ended up creating a custom templated control which i could assign a skinid to and then added all my common css declarations that needed to be themable in a resource.skin file the each theme of my app.

eg
in page head of master page:
<TPControl:ThemeCss runat="server" ID="themecss" SkinID="Theme2Css">
        <CssLiteralTemplate>
        </CssLiteralTemplate>
    </TPControl:ThemeCss>

Then in a controls.skin file in each of the themes in your app:
<TPControl:ThemeCss runat="server" SkinID="Theme2Css">
    <CssLiteralTemplate>
<style type="text/css">
@import url("~/Assets/Style/reset-fonts-grids.css");
@import url("~/App_Styles/tp/tp.css");
@import url("~/assets/style/commonform.css");
@import url("~/assets/style/commonStyle.css");
</style>
<!--[if IE ]>
<style type="text/css">
@import "~/App_Styles/tp/tpIEAll.css";
</style>
<![endif]-->   
<!--[if lte IE 6]>
<style type="text/css">
@import "~/App_Styles/tp/tpIE.css";
</style>
<![endif]-->   

 </CssLiteralTemplate>
</TPControl:ThemeCss>


This way you can use @import to bring in your stylesheets, use conditional comments to target css overrides, er *ahem* bug fixes for various versions of IE and position the whole thing where you want it in the head. I added a lame but serviceable implementation to resolve any references of the '~' and then stored any assets referred to within the control to a App_Styles directory.

Ben

Ryan Haney
September 25, 2007

# re: ASP.NET and Styles &amp; CSS Embedding

IMO - Conditional Comments / CSS Hacks - YUCK

Just use Request.Browser to get a browser name and version, and generate the link off of that. (ex: IE6.css, IE7.css, and Firefox2.css). Place all the global css in one file, and tweak the css with overrides.

Combine that with the web developer toolbars for Firefox and IE 6/7, and you have a workable solution.

@Rick - Did you know the validation code at the bottom always fails on first attempt?

Rick Strahl
September 25, 2007

# re: ASP.NET and Styles &amp; CSS Embedding

@Ryan - Hmmm... I don't see that here. It's possible though that this can happen occasionally as the id is stored in Session and the app may be cycling. I'm having some serious issues on the server with the worker processes going down from time to time and I believe what you're seeing is likely a worker process crash killing Session state with it.

Can any of you confirm that this is occasional or on every comment?

I'm holding off for Win Server 2008 until upgrading the box (which is resource starved) and in the meantime the server is struggling a bit.

barbod
September 25, 2007

# re: ASP.NET and Styles &amp; CSS Embedding, QUESTION???

hi Rick , tanx for your nice Article. i have some question about CSS and StyleSheet.
how can i use StyleSheet Dynamically ??? means i parse some parameter to stylesheet
and creat it with dynamic parameter?
i need it , tanx.

Rick Strahl
September 25, 2007

# re: ASP.NET and Styles &amp; CSS Embedding

You can add anything to the header in ASP.NET 2.0 by using the Page.Header object. To add a stylesheet dynamically use code like this:

HtmlLink css = new HtmlLink();
css.Href = this.ResolveUrl(this.StyleSheet);
css.Attributes.Add("rel", "stylesheet");
css.Attributes.Add("type", "text/css");
this.Header.Controls.Add(css);

cristian
September 27, 2007

# re: ASP.NET and Styles &amp; CSS Embedding

Hi Rick, great post. Another trick that might help in these situations is the specificity precedence rule in CSS, e.g.

body #clockimg {border:solid 1px red}
#clockimg {border:solid 1px blue}

#clockimg will have the red border since it's more specified, the order doesn't matter

Marc Grabanski
September 27, 2007

# re: ASP.NET and Styles &amp; CSS Embedding

Could you release jQuery Calendar as a ASP.NET control? If so, I will gladly link to it from the main page, this is something I planned on doing in the near future so it would save some work. Thank you for the article.

double-oh-seven
September 28, 2007

# re: ASP.NET and Styles &amp; CSS Embedding

Rick,

This seems to be a good occasion to tell you a couple of things concerning your weblog:

1. The minimum requirements for an (X)HTML page to be valid are:
- doctype
- html element
- head element
- body element

The head element must contain
- the character set meta element
- title element

You don't have the character set meta element.

2. You should/could get rid of the keywords meta tag. It got so abused by smart alecks striving to attain higher visibility that every self-respecting search engine on the planet simply ignores it.

Fine blog, however.

P.S.
I confirm the previous reader's claim your validation fails on the first attempt.

barbod
September 28, 2007

# Thanx for replay...

hi Rick , really tanx for replay , but how can i parse parameter to StyleSheet??
for example i have a stylesheet.css that it like this : ".head{height: #height;}"
that i wanna user can set "#height" at designTime , by Properties.
sorry for my bad English! :(
pleas say about it, really tanx

Rick Strahl
September 28, 2007

# re: ASP.NET and Styles &amp; CSS Embedding

007 - thanks for the comments on Validation. You know frankly I'm not too concerned about 100% XHTML compliance especially not on my personal stuff. I know, I know it's important but some things are just too much and add little or no value.

Gotta look into the validation issue. Oddly I just can't confirm this myself. I usually post as an admin and I bypass the validation, but even if I log out and comment here I don't see the failed validation. There is a timeout on the Captcha which is 5 minutes or so - could that be it?

double-oh-seven
September 29, 2007

# re: ASP.NET and Styles &amp; CSS Embedding

[Rick Strahl]
> I'm not too concerned about 100% XHTML compliance

As soon as I had submitted my post, it occurred to me this might be your reaction. I forgot to tell you the most important stuff.

> some things are just too much and add little or no value

The character set is EXTREMELY important. If a visitor to your website has a localized version of the operating system and/or the browser, he may not see what you see on your machine. Trust me, I'm a doctor. Many French websites, for some reason, notoriously omit the character set. When I visit them they are unreadable on my machine.

> There is a timeout on the Captcha which is 5 minutes or so - could that be it?

I think not. This is why:

1. I typed my post.
2. Entered the code.
3. Clicked Post Comment.
- Didn't validate. Timed out? Possibly.
4. Re-entered the code.
- Didn't validate because I didn't fill in Your Name.
5. Filled in Your Name.
6. Re-entered the code.
7. Clicked Post Comment.
- Didn't validate. Timed out? Impossible.
8. Re-entered the code.
9. Clicked Post Comment.
- Went through.

Rick Strahl
September 29, 2007

# re: ASP.NET and Styles &amp; CSS Embedding

Correct me if I'm wrong but browsers can easily determine the charset off the content type Http header. The charset meta tag is completely superfluous.


Thanks for taking the time to check the captcha code. I see some of this if I wait long enough. I think the problem here though is that the server has been having problems - I suspect the AppDomain is bouncing up and down and everytime that happens the cache where the Captcha is stored goes with it. I'll put the AppDomain Id on the page on the bottom to see if that's indeed the problem.

Thx.

Alex Salamakha
October 03, 2007

# re: ASP.NET and Styles &amp; CSS Embedding

hey Rick!

Your blog rocks! Keep up great work!

M. Agel
December 24, 2007

# re: ASP.NET and Styles &amp; CSS Embedding

@Ben Williams: Might you provide the source control for your TPControl:ThemeCss control? I tried it for a while now, but fail to get your example working.

Ben
January 24, 2008

# re: ASP.NET and Styles &amp; CSS Embedding

A simple solution to this is to store your css files outside of the App_Themes folder and use this simple control:

public class SkinedLink : WebControl
{
    private string strHref;
    public string Href
    {
        get { return strHref; }
        set { strHref = value; }
    }

    protected override void Render(HtmlTextWriter output)
    {
        output.Write("<link  rel=\"stylesheet\" type=\"text/css\" href=\"" + this.ResolveUrl(strHref) + "\" />");
    }
}


This allows you to place your link tags at any point in your head element. You just set the Href property in your skin file. You can use multiple controls with different SkinID attributes and allows you to bring the cascading back to css in your asp.net themes.

Rick Strahl
January 24, 2008

# re: ASP.NET and Styles &amp; CSS Embedding

@Ben - not a bad idea for embedding stylesheets but it doesn't do much for themes. If you change the theme you wouldn't get a theme specific css file. Themes do more than just ref a single css file - they can be differently named and it will pull all css files into the page.

But you're right, maybe it is better to just ditch themes. Building a custom control as you describe may be easier with an application global variable and some simple rules for picking style sheets (out of a directory or otherwise). Then again themes 'just work' with the exception of the placement overrides.

Ben
January 28, 2008

# re: ASP.NET and Styles &amp; CSS Embedding

I'm not sure why you say "If you change the theme you wouldn't get a theme specific css file." With this approach, you could include several SkinedLink tags, each with a different SkinID. Then in your skin files you can set the Href properties to css files specific to the respective themes. Changing the theme changes the skin file that is used and would therefore pull in different css files.

My biggest gripe with ASP.NET themes, and one of the reasons I devised this method, is that all of the css files are pulled into every page. This goes against some of the primary principles of css. If all of your css files are going to be pulled into every page, what is the purpose of having multiple files?

Ivan
February 29, 2008

# re: ASP.NET and Styles &amp; CSS Embedding

What's up with my double-injection of the CSS link in to my generated pages?

Is this going to affect anything?

Ivan
February 29, 2008

# re: ASP.NET and Styles &amp; CSS Embedding

FYI - I discovered that when I changed my web.config from:
<pages styleSheetTheme="default" theme="default">

to

<pages theme="default">

it fixes the error

William
March 20, 2008

# re: ASP.NET and Styles &amp; CSS Embedding

It seems that the folders in the them folder are loaded alphabetically as well. So if you want to load a stylesheet b.css before a.css then put b.css in folder1 and a.css in folder2.

JJ
April 14, 2008

# re: ASP.NET and Styles &amp; CSS Embedding

Using

HtmlLink css = new HtmlLink();
css.Href = this.ResolveUrl(this.StyleSheet);
css.Attributes.Add("rel", "stylesheet");
css.Attributes.Add("type", "text/css");
this.Header.Controls.Add(css);

in master page, VS 2008 RTM (3.5), I get squiggly lines, "The class or cssclass value is not defined", in all code pages.

Anyone know how to shut it off?

xxcemil
May 31, 2008

# re: ASP.NET and Styles &amp; CSS Embedding


Clinton Gallagher
December 09, 2008

# re: ASP.NET and Styles &amp; CSS Embedding

Hello Rick, I've tried your strategy using BrowserCapabilities and a switch statement to write the <link> element into the body of the HTML but it doesn't seem to be parsed by Firefox 3 (so far).

So after all this time and experience by those trying to use Themes what if any work-around actually supports the use of IE conditional comments but more importantly; how to change CSS stylesheets to cope with quirks of the other browsers?

Rob von Nesselrode
January 19, 2010

# re: ASP.NET and Styles &amp; CSS Embedding

Your Css contains references to images....

In my case, all css, scripts and images have to be embedded resources.

I haven't got images such as those only referenced from within a style sheet to behave yet.

The whole lot is embedded as I work in in a "portal" environment where we add custom controls and can not add to the root file system.

My "mental" hold on all this is that if you use the Assembly:WebResource declaration, then use scriptmanager to get the resource you can then use:

ClientScript.GetWebResourceUrl(....)

to return a runtime location.

As per you other article in which you create a "ControlResources" class, it works fine for scripts but I'm stuffed If I can get to work with these '$%*(%.. embedded CSs images.

Do you have an example of that?

Regards

Rob

Rashed
June 18, 2014

# re: ASP.NET and Styles &amp; CSS Embedding

Hello,
I applied all the rules to add a css in my custom control, but I can't find any effect of my css in parent page.
There is not any bug and I can see the css added in my custom control. but when I see the output viewsource I can see the CSS converted in WebResource.axd/.......encoded value.....
when I click in this link it shows 404 message that it cant find.

> Is there any change needed in web.config ?

I added a reference:

[assembly: WebResource("CustomizedModule.style.css", "text/css")]
namespace CustomizedModule
{

public class ServerCtrlCustomized : CompositeControl
{
....

On prerender I added this code:

protected override void OnPreRender(EventArgs e)
{
// Add our css file to our custom control
string cssUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(),"CustomizedModule.style.css");
HtmlLink cssLink = new HtmlLink();
cssLink.Href = cssUrl;
cssLink.Attributes.Add("rel", "stylesheet");
cssLink.Attributes.Add("type", "text/css");
this.Page.Header.Controls.Add(cssLink);
base.OnPreRender(e);
}

Please suggest me. I am stack with this problem from 2 days.
thanks.

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024