Friday, January 30, 2009

Custom Validation Step 3: Simple Page Validation

In my previous post Custom Validation Step 2: Business Object Validaiton, I showed how the custom validation design that I’m working with could be applied to a Person entity (business object).  The key piece of the puzzle is that my Person class contains a ValidationErrors member which is just a generic list of ValidationError objects.  This handy list is populated with all of the validation errors for my entity whenever I call the Validate() method.  Now it’s time for the payoff.  In this post I’ll walk through a basic implementation of the custom validation design on an ASP.Net page.  Next week I’ll show a more complex implementation that uses self registering ErrorLabel and ValidationSummary controls.

The PersonForm.aspx Page

We’re going to create a PersonForm.aspx page that will be used to create or edit a person entity. This is a simple data input form with labels and input controls.  It looks like this.

image

The PageErrorList Class

We’re going to create a new member on our page called PageErrors that will contain a list of  ValidationError objects, just like the ValidationErrors list on our Person class. This allows us to implement a design where the page’s PageErrors list can contain the ValidationErrors list from the Person object.  This is really the key to making the whole design work.  We use the exact same mechanism to store page level errors as we use to store validation errors in our business objects.  That makes it possible for us to run Validate() on a business object, then pass the resulting ValidationErrors() list to our page, the page can then just append those errors to it’s own PageErrors list. 

You may have noticed that I said PageErrors contains a List<ValidationError> just now. That’s because we need PageErrors to do some additional things that a plain generic list isn’t going to handle for us, like appending lists of ValidationError objects, and setting UIFieldNames when needed.  So, we’re going to create a wrapper class called PageErrorList.   

There are 3 main parts to PageErrorList.  First it contains our generic list of ValidationErrors. Second, it contains a FieldMappings dictionary that can be used to map entity FieldNames to the UIFieldNames used by our page (we want error messages to use “Mobile Phone” not “PhoneMobile”). Third, it contains a collection of helper methods that do things like adding a single ValidationError to the Errors list, adding lists of ValidationErrors, and generating a summary of error messages.  Here’s the code:

public class PageErrorList

    {

        // Errors

        private List<ValidationError> _errors;

        public List<ValidationError> Errors

        {

            get

            {

               if (_errors == null) { _errors = new List<ValidationError>(); }

               return _errors;

            }

            set { _errors = value; }

        }

 

        // FieldMappings

        // Dictionary that contains mappings between the FieldNames and UIFieldNames.

        // FieldName is the key, UIFieldName is the value.

        private Dictionary<string, string> _fieldMappings;

        public Dictionary<string, string> FieldMappings

        {

            get

            {

              if (_fieldMappings == null)

              {

                _fieldMappings = new Dictionary<string, string>();

              }

              return _fieldMappings;

            }

            set { _fieldMappings = value; }

        }

 

        // MapField

        // Helper method to create items in FieldMappings. Main purpose is

        // to make clear which item is FieldName and which is UIFieldName.

        public void MapField(string FieldName, string UIFieldName)

        {

            FieldMappings.Add(FieldName, UIFieldName);

        }

 

        // Add

        // Add a single ValidationError to Errors, and as we add it

        // check the FieldMappings to see if we have a UIFieldName

        // for this FieldName and set it if we do.

        public void Add(ValidationError e)

        {

            string uiFieldName;

            if (FieldMappings.TryGetValue(e.FieldName, out uiFieldName))

            {

              e.UIFieldName = uiFieldName;

            }

            Errors.Add(e);

        }

 

        // AddList

        // Same as Add() but this method takes a list of ValidationErrors.

        public void AddList(List<ValidationError> list)

        {

            foreach (ValidationError e in list) { Add(e); }

        }

 

        // GetErrorMessages

        // Returns a string that lists all errors and is meant

        // to be used for the error summary.

        public string GetErrorMessages(string ErrorBullet)

        {

            System.Text.StringBuilder messages = new System.Text.StringBuilder(512);

            foreach (ValidationError e in Errors)

            {

                messages.Append(ErrorBullet + e.ErrorMessage + "<br />");

            }

            return messages.ToString();

        }

    }

