Recipe: Dynamic Site Layout and Style Personalization with ASP.NET

Problem:

You want to enable end-users visiting your web-site to dynamically customize the look and feel of it.  Specifically, you want to enable them to choose different content layouts for the site, as well as different style experiences (colors, fonts, etc). 

When building this customization system, you want to make sure you avoid duplicating logic throughout the multiple pages.  Instead, you want to be able to author the dynamic personalization logic once and have all pages inherit it.

Solution:

ASP.NET 2.0 makes dynamically customizing the UI experience of a web-site easy.  Specifically, it provides a flexible mechanism for defining page layouts using a feature called “Master Pages”.  It also provides a flexible mechanism for defining a page’s style look and feel via a feature called “Themes”. 

What is nice about both “Master Pages” and “Themes” is that they can be configured either statically (by specifying the Master Page or Theme to use within an ASP.NET Page’s <% Page %> directive), or dynamically at runtime by setting the Page’s MasterPageFile and Theme properties via code.  This later approach can be easily used to enable dynamic user personalization of UI on a web-site.  The below walkthrough demonstrates how to implement this.  You can also download a complete sample that shows how to-do this here.

Step 1: Create a New Web-Site

Begin by starting a new web-site in either Visual Studio or Visual Web Developer (which is free).  Create two master page files called “Site1.Master” and “Site2.Master” within it. 

We will use these two master-pages to provide two alternative layout views for the site that end-users will be able to pick between.  For the purposes of this sample we’ll keep each master file simple (although obviously you could add much more layout and content to each).  Here is a trivial example of what you could have in “Site1.Master” to start with for the purposes of this sample:

<%@ Master Language="VB" CodeFile="Site1.master.vb" Inherits="Site1" %>

 

<html>

<head runat="server">

    <title>Site1 Template</title>

</head>

<body>

    <form id="form1" runat="server">

    <div>

        <h1>Site1 Master Template</h1>

   

        <asp:contentplaceholder id="MainContent" runat="server">

        </asp:contentplaceholder>

    </div>

    </form>

</body>

</html>

After creating the two master-pages, add two new ASP.NET pages to the project – and choose to have them built using a Master Page.  Call these two new pages “Default.aspx” and “Personalize.aspx”, and have them be based on the Site1.Master template above. 

Step 2: Add Two Themes to the Site

Right-click on the Solution Explorer within VS and choose the “Add ASP.NET Folder->Theme” context menu option.  This will create a folder called “App_Themes” immediately underneath your application root directory.  You should then create two sub-folders underneath the “App_Themes” folder called “Blue” and “Red”.  This will define two separate themes for the site.  Within each theme folder you can add CSS stylesheets, images, and .skin files to customize the site’s appearance however you want.

For the purposes of this sample, we’ll add a “blue.css” stylesheet under the “Blue” folder and a “red.css” stylesheet under the “Red” folder.  For now we’ll keep their implementations trivial and just set the background color for pages in them:

body

{

    background-color:blue;

}

Once this is done, our site project layout should look like this:

Now we are ready to enable end-users visiting our site to dynamically personalize which Master Page file is used (allowing them to control the layout of the site), as well as which Theme/Stylesheet is used (allowing them to control the style of the site).

Step 3: Enable ASP.NET Personalization

We could save our visitors’ layout and style preferences in a variety of different places (within an http cookie, within a custom database, in a directory on the file-system, etc).  For the purposes of this sample, I’m going to use the new ASP.NET 2.0 Profile Personalization feature.  This allows me to easily save/retrieve information about users accessing the site with minimal code.

You can use the ASP.NET Profile Personalization feature in combination with any authentication mechanism (Windows Authentication, Forms Authentication, Passport, or any other authentication approach you want).  For this sample I’m just going to use Windows Authentication to login and identify the user.  If you aren’t familiar with how Windows authentication works, please read my previous Enabling Windows Authentication Recipe.

