New Multiple Blog Feature - Site Aggregation

April 15, 2012 at 12:10 AMBen

A new feature named Site Aggregation is available in the BlogEngine.NET developer builds, and will of course be available in the next public release.  Site Aggregation is a feature related to Multiple Blog instances and this has been asked about by several people.

What is Site Aggregation?

You can designate up to 1 blog instance as being the Site Aggregation instance.  Typically it would be the primary blog instance.  When a blog instance becomes a Site Aggregation instance, the data displayed on the homepage, RSS feed and widgets contain blog posts from all the blog instances (see below for a complete list).  So the data is an aggregate of the blog posts across all instances.

This is useful for site visitors who will be able to visit the main blog, and see blog posts across all instances.  And if the RSS feed is subscribed to, that too aggregates blog posts across all instances.

What's Included

The main homepage, Posts by Tag page, and Posts by Date Range pages display the site aggregate data.  All the main widgets including the Tag Cloud, Recent Comments, Month List, Recent Posts, Calendar also take into account the site aggregate posts.  The Search results page, and RSS feed too.

For each post listed, clicking on the post title will take you to that post within that post's blog instance.  Each post usually also lists Tags and Categories tied to that post.  Clicking on a tag or category link will take you to the tag/category listing page for that post's blog instance (i.e. you will leave the site aggregate blog).

What's Not Included

Category based pages/widgets do not currently aggregate posts across all sites.  In particular, this includes the Category List widget and the Archive page.  These items still can be used on the site aggregate blog instance, but they will only display Posts for the site aggregate blog instance -- not for any of the non-site aggregate blogs.  The code needed to make these Category pages work with the site aggregated data is partially implemented in the Category core class (in particular, Category.AllBlogCategories and Category.ApplicableCategories), but some more work is needed for this.  It may not be until the following release where these category widgets/pages are site-aggregate capable.

The other item which is not included are Pages.  I think in most cases, it is preferable that Pages are not aggregated across all sites.

Configuring the Site Aggregate Blog Instance

It's very easy to designate a blog instance as being the site aggregate blog instance.  On the Blogs management page, there is a new "Is For Site Aggregation" property/checkbox you can check -- as shown below.

 

A Look at the Site Aggregate Blog Instance

In my test system, I have a primary blog instance, which is also the site aggregate blog instance, and 2 sub-blogs.  One sub-blog uses a virtual path, and the other sub-blog uses a different hostname.  Each blog instance has a post in it.  Here's what the homepage of the site aggregate blog instance looks like:

 

Similarly, here's a view of the RSS feed:

 

Site Aggregation Site - Includes "Local" Posts

The Site Aggregation site includes posts from other blog instances, as well as posts from its own instance (local posts).  This means you can create posts in the site aggregation instance as well, which is probably a handy feature for some.

Relative & Absolute Links

One issue that is accounted for is if you are using different hostnames for your blog instances.  For example, if your site aggregation instance is running at www.example.com and you have a sub-blog running at sub1.example.com, a lot of the links in BlogEngine.NET use relative paths, via RelativeLink and RelativeWebRoot.  A typical example is a hyperlink that you would find in PostView.ascx.

<a href="<%=Post.RelativeLink %>" class="taggedlink"><%=Server.HtmlEncode(Post.Title) %></a>

This URL in the HREF would end up being an invalid URL when the site aggregation site is displaying a post for sub1.example.com.  It would need to be an absolute link to "escape" out of the current hostname and get over to the other hostname (sub1.example.com).  The simple solution is to convert all these RelativeLink to AbsoluteLink.  A new property has been created off of IPublishable named RelativeOrAbsoluteLink.  This will return a relative link if it works, and if not, it will return an absolute link.  While an absolute link in theory always works, there are some advantages to using relative paths whenever possible.  So when using this new RelativeOrAbsoluteLink property, if the sub-blog's hostname matches the hostname of the site aggregation blog instance, you'll get a relative link, otherwise an absolute link.  Because the minority of BlogEngine.NET users won't be this situation where they are (a) using multiple blogs, (b) using the Site Aggregation feature, and (c) using different hostnames, RelativeOrAbsoluteLink will basically preserve backwards compatibility for everyone else not in this situation.