Now that we have our new PageErrorList class, we just add a PageErrors property to our PersonForm page. This property contains an object of type PageErrorList as shown below.   

// PageErrors

private PageErrorList _pageErrors;

public PageErrorList PageErrors

{

    get

    {

      if (_pageErrors == null)

      {

        _pageErrors = new PageErrorList();

      }

      return _pageErrors;

    }

    set { _pageErrors = value; }

}

 

Bringing It All Together

Now we have a PageErrors member that’s ready to be our container for all errors related to the page.  But how do we use it?  There are two main sources of errors on a page like this; entity validation errors, and page level errors.  Entity validation errors are exactly what you would think, the errors that we get back when validating an entity. Page level errors are errors that result from UI requirements. What does that mean?  Scroll up a bit and look at our UI page.  See the Password and Confirm Password fields?  Password is a member of our Person entity, so making sure we have a valid password is an example of entity level validation.  However, the entity doesn’t have anything to do with Confirm Password.  Making sure that the Confirm Password value matches the Password value is purely a requirement of the UI. That’s an example of page level validation.

So, our workflow on this page is simple.  A user clicks the save button, we need to run both page level validation and entity validation, if the page passes all validation we save the person, if not we give an error summary.  We need to make a ValidatePage() method to handle page level validation.  The method just runs the page level validation rules and adds an errors to our PageErrors member.  Remember that this method contains only validation rules not covered by the entity(s).

// ValidatePage

protected void ValidatePage()

    // Password Confirm Password must match - *** Page Validation ***

    if (FormHelper.ParseString(txtPassword.Text) != FormHelper.ParseString(txtPasswordConfirm.Text))

    {

        this.PageErrors.Add(new ValidationError("Page.ConfirmPassword", "Confirm Password and Password don't match"));

    }

You may have noticed the FormHelper class used in the code above.  Whenever I’m getting values from a form I always have repetitive code that does things like null check on a DropDownList.SelectedValue or trim TextBox.Text, so I stick that code in static methods contained in a static FormHelper class. This approach also has the benefit of codifying best practices for getting data from a form.  If everyone is using the FormHelper, you don’t have to worry about a junior developer (or me) creating code that will blow up because he isn’t doing something like a null check on ddl.SelectedValue.

Next is the GetPesonFromForm() method.  Before we can validate or save a Person object we need to create it from our submitted form data.  GetPersonFromForm() does that for us.  It also demonstrates how easy this type of thing can be when you use a FormHelper class. When you look at the code below, keep in mind our business object architecture.  We use PersonDTO objects to move data between layers of our app.  The DTO (Data Transfer Object) is just data container, it’s contains properties for all of the person data fields.  The Peson class that we’re creating below is a full fledged business object. It contains both data and behavior.  It’s data is stored in a very simple way, it just has a single Data property of type PersonDTO.  For more detail take a look at Custom Validation Step 2: Business Object Validation.

// GetPersonFromForm

private BAL.Person GetPersonFromForm()

{

  BAL.Person person = PersonRepository.GetNewPerson();

  if (this.PersonGuid != CommonBase.Guid_NullValue)

  {

    person.Data.PersonGuid = this.PersonGuid;

  }

  person.Data.Name = FormHelper.ParseString(txtName.Text);

  person.Data.Email = FormHelper.ParseString(txtEmail.Text);

  person.Data.Password = FormHelper.ParseString(txtPassword.Text);

  person.Data.TimeZoneId = FormHelper.ParseInt(ddlTimeZone.SelectedValue);

  person.Data.City = FormHelper.ParseString(txtCity.Text);

  person.Data.State = FormHelper.ParseString(ddlState.SelectedValue);

  person.Data.ZipCode = FormHelper.ParseInt(txtZipCode.Text);

  person.Data.PhoneHome = FormHelper.ParseString(txtPhoneHome.Text);

  person.Data.PhoneMobile = FormHelper.ParseString(txtPhoneMobile.Text);

  person.Data.ImAddress = FormHelper.ParseString(txtImAddress.Text);

  person.Data.ImType = FormHelper.ParseInt(ddlImType.SelectedValue);

  return person;

}

