Working with Data Source Controls and Data-Bound Controls in ASP.NET
- 12/10/2010
- Before You Begin
- Lesson 1: Connecting to Data with Data Source Controls
- Lesson 2: Working with Data-Bound Web Server Controls
- Lesson 3: Working with ASP.NET Dynamic Data
- Case Scenarios
- Suggested Practices
- Take a Practice Test
Lesson 3: Working with ASP.NET Dynamic Data
ASP.NET Dynamic Data is a set of user interface templates and controls for creating data-driven websites. You attach a data context to a Dynamic Data MetaModel object and can then work with that data to read, filter, insert, update, and delete. Dynamic Data does not generate code specific to a particular data schema. Rather, it is a series of extensible templates that can work with any LINQ to SQL, Entity Framework, or custom data context. If you need to customize a template to change the way data is displayed or behaves, you can do so by following the conventions of ASP.NET Dynamic Data.
This lesson helps you get started with using ASP.NET Dynamic Data to either create a new website or add these capabilities to an existing one. The lesson covers creating the scaffolding for using Dynamic Data and customizing the technology, including creating custom routes, fields, pages, and business logic.
Getting Started with Dynamic Data Websites
ASP.NET Dynamic Data works at run time to extract information from your data context and apply that information to template pages for handling CRUD operations against your data source. Recall that a data context is created by using LINQ to SQL or LINQ to Entities (see Chapter 11 for more details).
Dynamic Data uses the information from your model, such as table relationships and field data types, to create meaningful pages. For example, if you are showing data in a list, Dynamic Data will use foreign-key relationships to allow you to filter that data (into product categories, for example). It will also allow you to link to related data from a specified data record (such as customer orders). In addition, fields will be shown based on their underlying data type, and when users update or insert data, validation rules will be enforced based on the model.
To start creating a new website with Dynamic Data, you typically follow these steps:
Create a scaffold site that includes the default presentation layer templates used by ASP.NET Dynamic Data.
Define a data context that represents your data source and model by using either LINQ to SQL, LINQ to Entities, or a custom model.
Register your data context with the website’s MetaModel instance inside the Global.asax file.
Customize the URL routing, page and field templates, business logic, and validation in the website. (This step is optional.)
You will walk through each of these steps in the upcoming sections.
Creating a Dynamic Data Website
You can create a new ASP.NET Dynamic Data website or add Dynamic Data features to an existing site. This section covers the former; you will see how to add these features to existing sites later in the lesson.
Visual Studio ships with two ASP.NET Dynamic Data project templates: ASP.NET Dynamic Data Entities Web Site and ASP.NET Dynamic Data LINQ to SQL Web Site. The first is used to create sites that use the Entity Framework as the data context; the second is for sites that connect to a LINQ to SQL data context. You must choose between the two, because the templates only support a single data context in a site (as it is defined within the application’s Global.asax file).
Figure 12-22 shows the structure of a newly created Dynamic Data website. Note that you do not select a data source when you create the website. Instead, you can connect to a data source as a later step. The website only contains templates that use your data context information at run time. The next section covers this website structure in detail.
Figure 12-22 The structure of an ASP.NET Dynamic Data website.
Dynamic Data Website Structure
The Dynamic Data website contains the DynamicData folder and several files in the root of the site. You will look at the contents of the DynamicData folder soon. First, the files in the root of the site are listed here, along with a brief description of each:
Default.aspx This file is used to display all tables in the data context. It includes a GridView control that is bound to the MetaModel.VisibleTables collection. The MetaModel is exposed through an application variable (from the Global.asax file) called DefaultModel. It is referenced as follows:
ASP.global_asax.DefaultModel.VisibleTables.
Global.asax This file contains code that is run for application events, including Application_Start. Dynamic Data uses this file to call RegisterRoutes at Application_Start. This method connects your data context to a meta-model and uses ASP.NET routing to handle page requests for tables and actions (such as List, Details, Edit, and Insert).
Site.css This file represents a style sheet used by the master page and related page templates. You can customize this style sheet to change the look of your Dynamic Data site.
Site.master This is the master page for the Dynamic Data site. By default, it defines navigation for the site, a ScriptManager control, and the main ContentPlaceHolder control. The Site.master page is used by the page templates.
Web.config This is a less verbose version of the Web.config file that is created for standard ASP.NET websites.
The DynamicData folder contains several subfolders and another Web.config file. The Web.config file registers an HTTP handler for System.Web.HttpNotFoundHandler. The folders and their naming convention define a pattern that is used by Dynamic Data to route requests accordingly. Each folder is listed here, along with a brief description of its contents:
Content This folder contains content used by Dynamic Data, including images for the pager control and a custom GridViewPager user control.
CustomPages By default, this folder is empty. You use this folder if you need to create custom templates for displaying or editing data. The convention is to create a new folder under CustomPages with the name of the item in your data model that you want to customize (such as Customers). You then typically copy a page template (from the PageTemplates directory) to this new folder, customize it, and save it. The Dynamic Data routing will look in this CustomPages folder first for items that are named with this convention (and thus use those instead of the default templates).
EntityTemplates This folder contains user controls that are used by the page templates for displaying and editing data. These user controls include Default.ascx, Default_Edit.ascx, and Default_Insert.ascx.
FieldTemplates This folder contains a set of user controls (ASCX files) that define the UI for displaying table fields in either a view or edit mode. There are user controls based on the type of data being worked with, including Boolean, DateTime, Text, Url, Decimal, Email, and more. These fields are rendered based on the data type in the data context or on additional attributes that you add to extend the meta-model. You can also create custom field templates to display or edit data with other controls (including third-party controls).
Filters This folder contains user controls to define the UI that is used to filter data that is displayed in a list. These controls include Boolean.ascx, Enumeration.ascx, and ForeignKey.ascx.
PageTemplates This folder contains the default templates for working with data. These template pages can show a table in a list (List.aspx), allow you to create a new record (Insert.aspx), edit an existing record (Edit.aspx), view the details of a record (Details.aspx), and show a list of data and a selected record’s details on the same page (ListDetails.aspx).
Defining a Data Context
You create a data context by adding either a LINQ to SQL Classes (DBML) file or an ADO.NET Entity Data Model (EDMX) file to your project (see Chapter 11 for more details). You can also create a custom model through code.
As an example, suppose that the Northwind database is in the App_Data directory of your site. You can then create a LINQ to SQL DBML file based on tables found inside this database. Figure 12-23 shows an example.
Figure 12-23 An example LINQ to SQL DBML model.
Registering Your Data Context with the MetaModel
The next step is to register your data context with System.Web.DynamicData.MetaModel. The MetaModel defines the connectivity or mapping between the scaffold templates and your data layer.
You register your data context inside the Global.asax file. Here you will notice that, at the top of the file, the MetaModel is actually defined as a private application variable and exposed as a read-only property. The following code shows an example.
Sample of Visual Basic Code
Private Shared s_defaultModel As New MetaModel Public Shared ReadOnly Property DefaultModel() As MetaModel Get Return s_defaultModel End Get End Property
Sample of C# Code
private static MetaModel s_defaultModel = new MetaModel(); public static MetaModel DefaultModel { get { return s_defaultModel; } }
Further down in the file, notice that the RegisterRoutes method is called at application start. The following code shows an example.
Sample of Visual Basic Code
Private Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs) RegisterRoutes(RouteTable.Routes) End Sub
Sample of C# Code
void Application_Start(object sender, EventArgs e) { RegisterRoutes(RouteTable.Routes); }
The RegisterRoutes method is used to connect your data source to the MetaModel. You do so through the DefaultModel.RegisterContext method and pass the type name of your data context. You might also want to set the ScaffoldAllTables property to true to make sure that the site will return all tables in the data context by default. The following code shows an example of registering the NorthwindDataContext LINQ to SQL object with the MetaModel.
Sample of Visual Basic Code
Public Shared Sub RegisterRoutes(ByVal routes As RouteCollection) DefaultModel.RegisterContext( _ GetType(NorthwindDataContext), New ContextConfiguration() _ With {.ScaffoldAllTables = True}) routes.Add(New DynamicDataRoute("{table}/{action}.aspx") With { .Constraints = New RouteValueDictionary(New _ With {.Action = "List|Details|Edit|Insert"}), .Model = DefaultModel}) End Sub
Sample of C# Code
public static void RegisterRoutes(RouteCollection routes) { DefaultModel.RegisterContext(typeof(NorthwindDataContext), new ContextConfiguration() { ScaffoldAllTables = true }); routes.Add(new DynamicDataRoute("{table}/{action}.aspx") { Constraints = new RouteValueDictionary( new { action = "List|Details|Edit|Insert" }), Model = DefaultModel }); }
That is all that is required to create a basic Dynamic Data website. You can now run the site and view, edit, insert, and delete data by using the template user interface. Figure 12-24 shows an example of the Products table in a web browser. Notice the URL, Products/List.aspx. This table/action convention is defined in the routing. It simply indicates that the List.aspx template page should be called and the Product data should be displayed from the model.
Figure 12-24 An example LINQ to SQL DBML model.
Notice also the default filters created for this list. The Discontinued filter is based on a Boolean field in the data. The Category filter is based on the relationship between Product and Category. In fact, relational IDs won’t be displayed in the table, but related data will be. You can see that this was possible for the Category, because it exposed the CategoryName field. However, this was not possible for SupplierID because this table does not include a SupplierName field.
You can also see that the form allows you to page the data; select how many rows to show in a page; edit, delete, and view the details of an item; insert a new item; and connect to data related to a particular row (View Order_Details). Figure 12-25 shows an example of inserting a product. Notice that here you again get the Category drop-down list. You can also see that default data validation is being used. The validation is based on stored data types.
Figure 12-25 Automatic data validation in insert mode.
This site behavior is the default behavior with the template scaffolding. You can see that there was no code or customization done to this site. In upcoming sections, you will see how to customize the routing, create new page and field templates, and add custom business logic to validate data.
The Dynamic Data Classes
ASP.NET Dynamic Data relies on several classes inside the System.Web.DynamicData namespace. These classes include controls for defining custom field user controls, managing the routing, and using Dynamic Data inside a webpage. The MetaModel classes are found in the System.Data.Linq.Mapping namespace. Figure 12-26 shows some of the key classes used by Dynamic Data. Many of these classes will be discussed in upcoming sections.
Figure 12-26 Some of the key classes related to ASP.NET Dynamic Data.
Extending and Customizing Dynamic Data
ASP.NET Dynamic Data is fully extensible. You can customize how fields are edited, how data is displayed, the look of the pages, the URL routing, data validation, and more. You can create customizations to existing templates that will be applied to all data elements. You can also create new, entity-specific templates for customizations that are specific to a particular entity. This section walks you through common customizations you might want to make when using ASP.NET Dynamic Data.
Defining Custom Routing
Dynamic Data uses the same routing features from System.Web.Routing that are used by ASP.NET model-view-controller (MVC) (see Chapter 14, “Creating Websites with ASP.NET MVC 2”). This routing engine maps intelligent Uniform Resource Identifiers (URIs) that are defined based on user actions to actual pages in your site, which makes the URIs easier to understand for users and search engines.
For example, Dynamic Data exposes the List.aspx, Edit.aspx, Insert.aspx, and Details.aspx page templates for working with data. These pages are called by the routing engine based on the user’s action: List, Edit, Details, or Insert. A DynamicDataRoute class is created and added to the RouteTable.Routes collection inside the Global.asax file. This DynamicDataRoute indicates that a URI should be mapped as table/action.aspx. The following shows the code from the Global.asax file that does this.
Sample of Visual Basic Code
routes.Add(New DynamicDataRoute("{table}/{action}.aspx") With { .Constraints = New RouteValueDictionary( _ New With {.Action = "List|Details|Edit|Insert"}), .Model = DefaultModel})
Sample of C# Code
routes.Add(new DynamicDataRoute("{table}/{action}.aspx") { Constraints = new RouteValueDictionary( new { action = "List|Details|Edit|Insert" }), Model = DefaultModel
This code ensures that a call to http://mysite/products/Edit.aspx?ProductID=1 will be routed to the /DynamicData/PageTemplates/Edit.aspx page. When accessed, the page will know the right table to edit (Products) and right record to edit (ProductID=1).
Editing the URI Structure
You can customize this routing by editing this code. For example, you might want to change the structure of the URL or even add a custom action. You can switch the table and action in the route definition to read {action}/{table}.aspx and get the same results but a different URL. New requests will be made as http://mysite/Edit/Products.aspx?ProductID=1. In this case, the table is used for the ASPX page name, and the action precedes the table name.
Editing the Route Table to Show a List and Details on the Same Page
The Dynamic Data page templates include the ListDetails.aspx file. This page supports viewing a table in a list and selecting an item from the list to view its details. You can also edit the item or delete it, and you can insert a new item into the list by using this page. However, by default, requests are not routed to this page. You can change this behavior by removing the existing routing and adding entries for the action’s List and Details that both point to the ListDetails.aspx page. The following code shows an example.
Sample of Visual Basic Code
routes.Add(New DynamicDataRoute("{table}/ListDetails.aspx") With { .Action = PageAction.List, .ViewName = "ListDetails", .Model = DefaultModel}) routes.Add(New DynamicDataRoute("{table}/ListDetails.aspx") With { .Action = PageAction.Details, .ViewName = "ListDetails", .Model = DefaultModel})
Sample of C# Code
routes.Add(new DynamicDataRoute("{table}/ListDetails.aspx") { Action = PageAction.List, ViewName = "ListDetails", Model = DefaultModel }); routes.Add(new DynamicDataRoute("{table}/ListDetails.aspx") { Action = PageAction.Details, ViewName = "ListDetails", Model = DefaultModel });
The result is that all calls to view a record’s details or show a table in a list will be routed to the ListDetails.aspx page. From there, users can do everything they need to with the data. Note that ListDetails.aspx also handles inline editing in the GridView control.
Creating a Custom Route to a Specific Page
ASP.NET Dynamic Data can already route to custom pages you create. You simply need to follow the conventions already set up. You do not need to add custom routes. Instead, you create a new folder in the CustomPages folder and name this new folder with the name of an entity from your model. You then place appropriate action pages inside this folder (List.aspx, Edit.aspx, Insert.aspx, and so on). The default routing will look for pages in CustomPage first. You will walk through an example of creating a custom template page in an upcoming section.
Adding Metadata to Your Data Context
The classes in your data context are partial classes that include partial methods. This allows you to add to these classes and their methods outside the generated code. Doing so ensures that your changes are not overwritten if the model’s code gets regenerated when the database changes.
You create partial classes so that you can add metadata that defines how your fields are to be displayed and validated (see “Creating Custom Field Templates”, later in this lesson). You can also add custom business logic inside a partial method (see “Adding Custom Validation”, also later in this lesson).
To start, you simply add a class to the App_Code directory and give it the same name as the class in your data context. Of course, you mark this class as partial. You then create a related metadata class (typically added to the same file). You can name this class by using the EntityMetadata convention.
Inside this metadata class, you redefine the properties contained in your data context class (but as simple object types, because the underlying type already has strongly typed properties). You then mark your partial class with the MetadataType attribute from the System.ComponentModel.DataAnnotations namespace. This attribute should pass the metadata class type as a parameter.
You can then add attributes to your metadata class to change how your fields are rendered by Dynamic Data. As an example, if you want to exclude a field from display, you can add the ScaffoldColumn attribute and pass in false. Additionally, if you want to format a value for display, you can use the DisplayFormat attribute. This attribute allows you to indicate whether to only format the value when it is displayed or also when it is being edited. You can also use the Display attribute to change the name of a column.
The following shows an example from the Products entity partial class and metadata class. Notice the use of ScaffoldColumn, Display, and DisplayFormat.
Sample of Visual Basic Code
Imports Microsoft.VisualBasic Imports System.ComponentModel.DataAnnotations <MetadataType(GetType(ProductMetadata))> _ Partial Public Class Product End Class Public Class ProductMetadata Public Property ProductID As Object Public Property ProductName As Object Public Property SupplierID As Object Public Property CategoryID As Object <ScaffoldColumn(False)> _ Public Property QuantityPerUnit As Object <DisplayFormat(ApplyFormatInEditMode:=False, DataFormatString:="{0:c}")> _ <Display(Name:="Price")> _ Public Property UnitPrice As Object <ScaffoldColumn(False)> _ Public Property UnitsInStock As Object <ScaffoldColumn(False)> _ Public Property UnitsOnOrder As Object <ScaffoldColumn(False)> _ Public Property ReorderLevel As Object Public Property Discontinued As Object End Class
Sample of C# Code
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.ComponentModel.DataAnnotations; [MetadataType(typeof(ProductMetadata))] public partial class Product { } public class ProductMetadata { public object ProductID { get; set; } public object ProductName { get; set; } public object SupplierID { get; set; } public object CategoryID { get; set; } [ScaffoldColumn(false)] public object QuantityPerUnit { get; set; } [DisplayFormat(ApplyFormatInEditMode=false, DataFormatString="{0:c}")] [Display(Name = "Price")] public object UnitPrice { get; set; } [ScaffoldColumn(false)] public object UnitsInStock { get; set; } [ScaffoldColumn(false)] public object UnitsOnOrder { get; set; } [ScaffoldColumn(false)] public object ReorderLevel { get; set; } public object Discontinued { get; set; } }
When you run the application, this metadata is added to the Product class. Dynamic Data then picks up on this metadata and displays your fields accordingly. Figure 12-27 shows the results in a browser. Notice the missing fields, formatted UnitPrice column, and changed name.
Figure 12-27 Using metadata and partial classes to indicate field display options.
There are many additional annotations you can add to your metadata from the System.ComponentModel.DataAnnotations namespace. You will see some of these in the upcoming sections. Table 12-1 provides a partial list of these classes (note that all are attribute classes).
Table 12-1 Commonly Used Metadata Annotation Classes
CLASS |
DESCRIPTION |
Association |
Used to mark a property as a data relationship, such as a foreign key. |
CustomValidation |
Used to indicate a custom validation method to use for validating a property. |
DataType |
Used to indicate the data type to associate with the field. |
Display |
Allows you to indicate many things about the display of a field, including its order and its name. |
DisplayFormat |
Allows you to change how the data in a field is displayed. You can apply the formatting to affect only the view mode (and not the edit mode) if you want. |
Editable |
Used to indicate whether a property can be edited. |
EnumDataType |
Allows you to set an enum data type for a property. |
Key |
Allows you to set one or more properties as unique keys for a collection of objects. |
MetadataType |
Used to set the metadata class to associate with a type from your data context. |
Range |
Allows you to add a numeric range constraint to a property. You can also set an error message to be shown if the range is not valid. |
RegularExpression |
Used to add a regular expression constraint to a property. |
Required |
Used to indicate that a property is required when inserting or editing. |
ScaffoldColumn |
Indicates whether the column should be part of the scaffold (shown). |
ScaffoldTable |
Indicates whether an entire table should be part of the scaffold (shown). |
StringLength |
Allows you to set a constraint on a property based on the specified minimum and maximum number of characters for a property. |
UIHint |
Used to specify a custom field user control that should be used to display the property. |
Adding Custom Validation
You can see from Table 12-1 that you can use attributes to mark your metadata with specific constraints. These constraints will then be enforced by Dynamic Data, and ASP.NET validation controls will be rendered and invoked.
The validation attributes include Range, StringLength, Required, and RegularExpression (among others). You use these controls to enforce additional constraints on your data. For example, suppose you want to add the Range validator to the ReorderLevel property to indicate that users can only reorder stock in quantities of 1 to 144 units. You could do so with the following code.
Sample of Visual Basic Code
<Range(1, 144, ErrorMessage:="Quantity must be between 1 and 144")> _ Public Property ReorderLevel As Object
Sample of C# Code
[Range(1, 144, ErrorMessage = "Quantity must be between 1 and 144")] public object ReorderLevel { get; set; }
This business logic is then processed by Dynamic Data. You can see the results of a row edit in Figure 12-28. Here the user set the ReorderLevel to a value that is out of range (0). You can follow this same construct for other validation attributes, including StringLength and Required. Of course, you can apply more than one attribute to any property.
Figure 12-28 The Range constraint running in a browser window.
Custom Validation with Partial Methods
The data content model includes partial methods that allow you to add custom code that will be added to the method when the partial class is assembled. In fact, you can see these partial methods inside the Extensibility Method Definitions region of the generated code for a LINQ to SQL model. There are partial methods for OnChanging and OnChanged for each property in the data context. This means that the Product table has an OnUnitPriceChanging and OnUnitPriceChanged partial method. In fact, when you type “partial” into your partial class, Visual Studio’s IntelliSense provides you with a list of all partial methods that you can extend.
You can use these partial methods to write customized validation rules. Remember, your partial class is an instance of the actual class representing the entity. Therefore, you have access to all fields on the record. The OnChanging partial method is also passed the value to which the property is being changed. You can use these fields and this value to write custom business logic. If you encounter an error, you throw a ValidationException from System.ComponentModel.DataAnnotations. Dynamic Data will present the exception to the user as an ASP.NET validation error.
As an example, suppose you want to write custom business logic that processes when the Product.UnitPrice is being changed. This logic might prevent a user from lowering the price of an object if there are items still in stock and the product has not been discontinued. You would add this code to the Product partial class as follows.
Sample of Visual Basic Code
Private Sub OnUnitPriceChanging(ByVal value As System.Nullable(Of Decimal)) 'rule: can only lower the price if product is discontinued If (Me.UnitsInStock > 0 And Me.UnitPrice > value) _ And (Me.Discontinued = False) Then Throw New ValidationException( _ "Cannot lower the price of an item that is not discontinued.") End If End Sub
Sample of C# Code
partial void OnUnitPriceChanging(decimal? value) { //rule: can only lower the price if product is discontinued if ((this.UnitsInStock > 0 && this.UnitPrice > value) && (this.Discontinued == false)) { throw new ValidationException( "Cannot lower the price of an item that is not discontinued."); } }
Figure 12-29 shows an example of this logic processing. Notice that the validation message is shown as part of the validation summary controls in ASP.NET.
Figure 12-29 The custom business logic being processed in the browser.
Creating Custom Field Templates
Recall that the Dynamic Data templates include the FieldTemplates folder. Inside this folder is a set of user controls that inherit from FieldTemplateUserControl. These controls are used to render the appearance and functionality of your data context properties when they are displayed on a details or edit page (or page section).
Field template controls are named as either display controls or edit controls for a specific data type and functionality. For example, the controls used to render DateTime values are DateTime.ascx and DateTime_Edit.ascx. The former is simply a Literal control that is used when a DateTime value is rendered for display. The edit version includes a TextBox, a RequiredFieldValidator, a RegularExpressionValidator, a DynamicValidator, and a CustomValidator control. This composite user control is used whenever a DateTime value is displayed for editing.
You can edit the field template controls or create new ones in order to provide additional behavior in your website. Edits to an existing control will apply to all uses of that control within your website. When you create a new control, you use metadata to specify which properties in your data model use the new control. You will look at both examples next.
Customizing an Existing Field Template
You can customize an existing field template user control as you would any other user control. This means adding client-side script, changing the appearance, and modifying the processing logic.
For example, you could modify the code-behind file for each of the core edit controls (Text_Edit.ascx, DateTime_Edit.ascx, Decimal_Edit.ascx, Integer_Edit.ascx, and MultilineText_Edit.ascx) to change the background color of the control if the control represents a required field. To do so, you would add code to the OnDataBinding event. At this point, the SetupValidator methods have already run and any validation controls, including the RequiredFieldValidator, will already be set as either enabled or disabled. You could then add the following code to this event to set the background color accordingly.
Sample of Visual Basic Code
Protected Overrides Sub OnDataBinding(ByVal e As System.EventArgs) MyBase.OnDataBinding(e) If Me.RequiredFieldValidator1.Enabled = True Then TextBox1.BackColor = System.Drawing.Color.SandyBrown End If End Sub
Sample of C# Code
protected override void OnDataBinding(EventArgs e) { base.OnDataBinding(e); if (this.RequiredFieldValidator1.Enabled == true) TextBox1.BackColor = System.Drawing.Color.SandyBrown; }
You can then add the RequiredAttribute class to any properties in your metadata class that you want to be required. When the control is rendered, Dynamic Data will mark the edit versions for these properties with the color if they are required fields.
Creating a New Field Template Control
You can create your own custom field template user controls. You can do so by copying an existing user control in Dynamic Data/FieldTemplates or by selecting the Dynamic Data Field template from the Add New Item dialog box.
When you use the Dynamic Data Field item template, Visual Studio will actually generate two controls: one for display (ControlName.ascx) and one for editing (ControlName_Edit.ascx). The display version by default is just a Literal control. The edit version will include a TextBox control and the various validation controls used by DynamicData, some of which were shown in Table 12-1. You can then customize these controls to your needs.
As an example, suppose you want to create a new user control to allow users to use a Calendar control when picking a date in edit mode. To do so, you can add a new Dynamic Data Field to your project named CalendarPicker.ascx. Again, this will create two controls. You can leave the display version as is. You might then modify the markup in the CalendarPicker_Edit.ascx control to use a Calendar control, as follows.
<%@ Control Language="VB" CodeFile="CalendarPicker_Edit.ascx.vb" Inherits="DynamicData_FieldTemplates_CalendarPicker_EditField" %> <asp:Calendar ID="Calendar1" runat="server" CssClass="DDTextBox"></asp:Calendar>
You would then add code to the code-behind page for the control to set the value of the Calendar control when data binding occurs. This code might look as follows.
Sample of Visual Basic Code
Protected Overrides Sub OnDataBinding(ByVal e As System.EventArgs) MyBase.OnDataBinding(e) Dim dte As DateTime = DateTime.Parse(Me.FieldValue) Calendar1.SelectedDate = dte Calendar1.VisibleDate = dte End Sub
Sample of C# Code
protected override void OnDataBinding(EventArgs e) { base.OnDataBinding(e); DateTime dte = DateTime.Parse(this.FieldValue.ToString()); Calendar1.SelectedDate = dte; Calendar1.VisibleDate = dte; }
The last step is to mark your metadata properties to indicate that they should use this new control. You do so with the UIHint attribute. You pass the name of your control to this attribute. Dynamic Data will then find your control and use it for all display and edits (including GridView edits) that involve this field. As an example, the following metadata marks the HireDate and BirthDate properties of the Employee class to use the custom CalendarPicker control.
Sample of Visual Basic Code
<MetadataType(GetType(EmployeeMetadata))> _ Partial Public Class Employee End Class Public Class EmployeeMetadata <UIHint("CalendarPicker")> _ Public Property HireDate As Object <UIHint("CalendarPicker")> _ Public Property BirthDate As Object End Class
Sample of C# Code
[MetadataType(typeof(EmployeeMetadata))] public partial class Employee { } public class EmployeeMetadata { [UIHint("CalendarPicker")] public object HireDate { get; set; } [UIHint("CalendarPicker")] public object BirthDate { get; set; } }
Figure 12-30 shows the control in use on a webpage.
Figure 12-30 The custom field template being used in the browser.
Creating Custom Page Templates
Recall that the Dynamic Data templates are not specific to an object in your data context. Rather, they are generic templates that get information about the data they display from your data context at run time. You can customize how these templates display your data by using a metadata partial class and the data annotation attributes. However, you might find scenarios in which you need to create an entity-specific version of a page to control exactly how that entity is rendered to the browser. In this case, you need to create a custom page template.
Custom page templates are specific to a single entity in your data context. You create them by first creating a new folder inside the CustomPages folder. This folder should have the same name as your entity object. To create custom pages for working with products, for example, you would create a Products folder inside CustomPages. You then copy one or more of the page templates from the PageTemplates directory into your new folder. The Dynamic Data routing will look first in the CustomPages/EntityName folders for action pages such as Edit.aspx, Insert.aspx, and List.aspx. If the file it is looking for is there, it will use this custom page for display. If not, it will use the default action page inside the PageTemplates directory.
As an example, suppose you want to create a custom version of the List.aspx page for use with Products. You would create a Products folder in CustomPages. You would then copy the List.aspx page to that folder. You can then open this page and edit the markup and code-behind accordingly. A common edit is to bind the GridView control more tightly with the specific product. You can do so by setting the AutoGenerateColumns property to false. You can then add a series of DynamicField properties to represent your bound data. The following markup shows an example.
<asp:GridView ID="GridView1" runat="server" DataSourceID="GridDataSource" EnablePersistedSelection="true" AllowPaging="True" AllowSorting="True" CssClass="DDGridView" RowStyle-CssClass="td" HeaderStyle-CssClass="th" CellPadding="6" AutoGenerateColumns="false"> <Columns> <asp:DynamicField DataField="ProductID" HeaderText="ID" /> <asp:DynamicField DataField="ProductName" HeaderText="Name" /> <asp:DynamicField DataField="UnitPrice" HeaderText="Price" /> <asp:DynamicField DataField="UnitsInStock" HeaderText="Stock" /> <asp:TemplateField> <ItemTemplate> <asp:DynamicHyperLink runat="server" Action="Edit" Text="Edit"/> <asp:LinkButton runat="server" CommandName="Delete" Text="Delete" OnClientClick= 'return confirm("Are you sure you want to delete this item?");'/> <asp:DynamicHyperLink runat="server" Text="Details" /> </ItemTemplate> </asp:TemplateField> </Columns> <PagerStyle CssClass="DDFooter"/> <PagerTemplate> <asp:GridViewPager runat="server" /> </PagerTemplate> <EmptyDataTemplate> There are currently no items in this table. </EmptyDataTemplate> </asp:GridView>
Figure 12-31 shows this custom page running in a browser.
Figure 12-31 The custom page template being displayed in the browser.
Using Dynamic Controls in Existing Sites
You can use the features of Dynamic Data in existing sites. For example, you can add the full scaffolding and connect to a data context for CRUD operations, and you can create custom webpages (or customize existing ones) to take advantage of the lower code, lower markup solution that Dynamic Data offers.
Adding Dynamic Data Scaffolding to an Existing Website
You can add Dynamic Data scaffolding to an existing website. To do so, you must have a data context defined in your site (or you must create one). Remember, Dynamic Data works with either LINQ to SQL classes, the ADO.NET Entity Framework, or a custom data context.
You then edit the Global.asax file to register your context and enable custom routing. This code can be copied from the Global.asax file for a blank Dynamic Data site. Refer back to “Getting Started with Dynamic Data Websites” earlier in this lesson for details.
Finally, you copy the DynamicData folder from a blank Dynamic Data site into your existing site. You can then make customizations and use Dynamic Data features as discussed in prior sections.
Enabling Dynamic Data Inside a Webpage
You can use Dynamic Data inside completely custom webpages that do not use the scaffolding or routing common to Dynamic Data. For this scenario, you can create pages that use data-bound controls such as GridView, DetailsView, ListView, and FormView, and use Dynamic Data for displaying, editing, and validating data. In this case, you get the default behavior of Dynamic Data, including data validation. You can then customize this behavior by adding more information to your meta-model as described previously.
As an example, suppose you have a standard ASP.NET website (not one built with the Dynamic Data template). Suppose that this site has an ADO.NET Entity Data Model for working with the Northwind database.
You can then add an EntityDataSource control to the Default.aspx page for the site. This control can be set to use the entity model for selecting, inserting, updating, and deleting. Recall that you set EntitySetName to indicate the entity used by the data source. The following markup shows an example.
<asp:EntityDataSource ID="EntityDataSource1" runat="server" ConnectionString="name=northwndEntities" DefaultContainerName="northwndEntities" EnableDelete="True" EnableFlattening="False" EnableInsert="True" EnableUpdate="True" EntitySetName="Products"> </asp:EntityDataSource>
Next, you can add a data display control to the page. In this example, assume you add a GridView to the page. You would then configure this control to auto-generate columns and enable editing. The following markup shows an example.
<asp:GridView ID="GridView1" runat="server" DataSourceID="EntityDataSource1" AllowPaging="True" AllowSorting="True" AutoGenerateColumns="true"> <Columns> <asp:CommandField ShowEditButton="True" /> </Columns> </asp:GridView>
The final step is to add code to the Page_Init event to enable the Dynamic Data. The following code shows an example.
Sample of Visual Basic Code
Protected Sub Page_Load1(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Load GridView1.EnableDynamicData(GetType(northwndModel.Product)); End Sub
Sample of C# Code
protected void Page_Init(object sender, EventArgs e) { GridView1.EnableDynamicData(typeof(northwndModel.Product)); }
Figure 12-32 shows the results. Notice that the attempt to edit the first record failed. You can see the asterisk next to the ProductName field. This is because this field cannot be blank, as defined in the model. You can further customize this page to use even more features from Dynamic Data.
Figure 12-32 Dynamic Data being used on a standard webpage.
Practice Creating a Dynamic Data Website
In this practice, you create a Dynamic Data website and edit the default routing.
EXERCISE Creating a Dynamic Data Website and an Entity Model
In this exercise, you create a new Dynamic Data website, add a database to the site, and define an Entity Data Model. You then configure the site to work with the model and customize the routing.
Open Visual Studio and create a new ASP.NET Dynamic Data Entities Web Site. Name the site DynamicDataLab. Select your preferred programming language.
Add the northwnd.mdf file to your App_Data directory. You can copy the file from the samples installed from the CD.
Add an ADO.NET Entity Data Model to your site. Name the model Northwind.edmx. When prompted, allow Visual Studio to add this to your App_Code directory. Use the Entity Data Model Wizard to connect to all tables in the database; use the default settings throughout the wizard.
Open the Global.asax file. Inside RegisterRoutes, uncomment the line that calls RegisterContext. Change this code to register your new entity model and set ScaffoldAllTables to true. Your code should look as follows.
Sample of Visual Basic Code
'VB DefaultModel.RegisterContext(GetType(northwndModel.northwndEntities), _ New ContextConfiguration() With {.ScaffoldAllTables = True})
Sample of C# Code
DefaultModel.RegisterContext(typeof(northwndModel.northwndEntities), new ContextConfiguration() { ScaffoldAllTables = true });
Run the application and view the results. Select Products. Click to edit a product. Notice the URL routing. On the product editing page, clear the name field and click update. Notice the validation that is displayed.
Return to the Global.asax file to enable routing to ListDetails.aspx. This page allows you to edit a row directly in the GridView as well as in a DetailsView on the same page. To enable row editing, comment out the routes.Add call for {table}/{action}. Then uncomment both routes.Add calls at the bottom of the method. Notice that these routes indicate that the List and Details actions should route to ListDetails.aspx. This page handles all other actions inline with the page.
Rerun the site. Select the Products table. Notice the new URL. Click the Edit link for a product in the GridView control. Notice the inline editing and data validation.
Lesson Summary
There are two Dynamic Data website templates in ASP.NET: one for working with the Entity Framework and one for working with LINQ to SQL models. Both create a set of page and field template files inside the DynamicData folder. Both allow you to connect your data context to the site inside the Global.asax file.
You can use the System.Web.Routing inside the Global.asax file to indicate how your Dynamic Data site maps entity requests and actions formatted in a URI to page templates.
You use partial classes to extend the metadata of your data context. This metadata includes attributes from the DataAnnotations namespace that define display formatting properties and validation rules.
You can write additions to the OnChanging partial methods from your data context model to extend individual properties to include additional business logic.
You can create custom field templates that change how properties are displayed and edited. These field templates inherit from FieldTemplateUserControl. You apply a custom field template as metadata to a property in your partial entity class by using the UIHintAttribute.
You can create custom page templates that are entity specific inside the CustomPages folder. You add a folder with the same name as your entity. You then define custom action pages such as List.aspx and Edit.aspx.
You can use the EnableDynamicData method of the data view controls (such as GridView) to add Dynamic Data features to existing websites that do not contain the standard scaffolding.
Lesson Review
You can use the following questions to test your knowledge of the information in Lesson 3, “Working with ASP.NET Dynamic Data”. The questions are also available on the companion CD in a practice test, if you prefer to review them in electronic form.
You need to add metadata to your data context object, Invoice, in order to change how invoices are handled by Dynamic Data. Which actions should you take? (Choose all that apply.)
Create a new partial class called Invoice in the App_Code directory.
Create a new class called InvoiceAnnotations in the App_Code directory.
Decorate the Invoice class with the MetadataTypeAttribute class.
Decorate the InvoiceAnnotations class with the ScaffoldTableAttribute class.
You have defined a custom field template user control for changing the way an Invoice number is edited. You want to apply this control to the Invoice.Number property. Which data annotation attribute class would you use to do so?
MetadataType
Display
Editable
UIHint
You want to implement custom business logic that should run when the InvoiceNumber property is modified. What actions should you take? (Choose all that apply.)
Add a CustomValidator control to the DynamicData/FieldTemplates/Integer_Edit.asax file. Set this control to process custom logic to validate an invoice number.
Extend the OnInvoiceNumberChanged partial method inside the Invoice partial class to include additional validation logic.
Extend the OnInvoiceNumberChanging partial method inside the Invoice partial class to include additional validation logic.
If the logic fails, throw a ValidationException instance.