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

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