DataSources, Dynamic Data, and SoC

Abstract

This article demonstrates how ASP.NET Dynamic Data combined with Versatile DataSources allows new ways to build data entry web forms with an emphasis on separation of concerns between user interface and business objects.

DataSources, Dynamic Data, and SoC

A good strategy for application development involves Separation of Concerns (“SoC”). Build objects specific to a purpose and let other objects consume them without knowing the details of their underlying architecture. SoC is just as appropriate to building ASP.NET data entry web forms as anything else, but until recently there weren’t very good APIs to assist you. Now ASP.NET Dynamic Data and the attributes of System.ComponentModel.DataAnnotations are available and can help.

Dynamic Data employs two other technologies to construct a web form:

  • DataBound controls – Containers for the data entry areas of your web form, providing read-only and editable ways to interact with properties of an object supplied to it. These controls provide DataBinding between the object containing data and the controls they host. Examples include GridView, DetailsView, ListView, and FormView.
  • DataSource controls – Used by DataBound controls to get and set the object containing data. Examples include SqlDataSource (supports ADO.NET), LinqDataSource (supports LINQ to SQL), and EntityDataSource (supports ADO.NET Entity Framework.)

The “object” that is passed between DataBound and DataSource controls is often referred to as an “Entity”. It generally mimics the structure of a table with its properties matching the columns of the table. A single Entity is one record in the table.

The “Table” concept itself is a specific type of Entity. Not all data entry web forms interact with tables. Consider a web form which gathers information to send an email. It too has an Entity object and in theory you can use these Entities with ASP.NET Dynamic Data. In a data entry web application that imposes a strong separation of concerns, you probably have one or both types of classes.

Entity as a Table

public class Category
{
public Category() { }
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
public byte[] Picture { get; set; }
}

Entity to send an email

public class EmailGenerator
{
public EmailGenerator() { }
public string FromEmailAddress { get; set; }
public string ToEmailAddress { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public void Send()
{
MailMessage mailMessage =
new MailMessage(FromEmailAddress, ToEmailAddress);
mailMessage.Subject = Subject;
mailMessage.Body = Body.ToString();
SmtpClient client =
new SmtpClient("mail.mycompany.com");
client.UseDefaultCredentials = true;
client.Send(vMailMessage);
}
}

Let’s give a name to each of these Entity types:

  • DAO Entity – DAO stands for “Data Access Object”. A DAO class handles the CRUD (“Create, Read, Update, and Write”) actions that deliver the DAO Entity instances between the consumer (user interface, web service, etc) and the database.
  • POCO Entity – POCO stands for “Plain Old CLR Object”. A POCO class is any class, although in this case, it’s designed with properties to store some criteria that is consumed by an action method. The action method is either built into the class, like the Send() method above, or is in a separate class, making the POCO class an argument passed in to the action method.

Our goal is to use both types of entities with Dynamic Data while pursuing a strong separation of concerns. For both, the web form’s overall structure is basically the same.

<asp:DynamicDataManager ID="DDM" runat=server >
<DataControls>
<asp:DataControlReference ControlID="FormView1" />
</DataControls>
</asp:DynamicDataManager>

<asp:FormView ID="FormView1" runat="server" DataSourceID="DataSource1">
See the chart below
</asp:FormView>

<asp:someDataSource ID="DataSource1" runat="server">
</asp:someDataSource>

You populate the Template properties of the DataBound control with DynamicControl controls and other web controls.

Editing the Category DAO Entity

<EditItemTemplate>
Category Name:
<asp:DynamicControl ID="Name" runat="server"
DataField="Name" Mode="Edit" />
Description:
<asp:DynamicControl ID="Description" runat="server"
DataField="Description" Mode="Edit" />
Picture:
<asp:DynamicControl ID="Picture" runat="server"
DataField="Picture" Mode="Edit" />
<asp:Button ID="Submit" runat="server" Text="Save" CommandName="Update" />
</EditItemTemplate>

Editing the EmailGenerator POCO Entity

 

<EditItemTemplate>
Your Email Address:
<asp:DynamicControl ID="From" runat="server"
DataField="FromEmailAddress" Mode="Edit" />
Subject:
<asp:DynamicControl ID="Subject" runat="server"
DataField="Subject" Mode="Edit" />
Message:
<asp:DynamicControl ID="Body" runat="server"
DataField="Body" Mode="Edit" />
<asp:Button ID="Submit" runat="server" Text="Send" CommandName="Update" />
</EditItemTemplate>

The DynamicControl uses metadata about each property, coming from the Attributes on the property like RequiredAttribute and DataTypeAttribute, and from the property’s type (int, string, etc). It selects a predefined User Control called a Field Template that has been setup with exactly the web controls you want for that data type. As a User Control, you can easily customize its interface.

 

 

 

 

 

Dynamic Data could not handle either of these cases until now. The DAO Entity case was available if you allowed the web control developer to describe the query on the LinqDataSource or EntityDataSource. That breaks the goal of separation of concerns. The DataSource control should always delegate the query to the business object.

The POCO Entity case simply did not exist.

The problem: There needs to be a DataSource control capable of:

