Saturday, 1 March 2008

A first look at the Dynamic Data Engine—the DynamicGridView Control

By: Dino Esposito

Dino Esposito introduces the ASP.NET DynamicGridView Control.

Introduction

Dynamic Data Controls are a new family of strongly-typed data-oriented controls made available with the first CTP of ASP.NET 3.5 Extensions. Right after installing the package, a brand new project template shows up in your Visual Studio 2008 project box - Dynamic Data Web site. If you choose to create such a new project, you instantly get a complete Web application that just works and allows you to view and edit all the tables bound to a given LINQ-to-SQL domain model. Put another way, the Dynamic Data engine can give you a back-office application to edit and fill the contents consumed by a Web application. At least for relatively simple admin scenarios, you get the final code in a matter of seconds. And all that you have to do is point the web.config file of the auto-generated project to your LINQ-to-SQL DBML class.

The Web application you get in this way contains a few predefined pages and user controls and makes use of a handful of new data-bound controls. By editing page templates and user controls, you can further customize the vanilla code, polish and adapt it to your needs and preferences. At this point, many developers spend their time trying to figure out how to adapt the scaffolded site to their needs. Customization may happen at several levels. You can change page templates, indicate ad hoc pages for displaying data from certain tables, change the way in which a few data types are rendered and edited, add attributes to your domain model that the scaffolded site will automatically recognize and dynamically apply.

While I do recognize that a similar scaffolding data tool has good reasons to exist, and has an even more brilliant future if used in conjunction with the MVC Framework, I don't believe that scaffolding is the key feature of the Dynamic Data engine. I'll spend this article (and some of the following) to illustrate the characteristics of the data controls that the scaffolding engine largely uses to its magic. The spotlight this month is for the DynamicGridView control.

Like the Plain GridView—Just More Dynamic

The DynamicGridView control is directly derived from GridView and can be used wherever a GridView is accepted. In addition, the dynamic grid has a few extra properties and supports new field types. But what's the key benefit of having such a new control?

The overall idea behind the Dynamic Data engine is the will to provide developers with time-saving tools that are smarter than current controls. For example, you know that the vanilla GridView control features a Boolean property named AutoGenerateColumns. When this property is set to true, the grid appends a new column for each member in the bound data source. If you set the AutoGenerateEditButton to true, you also gain an Edit button and the logic to edit each data row in place. If what you get out of the box is good enough, you're fine. Otherwise, you have no other reasonable options than creating your own templates. This applies to edit scenarios, data validation, but also plain data formatting and display.

You can bind the DynamicGridView only to a special flavor or data source object - an object that implements the IDynamicDataSource interface.

  1. public interface IDynamicDataSource : IDataSource
  2. {
  3. // Properties
  4. bool AutoGenerateWhereClause { get; set; }
  5. string ContextTypeName { get; set; }
  6. bool EnableDelete { get; set; }
  7. bool EnableInsert { get; set; }
  8. bool EnableUpdate { get; set; }
  9. string EntitySetName { get; set; }
  10. string Where { get; set; }
  11. ParameterCollection WhereParameters { get; }
  12. }

As of today, only the LinqDataSource control that ships with ASP.NET 3.5 Extensions CTP implements the interface. If you try to bind the DynamicGridView control to another data source control (i.e., the SqlDataSource) you get an exception. The same happens if you bind the DynamicGridView control to its data using the DataSource property.

  1. Samples.NorthwindDataContext db = new Samples.NorthwindDataContext();
  2. var data = from c in db.Customers
  3. select c;
  4. GridView1.DataSource = data;
  5. GridView1.DataBind();

Again, the problem is not with the data access layer of choice (i.e., LINQ-to-SQL versus ADO.NET), but lies with the required characteristics of the bound data source object.

Note: Future releases of the ASP.NET 3.5 Extensions are expected to increase the offering of dynamic data source objects. In particular, it is expected some support for LINQ-to-Entities and incidentally for databases other than the sole SQL Server.

Making Sense of Dynamic Data Sources

It would be helpful to compare the structure of the IDynamicDataSource interface with the classic IDataSource interface that all ASP.NET data source controls implement. Here's the old IDataSource from which the newest IDynamicDataSource is derived.

  1. public interface IDataSource
  2. {
  3. // Events
  4. event EventHandler DataSourceChanged;
  5. // Methods
  6. DataSourceView GetView(string viewName);
  7. ICollection GetViewNames();
  8. }

As you can easily see, a dynamic data source is designed to take advantage of some LINQ-to-SQL features. The name of the data context type is explicitly associated with the data source and so it is the name of the entity set. Also some Boolean properties have been added to enable direct operations on the LINQ-to-SQL domain model. Put another way, we could say that wanting to create scaffolding for database-driven Web sites, the ASP.NET team found it easy and convenient to take advantage of the LINQ-to-SQL data access layer. So they modeled the data source objects for dynamic controls in a way that incorporated key features for a data-driven piece of code: foreign-key management, filtering, and CRUD operations.

DynamicGridView in Action

Besides scaffolded sites, what's appealing with a DynamicGridView control and why should you consider using a dynamic grid instead of a plain grid? Consider the following code:

  1. <asp:DynamicGridView ID="GridView1" runat="server"
  2. DataSourceID="LinqDataSource1"
  3. AllowSorting="True"
  4. AutoGenerateColumns="False"
  5. AllowPaging="True">
  6. <Columns>
  7. <asp:BoundField DataField="Title" />
  8. <asp:BoundField DataField="Year" />
  9. </Columns>
  10. </asp:DynamicGridView>
  11. <asp:LinqDataSource ID="LinqDataSource1" runat="server"
  12. ContextTypeName="Samples.BooksDataContext"
  13. TableName="MyReadings" />

You get exactly the same results you would obtain with a GridView and BoundField controls. What's the point with DynamicGridView controls? With a DynamicGridView control, you can use an extended type of field - the DynamicField. The benefits of the Dynamic Data engine are all in the extra properties that dynamic fields feature. Imagine that the MyReadings table has the schema in Figure 1.

Figure 1: A sample table schema

images/fig01.jpg

The AuthorID column is a foreign key that references another table in the same database - say, the Authors table. If you add the AuthorID column to a regular grid, you won't see anything other than the numeric key that uniquely identifies the author of the book. LINQ-to-SQL, though, has the ability to represent relationships between tables as a property on the entity object. Here's an excerpt from the LINQ-to-SQL class.

  1. public class MyReading
  2. {
  3. // Properties mapped to table columns
  4. [Association(Name="Author_MyReading", Storage="_Author", ThisKey="AuthorID",
  5. IsForeignKey=true)]
  6. public Author Author
  7. {
  8. :
  9. }
  10. }

Because you're bound to a LINQ-to-SQL data model, what if you reference the Author property directly, as shown below?

  1. <Columns>
  2. <asp:BoundField DataField="Title" />
  3. <asp:BoundField DataField="Author" />
  4. <asp:BoundField DataField="Year" />
  5. </Columns>

Whether you use a GridView or a DynamicGridView, the result is the same. The author column will display any text that the ToString method of Author class returns. Typically, it will be something like "Samples.Author", that is the fully qualified name of the entity class. What you really want to obtain, instead, is the effective resolution of the foreign key.

There are still two open points. First, which table column would you use as the display column? Should it be the author's LastName or perhaps a made-to-measure DisplayName column that includes both first and last name? Should it be a calculated column in the table or will an extension property added to the LINQ-to-SQL domain model suffice? Second, how should this information be formatted?

In a GridView scenario, you can certainly address of all these concerns; and you do that only through templates. In a DynamicGridView scenario, you can benefit of a more declarative model. Here's an example:

  1. <Columns>
  2. <asp:BoundField DataField="Title" />
  3. <asp:DynamicField DataField="Author" />
  4. <asp:BoundField DataField="Year" />
  5. </Columns>

Using the members of the dynamic data source object, the DynamicField control finds out whether the column is a foreign key. If so, it uses some internal (but overridable) settings to determine which column should be used for display. Likewise, it uses internal settings to host the foreign key value into the grid. Figure 2 shows the standard output you get.

Figure 2: Automatic resolution of a foreign key column.

images/fig02.jpg

A dynamic field applies some logic to the raw data value it receives from the data source for display. In particular, it checks the data type and whether it is a foreign key. Then, it figures out the right display template to employ. In any Dynamic Data Web site, you have a folder named App_Shared/DynamicDataFields. The folder contains a long list of ASCX user controls, one for each supported display scenario: data types, foreign key, filters, and child records. In particular, the foreignkey.ascx user control is used to render the content of a dynamic field that happens to be a foreign key. By editing the content of that ASCX resource, you can change the hyperlink to a Label or whatever else. It is recommended, though, that you just add a new resource rather than editing the default one. So let's create a new foreignkeyreadonly.ascx that uses a Label instead of a hyperlink for the foreign key.

  1. <%@ Control Language="C#"
  2. Inherits="System.Web.DynamicData.FieldTemplateUserControlBase" %>
  3. <script runat="server">
  4. protected string GetDisplayString() {
  5. DynamicMetaForeignKeyMember column = (DynamicMetaForeignKeyMember)MetaMember;
  6. return FormatDataValue(column.OtherMetaTable.GetDisplayString(DataValue));
  7. }
  8. </script>
  9. <asp:Label ID="Label1" runat="server" Text="<%# GetDisplayString() %>" />

The code of the user control is mandatory and kind of boilerplate - there's no need to edit it. How can you force the site to use this user control instead of the default one? You can use the new RenderHint property on the DynamicField control.

  1. <Columns>
  2. <asp:BoundField DataField="Title" />
  3. <asp:DynamicField DataField="Author" RenderHint="foreignkeyreadonly" />
  4. <asp:BoundField DataField="Year" />
  5. </Columns>

The value assigned to RenderHint is assumed to be the name of an ASCX resource in the DynamicDataFields folder.

What about the display column? In Figure 2, the LastName column of the Author table is used. Is there any chance to control that aspect too? You bet. Here's all that you need.

  1. [DisplayColumn("LastName")]
  2. public partial class Author
  3. {
  4. }

You create a new partial class to extend the class that contains the referenced value and add a DisplayColumn attribute to indicate your column of choice. By default, it is the first column in the table of type string.

Summary

The DynamicGridView control is not just for scaffolding as it also provides new capabilities to regular pages and, to some extent, improves the GridView control. In addition, understanding the behavior of the DynamicGridView control may help you making sense of the magic behind Dynamic Data Web sites in ASP.NET 3.5 Extensions.

No comments: