New John West Sitecore Blog on Sitecore Community Hub

I’ve finally started the John West Sitecore Blog on the Sitecore Community hub. At the moment, the first and only post is just an introduction, but I will post more as time permits.

John West Sitecore Blog

http://www.sitecore.net/Community/Technical-Blogs/John-West-Sitecore-Blog.aspx

Or for short:

http://bit.ly/aOlb42

 

Posted in Obsolete | 2 Comments

LearnSitecore Podcast: Ten Things You Didn’t Know about John West (Dreamcore 2010)

I am happy to announce that Ten Things You Didn’t Know about John West (Dreamcore 2010) is the first podcast to appear on LearnSitecore, a Web site devoted to educating Sitecore developers. At dreamcore 2010 Europe, the founders of the LearnSitecore, Jens Mikkelsen and Jimmi Lyhne Andersen, interviewed me for this podcast. Among other things, we talked about my responsibilities at Sitecore, what I do on a daily basis, trends in the Internet industry, and the topics from my presentation at dreamcore 2010.

Posted in Obsolete | 1 Comment

One More Reason to Own Your Web Analytics Data

Several months ago I blogged that I was leaving the Microsoft live.com platform because it feels like abandonware that largely enables spam. Now Microsoft appears to have disabled my access to Web statistics for my live.com site. While the browser-based statistics interface was terrible, I used these statistics to determine what search terms brought visitors to my blog, which I used to refine posts to address the most common issues. Previously, after I logged in, something like “Statistics” appeared on this Options menu:

I hope this is just a technical glitch and Microsoft will restore this functionality. Whether or not Microsoft intended to remove this feature, the experience demonstrates one reason you should always manage Web statistics data internally using a visitor experience analytics system such as Sitecore OMS rather than using an external service for Web analytics. Additionally, OMS provides features far beyond statistics.

Luckily, the Sitecore Community Hub is almost ready to host my blog.

Update 14.June.2010: I also see this more frequently now:

Posted in Obsolete | 2 Comments

Maximize Sitecore Content Editor Performance

Posted in Obsolete | Leave a comment

Free Ways to Make Your Computer Faster and More Stable: Disable Software

This post describes techniques that you can apply to improve the performance and stability of your computer by reducing the number of active programs.

You can use the Autoruns utility from Sysinternals to reduce the amount of software that runs when you boot or log in to Windows. You can download Autoruns individually or you can download the entire Sysinternals Suite of utilities, which includes Autoruns. These programs come as zip files with no installer. Installation instructions may be slightly different on editions of Windows other than 64-bit Windows 7, but for me:

  1. Create a subdirectory to contain the utilities, such as C:\Program Files (x86)\Sysinternals.
  2. Extract the Sysinternals zip file to that subdirectory.
  3. In that subdirectory, select autoruns.exe, and create a shortcut to that item on the desktop or the Windows Start menu. Name the shortcut Autoruns.

Consider creating another shortcut for Process Explorer (procexp.exe), which is an excellent replacement for Windows Task Manager.

Launch Autoruns, but think carefully before disabling any options. You might want to capture a screen shot of the default configuration so that you can see how things were and re-enable items you later determine some software to require. To capture a screen shot of the Autoruns window, press ALT-PRTSCN, paste that image into another application, and then save that file.

Most of the items that you should disable will appear towards the top of the Autoruns window under one of the following categories:

  • HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
  • HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Run
  • C:\Users\<profile>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup (or equivalent)
  • HKCU\Software\Microsoft\Windows\CurrentVersion\Run           

Many of these programs don’t seem to do anything significant, but consume resources, including space in the notification area, and introduce variables when you are trying to troubleshooting other issues. I like to disable anything that I don’t use every time I use the computer. It’s generally easy to start these applications when I need them:

  • The Windows Sidebar.
  • Citrix GoToMeeting.
  • Miicrosoft Gotomeeting, Groove, and OneNote.
  • Any instant messaging software.
  • Any software for hardware such as a fingerprint reader, camera, phone, scanner, printer, modem, or fax.
  • Adobe anything.
  • Google anything.
  • Roxio anything.
  • CyberLink anything.
  • Zune anything.
  • Skype anything,
  • Apple anything.

If you know you will never need a piece of software, you can use Autoruns to delete that entry. Otherwise just clear the checkbox. Here’s how Autoruns currently appears on my system:

 

