Today BlogEngine.NET 1.6 is available to everyone as announced here.
The two big features are a new Comment Management area to view, edit and delete comments across all posts. This also includes a new automated comment moderation system. Just like how you can build custom Extensions, custom filters can be built and plugged into the event system when a new comment is posted. BE.NET 1.6 ships with two custom filters – a filter for Akismet filter and one for StopForumSpam.com. It’s quite slick.
The other notable feature is Multiple Widget Zones. This has actually been in the code base for several months now. I blogged about Multiple Widget Zones back in April.
If you’re upgrading from BE.NET 1.5, there are a couple of changes to be aware of, including one change you must make related to the ExtensionManager sub-folder in the App_Code folder. Simple upgrade instructions are available here in the documentation.
A more complete list of features and changes in BE.NET 1.6 can be found here. It’s definitely a worthwhile upgrade I recommend going to.
The latest check-in of BE.NET (1.5.1.36) has a small, but important change. The three themes included with BE.NET now include rel=”nofollow” on the links of commenter’s websites.
This is a theme specific change. So if you’re using a custom theme, and even if you upgrade to the latest build of BE.NET, there’s a good chance you might not have the NOFOLLOW instruction on these links. It can simply be added in the CommentView.ascx file in your theme’s blog folder.
As I’m using a custom theme myself, I just added NOFOLLOW to this blog too. Wikipedia has a good writeup on NOFOLLOW, in case you aren’t familiar with its purpose. I’m a little surprised it’s taken this long to get NOFOLLOW into the themes that are included with BE.NET. Better late than never!
I get a lot of comment spam on this blog. As I’m moderating comments, it ends up never showing up since I don’t approve any of it (TIP to spammers, stop wasting your time!).
Comment spammers are a lot more likely to leave comments on blogs that do not include NOFOLLOW. Yes, I’m sure a lot of the spammers actually look at these types of details when scoping out blogs to attack.
Incidentally, the ResolveLinks extension that comes with BE.NET already includes the NOFOLLOW instructions. This is the extension that will convert URLs in comments into hyperlinks. If the extension finds a URL like www.google.com in the comment content, it will convert that into:
<a href="http://www.google.com" rel="nofollow">www.google.com</a>
This conversion is done as the comment is being served.
I’m anxious to see what type of impact adding NOFOLLOW will have on my level of comment spam.
Fingers crossed ...
December 8, 2009 10:01 PM
There’s two new features in the latest build of BE, 1.5.1.11. These features, Logging and Improved Error Reporting, are separate but related features. I think both features will turn out to be very helpful – especially when trying to diagnose a problem. I’ll explain both features and how they can be used independently of each other, as well as with each other.
Logging
There’s a new event handler in BE that any extension or other component can subscribe to: Utils.OnLog. It can be subscribed to in an extension, like:
Utils.OnLog += new EventHandler<EventArgs>(OnLog);
In this case, there’s an OnLog event handler that will fire every time any piece of code in BE.NET logs a message. I created a simple Logger extension that is now included in BE 1.5.1.11 that subscribes to log notifications, and writes the log message to a logger.txt file in the App_Data folder. Anyone can write a similar extension that will save log messages to a database. The code for the this new Logger extension can be viewed below.
Logger Extension 1.0 (view code)
#region using
using System;
using BlogEngine.Core;
using BlogEngine.Core.Web.Controls;
using System.IO;
using System.Text;
#endregion
/// <summary>
/// Subscribes to Log events and records the events in a file.
/// </summary>
[Extension("Subscribes to Log events and records the events in a file.", "1.0", "BlogEngine.NET")]
public class Logger
{
static Logger()
{
Utils.OnLog += new EventHandler<EventArgs>(OnLog);
}
/// <summary>
/// The event handler that is triggered every time there is a log notification.
/// </summary>
private static void OnLog(object sender, EventArgs e)
{
if (sender == null || !(sender is string))
return;
string logMsg = (string)sender;
if (string.IsNullOrEmpty(logMsg))
return;
string file = GetFileName();
StringBuilder sb = new StringBuilder();
lock (_SyncRoot)
{
try
{
using (FileStream fs = new FileStream(file, FileMode.Append))
{
using (StreamWriter sw = new StreamWriter(fs))
{
sw.WriteLine(@"*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*");
sw.WriteLine("Date: " + DateTime.Now.ToString());
sw.WriteLine("Contents Below");
sw.WriteLine(logMsg);
sw.Close();
fs.Close();
}
}
}
catch
{
// Absorb the error.
}
}
}
private static string _FileName;
private static object _SyncRoot = new object();
private static string GetFileName()
{
if (_FileName != null)
return _FileName;
_FileName = System.Web.Hosting.HostingEnvironment.MapPath(Path.Combine(BlogSettings.Instance.StorageLocation, "logger.txt"));
return _FileName;
}
}
Any code within BE.NET, a widget, extension, etc. can now log any message like:
Utils.Log("some message to log");
If more than one event handler is subscribed to the OnLog notifications, each event handler will of course fire. It’s worth noting that Utils.Log() accepts a parameter of type object. The Logger extension is designed to receive messages of a string type (it actually casts the object type parameter to a string type). If Logger receives a non-string type, it doesn’t log the message – because the Logger extension is designed to receive simple string-based messages. If an extension or other piece of code wants to pass a non-string type message to a logger, a different extension could be created that is equipped to handle log messages that are of a type different than string.
If you don’t want logging to take place, either because you don’t want to worry about having a log file that keeps growing, or because you prefer not to store data in the App_Data folder (as the Logger extension does), you can simply disable the Logger extension on the Extensions tab in the control panel. As of right now, even if you leave the Logger extension enabled, there’s going to be virtually no messages logged as there isn’t any code that currently calls Utils.Log().
This new logging feature is going to help keep track of events going on. Although logging can be used for many purposes, one of the reasons I wanted to have this in BE.NET was to be able to record unhandled errors.
Error Handling & Reporting
There’s now an event handler in the Global.asax file that catches all unhandled exceptions. Up till now, if a 500 server error occurred, you would be redirected to error404.aspx, as defined by the <customErrors> tag in the web.config file. While this is a nice catch-all method to handling errors, people just getting started with BE.NET are often confused why they are seeing a “Page cannot be found” message when they are trying to do something like save changes and an unhandled error occurs -- which they don’t know has occurred when all they see is a “Page cannot be found” message. To be fair, most people just getting started with BE.NET are not getting errors. But some do, and adjusting folder permissions or other settings fixes the errors they are seeing. In order to fix the problem, one must first know an error is actually occurring, and they need to know what the actual error is!
The new Application_Error event handler in Global.asax does up to three things for non-404 errors:
- It generates a summary, including details, of the unhandled error that just occurred.
- If error logging is turned on (a new option, explained below), it makes a call to Utils.Log(), passing the error summary to any event handlers registered to receive log notifications.
- It does a Server.Transfer() to a new error.aspx page in the root of the BE.NET web folder.
For #1, the summary generated includes a stack trace, inner exceptions, and the URL the page occurred on (both Request.Url and Request.RawUrl).
The summary that is generated is stored in the Items collection of HttpContext.Current. Why? When Application_Error in Global.asax does a Server.Transfer to the new error.aspx page, the error.aspx page checks to see if the person is logged in (i.e. if they’re authenticated). If they are logged in, error.aspx will display the error summary generated within Global.asax. The error summary is retrieved out of the HttpContext.Current.Items collection. If the person isn’t logged in, they just see a message similar to error404.aspx, indicating an “unexpected error has occurred”, and the developer will be tortured, blah blah. :-)
Displaying the error details directly on error.aspx for logged in users is helpful for two reasons. (a) Immediate knowledge of the error, and (b) even if the new error logging option is turned off, you still can see the error message in your browser when you’re transferred to error.aspx.
I’ve mentioned this new error logging option twice now. On the Settings tab in the control panel, in the Advanced Settings section, there is a new checkbox labeled “Enable error logging”. By default, it’s turned off. While this is turned off, the only notifications the Logger extension will receive (if you leave the Logger extension enabled) will be messages coming from some piece of code that explicitly makes a call to Utils.Log(). If you turn this new Enable Error Logging feature on, then when an unhandled exception occurs, Global.asax will pass the error details to Utils.Log() for any registered event handlers to deal with.
Result & Extensibility Possibilities
From version 1.5.1.11, any extension, widget or even BE.NET itself can now simply make a call to Utils.Log() to have any message logged. Logging can be done with the built-in Logger extension, or messages can be logged to a database, sent via email, etc. by any other extension someone creates. I’m also very excited about the new error handling mechanism which will give administrators a lot more information about errors that may be occurring in their blog.
Please download and test out the latest build. If any problems show up or you have any ideas for improvements, you can leave a comment here, or post a message on the CodePlex discussion boards.
The caching of data in BlogEngine.NET becomes a problem when BE.NET is installed in a web farm. When you add, edit or delete a post, that change is occurring on one machine within the farm as well as within the data store (App_Data or DB). But the other servers within the farm are unaware there’s been a change in data. Not until the data loaded in memory on these other servers clears out anywhere from minutes to hours to days later, the old set of data will continue to be shown to visitors hitting one of these other servers.
I created a WebFarm extension which may help some people in this situation. I haven’t worked much with web farms, so I’m not sure how well this extension will work. Any feedback is appreciated. I was able to test this extension on Vista/IIS7 where I had two separate web applications pointing to the same physical BE.NET location on my machine. Even in this situation, creating a new post within one application would normally result in the post NOT showing up in the other application. This new Web Farm extension did solve the caching problem for this scenario. I’m hoping this success will carry over to a Web Farm scenario.
There’s two files in the ZIP file download. WebFarm.cs should go in the App_Code\Extensions folder. The other file, webfarm_data_update_listener.ashx, should go in the root of your blog.
Once those files are in their correct locations, if you go to the Extensions tab in the control panel, you’ll want to click ‘Edit’ for this new WebFarm extension.