All right.  Now we’re ready to create our button click event.  After all of the plumbing we’ve created, our event code is very simple. To recap, we just create a person object from our form data, run both page level and entity level validation, then check to see if we have errors, if so show the error summary, if not save the person. Here it is:

            // btnSave_Click

            protected void btnSave_Click(object sender, EventArgs e)

            {

                BAL.Person person = GetPersonFromForm();

 

                // Run page and entity level validation

                ValidatePage();

                this.PageErrors.AddList(person.Validate());

                // If any errors were found during page validation or the domain

                // object validation then show an error summary.

                if (this.PageErrors.Errors.Count != 0)

                {

                  lblErrorSummary.Text = this.PageErrors.GetErrorMessages(“* ”);

                }

                else

                {

                    PersonRepository.SavePerson(ref person, true);

                }

            }

So that’s the meat of it but there is one last thing I should cover.  Remember when we created our PageErrorList class and we included a dictionary that could be used to store the mappings between entity FieldNames (like PhoneHome) and UIFieldNames used by the page (like Phone Number).  If we want to get error messages that match the names we use in our UI page, then we need to set up those mappings.  Fortunately, thanks to the plumbing we’ve created, it’s pretty easy.   The method below takes a reference to the PageErrorList object as a parameter and then creates the mappings using our MapField() helper method.  Note the careful “fully qualified” naming convention used for the first parameter, FieldName.  Data members that belong to the Person object are named “Person.FieldName”.  Data members that don’t belong to an entity but do exist on the page (like ConfirmPassword) are named “Page.FieldName”.  The main thing to remember is that the FieldName we use here in the mappings must match the FieldName we used when we created the ValidationError object back in the entity level Validate() method or in the page level ValidatePage() method.  With these mappings in place, our ValidationError objects will know to replace the <FieldName> pseudo tags in our error messages with UI specific names that will work with our Asp.Net page and will make sense to our users.  One more thing, for MapFieldNames() to work it needs to be called from some place in the page event lifecycle.  I put my call to MapFieldNames in the Page_PreInit() in my FormPageBase class (the base class that I use for all of my data entry style pages).

            // MapFieldNames

                 protected void MapFieldNames(PageErrorList ErrorList)

            {

                // Password

                ErrorList.MapField("Person.Password", "Password");

                // ConfirmPassword

                ErrorList.MapField("Page.ConfirmPassword", "Confirm Password");

                // Name

                ErrorList.MapField("Person.Name", "Name");

                // Nickname

                ErrorList.MapField("Person.Nickname", "Nickname");

                // PhoneMobile

                ErrorList.MapField("Person.PhoneMobile", "Mobile Phone");

                // PhoneHome

                ErrorList.MapField("Person.PhoneHome", "Home Phone");

                // Email

                ErrorList.MapField("Person.Email", "Email");

                // City

                ErrorList.MapField("Person.City", "City");

                // State

                ErrorList.MapField("Person.State", "State");

                // ZipCode

                ErrorList.MapField("Person.ZipCode", "Zip Code");

                // ImAddress

                ErrorList.MapField("Person.ImAddress", "IM Address");

                // ImType

                ErrorList.MapField("Person.ImType", "IM Type");

                // TimeZoneId

                ErrorList.MapField("Person.TimeZoneId", "Time Zone");

                // LanguageId

                ErrorList.MapField("Person.LanguageId", "Language");

            }

So that’s our implementation of custom validation on an Asp.Net page, the simple version.  Looking back it seems like a lot until you realize that most of what we’ve done is just plumbing that can be written once and then used across every page in your application.  The only validation code that you need to create for each individual page is the ValidatePage() method and the MapFieldNames() method.  Stuff like GetPersonFromForm() would have had to be written whether you use this model or not. So once initial development is done, your implementation for additional pages is almost trivial, and the benefit of consolidating your business logic inside your business objects is definitely worth it.  Remember that we’ve been focusing on validation like is this a valid email, but your validation should also implement rules like is this person allowed to add this feature to their service plan if they don’t have an email saved in the system and no other person in their customer group has an email saved to the system.  The latter is the type of validation rule that very clearly does not belong in your UI.  Consolidating your validation in one place, using a mechanism that can be understood and used by your UI can really pay big dividends when it comes to extending and maintaining your code.

