If you are reading this post, most probably you are familiar with aliases, and how they function.
If not, a good starting read regarding aliases can be found on Sitecore SDN site.
But what if we want or need to enhance the aliases functionality a little bit, and separate the alias part from the content editing one?
To make this possible, we can put a new field on our site base template, let’s call it “URL Value”, and create the corresponding alias item on the fly, if the field is filled in by a content editor.
Supposing we have a structure like this:
We will be able to call our “Smart Devices Application” product page by simply calling “http://{host-name}/smart-app“.
To do this, we would need to register a simple item:saved event in web.config:
<event name="item:saved">
...
<handler type="AliasFunctionality.AliasSaved, AliasFunctionality" method="OnItemSaved"/>
</event>
And here’s the code for it:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sitecore.Events;
using Sitecore.Data.Items;
using Sitecore.Data.Fields;
using Sitecore.SecurityModel;
using Sitecore.Diagnostics;
using Sitecore.Data;
namespace AliasFunctionality
{
public class AliasSaved
{
#region properties
public const string ALIASES_ROOT_FOLDER = "/sitecore/system/Aliases/";
public const string ALIAS_LINK_TYPE = "internal";
public const string ALIAS_TEMPLATE_ID = "{54BCFFB7-8F46-4948-AE74-DA5B6B5AFA86}";
#endregion
public void OnItemSaved(object sender, EventArgs e)
{
if (e == null)
return;
Item item = Event.ExtractParameter(e, 0) as Item;
if (item != null && item.Database == Sitecore.Context.ContentDatabase) // proceed only for master db
{
string aliasValue = item["URL Value"];
if (!string.IsNullOrEmpty(aliasValue))
{
try
{
var aliasesSiteFolder = GetAliasRootItem(item);
if (aliasesSiteFolder != null && !AliasAlreadyExists(aliasesSiteFolder, aliasValue))
{
// create new alias, then populate link for it
Item newAliasItem = CreateItem(aliasesSiteFolder, item, aliasValue, ALIAS_TEMPLATE_ID);
PopulateLink(item, newAliasItem);
}
}
catch(Exception ex)
{
//log error if needed
Log.Error("Exception happened on alias Save: " + ex.ToString(), this);
}
}
}
}
private Item CreateItem(Item rootFolder, Item item, string itemName, string templateId)
{
using (new SecurityDisabler())
{
var aliasTemplateId = ID.Parse(templateId);
Item result = rootFolder.Add(itemName, item.Database.GetTemplate(aliasTemplateId));
Log.Audit("Created new item: " + result.Paths.FullPath + ", language: " + result.Language.Name, rootFolder);
return result;
}
}
private void PopulateLink(Item item, Item newAliasItem)
{
if (newAliasItem != null)
{
using (new SecurityDisabler())
{
newAliasItem.Editing.BeginEdit();
try
{
LinkField linkedItem = newAliasItem.Fields["Linked item"];
//perform the editing
linkedItem.LinkType = ALIAS_LINK_TYPE;
linkedItem.Url = item.Paths.ContentPath;
linkedItem.QueryString = string.Empty;
linkedItem.Target = string.Empty;
linkedItem.TargetID = item.ID;
}
finally
{
//Close the editing state
newAliasItem.Editing.EndEdit();
}
}
}
}
private bool AliasAlreadyExists(Item root, string displayName)
{
if (root != null && root.HasChildren)
{
var alias = root.Children.FirstOrDefault(c => c.DisplayName == displayName);
if (alias == null)
{
return false;
}
}
return true;
}
private Item GetAliasRootItem(Item item)
{
return item.Database.GetItem("/sitecore/system/aliases/");
}
}
}
I don’t think there’s anything very complicated that needs to be explained regarding the above code. The main method is the one specified inside web.config, we check if the “URL Value” field is not empty and create an alias item if one with that name doesn’t exist already.
We can also go one step further and on publishing the initial item, check for the corresponding alias and publish that one too, if exists.
To do this, we need a new handler in web.config:
<event name="publish:itemProcessed" help="Receives an argument of type ItemProcessedEventArgs (namespace: Sitecore.Publishing.Pipelines.PublishItem)">
<handler type="AliasFunctionality.AliasPublish, AliasFunctionality" method="OnPublishItemProcessed">
</event>
And the code for the publish handler:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Publishing.Pipelines.PublishItem;
namespace AliasFunctionality
{
public class AliasPublish
{
#region properties
public const string MASTER_DB = "master";
public const string WEB_DB = "web";
#endregion
public void OnPublishItemProcessed(object sender, EventArgs e)
{
ItemProcessedEventArgs args = e as ItemProcessedEventArgs;
if (args != null)
{
Item item = args.Context.PublishHelper.GetTargetItem(args.Context.ItemId);
if (item != null)
{
string aliasName = item["URL Value"];
if (!string.IsNullOrEmpty(aliasName))
{
Item aliasItem = GetContentDbAlias(aliasName);
if (aliasItem != null)
{
PublishItem(aliasItem);
}
}
}
}
}
private void PublishItem(Item item)
{
// Create publishOptions (specify source and target database,
// the publish mode and language, and the publish date)
Sitecore.Publishing.PublishOptions publishOptions =
new Sitecore.Publishing.PublishOptions(item.Database,
Database.GetDatabase(WEB_DB),
Sitecore.Publishing.PublishMode.SingleItem,
item.Language,
System.DateTime.Now); // Create a publisher with the publishoptions
Sitecore.Publishing.Publisher publisher = new Sitecore.Publishing.Publisher(publishOptions);
// Choose where to publish from
publisher.Options.RootItem = item;
// Don't publish children
publisher.Options.Deep = false;
// Do the publish!
publisher.Publish();
Log.Audit("Publish item: " + item.Paths.FullPath + ", language: " + item.Language.Name, this);
}
private Item GetContentDbAlias(string aliasName)
{
return Factory.GetDatabase(MASTER_DB).GetItem(AliasSaved.ALIASES_ROOT_FOLDER + aliasName);
}
}
}
Inside the main method, which is OnPublishItemProcessed, we check if the item that was published had an alias, and if so,
we try to find it from Master database, and we publish it.
That would be it, we can now rely only on the content editors to add aliases only by changing “URL Value” field, and by publishing the original item, the alias also gets live!
Of course, there are some things worth mentioning here: the alias logic can be further enhanced to support multi-site solution. A very good article can be found here. Also, with the current implementation, there’s not an easy way to get rid of an old alias, if something needs to be changed, this will need to be done manually.
As Sitecore doesn’t encourage very much the usage of aliases, I think there are cases when the client demands to be able to have a custom URL independent of the tree-structure, and this is a pretty way to do it.