Accessing DataErrorInfoErrorsPerField in Validation classes

Posts   
 
    
jader201
User
Posts: 33
Joined: 20-Mar-2007
# Posted on: 12-Jul-2007 23:32:19   

I am trying to add validation to my entities, for basic rules such as required fields and string lengths.

For example, I have this as my overridden ValidateFieldValue like so:

        public override bool ValidateFieldValue(IEntityCore involvedEntity, int fieldIndex, object value)
        {
            bool isValid = true;
            switch ((CustomerFieldIndex)fieldIndex)
            {
                case CustomerFieldIndex.Address1:
                    // Address 1 is required
                    if (((string)value).Trim().Length == 0)
                    {
                        isValid = false;
                        involvedEntity.SetEntityFieldError(CustomerFields.Address1.Name, "Address 1 is required", false);
                    }
                    break;
                case CustomerFieldIndex.City:
                    // City is required
                    if (((string)value).Trim().Length == 0)
                    {
                        isValid = false;
                        involvedEntity.SetEntityFieldError(CustomerFields.City.Name, "City is required", false);
                    }
                    break;
                    //and so forth.....
            }
            return isValid;
        }

This works great using the ErrorProvider on my WinForm. All errors appear next to my bound controls as needed.

Now, when I try to save the CustomerEntity with broken rules, I want to display a message reiterating the fact that there are broken rules. So, I was going to build one exception and throw it containing each broken rule.

However, I'm having a hard time figuring out how to use DataErrorInfoErrorsPerField to iterate through the errors in IDataErrorInfo.

For example, this isn't available:

        public override void ValidateEntityBeforeSave(IEntityCore involvedEntity)
        {
            //following line throws compile error: 
            //  'SD.LLBLGen.Pro.ORMSupportClasses.EntityBase.DataErrorInfoErrorsPerField' is inaccessible due 
            //  to its protection level

            Dictionary<string,string> errors = ((CustomerEntity)involvedEntity).DataErrorInfoErrorsPerField;

            foreach (KeyValuePair<string,string> error in errors)
            {
                //build string containing message for each broken validation, and throw to UI
            }
        }

If DataErrorInfoErrorsPerField is protected, how can I use it in the Validation classes? Was it not meant to be used in validation classes? If not, how should I go about what I'm trying to accomplish?

Thanks in advance for your help.

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 13-Jul-2007 10:08:13   

I guess entity validation was ment to be used for general cross field validation, like Employee's ContractDate can't be greater than Today, and can't be greater than BirthDate. But if you want to also add single field's validations errors to the mix, I think you'd have to place their validation code again in the ValidateEntity method.

jader201
User
Posts: 33
Joined: 20-Mar-2007
# Posted on: 13-Jul-2007 14:56:15   

But if you want to also add single field's validations errors to the mix...

How should this be done -- or rather, how are developers doing this -- then, if it wasn't meant to be done in the entity validation?

Right now, validation in LLBL is unclear to me. Are most developers not using LLBL for basic validation (such as the examples I listed above), or are they just going about it totally differently than my example above?

I would just be happy to know that the entity is not valid due to the checks done on ValidateFieldValue, and then throw a generic "You have unresolved validations -- please resolve before continuing." But I can't seem to even be able to determine that at the ValidateEntityBeforeSave override.

Thanks again for your help.

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 13-Jul-2007 16:28:05   

Are most developers not using LLBL for basic validation (such as the examples I listed above),

On the contrary.

or are they just going about it totally differently than my example above?

I guess so. IMHO, The proccess should go as follows: 1- Validate Fields as they are being set. 2- in the GUI don't enable the user to Save unless all fields are valid. 3- When Save is enabled, do general validation accross the entity before commiting the save.

Now if you wish to skip step (2), and you want the entityValidation logic to check upon each field validity, you should include the FieldValidation code again inside the entity validation code. (i.e. manuall check each fields value for valid inputs).

jader201
User
Posts: 33
Joined: 20-Mar-2007
# Posted on: 13-Jul-2007 23:13:21   

Thanks again, Walaa, for your feedback so far. Things are starting to make more sense (slowly but surely simple_smile ).

After some thought and tweaking, I came up with the solution below. I think this is getting closer to what you're saying, but not sure.

VaidatorClass looks like this:

        public override void ValidateEntityBeforeSave(IEntityCore involvedEntity)
        {
            //Make sure entity is validated prior to saving.
            involvedEntity.ValidateEntity();
        }

        public override bool ValidateFieldValue(IEntityCore involvedEntity, int fieldIndex, object value)
        {
            bool isValid = true;
            switch ((CustomerFieldIndex)fieldIndex)
            {
                case CustomerFieldIndex.Address1:
                    // Address 1 is required
                    if (string.IsNullOrEmpty((string)value))
                    {
                        isValid = false;
                        involvedEntity.SetEntityFieldError(CustomerFields.Address1.Name, "Address 1 is required", false);
                    }
                    else
                    {
                        // Address 1 cannot exceed 50 characters
                        if (((string)value).Trim().Length > 50)
                        {
                            isValid = false;
                            involvedEntity.SetEntityFieldError(CustomerFields.Address1.Name, 
                                "Address 1 cannot exceed 50 characters", false);
                        }
                    }
                    break;

                //And a whole bunch of additional field validation similar to the above

            }
            return isValid;
        }