I wanted the help box on the right side to include as much information as possible. But as you can see, the Extensions page doesn’t currently handle long description boxes very well :)
The idea behind this extension is that if each server within the farm has a unique internal Ip address, this extension can notify each server that a change in data has occurred. Not knowing a lot about web farms, this is the part I’m unsure is possible. But it does some reasonable each server would have its own unique IP address. If you’re using host headers, this extension may not work for you – unless you have a unique URL to each server within the farm.
The extension currently notifies the servers in the web farm when a new post or page has been created, updated or deleted. Other data such as Settings, Profiles, Categories, Comments, etc. is not handled by this extension. Or at least not in this version.
The webfarm_data_update_listener.ashx file you placed in the root of the blog is the handler that receives notifications when a change in Post or Pages has occurred. The data passed to the handler includes the type of data changed (Post, Page), the type of change (Insert, Update, Deletion) and the ID of the Post or Page that has been inserted/updated/deleted. Rather than the handler re-loading all the Post/Page data, it will insert, update or delete just the one piece of data that changed. This is more efficient than re-loading all the data which could be taxing for those with a lot of blog data.
As described in the help area when adding the server Ip/Urls in the WebFarm extension, make sure the “Shared Key” you enter matches the key in the webfarm_data_update_listener.ashx handler file. The default key in the handler is “blogengine”. For security purposes, you may change the key in the ASHX handler. But be sure the ASHX key matches the key you enter for each Ip/Url.
Also, because of this same data caching issue in web farms, after you enter all the web farm server Ip addresses into the WebFarm extension, you’ll want to re-start BE.NET so the extension data you just entered is detected by all the servers in your farm. Updating the web.config file with any meaningless change will accomplish re-starting the blog application. This is just a one-time requirement so all servers have the list of servers they need to notify when a post or page change has occurred.
I realize this extension has its limitations and isn’t a comprehensive solution to undesired caching in a web farm scenario. But it does handle propagating changes in posts across the servers in the farm – one of the more important areas. This extension is also easy to get started with in contrast to making various changes within BE.NET itself. Again, any feedback on this extension is appreciated.
Download: WebFarmExtension_1.0.zip (3.26 kb)
One of the commonly asked for features in BlogEngine.NET has just made it into the codebase. This feature is Multiple WidgetZones.
To use multiple widget zones in your BE.NET blog, there's not too much you need to do. I'll explain how it works and a couple of minor changes you may need to make to your blog -- even if you won't be using more than one widgetzone.
Stylesheet Changes
When there was only one widgetzone, the widgetzone <div> and the widget selector dropdown list had a fixed ID on their HTML elements. So, there was these two elements:
<div id="widgetzone">
<select id="widgetselector">
Because there can now be multiple widget zones and multiple widget selectors, and because there cannot legally be more than one element with the same ID, the widgetzone <div> now uses "widgetzone" for its class, and the selector uses "widgetselector" for its class. Both the widgetzone and the selector also have a unique ID based on the new ZoneName property (see below). So, example markup of what the <div> and <select> elements now look like are:
<div class="widgetzone" id="widgetzone_PageBottom">
<select class="widgetselector" id="widgetselector_PageBottom">
There's a good chance you have #widgetzone or #widgetselector styles defined in your theme's CSS file. Since there no longer are elements with those IDs, you'll want to do a search-and-replace in your CSS file. Replacing #widgetzone with .widgetzone and #widgetselector with .widgetselector is all that is required. Fortunately, because each widgetzone <div> and <select> element now have their own unique IDs, you can use those IDs to style individual widgetzones if they need to be styled differently in your blog.
ZoneName
Each widgetzone you add to your blog needs to have it's own unique ZoneName. The ZoneName can be added as a simple property to the widgetzone's control markup. For example:
<blog:WidgetZone runat="server" ZoneName="PageBottom" />
Although the ZoneName property is new to BE.NET, the existing widgetzone you've had in your blog up till now will start off with a ZoneName of "be_WIDGET_ZONE". This was the name used to store widgetzone data in either the App_Data folder, or in a database. Although not required, it wouldn't hurt to add that ZoneName to your existing widgetzone for clarity:
<blog:WidgetZone runat="server" ZoneName="be_WIDGET_ZONE" />
If you don't explicitly state a ZoneName, that is okay for one widgetzone, as BE.NET will default to using "be_WIDGET_ZONE" when a ZoneName is not provided. But, you MUST give each widgetzone its own unique ZoneName if you will use more than one widgetzone in your blog.
App_Data and Database - Both OK
Sometimes designing a flexible, well structured application pays off. It's great that BE.NET was designed as such. There was very little change required to enable multiple widget zones to be saved in either the App_Data folder or in a database. No database changes are needed either since the data for all the widgetzones will be stored in the existing be_DataStoreSettings table. Whether you're using XML storage in the App_Data folder or a DB backend, multiple widget zones work in both scenarios.
Moving Widgets
Drag-and-drop is out, and dropdown is in! Rearranging widgets within a widgetzone has always been done by dragging and dropping a widget to a new location within the zone. While initially implementing multiple widgetzones, I had a zone on the left side of the page, and another on the right side. While trying to drag-and-drop a widget on the right side to a new location within the same right side zone, I was having troubles with the drag-and-drop library trying to drop the widget into the zone on the left side of the page! Instead of spending time trying to weed through the logic of the drag-and-drop library, it occurred to me a simpler interface would be the better route to go. Drag-and-drop may be sexy, but getting widgets to move without any hassles or confusion is a better goal.
There is now a 'Move' link for each widget, next to the Edit and X (remove) links.
Clicking the Move link will display a dropdown list and Move button above the Move link.
In this example, I have 3 widget zones in my blog. Opening the dropdown list shows all 3 zones and the widgets within each zone.
The 3 zones appear in the list with brackets around them. They are the HeaderZone, be_WIDGET_ZONE and the PageBottom zone. Under each zone name is the list of widgets within that zone. You can either select a widget to move the current widget in front of, or you can select a zone which will move the current widget to the bottom of that zone.
As you can tell, moving a widget from one zone to another zone is fully supported. There's lots of possibilities with this capability.
Few More Stylesheet Changes
In addition to the search-and-replace stylesheet modification mentioned above, there's a couple of other additions you may want to make to your stylesheet in support of the new Move link and dropdown list. These changes are already in the stylesheets of the themes included with BE.NET. So you know what they are, I've listed them below. The first style below was a modification of an existing style.
div.widget a.edit, div.widget a.move{
font-size: 10px;
font-weight: normal;
float: right;
z-index: 1;
margin-left: 5px;
}
.widgetzone div#moveWidgetToContainer {
text-align: right;
margin: 3px;
}
Credits
Thanks to McZosch who posted the initial code changes for multiple widgetzones in the Issue Tracker at CodePlex. I was able to use mostly everything he contributed. Integration of his code went smoothly. Most of the time I spent with adding this feature was with moving widgets. Switching from drag-and-drop to dropdown and enabling widgets to move from one zone to another took some time altogether.
Give it a Try!
Since it's of course not required to fill up a widgetzone with many widgets in it, multiple widgetzones will allow you to add a zone anywhere in your blog with just a single widget in it. There's a lot of possibilities with this, and for many BE.NET bloggers out there, I think multiple widgetzones are going to be very useful.
Everything seems to work well after testing out the new feature in a few scenarios. You can download the latest build of BE.NET in the Source Code section at CodePlex. If you run into any issues, please post them in the Discussions area at CodePlex.
The next version of BlogEngine.NET has been released and is available for download. This new version features several new upgrades including support for IIS7, a much newer version of the tinyMce WYSIWYG editor, support for WLW 2009, compiled extension support, and nested comments to name a few.
Arguably even more important for BE.NET users since the last release is the growing number of available themes, widgets and extensions for BlogEngine. A few noteworthy places to get themes is from this site that has aggregated themes from several locations (live previews available), from another theme aggregator with live previews, or directly from onesoft who has adapted several themes for BE.NET.
The documentation has also been updated and moved with this release. The new location is now at CodePlex.
If you're already using BE.NET, upgrading should be pretty painless -- especially if you haven't done too many customizations to files outside your theme. If you're looking to start blogging, BlogEngine.NET is a great blogging platform to go with. It runs right out of the box, and since it's open source, you can optionally customize any part of it.
BlogEngine.NET 1.5 can be downloaded here.
Bloggers often have a few words they like to say about themselves. With BlogEngine.NET, there are a few ways to achieve this. In the default BE.NET installation, a TextBox widget titled "About the author" is already in the blog's sidebar. The author can just click 'Edit' and quickly describe themselves using the WYSIWYG editor. BE.NET users will find the TextBox widget is a very powerful widget because it's generic enough to display virtually anything.
Another option for About Me is to create a Page in BE.NET, and add a link on your blog to the About Me page. The main difference with this 'Page' approach is the About Me content is only seen on the About Me page, rather than the widget approach where the About Me content is shown on every blog page in the sidebar. With the Page approach, you also get a lot more room horizontally which is nice if you have some pictures or other space consuming content. I'm currently using the Page approach for the About Me page on this blog.
A third approach which is very similar to using the TextBox widget described above is to use an "About Me" widget. BE.NET allows each editor/administrator to create their own profile on the Profiles tab in the control panel. On the Profile page, each editor/administrator can enter details such as their Name, Phone Number, Address, a Photo image, and last but not least, About Me! The About Me box is a WYSIWYG editor. Currently, profile information entered is hardly used anywhere within BE.NET. The information is available, however, through the AuthorProfile class in BlogEngine.
The AboutMe widget displays the About Me text entered for a user on the Profiles tab in the control panel.
If multiple editors/administrators have been created on the Users tab in the control panel, a separate profile can be created for each one of these users on the Profiles tab. Because multiple profiles may exist, the AboutMe widget needs to know which profile's About Me content it should display. The edit control in the AboutMe widget allows the user to choose the profile to display the About Me content for. Because the settings for each widget are stored independently of one another, multiple AboutMe widgets may be added to the blog -- one AboutMe widget for each profile.
The AboutMe widget consists of the 2 required files you find for widgets -- widget.ascx, widget.ascx.cs. The edit control is made up of edit.ascx and edit.ascx.cs. A download link for the entire widget is available at the bottom of this post if you are interesting in trying it out in your own BlogEngine.NET installation.
widget.ascx (view code)
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="widget.ascx.cs"
Inherits="widgets_AboutMe_widget" %>
<asp:PlaceHolder runat="Server" ID="phAboutMe" />
widget.ascx.cs (view code)
#region Using
using System;
using System.Web;
using System.Web.UI;
using System.Collections.Specialized;
using BlogEngine.Core;
#endregion
public partial class widgets_AboutMe_widget : WidgetBase
{
public override void LoadWidget()
{
string CacheKey = "widget_aboutme_" + this.WidgetID.ToString();
string ProfileUserName = (string)HttpRuntime.Cache[CacheKey];
if (ProfileUserName == null)
{
StringDictionary settings = GetSettings();
if (settings.ContainsKey("UserName"))
ProfileUserName = settings["UserName"];
if (ProfileUserName == null)
ProfileUserName = string.Empty;
HttpRuntime.Cache[CacheKey] = ProfileUserName;
}
if (string.IsNullOrEmpty(ProfileUserName))
{
// Find the first profile with About Me content.
foreach (AuthorProfile profile in AuthorProfile.Profiles)
{
if (!string.IsNullOrEmpty(profile.AboutMe))
{
ProfileUserName = profile.UserName;
HttpRuntime.Cache[CacheKey] = ProfileUserName;
break;
}
}
}
if (!string.IsNullOrEmpty(ProfileUserName))
{
AuthorProfile profile = AuthorProfile.GetProfile(ProfileUserName);
if (profile != null && !string.IsNullOrEmpty(profile.AboutMe))
{
phAboutMe.Controls.Add(new LiteralControl(profile.AboutMe));
}
}
}
public override string Name
{
get { return "AboutMe"; }
}
public override bool IsEditable
{
get { return true; }
}
}
edit.ascx (view code)
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="edit.ascx.cs"
Inherits="widgets_AboutMe_edit" %>
<div>
<h3>Select the Profile to show About Me for</h3>
<div id="noProfilesAvailable" runat="server">
No profiles have yet been created.
</div>
<asp:RadioButtonList ID="rblProfileToDisplay"
runat="server"
DataTextField="UserName"
DataValueField="UserName">
</asp:RadioButtonList>
<br />
<div>
Note: Make sure you have entered About Me content for
the selected user above. This content should be
entered on the Profiles tab in the Control Panel.
</div>
</div>
edit.ascx.cs (view code)
#region Using
using System;
using System.Collections.Specialized;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using BlogEngine.Core;
#endregion
public partial class widgets_AboutMe_edit : WidgetEditBase
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
string SelectedUserName = null;
StringDictionary settings = GetSettings();
if (settings.ContainsKey("UserName"))
SelectedUserName = settings["UserName"];
if (!string.IsNullOrEmpty(SelectedUserName))
{
if (AuthorProfile.GetProfile(SelectedUserName) != null)
rblProfileToDisplay.SelectedValue = SelectedUserName;
}
rblProfileToDisplay.DataSource = AuthorProfile.Profiles;
rblProfileToDisplay.DataBind();
noProfilesAvailable.Visible = rblProfileToDisplay.Items.Count == 0;
}
}
public override void Save()
{
StringDictionary settings = GetSettings();
settings["UserName"] = rblProfileToDisplay.SelectedValue;
SaveSettings(settings);
HttpRuntime.Cache.Remove("widget_aboutme_" + this.WidgetID.ToString());
}
}
AboutMe Widget Download (2.4 kb)