Turns out that it’s really easy to create add-ins for BlogEngine.NET! I’ve created a new widget for the site that loads the latest image from ICanHasCheezburger.com, and if you click on the image, it loads the actual page for that entry in to a new window. You can check it out by locating the bottom widget on the right side of the site.
To create a custom widget, you need to be running BlogEngine.NET version 1.3.0.24 or above. You’ll notice a folder called widgets, to which you want to add a folder with the name of your new widget. You then need to add, at minimum, a user control called widget.ascx. In the user control, I added the following markup:
<div class="icanhascheezburger">
<asp:Label ID="lblPostTitle" runat="server" CssClass="ichcTitle"></asp:Label>
<asp:Image ID="imgThumbnail" runat="server" />
</div>
and added the following styles to my main .css file:
/* ICanHasCheezburger styles */
.icanhascheezburger .ichcTitle
{
font-weight: bold;
}
.icanhascheezburger img
{
cursor: pointer;
}
In my code, I needed to do 2 things. The first thing was to create an HttpModule for creating a thumbnail image of the ICanHasCheezburger. I don’t want to use HTML to resize an image that may be 800x600, because the whole image, regardless of size, is still streamed to the browser. Thumbnails are much better for this.
I won’t go in to details on how to create an HttpModule, as there are tons of examples online, but the bulk part of my needs is in the handling of the HttpApplication.BeginRequest event:
private void context_BeginRequest(object sender, EventArgs e)
{
HttpContext ctx = HttpContext.Current;
if (ctx.Request.Url.AbsolutePath.EndsWith("thumbnail.aspx"))
{
string url = ctx.Request.QueryString["url"];
int width = !String.IsNullOrEmpty(ctx.Request.QueryString["w"]) ? Convert.ToInt32(ctx.Request.QueryString["w"]) : 100;
int height = !String.IsNullOrEmpty(ctx.Request.QueryString["h"]) ? Convert.ToInt32(ctx.Request.QueryString["h"]) : 100;
HttpWebRequest req = HttpWebRequest.Create(url) as HttpWebRequest;
HttpWebResponse resp = req.GetResponse() as HttpWebResponse;
Image img = Image.FromStream(resp.GetResponseStream());
img = img.GetThumbnailImage(width, height, delegate() { return false; }, IntPtr.Zero);
ctx.Response.Clear();
ctx.Response.Cache.SetCacheability(HttpCacheability.NoCache);
ctx.Response.Cache.SetExpires(DateTime.Now.AddDays(-1));
ctx.Response.Cache.SetNoStore();
ctx.Response.ContentType = "image/jpeg";
img.Save(ctx.Response.OutputStream, ImageFormat.Jpeg);
ctx.Response.Flush();
ctx.Response.End();
}
}
In this code, I get the URL of the image I want to resize, along with the width and height that I want my thumbnail to be. I then issue a request on the URL to get the image, and load it in to a System.Drawing.Image object, where I can get the thumbnail. Finally, the thumbnail image is sent to the browser, after caching has been disabled (I want the image gotten fresh every time).
The next code activity was to change the code behind of the widget.ascx file so it derives from WidgetBase, rather than UserControl. My widget.ascx.cs file looks like this:
public partial class widgets_ICanHasCheezburger_widget : WidgetBase
{
public override string Name
{
get { return "ICanHasCheezburger"; }
}
public override bool IsEditable
{
get { return false; }
}
public override void LoadWidget()
{
XmlTextReader rssReader = new XmlTextReader(ConfigurationManager.AppSettings["iCanHasCheezburger.RssSource"]);
XmlDocument rssDoc = new XmlDocument();
rssDoc.Load(rssReader);
XmlNamespaceManager nsm = new XmlNamespaceManager(rssDoc.NameTable);
nsm.AddNamespace("media", "http://search.yahoo.com/mrss/");
XmlElement elm = rssDoc.DocumentElement.SelectSingleNode("channel/item[1]") as XmlElement;
string title = elm.SelectSingleNode("title").InnerText;
string url = elm.SelectSingleNode("media:content[2]/@url", nsm).Value;
imgThumbnail.ImageUrl = String.Format("thumbnail.aspx?url={0}&h=200&w=200", url);
imgThumbnail.Attributes.Add("onclick", String.Format("window.open('{0}');", elm.SelectSingleNode("link").InnerText));
lblPostTitle.Text = title;
}
}
The Name property needs to match the folder name that the control lives in. The IsEditible property should be self explanatory. In the near future, I’ll be flipping this over, and documenting the changes necessary to make the widget editable. Finally, the LoadWidget method is the widget equivalent to Page_Load.
In it, I pull the ICanHasCheezeburger RSS feed, and extract the title and image url of the first entry. I also set the image control up so that clicking it will open the actual ICanHasCheezburger site in to a new window.
Most of the code here should be self explanatory, thus the lack of details in some areas, but if you have a question on how or why I did something, please feel free to comment or email me.
P.S.
As I side note, I also created a little trick to deal with the fact that the IE version of the Flash loading javascript created by the flash control (like when you have a YouTube video on your blog) errors when you refresh the page. You can use the following code to fix this (I use jQuery, so if you want a non-jQuery version, let me know):
(function($){
$(function(){
if (typeof __flash__removeCallback != 'undefined'){
__flash__removeCallback = function(instance, name){
if (instance != null && name != null)
instance[name] = null;
};
}
});
})(jQuery);