Next time I’m going to extend this model even further with a new FormBasePage that is an IValidationContainer, and self registering ValidationLabel, ErrorSummary, and MessageSummary controls.  These additions will automate a lot of the things that I want to happen in my UI forms like creating a summary whenever there are page errors and giving a visual indicator next to lines where there was an error. 

For people who want to see the full PersonForm page class, here is a more or less complete implementation of the things I covered in this post:

 

    public partial class PersonForm : FormPageBase

    {

        #region "PROPERTIES"

            // PageMode

            protected enum enumPageMode

            {

                NULL = 0,

                NEW= 1,

                EDIT = 3

            }

            private enumPageMode _pageMode=enumPageMode.NULL;

            protected enumPageMode PageMode

            {

                get

                {

                    // If _pageMode hasn't been set yet then we need to

                    // pull it from the querystring().  If a known page

                    // mode isn't found, throw an error.

                    if (_pageMode == enumPageMode.NULL)

                    {

                        string pageMode = Request.QueryString["PAGEMODE"];

                        if (String.IsNullOrEmpty(pageMode))

                        {

                           throw new Exception("Unhandled PageMode");

                        }

                        switch (pageMode)

                        {

                            case "NEW":

                                _pageMode = enumPageMode.NEW;

                                break;

                            case "EDIT":

                                _pageMode = enumPageMode.EDIT;

                                break;

                        }

                    }

                    return _pageMode;

                } 

            }

            // PersonGuid

            private Guid _personGuid = CommonBase.Guid_NullValue;

            protected Guid PersonGuid

            {

                get

                {

                    if (_personGuid == CommonBase.Guid_NullValue)

                    {

                        if (this.ViewState["__PersonGuid"] != null)

                        {

                            _personGuid=(Guid)ViewState["__PersonGuid"];

                        }

                    }

                    return _personGuid;

                }

                set

                {

                    _personGuid = value;

                    this.ViewState["__PersonGuid"] = value;

                }

            }

        #endregion

 

 

 

 

 

        #region "PAGE EVENTS"

            //--------------------------------------------------------

            // Page_Load

            //--------------------------------------------------------

            protected void Page_Load(object sender, EventArgs e)

            {

                if (!this.IsInitialized) { InitializePage(); }

            }

        #endregion

 

 

 

 

 

        #region "OTHER EVENTS"

            //--------------------------------------------------------

            // btnSave_Click

            //--------------------------------------------------------

            protected void btnSave_Click(object sender, EventArgs e)

            {

                BAL.Person person = GetPersonFromForm();

 

                // Run page and entity level validation

                ValidatePage();

                this.PageErrors.AddList(person.Validate());

                // If any errors were found during page validation or the domain

                // object validation then show an error summary.

                if (this.PageErrors.Errors.Count != 0)

                {

                  lblErrorSummary.Text = this.PageErrors.GetErrorMessages(“* ”);

                }

                else

                {

                    PersonRepository.SavePerson(ref person, true);

                }

            }

        #endregion

 

 

 

 

 

        #region "CLASS METHODS"

            //--------------------------------------------------------

            // GetPersonFromForm

            //--------------------------------------------------------

            private BAL.Person GetPersonFromForm()

            {

                BAL.Person person = PersonRepository.GetNewPerson();

                if (this.PersonGuid != CommonBase.Guid_NullValue)

                {

                    person.Data.PersonGuid = this.PersonGuid;

                }

                person.Data.Name = FormHelper.ParseString(txtName.Text);

                person.Data.Email = FormHelper.ParseString(txtEmail.Text);

                person.Data.Password = FormHelper.ParseString(txtPassword.Text);

                person.Data.TimeZoneId = FormHelper.ParseInt(ddlTimeZone.SelectedValue);

                person.Data.City = FormHelper.ParseString(txtCity.Text);

                person.Data.State = FormHelper.ParseString(ddlState.SelectedValue);

                person.Data.ZipCode = FormHelper.ParseInt(txtZipCode.Text);

                person.Data.PhoneHome = FormHelper.ParseString(txtPhoneHome.Text);

                person.Data.PhoneMobile = FormHelper.ParseString(txtPhoneMobile.Text);

                person.Data.ImAddress = FormHelper.ParseString(txtImAddress.Text);

                person.Data.ImType = FormHelper.ParseInt(ddlImType.SelectedValue);

                return person;

            }

 

 

            //--------------------------------------------------------

            // InitializePage

            // Sets the initial state of the page. 

            //--------------------------------------------------------

            override protected void InitializePage()

            {

                // Bind all ddl items.

                BindDdlTimeZone();

                BindDdlState();

                BindDdlImType();

 

                // Populate form values for the state we're in

                switch (PageMode)

                {

                    case enumPageMode.EDIT:

                        {

                            // Existing person.

                            BAL.Person thisPerson = PersonRepository.GetPersonByPersonGuid(this.PersonGuid);

                            PopulatePage(ref thisPerson);

                            break;

                        }

                    case enumPageMode.NEW:

                        {

                            // New person.

                            SetPageDefaults();

                            break;

                        }

                }

                // Set the initialized flag

                this.IsInitialized = true;

            }

 

 

            //--------------------------------------------------------

            // SetPageDefaults

            //--------------------------------------------------------

            protected void SetPageDefaults()

            {

                // do nothing for now.

            }

 

 

            //--------------------------------------------------------

            // PopulatePage

            //--------------------------------------------------------

            protected void PopulatePage(ref BAL.Person thisPerson)

            {

                // Set all data values. Be sure to check for null values against

                // the null defaults defined in CommonBase.

                txtName.Text = thisPerson.Data.Name == CommonBase.String_NullValue ? String.Empty : thisPerson.Data.Name;

                txtEmail.Text = thisPerson.Data.Email == CommonBase.String_NullValue ? String.Empty : thisPerson.Data.Email;

                txtPassword.Text = thisPerson.Data.Password == CommonBase.String_NullValue ? String.Empty : thisPerson.Data.Password;

                txtPasswordConfirm.Text = txtPasswordConfirm.Text;

            }

 

 

            //--------------------------------------------------------

            // BindDdlTimeZone

            //--------------------------------------------------------

            protected void BindDdlTimeZone()

            {

                ddlTimeZone.DataSource = TimeZoneRepository.GetAllUS();

                ddlTimeZone.DataTextField = "MicrosoftId";

                ddlTimeZone.DataValueField = "TimeZoneId";

                ddlTimeZone.DataBind();

                ListItem selectOne = new ListItem("Select One", "");

                ddlTimeZone.Items.Insert(0, selectOne);

            }

 

            //--------------------------------------------------------

            // BindDdlState

            //--------------------------------------------------------

            protected void BindDdlState()

            {

                ddlState.DataSource = StateRepository.GetAll();

                ddlState.DataTextField = "StateName";

                ddlState.DataValueField = "StateCode";

                ddlState.DataBind();

                ListItem selectOne = new ListItem("Select One", "");

                ddlState.Items.Insert(0, selectOne);

            }

 

            //--------------------------------------------------------

            // BindDdlImType

            //--------------------------------------------------------

            protected void BindDdlImType()

            {

                ddlImType.DataSource = ImTypeRepository.GetAll();

                ddlImType.DataTextField = "ImName";

                ddlImType.DataValueField = "ImId";

                ddlImType.DataBind();

                ListItem selectOne = new ListItem("Select One", "");

                ddlImType.Items.Insert(0, selectOne);

            }

 

            //--------------------------------------------------------

            // MapFieldNames

            // Required for PageBase implementation. Method maps full

            // Entity field names to the UI Names that need to be

            // used in error messages. Once fields are mapped, the

            // PageErrors object can automatically generate usable

            // error messages for validation errors. If no fields

            // need to be mapped then just create an empty method.

            //--------------------------------------------------------

            override protected void MapFieldNames(PageErrorList ErrorList)

            {

                // Password

                ErrorList.MapField("Person.Password", "Password");

                // ConfirmPassword

                ErrorList.MapField("Page.ConfirmPassword", "Confirm Password");

                // Name

                ErrorList.MapField("Person.Name", "Name");

                // Nickname

                ErrorList.MapField("Person.Nickname", "Nickname");

                // PhoneMobile

                ErrorList.MapField("Person.PhoneMobile", "Mobile Phone");

                // PhoneHome

                ErrorList.MapField("Person.PhoneHome", "Home Phone");

                // Email

                ErrorList.MapField("Person.Email", "Email");

                // City

                ErrorList.MapField("Person.City", "City");

                // State

                ErrorList.MapField("Person.State", "State");

                // ZipCode

                ErrorList.MapField("Person.ZipCode", "Zip Code");

                // ImAddress

                ErrorList.MapField("Person.ImAddress", "IM Address");

                // ImType

                ErrorList.MapField("Person.ImType", "IM Type");

                // TimeZoneId

                ErrorList.MapField("Person.TimeZoneId", "Time Zone");

                // LanguageId

                ErrorList.MapField("Person.LanguageId", "Language");

            }

 

 

            //--------------------------------------------------------

            // ValidatePage

            //--------------------------------------------------------

            protected void ValidatePage()

            {

                // Password Confirm Password must match - *** Page Validation ***

                if (FormHelper.ParseString(txtPassword.Text) != FormHelper.ParseString(txtPasswordConfirm.Text))

                {

                    this.ValidationBox.PageErrors.Add(new ValidationError("Page.ConfirmPassword", "Confirm Password and Password don't match"));

                }

                }            

        #endregion

 

    }

 

 