On this note, if you are using a custom theme, you may need to change RelativeLink in the PostView.ascx page to RelativeOrAbsoluteLink -- if you are in this situation.

A similar new property has been created in the Utils class, named RelativeOrAbsoluteWebRoot.  Just like RelativeOrAbsoluteLink, RelativeOrAbsoluteWebRoot will return the RelativeWebRoot if it works, otherwise it will return AbsoluteWebRoot.

AllBlogPosts and ApplicableBlogPosts

These are 2 new properties in the Post class, which are used to support this site aggregation feature.  AllBlogPosts is a list of posts across all blog instances.  It's very similar to Post.Posts.  Post.Posts is just for the current blog instance, where Post.AllBlogPosts is posts across all instances.  The only real challenge here is the blog posts for a particular blog instance are not loaded into memory until the first time that blog instance is hit.  If the site aggregation blog is hit before some of the sub-blogs have been hit, the data (Posts) for those sub-blogs will not yet be loaded in memory.  The AllBlogPosts property takes care of this by loading the posts of these sub-blogs into memory that have not yet had their first hit.

ApplicableBlogPosts is a shortcut property that either returns AllBlogPosts or Posts depending on which one is needed.

The Category class has similar properties that have been added to it -- AllBlogCategories and ApplicableBlogCategories.  These properties will be useful when the Archive page and Category based widgets such as the Category widget become site aggregate data capable (as mentioned above, these items are not yet site aggregate capable).

Blog and BlogId added to BusinessBase

The last notable change for this feature is the BusinessBase class now has Blog and BlogId properties.  This means objects such as Post, Category, BlogRollItem  (and anything else inheriting from BusinessBase) are now aware of which blog instance they belong to.  This has a lot of potential uses.  The main purpose for this at this point is properties such as RelativeLink (as in Post.RelativeLink) now take into account its blog instance's virtual path and hostname.  So when working with Post.AllBlogPosts which contains blog posts across all the blog instances, calling Post.RelativeLink or Post.AbsoluteLink on any of the posts in this AllBlogPosts collection will return the correct path.  As noted above, it's not safe to use Post.RelativeLink especially when working with Post.AllBlogPosts data.  Instead, Post.RelativeOrAbsoluteLink or just Post.AbsoluteLink are the safer choices.

Give it a Try

If you think this new Site Aggregation feature may be of use to you, I encourage you to try it out, either now or once the beta for the next version of BlogEngine.NET comes out.  The best place to bring up any issues or suggestions regarding this feature is in the CodePlex discussions group.

BlogEngine.NET Theme Contest

October 19, 2011 at 5:17 AMBen

If you haven't heard yet about the BE.NET theme contest, it started a couple of weeks ago, and continues on for about 1 more month -- until November 15th.

The 1st BlogEngine.NET Theme Contest!
http://www.dotnetblogengine.net/post/The-1st-BlogEngineNET-Theme-Contest!.aspx

If you know your way a bit around HTML/CSS and BE.NET themes, submit a theme, and you'll be contributing to the growing collection of BE.NET themes, and a chance to win one of the prizes.

So far there hasn't been too many submissions, so winning a prize is easier odds than a lot of other contests out there.

Posted in: General

Tags: , ,

Introducing Multiple Blogs in Single Instance for BlogEngine.NET

June 19, 2011 at 1:00 PMBen

The much requested and highly anticipated "multiple blogs" feature has made it into BlogEngine.NET 2.5.  BE.NET 2.5 will be released at the end of June.  This post goes over how it works and some information on making themes, widgets, extensions and your code be multiple-blog compatible.

Blogs Management Page

Each installation of BE.NET will have a "primary" blog instance.  By default this will be the normal blog you are accustomed to.  The primary blog has a new "Blogs" admin page.  That page looks like:

