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.

Comments (9) -

BlogEngineWall
BlogEngineWall says:

Hello Ben, this is the most interesting thing which we are awaiting since a long time. I will quickly check this multi blog BE feature and will do some R&amp;amp;amp;amp;D.
Regards,
http://www.blogenginewall.com

BlogEngineWall
BlogEngineWall says:

Hello Ben,

I am still not clear on Host Name &amp;amp;amp;amp; Virtual Path, can you please elaborate it more ? May be in next or separate post.

Regards,
http://www.blogenginewall.com

BlogEngineWall
BlogEngineWall says:

hi Ben,

Any update on this ?

Actually I have only one question is that with out creating the subdomain on the server can we browse parent blog using subdomain ?

I tried some test on BE RC 2.5 Demo - http://bercdemo.bloggersonline.com/

using credentials admin / admin

There, I have created one child blog with name &amp;amp;quot;m3&amp;amp;quot; now i am able to browse it with http://bercdemo.bloggersonline.com/m3

but not with

http://m3.bercdemo.bloggersonline.com/

Can you please clarify it ??

It is required that (a) DNS for the domain name or subdomain name is setup, and (b) IIS is setup to respond to that domain name or subdomain.

BE.NET will look at the incoming request to see what the domain/subdomain is, as well as look at the path to determine which blog instance the request is for.

In order for BE.NET to be able to see the incoming HTTP request to make this determination, DNS/IIS must be setup so requests actually are handled and routed to the BE.NET application.

Hopefully this makes sense.  Thanks.

BlogEngineWall
BlogEngineWall says:

Yeah Thanks, Now its clear.

All clear ;)

John Hamlin
John Hamlin says:

Hello - on the multiple blogs funcations, how about multiple domains?  I have several sites I would like to control under on BE instance (will start with a fresh installation).  

For example:  
Site 1 - Domain.com
Site 2 - Differentdomain.com
Site 3 - anotherdomain.com

I have full access to my own server and as such direct access to IIS 7.5.  Would you provide some insight as to how this would be handled in the control panel?

jch

Hi John, yes you can do that.

In the control panel, as you add each blog instance, there&#39;s a &quot;Host name&quot; field.  The Hostname to enter in would be the domain name -- domain.com for 1 blog instance, differentdomain.com for another blog instance, etc.  You would leave Virtual Path set to the default value of ~/

Hi!

Is it possible to use Windows LiveWriter to publish to multiple blogs in one instance?

Thanks!

Hi Mack.  I don't think I tested this specifically, but yes, that should work.  Each blog instance would be setup as a separate account within WLW.  When you setup the WLW account, you give it the blog instance homepage URL.  So you would just enter the homepage URL of a particular blog instance, and WLW will communicate w/ just that one blog instance.

When WLW communicates w/ the blog instance, because it will be communicating w/ the blog instance via the blog instance's unique URL, BlogEngine will recognize that the WLW requests are on behalf of that particular blog instance.

Pingbacks and trackbacks (9)+

Comments are closed