[WPF] Input Validation in mvvm mode (idataerrorinfo + dataannotations)

Source: Internet
Author: User
Tags case statement
[Preface]
Windows Presentation Foundation (WPF) has a rich data binding system. In addition to being the key driver for loose coupling of UI definitions by supporting logic and data in Model-View-viewmodel (mvvm) mode, the data binding system also provides powerful and flexible support for business data verification solutions. The Data Binding Mechanism in WPF includes multiple options to verify the validity of input data when creating an editable view.

Verification Mechanism Description
Exception By setting the validatesonexceptions attribute on a binding object, if an exception is thrown when you try to set a modified value for the source object attribute, a verification error is set for the binding.
Validationrules The binding class has an attribute used to provide a set of instances of the validationrule derived class. These validationrules must overwrite a validate method, which is called by binding each time the data in the bound control is changed. If the validate method returns an invalid validationresult object, a verification error is set for the binding.
Idataerrorinfo By implementing the idataerrorinfo interface on the bound data source object and setting the validatesondataerrors attribute on the binding object, binding calls the idataerrorinfo API exposed from the bound data source object. If a non-null or non-null string is returned from these properties calls, a verification error is set for the binding.

When you use data binding in WPF to present business data, you should usually use the binding object to provide a data pipeline between a single property of the target control and the property of the data source object.
To make binding verification valid, bind twoway data first. This means that, in addition to the data displayed by the source attribute to the target attribute, the edited data is also directed from the target stream to the source.
When data is input or modified in twoway data binding, the following workflow is started:

  • You can input or modify data by typing, clicking, touching, or interacting with each element to change the attributes of an element.
  • If necessary, you can convert the data to the data source attribute type.
  • Set the source property value.
  • Triggers the binding. sourceupdated additional event.
  • If an exception is thrown by the setter in the data source attribute, the exception is captured by the binding and can be used to indicate a verification error.
  • If the idataerrorinfo interface is implemented, the method that calls this interface on the data source object will get the error information of this attribute.
  • Presents a verification error indication to the user and triggers an additional event of validation. Error.

This article describes idataerrorinfo verification in mvvm mode and how it works with dataannotation. In comparison, the direct throw exception implementation in set is the easiest, but it is not suitable for combination verification and the verification code in the model is too heavy; validationrules is more suitable for use in user controls or custom controls. Idataerrorinfo is a common and flexible verification implementation method.

[Instance analysis]
Let's take a look at the idataerrorinfo implementation method: the error attribute is used to indicate the error of the entire object, and the indexer is used to indicate the error of a single attribute level.
The two work in the same way: if a non-null or non-null string is returned, a verification error exists. Otherwise, the returned string is used to display errors to users.

The age attribute of person. CS is verified.

Public class person: inotifypropertychanged, idataerrorinfo {private string _ name; Public string name {get {return _ name;} set {If (_ name! = Value) {_ name = value; raisepropertychanged ("name") ;}} private int _ age; Public int age {get {return _ age ;} set {If (_ age! = Value) {_ age = value; raisepropertychanged ("Age") ;}} public string error {get {return "";}} public String This [String columnname] {get {If (columnname = "Age") {If (_ age <18) {return "the age must be over 18 years old. ";}} Return string. Empty ;}} public event propertychangedeventhandler propertychanged; internal virtual void raisepropertychanged (string propertyname) {If (propertychanged! = NULL) {propertychanged (this, new propertychangedeventargs (propertyname ));}}}

Xmal binding:

<TextBox Text="{Binding Age, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" Grid.Row="1" Grid.Column="1" Margin="5"/>

(Note: The default updatesourcetrigger of textbox is lostfocus. If you want to verify the content as soon as the content changes, you need to change it to propertychanged)
An error message is displayed when an invalid age is entered.

The advantage of idataerrorinfo is that it can be used to easily process cross-coupling attributes. But it also has a major drawback:
The implementation of the indexer usually leads to a large switch-case statement (each attribute name in the object corresponds to one condition ),
Must be switched and matched based on strings, and a string indicating an error is returned. Besides, the implementation of idataerrorinfo is not called until the property value is set on the object.

To avoid a large number of switch-cases and separate the verification logic to improve code reuse, dataannotations was launched.
Modify the above person class and add [range] validationattribute :( you need to add system. componentmodel. dataannotations. dll)

[Range (19, 99, errormessage = "age must be over 18 years old. ")] Public int age {get {return _ age;} set {If (_ age! = Value) {_ age = value; raisepropertychanged ("Age ");}}}

Modify the idataerrorinfo indexer to pass validator to verify the attributes:

 public string this[string columnName] {     get      {         var vc = new ValidationContext(this, null, null);         vc.MemberName = columnName;         var res = new List<ValidationResult>();         var result = Validator.TryValidateProperty(this.GetType().GetProperty(columnName).GetValue(this, null), vc, res);         if (res.Count > 0)         {             return string.Join(Environment.NewLine, res.Select(r => r.ErrorMessage).ToArray());         }         return string.Empty;     } }

With dataannotions, the model is more concise and the verification is more flexible. You can also use customervalidation or custom validationattribute to further separate the validation logic and format the error message. Through reflection and other technologies, idataerrorinfo can be fully extracted into an abstract class for encapsulation, making programming more convenient.

(1) customize validationattribute
Added a check for the existence of the name attribute for the person above:

Class nameexists: validationattribute {public override bool isvalid (object Value) {var name = value as string; // you can retrieve if (name! = "Felix") {return false;} return true;} public override string formaterrormessage (string name) {return "enter an existing user name. ";}}

Add nameexistsattribute to the person. Name attribute:

[NameExists]public string Name{    get { return _name; }    set     {        if (_name != value)        {            _name = value;            RaisePropertyChanged("Name");        }    }}

(2) Use customervalidationattribute
First implement a public static verification method (validationresult must be returned)

Public class customervalidationutils {public static validationresult checkname (string value) {If (value. Length <8) {return New validationresult ("the name length must be greater than or equal to 8 characters. ") ;}Return validationresult. Success ;}}

Then add the customervalidation feature to the name attribute of person:

[CustomValidation(typeof(CustomerValidationUtils), "CheckName")]public string Name{    get { return _name; }    set     {        if (_name != value)        {            _name = value;            RaisePropertyChanged("Name");        }    }}

In actual development, we often use ORM such as EF for data access layer. model is usually automatically generated by this middleware (using code generation tools such as T4 ). They are usually poco data types. How can we add the attribute validation feature to this data type. In this case, typedescriptor. addprovidertransparent + associatedmetadatatypetypedescriptionprovider can be used to enhance the metadata description of the original type by adding the corresponding verification feature to another class. According to this idea, the above person class is separated into two files: The first separation class, which can be imagined as the model class automatically generated by the middleware. Idataerrorinfo is implemented in the second separation class, And a metadata class is defined to add the verification feature. (EF codefirst can also use this idea)

Person. CS (native person class, without verification features)

public partial class Person : INotifyPropertyChanged{    private string _name;    public string Name    {        get { return _name; }        set         {            if (_name != value)            {                _name = value;                RaisePropertyChanged("Name");            }        }    }    private int _age;       public int Age    {        get { return _age; }        set        {            if (_age != value)            {                _age = value;                RaisePropertyChanged("Age");            }        }    }    public event PropertyChangedEventHandler PropertyChanged;    internal virtual void RaisePropertyChanged(string propertyName)    {        if (PropertyChanged != null)        {            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));        }    }}

Personmetadata. CS (separated person class, implements the idataerrorinfo interface, in its internal classAdded verification features)

Public partial class person: idataerrorinfo {class personmetadata {[required] [nameexists] [customvalidation (typeof (customervalidationutils), "checkname")] public string name {Get; set ;} [range (19, 99, errormessage = "age must be over 18 years old. ")] Public String age {Get; set;} public string error {get {Throw new notimplementedexception () ;}} Public String This [String columnname] {get {return this. validateproperty <personmetadata> (columnname );}}}

The validateproperty method is an extension method based on the object type. Use the wildcard <metadatatype> to specify the type of enhancement information.

Public static class validationextension {public static string validateproperty
 
  
(This object OBJ, string propertyname) {If (string. isnullorempty (propertyname) return string. empty; var targettype = obj. getType (); // you can use metadatatype to declare // var targetmetadataattr = targettype on the separation class. getcustomattributes (false )//. firstordefault (A =>. getType () = typeof (metadatatypeattribute) as metadatatypeattribute; // If (targetmetadataattr! = NULL & targettype! = Targetmetadataattr. metadataclasstype) // {// typedescriptor. addprovidertransparent (// new Runtime (targettype, targetmetadataattr. metadataclasstype), targettype); //} If (targettype! = Typeof (metadatatype) {typedescriptor. addprovidertransparent (New associatedmetadatatypetypedescriptionprovider (targettype, typeof (metadatatype), targettype);} var propertyvalue = targettype. getproperty (propertyname ). getvalue (OBJ, null); var validationcontext = new validationcontext (OBJ, null, null); validationcontext. membername = propertyname; var validationresults = new list
  
   
(); Validator. tryvalidateproperty (propertyvalue, validationcontext, validationresults); If (validationresults. Count> 0) {return validationresults. First (). errormessage;} return string. Empty ;}}
  
 

Using this idea, you can easily implement the validation logic separation of various poco model types, which is very useful for developing the application framework of the layered architecture.

[Source code download]

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.