As the first two posts for this blog were more business focused
than technology focused, I wanted to do a little digging for this
post and get into some code.
"Fortunately" I came across a need for my blog which, if the
Umbraco Forum is any indication, has vexed couple of other people.
That is the need to have an alternate Post Date other than the
Created Date. This is more than just changing the date, but it is
also a matter of changing the structure of the date folders within
which it resides.
Background
This blog uses the Blog4Umbraco package put together by the
estimable Tim Geyssens. It is MIT licensed Open Source and you can download
the source from a link on the Blog4Umbraco project in Codeplex.
One useful part of the package is the feature which
automatically creates date folders for a blog post giving one the
handy /year/month/day/postname path. It does this by using the Umbraco event model to capture the New Event
which is fired when a new document is created. Within the event
handler, it checks to see if the post is currently in a Date
Folder, and if not, it creates a folder structure for the
/year/month/day as necessary (based on the CreateDate at the time
the New Document was created) and moves the post there. In
addition, many of the XSLT macros for ordering and rolling up posts
rely on the CreateDate to determine the order.
These facts have the following ramifications:
- Document path is set at the time of creation and is based on
the CreateDate, so your path will be based on that, regardless of
when you publish it.
- Changing the CreateDate by itself will not affect the path,
since the path is set when the document is first created and the
document must be created before you can reference the createdate in
code in order to change it.
- This is why importing blogs will often run into this
issue,
- Changing the path but not changing the CreateDate will cause
your documents to sometimes be ordered in a different order than
your published dates if the default XSLT files are used.
Requirements
So to address the shortfalls of using the CreateDate I want to
create an alternate Post Date which will be used for Blog dating
and ordering and which will determine the folder path to the
Blog.
To create an alternate post date I have to handle not only the
date structure, but also the XSLT which displays that date as the
Date Posted instead of the the CreateDate and also orders the posts
based on that date. In addition, it should pre-populate the new
field with the create date originally so that the XSLT has a value
to work with. Finally, to have an optimal solution, it should only
attempt to move the blog when necessary.
Solution
The first part of this solution and the key to the rest of it is
to create a new property on the Blog Post Document Type called
whatever you want (I called it Post Date) but with
an alias of PostDate. Use the DatePicker DataType for
convenience.

Once we have this in place, we'll need to do a few things to
make it useful. First we'll want to populate it automatically
with the Create Date upon generating a new post. Since we
will later be updating our XSLT to sort our entries using this
field, we want to make sure it has a value. Second, we'll
want to move the post to the appropriate folder structure based
upon this new Post Date, but only as when it changes.
For both of these requirements, the handy Document Events are
the way to go. Umbraco Events allow you to listen for actions
Umbraco is taking like creating new pages. Then you can run
your custom code to interact with Umbraco while this is
happening. (For those of you experienced in .Net Event
Handlers, that is what they are). The Umbraco Wiki has some
pages on how to make use of these events, so I won't go
into great detail, but it is really easy if you have any .Net
experience. [If you just want to use the dll that I've coded,
I've included it with the class file at the bottom of this
post. Just drop it in your bin directory and Umbraco will
know it is there. Usual caveats of "No Guarantees...''
and "Do so at your own risk".]
Auto-populate the Post Date
The first event we want to use if the Document.New event.
This will allow us to autopopulate our new Post Date field with the
Create Date.
void Document_New(Document sender, umbraco.cms.businesslogic.NewEventArgs e)
{
if (sender.ContentType.Alias == "BlogPost")
{
if (sender.getProperty("PostDate") != null)
{
sender.getProperty("PostDate").Value = sender.CreateDateTime.Date;
}
}
}
Notice the first If-statement. You will see this critical
If-statement in many examples of event handling in Umbraco.
It is important to understand that Umbraco fires an event whenever
an appropriate action occurs regardless of whether it is germane to
what you are doing. In this case the Document.New event will
be fired every time a new document is created, regardless of
whether it is a Blog Post or a News Article or some other new page
on your site. The sender.ContentType.Alias property is a good
way to make sure that you are only taking further action if it is
related to what you are trying to accomplish. If you are not
sure of the appropriate Document Alias, you can find it by going to
the Settings Section of your Umbraco Admin, expand the Document
Types folder and click on the document type you are trying to
handle. You will see the Alias (and be able to update it) on
the screen that is loaded.

