Deane
Deane

Reputation: 8747

Why can't my Sitecore custom validator read the item as it has been edited in Content Editor?

I have created a custom validator in Sitecore. It executes correctly, and I can debug it and step through it.

I extended my validator from StandardValidator, which in turn extends from BaseValidator.

In my validator, I get the item:

var item = GetItem();

GetItem is a method from BaseValidator. Based on my reading, this is the standard method to get the item to be validated.

The problem: the item I get back doesn't reflect the values that have been edited in Content Editor. Instead, it appears to simply be the item from the database (so, what it looked like when it first loaded in Content Editor, without reflecting any changes that might have been made to it).

Thus, I cannot validate. To validate the data an editor might have changed in Content Editor, I need to be able to see those changes. Seeing how the item sits in the database right now doesn't help me.

I did some extended debugging, which may or may not be helpful --

I decompiled the kernel, and walked through both StandardValidator and BaseValidator.

The GetItem method from BaseValidator does what I suspect -- it gets the item from the database. But then it runs it through a method called UpdateItem which appears be intended to overlay the inputted values from Content Editor on top of the stored values (thus returning an object that accurately reflects the data which is currently sitting in the editor). This is clearly what I want.

However, UpdateItem only appears to overlay anything if a property called ControlToValidate has a value...and it never does. I've tried this validator as both a Field Validator and an Item Validator. I've initiated it by both saving an item and by tabbing off a field. I put a breakpoint in Visual Studio and a watch on ControlToValidate. One time (inexplicably) it had a value of FIELD165901047 (which corresponded to a field ID in content editor), but in all other cases, it's been null.

What this means is that UpdateItem effectively does nothing, and the item is simply returned as it currently sits in a database, which doesn't help -- I'm trying to validator values entered in Content Editor before saving to the database.

Regardless of my investigation (I think I understand UpdateItem, but I concede that I might be misinterpeting it, and I'm not accounting for potential decompilation errors), I still have this basic problem:

var item = GetItem();

That never seems to return the values from Content Editor. It returns the item directly from the database, which doesn't help me.

Upvotes: 3

Views: 695

Answers (2)

Matt Eno
Matt Eno

Reputation: 697

I managed to create a work-around for this, and it seems to be working for me. I'm not that happy that I don't know why controlToValidate never has a value, but my workaround manages to get the new value that hasn't yet been saved.

Basically, I've overridden the code that runs GetItem(), and I find the new value in the page's SaveArgs as shown below. The magic happens on the line that has: field.Value = ((((SaveArgs) ((ClientPage) page)?.CurrentPipelineArgs)?.Items[0].Fields) ?? Array.Empty<SaveArgs.SaveField>()).FirstOrDefault(x => x.ID == field.ID)?.Value ?? field.Value;

protected override Item GetItem()
    {
        var obj = base.GetItem();
        if (obj == null || obj.Versions.Count == 0)
            return null;
        UpdateItem(obj);
        return obj;
    }

    private void UpdateItem(Item item)
    {
        Assert.ArgumentNotNull(item, nameof(item));
        using (new SecurityDisabler())
            item.Editing.BeginEdit();
        var page = (Page)null;
        var current = HttpContext.Current;
        if (current != null)
            page = current.Handler as Page;
        foreach (Field field in item.Fields)
        {
            var controlToValidate = ControlToValidate;
            if (string.IsNullOrEmpty(controlToValidate) || field.ID != FieldID)
            {
                field.Value = ((((SaveArgs) ((ClientPage) page)?
                        .CurrentPipelineArgs)?.Items[0].Fields) ?? Array.Empty<SaveArgs.SaveField>()).FirstOrDefault(x => x.ID == field.ID)
                    ?.Value ?? field.Value;
            }
            else
            {
                var str = current != null ? RuntimeValidationValues.Current[controlToValidate] : null;
                if (page != null && str != null)
                {
                    var control = page.FindControl(controlToValidate);
                    if (control != null)
                    {
                        if (control is IContentField contentField)
                            str = contentField.GetValue();
                        else if (ReflectionUtil.GetAttribute(control, typeof(ValidationPropertyAttribute)) is ValidationPropertyAttribute attribute)
                            str = ReflectionUtil.GetProperty(control, attribute.Name) as string;
                    }
                }
                if (str == null && current != null)
                    str = current.Request.Form[controlToValidate];
                if (str != null && str != "__#!$No value$!#__")
                    field.Value = str;
            }
        }
    }

Upvotes: 0

Hans
Hans

Reputation: 11

In my validator (a field type validator) I've used

 var field = this.GetField();

to get new value of the field to validate. The same should work for a field validator. It's probably a bit different for an item validator but I've never written an item validator.

It's also a method of the BaseValidator and returns a value of the type Field. You can assign this directly to the type of the target field you want, e.g. HtmlField (Sitecore has implemented the casting operators so you can do this):

HtmlField htmlField = field;

Upvotes: 1

Related Questions