If the name of the entry and its description don’t help you determine whether you can disable something, look at Publisher and Image Path. For example, I had no idea what EEventManager Application was, but the publisher SEIKO EPSON definitely makes printers, so I assumed I could disable it. You can also search the Internet for specific programs, such as eeventmanager.exe, to try to determine what they do and hence whether you can disable them. Be careful, as many search hits for process names will take you to Web sites that encourage you to download additional software that you don’t need.

If you see the following message when you select or clear a checkbox:

You can click Run as Administrator to close Autoruns and launch the program again with the administrative rights required to change these settings. Maybe this helps you avoid unintended changes, but I like to avoid the extra moment it takes for Autoruns to scan the system again. Next time you launch Autoruns, you can SHFT-CTRL-Click the Autoruns shortcut to run as administrator once, or you can:

  1. Right-click that shortcut and select Properties.
  2. On the Shortcut tab, click Advanced, and then select Run as administrator.

If you want, you can occasionally use Autoruns to enable software update managers, reboot, and then use Autoruns to disable them again. I think most programs that install their own updates check for updates when you launch the program anyway, so at least some of these are redundant.

This is a good time to remember to disable Internet Explorer plug-ins as well, and to hide any browser toolbars that you don’t use. To disable add-ons in Internet Explorer, click the Tools menu or press ALT-T, and then click Manage Add-Ons. I disable almost everything, leaving only HttpWatch. To hide browser toolbars, click the View menu or press ALT-V, then click Toolbars, and then disable individual toolbars. This reminds me that you might also want to hide the ESET NOD32 and Citrix GoToMeeting toolbars in Outlook. Click the View menu, then click Toolbars, and then disable individual toolbars.

You can disable specific Windows services as well, but you should be very careful not to disable important services. The next time I build a machine, I will try to list the services that I disable. I generally set services to Disabled instead of Manual, so that I can more easily identify services that I have disabled, and so that I find out when some software tries to use a service I disabled. To access services, type services.msc into the Start menu or the Run prompt. I seem to have disabled at least:

  • Internet Connection Sharing (ICS)
  • SQL Active Directory Helper Service
  • SQL Server Agent
  • Visual Studio Remote Debugger

It’s probably a good idea to review these issues at least a few times each year, and especially after you get a new machine or install Windows. Please add your suggestions for software to disable below, or reasons not to disable what I’ve suggested above.

Posted in Obsolete | 1 Comment

Publish Referenced Media When You Publish Sitecore Content

For the current edition of this blog post, see:

http://www.sitecore.net/Community/Technical-Blogs/John-West-Sitecore-Blog/Posts/2010/11/Publish-Referenced-Media-When-You-Publish-Sitecore-Content.aspx

I developed a prototype for a workflow action for use with the Sitecore ASP.NET CMS to publish referenced medias as a content item passes through workflow. This prototype also publishes any ancestors of each media item that do not exist in the publishing target database. You can use this workflow action to automatically publish media items and media folders before publishing content that depends on those items.

You can replace the default Auto Publish workflow action with this action. Additionally, you can use this workflow action earlier in the workflow, from a non-final workflow state, to publish only the media and not the content item.

The Publish Media and Publish Content actions in the following workflow definition both use the solution described in this post.

From the Draft state, the user submits a content item to the Media Approval state. From the Media Approval state, the media reviewer chooses the Approve Media command, which contains the Publish Media action that publishes referenced media, and transitions to the Content Approval state. From the Content Approval state, the content reviewer (who may also be the media reviewer) chooses the Approve Content commend, which transitions to the Approved state. The Approved state contains the Publish Content action that publishes the item and its descendants. The Validation Action under the Approve Content command prevents the content reviewer from publishing invalid content. Content can be invalid for a number of reasons, such as if you Validate Media Publishing Status with Sitecore and the content reviewer added a new image after media approval.

In the Parameters field in the Data section of the Publish Media workflow action definition item, enter:

IncludeSelf=1&Deep=1&IncludeMedia=1

The IncludeSelf parameter instructs the action to publish the item in workflow. The Deep parameter instructs the action to perform deep publishing. The IncludeMedia parameter instructs the action to publish related media. Alternatively, you can create a data template that defines three Checkbox fields named after these three parameters, use that data template for the workflow action definition item, and use those fields instead of the Parameters field.

In the Parameters field in the Data section of the Publish Content workflow action definition item, enter:

IncludeMedia=1

The IncludeSelf and Deep parameters default to false.

I will post the code at the Workflow with Media Items thread on the Sitecore Developer Network forums.

The Sitecore.Sharedsource.Workflow.Actions.Publish class that you reference in the Type field of workflow action definition items represents the workflow action. You can use this action to replace the default Sitecore.Workflows.Simple.PublishAction. Most of the logic in this class simply retrieves parameters. The Process() method passes those parameters to the Publish() method of an instance of Sitecore.Publishing.PublishHelper.

For each publishing target, the Publish() method of Sitecore.Publishing.PublishHelper creates a publishing queue containing the media referenced by the item in workflow that do not exist or differ from their correspondents in the publishing target database and any of their ancestors missing from that publishing target, and then publishes that queue.

The Sitecore.Publishing.PublishingTarget class represents a publishing target.

Even accounting for publishing targets, publishing related media is relatively straightforward if media will not have publishing restrictions, you don’t version/workflow media, early publication of media and media folders is unlikely to have a negative impact, and media items don’t have descendants. While this solution addresses the complexity of publishing targets, it does not address versioning, translation, publishing restrictions, and other factors. Nor does it address errors that can occur during publication.

Publishing related content would be more challenging than publishing related media. Referenced content may have publishing restrictions at the item and/or language/version level, including workflow state. Referenced content may contains references to additional media and content, including circular references. Still, it might be possible to publish related content, presentation components, data template definition components, or other relevant items.

At some point I tried to address some of these concerns with the PublishingSpider Sitecore shared source project, but I think it got too complicated and I didn’t maintain it. Maybe that project has some ideas or even code that someone reading this blog post could use.

Posted in Obsolete | 1 Comment

Validate Media Publishing Status with Sitecore

For the current edition of this blog post, see:

http://www.sitecore.net/Community/Technical-Blogs/John-West-Sitecore-Blog/Posts/2010/11/Validate-Media-Publishing-Status-with-Sitecore.aspx

You can can use the approach described in this document to validate whether the Sitecore ASP.NET CMS has published the media referenced by an item. You can use validation with workflow to require the user to publish related media before publishing an item that references that media.

You can use this validator in conjunction with a Content Editor Warning to Display Publishing Status.  You can get more information about validation from my post about how to Validate a Sitecore Checklist, Multilist, Treelist, or TreelistEx Field, which contains links to pages, posts, and threads containing much more information about validation, including the Client Configuration Cookbook.

Here is my prototype for the validator:

namespace Sitecore.Sharedsource.Data.Validators.ItemValidators
{
  using System;
  using System.Collections.Generic;
  using System.Runtime.Serialization;
  using Sitecore.Configuration;
  using Sitecore.Data;
  using Sitecore.Data.Items;
  using Sitecore.Data.Validators;
  using Sitecore.Diagnostics;
  using Sitecore.Links;

  [Serializable]
  public class MediaPublishingStatus : StandardValidator
  {
    public MediaPublishingStatus() : base()
    {
    }

    public MediaPublishingStatus(SerializationInfo info, StreamingContext context) : base(info, context)
    {
    }

    public override string Name
    {
      get
      {
        return this.GetType().ToString();
      }
    }

    public static bool IsPublished(Item item, Database targetDatbase)
    {
      Assert.ArgumentNotNull(item, "item");
      Assert.ArgumentNotNull(targetDatbase, "targetDatbase");
      Item targetItem = targetDatbase.GetItem(item.ID, item.Language, item.Version);
      return targetItem != null
             && item.Version.Number == targetItem.Version.Number
             && item.Statistics.Revision == targetItem.Statistics.Revision;
    }

    public static Database[] GetPublishingTargets(Item item)
    {
      Assert.ArgumentNotNull(item, "item");
      Item pubTargets = item.Database.GetItem("/sitecore/system/publishing targets");
      Assert.IsNotNull(pubTargets, "publishing targets");
      List<Database> databases = new List<Database>();

      foreach (Item target in pubTargets.Children)
      {
        if (PublishingTargetApplies(item, target.ID))
        {
          string name = target[FieldIDs.PublishingTargetDatabase];
          Assert.IsNotNull(name, target.Paths.FullPath);
          Database db = Factory.GetDatabase(name);
          Assert.IsNotNull(db, name);
          databases.Add(db);
        }
      }

      return databases.ToArray();
    }

