ASP.NET 2.0 Themes have a couple design flaws, all of which center around the way Themes reference Cascading Style Sheets (CSS). I’ve been posting Theme and CSS related issues and work arounds as I encountered them, and in one of my first posts I outlined the possibility of using a VirtualPathProvider to ignore the CSS files within a directory. In this post I share David Marzo’s implemented of this solution. This resolution resolves most of the issues surrounding Themes and CSS, and essentially puts the Cascades back into CSS.
The Problem / Question:
ASP.NET 2.0 Themes automatically includes all the Style Sheets (.css files) found in the active Theme (App_Theme) directory into the Head of the rendered document. This severely limits the robust CSS language, and works against the last decade of Cascading Style Sheet progress. For more specific details on the problems with ASP.NET 2.0 Themes start reading the articles found here.
A Solution / Work Around:
Add a custom VirtualPathProvider to ignore the Style Sheets (CSS files) found in the App_Theme directory. For more background on this solution see my article and David Ebbo’s article titled Overriding ASP.NET combine behavior using a VirtualPathProvider.
An example of the problem:
The directory structure:

Notice all the Style Sheets (.css files), there are more in the Adapters directory too.
Before including the CustomVirtualPathProvider (code provided below) into the project’s build – the XHTML rendered by Themes:
<html xmlns="http://www.w3.org/1999/xhtml" >
<head><title>The Problems With Themes and Skins in ASP.NET 2.0</title>
<link href="App_Themes/Default/CSS/Adapters/ChangePassword.css" type="text/css" rel="stylesheet" />
... The other 17 externally linked Style Sheets go here, they were removed to improve readability ...
<link href="App_Themes/Default/CSS/PrinterFriendly.css" type="text/css" rel="stylesheet" />
</head>
<body>
The Style Sheets are automagically inserted into the Head tag from the active Theme directory. Keep in mind that Internet Explorer has a 30 Style Sheet limitation (see article Q262161).
After including the CustomVirtualPathProvider (code provided below) into the project’s build – the XHTML rendered by Themes:
<html xmlns="http://www.w3.org/1999/xhtml" >
<head><title>The Problems With Themes and Skins in ASP.NET 2.0</title>
</head>
<body>
Notice the complete lack of Style Sheets – ahhh simplicity is bliss, the very foundations of CSS Zen Enlightenment… :) Now we can manually include our Style Sheets, use Conditional Comments, and so on.
The source code for the CustomVirtualPathProvider provided by David Marzo in C#:
namespace Arfila.Web.Logic {
[AspNetHostingPermission(SecurityAction.Demand,
Level = AspNetHostingPermissionLevel.Medium)]
[AspNetHostingPermission(SecurityAction.InheritanceDemand,
Level = AspNetHostingPermissionLevel.High)]
public class CustomVirtualPathProvider : VirtualPathProvider {
public static void AppInitialize() {
HostingEnvironment.RegisterVirtualPathProvider(new CustomVirtualPathProvider());
}
public CustomVirtualPathProvider() : base() { }
private bool IsThemeDirectory(string virtualPath) {
String checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
return checkPath.StartsWith("~/App_Themes/",
StringComparison.InvariantCultureIgnoreCase);
}
public override VirtualDirectory GetDirectory(string virtualDir) {
if (IsThemeDirectory(virtualDir)) {
return new ThemeDirectory(Previous.GetDirectory(virtualDir));
}
else {
return Previous.GetDirectory(virtualDir);
}
}
}
[AspNetHostingPermission(SecurityAction.Demand,
Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(SecurityAction.InheritanceDemand,
Level = AspNetHostingPermissionLevel.Minimal)]
public class ThemeDirectory : VirtualDirectory {
VirtualDirectory _toFilter;
private ArrayList _children = new ArrayList();
private ArrayList _directories = new ArrayList();
private ArrayList _files = new ArrayList();
public override IEnumerable Children {
get { return _children; }
}
public override IEnumerable Directories {
get { return _directories; }
}
public override IEnumerable Files {
get { return _files; }
}
public ThemeDirectory(VirtualDirectory toFilter) : base(toFilter.VirtualPath) {
_toFilter = toFilter;
BuildChild();
}
private void BuildChild() {
foreach (VirtualDirectory dirToFilter in _toFilter.Directories) {
ThemeDirectory themeDir = new ThemeDirectory(dirToFilter);
_children.Add(themeDir);
_directories.Add(themeDir);
}
foreach (VirtualFile fileToFilter in _toFilter.Files) {
string fileExtension = VirtualPathUtility.GetExtension(fileToFilter.VirtualPath).TrimStart('.');
if (string.Compare(fileExtension, "css", true) != 0) {
_children.Add(fileToFilter);
_files.Add(fileToFilter);
}
else {
//archivo .css no incluir
}
}
}
}
}
Some of the code has been modified for readability, download the original source code here.
Note: In order to use this VPP you’ll have to copy the code above into a new class in your App_Code directory.
Caveat: If a Web site is precompiled for deployment, content provided by a VirtualPathProvider instance is not compiled, and no VirtualPathProvider instances are used by the precompiled site. – Taken from the article titled
VirtualPathProvider Class on MSDN.
Conclusion: This is one of the nicest work arounds or resolution to the issues surrounding ASP.NET 2.0 Themes. It allows us to leverage the power of ASP.NET 2.0 default Skins, allows us to logically group design related resources (Style Sheets, images, default Skins, videos, etc…) in the App_Theme directory, allows us to control the loading order (cascades) of style sheets, allows us to use Conditional Comments, to define Media Types, to override / inherit Styles, and to continue using CSS as it’s intended. In addition we can now easily integrate the ASP.NET 2.0 CSS Friendly Control Adapters into our web applications.