4

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 folders. 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?

  • Can you try using item:added and seeing if it captures it? That is supposed to fire when an item is added through the UI. Assuming this is a manual add and not being called in code. – RReilley Mar 18 '18 at 1:36
  • 4
    If you are on 8.1 update 2 or later then you can change this setting without any custom implementation: <setting name="AllowDuplicateItemNamesOnSameLevel" value="false" /> – Tamás Tárnok Mar 18 '18 at 11:50
1

Regarding this stupid question of mine i found out that my code is glitchy on comparing the Sitecore Item name.

      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);
      }

The code above did not take into account the fact that Sitecore Media Library and Folder seems to disregard on lowercasing the item name and replacing spaces with dash.

Regarding Tamás Tárnok suggestion to use the configuration below is working as. However, it seems again, it regards upper-case and lower-case as different things if its a Folder or in Media Library. Not to mention it also did not offer better error message when uploading things to Media Library (it will simply says an exception occurs). It also still allows duplicates when we Duplicate item (Right click -> Duplicate Item). I also found out that the Sitecore built-in restriction throws an exception on item:added event - sorry but i can't remember how to replicate this when fiddling with edge cases.

  <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
      <sitecore>
          <settings>
              <setting name="AllowDuplicateItemNamesOnSameLevel" value="false" patch:instead="*[@name='AllowDuplicateItemNamesOnSameLevel']">
              </setting>
          </settings>
      </sitecore>
  </configuration>

AllowDuplicateItemNamesOnSameLevel Dealing with File Upload

For now, i'm stick on the custom implementation. Its kind of weird that it behaves differently although Folder and any other items are Item. Perhaps me just lacking understanding on Sitecore.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.