AdventureWorksDM.dbml. If you had chosen to create an Entities Dynamic Data project earlier you
would select the ADO.NET Entity Data Model instead.
After you click Add, the new item will open in the Object Relational Designer. Double-click the
AdventureWorksLT database file you added earlier to open it in the Server Explorer and then
expand the Tables node. Select all tables, except the first two, and drag them onto the Designer.
This populates the LINQ to SQL data model as shown in Figure 23-2.
fiGure 23-2
Finally, you’ll need to register your data model with Dynamic Data and enable scaffolding.
Open the Global.asax.cs (or Global.asax.vb if you are using Visual Basic) and locate
the following line of code. Uncomment this line and change the YourDataContextType to
AdventureWorksDMDataContext. Lastly, change the ScaffoldAllTables property to true.
c#
DefaultModel.RegisterContext(typeof(AdventureWorksDMDataContext),
new ContextConfiguration()
{ ScaffoldAllTables = true });
Code snippet Global.asax.cs
Creating a Dynamic Data Web application .
489
Vb
DefaultModel.RegisterContext(GetType(AdventureWorksDMDataContext), _
New ContextConfiguration() _
With {.ScaffoldAllTables = True})
Code snippet Global.asax.vb
That is all you need to do to get a data-driven web application with full CRUD support up
and running.
exploring a dynamic data application
When you run the application, it opens with the home page, Default.aspx, which displays a list
of hyperlinks for all the tables you added to the data model (see Figure 23-3). Note that the names
listed on this page are pluralized versions of the table name.
fiGure 23-3
When you click one of these links, you are taken to the List.aspx page, shown in Figure 23-4,
for the selected table. This page, along with the Details.aspx page for an individual record,
represents the “Read” function of your CRUD application and includes support for paging and
filtering of the records by foreign key. This page also displays links to view details, edit, or delete a
record. Any foreign keys are displayed as links to a details page for that foreign key record.
490 .
chaPter 23 dynAmic dATA
fiGure 23-4
You may notice that some database fields are missing from the web page, such
as ProductID and ThumbNailPhoto. By default, Dynamic Data will not
scaffold Identity columns, binary columns, or computed columns. This can be
overridden, as you will find out later in this chapter.
The “Update” CRUD function is accessed by clicking the Edit link against a record. This displays
the Edit.aspx page, as shown in Figure 23-5. You will notice that the textboxes are different
widths — this is determined based on the length of the database field. This page also includes a
number of ASP.NET validation controls, based on database field information. For example, the
ProductNumber field has a RequiredFieldValidator because the underlying database field is
not nullable. Likewise, the Weight field uses a CompareValidator to ensure that the value
entered is a decimal.
Foreign keys are also handled by drop-down selectors. For example, in Figure 23-5 the
ProductCategory and ProductModel fields are foreign keys. Tables that use the selected table as
a foreign key are displayed as hyperlinks. This can be seen in the SalesOrderDetails field in
Figure 23 - 5.
Customizing the Data Model .
491
fiGure 23-5
custoMizinG the data Model
While scaffolding an entire database makes for an impressive demo, it is unlikely that you would
actually want to expose every table and field in your database to end users. Fortunately, Dynamic
Data has been designed to handle this scenario, and many others, by customizing the data model.
scaffolding individual tables
Before you begin customizing the data model you should disable automatic scaffolding of all tables.
Open the Global.asax.cs file and change the ScaffoldAllTables property to false.
The next step is to selectively enable scaffolding for individual tables. Begin by adding a new class
file to the project called Product.cs. This class must be a partial class, because Product is already
defined in the LINQ to SQL data model. To enable scaffolding for the Product table, decorate
the class with the ScaffoldTable attribute. Once completed, the class should look similar to the
following code:
492 .
chaPter 23 dynAmic dATA
c#
using System.ComponentModel.DataAnnotations;
namespace DynDataWebApp
{
[ScaffoldTable(true)]
public partial class Product
{
}
}
Code snippet Product.cs
Vb
Imports System.ComponentModel.DataAnnotations
< ScaffoldTable(True) > _
Partial Public Class Product
End Class
Code snippet Product.vb
If you run the application now only the Product table will be listed and editable.
You can achieve the same result by leaving the ScaffoldAllTables property to
true and selectively hiding tables by decorating their corresponding classes with
the ScaffoldTable attribute set to false.
customizing individual data fields
In many cases you will want certain fields in a table to be either read only or hidden. This is
particularly useful if the table contains sensitive data such as credit card information.
For example, when you edit a record in the Products table, it displays a link to the SalesOrderDetails
table. This link is disabled because the SalesOrderDetails table has not been enabled for scaffolding.
Therefore displaying this field provides the user with no useful information. Also the ModifiedDate
field, while useful for end users to know, is not something that you would typically want them to
edit directly. Therefore it would be better to display this field as read only and allow the database
to modify it with an Update trigger.
These requirements are supported by Dynamic Data by adding a metadata class to your data model
class. In the Product.cs file add a new class to the bottom of the file called ProductMetadata.
This class can be associated by applying the MetadataType attribute to the Product class.
Customizing the Data Model .
493
In the ProductMetadata class, create public fields with the same name as each data field that you
want to customize. Because Dynamic Data will read the type of this field from the data model class
rather than the metadata class, you can use object as the type for these fields.
Add the ScaffoldColumn attribute to the SalesOrderDetails field and set it to false to hide the
field. To make the ModifiedDate field read only, decorate it with an Editable attribute that is set
to false.
The following code shows these changes:
c#
namespace DynDataWebApp
{
[ScaffoldTable(true)]
[MetadataType(typeof(ProductMetadata))]
public partial class Product
{
}
public class ProductMetadata
{
[ScaffoldColumn(false)]
public object SalesOrderDetails;
[Editable(false)]
public object ModifiedDate;
}
}
Code snippet Product.cs
Vb
<ScaffoldTable(True)> _
<MetadataType(GetType(ProductMetadata))> _
Partial Public Class Product
End Class
Public Class ProductMetadata
<ScaffoldColumn(False)> _
Public SalesOrderDetails As Object
<Editable(False)> _
Public ModifiedDate As Object
End Class
Code snippet Product.vb
Figure 23-6 shows the results of these changes in action. On the left is the original edit screen for
the Product table. On the right is the new edit screen after the data model has been customized.
494 .
chaPter 23 dynAmic dATA
fiGure 23-6
adding custom Validation rules
As mentioned earlier in this chapter, Dynamic Data includes some built-in support for validation
rules that are inferred from the underlying database schema. For example, if a field in a database
table is marked as not nullable, a RequiredFieldValidator will be added to the Update page.
However, in some cases there are business rules about the format of data that isn’t supported by the
built-in validation rules. For example, in the Product table, the values saved in the ProductNumber
field all follow a specific format that begins with two uppercase letters followed by a hyphen.
This format can be enforced by decorating the ProductNumber field with a RegularExpression
attribute, as shown in the following code:
c#
[ScaffoldTable(true)]
[MetadataType(typeof(ProductMetadata))]
public partial class Product
{
}
public class ProductMetadata
Customizing the Data Model .
495
{
[RegularExpression(“^[A-Z]{2}-[A-Z0-9]{4}(-[A-Z0-9]{1,2})?$”,
ErrorMessage=”Product Number must be a valid format”)]
public object ProductNumber;
}
Code snippet Product.cs
Vb
<ScaffoldTable(True)> _
<MetadataType(GetType(ProductMetadata))> _
Partial Public Class Product
End Class
Public Class ProductMetadata
<RegularExpression(“^[A-Z]{2}-[A-Z0-9]{4}(-[A-Z0-9]{1,2})?$”, _
ErrorMessage:=”Product Number must be a valid format”)> _
Public ProductNumber As Object
End Class
Code snippet Product.vb
There is also a Range attribute, which is useful for specifying the minimum and maximum allowed
values for a numeric field. Finally, you can apply the Required or StringLength attributes if
you want to enforce these constraints on a field in the data model without specifying them in the
underlying database.
Although useful, the attribute-based validations don’t support all scenarios. For example, a user
could attempt to enter a date for the Product SellEndDate that is earlier than the SellStartDate
value. Due to a database constraint on this field, this would result in a runtime exception rather
than a validation error, which is presented to the user.
For each field that is in the data model, LINQ to SQL defines two methods that are called during an
edit — the OnFieldNameChanging method, which is called just before the field is changed, and the
OnFieldNameChanged method, which is called just after. To handle complex validation rules, you
can complete the appropriate partial method declaration in the data model.
The following code shows a validation rule that ensures a value entered for the Product SellEndDate
field is not earlier than the SellStartDate:
c#
[ScaffoldTable(true)]
[MetadataType(typeof(ProductMetadata))]
public partial class Product
{
partial void OnSellEndDateChanging(DateTime? value)
{
if (value.HasValue && value.Value < this._SellStartDate)
496 .
chaPter 23 dynAmic dATA
{
throw new ValidationException(
“Sell End Date must be later than Sell Start Date”);
}
}
}
Code snippet Product.cs
Vb
<ScaffoldTable(True)> _
<MetadataType(GetType(ProductMetadata))> _
Partial Public Class Product
Private Sub OnSellEndDateChanging(ByVal value As Nullable(Of DateTime))
If value.HasValue AndAlso value.Value < Me._SellStartDate Then
Throw New ValidationException( _
“Sell End Date must be later than Sell Start Date”)
End If
End Sub
End Class
Code snippet Product.vb
Figure 23-7 shows how this custom validation
rule is enforced by Dynamic Data.
customizing the display format
The default way that some of the data types
are formatted is less than ideal. For example,
the Product StandardCost and ListPrice fields,
which use the SQL money data type, are
displayed as numbers to four decimal places.
Also, the Product SellStartDate and SellEndDate
fields, which have a SQL datetime data type,
are formatted showing both the date and time,
even though the time portion is not really useful
information.
The display format of these fields can be
customized in two ways: globally for a specific
data type by customizing the field template; or
on an individual field basis by customizing the
data model. Field template customization is
discussed later in this chapter.
First, to specify how the fields will be formatted in the user interface, decorate the corresponding
property in the data model with the DisplayFormat attribute. This attribute has a DataFormatString
fiGure 23-7
Customizing the Data Model .
497
property that accepts a .NET format string. The attribute also includes a number of additional parameters
to control rendering including the HtmlEncode parameter, which indicates whether the field should
be HTML encoded, and the NullDisplayText attribute, which sets the text to be displayed when the
field ’s value is null. The following code shows how the DisplayFormat attribute can be applied:
c#
[DisplayFormat(DataFormatString="{0:C}")]
public object ListPrice;
[DisplayFormat(DataFormatString="{0:MMM d, yyyy}",
NullDisplayText="Not Specified")]
public object SellEndDate;
Code snippet Product.cs
Vb
< Display(Name:="List Price") > _
< DisplayFormat(DataFormatString:="{0:C}") > _
Public ListPrice As Object
< Display(Name:="Sell End Date") > _
< DisplayFormat(DataFormatString:="{0:MMM d, yyyy}",
NullDisplayText:="Not Specified") > _
Public SellEndDate As Object
Code snippet Product.vb
By default, the display format will only be applied to the Read view. To apply
this formatting to the Edit view set the ApplyFormatInEditMode property to
true on the DisplayFormat attribute.
Second, it’s unlikely that you’ll want to use the database field names in the user interface. It
would be much better to provide descriptive names for all of your fields. You can use the Display
attribute to control how the field labels are rendered. This attribute accepts a number of parameters,
including Name, to specify the actual label and Order to control the order in which fields should be
listed. In the following code, the ProductNumber field has been given a display name of “Product