Monday, December 14, 2009 5:01 PM plblum

Exploring Dynamic Data: Validation attributes for business logic

Index to this series of articles

Business logic is applied to your Entity classes (objects that describe individual tables where columns are the properties) through metadata. Typically this metadata is defined by applying attributes from the System.ComponentModel.DataAnnotations namespace.

Validation attributes focus on evaluating data to report errors. The base class is System.ComponentModel.DataAnnotations.ValidationAttribute. It defines the IsValid() method, which is always overriden to evalutate the data. It also supplies the ErrorMessage property, where you can describe the error reported. (I have complaints about using the error message here. See Where does this go? Applying SoC to dynamic data – Part 2.)

The namespace defines the following attributes to validate your data.

  • RequiredAttribute – Marks the field as required. If your database already defines the column as “not nullable”, Dynamic Data knows that the field is required. Yet, since your Entity classes may be used outside of Dynamic Data, always consider assigning this.
    [Required()]
    public object Name { get; set; }

     

    Concerns

    This attribute is limited to testing text returned from the browser and is generally mapped to the RequiredFieldValidator web control.

    It is further limited to considering only the empty string is invalid, whereas you can use the InitialValue property on RequiredFieldValidator to recognize another case (a watermark in the textbox).

  • RegularExpressionAttribute – Evaluates text against a regular expression. Dynamic Data maps this attribute to the RegularExpressionValidator web control.
    [RegularExpression("^\d{5}$")]
    public object ZipCode { get; set; }

     

    Concerns

    Like with the RegularExpressionValidator, this attribute has no way to set the “ignore case” or “multiline” options, which are useful in developing regular expressions.

    When this expression is used with server side validation, it will use the .net framework’s regular expression engine. When it is used within the browser, it uses javascript’s regular expression engine. Javascript has a simpler syntax. Take care to define your expressions with the Javascript syntax when using Dynamic Data. See Javascript RegExp class guide.

  • StringLengthAttribute – Evaluates the length of text to confirm it has not exceeded a maximum value. If your database already defines the length on a textual column, Dynamic Data has access to that value. Yet, since your Entity classes may be used outside of Dynamic Data, always consider assigning this.
    [StringLength(10)]
    public object SocialSecurityNumber { get; set; }

     

    Concerns

    None of the Field Templates actually include a validator web control for this attribute. Those that have single-line textboxes (like Text_Edit.ascx) set their TextBox control’s MaxLength property. If you are using a multiline textbox, you will need to introduce a validator. I recommend using the RegularExpressionValidator with its expression programmatically set in the Field Template’s Page_Load method to a pattern like this:

    ^[\s\S]{0,#}$

    where # is replaced by the value from the Field Template’s Column.MaxLength property. Do NOT attach your validator to the FieldTemplateUserControl’s class by using the SetupValidator() method as that try to associate it with the RegularExpressionAttribute. It will either have its expression updated or be disabled.

    Instead, I recommend creating a method in the Field Template that sets it based on the StringLengthAttribute and if not found, sets the Validator’s Enabled property to false.

  • RangeAttribute – Confirms that the value is between a low and high value of a range. Dynamic Data maps this attribute to the RangeValidator web control.

    If you only want to compare greater than or equal to a value, just set the low (the Minimum property). Similarly set Maximum to compare less than or equal to the high value.

    [Range(0, 100)]
    public object Percentage{ get; set; }

     

  • CustomValidationAttribute – (requires ASP.NET 4). Lets you add a method to your Entity class which will be invoked to validate the column in some way. It can also evaluate multiple columns of an entity. The CustomValidationAttribute only validates on the server side. Dynamic Data uses its DynamicValidator web control to report errors.

    See my earlier posting, “The CustomValidationAttribute”.

While Dynamic Data installs validator web controls into the user interface, your Entity class should never assume that validation was handled elsewhere. It should always run a final validation against the validation rules that you have created. Use the System.ComponentModel.DataAnnotations.Validator class to run that validation. Typically you will call its TryValidateObject() method. It throws an exception for the first error

In this example, the Products table has a method called OnValidate() which invokes it. (OnValidate() is defined in Linq to SQL Entities automatically, but the call to TryValidateObject is not.)

public class Product
{
public void OnValidate(System.Data.Linq.ChangeAction pAction)
{
List<ValidationResult> vErrors = new List<ValidationResult>();
Validator.TryValidateObject(this,
new ValidatorContext(this, null, null), vErrors);
if (vErrors.Count > 0)
throw new Exception(vErrors[0].ErrorMessage)
}
}


Peter’s Soapbox

As many of you know, I have been developing an enhanced validation framework for ASP.NET for years. I’ve found and fixed numerous limitations. Not surprisingly, I find that many of my concerns remain unresolved within the Validation Attributes. I have worked to fix all of the complaints listed in this posting in my Peter’s Data Entry Suite. You’ll learn more here.

Field Templates cannot setup “compare two field” validators

One of the most common validation rules is not available as a ValidationAttribute: comparing two fields. For example, BirthDate < HireDate. This expression is easily represented with the CompareValidator in the web form level:

<asp:CompareValidator id="CompareBirthToHire" runat="server" 
ControlToValidate="BirthDateTextBox" ControlToCompare="HireDate"
Operator="LessThan" Type="Date" />

Field Templates are supposed to automatically setup Validator web controls like this based on the ValidationAttributes. I’d like to see something like:

[CompareColumn(OtherColumn="HireDate", Operator="LessThan")]
public DateTime BirthDate { get; set; }

 

Without this, you can still use the CustomValidatorAttribute on the class definition, but it is strictly server side. Field Templates do not manage a validator web control associated with it.

There are a few issues to fixing this problem, including:

  • Property-level validation only passes in the value of the property itself, not the overall object, to the IsValid() property. Therefore IsValid cannot see the second property’s value. Yet this is fixable because IsValid actually can be passed a second value, the entire instance, in the ValidationContext property. This issue requires a change to the DynamicValidator class. More importantly, if the goal is to convert the CompareColumnAttribute into a CompareValidator web control, we really don’t care about the IsValid method.
  • The two columns are converted into HTML using separate Field Templates. Each is a User Control, which means it uses a separate Naming Container. As a rule, properties on web controls take an ID to controls in the same naming container. So the CompareValidator cannot locate the two textboxes. My own Validator classes allow attaching to the textbox object as an alternative. You can learn more about my philosophy here: Improving ASP.NET: Finding Controls.
There are many more common validation rules

My validation suite includes around 25 Validator web controls. There are so many reusable ways to validate. In my Dynamic Data solution, I’ve created numerous ValidationAttributes.

Field Templates may not be coded to handle every ValidationAttribute

The Field Template must create a Validator web control for each ValidationAttribute declared on the column. Its easy to introduce a new ValidationAttribute on your business logic without it impacting the Field Template for which it was intended. For example, the default field template for strings, Text_Edit.ascx, lacks a validator for the StringLengthAttribute. Your Entity class should still catch errors prior to saving by using the System.ComponentModel.DataAnnotations.Validator class. But the Validator class is strictly server side.

In my solutions, I chose to abandon defining Validator web controls in the Field Template. Instead, the user drops in a new control called ColumnValidationManager. This control evaluates the list of ValidationAttributes on the column and creates all validators it needs. To work, the ValidationAttribute class must help out. My extensions to the ValidationAttribute class include methods to return an instance of the web control that will be used. If you do the same, be sure to also add the DynamicValidator in your validator generator code.

Here’s my version of Text_Edit.ascx Field Template:

<%@ Control Language="C#" AutoEventWireup="true" Inherits="PeterBlum.DES.DynamicData.TextEditFTUC" %>
<%@ Register assembly="PeterBlum.DES.DynamicData" namespace="PeterBlum.DES.DynamicData" tagprefix="desDD" %>
<script runat="server">
   1:  
   2:    protected void Page_Init(object sender, EventArgs e)
   3:    {
   4:       SetUpEditableDataControl(TextBox1);
   5:       SetUpColumnValidatorManager(ColumnValidatorManager1);
   6:    }
</script>
<des:FilteredTextBox ID="TextBox1" runat="server" ></des:FilteredTextBox>
<desDD:ColumnValidatorManager ID="ColumnValidatorManager1" runat="server" />

 

 

 

 

Index to this series of articles

Filed under: , , , ,

Comments

No Comments