Sunday, January 25, 2009

Custom Validation Step 2: Business Object Validation

The Plumbing

In the first post in this series, Custom Validation Step 1: The ValidationErrorClass, I created some of the plumbing for my custom validation design.  I created a ValidationError class and a BALBase that defined the interface for my entities (business objects) that will use the validation.  The two classes look like this:

ValidationDiagram 

The Person Class Design

Now, I’m going to put that plumbing to use and implement validation in my Person entity class.   Person is an entity in my domain, which is just another way of saying that it’s a business object.   I typically use DTOs (Data Transfer Objects) to move data between different layers in my architecture, so the design of the Person class is pretty simple.  All of the actual person data is stored in a PersonDTO class.  The Person class, my full fledged business object, has a property named “data” that holds a PersonDTO.  So I don’t have individual data members in the body of my Person class, the data is all held in the data property

That takes care of data.  The only other thing the Person class needs to worry about is validation logic.  For that, it implements the Validate() method required by BALBase, and it also implements individual validation methods for each piece of data that requires validation.  So I have a Val_Name() method, a Val_Email() method, etc.  The resulting classes end up looking like this.

PersonEntity

A Sample Val_Email() Method

Now I can finally get to the validation.  I’m going to start by writing a validation method named Val_Email() for the Email field.  This method will cover all validation rules for my email field.  There are three rules that the email data must pass. First, email is required data.  Second, email must be a valid email address.  Third, the email can’t be in use by another person.  If my data fails to pass any of the rules, the Val_Email() method creates a new ValidationError object, adds it to the ValidationErrors member of the Person class, and then returns false to signify that we failed validation.  If the email data passes all three rules, we return true. This method encapsulates all validation rules needed for the email field.  The result looks like this.

//

// Val_Email

//

public bool Val_Email()

{

  // Email required

  if (this.Data.Email == DTOBase.String_NullValue)

  {

    this.ValidationErrors.Add(new ValidationError("Person.Email", "<FieldName> is required"));

    return false;

  }

  // Email is valid email

  else if (!ValidationRules.IsValidEmail(this.Data.Email))

  {

    this.ValidationErrors.Add(new ValidationError("Person.Email", "<FieldName> must be a valid email address"));

    return false;

  }

  // Email is not already in use.

  else

  {

    Person checkPerson = PersonRepository.GetPersonByEmail(this.Data.Email);

    if((checkPerson != null) && (!checkPerson.Data.PersonGuid.Equals(this.Data.PersonGuid)))

    {

      this.ValidationErrors.Add(new ValidationError("Person.Email", "That <FieldName> is already in use"));

      return false;

    }                    

  }

  // If we reached this point then the email passed validation

  return true;

}

There are a couple of things I want to point out about the code above.  First, notice that when we create a ValidationError object, the constructor takes two string parameters, the FieldName and the ErrorMessage.  The FieldName that we pass in is “Person.Email” not just “Email”.  This “fully qualified” FieldName is a convention I’ve picked up to ensure that my FieldNames are unique in the situation where there are multiple entities on a page that may have members with the same name.  This way my page will be able to distinguish between validation errors for the Person.Email field and another entity’s field, perhaps Company.Email.

