LibrarySites.Banner

Repost: Use the Sitecore Rules Engine to Control Item Names

This blog post describes how you can use the ItemNamingRules Sitecore Shared Source project to apply the rules engine to control item names using the Sitecore ASP.NET CMS.

This is a repost of http://sitecorejohn.spaces.live.com/blog/cns!960125F1D4A59952!444.entry.

You can use the rules engine to control item names. For example, you may want to enforce character limits in the names of items that correspond to URLs. For more information about the rules engine, see the Rules Engine Cookbook on the Sitecore Developer Network (SDN). For instructions to use the solution described in this post, see the ItemNamingRules documentation. You may also want to read some of my previous posts, including Sitecore Rules Engine and Conditional RenderingIntercepting Item Updates with Sitecore, and Sitecore Shared Source NewsMover Categorizes News by Date

Sitecore supports at least five techniques to control item names:

  1. You can use use the InvalidItemNameChars, ItemNameValidation, and MaxItemNameLength settings in web.config to control all item names. See the comment above each setting in web.config for further details. The disadvantage of these settings in this context is that they apply to all items, when you might want rules only for the names of certain items. For example, you might want to allow spaces and other special characters in any item that doesn’t correspond to a URL, or have special naming rules for media.
  2. You can use events to intercept actions that name items. One advantage of events is that you can trap all types of user actions and API events that rename items with a single handler. One disadvantage of events in this context is that you specify handlers and parameters in web.config rather than through a user interface. Another disadvantage of events in this context is that it can become cumbersome to configure different rules for different branches of items in the content tree.
  3. You can use pipelines to intercept user actions that name items. One advantage of pipelines is that you can interact with the user. Some disadvantages of pipelines in this context are that multiple pipelines can rename and item, but only user actions invoke certain pipelines, and you often have to . Some API calls that name items do not invoke any pipeline.
  4. You can use validation, and provide different validation actions for each of the types of rules a name could violate (too long, invalid characters, etc.). The problem with validation in this context is that the user has to navigate to the item and take some actions; it might be nice to fully automate the solution.
  5. You can use the rules engine to control item names as described in this post. One advantage of the rules engine is a convenient user interface to define parameters, such as the items to which the naming logic applies.

 

You can continue to use settings, events, pipelines, and validation to control item names. But with 6.1, you can use the rules engine as well.

A rule consists of one or more conditions and one or more actions. When Sitecore evaluates a rule, if the conditions specified in that rule evaluate to true, then Sitecore invokes the actions specified in that rule.

For the condition part of this rule, users might want to apply the rule to one or more specific items, to all items based on a data template, to an item and its descendants, to the descendants of an item, to items matching a query, or to some other collection of items. I assume that the default conditions that ship with the product meet the most common requirements. Two conditions that might be helpful when determining whether or not to rename an item: whether the item has layout details for the default device, and whether the item has layout details for any device.

Regarding the action part of the rule, there would be numerous parameters to specify for a single action. The solution should not force the user to specify parameters they don’t use. But because actions are not aware of each other, the user might have to specify the same parameters to multiple actions. More importantly, the user has to understand the impact of ordering actions. For example, it doesn’t make sense to ensure a minimum name length before removing invalid characters.

I thought about merging certain actions and hard-coding certain parameters, but I generally like a flexible approach. Then I started prototyping, and realized that the flexible approach was more work for both me and any user. So I merged some of the actions (the strong words indicate parameters):

  • Replace characters that do not match the regular expression MatchPattern with ReplaceWith, replace sequences of ReplaceWith with a single instance of ReplaceWith, and remove leading and trailing instances of ReplaceWith.
  • Ensure a minimum name length by appending characters from DefaultName.
  • Ensure that the name is unique and does not exceed MaxLength characters.
  • Lowercase.

It’s honestly a coincidence that this simplification eliminates all of the duplicate parameters that I would otherwise have to pass to additional actions. I also sorted the actions so that the user interface displays them in the order in which the user should add them to the rule.

I started out with a base class for actions that rename items.

Then I created classes for each action

Before entering any parameters, my rule looks like this:

Screen shot of naming rule with no values for parameters

And after entering parameters:

