In This Chapter
• Edit and Insert Mode Templates
• Changing Existing Field Template
• Creating a New Field Template
Enterprise applications deal with employees, managers, emergency contacts, prospects, customers, suppliers, and vendors—in other words, people and organizations, who all have names, addresses, and phone numbers. Implementing data entry logic for these attributes is repetitive, and for years, the ASP.NET developers have been developing user controls to reduce the code duplication. It is common to see user controls with text boxes and validators for entering street, city, state, and postal code in many web applications today.
Although they are a great mechanism for reusing presentation and validation logic, user controls require a certain amount of plumbing before they can be used on a web page. At the very minimum, user controls must be registered, and because even the simplest controls need to provide slightly varying functionality in different contexts, they also usually have one or more properties that need to be configured. As the user controls get more and more granular, the amount of plumbing work required to apply them increases. It quickly reaches the point of diminishing returns, and developers usually stop short of implementing user controls for the individual data entry fields.
Field template is a special type of ASP.NET user control that encapsulates presentation and validation logic for a single field. Unlike the traditional user controls, field templates do not require registration. Instead, a simple DynamicControl
or a DynamicField
placed on a web page automatically loads and configures an appropriate field template based the column name and metadata.
Metadata information, readily available in Dynamic Data web applications, dramatically lowers the amount of plumbing code developers have to write when working with user controls, enables effective use of smaller user controls, and takes code reuse to an unprecedented new level.
By convention, field templates are located in the DynamicDataFieldTemplates
folder of the Dynamic Data web application projects and websites. A single instance of a field template provides user interface for one particular field value. Consider Listing 3.1, which shows markup file of the DateTime
field template.
<%@ Control Language="C#" CodeBehind="DateTime.ascx.cs"
Inherits="WebApplication.DynamicData.FieldTemplates.DateTimeField" %>
<asp:Literal runat="server" ID="literal" Text="<%# FieldValueString %>" />
This is a read-only field template, the simplest type of field template. It relies on ASP.NET data binding syntax <%# %>
to display the field value in a single Literal
control. Listing 3.2 shows the code-behind of the DateTime
field template. You can see that it inherits from the FieldTemplateUserControl
, a special base class provided for field templates by Dynamic Data. This class defines the FieldValueString
property to which the Literal
control is bound.
using System.Web.DynamicData;
using System.Web.UI;
namespace WebApplication.DynamicData.FieldTemplates
{
public partial class DateTimeField : FieldTemplateUserControl
{
public override Control DataControl
{
get { return this.literal; }
}
}
}
Code-behind files perform most of the work required to make the generated web pages dynamic. In this simplest example, the template overrides the DataControl
property defined by the base class. Entity templates (see Chapter 4, “Entity Templates”) use this property when dynamically generating entity forms, to associate each field template with a matching Label
control.
In addition to the FieldValueString
property, the FieldTemplateUserControl
class also provides another property called FieldValue
, which returns the raw value object. At a cursory glance, the FieldValue
property could be used to bind the Literal
control as well. However, it is important to use the FieldValueString
property as it automatically takes into account additional display format properties when converting the raw field value to a string:
• DataFormatString
specifies a .NET format string that will be passed to the String.Format
method to convert the field value to a display string.
• NullDisplayText
specifies a replacement string that will be used when the raw field value is null.
• ConvertEmptyStringToNull
is a Boolean value that determines whether an empty string value should treated as null and if the NullDisplayText
will be used as a replacement for empty strings as well as null values. This property is true
by default.
• HtmlEncode
is a Boolean value that determines whether any special HTML symbols in the raw field value, such as '<'
or '>'
, will be encoded using escape sequences, such as <
and >
. This property is true
by default.
Display format properties can be specified in the data model by applying the DataTypeAttribute
or the DisplayFormatAttribute
to a particular column, as shown in Listing 3.3. The DisplayFormatAttribute
, applied to the OrderDate column, specifies a long date {0:D}
format string. The DataTypeAttribute
, applied to the RequiredDate column, does not have its own DataFormatString
property. Instead, it offers the DisplayFormat
property, which gets a default DisplayFormatAttribute
generated, based on the specified data type. For DataType.Date,
the default data format string will be a short date {0:d}
.
using System.ComponentModel.DataAnnotations;
namespace DataModel
{
[MetadataType (typeof(Order.Metadata))]
partial class Order
{
public class Metadata
{
[DisplayFormat(DataFormatString = "{0:D}")]
public object OrderDate;
[DataType(DataType.Date)]
public object RequiredDate;
}
}
}
Display format properties can be also specified in page markup by setting the corresponding properties of the DynamicControl
or DynamicField
classes, similar to the properties of the “non-dynamic” BoundField
. Listing 3.4 shows an example where a DynamicField
is used in a GridView
control and specifies a custom date format string {0:MM/dd/yy}
for the ShippedDate column.
<%@ Page Language="C#"
MasterPageFile="~/Site.master" CodeBehind="SamplePage.aspx.cs"
Inherits="WebApplication.Samples.Ch01.DataFormatString.SamplePage" %>
<asp:Content ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<asp:GridView ID="gridView" runat="server" DataSourceID="dataSource"
AutoGenerateColumns="false" AllowPaging="true">
<columns>
<asp:DynamicField DataField="OrderDate" />
<asp:DynamicField DataField="RequiredDate" />
<asp:DynamicField DataField="ShippedDate" DataFormatString="{0:MM/dd/yy}" />
</columns>
</asp:GridView>
<asp:EntityDataSource ID="dataSource" runat="server"
ConnectionString="name=NorthwindEntities"
DefaultContainerName="NorthwindEntities" EntitySetName="Orders" />
</asp:Content>
Figure 3.1 shows the web page generated by this code. As you can see, the OrderDate
values use the long format specified in the DisplayFormatAttribute
in the model; the RequiredDate
values use the short format provided by the DataTypeAttribute
; and the ShippedDate
values use the custom format specified in the page markup.
Inside of the field templates, the display format properties are available via the FormattingOptions
property inherited from the FieldTemplateUserControl
. The base class automatically initializes them based on the formatting options of the host—DynamicControl
or DynamicField
.
As you might expect, a fair amount of logic is involved in converting the raw field value to the final display string. By using the FieldValueString
property of the FieldTemplateUserControl
class, field templates ensure consistent behavior that offers application developers great flexibility in defining the presentation aspects of their data. The DataTypeAttribute
allows specifying default display properties for a column in the data model based on its data type. The DisplayFormatAttribute
allows fine-tuning it with the standard or custom format strings. Finally, the DynamicControl
and DynamicField
controls allow tailoring the display of column values for a specific page in a web application.
In addition to the read-only field templates, Dynamic Data has Edit and Insert field templates. Unlike the Read-Only templates, where the field value is simply displayed on the page, the Edit templates allow the user to modify it using a data entry control, such as a TextBox
. By convention, Dynamic Data distinguishes Edit and Insert templates by the _Edit
and _Insert
they have at the end of their file names, respectively. The Insert templates are a special case of Edit templates, used for editing field values of rows that have not been saved to the database. However, most Edit mode field templates can be used in Insert mode as well.
The Edit field templates are significantly more complex than their read-only counterparts. Consider Listing 3.5, which shows the Edit version of the DateTime
field template.
<%@ Control Language="C#" CodeBehind="DateTime_Edit.ascx.cs"
Inherits="WebApplication.DynamicData.FieldTemplates.DateTime_EditField" %>
<asp:TextBox ID="textBox" runat="server" Text='<%# FieldValueEditString %>'
Columns="20" />
<asp:RequiredFieldValidator runat="server" ID="requiredFieldValidator"
ControlToValidate="textBox" Enabled="false" />
<asp:RegularExpressionValidator runat="server" ID="regularExpressionValidator"
ControlToValidate="textBox" Enabled="false" />
<asp:DynamicValidator runat="server" ID="dynamicValidator"
ControlToValidate="textBox" />
<asp:CustomValidator runat="server" ID="dateValidator" ControlToValidate="textBox"
EnableClientScript="false" Enabled="false"
OnServerValidate="DateValidator_ServerValidate" />
In addition to the data entry control, the edit field templates typically have one or more validation controls. The DateTime_Edit
template includes a RequiredFieldValidator
, a RegularExpressionValidator
, a DynamicValidator
, and a CustomValidator
control. Notice that with the exception of the DynamicValidator
, these controls are not enabled by default. They are configured dynamically by the Page_Load
event handler defined in the code-behind file shown in Listing 3.6.
Each validator control is configured by a call to the SetUpValidator
method of the FieldTemplateUserControl
base class. This method enables and configures the validator if the column metadata contains an appropriate data annotation attribute. In particular, the RequiredFieldValidator
will be enabled when the column is marked with a RequiredAttribute
that does not allow empty strings, and the RegularExpressionValidator
will be enabled when the column is marked with a RegularExpressionAttribute
. In addition to enabling the validator, the SetUpValidator
method also assigns its ErrorMessage
and ToolTip
properties with an error message either explicitly specified in the data annotation attribute or generated based on the column’s display name.
DynamicValidator
is a special validation control provided by Dynamic Data. It ensures all validation attributes applied to the column are invoked to verify its value is correct. This andincludes not only the well-known RequiredAttribute
, RangeAttribute
, and CustomValidationAttribute
, but also all other descendants of the ValidationAttribute
that may be applied to the column and are not covered by a specific ASP.NET validator control in this field template.
The CustomValidator
control in this field template performs the job you would normally expect the DataTypeAttribute
to do. Even though DataTypeAttribute
inherits from the ValidationAttribute
class, it does not actually perform any validation. Its IsValid
method always returns true
, which is why the DateTime_Edit
template uses the CustomValidator
control to ensure that the value entered by the user matches the type specified by the DataTypeAttribute
. The SetUpCustomValidator
method enables the validator if the column was marked with a DataTypeAttribute
and the DateValidator_ServerValidate
method performs the actual validation.
using System;
using System.Collections.Specialized;
using System.ComponentModel.DataAnnotations;
using System.Web;
using System.Web.DynamicData;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebApplication.DynamicData.FieldTemplates
{
public partial class DateTime_EditField : FieldTemplateUserControl
{
private static DataTypeAttribute DefaultDateAttribute =
new DataTypeAttribute(DataType.DateTime);
public override Control DataControl
{
get { return this.textBox; }
}
protected void Page_Load(object sender, EventArgs e)
{
this.textBox.ToolTip = this.Column.Description;
this.SetUpValidator(this.requiredFieldValidator);
this.SetUpValidator(this.regularExpressionValidator);
this.SetUpValidator(this.dynamicValidator);
this.SetUpCustomValidator(this.dateValidator);
}
protected override void ExtractValues(IOrderedDictionary dictionary)
{
dictionary[Column.Name] = this.ConvertEditedValue(this.textBox.Text);
}
protected void DateValidator_ServerValidate(object source,
ServerValidateEventArgs args)
{
DateTime dummyResult;
args.IsValid = DateTime.TryParse(args.Value, out dummyResult);
}
private void SetUpCustomValidator(CustomValidator validator)
{
if (this.Column.DataTypeAttribute != null)
{
switch (this.Column.DataTypeAttribute.DataType)
{
case DataType.Date:
case DataType.DateTime:
case DataType.Time:
this.EnableCustomValidator(validator, this.Column.DataTypeAttribute);
break;
}
}
else if (this.Column.ColumnType.Equals(typeof(DateTime)))
{
this.EnableCustomValidator(validator, DefaultDateAttribute);
}
}
private void EnableCustomValidator(CustomValidator validator,
DataTypeAttribute attribute)
{
validator.Enabled = true;
validator.ErrorMessage = HttpUtility.HtmlEncode(
attribute.FormatErrorMessage(this.Column.DisplayName));
}
}
}
You might have noticed that the DateTime_Edit
template uses the FieldValueEditString
property of the FieldTemplateUserControl
base class. This property is similar to the FieldValueString
property discussed earlier in that it respects the FormattingOptions
when converting the raw column value to a display string. However, it was specifically designed for use in the Edit mode templates, and an additional property is used to control its behavior.
• ApplyFormatInEditMode
is a Boolean value that determines whether the DataFormatString
, NullDisplayText
, ConvertEmptyStringToNull
, and HtmlEncode
display format properties will be used in Edit mode. This property is false
by default.
Similar to using the FieldValueString
property when implementing Read-only field templates, it is also important to rely on the FieldValueEditString
property as much as possible when implementing the Edit and Insert mode templates. This helps the field templates to behave consistently and allow the application developers to change presentation aspects of column values using metadata attributes as well as explicit configuration in page markup.
Dynamic Data uses the same field template for all matching columns in the model. The DateTime_Edit
field template is automatically used for all columns of type DateTime
in the Northwind data model, including OrderDate, RequiredDate, and ShippedDate columns of the Order table as well as BirthDate and HireDate columns of the Employee table. By changing definition of the field template, you affect all web pages where it is used. Figure 3.2 shows how the experience of entering date values can be improved by allowing the users to select a date from a calendar drop-down.
The modified field template, DateTime_Edit.ascx
, is shown in Listing 3.7. This example uses the CalendarExtender
control from the AJAX Control Toolkit, but you can use any other ASP.NET control from your favorite component vendor. The CalendarExtender
is associated with the TextBox
by specifying its TagetControlID
property. Using the PopupButtonID
property, it is also associated with an ImageButton
that users can click to display the calendar drop-down.
The AJAX (Asynchronous JavaScript and XML) Control Toolkit is an extension of the ASP.NET framework developed by Microsoft to enhance client-side behavior of the standard WebForms controls. It is an open source project hosted on CodePlex and available for download at http://AjaxControlToolkit.CodePlex.com.
The AJAX Control Toolkit comes in a ZIP archive that you need to extract to a directory on your hard drive. Before you can begin using AJAX controls, you need to add the downloaded AjaxControlToolkit.dll as a reference to your web application project in Visual Studio.
<%@ Control Language="C#" CodeBehind="DateTime_Edit.ascx.cs"
Inherits="WebApplication.DynamicData.FieldTemplates.DateTime_EditField" %>
<asp:TextBox ID="textBox" runat="server" Text='<%# FieldValueEditString %>'
Columns="20" />
<asp:ImageButton ID="calendarButton" runat="server"
ImageUrl="../Content/Images/Calendar.png" />
<ajax:CalendarExtender runat="server" TargetControlID="textBox"
PopupButtonID="calendarButton" />
<asp:RequiredFieldValidator runat="server" ID="requiredFieldValidator"
ControlToValidate="textBox" Enabled="false" />
<asp:RegularExpressionValidator runat="server" ID="regularExpressionValidator"
ControlToValidate="textBox" Enabled="false" />
<asp:DynamicValidator runat="server" ID="dynamicValidator"
ControlToValidate="textBox" />
<asp:CustomValidator runat="server" ID="dateValidator" ControlToValidate="textBox"
EnableClientScript="false" Enabled="false"
OnServerValidate="DateValidator_ServerValidate" />
Notice that this example uses a custom ajax:
tag prefix with the CalendarExtender
without actually registering it. We could have used the ASP.NET <%@ Register %>
directive to register this tag prefix directly in the DateTime_Edit.ascx
. However, because the AJAX Control Toolkit is used in other field templates of this sample project, it is better to register it in the Web.config
as shown in Listing 3.8. This makes the AJAX controls available in all pages and controls of the web application and helps you avoid having to register the same tag prefix in multiple pages and user controls.
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0"/>
<pages styleSheetTheme="Default">
<controls>
<add tagPrefix="ajax" assembly="AjaxControlToolkit"
namespace="AjaxControlToolkit"/>
</controls>
</pages>
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
<connectionStrings>
<add name="NorthwindEntities"
providerName="System.Data.EntityClient"
connectionString="
metadata=res://*/Northwind.csdl¦
res://*/Northwind.ssdl¦
res://*/Northwind.msl;
provider=System.Data.SqlClient;
provider connection string="
data source=.sqlexpress;
initial catalog=Northwind;
integrated security=True;
multipleactiveresultsets=True""/>
</connectionStrings>
</configuration>
Several of the built-in Dynamic Data field templates, including the DateTime
and DateTime_Edit
templates, rely on data binding to display column value on the page.
<asp:TextBox ID="textBox" runat="server" Text='<%# FieldValueEditString %>' />
In addition, the field template controls themselves are data bound, and several properties of the FieldTemplateUserControl
base class rely on the data binding mechanics. These properties can be used safely only from the DataBind
method and will throw an InvalidOperationException
when used outside of the data binding context, such as during a post-back:
• Row
provides access to the entity instance the field template is bound to, such as an Order object. In Read-only and Edit modes, it is an equivalent of calling the Page.GetDataItem
method. In Insert mode, this property returns a CustomTypeDescriptor
object that provides access to default field values extracted by the page template from the page request URL (more on this in Chapter 6, “Page Templates”).
• FieldValue
returns value of the column property, such as Order.OrderDate
. It is an equivalent of calling DataBinder.Eval(Row, Column.Name)
.
• FieldValueString
converts the field value to a display string in Read-only mode, using the formatting options specified in the data model or page markup. It is an equivalent of calling FormattingOptions.FormatValue(FieldValue)
.
• FieldValueEditString
converts the field value to a string in Edit or Insert mode, using the formatting options specified in the data model or page markup. It is an equivalent of calling FormattingOptions.FormatEditField(FieldValue)
.
During a post-back, a field template itself does not actually save the new column value back in its Row
object. Instead, the FieldTemplateUserControl
class implements the IBindableControl
interface and relies on the parent FormView
, DetailsView
, and GridView
controls to perform this task. When the parent control is handling an Insert or an Update command, it iterates through the list of its child controls and calls the ExtractValues
of each child that implements IBindableControl
interface.
protected override void ExtractValues(IOrderedDictionary dictionary)
{
dictionary[this.Column.Name] =
this.ConvertEditedValue(this.textBox.Text);
}
The ExtractValues
method receives a dictionary object used to store column name and value pairs. The extract from the DateTime_Edit
field template just shown is a typical implementation of this method. The ConvertEditedValue
method is provided by the FieldTemplateUserControl
base class. It can automatically convert the field value to null based on the ConvertEmptyStringToNull
and NullDisplayText
formatting options.
Notice that by providing the dictionary argument, ExtractValue
method allows a field template to return values of other columns or even multiple column values. This capability is important for the foreign key templates used for navigation properties, such as the Customer property of the Order
class in the Northwind data model, that need to return values of the corresponding primitive properties, such as CustomerID. Consider Listing 3.9, which shows the markup of the ForeignKey_Edit
template.
<%@ Control Language="C#" CodeBehind="ForeignKey_Edit.ascx.cs"
Inherits="WebApplication.DynamicData.FieldTemplates.ForeignKey_EditField" %>
<asp:DropDownList ID="dropDownList" runat="server"/>
<asp:RequiredFieldValidator runat="server" ID="requiredFieldValidator"
ControlToValidate="dropDownList" Enabled="false" />
<asp:DynamicValidator runat="server" ID="dynamicValidator"
ControlToValidate="dropDownList" />
Notice that the ForeignKey_Edit
template uses a DropDownList
control, which allows users to select from a list of items created programmatically, in the Page_Load
method shown in Listing 3.10. Each ListItem
object in the DropDownList
represents a single foreign key, with Text
property storing value of the display column from the referenced table and Value
property storing a comma-separated list of its primary key columns. This code relies on the convenience method, called PopulateListControl
, provided by the FieldTemplateUserControl
base class, which does all the heavy lifting required to query the referenced table, create the ListItem
objects, and add them to the DropDownList
.
using System;
using System.Collections.Specialized;
using System.Web.DynamicData;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebApplication.DynamicData.FieldTemplates
{
public partial class ForeignKey_EditField : FieldTemplateUserControl
{
public override Control DataControl
{
get { return this.dropDownList; }
}
protected void Page_Load(object sender, EventArgs e)
{
if (this.dropDownList.Items.Count == 0)
{
if (this.Mode == DataBoundControlMode.Insert ¦¦ !this.Column.IsRequired)
this.dropDownList.Items.Add(new ListItem("[Not Set]", string.Empty));
this.PopulateListControl(this.dropDownList);
}
this.SetUpValidator(this.requiredFieldValidator);
this.SetUpValidator(this.dynamicValidator);
}
protected override void OnDataBinding(EventArgs e)
{
base.OnDataBinding(e);
string selectedValueString = this.GetSelectedValueString();
if (this.dropDownList.Items.FindByValue(selectedValueString) != null)
this.dropDownList.SelectedValue = selectedValueString;
}
protected override void ExtractValues(IOrderedDictionary dictionary)
{
this.ExtractForeignKey(dictionary, this.dropDownList.SelectedValue);
}
}
}
To display the current field value, the ForeignKey_Edit
template cannot rely on the FieldValueEditString
property because the FieldValue
in this case consists of one or more values of the underlying primitive columns. For the Customer column of the Order table, this would mean displaying CustomerID, “ALFKI”, instead of the Customer’s CompanyName, “Alfreds Futterkiste”, which is not very user friendly. Instead, the ForeignKey_Edit
template overrides the OnDataBinding
method to select the matching item in the DropDownList
. It calls the GetSelectedValueString
, a convenience method provided by the FieldTemplateUserControl
base class. For foreign key columns, this method is equivalent to calling ForeignKeyColumn.GetForeignKeyString(Row)
and returns a comma-separated list of values of the primitive columns in the foreign key.
The ForeignKey_Edit
template also overrides the ExtractValues
method and calls the ExtractForeignKey
provided by the FieldTemplateUserControl
base class. Given a comma-separated list of column values, this method stores them in the dictionary under the names of the foreign key columns. Under the hood, this method simply calls ForeignKeyColumn.ExtractForeignKey(dictionary, selectedValue)
.
The set of field templates provided by Dynamic Data can be extended whenever a special data entry control is needed to handle values of a certain type. For example, the Northwind database uses regular string columns to store phone numbers for customers, employees, suppliers, and shippers. The phone numbers already stored in the database are in a consistent (###)###-####
format; however, Dynamic Data uses the regular Text_Edit
field template for these columns, which does not enforce the phone number format. It would be nice to create a field template that would help users enter the phone numbers correctly.
You create a new field template by adding a new user control to the DynamicDataFieldTemplates
folder of your project. You can also copy an existing field template that most closely matches the desired functionality. If you do copy an existing field template, remember to change the class name in both markup and code-behind files. In this example, however, we will start from scratch and create a new user control called PhoneNumber_Edit
, shown in Listing 3.11.
<%@ Control Language="C#" CodeBehind="PhoneNumber_Edit.ascx.cs"
Inherits="WebApplication.DynamicData.FieldTemplates.PhoneNumberEditField" %>
<asp:TextBox ID="textBox" runat="server" Text='<%# FieldValueEditString %>'/>
<ajax:FilteredTextBoxExtender TargetControlID="textBox" runat="server"
FilterType="Custom" ValidChars="+()-1234567890" />
<asp:RequiredFieldValidator runat="server" ID="requiredFieldValidator"
ControlToValidate="textBox" Enabled="false" />
<asp:RegularExpressionValidator runat="server" ID="regularExpressionValidator"
ControlToValidate="textBox" />
<asp:DynamicValidator runat="server" ID="dynamicValidator"
ControlToValidate="textBox" />
As you can see, the PhoneNumber_Edit
field template is similar to the DateTime
template discussed earlier. It has a TextBox
control used to enter the phone number, a FilteredTextBoxExtender
, another AJAX control used to prevent users from entering symbols that would not be valid in a phone number, as well as several validator controls.
Although having a single DynamicValidator
control is sufficient to enforce all validation attributes applied to the column in the data model, this control performs validation only on the server side, which requires a post-back before the error can be reported. Other ASP.NET validation controls, such as the RequiredFieldValidator
, are optional, but because they also offer client-side validation logic, it’s best to include them in field templates to provide immediate feedback to the user, and improve the data entry experience.
Listing 3.12 shows the code-behind of the PhoneNumber_Edit
field template.
using System;
using System.Collections.Specialized;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
using System.Web.DynamicData;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebApplication.DynamicData.FieldTemplates
{
public partial class PhoneNumberEditField : FieldTemplateUserControl
{
public override Control DataControl
{
get { return this.textBox; }
}
protected void Page_Load(object sender, EventArgs e)
{
this.textBox.ToolTip = this.Column.Description;
this.SetUpValidator(this.requiredFieldValidator);
this.SetUpValidator(this.dynamicValidator);
this.SetUpRegexValidator(this.regularExpressionValidator);
}
protected override void ExtractValues(IOrderedDictionary dictionary)
{
dictionary[this.Column.Name] = this.ConvertEditedValue(this.textBox.Text);
}
private void SetUpRegexValidator(RegularExpressionValidator validator)
{
RegularExpressionAttribute attribute = this.Column.Attributes
.OfType<RegularExpressionAttribute>().FirstOrDefault();
if (attribute == null)
{
attribute = new RegularExpressionAttribute(@"(d{3})d{3}-d{4}");
attribute.ErrorMessage = "The field {0} must be a valid phone number.";
}
validator.Enabled = true;
validator.Text = "*";
validator.ValidationExpression = attribute.Pattern;
validator.ErrorMessage = HttpUtility.HtmlEncode(
attribute.FormatErrorMessage(this.Column.DisplayName));
validator.ToolTip = validator.ErrorMessage;
this.IgnoreModelValidationAttribute(attribute.GetType());
}
}
}
Most of the phone number validation in the new field template is performed by the standard ASP.NET control, RegularExpressionValidator
, which ensures that the value entered by the user matches the regular expression pattern that defines a valid phone number. In addition to the server-side validation based on the .NET implementation of regular expressions in the System.Text.RegularExpressions
namespace, it also works on the client side taking advantage of the regular expression functionality in JavaScript.
The RegularExpressionValidator
is configured by the SetUpRegexValidator
method defined in the code-behind. This method first searches the list of attributes applied to the column in the data model, Column.Attributes
, for a RegularExpressionAttribute
that might have been supplied by the application developer to indicate the expected phone number pattern. If no such attribute exists, this method creates a default Regular ExpressionAttribute
with the pattern, (d{3})d{3}-d{4}
, that matches U.S. phone numbers, such as (877)514-9180, with a three-digit area code in parentheses, followed by a seven-digit local number. The default attribute also has a generic error message that says that a column must be a valid phone number.
Having obtained the RegularExpressionAttribute
instance, the SetUpRegexValidator
method configures the RegularExpressionValidator
control consistently with how the SetUpValidator
method of the FieldTemplateUserControl
base class does it—the Text
displayed by the validator itself is always a star symbol; its ErrorMessage
is reported through a ValidationSummary
control on the page and the ToolTip
contains the complete error message.
The last step in setting up the RegularExpressionValidator
is to call the IgnoreModel ValidationAttribute
method of the FieldTemplateUserControl
base class. This allows the DynamicValidator
to ignore the RegularExpressionAttribute
during its validation because this attribute is already covered by the RegularExpressionValidator
. Aside from helping to avoid doing the same validation check twice, this also prevents the regular expression validation error from being reported twice—first by the RegularExpression Validator
and then by the DynamicValidator
control.
Listing 3.13 shows an example of how the new PhoneNumber_Edit
field template can be associated with a column using data annotation attributes.
using System.ComponentModel.DataAnnotations;
namespace DataModel
{
[MetadataType(typeof(Employee.Metadata))]
partial class Employee
{
public class Metadata
{
[DataType(DataType.Date)]
public object BirthDate;
[DataType(DataType.Date)]
public object HireDate;
[DataType(DataType.PhoneNumber)]
[RegularExpression(@"(d{2,3})d{3}-d{4}",
ErrorMessage = "The Home Phone must be a valid phone number.")]
public object HomePhone;
}
}
}
DataTypeAttribute
, applied to the HomePhone column, specifies this string column’s actual data type is PhoneNumber
. Dynamic Data uses this information as a hint when looking for a matching field template for this column and chooses the new PhoneNumber_Edit
template instead of the default Text_Edit
. In this example, RegularExpressionAttribute
is also applied to the HomePhone column to provide a pattern that matches not only U.S. phone numbers, which have three-digit area codes, but also UK numbers, which might have two-digit area codes.
Figure 3.3 shows the Employee Edit page after the implementation of the PhoneNumber_Edit
field template has been completed. Although you cannot tell from this screenshot, thanks to the FilteredTextBoxExtender
, you cannot type any alphabetical symbols in the phone number text box. The pattern validation is performed on the client side as soon as you tab out of the text box, such as in this case, where one digit was removed from an otherwise valid UK phone number.
Dynamic Data tries to find the most specific template that matches the field based on the column type and control mode. If such a field template does not exist, Dynamic Data will gradually relax the matching rules until it finds the best possible match among the field templates in the project.
Consider the RequiredDate column of the Order table used in Listing 3.3 earlier in this chapter. If the developer specifies the exact template name by applying the UIHintAttribute
as shown in Listing 3.14, Dynamic Data first checks to see if a template with the specified name, MyDate.ascx
, exists. If this template does not exist, Dynamic Data then checks if a DataTypeAttribute
is applied to the column and tries using the specified DataType
value as the template name. In this example, it checks to see if a field template called Date.ascx
exists.
using System.ComponentModel.DataAnnotations;
namespace DataModel
{
[MetadataType(typeof(Order.Metadata))]
partial class Order
{
public class Metadata
{
[UIHint("MyDate")]
[DataType(DataType.Date)]
public object RequiredDate;
}
}
}
If the column has no data annotation attributes, Dynamic Data then tries to find a matching template based on the name of its type. First, it looks for a template whose name matches the full name of the type, including the namespace where the type is defined. In the example of RequiredDate
, when looking for a template for a column of DateTime
type, it checks to see if a template called System.DateTime.ascx
exists. If not, Dynamic Data then tries the short type name, without the namespace, and finally chooses the template called DateTime.ascx
provided by Dynamic Data out of the box.
Dynamic Data allows template names to use special aliases instead of actual type names for two built-in .NET types, Int32
and String
. When looking for a field template for a column of type String
, Dynamic Data considers the template called Text.ascx
to be a match. In the Northwind data model, this means that the Text.ascx
template will be used for the ProductName column of the Product table. Likewise, when looking for a template for an Int32
column, Dynamic Data considers Integer.ascx
to be a match as well. Table 3.1 shows the built-in type aliases at a glance.
Type aliases are considered less specific than the type names, and type names take precedence over aliases during template lookup. For example, if the Dynamic Data project contains templates called Int32.ascx
and Integer.ascx
, only the one called Int32.ascx
will be used for an integer column.
If there is no matching field template for a particular data type, Dynamic Data uses fallback logic to find a template for a more generic type that could be used instead. Consider the UnitsInStock column of the Product entity, which is of type Int16
. When looking for a field template for this column, Dynamic Data first checks to see if a template called Int16.ascx
exists. Because there is no template with this name, it uses the fallback rules shown in Table 3.2 to determine the fallback type, Int32
, and tries to find a matching template for that type.
Both type names and aliases are used during template lookup for the fallback type, just as they were used for the original type. For the UnitsInStock column, this means that Dynamic Data will then try System.Int32.ascx
, Int32.ascx
, and Integer.ascx
. Because none of these field templates exist in the default Dynamic Data project, it falls back to the next type—String
and tries System.String.ascx
, String.ascx
, and finally choosing Text.ascx
.
Control mode defines the context where the field template will be used. It can be one of the members of the ASP.NET DataBoundControlMode
enumeration:
• ReadOnly
templates are used by the Details.aspx
page template or custom pages to display field values in Read-only mode.
• Edit
templates are used by the Edit.aspx
page template or custom pages to change field values of an existing row.
• Insert
are used by the Insert.aspx
page template or custom pages to collect field values for a new row.
When looking for a ReadOnly
field template for the ShippedDate column of the Order table, which has DateTime
data type and no additional metadata attributes in the sample data model, Dynamic Data finds the DateTime.ascx
template. Edit
mode is considered more specific because it requires the field template to not only display the current field value, but also allow changing it. Therefore, when looking for an Edit field template for the ShippedDate column, Dynamic Data chooses the template called DateTime_Edit.ascx
if it exists. Note that Dynamic Data uses the "_Edit"
suffix for Edit templates and no suffix for the ReadOnly
templates. The Insert mode is even more specific because it might require special logic to deal with rows that have not been submitted to the database, and Dynamic Data chooses the template called DateTime_Insert.ascx
if it exists.
If there is no matching field template for a particular control mode, Dynamic Data uses fallback logic to find another template for a more generic mode. In this ongoing example, when looking for an Insert template for the ShippedDate column, Dynamic Data will first try to find the template called DateTime_Insert.ascx
. Because there is no field template with this name, it falls back from the Insert to the Edit mode and ends up using the template called DateTime_Edit.ascx
, assuming it can handle both Edit and Insert modes. Similarly, if an Edit mode template cannot be found, Dynamic Data falls back to Read-only mode.
However, before Dynamic Data falls back to the next mode, it performs the type fallback. Consider UnitsInStock, an Int16 column from the Products table. When looking for an Edit template for this field, it tries using System.Int16_Edit.ascx
and Int16_Edit.ascx
templates first, but because they do not exist, it falls back to type Int32
and checks for System.Int32_Edit.ascx
and Int32_Edit.ascx
and eventually finds Integer_Edit.ascx
. If Dynamic Data performed the mode fallback first, it would skip this template and try to look for a ReadOnly template called System.Int16.ascx
instead.
So far, our discussion has been about the lookup rules Dynamic Data uses for columns of primitive types that can be stored using built-in system types like Int32
and String
. The Dynamic Data metadata API represents them using the MetaColumn
class.
Navigation columns, on the other hand, are columns that provide access to one or more entities that are defined as custom classes in the data model itself. There are three types of navigation columns from the Dynamic Data prospective:
• MetaForeignKeyColumn
represents a navigation property that returns a single entity referenced by the underlying foreign key field. In the Northwind example, Product is a foreign key column of the Order_Detail entity that represents the Product entity referenced by the ProductID
foreign key field.
• MetaChildrenColumn
is an opposite of a foreign key. In the Northwind example, Order_Details column of the Product entity returns all Order_Detail
items that reference a particular product instance.
• Many-to-Many column is a special case of a MetaChildrenColumn
where the IsManyToMany
property is set to true
. In the Northwind example, the Territories column of the Employees entity returns all territories assigned to a particular employee. Likewise, the Employees column of the Territory entity returns all employees assigned to work in a particular Territory. Dynamic Data considers both of these columns as Many-to-Many.
Navigation columns get special treatment during field template lookup. Unless the developer already placed a UIHintAttribute
on a navigation property, Dynamic Data uses the rules shown in Table 3.3 to come up with a default UI Hint.
In this example, it means that for the Product column of the Order_Details table, Dynamic Data uses a template called ForeignKey.ascx
; for the Order_Details column of the Product table, it uses a template called Children.ascx
; and for both Employee.Territories and Territory.Employees columns, it uses the template called ManyToMany.ascx
.
Effectively, there is no type fallback during template lookup for navigation columns. However, Dynamic Data does perform mode fallback, so if your page needs a foreign key template in Insert mode, it first tries ForeignKey_Insert.ascx
, then ForeignKey_Edit.ascx
, and then ForeignKey.ascx
if necessary. The default Dynamic Data project template actually takes advantage of this to disable user interface for the children columns in Insert mode. If you open the Children_Insert.ascx
field template, you see that it is empty. If this empty field template did not exist, Children.ascx
would be used in its place, causing runtime errors because this template is not prepared to handle the scenario when the parent row does not exist.
Although the lookup rules for field templates might seem complex at a first, they are based on common-sense logic, and when you understand the idea behind them, the results will feel intuitive. Just remember that Dynamic Data tries to choose the most specific field template based on the column definition and control mode, and if such a field template does not exist, it gradually relaxes the matching rules until it finds a match or reports an error. The resulting behavior allows you to easily add new field templates where unique behavior is required to support a particular column type while allowing you to reuse existing templates with common behavior such as displaying read-only field values. Figure 3.4 shows the actual algorithm used by Dynamic Data. FieldTemplateFactory
, a special class defined in the System.Web.DynamicData
namespace, performs this task.
As an example, consider Table 3.4, which shows the sequence of template file names Dynamic Data will try when looking for a matching field template for the HomePhone
property of the Employee entity.
Because the HomePhone property has no UIHintAttribute
and UIHint
property is not specified for DynamicControl
or DynamicField
in page markup, the search sequence does not include the file names based on the UIHint
. However, the HomePhone property does have the DataType
specified in the data model using the [DataType(DataType.PhoneNumber)]
attribute.
In Insert mode, the FieldTemplateFactory
checks to see if PhoneNumber_Insert.ascx
, System.String_Insert.ascx
, String_Insert.ascx
, and Text_Insert.ascx
field templates exist before finding the PhoneNumber_Edit.ascx
template created earlier in this chapter. If this template didn’t exist, the FieldTemplateFactory
would continue checking for System.String_Edit.ascx
, String_Edit.ascx
, and eventually find the Text_Edit.ascx
field template provided by Dynamic Data projects out of the box.
In Edit mode, the FieldTemplateFactory
starts with PhoneNumber_Edit.ascx
and because it exists, uses it immediately. Likewise, had this field template not been defined, it would continue the search and eventually find the Text_Edit.ascx
template provided by Dynamic Data.
In ReadOnly mode, the FieldTemplateFactory
starts with PhoneNumber.ascx
. Because the read-only version of the phone number field template does not exist, it continues by trying System.String.ascx
and String.ascx
and finally finds the Text.ascx
field template provided by Dynamic Data out of the box.
Table 3.5 shows another example of field template lookup sequence, this time for the UnitsInStock
property of the Product entity. This column is of type Int32
and, unlike the HomePhone in the previous example, doesn’t have a DataTypeAttribute
applied to it. Instead, this field template takes advantage of the fallback logic Dynamic Data uses for types and modes.
In Insert mode, the FieldTemplateFactory
starts with the most specific template name (System.Int32
) and mode (Insert). It tries System.Int32_Insert
, Int32_Insert
, and Integer_Insert
before falling back to the next type—String
. It continues trying System.String_Insert
, String_Insert
, and Text_Insert
before falling back to the next mode, Edit, and repeating the steps starting with the most specific template name, System.Int32
. Eventually, it finds Integer_Edit.ascx
, the built-in field template provided by Dynamic Data for integer columns.
For the UnitsInStock column, Dynamic Data uses both type aliases during the lookup sequence—Integer
, which represents Int32
, and Text
, which represents String
.
The Edit mode lookup sequence for the UnitsInStock column is very similar to that of Insert mode’s. It starts with System.Int32_Edit
and finds the built-in Integer_Edit
template. The Read-only mode is different, however. Because Dynamic Data doesn’t provide a built-in read-only template for integer columns, the FieldTemplateFactory
falls back from Integer
to String
data type and eventually finds Text.ascx
, a read-only template that can display field values of all types.
Although the field template lookup algorithm might seem complex and require numerous file existence checks, in reality, Dynamic Data performs it only when a field template is needed for a particular column the first time. The results are stored in a hash table, and all subsequent lookups for this column are memory-based and very fast.
Dynamic Data web project templates in Visual Studio 2010 include a large number of ready-to-use field templates.
The Boolean.ascx
field template is used for Boolean columns in Read-only mode. It contains a disabled CheckBox
control, checked when the field value is true
.
The Boolean_Edit.ascx
field template is used for Boolean columns in Edit and Insert modes. It contains a CheckBox
control users can use to set field value to true
or false
. This field template does not support null values and, unlike other well-behaved field templates, does not perform validation.
The Children.ascx
field template is used in Read-only and Edit modes for navigation properties that represent the many side of one-to-many relationships between entities. For example, the Customer entity in the Northwind sample data model has a children navigation property called Orders
, which returns a collection of all orders placed by a given customer. In Read-only and Edit modes, the Children.ascx
entity template displays a hyperlink to the dynamic List.aspx
page that displays all child rows of the parent entity.
The Children_Insert.ascx
field template is empty. Because the dynamic List.aspx
page cannot display child rows of a parent entity that has not been submitted to the database, this template prevents the Children.ascx
field template from being used in Insert mode.
The DateTime.ascx
field template is used in Read-only mode to display DateTime
field values. This field template contains a Literal
control that injects the field value directly into the web page without any adorning HTML elements. This template is nearly identical to the Text.ascx
template described in the following sections and could be removed, making Text.ascx
responsible for displaying DateTime
field values in Read-only mode as well.
The DateTime_Edit.ascx
field template is used for DateTime columns in Edit and Insert mode. It contains a TextBox
control, accompanied by a CustomValidator
, used to make sure that the value entered in the TextBox
is a valid DateTime
. It also contains a RequiredFieldValidator
, used to enforce values for columns marked with the RequiredAttribute
, a RegularExpressionValidator
, used to enforce regular expression patterns for columns marked with the RegularExpressionAttribute
and a DynamicValidator
, used to enforce all other validation attributes applied to this column.
The Decimal_Edit.ascx
field template is used for Decimal
, Float
, and Double
columns in Edit and Insert mode. It contains a TextBox
control, accompanied by a CompareValidator
, used to ensure that values entered in the TextBox
are valid numbers. It also contains a RequiredFieldValidator
, used to enforce values for columns marked with the RequiredAttribute
; a RangeValidator
, used to enforce range of values for columns marked with the RangeAttribute
; a RegularExpressionValidator
, used to enforce regular expression patterns for columns marked with the RegularExpressionAttribute
; and a DynamicValidator
, used to enforce all other validation attributes applied to this column.
The EmailAddress.ascx
field template is a read-only template used for String
columns marked with the DataType(DataType.EmailAddress)
attribute. It includes a HyperLink
control that displays the field value as a link with the URL scheme "mailto:"
. When the user clicks this link, most web browsers launch the default e-mail client installed on the computer and create a new message with the field value automatically added in the “To” list.
The Enumeration.ascx
field template is used for enumeration columns in Read-only mode. It contains a Literal
control that displays the underlying integral field value converted to its equivalent enumerated value and then converted to a String
.
Enumeration columns represent properties with an actual enumerated .NET data type (supported only by LINQ to SQL today) or integer properties that have been marked with the EnumDataTypeAttribute
(supported by both LINQ to SQL and Entity Framework). As an example, consider the following enumerated type that could be used to track status of Orders in the Northwind database:
public enum OrderStatus: byte
{
Draft = 0,
Submitted = 1,
PaymentProcessed = 2,
Fulfilled = 3
}
In the sample data model, it could be expressed as an integer property called OrderStatus
in the Order entity:
[EnumDataType(typeof(OrderStatus))]
public byte OrderStatus { get; set; }
By marking the OrderStatus
property with the EnumDataTypeAttribute
, you can make Dynamic Data treat it as enumeration instead of an integer and use the Enumeration.ascx field template, which displays OrderStatus
field values as “Draft
” and “Submitted
,” instead of “1
” and “2
.”
The Enumeration_Edit.ascx
field template is used for enumeration columns in Edit and Insert modes. It contains a DropDownList
control, populated with enumerated items converted to string, plus “[Not Set]” if the column allows null values. This field template also includes a RequiredFieldValidator
to enforce values for columns marked with the RequiredAttribute
and a DynamicValidator
to enforce all other validation attributes applied to this column.
When converting enumeration values to string, the Enumeration field templates do not automatically separate words concatenated to form a valid programmatic identifier. For example, an enumerated value ReadOnly
would be normally displayed as “read-only” in a hand-coded web page. To make display names of the enumeration values more user-friendly, you can modify the Enumeration field templates to implement appropriate logic based either on the naming convention used to define the enumerated values, commonly PascalCase in .NET programs. Alternatively, you can use metadata attributes, such as DisplayAttribute
, that can be applied to the enumerated values to specify the exact display string. Chapter 11, “Building Dynamic Forms,” shows how to implement this.
The ForeignKey.ascx
is used for foreign key columns in Read-only mode. It contains a HyperLink
control that displays the field value of the “display” column of the parent table. You can specify the display column explicitly by applying the DisplayColumnAttribute
to the parent entity class, or it can be selected automatically based on the Dynamic Data heuristics. Users can click the link generated by the control to navigate to the List.aspx
page of the parent table.
The ForeignKey_Edit.ascx
field template is used for foreign key columns in Edit and Insert modes. It contains a DropDownList
control that displays all rows from the parent table. The display column of the parent table determines the Text
of each ListItem
, and its primary key columns determine its Value
.
The Integer_Edit.ascx
field template is used for Byte
, Int16
, Int32
, and Int64
columns in Edit and Insert modes. It contains a TextBox
control, accompanied by a CompareValidator
, which is used to ensure that values entered in the TextBox
are valid numbers. It also contains a RequiredFieldValidator
, used to enforce values for columns marked with the RequiredAttribute;
a RangeValidator
, used to enforce range of values for columns marked with the RangeAttribute
; a RegularExpressionValidator
, used to enforce regular expression patterns for columns marked with the RegularExpressionAttribute
; and a DynamicValidator
, used to enforce all other validation attributes applied to the column.
The ManyToMany.ascx
field template is used for children columns in a many-to-many relationship, such as the Employees column of the Territory entity in the Northwind sample database. This template contains a Repeater
control that generates a DynamicHyperLink
for every child entity. On the actual page, this looks like a list of hyperlinks that the user can click to navigate to the Details.aspx
page of a particular child entity.
Many-to-many relationships are natively supported in Entity Framework but not in LINQ to SQL. Therefore, only the Entity Framework flavors of the Dynamic Data project templates in Visual Studio include the ManyToMany
field template. Unlike other field templates, which work equally well with both data access frameworks, the ManyToMany
field template uses Entity Framework APIs directly.
LINQ to SQL natively supports only one-to-many relationships and requires you to have an explicit many-to-many entity in the model. However, it is possible to mimic many-to-many relationships by implementing a simple coding pattern as Eric Smith and Shannon Davidson describe in the article published on the Code Project, which can be found at the following location online:
http://www.codeproject.com/KB/linq/linq-to-sql-many-to-many.aspx
It is possible to create Dynamic Data many-to-many field templates for LINQ to SQL based on this approach. However, this exercise is outside the scope of this chapter.
The ManyToMany_Edit.ascx
field template is used for many-to-many children columns in Edit and Insert modes. This template contains a CheckBoxList
control that generates a check box for every entity the user can check to add it to the list of children.
The MultilineText_Edit.ascx
field template is used for long string columns in Edit and Insert modes. It contains a TextBox
control with TextMode
property set to MultiLine
. It also contains a RequiredFieldValidator
, used to enforce values for columns marked with the RequiredAttribute
, a RegularExpressionValidator
, used to enforce regular expression patterns for columns marked with the RegularExpressionAttribute
and a DynamicValidator
, used to enforce all other validation attributes applied to the column.
String columns are considered long when they do not have maximum length specified in the data model, such as the Notes column of the Employee entity in the Northwind sample, which has type NTEXT
. You can also force Dynamic Data to use the MultilineText_Edit
field template for columns that do have maximum length by applying the DataType(DataType.MultilineText)
attribute to their properties in the data model.
Due to the data type fallback used during template lookup, the Text.ascx
field template can be used in Read-only mode for columns of all primitive types with the exception of Boolean and DateTime columns, for which Dynamic Data provides specific templates. This field template includes a Literal
control that displays the field value converted to a string.
The Text_Edit.ascx
field template is used for string columns in Edit and Insert modes. It includes a TextBox
control, accompanied by a RequiredFieldValidator
, used to enforce values for columns marked with the RequiredAttribute
; a RegularExpressionValidator
, used to enforce regular expression patterns for columns marked with the RegularExpressionAttribute
; and a DynamicValidator
, used to enforce all other validation attributes applied to the column.
The Url.ascx
field template is used for columns marked with the DataType(DataType.Url)
attribute. It contains a HyperLink
control that displays the field value as a link in the generated HTML. The "http://"
scheme is automatically added to the URL unless the field value already contains it. Only HTTP and HTTPS schemes are recognized automatically. Although this field template does not require the column type to be only String
, the field value converted to string must be a valid URL for the generated hyperlink to work correctly.
A field template is a special type of ASP.NET user control that inherits from the FieldTemplateUserControl
base class provided by Dynamic Data. Multiple columns can reuse the same field template based on its compatibility with the column type and control mode. Changing a single field template automatically affects the user interface generated for all matching columns in the data model. Dynamic Data project template comes with a reasonable number of field templates that allow developers to create working web applications for many data types out of the box. Developers can easily modify the default field templates as well as add new ones to meet specific requirements of their projects.
Dynamic Data supports separate templates for Read-only, Edit, and Insert modes. Although a single template can support all three modes and programmatically display different versions of child controls, separating this logic into different templates helps to keep them simple and lightweight. Dynamic Data uses common-sense logic to find the best matching field template based on the column type and required control mode. If the most specific template does not exist, Dynamic Data gradually relaxes the matching rules and tries to find templates for a more generic column type and control mode.
3.138.123.106