Dynamic Themes
Assigning Page Themes at Runtime
October 30, 2009
ASP.NET Under the Hood
LANGUAGES:VB.NET | C#
ASP.NETVERSIONS: 2.0
Dynamic Themes
Assigning Page Themes at Runtime
By Michele Leroux Bustamante
Greetings ASP.NET architects and developers! I d like towelcome you to the first installment of my new ASP.NET Under the Hood column,and invite you to send your questions and requests to [email protected] me unravel some mysteries for you!
Q. I reallylike the new themes feature in ASP.NET 2.0. I ve played with it a bit, and itseems easy enough to create a theme, and then apply it to individual pagesusing the @Page directive. I also like the fact that you can specify a defaulttheme for all pages in the web.config. What I want to know is how I can set thetheme for a particular page dynamically based on a user profile or some otherconfiguration option.
A. This is agreat question, and the short answer is that you can set the Page.Themeproperty at runtime. Of course, there are a number of options for when andwhere you might choose to do this, some better than others, including:
Overriding the Page OnPreInit method.
Providing a common base type that overridesOnPreInit.
Hooking the PreInit event from the Global.asax.
Hooking the PreInit event from a reusable HTTPmodule.
My favorite option is the last one, but in the sections tofollow I ll first review each option and comment on their practical use.
Overriding Page.OnPreInit
The runtime applies stylesheets and skins to the pagecontrol tree somewhere between two page events: PreInit and Init. Rather thanassociating a stylesheet with your master pages or individual Web forms, youmay specify a default theme for all pages in the web.config:
Individual pages may also be configured to use aparticular theme by setting the Theme attribute of the @Page directive:
<# @Page Theme="Indigo" .../>
The site default is a great idea, but individual pagesettings have limited value since you d have to manually edit each Web form tomake changes.
To override the theme at runtime, your mission is tosupply the new theme after the runtime has applied these default configurations yet before it is applied to the control tree. On a per-page basis, you canoverride the virtual OnPreInit method to do just that. The following codeillustrates how to set the Theme property based on the user s Profile:
protected override void OnPreInit(EventArgs e)
{
if(!String.IsNullOrEmpty(Profile.Theme))
this.Theme =Profile.Theme;
base.OnPreInit(e);
}
This works fine for a one-off approach, but realisticallyyou want to apply the theme dynamically to all pages in the site, right?Clearly you are NOT going to override this method in every page you add to thesite. So, now that you understand the right timing during the round trip to setthe theme, let s explore alternatives that are more reusable.
Overriding Page.OnPreInit in a Base Page
You could provide a reusable base Page type that overridesOnPreInit to set the theme for each page request. In ASP.NET 2.0 you wouldplace this base type in the App_Code directory and provide an implementationsimilar to the following example:
public class SharedBasePage : System.Web.UI.Page
{
protected override voidOnPreInit(EventArgs e)
{
string theme =
HttpContext.Current.Profile["Theme"] as string;
if(!String.IsNullOrEmpty(theme))
this.Theme = theme;
base.OnPreInit(e);
}
}
Of course, the base page must inherit System.Web.UI.Pagein order to be a valid base type. You may have noticed that the code to accessthe user s Profile is no longer strongly typed. That s because the stronglytyped Profile reference is not available from the App_Code directory, solate-bound access through the HttpContext will have to suffice.
The base-page approach certainly reduces the overhead ofwriting the same code over and over in each Web form; however, it still has itsinconveniences. For pages that use a separate code file, you must still modifyeach Web form to inherit the base type:
public partial class UsesBasePage : SharedBasePage
If you are using .aspx pages without a separate code fileyou can supply a default base page in the web.config, which will be used forall .aspx without associated code files:
I always recommend separating your code from presentation(in other words, use a separate code file for all .aspx) so it won t surpriseyou that the last option isn t high on my list of practical choices.
Hooking PreInit from Global.asax
Now we are moving into more interesting territory. You canactually hook the PreInit event for any page in the application s Global.asaxfile. You see, the Page object is actually an HTTP handler that is invoked bythe ASP.NET runtime to handle requests. Each Web form derives from Page, andtherefore, through inheritance, implements the IHttpHandler interface.Immediately before a Page handler is executed, the application-levelPreRequestHandlerExecute event is fired by the runtime. Because the Global.asaxis in fact your hook into the application instance (Global.asax inheritsHttpApplication), you can provide an event handler for PreRequestHandlerExecuteand do some work before the Page handler is executed.
At this point in the request lifecycle, the Page handler(your Web form class) has been instantiated and is accessible through theHttpContext. In fact, the HttpContext has a Handler property that can besuccessfully cast to its base Page type if the request applies to a Web form.The following code illustrates the code to access the page handler for therequest and provide a handler for its Page.PreInit event:
void Application_PreRequestHandlerExecute(
object sender,EventArgs e)
{
HttpApplication app =sender as HttpApplication;
if (app == null) return;
Page p =app.Context.Handler as Page;
if (p == null) return;
p.PreInit += p_PreInit;
}
If the request applies to a Page handler, this code willsubscribe to PreInit and receive the PreInit event before the actual page does!In the PreInit handler you can set the Page.Theme property from the userProfile, as shown here:
private void p_PreInit(object sender, EventArgs e)
{
Page p = sender as Page;
if(!String.IsNullOrEmpty(Profile.Theme))
p.Theme =Profile.Theme;
}
The benefit of the Global.asax approach is that this codecan handle requests for all pages, and you could even supply logic to filterwhich pages are affected, if you like. This approach does not require anymodification to individual pages; but, we can actually do one better than thisby supplying a reusable HTTP module to fully decouple this functionality fromthe application, making it possible to reuse across multiple applications.
Hooking PreInit from a Reusable HTTP Module
This is by far a superior choice, because it is reusableand configurable. An HTTP module is a component that, when configured,participates throughout the lifetime of each request by interceptingapplication-level events. In this case, the module will only handle thePreRequestHandlerExecute event and hook the Page handler s PreInit (ifappropriate). For each request that is not cached, the module will have anopportunity to hook the event, and subsequently handle the event.
Modules implement IHttpModule, an interface that exposestwo methods: Init and Dispose. During Init you can hook any application eventin order to interact with each request at a specific point in the lifecycle.Your event handler is then invoked for each request as appropriate. The modulein this example (an HTTP module to handle dynamic theme assignment for Pagerequests) is shown in Listing One.
To configure an HTTP module you add it to the section of the web.config, like this:
type="RuntimeUtilities.DynamicThemeModule" /> The ASP.NET runtime will load the module into theapplication domain (one instance per application domain). If multiple modulesare configured, they receive events in the order they are configured in thecollective hierarchy of machine.config and web.config for the application. TheGlobal.asax receives the same application events after all modules. The advantage of this approach is that the configurationsetting controls enabling/disabling this theme assignment module, and you canshare the same module between applications by distributing it as a separateclass library possibly even installing it to the Global Assembly Cache (GAC). If you have additional questions on this or other ASP.NETtopics, drop me a line at [email protected] for reading! C# and VB.NET codeexamples accompanying this article are available for download. Michele LerouxBustamante is Chief Architect at IDesign Inc., Microsoft Regional Directorfor San Diego, Microsoft MVP for XML Web services, and a BEA TechnicalDirector. At IDesign Michele provides training, mentoring, and high-endarchitecture consulting services, specializing in scalable and secure .NETarchitecture design, globalization, Web services, and interoperability withJava platforms. She is a board member for the International Association ofSoftware Architects (IASA), a frequent conference presenter, conference chairof SD s Web Services track, and a frequently published author. She is currentlywriting a book for O Reilly on the Windows Communication Foundation. Reach herat http://www.idesign.net or http://www.dasblonde.net. Begin Listing Oneusing System; using System.Web; using System.Web.UI; namespace RuntimeUtilities{ public classDynamicThemeModule:IHttpModule { public void Dispose() { } public voidInit(HttpApplication context) { context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute); } voidcontext_PreRequestHandlerExecute (object sender,EventArgs e) { HttpApplication app =sender as HttpApplication; if (app == null)return; Page p =app.Context.Handler as Page; if (p == null)return; p.PreInit +=p_PreInit; } private voidp_PreInit(object sender, EventArgs e) { Page p = sender asPage; string theme =HttpContext.Current.Profile["Theme"] as string; if(!String.IsNullOrEmpty(theme)) p.Theme = theme; } } }End Listing One
About the Author
You May Also Like