views:

789

answers:

4

Hello,

I got a business object bounds to a form (each property is bound to a control). There is some business specificatios (such as this field should not be empty, this one must be greater than 0 etc...). What is the best way to check all the rules ?

I currently have a validator on each contorl, so I can check for all validator to be ok, but I don't really like this solution. Indeed the rules are dispached and it's no easy to see all at once.

I can have a big method CheckValidaty that check for all the rules but this leads to a double check with the validators.

What would you do, other solution ?

A: 

What is wrong with the validator approach? It's quite acceptable, and you can write your own, to implement your own rules.

Noon Silk
Inf act I have custom use controls and the validator are inside the control, so I declare the rules as validator property (eg : valueCanBeNull = true). But this approach do no underlines the rules. It's not easy for a coworker to find a specifik rule, all rules.
Toto
+3  A: 

There are two kinds of validation: data-specific validation (in the persistence layer) and user-interface validation. I prefer to put validation near the input side, because generally you want to show the user what's wrong, and trying to connect data validation to the user interface adds more indirection that has to match the data binding indirection.

Putting data validation in control classes does not seem like a good idea. That basically means the control class can be used only for one specific field.

The standard Windows Forms way of doing things is to put data validation in container. That way the validation can check against the state of other properties and connect the specific control to the ErrorProvider object(s) to display a pertinent error message.

class EmployeeForm : UserControl
{
    EmployeeObject employee;

    // ...

    void employeeNameTextBox_Validating (object sender, CancelEventArgs e)
    {
        if ( employee.Name.Trim ().Length == 0 ) {
            errorProvider.SetError (employeeNameTextBox, "Employee must have a name");
            e.Cancel = true;
        }
    }

    void employeeHireDateControl_Validating (...)
    {
        if ( employee.HireDate < employee.BirthDate ) {
            errorProvider.SetError (employeeHireDateControl, 
                "Employee hire date must be after birth date");
            e.Cancel = true;
        }
    }
}

class ExplorerStyleInterface : ... 
{
    // ...

    bool TryDisplayNewForm (Form oldForm, Form newForm)
    {
        if ( ! oldForm.ValidateChildren () )
            return false;

        else {
            HideForm (oldForm);
            ShowForm (newForm);
            return true;
        }
    }
}

The standard WF way is to fire the Validating event for the specific control when the control loses focus or when ValidateChildren is called on the container (or the container's container). You set up a handler for this event through the event properties for the control on the container; the handler is automatically added to the container.

I'm not sure why this way isn't working for you, unless you don't like the default behavior of refusing to reliquish focus on error, which you can change by setting the AutoValidate property of the container (or the container's container) to EnableAllowFocusChange.

Tell us specifically what you don't like about the standard Windows Forms way of doing things, and maybe we can either offer alternatives or persuade you the standard way will do what you want.

XXXXX
I like this approach. Going to go in depth about this. Stay no answered a chunk of time if other people want to had stuff.
Toto
+3  A: 

I would suggest that let the BusinessObject implement IDataErrorInfo. I think it is the cleanest way to handle business errors.

Take a look at these links:

  1. http://msdn.microsoft.com/en-us/library/system.componentmodel.idataerrorinfo%5Fmembers.aspx
  2. http://www.codegod.de/WebAppCodeGod/objectdatasource-and-idataerrorinfo-with-winforms-AID427.aspx
P.K
XXXXX
+1  A: 

If you have this situation:

   - Many controls in the Winform to
     validate
   - A validation rule for each control
   - You want an overall validation within the Save() command
   - You don't want validation when controls focus changes
   - You also need an red icon showing errors in each control

Then you can copy and paste the following code, that implements a Form with 2 textbox and a Save button:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace ValidationApp
{
    public  class ValidationTestForm : Form
    {
        private TextBox textBox1;
        private TextBox textBox2;
        private Button btnSave;
        private ErrorProvider errorProvider1;

          /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.textBox2 = new System.Windows.Forms.TextBox();
            this.btnSave = new System.Windows.Forms.Button();
            this.errorProvider1 = new System.Windows.Forms.ErrorProvider(this.components);
            ((System.ComponentModel.ISupportInitialize)(this.errorProvider1)).BeginInit();
            this.SuspendLayout();
            // 
            // textBox1
            // 
            this.textBox1.Location = new System.Drawing.Point(131, 28);
            this.textBox1.Name = "textBox1";
            this.textBox1.Size = new System.Drawing.Size(100, 20);
            this.textBox1.TabIndex = 0;
            // 
            // textBox2
            // 
            this.textBox2.Location = new System.Drawing.Point(131, 65);
            this.textBox2.Name = "textBox2";
            this.textBox2.Size = new System.Drawing.Size(100, 20);
            this.textBox2.TabIndex = 1;
            // 
            // btnSave
            // 
            this.btnSave.Location = new System.Drawing.Point(76, 102);
            this.btnSave.Name = "btnSave";
            this.btnSave.Size = new System.Drawing.Size(95, 30);
            this.btnSave.TabIndex = 2;
            this.btnSave.Text = "Save";
            this.btnSave.UseVisualStyleBackColor = true;
            this.btnSave.Click += new System.EventHandler(this.btnSave_Click);
            // 
            // errorProvider1
            // 
            this.errorProvider1.ContainerControl = this;
            // 
            // ValidationTestForm
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(266, 144);
            this.Controls.Add(this.btnSave);
            this.Controls.Add(this.textBox2);
            this.Controls.Add(this.textBox1);
            this.Name = "ValidationTestForm";
            this.Text = "ValidationTestForm";
            ((System.ComponentModel.ISupportInitialize)(this.errorProvider1)).EndInit();
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        public ValidationTestForm()
        {
            InitializeComponent();

            // path validation
            this.AutoValidate = AutoValidate.Disable; // validation to happen only when you call ValidateChildren, not when change focus
            this.textBox1.CausesValidation = true;
            this.textBox2.CausesValidation = true;
            textBox1.Validating += new System.ComponentModel.CancelEventHandler(textBox1_Validating);
            textBox2.Validating += new System.ComponentModel.CancelEventHandler(textBox2_Validating);

        }

        private void textBox1_Validating(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (textBox1.Text.Length == 0)
            {
                e.Cancel = true;
                errorProvider1.SetError(this.textBox1, "A value is required.");
            }
            else
            {
                e.Cancel = false;
                this.errorProvider1.SetError(this.textBox1, "");
            }
        }

        private void textBox2_Validating(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (textBox2.Text.Length == 0)
            {
                e.Cancel = true;
                errorProvider1.SetError(this.textBox2, "A value is required.");
            }
            else
            {
                e.Cancel = false;
                this.errorProvider1.SetError(this.textBox2, "");
            }
        }



        private void btnSave_Click(object sender, EventArgs e)
        {
            if (this.ValidateChildren()) //will examine all the children of the current control, causing the Validating event to occur on a control 
            {
                // Validated! - Do something then
            }

        }
    }
}

Note:

textBox1_Validating, textBox2_Validating...do the rule check
Marcello Belguardi