And my Entity class looks like this:

        public override void ValidateEntity()
        {
            StringBuilder sb = new StringBuilder();

            for (int index = 0; index < (int)CustomerFieldIndex.AmountOfFields; index++)
                this.Validator.ValidateFieldValue(this, index, this.GetCurrentFieldValue(index));
            
            foreach (KeyValuePair<string, string> error in this.DataErrorInfoErrorsPerField)
            {
                if (sb.Length == 0)
                    sb.AppendLine("The following validations have failed:");

                sb.AppendLine("- " + error.Value);
            }

            if (sb.Length > 0)
                throw new ORMEntityValidationException(sb.ToString(), this);
        }

What I can do, now, is explicitly call ValidateEntity prior to Save to ensure all validation is passed before attempting to Save, or just let the ValidateEntityBeforeSave fire whenever an attempt to Save is made, and respond to the user accordingly.

But to do what you suggest (and only enable the Save button when it is valid), at what point would ValidateEntity be called? I'm using the EntityBase.PropertyChanged and EntityContentsChanged events don't seem to fire if an invalid value is entered.

Regardless, I'm able to validate it, though it's not how you're proposing. So I'm now stuck on a new issue.

I'll give you a scenario.

Let's say I load a valid customer on my form, and then change First Name from a valid value to an invalid value (empty, for example), and then tab to the Last Name field. The ErrorSource receives the broken validation and displays the error on my form, as expected ("First Name is required."). I also clear out Last Name and tab to Address 1, which also shows the "Last Name is required." validation error. First Name and Last Name are still blank on my form.

Now let's say I change Address 1 from a valid value to a different -- but still valid -- value, and then tab to Address 2. When I do this, First Name and Last Name both reset to the original values. Additionally, the Error Validations for both fields are still displayed.

This behavior seems odd, maybe even buggy.

Ideally, the invalid values would remain, since after all, that's what the validation display is for -- to tell the user, "Hey, you have some invalid stuff -- you can't proceed until you fix it." Resetting it back to the previous (valid) value could be undesirable, because the user has lost the data they just tried to enter, rather than having the option to correct it themselves. Is this reset due to the functionality of .NET's ErrorSource and databinding, or is this happening in the LLBL code?

I tried working around this by not returning false for failed validations on ValidateFieldValue, but doing that prevents the ErrorSource from getting the failed validation and displaying the error.

Is there no way to get this to display an error but leave the invalid entry intact? I searched for a solution, and run across this thread:

http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=8410

Which is identical to my problem, but the solution you posted would require moving the validation to (or having redundant validation at) the presentation layer, which is definitely not desirable. This is basic validation that I want at the DAC level (anything using this Entity should pass these validations), and would like to set up databinding to take advantage of this validation without having to have redundant validations at other layers. And this is exactly how it's working for me, right now, except for this one minor (yet show-stopping) annoyance that the invalid values are getting reset.

Is there no other option?

Thanks again for your help.

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 16-Jul-2007 10:59:41   

But to do what you suggest (and only enable the Save button when it is valid), at what point would ValidateEntity be called? I'm using the EntityBase.PropertyChanged and EntityContentsChanged events don't seem to fire if an invalid value is entered.

I think if you have CausesValidation button property set to true, the button won't call its handler method unless there were no validation controls shouting out in the form.

Ideally, the invalid values would remain, since after all, that's what the validation display is for -- to tell the user, "Hey, you have some invalid stuff -- you can't proceed until you fix it." Resetting it back to the previous (valid) value could be undesirable, because the user has lost the data they just tried to enter, rather than having the option to correct it themselves. Is this reset due to the functionality of .NET's ErrorSource and databinding, or is this happening in the LLBL code?

You may raise an exception instead of returning FALSE in the ValidateFieldValue method. Returning FALSE won't let the field to be set to the invalid value.

jader201
User
Posts: 33
Joined: 20-Mar-2007
# Posted on: 16-Jul-2007 17:50:13   

You may raise an exception instead of returning FALSE in the ValidateFieldValue method. Returning FALSE won't let the field to be set to the invalid value.

The problem with this is that it doesn't allow me to continue until I fix the value.

Ideally, I want it to work like this:

1) User enters invalid data 2) User tabs to next field 3) Error is shown, invalid data remains in prior field 4) User can continue entering data in remainder of form 5) User is not able to save until all validation passes

This is the way I've always coded my validation. Am I not able to do this using ValidateFieldValue?

To me, it seems I have only two options:

1) Return false on ValidateFieldValue method if validation fails.

Results: Allow the user to continue, but reset the invalid data back to original value, and leave the validation error showing.

Problems with this solution: a) Entered value is lost if the value is invalid b) Even though invalid value is reset back to original valid value, value remains invalid value on form until user changes another field on form from valid value to a different valid value c) Error is shown, despite the fact that the value is reset back to a valid value, and remains even when the form refreshes the value back to its original valid value

2) Always return true on ValidateFieldValue method, but throw error if validation fails.

Results: User is prevented from doing anything (including closing form) until valid value is entered.

Problems with this solution: a) User has no choice but to enter valid value before carrying out any other action in the application.

Are these my only two options for using ValidateFieldValue and an ErrorProvider?

Thanks again.

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 17-Jul-2007 12:09:58   

OK so maybe you don't need to use EntityFieldValidation, and rather just use EntityValidation, so you can validate the entity when the user hits the save button. And then you can use SetEntityError to set the DataErrorInfoError property which can be used by an Error Provider in the form.

In the entityValidation logic you should check on all the fields and formulate the error message that should be set using SetEntityError method.