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#:
using System;
using
System.Data;
using
System.Security.Permissions;
using
System.Web;
using
System.Web.Caching;
using
System.Web.Hosting;
using
System.Collections;
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.