Optimizely CMS Validators vs Publish Events
In this blog post we are going to explain two ways to validate pages or block properties in the CMS. The first one using publish events initialization modules and the second one with validators. Later we will discuss the implications of using each option. So without further due, lets begin.
We will begin creating an initialization module which will validate that the heading property of a blog detail page model is not the same as the page name.
[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class BlogDetailPageInitialization : IInitializableModule
{
private IContentEvents _contentEvents;
public void Initialize(InitializationEngine context)
{
_contentEvents ??= ServiceLocator.Current.GetInstance<IContentEvents>();
_contentEvents.PublishingContent += ContentEvents_PublishingContent;
}
public void Uninitialize(InitializationEngine context)
{
_contentEvents ??= ServiceLocator.Current.GetInstance<IContentEvents>();
_contentEvents.PublishingContent -= ContentEvents_PublishingContent;
}
private void ContentEvents_PublishingContent(object sender, ContentEventArgs e)
{
if (sender == null || e == null) return;
if (e.Content is not BlogDetailPage page)
{
return;
}
if (!string.IsNullOrEmpty(page.Heading) && page.Heading != page.Name)
{
return;
}
e.CancelAction = true;
e.CancelReason = "Please set the heading with a different value from the page name";
}
}
Now, we will create a validator that will do the same.
public class BlogDetailPageValidator : IValidate<BlogDetailPage>
{
public IEnumerable<ValidationError> Validate(BlogDetailPage instance)
{
var errors = new List<ValidationError>();
if (!string.IsNullOrEmpty(instance.Heading) && instance.Heading != instance.Name)
{
errors.Add(new ValidationError
{
PropertyName = nameof(instance.Heading),
Severity = ValidationErrorSeverity.Error,
ErrorMessage = "Please set the heading with a different value from the page name"
});
}
return errors;
}
}
The blog detail page model has the following structure.
[ContentType(GUID = "{F7CFF581-ABBC-4CFE-89BB-F482EF244151}", DisplayName = "Blog Detail Page")]
public class BlogDetailPage : PageData
{
[Required]
[CultureSpecific]
[UIHint(UIHint.Textarea)]
[Display(GroupName = SystemTabNames.Content, Order = 200)]
public virtual string Heading { get; set; }
}
Now, lets analyze both a little bit. First, if you see the initialization module, you can hook up to any event needed. In our case we are subscribing to listen the publishing event, but there lays the problem. We are subscribing to the global publishing event including all pages, blocks and media; not for the publishing of the blog detail page type, so if we have several validators using initialization modules all of them will be called at least once until they reach the condition of the type we are validating. This will generate overhead and it is definitely not advice.
if (e.Content is not BlogDetailPage page)
The validator on the other hand, specifies the type you want to check and the Validate method only runs on that type when the user tries to save a draft. In addition, you can specify which property generated the error, the severity and a custom message while in the initialization module is more generic, only being capable of setting up if was cancelled or not and a message.
In other words, we recommend using validators instead of initialization modules in order to avoid the overhead caused by continue listening to the publishing event. And that is it. If you have any questions or suggestions please let me know in the comments. I hope this can help someone and as always keep learning !!!
Leave a Reply