Altijd als eerste onze nieuwste blogs lezen? Laat je email adres achter en je ontvangt een bericht als wij een nieuw blog plaatsen.

We at Suneco are specialized in building multiple websites in one Sitecore instance. In that process I often ask myself the question “why does this only work on Sitecore instance level and not on a website level?”

building aliases suneco

Onze technische blogs zijn in het Engels. Dit doen wij omdat wij menen dat technische kennis van Sitecore en Sitefinity grensoverschrijdend moet zijn. Wij leren veel van buitenlandse developers, wij delen onze kennis ook graag met hen. Mochten er in de code fouten zitten of punten ter verbetering, dan gaan we graag het gesprek aan. 

Aliases on website level

We at Suneco are specialized in building multiple websites in one Sitecore instance. In that process I often ask myself the question “why does this only work on Sitecore instance level and not on a website level?” Take for example the Alias functionality in Sitecore. Landing pages are often not created right under the “siteroot” of a Sitecore website.

In this example the division SoftwareHosting.nl of our company created a “Working Diner” event for their (potential) customers. Looking at the picture on the left you can see that the URL would be http://www.softwarehosting.nl/acties/working-diner/working-diner

That’s not quite a friendly URL to communicate. A nicer URL would be http://www.softwarehosting.nl/wd2013, which should not be so hard because Sitecore has Aliases!!!

Our content manager goes to the specific page and clicks on the alias button. A dialog appears and she can fill in the name of the alias and click the Add button. The alias is set to this page. She publishes the page and checks the alias. But the alias is not working. Keep in mind that she can’t start a Site publish because she doesn’t have right for that. 


Now, why is the alias not working??

The answer is very simple. When aliases are created they are stored under the folder /sitecore/system/Aliases . Our content manager doesn’t have access rights to the system folder and all childitems. It’s not visible in her content tree. So that’s problem number one.

The “working dinners” are a success so another division also want to do a “working diner” for their customers. But the aliases are stored in one place, so you can’t use the alias wd2013 anymore because this one is already taken by the other division. So that problem number two.

The main question is if it is possible to create aliases that works on a website level?

I think the answer is yes!

The following steps have to be taken:

  • Building a SiteAliasResolverRequest Processor
  • Create the necessary Sitecore Item
  • Create a New Dialog where you can set the Alias on Website level
  • Create Command to trigger the new Dialog

In the HttpRequestBegin pipeline there is a processor called AliasResolver. That Resolver will resolve the aliases on Sitecore level.

We have to build a SiteAliasResolverRequestProcessor that will resolves the aliases on website level. And that new processor has to be triggered after the Sitecore AliasResolver is triggered.

The code will look like this;


  public class SiteAliasResolver : HttpRequestProcessor
{
/// 
/// Runs the processor.
/// 
///
The arguments.
public override void Process(HttpRequestArgs args)
{
Assert.ArgumentNotNull(args, "args");
if (args.LocalPath.ToLowerInvariant().StartsWith("/sitecore")) return;
if (string.IsNullOrEmpty(args.LocalPath)) return;
if (Sitecore.Configuration.Settings.AliasesActive)
{
Database database = Context.Database;
if (database != null)
{
Profiler.StartOperation("Resolve Site alias.");
if (database.SiteAliases().Exists(args.LocalPath))
{
Item targetItem = database.SiteAliases().GetTargetItem(args.LocalPath);
Context.Item = targetItem;
}
Profiler.EndOperation();
}
else
{
Tracer.Warning("There is no context database in SiteAliasResover.");
}
}
else
{
Tracer.Warning("Aliases are not active.");
}
}
}

So that’s not that hard :)         

Where is the magic happening? The magic happens in this line:

 Item targetItem = database.SiteAliases().GetTargetItem(args.LocalPath);

The extension method .SiteAliases() contains the following code.



 public static class DatabaseExtentions
{
/// 
/// Sites the aliases exists.
/// 
///
The db.
/// 
public static SiteAliasResolver SiteAliases(this Database db)
{
return new SiteAliasResolver(db, Sitecore.Context.Site.RootPath);
}
}

