I want to prevent user from creating item with duplicate name. I've referenced Sitecore Climber's Prevent Duplicates approach with slight modification to prevent item duplication due to rename, copy, and move operation.
The code is like this:
using System;
using System.Linq;
using System.Data;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Events;
using Sitecore.SecurityModel;
using Sitecore.Data.Events;
namespace MyProject.Website.Events
{
/// <summary>
/// Prevents item duplication or at least alert the user.
/// https://sitecoreclimber.wordpress.com/2013/03/05/prevent-duplicates-items/
///
/// Uses HttpContext for denying event golf.
/// </summary>
public class PreventDuplicateItemName
{
#region Inner Class
private class PreventDuplicateParameter
{
public SitecoreEventArgs OriginalEventArguments { get; set; }
public Item ParentItem { get; set; }
public ID CurrentItemId { get; set; }
public string CurrentItemName { get; set; }
}
#endregion
#region Public Methods
/// <summary>
/// Alert and cancel item creation if duplicate is found.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
public void OnItemCreating(object sender, EventArgs args)
{
Func<SitecoreEventArgs, PreventDuplicateParameter> extractData = (SitecoreEventArgs evtArgs) =>
{
PreventDuplicateParameter param = new PreventDuplicateParameter();
param.OriginalEventArguments = evtArgs;
ItemCreatingEventArgs evtCreating = Event.ExtractParameter<ItemCreatingEventArgs>(evtArgs, 0);
param.ParentItem = evtCreating.Parent;
param.CurrentItemId = evtCreating.ItemId;
param.CurrentItemName = evtCreating.ItemName;
return param;
};
_HandleDuplicationCheck(args,
"Name \"{0}\" is already in use. Please use different name first.",
extractData);
}
/// <summary>
/// Alert and abort copying the item if duplicate is found.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
public void OnItemCopying(object sender, EventArgs args)
{
Func<SitecoreEventArgs, PreventDuplicateParameter> extractData = (SitecoreEventArgs evtArgs) =>
{
PreventDuplicateParameter param = _ExtractEventArgumentBase(evtArgs);
param.ParentItem = Event.ExtractParameter<Item>(evtArgs, 1);
param.CurrentItemName = Event.ExtractParameter<string>(evtArgs, 2);
param.CurrentItemId = Event.ExtractParameter<ID>(evtArgs, 3);
return param;
};
_HandleDuplicationCheck(args,
"Name \"{0}\" is already in use in the target path. Please make sure the item uses different name first.",
extractData);
}
/// <summary>
/// Alert and abort moving the item if duplicate is found.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
public void OnItemMoving(object sender, EventArgs args)
{
Func<SitecoreEventArgs, PreventDuplicateParameter> extractData = (SitecoreEventArgs evtArgs) =>
{
PreventDuplicateParameter param = _ExtractEventArgumentBase(evtArgs);
param.ParentItem = _GetItem(Event.ExtractParameter<ID>(evtArgs, 2));
return param;
};
_HandleDuplicationCheck(args,
"Name \"{0}\" is already in use in the target path. Please make sure the item uses different name first.",
extractData);
}
/// <summary>
/// Alert and abort renaming the item if duplicate is found.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
public void OnItemRenamed(object sender, EventArgs args)
{
//Callback for renaming the item back to previous name.
//OnItemSaving runs before OnItemRenamed and the <see csref="SitecoreEventArgs.Result.Cancel"> is not working as expected.
Action<PreventDuplicateParameter> callback = (PreventDuplicateParameter preventDuplicateParameter) =>
{
using(new SecurityDisabler())
{
Item item = _GetItem(preventDuplicateParameter.CurrentItemId);
if ((item != null) && _IsSafeEdit(item.ID))
{
string originalName = Event.ExtractParameter<string>(preventDuplicateParameter.OriginalEventArguments, 1);
item.Editing.BeginEdit();
item.Name = originalName;
item.Editing.EndEdit();
}
}
};
_HandleDuplicationCheck(args,
"Name \"{0}\" is already in use. Please rename the item with different name.",
_ExtractEventArgumentBase,
callback);
}
#endregion
#region Private Methods
private bool _IsBackend()
{
return ((Sitecore.Context.Site != null) && Sitecore.Context.Site.Name == "shell");
}
private string _RemoveDash(string original)
{
return original.Replace(' ', '-').ToLower();
}
private bool _IsDuplicate(Item targetParent, ID currentItemId, string currentItemName)
{
string newName = _RemoveDash(currentItemName);
return targetParent.GetChildren().Any(x => x.Name.Equals(newName) && x.ID != currentItemId);
}
private Item _GetItem(ID itemId)
{
//Operates on the assumption it works on the backend (CM).
Database master = Database.GetDatabase("master");
if(master != null)
{
Item item = master.GetItem(itemId);
return item;
}
return null;
}
private bool _IsSafeEdit(ID itemId)
{
//HACK: need to find better approach to avoid rename bouncing back and forth.
string key = "_PreventDuplicateItemName-ItemId";
if ((Sitecore.Context.Items != null))
{
if((Sitecore.Context.Items[key] != null))
{
ID prevId = Sitecore.Context.Items[key] as ID;
if(!prevId.IsNull && (prevId == itemId))
{
return false;
}
}
Sitecore.Context.Items[key] = itemId;
}
return true;
}
private PreventDuplicateParameter _ExtractEventArgumentBase(SitecoreEventArgs evtArgs)
{
PreventDuplicateParameter param = new PreventDuplicateParameter();
param.OriginalEventArguments = evtArgs;
Item currentItem = Event.ExtractParameter<Item>(evtArgs, 0);
param.ParentItem = currentItem.Parent;
param.CurrentItemId = currentItem.ID;
param.CurrentItemName = currentItem.Name;
return param;
}
/// <summary>
/// The callback will be called if the duplicate checks fires.
/// </summary>
/// <param name="args"></param>
/// <param name="message"></param>
/// <param name="delegateGetParameters"></param>
/// <param name="callback"></param>
private void _HandleDuplicationCheck(EventArgs args, string message, Func<SitecoreEventArgs, PreventDuplicateParameter> delegateGetParameters, Action<PreventDuplicateParameter> callback = null)
{
if((args != null) && _IsBackend())
{
using (new SecurityDisabler())
{
SitecoreEventArgs evtArgs = args as SitecoreEventArgs;
if(evtArgs != null)
{
try
{
PreventDuplicateParameter data = delegateGetParameters(evtArgs);
if (data != null)
{
if (_IsDuplicate(data.ParentItem, data.CurrentItemId, data.CurrentItemName))
{
try
{
//HACK: (Sitecore.Context.ClientPage != null) && (Sitecore.Context.ClientPage.ClientResponse != null)
//fails with null exception
evtArgs.Result.Cancel = true;
Sitecore.Context.ClientPage.ClientResponse.Alert(string.Format(message, _RemoveDash(data.CurrentItemName)));
callback?.Invoke(data);
}
catch (Exception ex)
{
Sitecore.Diagnostics.Log.Error(string.Format("Error on preventing duplicates of {0}.",
data.CurrentItemName),
ex,
this);
}
}
}
}
catch(Exception ex)
{
Sitecore.Diagnostics.Log.Error(string.Format("Error on fetching event data: {0}", evtArgs), ex, this);
}
}
}
}
}
#endregion
}
}
I've registered the code above through configuration and tested that it runs properly for most Sitecore Items.
<?xaml version="1.0"?>
<configuration xmlns:patch="http:www.sitecore.net/xmlconfig/">
<sitecore>
<events>
<event name="item:creating">
<handler type="MyProject.Website.Events.PreventDuplicateItemName, MyProject.Website" method="OnItemCreating"/>
</event>
<event name="item:copying">
<handler type="MyProject.Website.Events.PreventDuplicateItemName, MyProject.Website" method="OnItemCopying"/>
</event>
<event name="item:moving">
<handler type="MyProject.Website.Events.PreventDuplicateItemName, MyProject.Website" method="OnItemMoving"/>
</event>
<event name="item:renamed">
<handler type="MyProject.Website.Events.PreventDuplicateItemName, MyProject.Website" method="OnItemRenamed"/>
</event>
</events>
</sitecore>
</configuration>
Surprisingly, it does not work with Sitecore folder
s. The event never fired.
I've checked on Sitecore Documentation and never see it states folder
as different entity compared to common Sitecore items.
Am i missing something? How to prevent folders from being a duplicates?
<setting name="AllowDuplicateItemNamesOnSameLevel" value="false" />