I also want to point out my use of  a ValidationRules class.  This class (or some equivalent mechanism) should be part of any BAL.  There is no reason to write the same regular expression to validate an email address over and over.  The ValidationRules class is just a static class that contains a bunch of static validation methods that are used throughout my BAL. 

Pulling it All Together: The Validate() Method

So I now have a validation method for the email member that runs all of my email validation checks and then adds a new ValidationError object to the Person.ValidationErrors list for any errors that it finds.  Now I want to complete my BALBase implementation by adding a call to Val_Email() to my Validate() method.  Person.Validate() is really just a collection of calls to all of the individual validation methods for my entity. This may seem a little simplistic and at first I was tempted to implement a more flashy solution using attributes like EntLib, and I came very close to implementing a solution using delegates like Rocky Lhotka.  But at the end of the day the pragmatist in me took over and I realized that this method works, it gives me the flexibility that I’m looking for, and it will be easy to understand and maintain for those who come after me. So, after writing validation methods for the other data fields, here is what the Person.Validate method looks like. 

//

// Validate

// Required for BALBase implementation

//

public override List<ValidationError> Validate()

{

  // Call all validation functions

  Val_Name();

  Val_Email();

  Val_Password();

  Val_TimeZone();

  Val_City();

  Val_State();

  Val_ZipCode();

  Val_ImType();

 

  // Return any errors

  return this.ValidationErrors;

}

One last thing I want to point out is that the Validate() method returns the ValidationErrors list when it is done.  I decided on this return type after initially returning a boolean then repeatedly having to check that boolean flag just to see if I needed to then go and get the ValidationErrors list and append it to my PageErrors list.  Now, by returning the ValidationErrors list in the Validate() method, I can write code in my UI that looks like this. 

this.PageErrors.AddList(person.Validate());

 

Much cleaner.  So that’s it.  I now have a consistent interface used by all of my entity classes to run validation for the object and then store any errors in a ValidationErrors list.  Next time I’ll look at how to implement this in my ASP.Net pages.  Below is the complete Person class for anyone who is interested.

public class Person : BALBase

{

    //

    // Data

    // This property exists for all BAL objects, and it is

    // set to the DTO type for this entity.  This is the

    // mechanism that we use to implement "has a" inheritance

    // instead of "is a" inheritance.

    //

    public PersonDTO Data { get; set; }

 

 

    //

    // Person - default constructor

    //

    public Person() { this.Data = new PersonDTO(); }

    //

    // Person - takes a DTO

    //

    public Person(PersonDTO dto) { this.Data = dto; }

 

 

    #region "Validation"

         //

    // Validate

    // Required for BALBase implementation

    //

    public override List<ValidationError> Validate()

    {

        // Call all validation functions

        Val_Name();

        Val_Email();

        Val_Password();

        Val_TimeZone();

        Val_City();

        Val_State();

        Val_ZipCode();

        Val_ImType();

 

             return this.ValidationErrors;

    }

 

 

    // Validation Methods:

    // There are only 2 requirements on validation methods.

    //  - They must handle adding a Validation Error to the

    //    ValidationErrors list if they find an error.

    //  - You must manually add a call to all validation methods

    //    to the Validate() function.

    //  When creating a new ValidationError object, remember

    //  that the first parameter is the exact name of the field

    //  that has the bad value, and the error message should

    //  not contain the field name, but instead the <FieldName>

    //  tag, which will be replaced by the UI or consuming app.

 

    //

    // Val_Name

    //

    public bool Val_Name()

