Skip navigation

Dynamic Themes

Assigning Page Themes at Runtime

ASP.NET Under the Hood

LANGUAGES: VB.NET | C#

ASP.NET VERSIONS: 2.0

 

Dynamic Themes

Assigning Page Themes at Runtime

 

By Michele Leroux Bustamante

 

Greetings ASP.NET architects and developers! I d like to welcome 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]. Let me unravel some mysteries for you!

 

Q. I really like the new themes feature in ASP.NET 2.0. I ve played with it a bit, and it seems easy enough to create a theme, and then apply it to individual pages using the @Page directive. I also like the fact that you can specify a default theme for all pages in the web.config. What I want to know is how I can set the theme for a particular page dynamically based on a user profile or some other configuration option.

 

A. This is a great question, and the short answer is that you can set the Page.Theme property at runtime. Of course, there are a number of options for when and where you might choose to do this, some better than others, including:

  • Overriding the Page OnPreInit method.
  • Providing a common base type that overrides OnPreInit.
  • Hooking the PreInit event from the Global.asax.
  • Hooking the PreInit event from a reusable HTTP module.

 

My favorite option is the last one, but in the sections to follow I ll first review each option and comment on their practical use.

 

Overriding Page.OnPreInit

The runtime applies stylesheets and skins to the page control tree somewhere between two page events: PreInit and Init. Rather than associating a stylesheet with your master pages or individual Web forms, you may specify a default theme for all pages in the web.config:

 

<pages theme="Indigo"/>

 

Individual pages may also be configured to use a particular theme by setting the Theme attribute of the @Page directive:

 

<# @Page Theme="Indigo" .../>

 

The site default is a great idea, but individual page settings have limited value since you d have to manually edit each Web form to make changes.

 

To override the theme at runtime, your mission is to supply 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 can override the virtual OnPreInit method to do just that. The following code illustrates 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 realistically you 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 the site. So, now that you understand the right timing during the round trip to set the 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 overrides OnPreInit to set the theme for each page request. In ASP.NET 2.0 you would place this base type in the App_Code directory and provide an implementation similar to the following example:

 

public class SharedBasePage : System.Web.UI.Page

{

 protected override void OnPreInit(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.Page in order to be a valid base type. You may have noticed that the code to access the user s Profile is no longer strongly typed. That s because the strongly typed Profile reference is not available from the App_Code directory, so late-bound access through the HttpContext will have to suffice.

 

The base-page approach certainly reduces the overhead of writing the same code over and over in each Web form; however, it still has its inconveniences. For pages that use a separate code file, you must still modify each Web form to inherit the base type:

 

public partial class UsesBasePage : SharedBasePage

 

If you are using .aspx pages without a separate code file you can supply a default base page in the web.config, which will be used for all .aspx without associated code files:

 

<pages pageBaseType="SharedBasePage" />

 

I always recommend separating your code from presentation (in other words, use a separate code file for all .aspx) so it won t surprise you 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 can actually hook the PreInit event for any page in the application s Global.asax file. You see, the Page object is actually an HTTP handler that is invoked by the ASP.NET runtime to handle requests. Each Web form derives from Page, and therefore, through inheritance, implements the IHttpHandler interface. Immediately before a Page handler is executed, the application-level PreRequestHandlerExecute event is fired by the runtime. Because the Global.asax is in fact your hook into the application instance (Global.asax inherits HttpApplication), you can provide an event handler for PreRequestHandlerExecute and 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 the HttpContext. In fact, the HttpContext has a Handler property that can be successfully 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 the request 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 will subscribe 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 user Profile, 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 code can handle requests for all pages, and you could even supply logic to filter which pages are affected, if you like. This approach does not require any modification to individual pages; but, we can actually do one better than this by supplying a reusable HTTP module to fully decouple this functionality from the 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 reusable and configurable. An HTTP module is a component that, when configured, participates throughout the lifetime of each request by intercepting application-level events. In this case, the module will only handle the PreRequestHandlerExecute event and hook the Page handler s PreInit (if appropriate). For each request that is not cached, the module will have an opportunity to hook the event, and subsequently handle the event.

 

Modules implement IHttpModule, an interface that exposes two methods: Init and Dispose. During Init you can hook any application event in 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 module in this example (an HTTP module to handle dynamic theme assignment for Page requests) is shown in Listing One.

 

To configure an HTTP module you add it to the <httpModules> section of the web.config, like this:

 

<httpModules>

 <add name="DynamicThemeModule"

   type="RuntimeUtilities.DynamicThemeModule" />

</httpModules>

 

The ASP.NET runtime will load the module into the application domain (one instance per application domain). If multiple modules are configured, they receive events in the order they are configured in the collective hierarchy of machine.config and web.config for the application. The Global.asax receives the same application events after all modules.

 

The advantage of this approach is that the configuration setting controls enabling/disabling this theme assignment module, and you can share the same module between applications by distributing it as a separate class library possibly even installing it to the Global Assembly Cache (GAC).

 

If you have additional questions on this or other ASP.NET topics, drop me a line at [email protected]. Thanks for reading!

 

C# and VB.NET code examples accompanying this article are available for download.

 

Michele Leroux Bustamante is Chief Architect at IDesign Inc., Microsoft Regional Director for San Diego, Microsoft MVP for XML Web services, and a BEA Technical Director. At IDesign Michele provides training, mentoring, and high-end architecture consulting services, specializing in scalable and secure .NET architecture design, globalization, Web services, and interoperability with Java platforms. She is a board member for the International Association of Software Architects (IASA), a frequent conference presenter, conference chair of SD s Web Services track, and a frequently published author. She is currently writing a book for O Reilly on the Windows Communication Foundation. Reach her at http://www.idesign.net or http://www.dasblonde.net.

 

Begin Listing One

using System;

using System.Web;

using System.Web.UI;

namespace RuntimeUtilities

{

 public class DynamicThemeModule:IHttpModule

 {

   public void Dispose()

   {

   }

   public void Init(HttpApplication context)

   {

     context.PreRequestHandlerExecute += new

       EventHandler(context_PreRequestHandlerExecute);

   }

   void context_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 void p_PreInit(object sender, EventArgs e)

   {

     Page p = sender as Page;

    

     string theme = HttpContext.Current.Profile["Theme"] as string;

     if (!String.IsNullOrEmpty(theme))

       p.Theme = theme;

    

   }

 }

}

End Listing One

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish