Sitecore Sunday Pt 4: Conditional (Rules Based) Field Validators

Sitecore’s OOTB field validators are used on pretty much every Sitecore implementation I’ve seen in the last 10 years, which makes it a pretty useful feature! However there are times when there is a little more context surrounding whether or not a particular field is valid.

Thankfully Sitecore makes it super simple to create custom field validators where we have reign to do pretty much whatever we want and there have been some interesting ones put out there for example this image width, height and aspect ratio validator by Brian Pedersen.

I will be calling this approach IFTTV (If This Then Valid).

If This Then Valid

Sorry.

To setup field validator items that use the rules engine we first of course need a template with the necessary validation fields. Sitecore actually already provides this template:

/sitecore/templates/System/Validation/Rules Validation Rule

However this template is intended for use with item level validation that reads from rules under the item:

/sitecore/system/Settings/Rules/Validation Rules/Rules

I feel this approach is disconnected from the management of field validation rules under:

/sitecore/system/Settings/Validation Rules/Field Rules

But we can still take advantage of the template. The first step is to create a template that inherits from the Rules Validation Rule template and specifies our field level rules implementation in the type field.

If This Then Valid Template

The implementation is fairly straight forward:


using Sitecore.Data.Validators;
using Sitecore.Rules;
using Sitecore.Rules.Validators;
using System.Runtime.Serialization;

public class IfThisThenValid : StandardValidator
{
    public override string Name => "If This Then Required";

    public IfThisThenValid()
    {
    }

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

    protected override ValidatorResult Evaluate()
    {
        var item = GetItem();

        if (item == null)
            return ValidatorResult.Valid;

        var ruleContext = new ValidatorsRuleContext
        {
            Item = item,
            Validator = this,
            Result = ValidatorResult.Valid,
            Text = string.Empty
        };

        var validatorItem = item.Database.GetItem(ValidatorID);

        if (validatorItem == null)
            return ValidatorResult.Valid;

        var rules = RuleFactory.GetRules<ValidatorsRuleContext>(new[] { validatorItem }, "Rule");

        if (rules == null)
            return ValidatorResult.Valid;

        rules.Run(ruleContext);

        if (ruleContext.Result == ValidatorResult.Valid)
            return ValidatorResult.Valid;

        base.Text = ruleContext.Text;

        return base.GetFailedResult(ruleContext.Result);
    }

    protected override ValidatorResult GetMaxValidatorResult()
    {
        // Critical or above (Fatal) is important for experience editor support

        return base.GetFailedResult(ValidatorResult.CriticalError);
    }
}

To summarise:

  • Inherit from Sitecore’s StandardValidator
  • Get the item being validated (rules are run against this as the context item)
  • Get the validator item (this is where our rules live)
  • Get the rules in the validator item and run them against the context item using a ValidatorsRuleContext (which returns a ValidationResult). This is where the difference lies from Sitecore’s standard implementation*, which evaluates the context item against the rules under: /sitecore/system/Settings/Rules/Validation Rules/Rules
  • Return the result

*Sitecore.Data.Validators.ItemValidators.ValidationRulesValidator,Sitecore.Kernel

Then I will create a folder under /sitecore/system/Settings/Validation Rules/Field Rules and an insert options rule so we can create new rules based field validators and organise them in folders:

If This Then Valid Insert Options

As an example I added two fields to the Sample Item template, Sitecore Image and External Image, the validation scenario is at least one of them must be provided. Here’s our rule based validator:

If This Then Valid Rule

Which is associated with our fields on the sample item:

If This Then Valid Field

And here’s it in action:

If This Then Valid Usage

And that’s it, pretty straight forward but a ton of uses, especially considering the extensibility of the rules engine.

Happy validating!

Advertisements

Sitecore Sunday Pt 3: Conditional (Rules Based) Goal Registration

Goals in Sitecore allow us to register the fact that a site visitor has performed a specific action on the site and more importantly that that action has engagement value associated with it. Actions can vary but typically fall into one of two buckets, significant page views (end of conversion funnel pages) and on-page events (interacting with a map, playing a video etc.). For more on the importance of engagement value as a metric take a look at this guide by Sitecore’s SBOS (Sitecore Business Optimisation Services) team.

This post tackles more advanced scenarios where a certain pre-condition must be satisfied before considering a page view or on-page event as legitimate engagement, to put it another way, we want to qualify the action before registering the conversion. The example I will use is site search related but the concept and approach can easily be extended to cover other scenarios. The scenario is this, we want to register the “Site Search Page View” goal when a visitor views a search results page but not when the user subsequently pages through the search results as this is considered an extension of behavior but ultimately part of the original conversion.