    {

        // Name required

        if (this.Data.Name == DTOBase.String_NullValue)

        {

            this.ValidationErrors.Add(new ValidationError("Person.Name", "<FieldName> is required"));

            return false;

        }

        else

        {

            return true;

        }

    }

    //

    // Val_Email

    //

    public bool Val_Email()

    {

        // Email required

        if (this.Data.Email == DTOBase.String_NullValue)

        {

            this.ValidationErrors.Add(new ValidationError("Person.Email", "<FieldName> is required"));

            return false;

        }

        // Email is valid email

        else if (!ValidationRules.IsValidEmail(this.Data.Email))

        {

            this.ValidationErrors.Add(new ValidationError("Person.Email", "<FieldName> must be a valid email address"));

            return false;

        }

        // Email is not already in use.

        else

        {

            Person checkPerson = PersonRepository.GetPersonByEmail(this.Data.Email);

            if ((checkPerson != null) && (!checkPerson.Data.PersonGuid.Equals(this.Data.PersonGuid)))

            {

                this.ValidationErrors.Add(new ValidationError("Person.Email", "That <FieldName> is already in use"));

                return false;

            }

        }

        // If we reached this point then the email passed validation

        return true;

    }

    //

    // Val_Password

    //

    public bool Val_Password()

    {

        // Password required

        if (string.IsNullOrEmpty(this.Data.Password))

        {

            this.ValidationErrors.Add(new ValidationError("Person.Password", "<FieldName> is required"));

            return false;

        }

        // Password is valid password

        else if (!ValidationRules.IsValidPassword(this.Data.Password))

        {

            this.ValidationErrors.Add(new ValidationError("Person.Password", "<FieldName> must be at least 6 characters"));

            return false;

        }

        else

        {

            return true;

        }

    }

    // TimeZone required

    public bool Val_TimeZone()

    {

        //  TimeZone required

        if (this.Data.TimeZoneId == CommonBase.Int_NullValue)

        {

            this.ValidationErrors.Add(new ValidationError("Person.TimeZoneId", "<FieldName> is required"));

            return false;

        }

        else

        {

            return true;

        }

    }

    // Val_City

    public bool Val_City()

    {

        // City required

        if (string.IsNullOrEmpty(this.Data.City))

        {

            this.ValidationErrors.Add(new ValidationError("Person.City", "<FieldName> is required"));

            return false;

        }

        // Valid City business rule

        else if (!ValidationRules.IsValidCity(this.Data.City))

        {

            this.ValidationErrors.Add(new ValidationError("Person.City", "<FieldName> must be a valid city"));

            return false;

        }

        else

        {

            return true;

        }

    }

 

    // Val_State

    public bool Val_State()

    {

        //  State required

        if (string.IsNullOrEmpty(this.Data.State))

        {

            this.ValidationErrors.Add(new ValidationError("Person.State", "<FieldName> is required"));

            return false;

        }

        // Valid StateCode business rule

        else if (!ValidationRules.IsValidStateCode(this.Data.State))

        {

            this.ValidationErrors.Add(new ValidationError("Person.State", "<FieldName> must be a valid state"));

            return false;

        }

        else

        {

            return true;

        }

    }

 

    // Val_ZipCode

    public bool Val_ZipCode()

    {

        // ZipCode Required

        if (this.Data.ZipCode == CommonBase.Int_NullValue)

        {

            this.ValidationErrors.Add(new ValidationError("Person.ZipCode", "<FieldName> is required"));

            return false;

        }

        // Valid ZipCode business rule

        else if (!ValidationRules.IsValidZipCode(this.Data.ZipCode))

        {

            this.ValidationErrors.Add(new ValidationError("Person.ZipCode", "A valid <FieldName> is required."));

            return false;

        }

        else

        {

            return true;

        }

    }

 

    // ValImType

    public bool Val_ImType()

    {

        //  If ImAddress exists, ImType is required

        if (!string.IsNullOrEmpty(this.Data.ImAddress) && (!ValidationRules.IsValidImTypeId(this.Data.ImType)))

        {

            this.ValidationErrors.Add(new ValidationError("Person.ImType", "<FieldName> is required for the IM Address"));

            return false;

        }

        else

        {

            return true;

        }

    }

    #endregion

}