Download the Random Image Rotator Test Project
Introduction
Recently, I wanted to modify the main logo that appears at the top of each page on my website. I wanted to create some variations during the holiday seasons. This is similar to how Google customizes their Google logo to recognize various holidays and events, except I just wanted some fun variations for Christmas and Halloween.
After spending time coming up with a few designs, I found I was having trouble choosing between the best of them. It occurred to me that it would be nice if I could use all my best designs, randomly picking one of them each time the page is served. So I started to think about how best to do this without adding a bunch of overhead to every single page request.
I've done similar things in the past. But I did it by simply copying over my logo file on the web server. This was a little awkward. I had to be careful when juggling all the different versions of the logo, especially since the names would change. And if I wasn't careful and made a mistake, I'd end up with no logo displayed for a while until I corrected the error. And, of course, this approach didn't didn't support randomly cycling through more than one image.
The RandomImageRotator Web User Control
With this in mind, I decided to create a web user control that reads an XML file that contains a list of images. Each image listed in the file has an active attribute that indicates if that image should be displayed. Listing 1 shows an example XML file.
Listing 1: An Example XML File
<?xml version="1.0" encoding="utf-8" ?>
<images>
<image active="true">~/images/banner1.jpg</image>
<image active="true">~/images/banner2.jpg</image>
<image active="true">~/images/banner3.jpg</image>
<image active="false">~/images/banner4.jpg</image>
<image active="false">~/images/banner5.jpg</image>
</images>
The control automatically and randomly selects from the list of images where the active attribute is set to true. This allows me to store all the different logo images on the server (no more renaming files). And to change which images are displayed, I only need to open and edit the XML file. Note that the images can start with the tilde (~) character to signify a path relative to the application root. They could also be complete URLs to reference images on other sites.
Listing 2 shows the RandomImageRotator control. As stated previously, the control is for an ASP.NET WebForms application but it could easily be modified to work with ASP.NET MVC.
Listing 2: The RandomImageRotator Control
public partial class RandomImageRotator : System.Web.UI.UserControl
{
// List of active images
private static List<string> ImageFileList = null;
// Time the list of active images was last refreshed
private static DateTime LastRefresh = DateTime.UtcNow;
// Object used to lock access to above members
private static Object RefreshLock = new Object();
/// <summary>
/// The alternate text displayed when the image cannot be shown.
/// </summary>
public string AlternateText
{
get { return Image1.AlternateText; }
set { Image1.AlternateText = value; }
}
/// <summary>
/// The tooltip displayed when the mouse is hovered over the control.
/// </summary>
public string ToolTip
{
get { return Image1.ToolTip; }
set { Image1.ToolTip = value; }
}
/// <summary>
/// Determines the name of XML file that contains a list of image files
/// to select from.
/// </summary>
public string XmlFileName { get; set; }
/// <summary>
/// Determines how long an internal cache lives until the XML file is
/// read again. Higher values will be more efficient. Lower values will
/// cause changes to the XML file to be picked up quicker.
/// </summary>
[DefaultValue(60)]
public int CacheLifetimeMinutes { get; set; }
public RandomImageRotator()
{
// Default property values
CacheLifetimeMinutes = 60;
}
protected void Image1_PreRender(object sender, EventArgs e)
{
// Get random image URL
Image1.ImageUrl = RandomlyChooseImageUrl();
}
protected string RandomlyChooseImageUrl()
{
// Handle case where no XML file has been specified
if (String.IsNullOrWhiteSpace(XmlFileName))
return null;
// Get current image file list
lock (RefreshLock)
{
// Reload file if cache not found or has expired
if (ImageFileList == null ||
(LastRefresh == null ||
(DateTime.UtcNow - LastRefresh).TotalMinutes > CacheLifetimeMinutes))
{
// Load XML file
string filename = HttpContext.Current.Server.MapPath(XmlFileName);
XElement element = XElement.Load(filename);
var images = from i in element.Elements("image")
where (bool)i.Attribute("active") == true
select i;
// Update file list
ImageFileList = images.Select(i => i.Value).ToList();
LastRefresh = DateTime.UtcNow;
}
}
// Handle empty list
if (ImageFileList.Count() == 0)
return null;
// Select random image from active image list
Random rand = new Random();
return ImageFileList[rand.Next(ImageFileList.Count)];
}
}
As you can see, the control is pretty simple. The control contains one ASP.NET server control: An Image control named Image1. The AlternateText and ToolTip properties simply defer to the Image control.
The XmlFileName and CacheLifetimeMinutes properties are specific to the RandomImageRotator control.
The XmlFileName property specifies the filename of your XML file. The code uses LINQ to XML to read and parse this file. Because opening and parsing an XML file is relatively time consuming, I didn't want to do this on every request. So the code caches the list of active image files along with the date and time they were last refreshed from the file. By caching this information in static memory, it is considerably faster than if it read the XML file on each request. By default, the code updates the image list every 60 minutes but you can set the CacheLifetimeMinutes property to change this.
The CacheLifetimeMinutes property specifies how long the data from the XML file is cached. Since the list of image files and the date and time they were last updated must be stored in static variables, it was necessary for the code to take concurrency issues into consideration.
Each HTTP request loads and new instance of the RandomImageRotator control in a different thread. If one thread is loading the XML file when another thread starts checking if the cache needs to be refreshed, the second thread won't detect that the data is being refreshed and will try to refresh the data again. The code deals with this by using a lock statement to ensure only one thread can enter the locked section at the same time.
Whether the image list was current or had to be refreshed, the code then uses the Random class to randomly select one of the current image files. The selected filename is then assigned to the ImageUrl of the hosted Image control.
If there's an exception, there is probably something seriously wrong and the code just lets the exception propagate to the caller. However, if no XML filename was specified or if the XML file contains no active image files, the code just sets the ImageUrl property to null and continues without complaint.
Conclusion
In case it wasn't obvious, the code does not cycle through images while the user is viewing the page. Rather, it simply picks a single image to display each time the page is served to a browser.
That's about all there is to it. The attached download contains a complete working WebForms project that tests the RandomImageRotator control.
End-User License
Use of this article and any related source code or other files is governed
by the terms and conditions of
.
Author Information
Jonathan Wood
I'm a software/website developer working out of the greater Salt Lake City area in Utah. I've developed many websites including Black Belt Coder, Insider Articles, and others.