Validate winform bằng SpecExpress

SpecExpress là một Fluent validation framework, trong bài viết này, tôi xin giới thiệu cách validate Winform bằng SpecExpress và Error provider.

Xem giới thiệu về SpecExpress.

Trước hết tạo một Entity kế thừa từ IDataErrorInfo interface.

image

Sau đó tạo một dictionary đẻ lưu lại các message lỗi của các Property trong Entity đó.

   1: /// <summary>

   2:        /// Dictionary of validation errors, it contains Property and Error messages...

   3:        /// </summary>

   4:        public Dictionary<string, string> Errors

   5:        {

   6:            get;

   7:            set;

   8:        }

 

Việc tiếp theo là implement interface IDataError: index this và thuộc tính Error:

   1: public string Error

   2:        {

   3:            get

   4:            {

   5:                return lastError; 

   6:            }

   7:              

   8:        }

index this[propertyName] trả về Error message cho Error Provider

 

   1: /// <summary>

   2:     /// Gets the error of a specific property

   3:     /// </summary>

   4:     /// <param name="propertyName">The property name</param>d

   5:     /// <returns>The error message</returns>

   6:     public string this[string propertyName]

   7:     {

   8:         #region Doan Manh Ha commented for write new Error validation method by Specexpress

   9:         //get { return _propError[propertyName]; }

  10:         //set 

  11:         //{

  12:         //    _propError[propertyName] = value;

  13:         //    PropertyHasChanged(); 

  14:         //}

  15:         #endregion

  16:  

  17:         get

  18:         {

  19:             if (!IsNeedValidation)

  20:             {

  21:                 if (Errors!=null && Errors.ContainsKey(propertyName))

  22:                     return Errors[propertyName];

  23:                 else

  24:                     return string.Empty;

  25:             }

  26:  

  27:             var notification = this.ValidateProperty(propertyName);

  28:  

  29:             

  30:  

  31:             if (notification==null || notification.IsValid)

  32:             {

  33:                 lastError = string.Empty;

  34:                

  35:             }

  36:             else

  37:             { 

  38:                 // TODO: aggregate notification messages in readable form and assign to lastError 

  39:                 lastError = this.BuildErrorMessages(notification.ErrorSummary());

  40:             }

  41:  

  42:             AddError(propertyName, lastError);

  43:             return lastError;

  44:         }

  45:         set

  46:         {

  47:             AddError(propertyName, value);

  48:         }

  49:  

  50:     }

– IsNeedValidation là một thuộc tính xác định xem có cần phải validate không.

– Validate chính là Virtual method để validate property đó, trả về ValidationNotification

   1: protected virtual ValidationNotification ValidateProperty(string propertyName)

   2:     {

   3:         throw new NotImplementedException();

   4:     }

AddError là hàm add nội dung Error vào Error dictionary:

   1: private void AddError(string propertyName, string error)

   2:         {

   3:             //lastError = value;

   4:             if (Errors == null)

   5:                 Errors = new Dictionary<string, string>();

   6:  

   7:             if (Errors.ContainsKey(propertyName))

   8:                 Errors[propertyName] = error;

   9:             else

  10:                 Errors.Add(propertyName, error);

  11:         }

 

Tiếp theo ta viết Entity cho User, kế thừa từ AbstractBaseData:

 

   1: public class User:AbstractBaseData<int>

   2:    {

   3:  

   4:        private int id;

   5:        private string name;

   6:        private string password;

   7:        private string rules;

   8:  

   9:        #region Public properties

  10:  

  11:        public int Id

  12:        {

  13:            get

  14:            {

  15:                return this.id;

  16:            }

  17:            set

  18:            {

  19:                if ((this.id != value))

  20:                {

  21:                    this.id = value;

  22:                    this.OnPropertyChanged("Id");

  23:  

  24:                }

  25:            }

  26:        }

  27:  

  28:        public string Name

  29:        {

  30:            get

  31:            {

  32:                return this.name;

  33:            }

  34:            set

  35:            {

  36:                if ((this.name != value))

  37:                {

  38:                    this.name = value;

  39:                    this.OnPropertyChanged("Name");

  40:  

  41:                }

  42:            }

  43:        }

  44:  

  45:        public string Password

  46:        {

  47:            get

  48:            {

  49:                return this.password;

  50:            }

  51:            set

  52:            {

  53:                if ((this.password != value))

  54:                {

  55:                    this.password = value;

  56:                    this.OnPropertyChanged("Password");

  57:  

  58:                }

  59:            }

  60:        }

  61:  

  62:        public string Rules

  63:        {

  64:            get

  65:            {

  66:                return this.rules;

  67:            }

  68:            set

  69:            {

  70:                if ((this.rules != value))

  71:                {

  72:                    this.rules = value;

  73:                    this.OnPropertyChanged("Rules");

  74:  

  75:                }

  76:            }

  77:        }

  78:  

  79:        public IList<Role> Roles

  80:        {

  81:            get;

  82:            set;

  83:        }

  84:  

  85:        #endregion

  86:  

  87:        #region Validations

  88:  

  89:        UserSpecification spec = new UserSpecification();

  90:  

  91:        protected override ValidationNotification ValidateProperty(string propertyName)

  92:        {

  93:  

  94:            try

  95:            {

  96:  

  97:                return ValidationCatalog.ValidateProperty(this, propertyName, spec);

  98:            }

  99:            catch (Exception)

 100:            {

 101:                return null;

 102:            }

 103:        }

 104:  

 105:        public override bool Validate()

 106:        {

 107:           

 108:            this.IsNeedValidation = true;

 109:           

 110:            this.PropertyHasChanged();

 111:            this.IsNeedValidation = false;

 112:            return this.IsValid;

 113:        }

 114:        #endregion

 115:    }

 