    public static bool PublishingTargetApplies(Item item, ID publishingTarget)
    {
      while (item != null)
      {
        string restricted = item[FieldIDs.PublishingTargets];

        if (!(String.IsNullOrEmpty(restricted) || restricted.Contains(item.ID.ToString())))
        {
          return false;
        }

        item = item.Parent;
      }

      return true;
    }

    protected override ValidatorResult Evaluate()
    {
      Item item = this.GetItem();
      Assert.IsNotNull(item, "GetItem");
      Database[] pubTargets = GetPublishingTargets(item);

      foreach (ItemLink link in item.Links.GetAllLinks())
      {
        if (link.SourceFieldID == Sitecore.Data.ID.Null)
        {
          continue;
        }

        Sitecore.Data.Fields.Field field = item.Fields[link.SourceFieldID];
        Assert.IsNotNull(field, link.SourceFieldID.ToString());
        Item media = link.GetTargetItem();

        if (String.IsNullOrEmpty(field.Value) || media == null || !media.Paths.IsMediaItem)
        {
          continue;
        }

        if (field.Value.Contains(media.ID.ToString()) || field.Value.Contains(ShortID.Encode(media.ID)))
        {
          foreach (Database db in pubTargets)
          {
            if (!IsPublished(media, db))
            {
              this.Text = String.Format(
                "The field {0} references the media item {1}, which is not current in the {2} database.",
                field.Name,
                media.Paths.FullPath,
                db.Name);
              return GetFailedResult(ValidatorResult.Error);
            }
          }
        }
      }

      return ValidatorResult.Valid;
    }

    protected override ValidatorResult GetMaxValidatorResult()
    {
      return ValidatorResult.Error;
    }
  }
}

While iterating the publishing target databases, this validator iterates each link from the current item to any other item. If the link exists in the selected version of the item, and the publishing target database does not contain the referenced media item or contains a different version or revision of that media item, then the validator returns a warning.

You could use this approach to check references other than media, such as presentation components. You could even check the template definition to see if it contains a field definition or standard values change to publish.

To define the item validator:

  1. Add the class to the Visual Studio project and compile.
  2. In the Content Editor, navigate to the /Sitecore/System/Settings/Validation Rules/Item Rules item and insert an item named Media Publishing Status using the System/Validation/Validation Rule data template.
  3. In the Media Publishing Status item, set the value of the Type field in the data section to the type signature of the validator class, such as Sitecore.Sharedsource.Data.Validators.ItemValidators.MediaPublishingStatus,assembly
  4. In the /Sitecore/System/Settings/Validation Rules/Global Rules item, in all four fields in the Validation Rules section, add Media Publishing Status to the selection.

There is clearly some code to factor out into other classes, and as usual, this is not well tested. If I have time, I hope to perform some refactoring while (re)developing a solution to publish related media. Publishing targets, item publishing restrictions, languages versions including restrictions, workflow status, revision identifiers, and probably other factors complicate the challenge of developing generic solutions to automate publishing.

Posted in Obsolete | 2 Comments

sitecoreaustralia: Top 100 Things That Are in Sitecore That You May Have Missed

I thought anyone reading this blog primarily about the Sitecore ASP.NET CMS might appreciate the Discovering new things everyday… blog post from sitecoreaustralia listing 25 of what could eventually be 100 or more things that are in Sitecore that you may have missed.

Posted in Obsolete | 1 Comment

Intercepting Item Updates with Sitecore

The current edition of this blog post is at:


http://www.sitecore.net/Community/Technical-Blogs/John-West-Sitecore-Blog/Posts/2010/11/Intercepting-Item-Updates-with-Sitecore.aspx

Introduction

After my presentation at dreamcore 2010, someone asked me which of the techniques available in the Sitecore ASP.NET CMS to use for integrating custom logic when data changes. The available techniques for intercepting data updates include at least:

  1. The item:saved event.
  2. The item:saving event.
  3. The saveUI pipeline.
  4. Item Saved rules.
  5. Validators.

This post describes some of the criteria that you can use to determine which technique is appropriate for your requirements.

The item:saved Event

You can implement an item:saved event handler to intercept data changes.

Various operations raise the item:saved and item:saving events as described in this section and the next section. For example, updating an item raises the save events, and creating or renaming an item eventually raises save events as well. Therefore, if you write an item:saved or item:saving event handler well, you might be able to avoid writing separate handlers for create, rename, and other events.