Here the blog instances (the primary blog instance and child blogs) can be managed.  A blog instance can be made Inactive.  An inactive blog instance will appear as if it does not exist.  Other properties that can be configured for each blog instance can be seen in the following "Add New Blog" dialog window:

Let's look at the properties for each blog instance.

Name

Any name you'd like to give the blog instance.  The name does not appear anywhere outside this Blogs management page.

Storage Container Name

This is the name of the folder where the blog data will live.  Basically a copy of the files and folders for each blog instance will be made -- i.e. each blog instance will have its own separate folder of data.  This makes the blog instances portable and keeps the data separated.  Databases can also be used with multiple blogs.  Even when using a database, there is still some data (blog post images/files, and a few other small files) that is stored in the file system.  So a separate folder will be created for each blog instance even when using the DbBlogProvider.

As in the past, the primary blog instance data is stored in App_Data.  There's now a new Blogs folder under App_Data.  Within App_Data\Blogs, a new folder matching the "Storage Container Name" will be created when a new blog instance is created.  The data that is initially put into this child blog storage folder is explained next.

Existing Blog to Create New Blog from

This is partially related to the above Storage Container Name.  When a new blog instance is created, the "Existing Blog to Create New Blog From" dropdown list will contain all of the existing blog instances.  The files & folders from the existing blog instance you choose here will be copied into the storage folder for the new blog instance as "initial data".  BE.NET 2.5 also includes a "Template" blog instance (which is inactive), and can be used as the existing blog instance to create new blog instances from.  The Template blog instance has the same data and settings as the primary blog.  You can optionally change the Template blog data, or even create multiple template blog instances to choose from when creating new blog instances.

If using a database, in addition to the storage container folder being copied for the new blog instance, the DB data from the existing blog instance will be copied in the DB for the new blog instance.  So for both XML and DB, the new blog instance created will start off using the exact data, files, etc that the blog instance you copied from has.

Note, there is no Template blog included with the database.  You can still create your own template blog for the DB by creating a new blog instance of your Primary blog, naming it "Template" (or any name) and customizing it to your liking.

Host Name & Virtual Path

These two properties along with the "Accept Any Text before Host Name" checkbox are what is used for BE.NET to determine which blog instance a request to the application is being made for.  Virtual Path refers to the path following the root.  Host name is the domain name (plus any subdomain).  You can mix and match Hostname and Virtual Path to create many URL combinations.  For example, the following URLs can be used:

http://www.example.com
http://www.example.com/blog1
http://blog1.example.com
http://blog2.domain.com
http://blog.domain.com/blog2
... etc ...

The 2nd and 5th examples above would be given a Virtual Path of ~/blog1 and ~/blog2 respectively.  Prefixing the virtual path with ~/ is required, and the ~/ will automatically be inserted for you in the Virtual Path field.  In the 3rd example above, the Host Name would be "blog1.example.com", in the 4th example the host name would be "blog2.domain.com" and in the 5th example, the host name would be "blog.domain.com".

If you are only using a single hostname for your blog, you can omit the hostname.  Entering it is really only required if you will be using multiple hostnames.

With the Virtual Paths above of blog1 and blog2 (examples 2 and 5), you do not need to create physical directories named blog1 and blog2.  These are virtual directories that BE.NET will look for in the URL to treat that request to the web server as being on behalf of those blog instances.  At the same time, you will want to make sure that physical directories matching the virtual paths (blog1, blog2, etc) do not already exist.

The "Accept Any Text before Host Name" checkbox is another option to allow any text (subdomains) to appear before the Host Name you enter.  If you leave this box unchecked, then the Host Name you enter will need to match exactly what is in the browser address bar.

Multiple-Blog Capable Themes, Widgets and Extensions

If you will be running a single instance blog, all of the themes, extensions and widgets that run under BE.NET 2.0 will still work perfectly fine for BE.NET 2.5.

If you will be running multiple blog instances, existing themes, extensions and widgets may need some adjustments to work with this new multiple blogs feature.  The following types of issues need to be addressed.