The SiteAliasResolver class is very specific for our site structure implementation (SDP Best practice)

The footprint of the class must look like this. You will have to implement you own code that will return the website aliases.

public class SiteAliasResolver
{
private readonly Database _database;
private readonly string _settingsRootpath;
public SiteAliasResolver(Database database, string siteRootPath)
public Field this[string alias]
public bool Exists(string alias)
public ID GetTargetID(string alias)
public Item GetTargetItem(string alias)
}

In our SDP best practices all of our websites have the same structure. See the left picture .

Coworker is a multi-language and multi country example website. Looking at the picture you can see that only the website for the Netherlands is created. So the Item called Netherlands is called in the SDP the WebSiteRoot but very important is that the siterootpath which declared in <site> is pointing the Netherlands/Website.

In our situation the class SiteAliasResolver gets the WebsiteRoot item from the given siterootpath and then selects the folder aliases which is in the folder Sitedata. All items in that folder are SiteAliases. 

Now we have to make a SiteAlias template. Because we don’t want to use the existing Alias template. That’s because we want to fill in a specific datasource query.

And give it a nice icon

Now we have a SiteAliasResolverRequestProcessor and a SiteAlias in place. The content manager now can start adding aliases to the aliases folder. And publish them.

But there is one thing missing. The content editor also wants to add SitelAliases like she did with the standard sitecore aliases.

We do have to build our own dialog for that.

For a dialog in Sitecore we have to create a xaml and a code behind file. We put the xaml file in the folder structure as shown in the left picture.

The xaml file will look like this.
