Once I’ve configured the site to use Windows Authentication, I can enable the ASP.NET Profile system by adding a <profile> section within my web.config file that lists the properties I want to store about users.  For the purposes of this sample I’m going to store two string properties:

<profile>

  <properties>

    <add name="ThemePreference" type="string" defaultValue="Blue"/>

    <add name="MasterFilePreference" type="string" defaultValue="~/Site1.master"/>

  </properties>

</profile>

If you have SQL Express installed on your machine, then you are done.  ASP.NET will automatically provision a new SQL Express database within your app_data folder at runtime that has the appropriate Profile tables configured to save your profile data above.  You don’t need to take any additional steps to configure this.

If you don’t have SQL Express installed, and instead want to use a SQL Server to store the Profile data, you’ll need to create a database within SQL to store the ASP.NET Application Service tables, and update your web.config file to point at the database.  The good news is that this is easy to-do.  If you haven’t done this before, please read my previous Configuring ASP.NET 2.0 Application Services to use SQL Recipe that demonstrates how to do this.

Step 4: Building the Personalize.aspx Page

Now that we have enabled ASP.NET Profile Personalization, we can go to work building a Personalize.aspx page that we can use to enable end-users on the site to dynamically pick which Master Page and Theme they want to use.  To do this we’ll add two <asp:dropdownlist> controls to the page to enable users to select the appropriate choices:

<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" Runat="Server">

 

    <h2>

        Pick your master file:

        <asp:DropDownList ID="ddlMasterFilePreference" runat="server">

            <asp:ListItem Text="Site Choice One" Value="~/Site1.Master" />

            <asp:ListItem Text="Site Choice Two" Value="~/Site2.Master" />

        </asp:DropDownList>

    </h2>

 

    <h2>

        Pick your theme preference:

        <asp:DropDownList ID="ddlThemePreference" runat="server">

            <asp:ListItem Text="Blue" />

            <asp:ListItem Text="Red" />

        </asp:DropDownList>

    </h2>

 

    <div>

        <asp:Button ID="UpdateBtn" runat="server" Text="Update" />

    </div>

 

</asp:Content>

Within the code-behind of the Personalize.aspx page we’ll then write this code to save and restore the selection to the ASP.NET Profile system:

Partial Class Personalize

    Inherits System.Web.UI.Page

 

    Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

 

        If (Page.IsPostBack = False) Then

 

            ddlMasterFilePreference.SelectedValue = Profile.MasterFilePreference

            ddlThemePreference.SelectedValue = Profile.ThemePreference

 

        End If

 

    End Sub

 

    Sub UpdateBtn_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles UpdateBtn.Click

 

        Profile.MasterFilePreference = ddlMasterFilePreference.SelectedValue

        Profile.ThemePreference = ddlThemePreference.SelectedValue

 

        Response.Redirect("default.aspx")

 

    End Sub

 

End Class

Notice that ASP.NET automatically adds a strongly-typed “Profile” object to our pages that we can use to easily get type-safe access to the Profile collection we defined within our web.config file previously.  It automatically handles saving/retrieving the user’s profile data from the configured profile database.

When the Personalize.aspx page is visited, an end-user will now be able to pick which layout and style choice they want:

When the end-user clicks “Update” their settings will be persisted in the ASP.NET Profile database automatically.  When they revisit the Personalize.aspx page later, their preferences will be retrieved and populated within the dropdownlists.

Step 5: Building a Base Page Class to Personalize Page Content

We want to avoid having to repeatedly add the same logic to set the Master Page and Theme on every page of the site.  We’ll instead create a base page class that encapsulates this functionality, and have each of the pages on our site inherit from it. 

To-do this, we’ll create a new class called “MyBasePage” that will live within our app_code directory (if we are using the VS 2005 Web Site Project option):

Imports System.Web.UI.Page

 