Caching - HttpRuntime.Cache

If an existing theme, widget or extension is storing data in the HttpRuntime.Cache, a likely needed change is to store that data separately for each blog instance.  With multiple blog instances, the same set of (physical) themes, extensions and widgets are being used across all the blog instances.  So if you have code in a widget that looks like:

HttpRuntime.Cache["recent-posts"] = recentPostList;

This same data you are storing will be shared across all blog instances that are using this widget.  This is probably not what was intended.  You can either change that code so it uses a dynamic cache key based on the "current blog instance", or as a convenient shortcut, you can use a new Caching provider built into BE.NET 2.5.  That line of code above using the new caching provider would look like:

Blog.CurrentInstance.Cache["recent-posts"] = recentPostList;

The cache provider will use your "recent-posts" cache key, prefixing it with the ID of the current blog instance (a GUID).  You can retrieve the current blog instance and/or the ID of the current blog instance via:

Blog currentInstance = Blog.CurrentInstance;
Guid currentInstanceId = Blog.CurrentInstance.Id;

Caching - Static

If your theme, widget or extension is storing data in static fields or properties, the cache provider described above cannot be used, however one approach is to convert your static data into a generic dictionary, with Guid as the key and your data type as the value.  The Guid key will represent the current blog instance ID.  You need to manage this dictionary by adding in each blog instance in the dictionary, ideally as the data needs to be stored the first time.  That code might look like:

private static Dictionary<Guid, string> _recentPostsMarkup =
    new Dictionary<Guid, string>();

private static string RecentPostsMarkup
{
	get
	{
		if (!_recentPostsMarkup.ContainsKey(Blog.CurrentInstance.Id))
		{
			_recentPostsMarkup[Blog.CurrentInstance.Id] = GetRecentPostsMarkup();
		}

		return _recentPostsMarkup[Blog.CurrentInstance.Id];
	}
}

URL Paths

This is important and a problem you may notice, especially if you are using Virtual Paths for your blog instances.  Let's look at a simple line of code in BE.NET 2.0 that worked fine there:

Response.Redirect("~/Account/login.aspx");

Simple enough.  However, if you're using Virtual Paths and you have a blog instance with a Virtual Path of "blog1", the homepage for that blog instance will be something like:

http://www.example.com/blog1

If you have code that uses that redirect shown above, you will end up at:

http://www.example.com/Account/login.aspx

When you probably wanted to end up at:

http://www.example.com/blog1/Account/login.aspx

So instead of redirecting to the login page for "blog1", you've incorrectly redirected the person to the login page for the primary blog instance.  That redirect should now look like the following to work well with multiple-blogs:

Response.Redirect(Utils.RelativeWebRoot + "Account/login.aspx");

In the past, Utils.RelativeWebRoot would resolve to the root of the blog.  Starting with BE.NET 2.5, Utils.RelativeWebRoot will resolve to the homepage/root of the current blog instance.  So when you are in "blog1", Utils.RelativeWebRoot will resolve to:

/blog1/

Similarly, Utils.AbsoluteWebRoot also takes the relative path of the current blog instance into consideration.

If you need the "real" web root of the application, you could use ~/ or you can use a new Utils.ApplicationRelativeWebRoot property.

While on the topic, a couple of other similar new properties are Blog.CurrentInstance.StorageLocation which will return a storage location beginning with ~/ to the physical location of the storage container for the current blog instance.  So Blog.CurrentInstance.StorageLocation may return a value looking like:

~/App_Data/Blogs/blog1/

BlogConfig.StorageLocation will return the same type of path, but to the direct App_Data folder (which equates to the storage location of the primary blog instance), so BlogConfig.StorageLocation will generally return:

~/App_Data/

Extensions

With extensions, the primary blog instance has the "master" switch over whether extensions are enabled.  If the primary blog instance disables an extension, that extension is no longer available to any of the child blogs.  If the primary blog instance enables an extension, by default this extension will be enabled for all the child blogs.  A child blog can disable an extension.