Unfortunately, the duplicate command does not raise save events. You can probably safely assume that the user will change the data after duplication, which will eventually raise a save event. Or you can add a processor to the item:duplicated pipeline or any other event or pipeline to raise the save event(s) after those operations.

If you change data from an item:saved event handler, you have to worry about infinite event recursion. A change from within an item:saved event handler would raise a nested item:saved event. You can solve this by immediately exiting if the item being saved is in a static list that you maintain in the handler. If the item is not in the list, add the item to the list at the beginning of the handler, handle the event, and remove the item from the list at the end.

One concern with event handlers is that events fire for save operations in all databases, an you probably only want to handle save events in the Master database. I typically define a Database property in the handler and set that property in handler definition in web.config to the name of the database for which I want to handle events, and then exit the handler if the saved item is in a different database.

Here’s a code template for an item:saved event handler that shows how to change field values:

namespace Sitecore.Sharedsource.Tasks
{
  using System;
  using System.Collections.Generic;

  public class TitleChanger
  {

    private static readonly SynchronizedCollection<Sitecore.Data.ID> _inProcess =
      new SynchronizedCollection<Sitecore.Data.ID>();

    public string Database
    {
      get;
      set;
    }

    public void OnItemSaved(object sender, EventArgs args)
    {
      Sitecore.Events.SitecoreEventArgs eventArgs = args as Sitecore.Events.SitecoreEventArgs;
      Sitecore.Diagnostics.Assert.IsNotNull(eventArgs, "eventArgs");
      Sitecore.Data.Items.Item item = eventArgs.Parameters[0] as Sitecore.Data.Items.Item;
      Sitecore.Diagnostics.Assert.IsNotNull(item, "item");

      if (item.Database != null && String.Compare(item.Database.Name, this.Database) != 0)
      {
        return;
      }

      if (_inProcess.Contains(item.ID))
      {
        return;
      }

      Sitecore.Data.Fields.TextField title = item.Fields["title"];

      if (title == null)
      {
        return;
      }

      _inProcess.Add(item.ID);

      try
      {
        using (new Sitecore.Data.Items.EditContext(item))
        {
          title.Value += new Random().Next();
        }
      }
      catch (Exception ex)
      {
        throw ex;
      }
      finally
      {
        _inProcess.Remove(item.ID);
      }
    }
  }
}

Add the handler to the /configuration/sitecore/events/event element in web.config named item:saved.

<event name="item:saved">
  <handler type="Sitecore.Sharedsource.Tasks.TitleChanger, dreamcore" method="OnItemSaved">
    <database>master</database>
  </handler>

Sitecore raises the item:saved event after the save operation, so you can’t access the field values as they were before the item:saved event, and I you can’t determine which fields changed.

Inside the event handler, you can cancel the event:

eventArgs.Result.Cancel = true;

This prevents Sitecore from processing additional event handlers for the event, but does not prevent Sitecore from committing the change, or the UI shrinking effect that indicates to the user that they saved the item. I don’t recommend cancelling a save event.

As you will see from the following example, it looks easier to change values with an item:saving event than an item:saved event. I have successfully used the item:saved event to relocate news items into a hierarchy based on publication date. I hope to have a chance to write a post on that topic.

The item:saving Event

Some of the issues with the item:saved event also apply to the item:saving event. One difference is that Sitecore raises the item:saving event before committing the change, while it raises the item:saved event afterwards. An advantage of using the item:saving event as opposed to the item:saved event is that you can access the values from the item before the save, and the new values, and can prevent the user from saving their changes.

Here’s a code template for an item:saving event that shows how to access the new and old states of the item, and how you can alter the data before the commit.

public void OnItemSaving(object sender, EventArgs args)
{
  Sitecore.Events.SitecoreEventArgs eventArgs = args as Sitecore.Events.SitecoreEventArgs;
  Sitecore.Diagnostics.Assert.IsNotNull(eventArgs, "eventArgs");
  Sitecore.Data.Items.Item updatedItem = eventArgs.Parameters[0] as Sitecore.Data.Items.Item;
  Sitecore.Diagnostics.Assert.IsNotNull(updatedItem, "item");
  Sitecore.Data.Items.Item existingItem = updatedItem.Database.GetItem(
    updatedItem.ID,
    updatedItem.Language,
    updatedItem.Version);
  Sitecore.Diagnostics.Assert.IsNotNull(existingItem, "existingItem");

  Sitecore.Data.Fields.TextField title = updatedItem.Fields["title"];

  if (title == null)
  {
    return;
  }

  title.Value += new Random().Next();
}

