Where does this go? Applying SoC to dynamic data – Part 2
Since a major goal of Dynamic Data is to separate the business logic from the user interface, let’s look at a few areas where these concepts still blend and their alternatives.
Today we look at the…
ValidationAttribute
The System.ComponentModel.DataAnnotations.ValidationAttribute is the base class for attributes that describe validation rules. Its children include RequiredAttribute, RangeAttribute, and RegularExpressionAttribute.
Validation has crossover with the user interface in that a validation error must be presented to the user. Therefore the business logic which specifies ValidationAttributes needs to expect that its communications will be handled in the User Interface layer.
Yet the ValidationAttribute lets you define an error message in its ErrorMessage property.
[Required(ErrorMessage="The Price is required.")]
public decimal UnitPrice { get; set; }
The ErrorMessage property violates the separation between the business logic and user interface in several ways:
- There is a single message for a property. It lacks context assigned by the user interface. While “The Price is required” may make sense initially, the user interface may need “Go to the Price box and enter a value.”
- If you want formatting, you have to avoid using UI specific tokens like HTML or RTF because the Entity class is in the middle tier.
- While the many attributes support localization, care must be taken to insure that the Entity class has the same culture settings and string storage mechanism.
How do you fix this?
I see nothing wrong with a standard error message in the Entity layer, void of any formatting. It handles the cases where the user interface is less demanding, such as when a Web Service needs to report an error to the user.
The user interface developer overrides the error message from the business layer in the Field Templates by assigning the ErrorMessage property on individual Validators.
Here is a modified version of the Integer_Edit.ascx Field Template.
<%@ Control Language="C#" CodeFile="Integer_Edit.ascx.cs" Inherits="Integer_EditField" %>
<asp:TextBox ID="TextBox1" runat="server" Text="<%# FieldValueEditString %>" Columns="10" CssClass="DDTextBox"></asp:TextBox>
<asp:RequiredFieldValidator runat="server" ID="RequiredFieldValidator1" CssClass="DDControl DDValidator" ControlToValidate="TextBox1" Display="Dynamic" Enabled="false"
ErrorMessage='<%# String.Format("Go to the {0} box and enter a value.", Column.DisplayName) %>' />
<asp:CompareValidator runat="server" ID="CompareValidator1" CssClass="DDControl DDValidator" ControlToValidate="TextBox1" Display="Dynamic"
Operator="DataTypeCheck" Type="Integer"
ErrorMessage=='<%# String.Format("The {0} box must only contain numbers.", Column.DisplayName) %>' />
<asp:RangeValidator runat="server" ID="RangeValidator1" CssClass="DDControl DDValidator" ControlToValidate="TextBox1" Type="Integer"
Enabled="false" EnableClientScript="true" MinimumValue="0" MaximumValue="100" Display="Dynamic"
ErrorMessage='<%# String.Format("Enter a number between {0} and {1}.", MinimumValue, MaximumValue) %>' />
<asp:DynamicValidator runat="server" ID="DynamicValidator1" CssClass="DDControl DDValidator" ControlToValidate="TextBox1" Display="Dynamic" />
Here are a few guidelines:
- To insert the property’s “DisplayName” (which comes from the DisplayNameAttribute or DisplayAttribute), use the Column.DisplayName property. You can see this in the RequiredFieldValidator.
- Don’t set up the ErrorMessage of the DynamicValidator. Its the one validator that will use the actual error message from the business logic. But it doesn’t come from a ValidationAttribute. Instead, it comes from exceptions thrown within your business logic as it validates a property.
- The RangeValidator’s MinimumValue and MaximumValue properties are established from the RangeAttribute.
Peter’s Soapbox
Personally I don’t think validators are the place for context specific error messages any more than the ValidationAttributes. Field Templates are data type specific, not situation specific. Instead the page developer should be able to specify their desired error messages.
Suppose you have a web form where you need the date fields to show a different error message than other date fields throughout the application. You would have to duplicate your Date_Edit.ascx Field Template, giving it new error messages and a new name like “DateVal001_Edit.ascx”. I don’t like the idea of creating a new Field Template just to modify something as minor as a string, style sheet class, or other visual element.
In my DES Dynamic Data, I introduced a new control called Customizer. It exposes numerous properties found on Field Templates, including validator error messages. My Field Templates are coded to look for an override found on the Customizer and update their Validator controls. This gives a context specific solution.
<desDD:Customizer ID="Customizer1" runat="server"
CurrencyTextBox_AlignRight="True" CurrencyTextBox_ShowSpinner="True"
Text_ShowEmailsAsHyperlinks="Default" Text_ShowUrlsAsHyperlinks="Default">
<ErrorMessages>
<desDD:ValidatorErrorMessages DataTypeName="Integer"
NameOfValidatorType="RequiredTextValidator"
ErrorMessage="Please enter only <b>digits</b>."
SummaryErrorMessage="Please enter only digits on the {LABEL} field." />
</ErrorMessages>
</desDD:Customizer>