The “Site Search Page View” goal looks like this.

Sample Internal Search Goal Item

On inspection we can see that the goal has a rules field. This field however does not represent a pre-condition that must be true before the goal is registered, it allows us to trigger further actions after the goal is registered if the conditions of the rules field are true. This could have some interesting uses but doesn’t solve the problem we are trying to solve here.

The solution to our problem once again lies in the processItem pipeline, defined in Sitecore.Analytics.config.


<processItem>
  <processor type="Sitecore.Analytics.Pipelines.ProcessItem.CollectParameters,Sitecore.Analytics" />
  <processor type="Sitecore.Analytics.Pipelines.ProcessItem.TriggerCampaigns,Sitecore.Analytics" />
  <processor type="Sitecore.Analytics.Pipelines.ProcessItem.RegisterPageEvents,Sitecore.Analytics" />
  <processor type="Sitecore.Analytics.Pipelines.ProcessItem.ProcessProfiles, Sitecore.Analytics" />
</processItem>

This is an extremely useful pipeline, which I also make use of in the post Sitecore Sunday Pt 2: Programmtic Profile Association. When goals are associated with page items in the traditional way (select the page and select Analyze > Goals), this information is stored in the pages __Tracking field as xml, just as associated profiles are.

The CollectParameters processor checks for the existence of the __Tracking field on the context item (or the context items template standard values) and if it is not null adds the tracking field to the TrackingParameters property of the ProcessItemArgs passed into Process method of the processor (a bit of a mouthful yes).


public class CollectParameters : ProcessItemProcessor
{
    public override void Process(ProcessItemArgs args)
    {
        // code removed for brevity

        if (!string.IsNullOrEmpty(args.Item["__Tracking"]))
        {
            Field item = args.Item.Fields["__Tracking"];

            if (item != null)
            {
                args.TrackingParameters.Add(new TrackingField(item));
            }
        }
    }
}

The RegisterPageEvents processor further down in the pipeline uses the TrackingParameters property to collect up all the goals associated with the current context (request) and registers them against the current page on the Interaction (session) object of the current contacts xDb record.

The trick now is to add additional tracking fields to the TrackingParameters property if certain conditions are true. To do this we will add an additional processor to the pipeline after the CollectParameters processor (to preserve standard functionality) and we’ll call it CollectConditionalParameters.


<pipelines>
  <processItem>
    <processor
      type="Sitecore.Feature.ConditionalGoals.Pipelines.ProcessItem.CollectConditionalParameters, Sitecore.Feature.ConditionalGoals"
      patch:after="*[@type='Sitecore.Analytics.Pipelines.ProcessItem.CollectParameters,Sitecore.Analytics']" />
  </processItem>
</pipelines>

The code in this processor looks like this:


public class CollectConditionalParameters : ProcessItemProcessor
{
    public override void Process(ProcessItemArgs args)
    {
        var goals = ConditionalGoalsRepositoryFactory.Repository.Goals();

        foreach (var goal in goals.Where(x => x.IsMatch()))
        {
            args.TrackingParameters.Add(new TrackingField(goal.Fields["__Tracking"]));
        }
    }
}

To summarise before going into more detail:

  1. A collection of conditional goals is retrieved from a repository (obvious I know, detail to follow).
  2. For each conditional goal that is considered a match for the current context (based on its rules), add the tracking field on the conditional goal to the TrackingParameters property, which will be used later on in the pipeline by the RegisterPageEvents processor

The conditional goals repository gets a collection of items (separate from the goal items in the Marketing Control Panel and scoped to specific sites to support multi-tenancy) based on the “Conditional Goal” template. This template has three fields:

  1. Applies to Templates
  2. Rules
  3. Attributes To Register

The applies to templates field allows the author to select which page templates the goal applies to or if it applies to any page type, can be left blank. The rules field allows the author to set the condition(s) that must be satisfied for the goal to be registered. You’re probably thinking “The rules engine has conditions for the template that the context item is based on, why do we need the applies to templates field?”. Whilst this is a correct assertion, traditionally goals are assigned to specific pages or templates and marketers more often than not associate specific goals with specific page types, it felt an important enough condition to pull it out and pay special attention to it.

Finally the attributes to register field is a field that uses the field type “Tracking” and allows the author to select the specific goal that should be registered if all conditions are true.

Conditional Goal Item

One thing I have noticed about the tracking field is that regardless of the values selected the field always displays “Nothing has been set”, I have opened a ticket with Sitecore support, I will update this post when I get a response.