As long as the primary blog instance has an extension enabled, that extension will be loaded into memory and if it wires up any of the normal event handlers (Post.Serving, Commnet.Serving, etc), that extension is going to fire even if the event is occurring in a child blog that has explicilty disabled the extension.  In this scenario, the extension is handling an event for the child blog, but it should not do anything since the child blog disabled the extension.  In other words, we want the extension to "pretend" it was never called, or at least not take any action.  This is where a change is required in extensions so they check to see if the blog instance the extension is firing for has the extension enabled or disabled.  If it is disabled, then the extension should not run any of it's normal code.  The extensions included with BE.NET 2.5 have a new line of code added to them for this.  Taking the ResolveLinks extensions as an example, it has this new line of code (technically 2 lines of code) added to the beginning of PostCommentServing:

private static void PostCommentServing(object sender, ServingEventArgs e)
{
	if (!ExtensionManager.ExtensionEnabled("ResolveLinks"))
		return;

	if (string.IsNullOrEmpty(e.Body))
		return;

	e.Body = LinkRegex.Replace(e.Body, new MatchEvaluator(Evaluator));
}

The new lines of code above are lines 3 and 4.  It makes a call into the Extension Manager, passing to it the name of the extension (in this case, ResolveLinks).  ExtensionEnabled() will return false if the extension is disabled for the current blog instance.

In Summary

As mentioned earlier, if you will be running a single instance of BE.NET, these changes described above will not need to be made -- although they can be made.  Overall, I think we maintained a lot of backwards compatibility as the changes you will need to make to have a multiple-blog capable BE.NET application are very minimal.  The themes, widgets and extensions included with BE.NET 2.5 already have these changes  made to them.  Enjoy the new multiple blog capability and if you run into any issues or have any suggestions, the best place to pass along your thoughts is in the CodePlex discussion group.

BlogEngine.NET 2.0 RC - Available now!

November 24, 2010 at 12:36 AMBen

On Tuesday, a release candidate for BlogEngine.NET 2.0 was published.  Many nice features were added, with lots of great contributions from the community.  Switching to Mercurial for source code management made it very easy for people to create their own forks and for us to merge their code in.

Here's the official RC announcement that contains some additional details, including a short 5 minute video demo'ing the redesigned control panel.

If you're already using BlogEngine, I think you'll really like the improvements.  These upgrade instructions will come in handy.  If you're not using BlogEngine, what are you waiting for?!  Stop copying your friends, throw away WordPress and get BlogEngine! :)

I expect we will get some good feedback on issues people run into while using the RC.  During my own testing, it's actually working very good even now.  Depending on how well the RC goes, the final RTW version may be out in December.

If you download it and find any issues, the best place to report those are in the Issue Tracker.

Give it a try and have fun with it!

Download BlogEngine.NET 2.0 Release Candidate

Posted in: General

Tags: ,

BlogEngine 1.6 – Released!

February 1, 2010 at 7:50 PMBen

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.

Posted in: General

Tags: ,

Giving Comment Spammers Less Incentive to Spam You

December 8, 2009 at 10:01 PMBen

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 ...

Logging & Improved Error Reporting in BlogEngine

June 13, 2009 at 6:26 PMBen

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.

#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:

  1. It generates a summary, including details, of the unhandled error that just occurred.
  2. 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.
  3. 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.

Web Farm Extension 1.0

May 10, 2009 at 12:13 AMBen

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.

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)

Multiple WidgetZones in BE.NET

April 18, 2009 at 2:35 PMBen

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.

New Move Link

Clicking the Move link will display a dropdown list and Move button above the Move link.

Move Dropdown List and Button

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.

Move Widget Dropdown List Contents

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.

Posted in: Development

Tags: ,

BlogEngine.NET 1.5 Ships!

April 13, 2009 at 8:59 PMBen

BlogEngine.NET 1.5 Ships! 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.

Posted in: General

Tags: ,