Home > .NET, BlogEngine.NET > Getting BlogEngine.NET to Work in a Web Farm

Getting BlogEngine.NET to Work in a Web Farm

The Problem

I recently needed to add a blog to a customer’s site, and after doing some comparison shopping of the open source blog software out there – dasBlog and BlogEngine.NET being the most popular – I proposed using BlogEngine.NET.  The end result worked out very well.  The template structure made it very easy to add customizations, and the extensions available in the community were very useful.

Our problem with BlogEngine.NET didn’t arise until we moved into production (as all good bugs often do).  The issue is that BlogEngine.NET is not built to work in a web farm environment, and its heavy use of caching causes some “interesting” results when web heads become out of sync.

The Fix

Unfortunately (or fortunately, depending on how you look at things), many other users have run into this problem, and it’s been documented on several message board postings.  Thankfully Ben at Ben’s Quarters published a great little BlogEngine extension — Web Farm Extension — that can be downloaded, installed, and configured fairly quickly to remedy posts not updating on all web sites in a web farm.

And More Fixes

The one thing that he admittedly left out is refreshing the cache when comments are added or updated.  I took his source, added a couple of event handlers, and rearranged some code to make things work for cached comments as well.  (Download source code.)

The first thing I added was event handlers to WebFarm.cs:

Post.Saved += new EventHandler<SavedEventArgs>(Post_Page_Saved);
Page.Saved += new EventHandler<SavedEventArgs>(Post_Page_Saved);

// Add these event handlers to capture comment changes

Post.CommentAdded += new EventHandler<EventArgs>(Post_CommentChanged);
Post.CommentUpdated += new EventHandler<EventArgs>(Post_CommentChanged);
Post.CommentRemoved += new EventHandler<EventArgs>(Post_CommentChanged);

Next, I added the event handler code to parse out the post ID from the comment:

static void Post_CommentChanged(object sender, EventArgs e)
{
    Comment comment = sender as Comment;

    if (comment != null && comment.Parent != null)
    {
        NotifyPostChange(comment.Parent, SaveAction.Update);
    }
}

The rest of the code was restructured (but basically unchanged) to allow for multiple paths into the web farm notification method:

/// <summary>
/// Handler for when comments are added, updated, or deleted
/// </summary>
static void Post_CommentChanged(object sender, EventArgs e)
{
    Comment comment = sender as Comment;

    if (comment != null && comment.Parent != null)
    {
        NotifyPostChange(comment.Parent, SaveAction.Update);
    }
}

/// <summary>
/// Handler when a Post or Page has been saved.
/// </summary>
private static void Post_Page_Saved(object sender, SavedEventArgs e)
{
    if (e.Action == SaveAction.None)
        return;

    IPublishable item = (IPublishable)sender;

    NotifyPostChange(item, e.Action);
}

/// <summary>
/// Send notification message to each server in the web farm
/// </summary>
/// <param name="item">Post or Page that has been updated</param>
/// <param name="saveAction">Change action</param>
private static void NotifyPostChange(IPublishable item, SaveAction saveAction)
{
    // To avoid an endless loop, if the webfarm listener (webfarm_data_update_listener) is
    // updating a post in response to being notified of a change, we don't want this extension
    // sending out more notifications.
    if (HttpContext.Current != null && HttpContext.Current.Items.Contains("is_webfarm_update"))
        return;

    List<Uri> uris = new List<Uri>();

    string commonData =
        "?datatype=" + HttpUtility.UrlEncode(item.GetType().ToString().Replace("BlogEngine.Core.", string.Empty)) +
        "&dataid=" + HttpUtility.UrlEncode(item.Id.ToString()) +
        "&dataupdatetype=" + saveAction.ToString();

    DataTable table = _settings.GetDataTable();
    foreach (DataRow row in table.Rows)
    {
        string url = (string)row["NotifyUrl"];
        string key = (string)row["SharedKey"];

        if (!string.IsNullOrEmpty(url))
        {
            if (!url.Contains("://"))
            {
                url = "http://" + url;
            }
            if (!url.EndsWith("/"))
                url += "/";
            url +=
                "webfarm_data_update_listener.ashx" + commonData +
                "&key=" + HttpUtility.UrlEncode(key ?? string.Empty);

            Uri uri;
            if (Uri.TryCreate(url, UriKind.Absolute, out uri))
            {
                uris.Add(uri);
            }
        }
    }

    if (uris.Count > 0)
    {
        ThreadPool.QueueUserWorkItem(delegate { NotifyServers(uris); });
    }
}

Oh Yeah, And Images Too

The final issue with running BlogEngine.NET in a web farm is dealing with images that are uploaded as part of a blog posting.  By default, BlogEngine.NET saves these files to its App_Data/files folder.  As you can probably see, this won’t work so well if the blog is hosted on several servers.

The work-around for this limitation is actually fairly simple (compared to implementing an extension).  You will need to create a virtual directory in IIS that points to a network share that all of the websites can access.  Simply right-click on App_Data and select “Add Virtual Directory”.  Give “files” as the Alias and set the Physical Path to your network share.  I can’t tell you the exact file permissions you will need to use (since it varies depending on your environment), but it may take some tweaking to make sure that the web application can read and write to this folder.

BlogFiles

Conclusions

BlogEngine.NET is a nice blogging platform, especially for those out there that want to create a customizable .NET blog.  However, it has a big hole in its support for web farms, which may prevent it from being adopted for more serious web sites.  Hopefully these work-arounds help, and future versions address this issue.

Categories: .NET, BlogEngine.NET
  1. October 20th, 2010 at 09:28 | #1

    Genius!

  2. genial
    May 6th, 2011 at 13:15 | #2

    awesome blog thanks for the information

  3. May 16th, 2011 at 11:51 | #3

    great……………

  1. No trackbacks yet.