Note that you don’t have to establish an editing context, as you are already within an editing transaction. Note also that I could not easily determine which fields had changed. 

Cancelling an item:saving event prevents Sitecore from saving the data, but does not stop the shrinking effect that makes it appear to the user that they saved. Note that Sitecore does not include any default handlers for the item:saving event, so use of this event might not be very common.

The saveUI Pipeline

When a user saves an item in the Sitecore user interface, Sitecore invokes the saveUI pipeline. A pipeline is basically a sequence of processors that implement an operation, where each processor is a class that contains a method that implements some portion of the operation.

The saveUI pipeline is most appropriate when you need to interact with the user saving the item. The saveUI pipeline has access to the state of the item before and after the save operation.

One drawback of the saveUI pipeline is that only certain operations invoke the pipeline. For example, if you automatically import data using APIs, Sitecore does not invoke the saveUI pipeline. This may require you to duplicate logic in multiple processes.

Just for demonstration purposes, here’s a prototype saveUI processor that limits the length of field values.

namespace Sitecore.Sharedsource.Pipelines.Save
{
  using System;

  public class SampleSaveProcessor
  {
    public int Limit
    {
      get;
      set;
    }

    public void Process(Sitecore.Pipelines.Save.SaveArgs args)
    {
      Sitecore.Diagnostics.Assert.ArgumentNotNull(args, "args");

      if (args.Items == null)
      {
        return;
      }

      if (this.Limit < 1)
      {
        this.Limit = 100;
      }

      foreach(Sitecore.Pipelines.Save.SaveArgs.SaveItem saveItem in args.Items)
      {
        foreach(Sitecore.Pipelines.Save.SaveArgs.SaveField saveField in saveItem.Fields)
        {
          if (saveField.Value.Length <= this.Limit)
          {
            continue;
          }

          Sitecore.Data.Items.Item item = Sitecore.Client.ContentDatabase.GetItem(saveItem.ID);
          Sitecore.Data.Fields.Field field = item.Fields[saveField.ID];
          string message = String.Format(
            "Length {0} of field {1} exceeds limit {2}; prior length was {3}.",
            saveField.Value.Length,
            Sitecore.StringUtil.GetString(field.Title, field.Name),
            this.Limit,
            field.Value.Length);
          Sitecore.Web.UI.Sheer.SheerResponse.Alert(message, new string[0]);
          args.SaveAnimation = false;
          args.AbortPipeline();
          return;
        }
      }
    }
  }
}

And here’s the corresponding update to web.config,

<saveUI>
  <processor mode="on" type="Sitecore.Pipelines.Save.BeforeSaveEvent, Sitecore.Kernel"/>
  <processor mode="on" type="Sitecore.Pipelines.Save.ParseXml, Sitecore.Kernel"/>
  <processor mode="on" type="Sitecore.Pipelines.Save.CheckItemLock, Sitecore.Kernel"/>
  <processor mode="on" type="Sitecore.Pipelines.Save.CheckRevision, Sitecore.Kernel"/>
  <processor mode="on" type="Sitecore.Pipelines.Save.Validators, Sitecore.Kernel"/>
  <processor mode="on" type="Sitecore.Pipelines.Save.ValidateFields, Sitecore.Kernel"/>
  <processor mode="on" type="Sitecore.Pipelines.Save.HasWritePermission, Sitecore.Kernel"/>
  <processor mode="on" type="Sitecore.Pipelines.Save.NewVersion, Sitecore.Kernel"/>
  <processor mode="on" type="Sitecore.Pipelines.Save.TightenRelativeImageLinks, Sitecore.Kernel"/>
  <processor mode="on" type="Sitecore.Pipelines.Save.ConvertToXHtml, Sitecore.Kernel"/>
  <processor mode="on" type="Sitecore.Pipelines.Save.CheckLock, Sitecore.Kernel"/>
  <processor mode="on" type="Sitecore.Pipelines.Save.Lock, Sitecore.Kernel"/>
  <processor mode="on" type="Sitecore.Pipelines.Save.CheckTemplateFieldChange, Sitecore.Kernel"/>
  <processor mode="on" type="Sitecore.Pipelines.Save.ConvertLayoutField, Sitecore.Kernel"/>
  <processor mode="on" type="Sitecore.Pipelines.Save.CheckLinks, Sitecore.Kernel"/>

  <processor mode="on" type="Sitecore.Sharedsource.Pipelines.Save.SampleSaveProcessor, assembly"/>

  <processor mode="on" type="Sitecore.Pipelines.Save.Save, Sitecore.Kernel"/>
  <processor mode="off" type="Sitecore.Pipelines.Save.RenderingHack, Sitecore.Kernel"/>
  <processor mode="on" type="Sitecore.Pipelines.Save.Unlock, Sitecore.Kernel"/>
  <processor mode="on" type="Sitecore.Pipelines.Save.WorkflowSaveCommand, Sitecore.Kernel"/>
  <processor mode="on" type="Sitecore.Pipelines.Save.PostAction, Sitecore.Kernel"/>