Move the Document to the right Folder path
Ok, now we've populated our PostDate field. How do we move
our post to the correct folder path? Also, just as
importantly, how do we avoid trying to move it when we don't need
to?
Fortunately, for moving the folder, the Blog4Umbraco code shows us how it is being
placed in the folder path in the first place. We just have a
few tweaks for this part. First, we want to use the
Document.BeforePublish event and not the new event.
void Document_BeforePublish(Document sender, umbraco.cms.businesslogic.PublishEventArgs e)
{
//... Our code will go here.
}
Next, we only want to run the code if we need to. So we'll
check for the Post Date and make sure it has changed since the last
time the page was published.
//Don't forget that above this is:
//if (sender.ContentType.Alias == "BlogPost")
if (sender.getProperty("PostDate") != null) //If no post date, skip
{
if (sender.Parent != null) //If top of tree, something is wrong. Skip.
{
try
{
DocumentVersionList[] postVersions = sender.GetVersions();
bool _versionCheck = true;
DateTime postDate;
postDate = System.Convert.ToDateTime(sender.getProperty("PostDate").Value);
if (postVersions.Length > 1) //If it has been published, check post date info
{
//Length -1 is current version Length -2 is past version (if it exists)
Guid previousVersion = postVersions[postVersions.Length - 2].Version;
Document doc = new Document(sender.Id, previousVersion);
DateTime previousPostDate = System.Convert.ToDateTime(doc.getProperty("PostDate").Value);
_versionCheck = (postDate != previousPostDate);
}
if (_versionCheck) //Only do the date folder movement if the PostDate is changed or is new Post.
{
//... More Code
}
}
}
}
Next, we want to use the post date and not the created date.
string[] strArray = { postDate.Year.ToString(), postDate.Month.ToString(), postDate.Day.ToString() };
Next, we are likely in a Date Folder, so well need to find the
top of the blog so that we can create the correct Date Folders in
the correct places.
if (strArray.Length == 3)
{
Node topBlogLevel = new Node(sender.Parent.Id);
//Traverse up the tree to Find the Blog Node since we are likely in a Date Folder path
while (topBlogLevel != null && topBlogLevel.NodeTypeAlias != "Blog")
{
if (topBlogLevel.Parent != null)
{
topBlogLevel = new Node(topBlogLevel.Parent.Id);
}
else
{
topBlogLevel = null;
}
}
if (topBlogLevel != null)
{
//Create Appropriate Date Folders
}
}
Finally, check for and create the post folders similar to the
way the Blog4Umbraco does it when first creating a post.
Given that my code is very similar to what Tim did [read: brazen
code reuse] I won't post it here, but as I mentioned, I've included
that class file, modified XSLT, modified BlogPost.master and dll at
the bottom of this post.
Final Comments
This is one solution to moving Blog Posts to new Date Folder
paths. Some things to understand before you implement this
solution.
- Although this should work for importation, it hasn't been
tested. The biggest issue, in my opinion, will be
threading. If this is being called asynchronously, it is
theoretically possible for duplicate folders to be created since a
second thread may find a date folder isn't there and try to create
it while a first thread is in the process of doing the same.
Although it would slow things down a little, some Mutex calls
around the Folder Discovery part may be necessary.
- Use at your own risk. This was only tested against my
personal install(s) of blog4Umbraco 2.0.26. Additionally, it
modifies the xslt and BlogPost.master so future updates to the blog
engine may overwrite these changes. I'm going to submit this
as a patch to the codeplex project, so it may make it in. If
it does, you may have to remove the homemade version so it doesn't
try to do this twice.
BTW, I started this blog post March 30th.
You can download the code and binary here.