  • Supporting a strong separation of concerns
  • Supporting Dynamic Data

Today there are two sets of solutions. Microsoft is working on the DomainDataSource (now in the preview stage) to handle the DAO Entity case. Dynamic Data has the EnableDynamicData() method for the POCO Entity case. The Versatile DataSources project on CodePlex provides EntityDAODataSource and POCODataSource, one for each type of entity.

DataSource controls for DAO Entity classes

Here are the available DataSource Controls. Use this chart to understand why DataSources of the past do not support the goals of SoC and Dynamic Data.

Control name

Supported CRUD technologies

Dynamic Data

DAO Entity classes

SoC

Source

SqlDataSource

ADO.NET

No

No

No

ASP.NET 2

OleDbDataSource

ADO.NET

No

No

No

ASP.NET 2

LinqDataSource

LINQ to SQL

Yes

No

Not for queries

ASP.NET 3.5

EntityDataSource

Entity Framework

Yes

No

Not for queries

ASP.NET 3.5 SP1

ObjectDataSource

Custom – Your own classes

No

Yes

No

ASP.NET 2

DomainDataSource

LINQ to SQL

Entity Framework

Custom

Yes

Yes

Yes

RIA Services

EntityDAODataSource

ADO.NET

LINQ to SQL

Entity Framework

Custom

Yes

Yes

Yes

CodePlex (from Third Party)

“CRUD technologies” refers to a framework that handles CRUD actions: ADO.NET, LINQ to SQL, ADO.NET Entity Framework, and the like.

Choosing between DomainDataSource and EntityDAODataSource

The DomainDataSource or EntityDAODataSource are the two solutions now available. Since they attempt to solve the same problem, here’s a comparison. Since EntityDAODataSource is of my own creation, I certainly hope you see its advantages, but choose the right one for you.


DomainDataSource

EntityDAODataSource

Base classes for developing Data Access Objects

LINQ to SQL, ADO.NET Entity Framework. Can be extended to handle ADO.NET

ADO.NET, LINQ to SQL, ADO.NET Entity Framework

DAO model

Domain – 1 class for all Entities

Per entity

DAO initial code

Code Generator creates Domain class

Inheritance

Setup

Very easy

Very easy

Supports filters from DataSource

Yes

Yes

Supports sort expression from the DataBound control

Yes

Yes

Supports paging

Yes

Yes

Supports caching queries

Could not determine

Yes

Supports DynamicFilter and QueryableFilterRepeater controls

Yes – Using QueryExtender control.

Yes – Directly. No use of QueryExtender control.

Design mode support

Limited

Extensive

Example

The user wants to display a list of Products and offer search criteria on the Unit Price field. They elect to use the GridView for its quick setup, including the automatic generation of the columns list. The Data Access Object has defined a query method called “SelectPriceRange” which takes the low and high price criteria and other parameters to handle sorting, paging, etc.

clip_image002

The Data Access Object class: Using DomainDataSource

DomainDataSource implements the Data Access Object in the DomainService subclass where the SelectPriceRange() method is defined.

public class LinqToSqlNorthwindDomainService : LinqToSqlDomainService<NorthwindDataContext>
{
[Query()]
public IQueryable<Product> SelectPriceRange(decimal startPrice, decimal endPrice,
string sortExpression, int startRowIndex, int maxRows)
{ … }
}

The Data Access Object class: Using EntityDAODataSource

EntityDAODataSource requires a separate Data Access Object class for each Entity class. The DAO class for Products is called ProductsDAO where the SelectPriceRange() method is defined.

While these two examples are very similar, the big difference is in the last parameter of the SelectPriceRange method, SelectArgs. It allows the Select method to handle sorting, filtering, paging, and caching without making the web form developer be concerned about those details. It also allows the implementation to be changed to start supporting those features without affecting the user interface.

 

public class ProductsDAO : BaseEntityDAO<Product>
{
[DataObjectMethod(DataObjectMethodType.Select, false)]
[SelectArgs(WhereClause=false, FilterExpressions=false, SortExpression=true, Paging=true)]
public IEnumerable<Product> SelectPriceRange(decimal startPrice, decimal endPrice,
SelectArgs pSelectArgs)
{ … }
}

The Web form

This form includes several parts: DynamicDataManager control, Filtering controls, GridView, and DataSource control. Notice how aside from the DataSource controls, the web form is the same for both cases.

<asp:DynamicDataManager ID="DynamicDataManager1" runat="server" AutoLoadForeignKeys="true" >
<DataControls>
<asp:DataControlReference ControlID="GridView1" />
</DataControls>
</asp:DynamicDataManager>

Price Range:
<asp:TextBox ID="StartPrice" runat="server" Text="0" ></asp:TextBox>
<asp:RequiredFieldValidator ID="ProductNameTextRequired" runat="server"
ControlToValidate="StartPrice" ErrorMessage="Required" Display="Dynamic" />
<asp:CompareValidator ID="StartPriceDTC" runat="server"
ControlToValidate="StartPrice" Operator="DataTypeCheck"
ErrorMessage="Bad format" Display="Dynamic" />
&nbsp;-&nbsp;
<asp:TextBox ID="EndPrice" runat="server" Text="0" ></asp:TextBox>
<asp:RequiredFieldValidator ID="EndPriceTextRequired" runat="server"
ControlToValidate="EndPrice" ErrorMessage="Required" Display="Dynamic" />
<asp:CompareValidator ID="EndPriceDTC" runat="server"
ControlToValidate="EndPrice" Operator="DataTypeCheck"
ErrorMessage="Bad format" Display="Dynamic" />
<asp:Button ID="SearchBtn" runat="server" Text="Go" />
<br />

<asp:GridView ID="GridView1" runat="server"
DataSourceID="GridDataSource" DataKeyNames="ProductID"
AllowPaging="True" AllowSorting="True" >
<PagerTemplate>
<asp:GridViewPager runat="server" />
</PagerTemplate>
<EmptyDataTemplate>
There are currently no items in this table.
</EmptyDataTemplate>
</asp:GridView>

<asp:DomainDataSource ID="GridDataSource" runat="server" 
ContextTypeName="Product" SelectMethod="SelectPriceRange"
SortParameterName="sortExpression" StartRowIndexParameterName="startRowIndex"
MaximumRowsParameterName="maxRows" >
<SelectParameters>
<asp:ControlParameter ControlID="StartPrice" />
<asp:ControlParameter ControlID="EndPrice" />
</SelectParameters>
</asp:DomainDataSource>

<poco:EntityDAODataSource ID="GridDataSource" runat="server" 
EntityTypeName="Product" SelectMethod="SelectPriceRange">
<SelectParameters>
<asp:ControlParameter ControlID="StartPrice" />
<asp:ControlParameter ControlID="EndPrice" />
</SelectParameters>
</poco:EntityDAODataSource>

DataSource controls for POCO Entity classes

Only the POCODataSource control (found on CodePlex) can handle POCO Entity classes with Dynamic Data using a strong separation of concerns.

Dynamic Data also has the EnableDynamicData() method. The POCODataSource control is more consistent with other uses of Dynamic Data and is less CPU intensive on each page request.

Example

Let’s go use the EmailGenerator class from before, with attributes applied. Your web form will define the value for ToEmailAddress. The rest are entered by the user.

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

 

Web form
<asp:FormView ID="FormView1" runat="server" DataSourceID="POCODataSource1" DefaultMode="Edit"
onitemupdated="FormView1_ItemUpdated" >
<EditItemTemplate>
Your Email Address:
<asp:DynamicControl ID="From" runat="server" DataField="FromEmailAddress" Mode="Edit" />
Subject:
<asp:DynamicControl ID="Subject" runat="server" DataField="Subject" Mode="Edit" />
Message:
<asp:DynamicControl ID="Body" runat="server" DataField="Body" Mode="Edit" />
<asp:Button ID="Submit" runat="server" Text="Send" CommandName="Update" />
</EditItemTemplate>
</asp:FormView>
<poco:POCODataSource ID="POCODataSource1" runat="server"
OnCreatePOCOInstance="POCODataSource1_CreatePOCOInstance" />

Web form code behind
public partial class RunProductReport : System.Web.UI.Page
{
protected void FormView1_ItemUpdated(object sender, FormViewUpdatedEventArgs e)
{
if (Page.IsValid)
{
EmailGenerator emailGenerator = (EmailGenerator) POCODataSource1.POCOInstance;
emailGenerator.Send();
}
}
protected void POCODataSource1_CreatePOCOInstance(
object sender, PeterBlum.DataSources.CreatePOCOInstanceEventArgs e)
{
EmailGenerator emailGenerator = new EmailGenerator();
emailGenerator.ToEmailAddress = "CEO@mycompany.com";
e.POCOInstance = emailGenerator;
}
}

1 Comment

  • Very helpful series of posts related to dynamic data. It is exactly what I was searching for; too bad it took me 2 days learning dynamic data and searching the web to find this blog.
    Thanks for your effort of sharing this!

Comments have been disabled for this content.