Pages

Sunday, August 16, 2009

Themes are a great feature in our beloved ASP.NET platform.

Themes are a great feature in our beloved ASP.NET platform.

They allow us to easily create skins for our controls and we can bind stylesheets to each theme.

But did you notice most of the times we use a main color in a theme's stylesheet, and two or three other related colors, and we continously repeat their #rrggbb code along the whole .css files?
Most of the times, different themes simply means different color.
And there we go customizing the stylesheet files to repleace that red with this blue, etc.

Here I'm going to explain a way to create a Theme-based styleheet parameterizer which will allow us to place variable placeholders in our .css files.
The variables' values can be assigned from a Theme-dependant .xml file, our we could customize this parameterizer to get the values from a database.

The StylesheetParameterizer will be implemented as an HttpHandler which will handle each request to .css files, parse them looking for the variable placeholders, and replace their value depending on the current theme.

Since an HttpHandler is not theme-aware, we will need an HttpModule as well. It will listen to each page request and store the page's Theme name in a Session variable that will later be accessed by the HttpHandler.

We will start from the HttpModule:


using System;
using System.Web;
using System.Web.UI;

public class StylesheetParameterizerModule : IHttpModule
{
#region IHttpModule Members

public void Dispose()
{
}

public void Init(HttpApplication context)
{
context.PostRequestHandlerExecute += new EventHandler(context_PostRequestHandlerExecute);
}

void context_PostRequestHandlerExecute(object sender, EventArgs e)
{
if (HttpContext.Current.Handler is Page)
HttpContext.Current.Session["StylesheetParameterizerTheme"] = ((Page)HttpContext.Current.Handler).Theme;
}

#endregion
}

This is a simple IHttpModule implementation, handling the PostRequestHandlerExecute event.
After a request has been executed, this module verifies if the executed handler was a Page.
If so, it stores the Page's Theme property in a Session variable.

This module has to be registered in the web.config file in the section ( in IIS7).



Now let's give a look at the HttpHandler.


using System.IO;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.SessionState;

public class StylesheetParameterizerHandler : IHttpHandler, IReadOnlySessionState
{
DataTable _parameters;

#region IHttpHandler Members

public bool IsReusable
{
get { return false; }
}

public void ProcessRequest(HttpContext context)
{
string requestedStylesheetPath = context.Request.PhysicalPath;
string stylesheet = string.Empty;

using (StreamReader reader = new StreamReader(requestedStylesheetPath))
{
stylesheet = reader.ReadToEnd();
reader.Close();
}

string cacheKey = "ParameterizedStylesheet_" + context.Session["StylesheetParameterizerTheme"].ToString();
if (context.Cache[cacheKey] != null)
stylesheet = context.Cache[cacheKey].ToString();
else
{
_parameters = getParameters(context.Session["StylesheetParameterizerTheme"].ToString());

Regex regex = new Regex("\\$.+\\$", RegexOptions.IgnoreCase);
stylesheet = regex.Replace(stylesheet, matchEvaluator);

context.Cache.Add(
cacheKey,
stylesheet,
null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
TimeSpan.FromMinutes(context.Session.Timeout),
System.Web.Caching.CacheItemPriority.Normal,
null);
}

context.Response.Write(stylesheet);
}

#endregion
}

One important thing to note: the class implements the IReadOnlySessionState marker-interface.
An HttpHandler is not allowed to access the SessionState by default.
Implementing this interface tells the framework that the HttpHandler needs to access the SessionState, in read only mode.

The ProcessRequest method is where the magic occurs.

The handler reads the physical file that was requested (the .css stylesheet file), and stores its full content in a local string variable.
The handler looks then for the variable plaholders through the \$.+\$ regular expressions.
This means our placeholders will have this format within a stylesheet:

body
{
color: $color$;
}

In this example $color$ will be a placeholder that will be substituted by the "color" variable value defined in the parameter xml file.

Each match will be handled by the matchEvaluator() method.

This is the matchEvaluator() body


private string matchEvaluator(Match match)
{
string name = match.Value.Substring(1, match.Value.Length - 2);
DataRow row = _parameters.Rows.Find(new object[] { name });

return row["value"].ToString();
}

Parameters are stored in a DataTable that will be populated by the getParameters() method.
The matchEvaluator looks for a row in the table whose "name" column value equals the name of the matched variable in the stylesheets.
It then returns the corresponding value as a substitution.

Finally, this is the getParameters() implementation.


private DataTable getParameters(string theme)
{
DataTable parameters;

string parametersXmlPath = string.Format("~/App_Themes/{0}/StylesheetParameters.xml", theme);

DataSet dataSet = new DataSet();
dataSet.ReadXml(HttpContext.Current.Server.MapPath(parametersXmlPath));
parameters = dataSet.Tables[0];
parameters.PrimaryKey = new DataColumn[] { parameters.Columns["name"] };

return parameters;
}

This method reads the StylesheetParameters.xml file within the current Theme's folder.
The xml file is parsed as a DataTable and will have the following format:


This implementation can be customized for retrieving the parameters from different other stores: database, remote webservices, web.config configuration file, etc.

A little note about Caching: the whole modified stylesheet is stored in the Application Cache so it can be shared by any Session in the same application.
The stylesheet is stored in Cache with a sliding expiration equal to the timeout period of a Session.
This will prevent too much overload since this handler will be invoked on each page request.

To get it working, we have to register the handler in the web.config configuration file


That's all.

Now we can put a StylesheetParameters.xml file in each Theme's folder, and have a single stylesheet with variable placeholders set as needed.

I hope you will find the StylesheetParameterizer helpful in your projects.

Feedback will be greatly appreciated.

1 comment:

  1. Ya.. Its a very useful link which i never seen.
    This creator have d mindblowing knowledge in .Net.. Amazing!!!!!!!!!!!

    ReplyDelete