Sitecore Support Response: This is a system field, when selections are made it modifies the __Tracking field (which we know). The text “Nothing has been set” is hard coded. Not considered a bug.

So that is really all there is to it, a simple yet powerful way to register page view goals but only when specific conditions are met, powered by the Sitecore rules engine and processed in the processItem pipeline.

Sitecore Module: Profile Key Scaffolder

In the Sitecore Sunday Pt2: Programmatic Profile Association post I talked about Sitecore profiles that map 1 to 1 with existing site taxonomies and how to associate these profiles to your site visitors without having to re-tag all of your content.

As part of this process setting up profile, profile key, profile card and pattern card items is still required and this can be quite a time consuming task because for each profile key you have to:

  1. Create the profile key item
  2. Set its max value to 10 (or a scale of your choosing)
  3. Create a profile card item with the same name
  4. Set its radar graph to the maximum value for the profile key
  5. Repeat steps 3 & 4 for the pattern card

You can see with a lot of profile keys how this could become tedious.

Profile scaffolding to the rescue!

Download the Profile Scaffolder, which is a small and unobtrusive module (Helix stylee) that reduces the task outlined above to a single step:

  1. Create a profile key and give it a name. Done.

Happy profiling!

Sitecore Quick Tip: Programmatically Set Item to Not Publishable & Check if Is Publishable

Pretty straight forward but I needed this today. To set an entire item (not just certain versions of it) to not publishable:


using (new SecurityDisabler())
{
    item.Editing.BeginEdit();
    item.Publishing.NeverPublish = true;
    item.Editing.EndEdit();
}

Now of course this by itself will not remove the item from the content delivery servers, it needs to be published, so you can follow this up with:


Sitecore.Publishing.PublishManager.PublishItem(
    item,
    new Database[] { Database.GetDatabase("web") },
    new Language[] { Language.Parse("en") },
    false,
    true,
    false);

Which essentially says publish the item to the web database in english, do not perform a deep publish, perform a smart publish and do not publish related items.

Now more interestingly if you want to take any given item and tell whether its publishing is restricted in any way you can use the following:


var pubInfo = PublishingInformationBuilder.GetPublishingInformation(item, PublishingInformationLevel.Item);

var restrictions = pubInfo.OfType<ItemPublishingRestrictedError>().FirstOrDefault<ItemPublishingRestrictedError>();

This requires the using statements:


using Sitecore.Publishing.Explanations;
using Sitecore.Publishing.PublishingInformation;

Sitecore Sunday Pt 1: Programmatic Layout Assignment

It has been some time since I last updated this blog and it has been quite a year and a half! It all started when my family and I decided that after 5 years of living in Toronto and working for nonlinear creations it was time to move back to the UK. I was also in the middle of delivering a large project during the move and it wasn’t long after I got back to the UK that I began helping nonlinear start the nonlinear London office! For those of you who are familiar with nonlinear creations you may also be aware that nonlinear was acquired by Valtech in June.

So this is all basically a reason (read excuse) for my dip in contributions to the Sitecore community with the exception of presenting Sitecore XP at Scale at the London user group in May. It is also the motivation to get involved again and it starts with this blog series entitled Sitecore Sundays. The idea is simple, write a Sitecore blog post every Sunday for a 100 Sundays (100 always seems like a nice round number and a challenge).

So without further ado let’s get into the first post in the series, programmatic layout assignment.

The standard way that Sitecore “presents” a page is by resolving the context item (the page) and then inspecting the renderings (shared) and final renderings (not shared) field of the context item and its templates standard values. This is all known as resolving layout deltas and is a fairly complex subject that I won’t go into in detail now but follow this link for a flow chart of the process.

But what if we want to resolve the presentation that should be used for the context item programmatically based on some business logic? What if the context item has no presentation details associated with its templates standard values (think data repositories)? What if we want to expose a subset of products to a version of the presentation that contains a specific personalisation scenario?

Well, it turns out there’s a pipeline for that (disclaimer: this applies to MVC only, but we’re all using MVC now, right?). It’s in Sitecore.MVC.config and by default it looks like this:

<mvc.getXmlBasedLayoutDefinition>
  <processor type="Sitecore.Mvc.Pipelines.Response.GetXmlBasedLayoutDefinition.GetFromLayoutField, Sitecore.Mvc"/>
</mvc.getXmlBasedLayoutDefinition>

The GetFromLayoutField processor inherits from GetXmlBasedLayoutDefinitionProcessor which takes in a GetXmlBasedLayoutDefinitionArgs parameter in its constructor that has one really important property, ContextItem. Essentially if ContextItem is set on the args it will use that item to get the presentation details instead of the default, which is the standard context item:

