UI Validation with the MVP pattern. Part #03

As we discussed in the previous posts, MVP UI Validation 01 and MVP UI Validation 02, thanks to the Microsoft enterprise library, we can easily handle a perfect and professional UI Validation without leaving the MVP pattern in our Client applications.

Today I want to show you a final solution, that will handle in a professional way the UI validation.

The basic validation, refractor process.

In order to view what I am going to do today, you MUST read the previous articles related to the UI validation with the enterprise library. Actually we have our domain object that is in this way:

image

We need to do some refactoring, in order to use our validation process in a better way. First of all let’s create a new entity called ErrorResult that will contain some useful information and, of course, its custom collection.

image

Now let’s go back to the Domain Object entity and let’s do some refactoring. First of all we need to change the ListResults in this way:

  1: /// <summary>
  2: /// Custom collection used to interact with the UI
  3: /// </summary>
  4: public ErrorResultCollection ListResults 
  5: { 
  6:     get; set; 
  7: }

Then we need to change the validation function. We still declare the validation results collection:

  1: Validator validator = 
  2:    ValidationFactory.CreateValidator(
  3:       Type.GetType(this.ToString())
  4:    );
  5: ValidationResults errors = validator.Validate(this);

Then we verify that our custom collection is initialized:

  1: if (ListResults == null) {
  2:     ListResults = new ErrorResultCollection();
  3: }

And finally we loop the errors and we populate the custom collection in this way:

  1: foreach (var error in errors) {
  2:     ListResults.Add(
  3:         new ErrorResult() 
  4:         { 
  5:             Field = error.Key,
  6:             Target = error.Target,
  7:             ErrorMessage = error.Message
  8:         });
  9: }

The Customer Entity and its validation attributes.

Now we can have fun and build a custom entity that will inherit from the domain object abstract class and that will implement some custom validations.

  1: [NotNullValidator(
  2:     MessageTemplate="Id cannot be null.")]
  3: public int Id { get; set; }
  4: [NotNullValidator(
  5:     MessageTemplate="First name cannot be null.")]
  6: [StringLengthValidator(
  7:     1,
  8:     30,
  9:     MessageTemplate="First name leght has to be grater then 0 and lower then 31.")]
 10: public string FirstName { get; set; }
 11: [NotNullValidator(
 12:     MessageTemplate = "Last name cannot be null.")]
 13: [StringLengthValidator(
 14:     1,
 15:     30,
 16:     MessageTemplate = "Last name leght has to be grater then 0 and lower then 31.")]
 17: public string LastName { get; set; }

And we can do some simple test. The first test will verify that we respect the NotNull validation rule:

  1: [TestMethod]
  2: public void TestNotNull() {
  3:     Customer target = new Customer();
  4:     Assert.IsTrue(
  5:        target.IsValid(),
  6:        target.ListResults.ToString()
  7:     );
  8: }
  9: 

Note: I added a simple ToString() function in the custom error collection in order to have the entire output in one line, only for my tests …

  1: public override string ToString() {
  2:     StringBuilder message = new StringBuilder();
  3:     for (int i = 0; i < this.Count; i++) {
  4:         message.Append(this[0].ErrorMessage);
  5:     }
  6:     return message.ToString();
  7: }

Reflect the validation trough the Presentation layer.

Now we need to create a simple form and a custom presentation layer in order to apply the UI logic. First of all, the form. Remember to include and error component in your form:

image

Then this is the view:

  1: int Id { get; set; }
  2: string Firstname { get; set; }
  3: string Lastname { get; set; }
  4: void ShowError(string message, string controlName);
  5: void Validate(object sender, EventArgs e);
  6: void ClearErrors();

Finally, this is the implementation of the view in our Windows Form:

  1:         public int Id {
  2:             get {
  3:                 try {
  4:                     int id = Convert.ToInt32(txtId.Text);
  5:                     return id;
  6:                 } catch (Exception ex) {
  7:                     return 0;
  8:                 }
  9:             }
 10:             set {
 11:                 txtId.Text = value.ToString();
 12:             }
 13:         }
 14: 
 15:         public string Firstname {
 16:             get {
 17:                 return txtFirstname.Text;
 18:             }
 19:             set {
 20:                 txtFirstname.Text = value;
 21:             }
 22:         }
 23: 
 24:         public string Lastname {
 25:             get {
 26:                 return txtLastname.Text;
 27:             }
 28:             set {
 29:                 txtLastname.Text = value;
 30:             }
 31:         }
 32: 
 33:         public void ShowError(string message, string controlName) {
 34:             MyError.SetError(GetControl(controlName), message);
 35:         }
 36: 
 37:         private Control GetControl(string fieldName) {
 38:             switch (fieldName) {
 39:                 case "Id":
 40:                     return txtId;
 41:                 case "FirstName":
 42:                     return txtFirstname;
 43:                 case "LastName":
 44:                     return txtLastname;
 45:                 default:
 46:                     return txtId;
 47:             }
 48:         }
 49: 
 50:         public void ClearErrors(){
 51:             foreach (Control item in grpCustomer.Controls) {
 52:                 if (item.GetType() == typeof(TextBox)) {
 53:                     MyError.SetError(item, "");
 54:                 }
 55:             }
 56:         }
 57: 
 58:         public void Validate(object sender, EventArgs e) {
 59:             presenter.SubmitChange();
 60:         }

I just added a couple of routines to better manage the control in my form: ClearErrors and GetControl.

Now it’s time for the presenter. This presenter will be very easy. It will have a private IView as a field and only one constructor:

  1:         //Instance of the current view
  2:         ICustomerView view;
  3:         //Constructor
  4:         public CustomerPresenter(ICustomerView _view) {
  5:             this.view = _view;
  6:         }
  7: 

I have created also a custom init routine in order to fill my form from the presenter:

  1:         //Initialization
  2:         public void Initialize() {
  3:             Customer newCustomer = 
  4:                 new Customer() { 
  5:                     Id = 0, 
  6:                     FirstName = "Raffaele", 
  7:                     LastName = "Garofalo" };
  8:             PassViewInfo(newCustomer);
  9:         }
 10: 

And this is the routing that fill the view from the presenter:

  1:         private void PassViewInfo(Customer customer) {
  2:             view.Id = customer.Id;
  3:             view.Firstname = customer.FirstName;
  4:             view.Lastname = customer.LastName;
  5:         }
  6: 

Last is the SubmitChanges called by the view. Here is where we are running the domain validation logic:

  1:         //Submit the change, but before, validate them
  2:         public void SubmitChange() {
  3:             Customer currentCustomer = new Customer();
  4:             GetViewInfo(currentCustomer);
  5:             if (currentCustomer.IsValid()) {
  6:                 view.ClearErrors();
  7:             } else {
  8:                 foreach (ErrorResult error in currentCustomer.ListResults) {
  9:                     view.ShowError(error.ErrorMessage, error.Field);
 10:                 }
 11:             }
 12:         }
 13: 

So for each error raised by our Entity, I am going to add the error to the corresponding control in the view. If there are no errors at all, the view will be cleaned. In this way we can also enable/disable a save button for example. But it is not the purpose of this article.

The final result will be:

image

I hope you enjoy my series of articles, and if you have any feedback, feel free to post them on my blog.

The next series will focus on the MVVM, I have already posted something.

If you want the solution of this tutorial, it is saved in my sky drive here

Tags: