Complex validator combining 3 properties

Topics: Validation Application Block
Sep 2, 2010 at 2:49 PM

Hi,

I need a validator for a Belgian social security number.

2 of the rules for this number depend on other values of the person, namely sex and date of birth.

When I define my validator as SSN_Validator : Validator<Person> , it works, but i cannot use this validator as property validator,

and so it cannot trigger the error - template in my wpf form.

If I define my validator as a SSN_Validator : Validator<string> ( string being the datatype of the SSN ), I can assign this validator to the property,

and is can trigger the error-template in my wpf form, but  I cannot access the owning entity , or find any other way to know the date of birth and sex of the person.

Is there a way for the first validator to trigger the correct error-template, ( MVVM pattern in WCF scenario)

 or for the second to access the date of birth and sex of the person ?  (Validation Configuration in xml files )

 

Thanx for any feedback.

Sep 3, 2010 at 12:56 AM

Why not simply add a property in your SSN_Validator and SSN_ValidatorAttribute that will hold the error template? 

 

Sarah Urmeneta
Global Technology and Solutions
Avanade, Inc.
entlib.support@avanade.com

Sep 3, 2010 at 6:58 AM

Sarah,

never even heard of the concept, that's why :-),

I will have to look around to see what that actually means, any chance of pointing to some examples that do that ?

 

 

thnx

Sep 3, 2010 at 7:13 AM

If you have the 4.1 version of the enterprise library quickstart, there are two validator attributes included there.  If you don't, here's a sample of what your SSNValidatorAttribute looks like:

public class SSNValidatorAttribute: ValidatorAttribute
{
    private string errorMessage;

    public SSNValidatorAttribute(string errorTemplate)
    {
        this.errorMessage = errorTemplate;
    }

    protected override Validator DoCreateValidator(Type targetType)
    {
        return new SSN_Validator(errorMessage);
    }
}

When decorating then your class with the SSNValidator attribute, you'll be able to specify then the error template  you want:

[SSNValidator("Error template....")]
public class Person
{
          .....
}

Does this satisfy your requirement?

 

Sarah Urmeneta
Global Technology and Solutions
Avanade, Inc.
entlib.support@avanade.com

Sep 3, 2010 at 7:33 AM

Hi,

thx for the clarification,

but i dont think does helps me :  I am using the config file way of validation, not the attribute way.

and what i meant with triggering the error template, was that i want the error - control template ( wpf ) to indicate what control is involved in the validation error,

not what message is shown.

My control template is  a default : 

<ControlTemplate x:Key="validationTemplate">

<DockPanel><Border BorderBursh="Red" BorderThickness="1"><AdornedElementPlaceHolder/></Border> <Image wit Tooltip...+>

</ControlTemplate>

<Style TargetType="TextBox" ><Setter Property="Validation.ErrorTemplate" Value="{StaticResource validationTemplate}"></Style>

 

 



Sep 3, 2010 at 8:39 AM

When you override the DoValidate method the currentTarget property will normally contain the instance of the entity you are trying to validate. In your situation Person. This will also work when defining a validator on a property. Your code may look like this:

public SSN_Validator : Validator<string>
{
  protected override void DoValidate(string objectToValidate, object currentTarget, string key, ValidationResults validationResults)
  {
    string ssn = objectToValidate;
    Person p = (Person)currentTarget;

    if (!SSNHelpers.SSNIsValid(ssn, p.DateOfBirth, p.Sex))
    {
       this.LogValidationResult(validationResults, "SSN invalid", currentTarget,key);

    }
  }
}

Note however that there are situations where the currentTarget does not hold the entity of the property you validate. For instance when using the PropertyProxyValidator of the ASP.NET integration assembly. When validating properties using this validator, the entity does not exist and the current target is the used PropertyProxyValidator. I don't know if that also holds in with WPF, but it is easy for you to find out, because the above code snippet will fail with a TypeCastException.

Good luck.

Sep 3, 2010 at 9:57 AM

hi : dot_net_junkie

I treid that before, and it didnt work in my main project.

I tried it again now, in a test application and it works fine there.

The difference between the 2 being : my main project  has 2 layers, joined by WCF, the Person objects being Self Tracking EF Entities .

My test application only uses WPF and Validation Application Block, the Person object being a quickly written POCO object initialized in code.

 

In my main project, the currentTarget contains : {Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WPF.ValidatorRule.ExternalValue}, which contains the same value as the objectToValidate

I fear this is a WCF induced problem ? ( even if the validation occurs in the UI layer, ? dont really understand why WCF would even be involved ?

Sep 3, 2010 at 12:45 PM

The WPF integration seems to work the same way as the ASP.NET integration does (no surprise of course).The thing is that you can validate the value of some WPF control using the rules for a certain property in your domain.  This integration mechanism is designed around validating single values. When you think about it, the design is obvious, because this allows you to have an interface that only allows to insert a single property of an entity, without you needing the complete object at that moment. Especially when you have an extra layer that returns DTOs instead of the actual domain objects, this will be very useful.

This however, doesn't solve your problem. I'm afraid that what you are trying to achieve is not supported by VAB out of the box. However.... there is always a way to do this. Here is an idea. You can try to inherit from the Integration.WPF.ValidatorRule class. What you must do two things. 1. Let this inherited class have a new property that you can use to set the entity to validate. 2. Override the Validate method so that you can send this entity to the created validator.

I must admit I have little experience with WPF and no experience with integrating it with VAB, but I came up with the next code that could give you a head start (it compiles, but I'm not sure if this will work for you):

public class EntityValidatorRule : ValidatorRule
{
    public object Entity { get; set; }

    public override System.Windows.Controls.ValidationResult Validate(
        object value, CultureInfo cultureInfo)
    {
        Validator validator = this.GetValidator();
        
        if (validator == null)
        {
            return System.Windows.Controls.ValidationResult.ValidResult;
        }

        ValidationResults results = new ValidationResults();

        validator.DoValidate(value, this.Entity, null, results);
        
        if (results.IsValid)
        {
            return System.Windows.Controls.ValidationResult.ValidResult;
        }

        var errorContent = results
            .Select(vr => vr.Message)
            .Aggregate((acc, message) => acc + Environment.NewLine + message);

        return new System.Windows.Controls.ValidationResult(false, errorContent);
    }

    private Validator GetValidator()
    {
        // A little bit of magic here, because ValidatorRule contains 
        // a private GetValidor method.
        return (Validator)typeof(ValidatorRule).GetMethod("GetValidator")
            .Invoke(this, null);
    }
}

I hope this helps.

 

Sep 3, 2010 at 1:20 PM

Hi,

I will get back on this,  but it looks 'different' :)

 

thnx

 

Stijn

Sep 4, 2010 at 7:05 PM

Hi ,

there are 2 problems,

1 : for unknow reasons the typeof(ValidatorRule).GetMethod("GetValidator")
returns null, I reflected the class 'ValidatorRule' and the method name is like it should be, so dunno whats wrong ( also tried adding the attrib flags nonpublic and instance, but no better).

2 Cannot set the Entity object to the 'Person' object, becus i cannot bind on that property, and cannot make it a dependency property, becus de validatorrule class isnt a dependency object.

 

Stijn

Sep 5, 2010 at 9:33 AM

Stijn,

1. I'm sorry, you will indeed need the BindingFlags. Using the NonPublic and Instance BindingFlags should work, or at least, it will return the MethodInfo. This example would correctly call the private ValidatorRule.GetValidator() method:

private Validator GetValidator()
{
    return (Validator)typeof(ValidatorRule)
        .GetMethod("GetValidator", BindingFlags.Instance | BindingFlags.NonPublic)
        .Invoke(this, null);
}
2. I have too little experience with WPF. Dependency properties and dependency objects are a bit abracadabra to me. Can't you register the Entity property in your code (just like we can do with ASP.NET in the code behind)?

Sep 9, 2010 at 12:00 PM

Hi,

 

the WPF integration didnt support it,

I solved it by using the ErrorProvider by Paul Stovell, to trigger the validation, where it works.

 

 

Thx for the help and tips.