Customizing the look of a web site using ASP.NET themes

by Michael F. Collins, III September 14, 2008 10:52

In this post, I'll continue my discussion of the construction of a new web site for ImaginaryRealities.com by discussing how I'm going to use ASP.NET themes to style and configure the "look" for my new web site. ASP.NET 2.0 introduced a new feature called themes which allows responsibilities on a web site to be truly separated between a developer that implements functionality and a designer that implements style. Using themes, the same web site can take on several different looks through a simple modification of the Web.config or application setting in a production environment. New looks can also be easily developed and deployed without making modifications to the existing web site. This post will discuss what ASP.NET themes are, how they work, and how they can be used in an ASP.NET web site or application.

What are ASP.NET themes?

An ASP.NET theme is basically a collection of files called skins, CSS style sheets, and other theme-based resources such as images, icons, and backdrops that are used to style a web site. In ASP.NET, there are two types of themes: themes and style sheet themes. It's a confusing concept, but it will make sense in a moment. The important thing to know is that themes and style sheet themes are the same, but ASP.NET applies them differently depending on what theme is specified as the theme, and what theme is specified as a style sheet theme. I'll revisit this topic and explain themes and style sheet themes in a moment. First, let's look at what's in a theme.

The first item that goes into an ASP.NET theme is usually a theme-specific CSS style sheet. Actually, a theme can have more than one CSS style sheet, but if you create multiple style sheets for your theme, you should know that ASP.NET automatically adds the style sheets in alphabetical order. This is important, for example, if you define one style sheet to control or modify the structure and layout of the document, and another style sheet that has colors and background images. A typical strategy that I employ is to prefix the style sheet's file name with a number indicating the order of inclusion. For example, I may have the following style sheets defined for my theme.

  • 01_Layouts.css
  • 02_Styles.css

The second item that goes into an ASP.NET theme are files called skins. A skin file has a .skin extension and contains markup for ASP.NET controls. At run time, ASP.NET will use the control definitions in a skin and will merge the skin definitions with the control definitions in a user control, page, or master page. This allows the designer to set style-specific attributes on an ASP.NET control in a skin, and let the developer specify the functional aspects of the control in the ASP.NET page or user control.

Let's see an example of a skin in action. Most web sites will have some kind of logo in the upper left-hand corner of the web page. If the user clicks on the logo, they will typically be taken back to the home page of the web site. It's kind of a universal fail-safe if the user ever gets lost in the vast web of a large web site. In an ASP.NET page or master page, the developer may insert the following control definition for a hyperlink:

(Notice the meta:resourcekey attribute that will be used for globalizing the web page that we discussed in my last post.)

This ASP.NET fragment uses the HyperLink control to specify a hyperlink that will take the user back to the home page of the web site. It's great, but it's not the logo that I had in mind. When I view it in my development environment with the raw, non-styled HTML, I'll see the link Return to the ImaginaryRealities.com home page instead of a cool ImaginaryRealities Software Company logo.

Later in the development phase for the web site, a designer (or me in a designer role) can sit down and create a skin for this hyperlink control that will use the new logo that I designed for my new web site (when I actually learn to design something cool). Here's what the skin would look like:

The key to this skin and the above control fragment is the SkinID attribute. Notice how the two SkinID values match? If I defined a HyperLink skin without specifying the SkinID attribute for it, then all of my hyperlinks on the page (the hyperlinks created with the HyperLink control and not normal, non-ASP.NET tags) would appear as my web site logo. A skin control specified without a SkinID attribute is called a generic skin because it is the default skin that is applied to all controls of that type. Generic skins could be useful if you wanted to apply a specific CSS style or class to all HyperLink controls in your document.