Và Specification cho User entity:

   1: public class UserSpecification:Validates<User>

   2:   {

   3:       public UserSpecification()

   4:       {

   5:           Check(user => user.Name).Required(ValidationConstants.USER_NAME_REQ);

   6:  

   7:           Check(user => user.Password).Required(ValidationConstants.PASSWORD_REQ)

   8:                                       .MinLength(6);

   9:  

  10:           Check(user => user.Roles).Required(ValidationConstants.USER_ROLE_REQ);

  11:       }

  12:   }

Xử lý ở Winform UI

image

Sự kiện cho button Cập nhật:

   1: private void btnUpdate_Click(object sender, EventArgs e)

   2:    {

   3:        string typeOfUserViewData = typeof(UserViewData).Name;

   4:        UserViewData userViewData = this.GetSessionData(typeOfUserViewData) as UserViewData;

   5:        

   6:        var isValid = userViewData.User.Validate();

   7:        

   8:        //Récupération de la VSD

   9:        if (!isValid)

  10:        {

  11:            epUserView.DataSource = userViewData.User;

  12:            return;

  13:        }

  14:  

  15:        

  16:        userViewData.IsCreation = false;

  17:        //Transfert de l'exécution vers le controleur

  18:        //this.InvokeController(ControllerConstants.USER_CONTROLLER, ControllerActionConstants.SAVE_ACTION);   

  19:    }

Và đây là kết quả:

image

SpecExpress – A fluent validation

SpecExpress là một fluent API để validate dữ liệu có hợp lệ hay không.

SpecExpress không dùng các Custom attribute để decorator thuộc tính như các Validation framework khác (ví dụ: Validation application block,…), mà SpecExpress dùng riêng một lớp gọi là Specification, lớp này sẽ chứa các rule cho tất cả các thuộc tính cần validate. Việc này giúp cho code dễ đọc hơn (vì dùng Fluent API) và code cũng dễ maintaince hơn.

Ví dụ:

   1: public class UserSpecification:Validates<User>

   2:   {

   3:       public UserSpecification()

   4:       {

   5:           Check(user => user.Name).Required(ValidationConstants.USER_NAME_REQ);

   6:  

   7:           Check(user => user.Password).Required(ValidationConstants.PASSWORD_REQ)

   8:                                       .MinLength(6);

   9:  

  10:           Check(user => user.Roles).Required(ValidationConstants.USER_ROLE_REQ);

  11:       }

  12:   }

Class này kế thừa từ class Validates<T> của SpecExpress.

Constructor định nghĩa các rule để validate Entity User: Cần phải có Tên đăng nhập, cần phải có mật khẩu tối thiểu 6 ký tự, và User cần phải có ít nhất một Role.

Chi tiết hơn, các bạn xem tại đây.

AutoMapper – a fluent configuration API to define an object-object mapping strategy

Trong bài lần trước nói về fluent interface. Lần này mình giới thiệu 1 API theo hướng “fluent” dùng để map giữa các object với nhau.

Chi tiết về AutoMapper, bạn có thể xem tại đây.

Ta đi vào ví dụ cho dễ hình dung.

 

Giả sử bạn có StudentRepository để lấy về chi tiết sinh viên.

  public override Student GetItem(int id)
  {
           

  }

Bạn dùng LINQ2SQL để lấy về chi tiết sinh viên như sau:

var context = new StudentCardDBMappings.StudentCardDBDataContext();

context.Connection.Open();

var studentLinq = (from s in context.Students
                   where s.Id.Equals(id)
                   select s).FirstOrDefault();

 

Nhưng LINQ2SQL sẽ trả về Entity mà Linq2Sql sinh ra, không phải Entity của ta mong đợi (Entity mong đợi có thể là DTO, custom entity mà support validating hoặc là business object,… ). Do đó ta phải convert từ Entity  của Linq2Sql sang Entity của ta:

 

1) Cách cổ điển: Gọi Set, Get của các thuộc tính của entity:

 var student = new Student
          {
              Id = studentLinq.Id,
              Name = studentLinq.Name,
              MidleName = studentLinq.MidleName
          };

2) Dùng AutoMapper: trong trường hợp này, AutoMapper sẽ tự động map các thuộc tính cho các entity ta cần dùng. Dùng AutoMapper như sau:

a) Config source và destination cần phải map

 Mapper.Initialize
         (
             cfg => cfg.CreateMap<StudentCardDBMappings.Student, Student>()
         );

b) Gọi hàm map để AutoMapper tự động map các thuộc tính của source và destination (trường hợp này là mặc đinh,..)

var student = Mapper.Map<StudentCardDBMappings.Student, Student>(studentLinq);

Kết quả, nếu dùng AutoMapper ta có hàm như sau:

public override Student GetItem(int id)
{


	var context = new StudentCardDBMappings.StudentCardDBDataContext();
	
	context.Connection.Open();
	
	var studentLinq = (from s in context.Students
						where s.Id.Equals(id)
						select s).FirstOrDefault();
	
	//var student = new Student
	//        {
	//            Id = studentLinq.Id,
	//            Name = studentLinq.Name,
	//            MidleName = studentLinq.MidleName
	//        };
	
	Mapper.Initialize
		(
			cfg => cfg.CreateMap<StudentCardDBMappings.Student, Student>()
		);
	
	var student = Mapper.Map<StudentCardDBMappings.Student, Student>(studentLinq);
	
	return student;

}

Và đây là kết quả Unit Test:

– Test Null

image

– Test có giá trị:

image

Chi tiết về AutoMapper, mời các bạn tham khảo tại: http://automapper.codeplex.com/