Public Class MyBasePage

    Inherits System.Web.UI.Page

 

    Protected Overrides Sub OnPreInit(ByVal e As System.EventArgs)

 

        MyBase.OnPreInit(e)

 

        Dim MyProfile As System.Web.Profile.ProfileBase

        MyProfile = HttpContext.Current.Profile

 

        Me.MasterPageFile = MyProfile.GetPropertyValue("MasterFilePreference")

        Me.Theme = MyProfile.GetPropertyValue("ThemePreference")

 

    End Sub

 

End Class

The above class inherits the base ASP.NET Page Class (System.Web.UI.Page), and overrides the OnPreInit method (which is called when the “PreInit” event on a page executes).  Within this method we then set the page’s “MasterPageFile” and “Theme” properties with the values the user persisted within the ASP.NET Profile Store. 

Note that when accessing the Profile data in the code above, I’m accessing it via the “GetPropertyValue()” helper method.  This code will work both within the web-site project, as well as any companion class-library projects I might want to use to store this base class in instead (enabling me to easily re-use it across multiple projects).

Step 6: Using the Base Page Class within Pages

Using the base page class we built above with pages is simple.  Just open the code-behind file of any of the pages within the site, and update its base class declaration from:

Partial Class _Default

    Inherits System.Web.UI.Page

 

End Class

To instead be:

Partial Class _Default

    Inherits MyBasePage

 

End Class

Now when the pages within our site run, the OnPreInit method within our base class will execute, and we’ll dynamically update the Page’s Master Page and Theme to reflect what the user has personalized.  For example:

Or:

This is all of the logic we need to enable dynamic layout and style personalization for the site.  We can now pass off the master page templates and CSS style-sheets to a designer to customize to make them as rich and attractive as we want.

Summary

The above walkthrough illustrates the basics of how you can enable rich site layout and style personalization.  It also demonstrates how you can encapsulate common page functionality within a base class that you can easily re-use across multiple pages in a site – a very useful approach that can be used far beyond just personalization.

Click here to download a complete version of the sample that you can run and examine locally.

Below are also some links/resources I recommend checking out to learn more about ASP.NET Master Pages, Themes, and Personalization:

ASP.NET 2.0 Website Programming : Problem – Design – Solution: This is an outstanding book by Marco Bellinaso, and uses a great “real world example” approach to walkthrough how to best take advantage of ASP.NET 2.0 features (including Master Pages, Themes, Profiles and more).

ASP.NET “How Do I” Videos: This is a great series of short 10-15 minute videos that you can watch online to learn ASP.NET concepts.  Included are several videos that show off Master Pages, Themes and Profiles.

ASP.NET Master Page Articles: Two great articles by K. Scott Allen that talk about both the basics and the advanced scenarios involving ASP.NET 2.0 Master Pages.

Free ASP.NET Design Template Downloads: Professionally designed site templates for ASP.NET that are XHTML compliant, and provide re-usable Master Pages and Themes that you can use in your own projects.

Profiles in ASP.NET 2.0: A great article by K. Scott Allen about the new ASP.NET 2.0 Profile System.

ProfileView Control: Joshua Flanagan has implemented a cool control that you can use in ASP.NET to provide an editing view of your profile personalization data.  You can download the control + its source code from this link.

ASP.NET 2.0 SQL Table Profile Provider: An alternative Profile Provider implementation for ASP.NET 2.0 that allows you to save Profile properties in schematized Tables or SPROCs (rather than the default storage mechanism which is an XML blob).

VS 2005 Web Application Project Profile Support: Available as a free download, this utility works with VS 2005 Web Application Projects and enables strongly-typed Profile objects to be used within them.

Hope this helps,

Scott

32 Comments

  • Thanks for the post, Scott. I&#39;m interested from a software company perspective, how this would work as new versions of our ASP.Net app is sent out to our customers for installation. Couple of quick questions...
    1. What if we don&#39;t know or can&#39;t require SQL Server 2000/2005 is installed on the web server? Is there a file-based solution for the database side of the profile/membership stuff? Access?
    2. As we send out new installers, as we release upgrades, is it possible to prevent overwriting the existing profile/membership database?
    3. So this will require backing up apps on the web server so that the database is backed up?
    As always, thanks for the awesome posts. I read each and every one of them. hope you can, in &nbsp;future posts, focus on solutions for software vendors distributing installers for ASP.NET products every quarter year or so.
    John
    Thanks again!

  • Great write up Scott,

    These "Build from scratch" articles have huge value to the community/readers.

    Thanks!

  • Great for people who are happy to tinker with CSS code to position elements.
    I found an application that used JavaScript to enable a client to control where content was placed. This app merely changed the TOP and LEFT values in the CSS file.
    Normally, one would use tables for content positioning, but CSS is the best way for dynamic positioning or for being able to change the position of content remotely.
    Is there an Atlas control that reports the screen X,Y co-ordinates and then stores this in a session object, for example?

  • Hi John,

    To answer your questions above:

    1) One of the really nice architectural elements of ASP.NET 2.0 is what we call the "provider model". This enables you to swap out the implementation of core ASP.NET services like Membership, Profile, Roles, etc -- without having to change the code that calls them.

    This means that you could take my app above, and implement a MySql, File System or alternative provider to store Profile data, register it within the web.config file, and not have to change any code above.

    This website provides a lot more information about the provider model, as well as source code for the built-in ASP.NET providers that you can checkout yourself: http://msdn.microsoft.com/asp.net/downloads/providers/

    2) Yep -- you could ceretainly do this. You could take care to make sure certain tables within the database aren't modified or deleted when you update the app. In general I expect this would be similar to any database table model you might use (for example: an orders table) -- where you want to make sure you don't overwrite user data.

    3) The web-server in the above example is completely stateless, so no user information is stored there (meaning you theoretically don't need to back it up). You would probably want to have a regular backup plan for your database (this is something I'd recommend for any database app). Note that SQL (and Oracle and other) databases has good built-in support for this, since it is vistal for any mission critical application.

    I actually have a blog post that I'm hoping to publish at somepoint soon that walksthrough how to build VS Web Setup packages with custom action support. That should hopefully provide more guidance on creating custom installers that you can use.

    Hope this helps,

    Scott

  • Hi Dom,

    The approach above works with both CSS and tables for doing positioning and layout.

    For enabling users to dynamically re-position elements within an individual page, you can also check out two cool ASP.NET features:

    Web Parts -- this enables you to allow end-users to drag/drop and customize positioning of sub-elements within the page. This data is also saved on the backend in a personalization database for future use.

    The Atlas Control Toolkit also implements a draggable panel that you can use to give any server control positioning support in a browser. You can see an online demo of this here: http://atlas.asp.net/atlastoolkit/PanelExtenders/DragPanel.aspx

    Hope this helps,

    Scott

  • Hi Scott

    I was lookin for an article like this. Great work. This type of article really help in understanding the subject and the good thing is that you can download the code for any reference later on

  • Added the Theme changer in My website in less than 10 line of code. This is a great API to work with

  • I need a short subdomain web hosting ( easy and rememeberable reason ), with feature

    Short subdoamin ex. www.xx.com, so we can access with myname.xx.com or mybe www.myname.xx.com

    Thank

  • Hey Scott,
    Good post, is PreInit not accessible to controls at any point in the event life cycle?

    I would like to add a Print Button, derived from ImageButton, to pages, but instead of hard-coding the logic for checking whether a query string value has been passed on GET operations, I'd rather check for the existence of the control's ID in the postback body, and then change the theme.

    But, if I try to hook up an event handler to Page.PreInit within the constructor of the derived-control, then Page is null at that point. If I try to do it in the control's Init event, then Page.PreInit has already fired at this point.

    My main goal is not to introduce extra dependencies into my basePage class if not necessary, but it seems like I am out luck?

    Thanks,
    Josh

  • Hi Josh,

    Unfortunately the OnPreInit event is not available on the Control base class -- but is rather something specific to page I believe (it happens before the control Init event).

    Hope this helps,

    Scott

  • "We can now pass off the master page templates and CSS style-sheets to a designer to customize to make them as rich and attractive as we want."

    That sounds good on paper, but in practice, that's a nightmare when using master pages with any level or reusability among master/child pages and user control blocks. (take this with a grain of salt).

    The minute you want to start styling specific parts of your site using IDs, what the developer has for any server control Id and what the designer gets as the Id attributes from the ASP.NET output html are two completely different things; prosumabely due to ASP.NETs needs to dynamically id server controls.

    This makes it near impractical to start with a design based on pre IDed divs/contents sections/lists and have them stick after .NET gets done going its magic. And if you have to move things around, all of the Ids could once again change hosing up your CSS #id references. Things good seperation of content vs. look shouldn't ever do.

    Sorry, just a rant out of frustration. If it were just programmers doing a site, the master pages/themes are great. When you're trying to split data from presentation among two groups, Master pages and themes aren't always your friends.

  • Hi Chris,

    You can use class names instead of IDs to name CSS rules. I actually recommend these since they allow you to use them with multiple elements across a site, and also work fine regardless of how deep a control is nested.

    Hope this helps,

    Scott

  • I am getting following error - any idea please
    "This property cannot be set for anonymous users."

  • Hi Z,

    The reason you are seeing that error is probably because a user wasn't logged into the page when you set the profile property.

    Hope this helps,

    Scott

  • Hi Scott - great tutorial like the many ones i've read and implemented in my work.

    I am currently faced with dynamically setting the color of panels (among other web controls) in pages based on link clicked. How do I dynamically set the color of a web control?

    Thanks.

  • Hi Doods,

    The control has a "backcolor" property that allows you to dynamically change the color of a control.

    Hope this helps,

    Scott

  • Hi Bob,

    You should be able to retrieve the Profile value by writing code like this in your GetVaryByCustomString method:

    ProfileBase profile = ProfileBase.Create(username);

    profile.GetPropertyValue("property")

    Hope this helps,

    Scott

  • Scott,
    Thanks for the help. I got further without an exception error. I'll summary my changes:

    In Global.asax, I have
    Public Overrides Function GetVaryByCustomString( _
    ByVal context As System.Web.HttpContext, _
    ByVal custom As String) As String

    Select Case custom
    Case "theme"
    Dim profile As ProfileBase = ProfileBase.Create(context.User.Identity.Name)
    Return profile.GetPropertyValue("PreferredTheme")
    Case Else
    Return MyBase.GetVaryByCustomString(context, custom)
    End Select
    End Function

    In web.config, I have:


    <add name="PreferredTheme" type="string" defaultValue="Blue"




    And in all of my .aspx files, I have


    What is still brothering me is that when I visit several pages with a particular theme set, and then change the theme and revisit the pages with a previous theme, the page is displayed with the previous theme. Is my OutputCache directive incorrect?

    I also have several buttons defined with onclick="history.go(-1);". These buttons are now disabled. Is this another problem with the OutputCache directive?

    I just want to tell the browser that after a theme is change, delete the cache so that all pages displayed with reflect the new theme.

    Thanks for the help.
    Bob

  • Hi Bob,

    Can you check the cache settings in your browser to make sure it is checking for an update on each request?

    It could be that you have some client-side caching in effect that is causing this behavior.

    Thanks,

    Scott

  • I have done this in C# but as far as I can see the PreInit event fires in the Base class before the dropdownlist selectedindex change event. Therefore does not behave properly??

    Guidance would be appreciated!

    Thanks

  • I have accepted that you have to do a re-direct because the Pre_Init fires before that newly chose theme selected is available.

    Could this be done with a Master page? Or do I just change my aspx pages that have a master page reference to inherit off the base class?

  • Hi Jon,

    Yep - the PreInit event fires before the DropDownlist is populated.

    What you might want to consider doing is using the Request.Form collection to retrieve the dropdownlist's value directly. This is available within PreInit.

    Hope this helps,

    Scott

  • I dont see how accessing the dropdown list in the pre-init will help because the postback will not have occured and u cant set the theme then.

    Could this be done with a master page or do my pages that have a master page have to inherit off the base class?

  • Hi me again!

    Is there anyway that a users theme choice can be applied to a login page.

    As far as I can see it can't. Anonymouse access is not an option

  • Hi Jon,

    The postback won't have occured, but the data for the postback will still be in the Request object.

    So if you know the dropdownlist's name is "Theme1", you should be able to write Request.Form("Theme1") to retrieve the name.

    Hope this helps,

    Scott

  • Hi Jon,

    You can register user profile properties when you create a new user using an approach I describe in this blog post: http://weblogs.asp.net/scottgu/archive/2005/10/18/427754.aspx

    You can also optionally use the "anonymous profile" feature to enable profiles for anonymous (non logged in) users. This blog post has more on this: http://msdn.microsoft.com/msdnmag/issues/05/10/CuttingEdge/ and http://www.odetocode.com/Articles/440.aspx

    Hope this helps,

    Scott

  • Scott,

    Just wondering about allowing the actual theme to be modified by the end user? I want the user to be able to create/modify the actual theme settings. Each user can create their own theme and have it applied to their website (I'm developing a web app with seperate user accounts which represent websites themselves). So I probably want to have my themes persisted to a DB as well. Any pointers or resources you know of that would help me do this?

    Whenever I search for dynamic themes I wlways find material on how to dynamically change the theme, which is stored in the theme folder. How would do this without using the theme forder but a DB instead?

    thanks for any help.
    Todd

  • Hi Todd,

    In theory you could do this, and use a virtual path provider (a new feature in ASP.NET 2.0) to dynamically load the .skin files from a database.

    The one thing you might want to be careful about is whether .skin files give your users too much power - in which case they could potentially get themselves in trouble if you allow them to completely customize it. You might instead want to just allow your users to customize CSS files - since this would restrict them a little to just customize the look and feel.

    Thanks,

    Scott

  • Hi Scott,

    Thanks for the feedback. Actually there will be a UI layer between the user and the actual theme (or css) so the user will be not be aware of the implimentation. I guess I wasn't clear on that, but the user will be setting properties on some web form and the app code will programmatically handle the modification and persistance of the theme or css.

    The idea of custom sql provider for the themes had crossed my mind. I was hoping one existed or that someone else had come across this problem and handled it a specific way.

    Since I'm mostly a enterprise server-side guy it's quite possible I'm already going down the garden path on how to manage the UI customization. I've looked at using web parts and it was overkill for what I wanted. Also the personalization provider persists all the presonalization info as a blob so I can't query the data for other application purposes. (For example, how many people are using the xyz property of the navi window?).

    well, thanks for the feedback scott, I appreciate it.

    Todd

  • Hi James,

    The redirect is necessary in cases where you are changing the theme -- since the theme can only be applied at the beginning of the request.

    What you could do is to change the Response.Redirect statement to instead be:

    Response.Redirect(Request.RawUrl)

    This will redirect you to exact same page that the user is already on - in which case they shouldn't notice that a redirect happened.

    Hope this helps,

    Scott

  • Scott-

    Thanks for this tutorial.

    To you or anyone--

    When changing Themes, what is the best method for handling variance in styles for Internet Explorer?

    Currently reference a conditional comment within Master Pages to load IE style sheets. However, when using dynamic style sheet or Theme selection, the loaded IE styles from the conditional comment interfere with the selected
    Theme. What are some viable alternatives for handling styles within Internet Explorer?

    Thanks.

  • Hi,

    i have almost the same code u have above and i'd like to give the user more options, by allowing him to pick colors from a color palette(containing a huge number of colors). So what i need is to retrieve the value of the colors used in the .css from DB or XML file.
    Can u help?

Comments have been disabled for this content.