Entity Framework Windows Form validation

Introduction

Validating data in windows forms application is generally done by determining if the information entered by a user is the proper type e.g. price of a product is type decimal, the first and last name of a person entered are not empty strings. In a conventional application, a developer will have logic in a button click event to validate all information entered on a form, which has worked for many, yet this logic is locked into a single form/window.  For ASP.NET MVC Data Annotations Jump are used to validate against a model (a class) using built-in attributes to validate members in a model/class along with the ability to override built-in attributes and create custom attributes. In this article, exploration will be done to show how to use built-in and custom Data Annotation Attributes in windows forms applications using Entity Framework for data operations.

Overview

A simple table within a SQL-Server is used A model where all fields are marked as required along with on string fields are marked with a max string length. 

The goal is to disallow a record to be added to the backend database table that does not have all required fields populated. What is not covered, on a phone number field if it’s in the correct format or the join date is within a specific range.

The following two tables are used, MemberList1 which will be aliased in the Code First database exists approach.

Note there is no primary key for MemberList. Entity Framework will still auto-generate an incrementing key using.

[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int  Id { get; set; }

Validation version 1

In this version, all validation information is pushed to a string. Note mHasException and mLastException which are part of the following NuGet package. The code from the package is implemented using public class ClubOperations : BaseExceptionProperties.

public void  AddBadMember1(ClubMember pMemberList1)
{
    using (var context = new ClubMembersEntity())
    {
        context.Entry(pMemberList1).State = EntityState.Added;
        try
        {
 
            context.SaveChanges();
 
        }
        catch (FormattedDbEntityValidationException fve)
        {
 
            mHasException = true;
            _validationErrorMessage = fve.Message;
 
        }
        catch (Exception ex)
        {
 
            mHasException = true;
            mLastException = ex;
 
        }
    }
}

Note in the above catch, interest is focused on FormattedDbEntityValidationException which is a class which inherits from the base Exception class. When validation fails for any reason a message is passed back to the caller which may be presented in a MessageBox. There is an issue, this is not very professional for a production application while it's fine for development of the application. 

Validation version 2

A better way to present validation errors is to use an ErrorProvider component which extends any control on a form to show a red icon which when hovered over will display the validation issue.

When implemented in a form, in this case, to add a new record, information entered is placed into a new instance of a class which represents a new record in the backend database table.

Code is broken down

  1. Clear all TextBox controls of any error messages set by an ErrorProvider
  2. Determine if either ComboBox controls have valid values as the first item in each is "Select" which is invalid.
  3. Create a new instance of the class ClubMember and populate with data entered by a user.
  4. Create an instance of ClubOperations which contains code to add a new record.
  5. Pass the new instance of Clubmember in step 3 to ClubOperations.AddBadMember.
  6. Check for success via if (!ops.IsSuccessFul) where IsSuccessful comes from BaseExceptionProperties (NuGet package indicated above). If successful indicate this to the user using a MessageBox. Note the new primary key is returned if it's needed.
    private void  AddMemberButton_Click(object sender, EventArgs e)
    {
        var controls = this.TextBoxList();
        foreach (var textBox in controls)
        {
            errorProvider1.SetError(textBox,"");
        }
 
        if (GenderComboBox.SelectedIndex == 0 || CountryComboBox.SelectedIndex == 0)
        {
            MessageBox.Show("Dropdown items must be selected");
            return;
        }
        var person = new  ClubMember
        {
            FirstName = FirstNameTextBox.Text,
            LastName = LastNameTextBox.Text,
            Gender = ((Gender) GenderComboBox.SelectedItem).Id,
            Street = StreetTextBox.Text,
            City = CityTextBox.Text,
            State = StateTextBox.Text,
            Country = CountryComboBox.Text,
            ContactPhone = PhoneTextBox.Text
        };
 
        var ops = new  ClubOperations();
        ops.AddBadMember1(person);
 
        if (!ops.IsSuccessFul)
        {
            /*
             * Do we have a generic error?
             */
            if (ops.LastException != null)
            {
                MessageBox.Show(ops.LastExceptionMessage);
                return;
            }
 
            /*
             * No generic error, move on to validation errors.
             */
            var errorInformation = ops.ValidationErrors;
            foreach (var item in errorInformation.EntityValidationExceptionList)
            {
                foreach (var itemItem in item.Items)
                {
                    var currentBox = controls
                        .FirstOrDefault(tb => tb.Tag.ToString() == itemItem.PropertyName);
 
                    if (currentBox != null)
                    {
                        errorProvider1.SetError(currentBox,itemItem.ErrorMessage);
                    }
                }
            }
        }
        else
        {
            MessageBox.Show("Added member");
        }
    }
}

In the above code, if there is a generic error the error is displayed in a MessageBox. If there are validation errors set the TextBox controls with an error message. When each TextBox is placed on the form their Tag property is set to the field name for the SQL-Server database table which permits the code to find the proper TextBox and set the validation error.

The following code (within a separate class project making it available for other projects) is responsible for return validation details, property name, error message, and offending value.

using System.Collections.Generic;
using System.Data.Entity.Validation;
using System.Text;
using LanguageExtensionsLibrary;
 
namespace EntityValidationLibrary.Classes
{
    public class  ValidationErrors
    {
        public List<EntityValidationExceptionItem> EntityValidationExceptionList = 
            new List<EntityValidationExceptionItem>();
 
        public ValidationErrors(DbEntityValidationException pEv)
        {
 
            foreach (var eve in pEv.EntityValidationErrors)
            {
                var item = new  EntityValidationExceptionItem()
                {
                    Name = eve.Entry.Entity.GetType().Name,
                    State = eve.Entry.State
                };
 
                EntityValidationExceptionList.Add(item);
 
                item.Items = new  List<EntityValidationExceptionProperty>();
 
                foreach (var ve in eve.ValidationErrors)
                {
 
                    item.Items.Add(new EntityValidationExceptionProperty()
                    {
                        PropertyName = ve.PropertyName,
                        ErrorMessage = ve.ErrorMessage
                            .SplitCamelCase().Replace("field ",""),
                        Value = eve.Entry.CurrentValues.GetValue<object>(ve.PropertyName)
                    });
                     
                }
            }
 
            var sb = new  StringBuilder();
 
            foreach (var entity in EntityValidationExceptionList)
            {
                sb.AppendLine(entity.ToString());
                foreach (var item in entity.Items)
                {
                    sb.AppendLine($"  {item.ToString()}");
                }
            }
        }
    }
}

This version could work differently if so desired in regards to the ErrorProvider e.g. show a modeless window residing to one side of the current form to show validation information so the user does not have to hover over the ErrorProvider tooltips. 

Using in your projects

  1. Add the project EntityValidationLibrary to your solution.
  2. Add a reference to EntityValidationLibrary to your windows form project.
  3. Alter your model, using the code similar to the code found in SaveChanges override.

To try out the sample code, first, create the database using the following script.

Summary

This article with accompanying code provides methods to allow validation errors propagated back to windows form used for adding new records and the same logic works for modified records as the only difference is one has no key (adding) while modified records have a primary key set.

See also

Source code

The following repository contains source code for this page.