</saveUI>

Notice how the processor must precede the Save processor to abort the pipeline and prevent the save visual effect in the user interface.

Item Saved Rules

Item saved rules let you process item updates using the rules engine. You can read about the rules engine in the Rules Engine Cookbook on the Sitecore Developer Network. One of the default event handlers for the item:saved event invokes item saved rules – item saved rules run any time the item:saved event fires.

Probably the primary advantage of using the rules engine as opposed to an event handler is that you can control the logic and parameters through a browser-based user interface instead of using the web.config file. Another advantages of using the rules engine is separation of concerns: instead of writing a save handler that runs for each save event, which must check whether it should do anything with every item, you write that check into the condition part of the rule. Also, Sitecore runs event handlers for save events in all databases, where item saved rules only run for items updated in the database that contains the rule definition item.

Some potential drawbacks of using item saved rules are similar to those using an item:saved event handler. For example, you cannot access the prior state of the item from an item saved rule, and you cannot stop the save operation from an item saved rule.

You can see an example of using item saved rules in my post about how to Use the Sitecore 6.1 Rules Engine to Control Item Names.

Validators

Validators are not specifically for handling item updates, but you can use them for this purpose. You can use a field validator and set the error level to fatal or critical to prevent the user from saving their changes. Field validators can access the old and new values for the field. Field validators can also provide the user with validation actions, which they can invoke through the user interface to automatically correct data error conditions. You can get more information about validators from my post about how to Validate a Sitecore Checklist, Multilist, Treelist, or TreelistEx Field, which contains links to pages, posts, and threads containing much more information about validation, including the Client Configuration Cookbook.

Conclusion

Depending on your requirements, you can use one or more of these techniques to intercept data changes. Investigate the existing validators, item saved rules, event handler and pipeline definitions to see when Sitecore itself uses each of these approaches. Think about whether you need to block the save operation and the shrinking effect, access the state of the item prior to the save (item:saving, saveUI, or validator), or interact with the user (saveUI or validator). You can implement a saveUI pipeline processor and an event handler for a single event, for example to ensure you trap user and automated updates.

Think about at least the following criteria to determine which approach or approaches to apply:

  • If you need to operate on the save process or only the saved item, consider the item:saved event or item saved rules.
  • If you need to access the previous values of any field or property from before the change operation, as opposed to only the current values, consider the saveUI pipeline, the item:saving event, or a field validator,
  • If you need to provide a user interface to define criteria for handling data changes, consider the rules engine. 
  • If you need to be able to block the save operation, consider the saveUI pipeline, the item:saving event, or field validators. If you need to prevent the visual effect in the user interface that indicates that Sitecore has saved the selected item, consider the saveUI pipeline or field validators.
  • If you need to trap data changes made through APIs, as opposed to only changes made through UIs, do something more than a saveUI pipeline processor.
  • Do you need to trap only save, or additional operations such as create, duplicate, and rename?
  • Which approach or approaches provide the best user experience?

You can use multiple techniques. For instance, you can implement logic in the saveUI pipeline and the item:saved event to achieve the same objective. You can combine multiple techniques. For example, it should be possible to add a processor to the saveUI pipeline defined in web.config to apply rules using the rules engine.

Update: You can’t be too confident in intercepting data changes. For example, what if a rule changes or you fix a defect in your action code and you need to apply that rule to all existing items. You could use something like a scheduled task to iterate all the items and invoke a process, such as to invoke item saved rules. You can read about scheduled tasks in my previous post, All About Sitecore Scheduling: Agents and Tasks.

Posted in Obsolete | 1 Comment

Cascade the Data Source of a Sublayout down to Nested Controls

Posted in Obsolete | 3 Comments