And the code behind file
   public class AliasesForm : DialogForm
{
/// 
///     The list.
/// 
protected Listbox List;
/// 
protected Border ListHolder;
/// 
protected Edit NewAlias;
/// 
///     Handles a click on the Add button.
/// 
protected void Add_Click()
{
string value = NewAlias.Value;
if (value.Length == 0)
{
SheerResponse.Alert("Enter a value in the Add Input field.", new string[0]);
return;
}
var aliasInfo = new AliasInfo(value);
foreach (string ascendersAndName in aliasInfo.AscendersAndName)
{
if (Regex.IsMatch(ascendersAndName, Sitecore.Configuration.Settings.ItemNameValidation,
RegexOptions.ECMAScript))
{
if (ascendersAndName.Length <= Sitecore.Configuration.Settings.MaxItemNameLength)
{
continue;
}
SheerResponse.Alert("The name is too long.", new string[0]);
return;
}
SheerResponse.Alert("The name contains invalid characters.", new string[0]);
return;
}
Item itemFromQueryString = UIUtil.GetItemFromQueryString(Context.ContentDatabase);
Error.AssertItemFound(itemFromQueryString);
Item item = SitecoreHelper.GetAliasesFolderItem(itemFromQueryString);
ListItem listItem = CreateAlias(aliasInfo, itemFromQueryString, item);
if (listItem != null)
{
var strArrays = new[]
{
"scCreateAlias(", StringUtil.EscapeJavascriptString(StringUtil.EscapeQuote(listItem.ID)), ", ",
StringUtil.EscapeJavascriptString(StringUtil.EscapeQuote(listItem.Header)), ", ",
StringUtil.EscapeJavascriptString(StringUtil.EscapeQuote(listItem.Value)), ");"
};
SheerResponse.Eval(string.Concat(strArrays));
NewAlias.Value = string.Empty;
}
}
/// 
///     Creates the alias.
/// 
///
The alias info.
///
The target.
///
The root.
private ListItem CreateAlias(AliasInfo aliasInfo, Item target, Item root)
{
Assert.ArgumentNotNull(aliasInfo, "aliasInfo");
Assert.ArgumentNotNull(target, "target");
Assert.ArgumentNotNull(root, "root");
TemplateItem itemTemplate = root.Database.Templates[TemplateIds.AliasTemplateId];
Error.AssertTemplate(itemTemplate, "Alias");
foreach (string ascender in aliasInfo.Ascenders)
{
root = root.Children[ascender] ?? root.Add(ascender, itemTemplate);
}
if (root.Children[aliasInfo.Name] != null)
{
SheerResponse.Alert("An alias with this name already exists.", new string[0]);
return null;
}
Item itemAlias = root.Add(aliasInfo.Name, itemTemplate);
itemAlias.Editing.BeginEdit();
itemAlias["Linked Item"] = target.ID.ToString();
itemAlias.Editing.EndEdit();
var listItem = new ListItem();
List.Controls.Add(listItem);
listItem.ID = string.Concat("I", itemAlias.ID.ToShortID());
listItem.Header = GetAliasPath(itemAlias);
listItem.Value = itemAlias.ID.ToString();
return listItem;
}
/// 
///     Gets the alias path.
/// 
///

///     The alias.
///
/// 
///     The alias path.
/// 
private static string GetAliasPath(Item alias)
{
Assert.ArgumentNotNull(alias, "alias");
Item itemFromQueryString = UIUtil.GetItemFromQueryString(Context.ContentDatabase);
Item item = SitecoreHelper.GetAliasesFolderItem(itemFromQueryString);
string path = alias.Paths.GetPath(item.Paths.FullPath, "/", ItemPathType.Name);
if (path.StartsWith("/", StringComparison.InvariantCulture))
{
path = path.Substring(1);
}
return path;
}
/// 
///     Called when the item is deleted.
/// 
///

///     The sender.
///
///

///     The arguments.
///
private void ItemDeletedNotification(object sender, ItemDeletedEventArgs args)
{
Assert.ArgumentNotNull(sender, "sender");
Assert.ArgumentNotNull(args, "args");
foreach (string str in new ListString(ServerProperties["deleted"] as string ?? string.Empty))
{
SheerResponse.Eval(string.Concat("scRemoveAlias(",
StringUtil.EscapeJavascriptString(StringUtil.EscapeQuote(str)), ")"));
}
}
/// 
///     Raises the load event.
/// 
///

///     The  instance containing the event data.
///
protected override void OnLoad(EventArgs e)
{
Assert.CanRunApplication("Content Editor/Ribbons/Chunks/Page Urls");
Assert.ArgumentNotNull(e, "e");
base.OnLoad(e);
if (!Context.ClientPage.IsEvent)
{
RefreshList();
}
Context.Site.Notifications.ItemDeleted += ItemDeletedNotification;
}
/// 
///     Refreshes the list.
/// 
private void RefreshList()
{
Item itemFromQueryString = UIUtil.GetItemFromQueryString(Context.ContentDatabase);
Error.AssertItemFound(itemFromQueryString);
using (new SecurityDisabler())
{
List.Controls.Clear();
Item item = SitecoreHelper.GetAliasesFolderItem(itemFromQueryString);
Item[] descendants = item.Axes.GetDescendants();
for (int i = 0; i < descendants.Length; i++)
{
Item item1 = descendants[i];
if (string.IsNullOrWhiteSpace(item1["linked item"])) continue;
var id = new ID(item1["linked item"]);
if (id == itemFromQueryString.ID)
{
var listItem = new ListItem();
List.Controls.Add(listItem);
listItem.ID = string.Concat("I", item1.ID.ToShortID());
listItem.Header = GetAliasPath(item1);
listItem.Value = item1.ID.ToString();
}
}
}
}
/// 
///     Handles a click on the Remove button.
/// 
protected void Remove_Click()
{
Item itemFromQueryString = UIUtil.GetItemFromQueryString(Context.ContentDatabase);
Error.AssertItemFound(itemFromQueryString);
var arrayLists = new ArrayList();
ListItem[] selected = List.Selected;
for (int i = 0; i < selected.Length; i++)
{
string d = selected[i].ID;
d = ShortID.Decode(StringUtil.Mid(d, 1));
Item item = itemFromQueryString.Database.GetItem(d);
if (item != null)
{
arrayLists.Add(item);
}
}
if (arrayLists.Count == 0)
{
SheerResponse.Alert("Select an alias from the list.", new string[0]);
return;
}
Items.Delete(arrayLists.ToArray(typeof (Item)) as Item[]);
var listStrings = new ListString();
foreach (Item arrayList in arrayLists)
{
listStrings.Add(string.Concat("I", arrayList.ID.ToShortID()));
var strArrays = new[] {AuditFormatter.FormatItem(arrayList)};
Log.Audit(this, "Remove alias: {0}", strArrays);
}
ServerProperties["deleted"] = listStrings.ToString();
}
/// 
/// Alias Info
/// 
private class AliasInfo
{
private readonly ListString _path;
/// 
/// Initializes a new instance of the  class.
/// 
///
The value.
public AliasInfo(string value)
{
Assert.ArgumentNotNullOrEmpty(value, "value");
value = StringUtil.RemovePrefix("/", value);
value = StringUtil.RemovePostfix("/", value);
_path = new ListString(value, '/');
}
/// 
/// Gets the ascenders.
/// 
/// 
/// The ascenders.
/// 
public IEnumerable Ascenders
{
get
{
if (_path.Count > 1)
{
for (int i = 0; i < _path.Count - 1; i++)
{
yield return _path[i];
}
}
}
}
/// 
/// Gets the name of the ascenders and.
/// 
/// 
/// The name of the ascenders and.
/// 
public IEnumerable AscendersAndName
{
get { return _path.Items; }
}
/// 
/// Gets the name.
/// 
/// 
/// The name.
/// 
public string Name
{
get { return _path[_path.Count - 1]; }
}
}
}

