Again, I’ve seen hundreds of these out there, but they all seem to work differently from what I expected. So…I decided to write my own. It’s really not that sophisticated here. What I decided to do is to display a random photo pulled from the HTML of a post that has one of the specified tags.
So, in the BlogEngine “widgets” directory, create a folder called “RandomPhoto”.
Create two .ascx UserControls called “edit.ascx” and “widget.ascx”.
[more: Dude, this is long one…click here to read the full post]
Widget.ascx
This is the control that will be displayed on the widget area. It’s simple:
1 <%@ Control Language="C#" AutoEventWireup="true" CodeFile="widget.ascx.cs" Inherits="widgets_RandomPhoto_widget" %>
2 <asp:Panel ID="Panel1" runat="server" HorizontalAlign="Center">
3 <asp:HyperLink runat="server" ID="PhotoLink" NavigateUrl="#" rel="lightbox">
4 <asp:Image runat="server" ID="PhotoImage" ImageUrl="#" />
5 </asp:HyperLink>
6 </asp:Panel>
Yeah, that’s it. It’s a panel, containing a link and an image. We will have to set the NavigateUrl of the link in codebehind, as well as the ImageUrl of the Image.
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Web;
5 using System.Web.UI;
6 using System.Web.UI.WebControls;
7 using System.Collections.Specialized;
8 using System.IO;
9 using System.Linq;
10 using Funkymule.BlogEngine.Data;
11 using System.Configuration;
12 using System.Globalization;
13 using System.Text;
14
15 public partial class widgets_RandomPhoto_widget : WidgetBase
16 {
17 protected void Page_Load(object sender, EventArgs e)
18 {
19 }
20
21 public override string Name
22 {
23 get { return "RandomPhoto"; }
24 }
25
26 public override bool IsEditable
27 {
28 get { return true; }
29 }
30
31 public override void LoadWidget()
32 {
33 StringDictionary settings = GetSettings();
34
35 ChangeFrequency frequency = ChangeFrequency.Always;
36 try
37 {
38 if (!string.IsNullOrEmpty(settings["ChangeFrequency"]))
39 frequency = (ChangeFrequency)Enum.Parse(typeof(ChangeFrequency), settings["ChangeFrequency"]);
40 }
41 catch (FormatException)
42 {
43 }
44
45 int dx;
46 int dy;
47 int rr;
48 int rl;
49
50 int.TryParse(settings["dx"], out dx);
51 int.TryParse(settings["dy"], out dy);
52 int.TryParse(settings["rl"], out rl);
53 int.TryParse(settings["rr"], out rr);
54
55 var tags = settings["Tags"] == null ? new string[0] : settings["Tags"].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
56
57 if (tags.Length > 0) GetImage(frequency, tags, dx, dy, rl, rr);
58 }
59
60 void GetImage(ChangeFrequency changeFrequency, string[] tags, int dx, int dy, int rl, int rr)
61 {
62 RandomImageGenerator rig = new RandomImageGenerator();
63 if (changeFrequency == ChangeFrequency.Daily) rig = new RandomImageGenerator(DateTime.Now.DayOfYear);
64 else if (changeFrequency == ChangeFrequency.Hourly) rig = new RandomImageGenerator(DateTime.Now.DayOfYear ^ (int)DateTime.Now.TimeOfDay.TotalHours);
65 else if (changeFrequency == ChangeFrequency.Minutely) rig = new RandomImageGenerator(DateTime.Now.DayOfYear ^ (int)DateTime.Now.TimeOfDay.TotalMinutes);
66
67 foreach (string tag in tags) rig.Tags.Add(tag.Trim());
68 string[] images = rig.GetRandomImage();
69
70 if (images.Length == 2)
71 {
72 this.PhotoImage.ImageUrl = images[1];
73 this.PhotoLink.NavigateUrl = images[0];
74 }
75 if (dx > 0) this.PhotoImage.Width = dx;
76 else if (dy > 0) this.PhotoImage.Height = dy;
77 else this.PhotoImage.Width = 200;
78 }
79 }
Overriding the “IsEditable” makes an edit link on the widget.
Let’s examine the “LoadWidget” override:
GetSettings() on line 33 returns the settings configuration of the widget in a name/value string dictionary.
Line 35 sets the “frequency”. I’m setting this to an enumeration that I’ve created in my blog engine assembly.
1 public enum ChangeFrequency
2 {
3 Always,
4 Daily,
5 Hourly,
6 Minutely
7 }
This is an enumeration I use to determine when to change the random things on my blog, like Quote of the Day, and Random Photo. “Always” always changes the content when the page is refreshed or a new page is rendered. “Daily” keeps the random item the same for the entire day. “Hourly” keeps the random item the same for the current hour, and “Minutely” keeps the item the same for the minute. Note that this is NOT A DURATION. i.e. “Daily” will not keep an item good for a 24 hour period. It will keep a random item constant for the calendar day. Once the day changes, the random item will be different.
The values dx and dy will be used for the width and height of the thumbnail. The rr and rl are from an earlier version where I was generating a thumbnail dynamically and rotating it. It’s not necessary now and should be removed.
The “Tags” value is a comma delimited list of tags from which images will be selected. Once we have all of the settings parsed and default values in place of missing items, we can get the image.
The “GetImage” method creates a new “RandomImageGenerator” class. on lines 63 – 65, you can see how the change frequency discussed above keeps a random item constant for a specified period by using a seed derived from the change frequency. Add the tags on line 67, and get the image names on line 68. The RandomImageGenerator class will return two strings in an array. The first string is the URL to the thumbnail image, and the second string is the URL to the full image.
Lines 75 – 77 set the size of the image based on limiting dimensions from the settings. The aspect ratio of the actual thumbnail will be used, so we only specify the LIMITING side. If dx is specified, the thumbnail is constrained on the width and is allowed to grow vertically. If dy is specified, the thumbnail is constrainted on the height, and will grow on the width. If they are both specified, dy is ignored.
Let’s take a look at the class that gets the random image.
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Configuration;
6 using System.Text.RegularExpressions;
7 using System.IO;
8 using System.Web;
9
10 namespace Funkymule.BlogEngine.Data
11 {
12 public class RandomImageGenerator
13 {
14 public class DirectorySpec
15 {
16 public bool Deep { get; set; }
17 public string Directory { get; set; }
18 }
19
20 private static Regex _rx = new Regex(@"<a.*?href=""(?'image'.*?)"".*?>.*?<img.*?src=""(?'thumbnail'.*?)"".*?>.*?</a>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
21 private static Random _random = new Random();
22
23 private List<string> _tags = new List<string>();
24
25 public IList<string> Tags { get { return _tags; } }
26
27 public RandomImageGenerator()
28 {
29 }
30
31 public RandomImageGenerator(int seed)
32 {
33 _random = new Random(seed);
34 }
35
36 public string[] GetRandomImage()
37 {
38 return GetRandomTaggedImage();
39 }
40
41 private string[] GetRandomTaggedImage()
42 {
43 using (BlogEngineDataContext ctx = new BlogEngineDataContext(ConfigurationManager.ConnectionStrings["BlogEngine"].ConnectionString))
44 {
45 // get a random post content that has an image referenced in it
46
47 // get post ids for all posts with the tag
48 var postIds = ctx
49 .be_PostTags
50 .Where(pt => this.Tags.ToArray().Contains(pt.Tag))
51 .Select(pt => pt.PostID);
52
53 // get a random one
54 for (int i = 0; i < 1000; i++)
55 {
56 Guid postId = postIds.Skip(_random.Next(0, postIds.Count())).First();
57 string content = ctx.be_Posts.First(p => p.PostID == postId).PostContent;
58 IEnumerable<Match> matches = _rx.Matches(content).Cast<Match>().Where(m => m.Success == true);
59 if (matches.Count() > 0)
60 {
61 Match match = matches.Skip(_random.Next(0, matches.Count())).First();
62 return new string[] { match.Groups["image"].Value, match.Groups["thumbnail"].Value };
63 }
64 }
65 }
66
67 return new string[0];
68 }
69 }
70 }
It’s not terribly complicated. A list of tags, a Random that is initialized from the seed specified in the constructor. Using ADO.NET Entity Framework generated from the BlogEngine database, I am getting a list of post ids where their tag is in the list of specified tags. Once I have a post, use the regular expression to find an image in it. Not all posts will have an image, so I look 1000 times to try to find one. Not elegant, but functional. The regular expression gets the virtual path to the image, and the thumbnail.
Note that if the post contains an image that is not a thumbnail that links to a full sized image, this method will not work for you.
Edit.ascx
In order to be able to specify parameters to our widget at run time through the web interface, we need to specify an edit control. The edit control will look like this:

Here, you enter the change frequency, comma delimited list of tags, constraining width, constraining height, and the two fields that are no longer used and need to be removed when I have time.
The ascx source looks like this:
1 <%@ Control Language="C#" AutoEventWireup="true" CodeFile="edit.ascx.cs" Inherits="widgets_RandomPhoto_edit" %>
2 <asp:Table runat="server" CellPadding="5">
3 <asp:TableRow runat="server">
4 <asp:TableCell runat="server" HorizontalAlign="Right">Change Frequency</asp:TableCell>
5 <asp:TableCell runat="server">
6 <asp:DropDownList ID="FrequencyBox" runat="server" />
7 </asp:TableCell>
8 </asp:TableRow>
9 <asp:TableRow runat="server">
10 <asp:TableCell runat="server" HorizontalAlign="Right">
11 Tags:<br />
12 <span style="font-style: italic">Separate multiple tags with a comma.</span>
13 </asp:TableCell>
14 <asp:TableCell runat="server">
15 <asp:TextBox runat="server" ID="TagsTextbox" Width="200" />
16 </asp:TableCell>
17 </asp:TableRow>
18 <asp:TableRow ID="TableRow1" runat="server">
19 <asp:TableCell ID="TableCell1" runat="server" HorizontalAlign="Right">
20 Width:<br />
21 </asp:TableCell>
22 <asp:TableCell ID="TableCell2" runat="server">
23 <asp:TextBox runat="server" ID="WidthTextbox" Width="200" />
24 </asp:TableCell>
25 </asp:TableRow>
26 <asp:TableRow ID="TableRow2" runat="server">
27 <asp:TableCell ID="TableCell3" runat="server" HorizontalAlign="Right">
28 Height:<br />
29 </asp:TableCell>
30 <asp:TableCell ID="TableCell4" runat="server">
31 <asp:TextBox runat="server" ID="HeightTextbox" Width="200" />
32 </asp:TableCell>
33 </asp:TableRow>
34 <asp:TableRow ID="TableRow3" runat="server">
35 <asp:TableCell ID="TableCell5" runat="server" HorizontalAlign="Right">
36 Left Rotation:<br />
37 </asp:TableCell>
38 <asp:TableCell ID="TableCell6" runat="server">
39 <asp:TextBox runat="server" ID="LeftRotationTextbox" Width="200" />
40 </asp:TableCell>
41 </asp:TableRow>
42 <asp:TableRow ID="TableRow4" runat="server">
43 <asp:TableCell ID="TableCell7" runat="server" HorizontalAlign="Right">
44 Right Rotation:<br />
45 </asp:TableCell>
46 <asp:TableCell ID="TableCell8" runat="server">
47 <asp:TextBox runat="server" ID="RightRotationTextbox" Width="200" />
48 </asp:TableCell>
49 </asp:TableRow>
50 </asp:Table>
It’s a simple table. The codebehind:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Web;
5 using System.Web.UI;
6 using System.Web.UI.WebControls;
7 using System.Collections.Specialized;
8 using Funkymule.BlogEngine.Data;
9 using System.Globalization;
10
11 public partial class widgets_RandomPhoto_edit : WidgetEditBase
12 {
13 protected void Page_Load(object sender, EventArgs e)
14 {
15 if (!IsPostBack)
16 {
17 int ti;
18
19 FrequencyBox.DataSource = Enum.GetNames(typeof(ChangeFrequency));
20 FrequencyBox.DataBind();
21
22 FrequencyBox.SelectedValue = ChangeFrequency.Always.ToString();
23
24 StringDictionary settings = GetSettings();
25 if (settings.ContainsKey("Tags")) TagsTextbox.Text = settings["Tags"];
26 if (settings.ContainsKey("ChangeFrequency")) FrequencyBox.SelectedValue = settings["ChangeFrequency"];
27
28 if (settings.ContainsKey("dx") && int.TryParse(settings["dx"], out ti)) WidthTextbox.Text = ti.ToString(CultureInfo.InvariantCulture);
29 if (settings.ContainsKey("dy") && int.TryParse(settings["dy"], out ti)) HeightTextbox.Text = ti.ToString(CultureInfo.InvariantCulture);
30 if (settings.ContainsKey("rl") && int.TryParse(settings["rl"], out ti)) LeftRotationTextbox.Text = ti.ToString(CultureInfo.InvariantCulture);
31 if (settings.ContainsKey("rr") && int.TryParse(settings["rr"], out ti)) RightRotationTextbox.Text = ti.ToString(CultureInfo.InvariantCulture);
32 }
33 }
34
35 public override void Save()
36 {
37 StringDictionary d = GetSettings();
38 int tmp;
39
40 d["Tags"] = this.TagsTextbox.Text;
41 d["ChangeFrequency"] = FrequencyBox.SelectedValue;
42 if (int.TryParse(WidthTextbox.Text, out tmp)) d["dx"] = tmp.ToString(CultureInfo.InvariantCulture);
43 else d.Remove("dx");
44 if (int.TryParse(HeightTextbox.Text, out tmp)) d["dy"] = tmp.ToString(CultureInfo.InvariantCulture);
45 else d.Remove("dy");
46 if (int.TryParse(this.LeftRotationTextbox.Text, out tmp)) d["rl"] = tmp.ToString(CultureInfo.InvariantCulture);
47 else d.Remove("rl");
48 if (int.TryParse(this.RightRotationTextbox.Text, out tmp)) d["rr"] = tmp.ToString(CultureInfo.InvariantCulture);
49 else d.Remove("rr");
50
51 SaveSettings(d);
52 }
53 }
Lines 19 and 20 populate the combo box with the values of the ChangeFrequency enumeration. The text of the other controls is set from the values in the settings dictionary.
The “Save” method sets the settings values from the textboxes, etc.
That’s really all there is to it. I’ve been using it on my blog for a while, and haven’t noticed any problems. The regular expression might not cover all scenarios, but it should work fine for the most part. Since my implementation uses a “RandomImageGenerator” class that is in my custom assembly, I haven’t included a project. The functionality of that class can be merged into the widget.ascx file. That way, you only need to place the 2 ascx files and 2 ascx.cs files in the widgets/randomPhoto directory and an assembly isn’t required.

7a246376-185c-4ff0-beb1-96fbd1380912|0|.0
Development
blogengine