What Dynamic Data REALLY is

In my recent posting “What do you think of Dynamic Data?”, users have offered many views on what ASP.NET Dynamic Data is and is not. In general, they feel like it’s for simple cases, mockups, and where customization isn’t needed. Everyone seems to agree it makes for very fast development, when limited to those situations.

I would like to reintroduce you to what Dynamic Data is. Forget what you know and follow along.

Dynamic Data is…

1. Separate classes that describe the data's model and its business logic.

2. A control generator that converts the data model into the web controls that match the business logic.

That’s it. Everything else you’ve learned is an implementation of these two items. Let’s look at them more carefully.

Separate classes for business logic

An experienced OOP developer knows to build objects specific to the task. Don’t let one class take on too many responsibilities. Yet when building an ASP.NET web form, many experienced OOP developers stick most of their code in a single class: the web form class. That class contains queries, validation rules, and so many things that are about the data. The web form class should interact with a separate class to handle all of that (…wait for it…) business logic.

When it comes to CRUD (“create, read, update, delete”) activities performed on a database, you should actually have two classes: the “Entity” which describes the structure of a table; and the Data Access Object (or if you prefer Domain Design, the domain class) which handles the CRUD actions, passing Entity classes between the consumer (your web form) and the data storage.

You also can have classes for non-CRUD activities. We tend to label these classes POCO (for “Plain old CLR objects”).

Example business logic for CRUD

These two classes describe the “Category” table. Category is the Entity class. It describes the columns in the table. The Entity class will be enhanced with business logic for validation and many other possible behaviors from the System.ComponentModel.DataAnnotations namespace. When using LINQ to SQL or Entity Framework, it generates this class for you.

CategoryDAO is the Data Access Object. It has to perform CRUD actions. They are defined in the Insert, Update, Delete, and the methods that get a list of Categories back.

 

public class Category
{
public int CategoryID { get; set; }
[Required()]
[DisplayName("Name")]
[StringLength(15)]
public string CategoryName { get; set; }
   [DataType(DataType.MultilineText)]
public string Description { get; set; }
[ScaffoldColumn(false)]
public DateTime LastUpdated { get; set; }
}
public class CategoryDAO
{
public void Update(Category entity) { … }
public void Insert(Category entity) { … }
public void Delete(Category entity) { … }
public IEnumerable<Category> GetAll(string sortExpression) { … }
public IEnumerable<Category> GetOne(int primaryKey) { … }
public IEnumerable<Category> GetRecentlyAdded(int numDaysOld) { … }
}

 

Example business logic for POCO

This example provides code that generates an email. It has two POCO classes. EmailGeneratorArgs holds the fields to establish on the email. It is passed to the Send() method of EmailGenerator.

 

public class EmailGeneratorArgs
{
public EmailGenerator() { }

public EmailGenerator(string fromEmailAddress, string toEmailAddress)
{
FromEmailAddress = fromEmailAddress;
ToEmailAddress = toEmailAddress;
}
[Required()]
[DataType(DataType.EmailAddress)]
[RegularExpression(@"^([\w\.!#\$%\-+.'_]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]{2,})+)$")]
public virtual string FromEmailAddress { get; set; }
[Required()]
[DataType(DataType.EmailAddress)]
[RegularExpression(@"^([\w\.!#\$%\-+.'_]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]{2,})+)$")]
public virtual string ToEmailAddress { get; set; }
[Required()]
[StringLength(80)]
public virtual string Subject { get; set; }
[Required()]
[StringLength(5000)]
[DataType(DataType.MultilineText)]
public virtual string Body { get; set; }
}
public class EmailGenerator
{
public static void Send(EmailGeneratorArgs args)
{
MailMessage mailMessage = new MailMessage(args.FromEmailAddress, args.ToEmailAddress);
mailMessage.Subject = args.Subject;
mailMessage.Body = args.Body.ToString();
SmtpClient client = new SmtpClient("mail.mycompany.com");
client.UseDefaultCredentials = true;
client.Send(vMailMessage);
}
}

Business logic rules have been standardized

Most concepts you can come up with for business logic have been used by thousands of other developers. You didn’t think of validation, security restriction rules, or that a decimal column represents more specific types like currency, percentage, and duration, right?

Microsoft has built many Attribute classes to host these business logic rules in the System.ComponentModel.DataAnnotations namespace. For those they didn’t declare (like security restrictions), someone else has probably posted a solution on the web, or you can create your own Attribute classes.

Once you have defined these business logic classes, your web forms can consume them.

Dynamic Data is a Control Generator

In my business selling data entry oriented web controls, I get support questions where they have built a code generator that follows their business logic to create the web form contents. In effort to describe the problem they have with one of my controls, they pass along their control generator code. Unfortunately everyone has had to write this from scratch, yet the concepts are standardized. ASP.NET Dynamic Data is Microsoft’s control generator.

It’s simple to use. Add the DynamicControl to the location where a property from the Entity or POCO class is shown. Dynamic Data will replace it with the right controls, following your business logic.

The control generator is very flexible because no user interface for a specific data type is hard coded. Instead, there is a folder full of User Controls called “Field Templates”. You define one or more Field Templates for a specific data type and its mode (read-only, edit, or insert). To me, this is a big improvement over those hard coded control generators I’ve seen from my customers. You can add or modify your app’s user interface without modifying your app’s code.

Example

Assume we have a web form where we edit the CategoryName property of Category table. Amongst the other HTML and web controls used to format the page, you add:

<asp:DynamicControl id="CategoryName" runat="server" DataField="CategoryName" Mode="Edit" />

