Convert Validator from EVil to DataAnnotation

We were using Evil Validation library for a while, works OK, but doesn’t support WPF/Silverlight and MVC very well. Fortunately, it’s not too hard to convert it to DataAnnotations.

Evil code example:


    class EmailAddress
    {
        public EmailAddress(string value)
        {
            Value = value;
        }

        [ValidateRegex(@"\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}", "Invalid recipient email address")]
        public string Value { get; set; }

    }

        public static bool ValidateEmailAddress(string emailAddress)
        {
              return ((new EmailAddress(emailAddress)).IsValid());
        }

To DataAnnotation:


    class EmailAddress
    {
        public EmailAddress(string value)
        {
            Value = value;
        }

        [RegularExpression(@"\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}", ErrorMessage = "Invalid recipient email address")]
        public string Value { get; set; }

    }

       public static bool ValidateEmailAddress(string emailAddress)
        {

            EmailAddress emailToValidate = new EmailAddress(emailAddress);

            // Watchout the last bool flag, indicate whether validate all properties. (What else? just modified one?)
            return Validator.TryValidateObject(emailToValidate, new ValidationContext(emailToValidate, null, null), null, true);

            // For one property model, we can also use this syntax.
            ValidationContext validationContext = new ValidationContext(emailToValidate, null, null);
            validationContext.MemberName = "Value";
            return Validator.TryValidateProperty(emailAddress,
                validationContext
                , null);

        }
Advertisements

Rewrap Evil ValidationErrors in WPF ValidationRule to enable ajax like validation on WPF Form

We use Evil Attribute library in our service and UI/ViewModel to do validation on DTO, the big advantage is centralizing the validation rules. One challenge left is how to enable WPF Forms dynamically showing the error message, like ajax doing on web.

Found a very smart and simple solution for this, what we need to do is simply to wrap the Evil ValidationErrors in WPF ValidationRule class.

<Window.Resources>
 <ControlTemplate x:Key="TextBoxErrorTemplate">
 <StackPanel>
 <StackPanel Orientation="Horizontal">
 <Image Height="16" Margin="0,0,5,0"
 Source="warning_48.png"/>
 <AdornedElementPlaceholder x:Name="Holder"/>
 </StackPanel>
 <Label Foreground="Red" Content="{Binding ElementName=Holder,
 Path=AdornedElement.(Validation.Errors)&#91;0&#93;.ErrorContent}"/>
 </StackPanel>
 </ControlTemplate>
 </Window.Resources>
 <Grid>
    ...

  <TextBox Height="23" Margin="35,9,77,0" Name="textBox1" VerticalAlignment="Top"
     KeyUp="OnTextBoxKeyUp"
     Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate}">
     <TextBox.Text>
    <Binding ElementName="listBox1" Path="SelectedItem" UpdateSourceTrigger="PropertyChanged">
      <Binding.ValidationRules>
        <ViewModels:EmailRoleRule />
      </Binding.ValidationRules>
    </Binding>
  </TextBox.Text>
 </TextBox>


public class EmailRoleRule : ValidationRule
 {
  public override ValidationResult Validate(object value, CultureInfo cultureInfo)
   {
   var roleName = (string) value;
   var validator = new EvilValidator(new EmailRoleDto() { Name = roleName });
   if (validator.IsValid())
   {
    return new ValidationResult(true, null);
   }
   return new ValidationResult(false, string.Join("\n", validator.ValidationErrors.ToArray()));
  }
 }

wpf_validation

This technoque is actually described on MSDN.

Here is a post talking about the same skill but takes advantage of tooltips, pretty close to CSLA content control.

Pull up CSLA BO’s validation message

By default the CSLA BO’s brokenRulesCollection doesn’t contain child BO’s brokenrules, I tried to create a GetValidationMessages() method in MyBusinessBase class, the difficult I had was to cast and detect the generic type. Thanks to this post, I made it happen by creating an IsDerivedFromGenericType method.

I don’t need to call this:  if (child.GetType().IsSubclassOf(typeof(MyBusinessBase<>)))

Or if (child is MyBusinessBase<>)

Actually those two won’t work. This IsDerivedFromGenericType is handy.

if (IsDerivedFromGenericType(child.GetType(), typeof(MyBusinessBase<>) ) )

But it’s too complex, especially I had to call reflection to  call this GetValidationMessages recursively. Because I don’t know how to do this:

((MyBusinessBase<>)child).GetBOValidataionMessage()

Instead I had to call reflection:

        private string GetBOValidataionMessage(object v)
        {
            // Would be nice if I can cast to MyBusinessBase
            // T is an unknow onject type below, so I had to use reflection to invock method.
//            MyBusinessBase bo = (MyBusinessBase)v;
//            return bo.GetValidationMessage();

            string childValidataionMessage = "";
            // invoke method to get validation message from child BO.
            // kind of recursive call here.
            var flags =
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
            MethodInfo method = v.GetType().GetMethod("GetValidationMessage", flags);

            if (method != null)
            {
                childValidataionMessage = (string)method.Invoke(v, null);

            }
            return childValidataionMessage;
        }

In fact, it could be much easier if I just simply override the BrokenRulesCollection property. (Idea from this post.)

        public override BrokenRulesCollection BrokenRulesCollection
        {
            get
            {
                BrokenRulesCollection brokenRules = BrokenRulesCollection.CreateCollection();
                // Get root broken rules.
                brokenRules.Merge(this.ToString(), base.BrokenRulesCollection);

                foreach (var child in this.FieldManager.GetChildren())
                {
                    if (child is IEnumerable)
                    {
                        foreach (var v in (IEnumerable)child)
                        {
                                if (v is BusinessBase){
                                    brokenRules.Merge(v.ToString(), ((BusinessBase) v).BrokenRulesCollection);
                                }
                        }
                    }
                    else
                    {
                        if (child is BusinessBase)
                        {
                            brokenRules.Merge(child.ToString(), ((BusinessBase)child).BrokenRulesCollection);
                        }
                    }
                }

                return brokenRules;
            }
        }

        // BrokenRule class doesn't have an "Owner" field, which is useful in this kind of
        // multi-level object map to locate the actual error message from.
        // Fortunately, the rulename contains object name, so I can do this stupid string parse here.
        public string ExtractObjectName(string ruleName)
        {
            string result;

            // get namespace
            string spacename = this.GetType().Namespace;

            // get rid of namespace
            result = ruleName.Replace(spacename + ".", "");

            // get rid of everything after the dot
            int start = result.LastIndexOf("//") + 2;
            int end = result.IndexOf(".") ;
            result = result.Substring(start, end - start);

            return result;
        }
        public string GetValidationMessage()
        {
            StringBuilder sb = new StringBuilder();

            foreach (var x in this.BrokenRulesCollection)
            {
                 sb.AppendLine(string.Format("{0}.{1}: {2}", ExtractObjectName(x.RuleName),x.Property,  x.Description));
             }

            return sb.ToString();
        }