Ok, with that in place the only thing we have to do is to create a command that will trigger the Dialog.

The execute method of the command is straight forward.



   public override void Execute(CommandContext context)
{
Assert.ArgumentNotNull(context, "context");
if ((int)context.Items.Length != 1)
{
return;
}
Item items = context.Items[0];
UrlString urlString = new UrlString(UIUtil.GetUri("control:AliasesOnSitelevel"));
urlString.Append("id", items.ID.ToString());
urlString.Append("la", items.Language.ToString());
urlString.Append("vs", items.Version.ToString());
SheerResponse.ShowModalDialog(urlString.ToString());
}

The method QueryState in often forgotten in this class. With this method you can set the state of the command button. There are four states the command button can get: Disabled, Down, Enabled and Hidden. For our button we will only be using the Disabled and Enabled state.

The QueryState method will look like this.



public override CommandState QueryState(CommandContext context)
{
Assert.ArgumentNotNull(context, "context");
if(SitecoreHelper.ContentDatabase.Name.Equals("core",StringComparison.OrdinalIgnoreCase))
{
return CommandState.Disabled;
}
if (context.Items.Length != 1)
{
return CommandState.Disabled;
}
Item items = context.Items[0];
if (!HasField(items, FieldIDs.LayoutField))
{
return CommandState.Hidden;
}
if (!items.IsInstanceOfTemplate(TemplateIds.BaseWebpageTemplateId.ToGuid()))
{
return CommandState.Disabled;
}
Item websiteRoot = SitecoreHelper.WebsiteRootItemFromItem(items);
Item item = SitecoreHelper.GetAliasesFolderItem(websiteRoot);
if ( item == null ||
!item.Access.CanRead() ||
!item.Access.CanWrite() ||
!item.Access.CanCreate()
)
{
return CommandState.Disabled;
}
return base.QueryState(context);
}

In short the button must only be enabled when the selected item is an instance of the BaseWebpageTemplate. So the content manager can only create aliases on items that are webpages and sits beneath the siterootpath. Also if the content manager doesn’t have the appropriate right for the aliases folder the button must be disabled.

Now we have a great new functionality. And best of all Happy Content Managers !!

If you find this blog useful, please let us know. We got plenty more nice things to share.



Meer achtergrond nodig? Neem contact met ons op

Deel deze pagina