Screen shot of naming rule with no values for parameters

Now, when I save any item that has layout details for any device, whether I save that item through the UI or through an API call, Sitecore replaces invalid characters with underscores, merges sequences of underscores into a single underscore, trims underscores from the beginning and the end of the item name, appends to the item name to ensure at least seven characters, appends a timestamp to the item name if required to make it unique among its siblings, and ensures that the item name does not exceed 35 characters.

Warning: Like almost any customization, this could have dangerous repercussions, especially if configured incorrectly (for example, by renaming any item in the wrong branch, or in the Core database). Additionally, I didn’t significantly test this before posting, and there are definitely still some issues to think about. For example, you probably don’t want to rename the home item, you don’t want these rules to apply to the __Standard values of data templates (even though they may contain layout details), etc. But enough people ask about this topic that I thought it was time to describe this approach. After some refinement, I will try to make this a shared source project.

  • Hi John,  After reading your article, this handy trick seemed very easy to implement, but I'm having some problems. The new rule I created using the provided actions in ItemNamingRules don't seem to take effect when I insert a new item into sitecore/content/home. Do you know what am doing wrong?  Thank you in advance, Reka  

  • @Reka: I think the most likely cause is probably a typo in the type signature. I suggest checking the Sitecore log from around the time that you create the item - you might see something indicating that .NET cannot instantiate a designated type. If that doesn't shed any light on the issue, I would describe the issue on the Shared Source forum on SDN:  sdn.sitecore.net/.../ShowForum.aspx

  • I was facing a similar issue where the renaming action was not being implemented. I was able to resolve this by changing the following conditional statement in RenamingAction.cs from...  if ((!String.IsNullOrEmpty(site.StartItem)) && String.Compare(site.StartItem, item.Paths.FullPath, true) != 0) {     return; }  ...to...  if ((!String.IsNullOrEmpty(site.StartItem)) && String.Compare(site.StartItem, item.Paths.FullPath, true) == 0) {     return; }  I believe it is only when the item is the same as the site's start item (i.e., String.Compare(...) == 0) do you want to hit the return statement.

  • Hi Valerie and John,   Yes, this solved my problem too! But I was too embarrassed to post it here. :) Thank you for sharing and the support!    

  • Sorry, I'm not sure how that defect got through - I tested this and even demonstrated it at dreamcore. I'm concerned that I might have checked in old code, or made some stupid changes right before checking in, so there may be other issues with the solution.  That specific code was intented to avoid renaming the home item for any managed site because the web.config file references those items by name and they don't appear in URLs. I changed it to the following and performed more lightweight testing with 6.4.1 101221, which seemed to indicate that this works:  if (String.Compare(site.RootPath + site.StartItem, item.Paths.FullPath, true) == 0)  I realize it seems strange to put conditional logic in an action, but I didn't want to require the user to remember to select another condition to define "if the item has layout details AND it's not the home item for any managed site".  I have checked updated code into the Sitecore Shared Source repository. I also updated the documentation - removed HasLayoutDetailsForSpecificDevice (that code wasn't in the repository anyway, and I've apparently deleted my local copy), moved the condition definition items from /sitecore/system/Settings/Rules/Common/Conditions to /sitecore/system/Settings/Rules/Common/Conditions/Fields (maybe 6.4.1 added this structure), updated the text for HasLayoutDetailsForDefaultDevice (it was inadvertently copied from HasLayoutDetailsForAnyDevice), and updated the regex example, which seemed to be invalid, resulting in an infinite loop.  Obviously, use anything I write at your own risk...again, my apologies.

  • John, Thank you for this article, it has added another tool to my ever growing bag of Sitecore techniques.   Something else I thought worthy of sharing. I've updated my local ReplaceInvalidCharacters class to accept a null value for ReplaceWith.    I just needed this.ReplaceWith = this.ReplaceWith ?? string.Empty; instead of the assertion and wrapped all of the while loops in if (this.ReplaceWith!=string.Empty)  This works perfect for me.  

  • In some cases, the Sitecore.Data.Items.ItemUtil.ProposeValidItemName() method may be of use.

  • Is that possible to show Content Name/Path of content in Validation result dialog window?