protected virtual XElement GetFromField(GetXmlBasedLayoutDefinitionArgs args)
{
    Item contextItem = args.ContextItem ?? PageContext.Current.Item;
    
    if (contextItem == null)
    {
        return null;
    }

    return this.GetFromField(contextItem);
}

So this gives us an opportunity to place a processor in the pipeline that runs before the default processor and sets the ContextItem on the args based on some business logic. Et voila, dynamic presentation resolution that means the presentation item and the context item do not have to be the same thing. There are lots of possibilities with this.

Until next Sunday!

Disable WebAPI Verbs (Http Methods) on Sitecore Content Delivery Servers

It’s increasingly common to implement web services in a Sitecore hosted solution using Microsoft’s WebAPI framework. I’m personally a fan of this approach and if you’re paying attention to your usage of verbs then typically services responsible for modifying content in Sitecore will use action methods decorated with POST, PUT or DELETE http methods.

In a typical scenario you would only want these services to be accesible in content management server roles with the distribution of content modifications to content delivery server roles handled by Sitecore’s publishing API (or task runner).

So how do I ensure that only GET requests will be accepted on a content delivery server? In this example I’ll setup my WebAPI endpoint configuration in the initialize pipeline:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <initialize>
        <processor type="MyClass, MyAssembly" patch:before="processor[@type='Sitecore.Pipelines.Loader.ShowVersion, Sitecore.Kernel']" />
      </initialize>
    </pipelines>
  </sitecore>
</configuration>

Inside of my class I will implement a method that reads a boolean value (which defaults to false to be safe) from a patch file that will instruct my WebAPI configuration method in terms of the constraints it should place on my WebAPI routes.

public void Process(PipelineArgs args)
{
    GlobalConfiguration.Configure(ConfigureWebApi);
}

protected void ConfigureWebApi(HttpConfiguration configuration)
{
    var routes = configuration.Routes;

    routes.MapHttpRoute("WebApiDefault", "projectname/api/{controller}/{action}/{code}", new
    {
        code = RouteParameter.Optional
    },
    new
    {
        // read route constraints from include file

        httpMethod = 
            SettingsUtil.GetBool("Api.ModificationsAllowed", false) 
                ? new HttpMethodConstraint(HttpMethod.Get, HttpMethod.Put, HttpMethod.Post, HttpMethod.Delete) 
                : new HttpMethodConstraint(HttpMethod.Get)
    });
}

Now we simply to need to add config transforms to the include file in order to control the value of “Api.ModificationsAllowed” for each server role.

<configuration xmlns:x="http://www.sitecore.net/xmlconfig/" xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <sitecore>
    <settings>
      <setting xdt:Locator="Match(name)" xdt:Transform="SetAttributes(value)" name="Api.ModificationsAllowed" value="true" />
    </settings>
  </sitecore>
</configuration>

That’s it, nice and simple!

Sitecore Heartbeat Page & Web Forms for Marketers on Remote Servers

When you are running Sitecore with multiple load balanced content delivery servers, typically you would use Sitecore’s heartbeat page to monitor the health of each content delivery instance:

http://host/sitecore/service/heartbeat.aspx

This service does more than just return a http 200 status letting you know that the site is running, it also iterates over each connection string defined in ConnectionStrings.config and executes a simple SQL query against sys.tables to check for un-responsive databases.

This is all fine and dandy until you throw Web Forms for Marketers into the mix. On a remote installation of Web Forms for Marketers a connection string is added to ConnectionStrings.config that communicates with a content management instance and looks something like this:

Unfortunately the heart beat page will try to process this connection string but it doesn’t know what to do with it so the page subsequently returns a 500 server error every time.

Fortunately Sitecore provides a way around this. If you look at the standard web.config you will not find a setting called:

Sitecore.Services.Heartbeat.ExcludeConnection

But, the heart beat page is coded to check for it (expecting a pipe separated list) and by default excludes the connection string called “LocalSqlServer”.

As we know the best practice when adding custom settings to a Sitecore solution is to use include files. So, to fix this problem all we need to do is add (or update an existing) include file that contains the following:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <settings>
            
            <!-- stops the heartbeat page from returning an error 500 on delivery servers -->
            <setting name="Sitecore.Services.Heartbeat.ExcludeConnection" value="LocalSqlServer|remoteWfmService"/>

        </settings>
    </sitecore>
</configuration>

And that’s it! We have a working heart beat page on a content delivery instance that also has Web Forms for Marketers installed.