At runtime, Dynamic Data looks at the DataField property. For CategoryName, it gets this from the business logic:
 
[Required()]
[DisplayName("Name")]
[StringLength(15)]
public string CategoryName { get; set; }

It replaces the DynamicControl with controls from a Field Template. It identifies the data type, first by looking for a DataTypeAttribute. None is specified here, so it looks at the property type, which is a string here. It knows to map the string type to the Field Template “Text.ascx”, “Text_Edit.ascx”, or “Text_Insert.ascx” depending on its Mode property.

Here is the ASP.NET markup from the default Text_Edit.ascx Field Template file:

<%@ Control Language="C#" CodeFile="Text_Edit.ascx.cs" Inherits="Text_EditField" %>

<asp:TextBox ID="TextBox1" runat="server" Text='<%# FieldValueEditString %>' CssClass="DDTextBox"></asp:TextBox>

<asp:RequiredFieldValidator runat="server" ID="RequiredFieldValidator1" CssClass="DDControl DDValidator" ControlToValidate="TextBox1" Display="Dynamic" Enabled="false" />
<asp:RegularExpressionValidator runat="server" ID="RegularExpressionValidator1" CssClass="DDControl DDValidator" ControlToValidate="TextBox1" Display="Dynamic" Enabled="false" />
<asp:DynamicValidator runat="server" ID="DynamicValidator1" CssClass="DDControl DDValidator" ControlToValidate="TextBox1" Display="Dynamic" />

Here is its code behind file.

using System;
using System.Collections.Specialized;
using System.ComponentModel.DataAnnotations;
using System.Web.DynamicData;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class Text_EditField : System.Web.DynamicData.FieldTemplateUserControl {
protected void Page_Load(object sender, EventArgs e) {
if (Column.MaxLength < 20) {
TextBox1.Columns = Column.MaxLength;
}
TextBox1.ToolTip = Column.Description;

SetUpValidator(RequiredFieldValidator1);
SetUpValidator(RegularExpressionValidator1);
SetUpValidator(DynamicValidator1);
}

protected override void OnDataBinding(EventArgs e) {
base.OnDataBinding(e);
if(Column.MaxLength > 0) {
TextBox1.MaxLength = Math.Max(FieldValueEditString.Length, Column.MaxLength);
}
}

protected override void ExtractValues(IOrderedDictionary dictionary) {
dictionary[Column.Name] = ConvertEditedValue(TextBox1.Text);
}

public override Control DataControl {
get {
return TextBox1;
}
}

}

 
 

Dynamic Data knows to enable the RequiredFieldValidator (due to the RequiredAttribute) and the Field Template knows to set the TextBox Columns property to 15.
 
 

Dynamic Data is not...

  1. Limited to using Linq to SQL or Entity Frameworks. There is support for LLBLGen (within their product) and ADO.NET (from my Versatile DataSources). Any modeling system can create the necessary "provider" classes to communicate their description of your data to Dynamic Data.
  2. Just for building database explorer apps. That's what I call the demo app that comes with DD. Those darned "Page Templates" and Url Routing features keep confusing things. Dynamic Data does not require using either one. I'd say that most apps will not use Page Templates or Url Routing (at least as defined by DD to deliver Page Templates). You can apply Dynamic Data to almost any web form that involves data entry.
  3. A complete solution. DD handles a specific part of web form development. You still have to build the look of your user interface using other web controls, HTML, and style sheets. DD gives you a few nice tools that come from working around business logic, like the Filtering controls.

Peter’s Soapbox

Having worked with DD since early 2008, I have found more than enough limitations and flaws to the ASP.NET Dynamic Data design that I can understand why users have not chosen it. My commercial product DES Dynamic Data is there to fix those problems. I intend to discuss the issues and my solutions in upcoming blogs, but let’s start now using the previous example.

The Text_Edit.ascx Field Template defines a list of 3 validators. They make sense in many cases. You wouldn’t use a CompareValidator to check the data type is a Date or Currency here because Text_Edit.ascx is expressly for the string data type. But what happens when the business logic is assigned a ValidationAttribute that isn’t mapped to a validator control in the Field Template? The Field Template will ignore it. It should not!

In DES Dynamic Data, I replace the validators with a single control, ColumnValidatorManager. It creates validator web controls by following the ValidationAttributes in the business logic.

Here’s my Text_Edit.ascx file, complete with the C# code.

<%@ Control Language="C#" AutoEventWireup="true" Inherits="PeterBlum.DES.DynamicData.TextEditFTUC" %>
<%@ Register assembly="PeterBlum.DES.DynamicData" namespace="PeterBlum.DES.DynamicData" tagprefix="desDD" %>
<script runat="server">
   1:  
   2:    protected void Page_Init(object sender, EventArgs e)
   3:    {
   4:       SetUpEditableDataControl(TextBox1);
   5:       SetUpColumnValidatorManager(ColumnValidatorManager1);
   6:    }
</script>
<des:FilteredTextBox ID="TextBox1" runat="server" />
<desDD:ColumnValidatorManager ID="ColumnValidatorManager1" runat="server" />

You can also see a second improvement here. The C# code is much simpler. ASP.NET Dynamic Data has a single base class for all FieldTemplates, System.Web.DynamicData.FieldTemplateUserControl.

Given the idea of a Field Template is to provide special behaviors for each data type, I felt there should be a class hierarchy inherited from FieldTemplateUserControl to cover many of the common data types. My Text_Edit.ascx file inherits from PeterBlum.DES.DynamicData.TextEditFTUC, which knows how to work with a textbox control, including transferring the string between the data and the textbox’s Text property.

1 Comment

Comments have been disabled for this content.