- An ASP.NET MVC 2 & JQueryUI example : Part 1 – Introduction
- An ASP.NET MVC 2 & JQueryUI example: Part 2 – Jumping in
Since this series is primarily about applying the JQueryUI themes to an ASP.NET application we will start with looking at how the theme is stored for a user and how this translates into a CSS file applied to the website for an individual user.
Theme storage and retrieval:
The first thing I did was add a user accessible property to the Profile Provider configuration in the web.config as shown below.
Listing 1:
<profile>
<providers>
<clear/>
<add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/" />
</providers>
<properties>
<add name="Theme"
type="System.String" />
</properties>
</profile>
As you can see in Listing 1 I am using the standard AspNetSqlProfileProvider and I have added property called “Theme” of type string. This will allow us to store the theme chosen by each user on an individual basis for each user registered by the Membership Provider. I modified the default AccountService which is created by the ASP.NET MVC 2 Project Wizard in the “AccountModels.cs” file by adding the line “ProfileBase.Create(userName, true)” (Listing 2 line 10).
Listing 2:
public MembershipCreateStatus CreateUser(string userName, string password, string email)
{
if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
if (String.IsNullOrEmpty(password)) throw new ArgumentException("Value cannot be null or empty.", "password");
if (String.IsNullOrEmpty(email)) throw new ArgumentException("Value cannot be null or empty.", "email");
MembershipCreateStatus status;
_provider.CreateUser(userName, password, email, null, null, true, null, out status);
ProfileBase.Create(userName, true);
return status;
}
When a user registers on the website a user profile will be created automatically for the user, however it will not have any data in the profile yet. Setting profile properties during the registration process will NOT work as the request is not in fact authenticated until the user logs in. We will get around this by checking for an empty profile when retrieving the users theme choice and setting a default theme if one does not exist.
In order to set the correct CSS file for a user in the Site.Master file we will user a Helper method to retrieve the CSS file name.
Listing 3:
<head runat="server">
<link href="<%: Url.Content("~/Content/default.css")%>" rel="stylesheet" />
<link href="<%: Url.Content("~/Content/themes/" + Html.GetTheme() + "/jquery.ui.all.css")%>" rel="stylesheet" />
<script type="text/javascript" src="<%:Url.Content("~/Scripts/jquery-1.4.2.min.js") %>"></script>
<script type="text/javascript" src="<%:Url.Content("~/Scripts/jquery-ui.1.8.2.min.js") %>"></script>
<script type="text/javascript" src="<%:Url.Content("~/Scripts/jquery-getCSS.min.js") %>"></script>
<script type="text/javascript" src="<%:Url.Content("~/Scripts/jquery.loadImages.1.0.1.min.js") %>"></script>
<script type="text/javascript" src="<%:Url.Content("~/Scripts/mvcjqueryuiexample.js") %>"></script>
<title>
<asp:ContentPlaceHolder ID="TitleContent" runat="server" />
</title>
<script type="text/javascript">
$(document).ready(initialise);
</script>
<asp:ContentPlaceHolder ID="HeaderContent" runat="server" />
</head>
As you can see on the third line we use “Html.GetTheme()” (Listing 3 Line 3) to retrieve the theme name, the “GetTheme()” function is shown below.
Listing 4:
public static MvcHtmlString GetTheme(this HtmlHelper helper)
{
string baseTheme = "ui-lightness";
string theme = baseTheme;
if (helper.ViewContext.HttpContext.User.Identity.IsAuthenticated)
{
theme = helper.ViewContext.HttpContext.Profile.GetPropertyValue("Theme").ToString();
if (string.IsNullOrEmpty(theme))
{
helper.ViewContext.HttpContext.Profile.SetPropertyValue("Theme", baseTheme);
theme = baseTheme;
}
}
return MvcHtmlString.Create(theme);
}
The function first checks if the current request is authenticated otherwise it just returns the default theme name. If the request is authenticated it uses the profile provider to retrieve the “Theme” profile property, if it does not exist we set the property to the default theme else we return the users chosen theme.
The theme name that is stored in the profile corresponds directly to the name of a theme directory in the /Content/themes directory, these themes are the standard themes included in the JQueryUI themes package and can be downloaded from the JQueryUI blog.
Listing 3 has another very important bit of code, lines 13-15 show a piece of JQuery which hooks the document load event and calls the initialise function, this function resides in the “mvcjqueryuiexample.js” file that I will discuss in detail later, for now just take note that we are initialising some clientside scripts on each load of a page.
A quick aside – the User Menu Area:
Being able to store a users chosen theme against their profile is all well and good but there needs to be a way to change the theme, the gateway to the profile editing pages is the user menu area which I will discuss briefly before talking about how the theme itself is selected.
In the Site.Master file I use the Html.RenderPartial helper method to render a usercontrol, in this case “LogOnUserControl.ascx”. Being in the Site.Master master page this is rendered on every page and so needs to be able to differentiate between authorised and anonymous users.
Listing 5:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>
<% if (Request.IsAuthenticated)
{
%>
<ul>
<li>
<img alt="avatar" src="<%:Html.GetGravatarUrl(24)%>" width="24px" height="24px" />
</li>
<li>
<a class="ui-button ui-state-default ui-corner-all ui-button-text-only" href="<%: Url.Content("~/account/editprofile/") %>">
<%: Page.User.Identity.Name %>
</a>
</li>
<li>
<a class="ui-button ui-state-default ui-corner-all ui-button-text-only" href="<%: Url.Content("~/account/logoff/") %>">
Log Out
</a>
</li>
</ul>
<% }
else
{
%>
<ul>
<li></li>
<li>
<a class="ui-state-default ui-button-text-only ui-corner-all ui-button" href='<%: Url.Content("~/account/logon/") %>'>
Log On
</a>
</li>
</ul>
<%} %>
The usercontrol just uses some simple conditional statements to render different content based on whether the request is authenticated or not. If the request is authenticated it uses another HtmlHelper to render a gravatar, a button with the user’s username on it to edit the user’s profile and a logout button versus just a logout button when the request is not authenticated. The gravatar url is created by generating an MD5 hash of the user’s email address and appending this and the requested image size to the gravatar service url. The code is shown below.
Listing 6:
public static string GetGravatarUrl(this HtmlHelper helper, int imageSize)
{
string result = string.Empty;
MembershipUser User = Membership.GetUser();
if (User != null)
{
System.Security.Cryptography.MD5CryptoServiceProvider x = new System.Security.Cryptography.MD5CryptoServiceProvider();
byte[] bs = System.Text.Encoding.UTF8.GetBytes(User.Email);
bs = x.ComputeHash(bs);
System.Text.StringBuilder s = new System.Text.StringBuilder();
foreach (byte b in bs)
{
s.Append(b.ToString("x2").ToLower());
}
string gravatarHash = s.ToString();
result = string.Format("http://www.gravatar.com/avatar/{1}.png?s={0}", imageSize, gravatarHash);
}
return result;
}
Theme Selection:
In the previous section I described how the user menu area displays a button with the currently logged in user’s name on it as a means to edit the user’s profile. I’ll now show how the user is able to preview and select a theme from the available themes. For simplicity’s sake on application start up I retrieve a list of sub-directories in the /Content/themes directory and store these in the application state as shown below.
Listing 7:
protected void Application_Start()
{
// build the list of themes
string physicalPath = Server.MapPath("~/content/themes");
string[] themeDirs = Directory.GetDirectories(physicalPath);
IList<string> themes = new List<string>();
foreach (string themeDir in themeDirs)
{
string theme = themeDir.Split(new char[] { '\\' }).Last();
if (theme != "base")
themes.Add(theme);
}
Application.Add("themes", themes);
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
The actual theme selection happens on the edit profile view, this view contains two tabs for editing basic user details and profile settings as shown below.
This view is managed by the account controller using three methods, “EditProfile” to return the view and the data for the two tabs, “EditProfileBasic” to save the data for the basic tab, and “EditProfileSettings” to save the data from the profile tab. Notice the use of the “Authorize” attribute to ensure only authenticated users can edit their profiles as well as the “HttpPost” attribute on the two methods used for saving the users profile details which only allows the methods to be called from a page post. I also redirect back to the “EditProfile” action at the end of the two post methods as we need to refresh the data model being returned as the data posted to each method is only for the current tab and I would end up with one of the tabs having no data if just returned the view with the current model.
Listing 8:
[Authorize]
public ActionResult EditProfile()
{
MembershipUser user = Membership.GetUser();
return View(new ProfileModel(User.Identity.Name, user.Email, user.CreationDate.ToShortDateString(), this.HttpContext.Profile.GetPropertyValue("Theme").ToString()));
}
[Authorize]
[HttpPost]
public ActionResult EditProfileBasic(ProfileModel model)
{
if(ModelState.IsValid)
{
if (string.IsNullOrWhiteSpace(model.EmailAddress))
{
ModelState.AddModelError("", "Email address may not be empty.");
}
else
if (MembershipService.ChangeEmail(User.Identity.Name, model.EmailAddress) == false)
{
ModelState.AddModelError("", "New email address is not valid.");
}
}
return RedirectToAction("EditProfile");
}
[Authorize]
[HttpPost]
public ActionResult EditProfileDetails(ProfileModel model)
{
if (ModelState.IsValid)
{
if (string.IsNullOrWhiteSpace(model.Theme))
{
ModelState.AddModelError("", "A theme must be selected.");
}
else
{
this.HttpContext.Profile.SetPropertyValue("Theme", model.Theme);
}
}
return RedirectToAction("EditProfile");
}
In the EditProfileDetails method in Listing 8 above I save the the chosen theme back to the current users profile which in turn will change which CSS file will be returned next time a page is requested by the user.
The tabs are created using the JQueryUI library and the view includes the script below in its header to initialise the tabs:
Listing 9:
<script type="text/javascript">
$(document).ready(initialiseSettings);
</script>
The javascript function initialiseSettings is contained in the mvcjqueryuiexample.js file. This function sets up the tabs and hooks the change event of the dropdown box so that the theme can be previewed.
Listing 10:
function initialiseSettings() {
/// <summary>
/// Called from the edit profile page. Sets up the tabs to be JQueryUI tabs, initialises the themes select box and preloads the busy image.
/// </summary>
// Setup the tabs
$("#editprofile").tabs();
// initialise the themes select options
initialiseThemes();
// preload the busy image
$.loadImages('/Content/ajax-loader.gif', preloadDone());
}
JQuery is used to select the div with id “editprofile” and then the JQueryUI tabs() method is called to create the tabs. Then the initialiseThemes() method is called which hooks the dropdown change event and finally I preload the busy spinner image that will be shown while the new theme is applied so that there is no delay in showing the animation, the preloading is done via a JQuery plugin (loadImages) which is included in the scripts directory.
As the theme preview is rather complicated I will talk about this in part three of this series.

2 Comments
Nice article.
What about using just one single “Save Changes” button for tabs (in case you have more tabs)? This would reside outside tabs div and collect the data from all tabs which have editable elements.
Cheers,
Florin
Hello Eugene:
Thank you for posting your most excellent example.
I finally got my head around MVC, I’ve been using straight .aspx pages for too damn long!
Besides that, it is funny that you live in South Africa, my daughter moved there about a month ago. Currently living in Welcom but she was going to go to Johannesburg.
Thanks again, you have helped me learn something new.
Cheers from Cancun, Mexico