The third component of ASP.NET themes are called resources. A resource is basically a file such as an image or icon that is used for a theme. Let's say that I wanted to create a normal theme that my web site would regularly use, but I would like to change the colors and the look for major holidays such as the 4th of July, Halloween, Thanksgiving, Christmas, and St. Patrick's Day (since I'm an Irish-American of course). For the 4th of July, I want my logo to be shown with fireworks and the U.S. flag. For Halloween, I want more of an orange-color theme and I want to put a pumpkin on the logo. For Thanksgiving, I want a turkey prominently displayed around the logo. For Christmas, I want to use red and green colors and have presents, a Christmas Tree, Santa Claus, and a fireplace around my logo. And for St. Patrick's Day, I want to have more of an emerald green theme with shamrocks on my logo and a leprechaun sitting on top of it, drinking a pint of beer. I can do this by including these specific logo images in each of my themes, and then using a relative reference to the image in my skins just like I did in the above example.

Back now to the different kinds of themes that ASP.NET supports. There's actually a third theme that I didn't tell you about yet. That is a global theme. Global themes are installed in a global location on a web server and are accessible to all ASP.NET applications to use. I'm not really going to discuss installing and using global themes, because my web site is hosted on a third-party web hosting provider (WebHost4Life), and I can't install or have any control over the global themes. However, it is important to understand how global themes play in the ASP.NET theme engine because themes have an order of precedence.

When using ASP.NET themes, especially given so many choices as to where to put style information, there can be conflicts. What happens if you specify the color for a HyperLink in three places: on the style sheet theme, on the page, and on the application theme? Which definition wins? That is determined by the order of precedence:

image

Style sheet themes provide the initial value for control properties containing theme and style information. ASP.NET will also apply CSS style sheets in a style sheet theme before any other styles are applied to a page. ASP.NET will accomplish this by adding the CSS style sheets in a style sheet theme to the beginning of the element in the rendered HTML page.

Once the style sheet theme has been applied, then ASP.NET will apply the CSS styles and control settings in the page. Style settings in the ASP.NET master pages, page, or user controls will override settings that have been set in the style sheet theme's CSS style sheets or skins.

Third, ASP.NET will apply the CSS style sheets and skins in the application theme. CSS styles or control settings in the application theme will override any competing settings in either the style sheet theme, master page, page, or user control. ASP.NET will include the CSS style sheets in the application theme at the end of the element to ensure that the CSS styles in the application theme will override CSS styles defined in either the style sheet theme or the page.

Finally, if a global ASP.NET theme has been defined on the web server or by the hosting provider (which is unusual), then the skin settings and CSS style sheets will override the competing settings in either the style sheet theme, page, or application theme.

Cascading style sheets

Before moving on to actually using themes, I want to revisit one of the most important concepts in CSS: the cascade. Remember that CSS is an acronym for Cascading Style Sheets. What this means is that if multiple styles or style sheets are defined, then there are rules for which styles take precedence. You may be thinking that you'll only define a single CSS style sheet for your web application so that you won't have to deal with cascades or conflicting rules, but you'll actually never be able to avoid it. That's because browsers specify their own style sheet and users can apply their own style sheet to customize the look of web pages in their browser. An example of the latter case is a user that has vision problems and wants larger body text on web sites to reduce strain on his eyes.

SitePoint has a good article that fully explains the process of "the cascade," but here's a brief summary. Browsers classify style sheets into three categories: user agent style sheets, author style sheets, and user style sheets. User agent style sheets define the default representation of HTML in a browser and are provided by the browser manufacturer. Internet Explorer's style sheet is going to be different than the style sheets for Firefox, Opera, and Safari. The default style sheets are usually designed to maximize the performance and look of pages in the selected web browser. Some browsers, such as Internet Explorer, will also let users apply their own style sheet to customize the default style sheet. This would be done for the purpose that I described earlier. Finally, author style sheets are defined by the web site author or designer to maximize the look of the web site such as the color schemes or to apply branding to the site. Also remember that web site authors can specify multiple style sheets for an application that may contain conflicting styles.

The first step of rendering web pages in a browser is for a browser to take each HTML element in a web page and find all of the applicable styles that could apply to that element from the three sources of CSS styles. If there are no conflicts between the difference CSS style sheet sources, then the browser's job is easy and the browser can apply the selected styles to the HTML element. However, if there are conflicts, the process gets a little bit trickier.

The next step for the browser, if there are conflicts, is to sort the applicable CSS styles based on their origin and level of importance. Importance is determined by whether a CSS style is specified using the !important modifier. If a CSS style has the !important modifier, then the CSS style is considered an important declaration, otherwise the CSS style is a normal declaration. CSS styles are sorted in the following order (1 being the highest priority and 5 being the lowest):

  1. Important declarations in user style sheets
  2. Important declarations in author style sheets
  3. Normal declarations in author style sheets
  4. Normal declarations in user style sheets
  5. User agent declarations

What's important to see here is that important declarations in a user style sheet trump all other style definitions. So, if a user really wants your body text to be in a 48-point Times New Roman font, then it's going to be that way if the user applies the !important modified to his custom CSS. There's nothing that you can do about it.

What happens, if after this sorting, we still have a conflict? What if we have two conflicting rules at the same priority? We go to the third step in the cascading process: specificity. Specificity states that "when two or more declarations that apply to the same element, and set the same property, have the same importance and origin, the declaration with the most specific selector will take precedence (SitePoint, http://reference.sitepoint.com/css/specificity)."

Specificity is a math-based calculation to determine which CSS style is more specific to the HTML element being styled. First, the web browser will look at the HTML element to see if an inline style attribute is specified. This can also be done in an ASP.NET control using the Style property on the control, if it is supported. If an inline CSS style is specified, then that style is used. For example:

This text is 48 pixels tall!

The above HTML fragment will have text that is 48 pixels in height. If the HTML element does not have an inline style attribute, then the next step in the process is to look at the CSS rules and count the number of matching id elements between the CSS rule and HTML element. Let's use the following HTML fragment for this example:

...
...
....
...

Let's also create a CSS style sheet for this HTML document:

#doc4
{
    font: normal normal normal 12px Arial;
}

#introduction
{
    font: normal normal bold 16px "Times New Roman";
}

#bd #introduction
{
    font: italic normal normal 14px Verdana;
}

#doc4 #bd #introduction
{
    font: italic normal bold 10px Georgia;
}

According to the rules of specificity, the 4th rule (#doc4 #bd #introduction) is the most specific because it has the highest count of matching ID selectors. The next most specific rule would be the third rule (#bd #introduction), and the first two rules would have the same level of specificity. If two or more rules exist with the same level of specificity, or if there are no matching rules with id selectors, then the next step is to do the same thing with class selectors, attribute selectors, and pseudo-classes:

/* class selector */
.bodyText { ... }

/* attribute selector */
input[type="submit"] { ... }

/* pseudo-selector */
a:hover { ... }

If a conflict still exists, then the final step in specificity is to count the number of matching element type selectors (for example, DIV, INPUT, P, etc.) and pseudo-elements (for example, first-letter, first-line, etc.).

If after all of these steps there are still conflicts, then the decision is simple. Whichever CSS rule is specified last is the winner. This also plays into how ASP.NET themes work in regards to what order CSS style sheets are included in a page. Style sheet theme CSS files are always included at the beginning of the element, followed by CSS style sheets specified by the page or master page. Application theme CSS files are then included at the end of the element, followed by CSS style sheets from the global theme. Therefore, the rules of theme precedence also correspond to the rules of CSS cascades.

The reason for reviewing how CSS cascades work is important if you use style sheet themes, because you need to have a very high level of specificity in your style sheet theme CSS files to counter lower-level CSS styles in CSS style sheets that are imported later in the process. In my earlier posts, I designed my web site using the Yahoo! User Interface Library's Reset CSS style sheet that neutralizes the differences in the default browser style sheets in order to implement a common default look to web pages. If I try to set a background color in a style sheet theme CSS file by only specifying the BODY tag selector, this rule will be overridden by the Reset CSS definition that clears the page background. However, if I use the id for my BODY tag (remember, it's www-imaginaryrealities-com), then my style sheet theme's CSS rule will have a higher precedence for the CSS background-color attribute than the YUI Reset CSS's rule.

Creating an ASP.NET theme

Now that we have all of the theory of themes behind us, we can focus on building a theme in ASP.NET. In an ASP.NET web site, all themes are placed in a special folder named App_Themes that is located in the root folder of your web site. Inside of the App_Themes folder are more sub-folders. Each sub-folder contains a single theme that has the same name as the sub-folder. For example:

  • ~/App_Themes/Default: default theme
  • ~/App_Themes/StPatricks: St. Patrick's Day theme
  • ~/App_Themes/July4th: 4th of July theme
  • ~/App_Themes/Halloween: Halloween theme
  • ~/App_Themes/Thanksgiving: Thanksgiving theme
  • ~/App_Themes/Christmas: Christmas theme

Inside of each of the theme folders are all of the CSS style sheets for the theme, skins for ASP.NET controls, and related resources such as images, logos, music or audio files, and icons.

With these themes defined, we need to look at the strategy for using the themes. If we avoid using style sheet themes, which we can, we would have to create our default theme and then copy that theme and modify it for each of the special holiday themes. That could be a lot of work if you later modify the look of your web site because then those changes have to be pushed to all of the holiday themes. What would be better is to just have the holiday themes modify those elements that the need to, such as the background color or web site logo.

My typical strategy for using ASP.NET themes is to create the Default theme and set this theme as the style sheet theme for my web site. If no other style rules are specified, then my default skins and CSS styles will be used. This means also that my Default theme is larger than the other themes and has most of the images that I use on the web site. Also, remembering the rules of theme and CSS precedence, my Default theme is also highly specific with regards to CSS because I remember that I am using the YUI! Reset CSS inside of my document. So, if I want to specify a default background image or color in my default theme, I can override the background reset in the YUI Reset CSS by specifying the id value of my BODY tag (www-imaginaryrealities-com).

The only issue with using the Default theme as the style sheet theme is that controls on the ASP.NET master page, page, or user controls can override my style settings. This is ok, because as a general rule I do not allow myself to put any kind of style information on ASP.NET controls in a page. I only do styling through CSS actually and at most will specify the name of the CSS class for an ASP.NET control to use. I usually never set the font or color information directly on a CSS control. But I leave myself the option for doing so if I decide at some point that it may be necessary. And if that is the case, I'll only do so on a skin and not on an actual control and I'll specify the SkinID property on the skin and ASP.NET control.

Once the Default theme is established, then I can create the additional holiday themes as variations or deltas of the Default theme. I only have to include the CSS styles and skins that I specifically want to override from the Default theme. Plus, I only have to include the images and other resources that I want to show for my holiday look, and I can still have access to and use all of the original resources in the Default theme that I don't want to override. When a holiday approaches and I want to apply my holiday theme, I simply configure my web site to apply the holiday theme on top of my Default theme. We'll discuss how to do this next.

Using an ASP.NET theme

There are three ways to configure the style sheet theme or application theme for an ASP.NET web site or application:

  • Modify the Web.config file to apply the style sheet theme or application theme globally across the application.
  • Setting the themes declaratively on an ASP.NET page.
  • Setting the themes programmatically on an ASP.NET page.

The themes in an ASP.NET web site or application can be applied globally to all pages in the application using the Web.config file:


    
        ...
    

You can also customize the theme by using a Web.config file in a sub-folder to apply a folder-specific theme. This could come in very handy if you were running an e-commerce store selling holiday goods and you wanted part of your site to have a Christmas theme while another part of your site has a Halloween theme.

The Web.config is a great place to easily control the theme settings for your entire site, but what happens if you have a page or two that you always want to look different from the rest of the pages in your site? In this case, you can control the theme declaratively in the Page directive at the top of an ASP.NET page:

<%@ Page StyleSheetTheme="Default" Theme="BahHumbug" %>

In this case, I can set most of my site to use my Christmas theme, but I can have Mr. Scrooge's biography page to be very "business as usual."

Finally, you can set the style sheet theme and application theme for your web page programmatically, although there's a bit of a trick to it. When you set the themes programmatically, you need to do so at a specific point in the page's lifecycle, otherwise you'll get exceptions from ASP.NET. Specifically, the theme settings for a page can only be changed in the PreInit event of the page lifecycle.

Why would you set the theme programmatically? It turns out that this is a great technique that works if you are building a web site framework similar to Community Server, BlogEngine.NET, MySpace, or FaceBook. If you build a web site that can be customized, or where each user can have their own sub-site, then you can allow users to choose their own themes for their sub-sites. You can store the theme selections in the definition of the sub-site, and when a user requests a page from the sub-site, you can set the sub-site theme while serving content from the same web site template such as a blog or community site template.

Another area where setting the themes programmatically can help is in testing out a theme. Remember in my last post, I modified the base WebsitePage class to allow me to specify the culture values to test out translations without having to change my browser settings. Likewise, it would be great if I could pop open a browser or pop-up window and specify the themes in my query string so that I could test or preview a theme before turning it on for my web site or sub-site. I made a similar modification to the WebsitePage class to set the themes during the PreInit event from attributes specified in the request query string:

//------------------------------------------------------------------------
// 
// Copyright (c) 2008 ImaginaryRealities Software Company
// 
// 
// Implements the WebsitePage abstract base class that is used as the base
// class for all web pages on the web site.
// 
//------------------------------------------------------------------------

namespace ImaginaryRealities.Website.UI.Framework
{
    using System.Globalization;
    using System.Threading;
    using System.Web.Configuration;
    using System.Web.UI;
    using Wintellect.Diagnostics;

    /// 
    /// Abstract base class for web pages in the website.
    /// 
    [CodeOwner("Michael F. Collins, III", "michael@imaginaryrealities.com")]
    public abstract class WebsitePage : Page
    {
        /// 
        /// Initializes a new instance of the WebsitePage class.
        /// 
        protected WebsitePage()
        {
            this.InitializePageWidth();
        }

        /// 
        /// Gets the CSS class name for the web page.
        /// 
        /// 
        /// The value of this property is the name of the CSS class that
        /// represents the specific web page.
        /// 
        public abstract string PageCssClassName
        {
            get;
        }

        /// 
        /// Gets the identifier that is used to specify the width of the
        /// web page.
        /// 
        /// 
        /// The value of this property is the identifier that is used to
        /// specify the width of the content area of the web page.
        /// 
        public string PageWidth
        {
            get;
            protected set;
        }

        /// 
        /// Gets or sets the name of the ASP.NET theme to use as the style
        /// sheet theme for the page.
        /// 
        public override string StyleSheetTheme
        {
            get
            {
                var themeName = this.Request.QueryString["stylesheettheme"];
                return themeName ?? base.StyleSheetTheme;
            }

            set
            {
                base.StyleSheetTheme = value;
            }
        }

        /// 
        /// Sets the page and thread culture and UI culture values based on
        /// values provided in the request's query string.
        /// 
        protected override void InitializeCulture()
        {
            var culture = this.Request.QueryString["culture"];
            if (!string.IsNullOrEmpty(culture))
            {
                this.Culture = culture;
                Thread.CurrentThread.CurrentCulture =
                    CultureInfo.CreateSpecificCulture(culture);
            }

            var uiCulture = this.Request.QueryString["uiculture"] ?? culture;
            if (string.IsNullOrEmpty(uiCulture))
            {
                return;
            }

            this.UICulture = uiCulture;
            Thread.CurrentThread.CurrentCulture =
                CultureInfo.CreateSpecificCulture(uiCulture);
        }

        /// 
        /// Sets the theme and style sheet theme for the page based on values
        /// provided in the request's query string.
        /// 
        /// The event arguments.
        protected override void OnPreInit(System.EventArgs e)
        {
            var theme = this.Request.QueryString["theme"];
            if (!string.IsNullOrEmpty(theme))
            {
                this.Theme = theme;
            }

            base.OnPreInit(e);
        }

        /// 
        /// Initializes the  property by loading the
        /// default page width from the website configuration.
        /// 
        private void InitializePageWidth()
        {
            var configurationSection = (WebsiteConfigurationSection)
                WebConfigurationManager.GetSection("website");
            this.PageWidth =
                configurationSection != null
                    ? configurationSection.PageWidth
                    : "doc";
        }
    }
}

The two additions to this class is the override of the Page.OnPreInit method on line 108. OnPreInit will set the value of the Page.Theme property from the theme query string attribute if it is provided. The second addition is the override of the Page.StyleSheetTheme property on line 64. The reason why I had to override Page.StyleSheetTheme instead of putting this code into the OnPreInit method is that the Page.StyleSheetTheme property is called before OnPreInit is called. Basedo n this implementation, if the stylesheettheme attribute is specified on the query string, it will override the default style sheet theme set on the page or in the Web.config file.

Using this updated base class, I can test out different themes with URLs like the following:

If you want to make sure that I'm keeping honest with you, you can also try this: http://www.imaginaryrealities.com/beta/Default.aspx?stylesheettheme=Default&theme=Christmas&culture=es-MX.

Remember, since I'm using the YUI Reset CSS, I can't do something like this in my Default theme's CSS file:

body
{
    background: Gray;
}

The problem is that if I do this, then the YUI Reset CSS will override my background color given the rules of CSS cascades. Instead, my CSS rule needs to be more specific than the YUI Reset CSS:

body#www-imaginaryrealities-com
{
    background-color: Gray;
}

Then, if I were to apply a holiday theme, the holiday theme has to be just as specific as the Default theme in order to override the Gray background color using CSS cascades. This is the CSS for the Christmas theme:

body#www-imaginaryrealities-com
{
    background-color: Red;
    color: Green;
}

The Christmas theme's CSS rule is just as specific as the Default theme's CSS rule, however, because the Default theme is the style sheet theme and the Christmas theme is the application theme, the CSS for the Christmas theme is included after the CSS for the Default theme. Therefore, using the CSS cascade rules, the CSS for the Christmas theme overrides the CSS for the Default theme.

Conclusion

In this post, I introduced you to ASP.NET themes and skins. We covered the differences between application themes and style sheet themes. I also reviewed for you the rules behind CSS cascades. I then explained how to create ASP.NET themes in your web site and showed you how to turn on themes using the Web.config file, declaratively in your .aspx page markup, or programmatically. I also enhanced the WebsitePage base class for my web site to allow me to set the style sheet and application themes using the request query string for testing and previewing theme changes before applying themes.



Tags: , , ,

ASP.NET | ImaginaryRealities.com | ASP.NET Themes | CSS

Comments

Powered by BlogEngine.NET 1.5.0.7
Theme by Mads Kristensen | Modified by Mooglegiant

Calendar

<<  March 2010  >>
MoTuWeThFrSaSu
22232425262728
1234567
891011121314
15161718192021
22232425262728
2930311234

View posts in large calendar

What I'm reading now


Add to Technorati Favorites

Disclaimer

The views expressed on this website/blog are the opinions of Michael F. Collins, III, and do not necessarily reflect the views of my employer.