Tuesday, July 17, 2012

Validating content on Save - Part 1 of many

Recently I had to do a project where we are validating quite some content options whenever the editors decide their work is done. This got me thinking about sharing some of these experiences. This post is the first of a few that I have lined up on this subject, and where I try to cover the available options for content validation from the simple (and cheap) ones to the very complex and over-engineered.

For (many) years, the solution to the eternal "how do I validate content" question with Tridion has been a combination of making fields mandatory and using the Event System (OnComponentSavePre anyone?).
Recently I had to take a different approach to this, since Tridion 2011 brings all this amazing extensibility framework (codenamed Anguilla) that allows to do whatever we want with our content, right? While this is true, the correct answer comes in many shades of gray, and the biggest obstacle on creating a great solution for validating content for your editors is the learning curve.

Let’s think about how we can do this, by taking a very simple example, then growing it out to bigger things. In the first scenario we're covering in this series of posts, we will focus on how to validate that a field named "Title" starts with a capital letter, and we'll do this using 3 different approaches:
  • Schema restrictions
  • Event System
  • CME Command Extension
Using A Schema Restriction As described in LiveContent, you can apply XSD restrictions to simple Tridion Web Schema fields, so we could change the field definition to be something like this:
<xsd:element maxoccurs="1" minoccurs="1" name="Title">
    <xsd:annotation>
        <xsd:appinfo>
            <tcm:extensionxml xmlns:tcm="http://www.tridion.com/ContentManager/5.0"></tcm:extensionxml>
        </xsd:appinfo>
    </xsd:annotation>
    <xsd:simpletype>
        <xsd:restriction base="xsd:string">
            <xsd:pattern value="[A-Z][A-Za-z0-9_ ]*"></xsd:pattern>
        </xsd:restriction>
    </xsd:simpletype>
</xsd:element> 

This will now force our field to start with an upper case letter, and Tridion will enforce the rule.

 If I now try to save my content, Tridion will let me know I don’t understand what I’m doing:

And since I won’t understand the error message either, this becomes more complex than what we can handle with simple schema restrictions…
There are other simple scenarios, particularly with number fields, that Tridion handles quite gracefully (by not allowing the editor to type a number higher than specified in "xsd:maxInclusive" for instance), or limiting the maximum number of characters you can type in a field. However, fairly quickly we fall into a domain where business logic is required to validate fields (common examples are Start and End Dates for events, where you depend on 2 fields to provide the correct validation).

So this was the "cheap" content validation: schema restrictions.

Let’s go now to the 2nd option, using the Event System.

Taking the same example from above (first character must be upper case) we could take one of 2 approaches: Let the user know that the first letter must be upper case, or automatically changing it. Which approach you take depends on your organization’s culture. In some companies it is OK to have the system take decisions for you, while other organizations need to give the editorial team full control about any content that gets created. Let's build an event system to deal with the second approach (letting the editors know about the errors of their ways):



Step 1: create an Event Class Library with Visual Studio.


Add some references from [Tridion-Home]\bin\client


Then let’s write some code to validate if your first character is upper case, and notify the editor if that’s not the case.
using System;
using System.Collections.Generic;
using Tridion.ContentManager.ContentManagement;
using Tridion.ContentManager.ContentManagement.Fields;
using Tridion.ContentManager.Extensibility;
using Tridion.ContentManager.Extensibility.Events;

namespace ValidateTitleFieldUpperCaseFirstLetter
{
    [TcmExtension("ValidateTitleFieldUpperCaseFirstLetter")]
    public class Validate : TcmExtension, IDisposable
    {
        private readonly List<EventSubscription> _subscriptions = new List<Eventsubscription>();

        public Validate()
        {
            Subscribe();
        }

        private void Subscribe()
        {
            EventSubscription subscription =
                EventSystem.Subscribe<Component, SaveEventArgs>(ValidateFirstLetterIsUpperCase, EventPhases.Initiated);
            _subscriptions.Add(subscription);
        }

        private void ValidateFirstLetterIsUpperCase(Component component, SaveEventArgs args, EventPhases phases)
        {
            if (component.Schema.Title != "Article") return;
            if (component.ComponentType != ComponentType.Normal) return;
            ItemFields fields = new ItemFields(component.Content, component.Schema);
            SingleLineTextField titleField = (SingleLineTextField)fields["Title"];
            if(titleField.Values.Count == 0)
            {
                // Tridion should take care of this, but just in case someone changed the field to optional
                throw new Exception("Title field is mandatory.");
            }
            string fieldContent = titleField.Value;
            if(char.IsLower(fieldContent[0]))
            {
                throw new Exception("The first letter of the Title field must be a Capital letter.");
            }
        }

        public void Dispose()
        {
            foreach (EventSubscription subscription in _subscriptions)
            {
                subscription.Unsubscribe();
            }
        }
    }
}

Now let’s build our code and tell Tridion to execute it (add a line similar to this to Tridion.ContentManager.Config under "<extensions>"

<add assemblyFileName="D:\Tridion 2011\PathToYourDll\ValidateTitleFieldUpperCaseFirstLetter.dll" />

Restart Tridion (COM+ and Service Host) and let’s see what it does.


So, it’s a nice improvement from the previous message. Cost of development? Well, it took me about 20 minutes to write it and deploy it (fair enough, it’s far from ready, with things that you shouldn’t see in production like "if (component.Schema.Title != "Article") return;"), but you get the picture.

You could also just be slightly smart about it and outline this requirement in the field’s description:
But this is too specific to this use case, and it’s a simple one after all.


Next (within a few days, I promise) we will start looking at how we could do this with Anguilla.