Creating a Dynamic Data-Driven User Interface (Part 3)
By Scott Mitchell
Creating a Dynamic, Data-Driven User Interface |
---|
This article is one in a series of articles on building a web application that supports dynamic, data-driven user interfaces.
|
Introduction
This article is the third installment of a four-part series that examines how to build a data-driven web applications that offers dynamic user interfaces. Over the past two articles we created a sample web application that allows for numerous law firms to log in to the site and manage their clientele. The application's data model contains a
Clients
table that defines the fixed attributes for a client -
ClientId
, CustomerId
, FirstName
, LastName
, and so on. All law firms have these fixed
attributes available to them. Each law firm can also define dynamic attributes. For example, a law firm that specializes in personal injury might
need to capture client information like type of injury, whether the injury occurred on a job site, and so forth. The custom client attributes for
each law firm are stored in a database table named DynamicAttributesForClients
.
Part 1 examined the scope of the project and created the data model, while Part 2 showed how to allow customers (law firms) to define their custom client attributes. In this installment we create the web pages for managing clients. This includes two pages: one page to create new clients and manage their fixed attributes, and a second page to manage their custom attributes. Read on to learn more!
Getting Started...
Our web application models client information using three database tables:
Clients
- captures fixed client attributes, which are attributes common to all clients across all law firms. This includes attributes likeClientId
,CustomerId
,FirstName
, andLastName
.DynamicAttributesForClients
- captures the set of custom client attributes for each law firm. Part 2 looked at how to create a web interface for creating and managing these custom attributes.DynamicValuesForClients
- this table captures the values of the custom client attributes for a particular client. For instance, if a law firm has three custom client attributes - Date of Injury, Injured On Job Site, and Cannot Work Because of Injury - a particular client might have three records inDynamicValuesForClients
, storing the values 4/1/2008, True, and False for the three custom client attributes.
ClientId
, FirstName
, etc.) and the custom ones
(Date of Injury, Injured On Job Site, etc.). However, this adds more complexity. For the time being I'm going to make a very simple user interface
that separates the fixed and custom attributes into two separate pages. On one page users can create new clients by specifying the values for their
fixed attributes. Moreover, they can edit these fixed attributes and delete clients. To manage a client's custom attributes the user will need to visit
a second page, which enumerates the custom attributes, loads the current client's information, and allows this data to be edited. (With a little work
you could meld the fixed and custom attributes into a single, unified page.)
Creating a New Client
The demo application (available for download at the end of this article) has been updated to include a new page named
~/Customers/Default.aspx
.
This page uses a DetailsView and SqlDataSource control to allow visitors to add a new client to the system. As aforementioned, when adding a new client
to the system the user is prompted for just the fixed data attributes, after which a new record is added to the fixed data model table, Clients
.
I started building this page by adding a SqlDataSource control with the following INSERT
statement:
INSERT INTO [Clients] ([CustomerId], [FirstName], [LastName], [Email])
|
Note that the user will be prompted to enter values for the FirstName
, LastName
, and Email
parameters.
The CustomerId
value is based on what customer (law firm) the currently logged on user is associated with. In Part 2 we built a
Helpers
class with a method named GetCustomerIdForLoggedOnUser
that returns the CustomerId
value for the
currently logged on user. The value for the CustomerId
parameter is set in the DetailsView control's ItemInserting
event:
Protected Sub dvAddClient_ItemInserting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.DetailsViewInsertEventArgs) Handles dvAddClient.ItemInserting
e.Values("CustomerId") = Helpers.GetCustomerIdForLoggedOnUser()
End Sub
|
After creating and configuring the SqlDataSource, I bound it to the DetailsView and configured the DetailsView to support inserting. This DetailsView is used
solely for adding new clients; in the next section we'll add a GridView to the page to display the clientele. To have the DetailsView permanently
render its inserting interface I set its DefaultMode
property to Insert
. Finally, I customized the DetailsView control's
inserting interface to include validation controls for the FirstName
, LastName
, and Email
fields.
The following screen shot shows the DetailsView control when viewed through a browser.

Editing and Deleting Clients
Along with the DetailsView control the
~/Customers/Default.aspx
page also includes a GridView that lists the customer's clientele.
The GridView lists only the fixed attribute values, and these values are editable from the GridView interface. When a client is edited from the
GridView the SqlDataSource control's UpdateCommand
is sent to the database. The UPDATE
statement used here simply
updates the corresponding record in the Clients
table; it does not modify any of the custom attribute values for the client.
The GridView also includes the ability to delete clients. The SqlDataSource's DELETE
statement deletes the corresponding record
from the Clients
table. If there are any custom attribute values for the client then there will be records in the
DynamicValuesForClients
table. When defining the data model in Part 1 we created a foreign key constraint between the
Clients.ClientId
and DynamicValuesForClients.ClientId
columns, and instructed the relationship to cascade deletes.
If you are following along by creating your own web application and did not configure the constraint to cascade deletes you will get an exception
if you attempt to delete a client that has custom attribute values. To remedy this you can either configure the constraint to cascade deletes or
proactively delete the corresponding custom attribute values before deleting the client. This can be done through a stored procedure.
The GridView allows the visitor to only edit the fixed client attributes. To manage the custom attributes the visitor must go to another page.
To help the user reach this page I added a HyperLinkField to the GridView as its far right column. This renders a hyperlink that points to
~/Customers/ClientCustomAttributes.aspx?ID=ClientID
, where ClientID is the value of the ClientId
column
of the client whose custom attributes are to be viewed or edited.
The following screen shot shows the GridView in action. Note that the Edit and Delete buttons enable the visitor to edit a client's fixed attribute values and delete a client. The View/Edit Custom Attributes link, when clicked, whisks the user to a second page where they can view and edit the selected client's custom attributes.

The Challenges Behind Building the Custom Client Attributes User Interface
Because each customer can define their own custom client attributes, the
~/Customers/ClientCustomAttributes.aspx
page, which displays
a user interface for viewing and editing these attributes, needs to be able to dynamically generate the user interface for the attributes defined for
the customer to which the currently logged on user is associated with. The good news is that ASP.NET enables page developers to programmatically add
Web controls to the page's control hierarchy. In other words, we can write code to programmatically construct TextBoxes, CheckBoxes, and other
controls so as to dynamically generate the appropriate user interface for the customer's own unique client attributes. In this article we will skim
over the code for dynamically adding controls to a page; for a more in-depth look see Dynamic
Controls in ASP.NET and Creating Dynamic Data Entry User Interfaces.
Generating the appropriate user interface is one challenge. Another is displaying the custom attribute values for the client being edited. Yet another challenge is saving these custom attribute values back to the database. These challenges are explored over the next two sections.
Creating the Custom Client Attribute UI and Loading the Client's Current Values from the Database
When programmatically adding Web controls to an ASP.NET page it is essential that the controls are added to the page on each and every page visit. This includes the first page visit and all subsequent postbacks. For the demo application this user interface is created during the Init event. In the Page_Init event handler I get all custom client attributes from the
DynamicAttributesForClients
table for the currently logged on
custom and LEFT JOIN
that with the DynamicValuesForClients
table, which returns the values (if any) for the custom client
attributes for the client whose ClientId
value was passed through the querystring.
Protected Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init
|
After the custom attributes (and the client's corresponding values) the results are enumerated and the appropriate set of controls are created.
The ~/Customers/ClientCustomAttributes.aspx
page includes a Table Web control named CustomUITable
. This Table has
a row programmatically added for each custom client attribute. Each row includes two columns that contain:
- The
AttributeName
value (i.e., the text, "Date of Injury"). - The user interface associated with the attribute's data type.
AddCustomAttribute
adds a two-column row to the CustomUITable
table for each custom client attribute.
Private Sub AddCustomAttribute(ByVal DynamicAttributeId As Guid, ByVal DataTypeId As DataTypeIdEnum, ByVal AttributeName As String, ByVal AttributeValue As Object)
|
The CreateCustomAttributeUI
method generates the Web controls for the user interface for the current custom client attribute.
This method returns a List
of Control
instances (each Web control in ASP.NET derives from the
Control
class).
the appropriate user interface for each custom client attribute.
Recall that each attribute is assigned a data type, which dictates the type of information expected from the user. We defined four data types in Part 1. The following list enumerates these data types and specifies the user interface the demo application uses for each:
- String - a multi-line TextBox Web control.
- Boolean - a CheckBox Web control.
- Numeric - a TextBox Web control and a CompareValidator that ensures that the user's input is a valid numeric value.
- Date - a TextBox Web control and a CompareValidator that ensures that the user's input is a valid date value.
CreateCustomAttributeUI
method. An alternative approach
would be to model the data types as classes in the web application that were responsible for their own UI rendering.
This is the ideal approach, but it is easier to see and understand what's happening with the UI creation hard-coded in the code-behind, which is why
I've gone with that approach here. In addition to creating the UI, the CreateCustomAttributeUI
method also assigns the current value
in the database to the Web control. For example, if there's a custom client attribute named "Date of Injury" and the edited client has a value of
4/1/2008 for this attribute, the CreateCustomAttributeUI
method both creates the TextBox and CompareValidator controls associated with
this UI and assigns the Text
property of the TextBox to "4/1/2008".
The CreateCustomAttributeUI
method and helper methods called from within follow.
Private Function CreateCustomAttributeUI(ByVal DynamicAttributeId As Guid, ByVal DataTypeId As DataTypeIdEnum, ByVal AttributeValue As Object) As List(Of Control)
|
A couple of comments before we move on. First, note the each Web control added to the page has its ID
property set. This is done because
later, when we update the database with the user's input, we need to be able to programmatically access the controls. Therefore, the ID
is set
to the primary key of the DynamicAttributesForClients
table, DynamicAttributeId
. This column is a uniqueidentifier
,
which is equivalent to a Guid
type in the .NET Framework. The Guid
class has a ToString()
method that turns the
value into a string representation like 37146444-9d4c-4306-bc4a-fdab87911015. However, the hyphens cause problems with the client-side script emitted
by the CompareValidator controls. Therefore, I created and use the GetID
method when assigning a control's ID
value. The
GetID
method converts the Guid
to a string and then replaces all hyphens with underscores.
Another thing to take note of is that when working with the AttributeValue
we need to first see if it is equivalent to a database NULL
value before assigning the value to the control. This is because there may not be a corresponding record in DynamicValuesForClients
for all
of the custom client attributes. For example, if a client has just been created but has not yet had any custom attributes defined then the SELECT
query used to return the custom client attributes will have a NULL
for the custom attribute values.
The following screenshot shows the ~/Customers/ClientCustomAttributes.aspx
page when visited for a client that does not yet have any
custom attributes values specified. This customer - Hutz and Hutz - has five custom client attributes defined:
- Birthdate (Date)
- Employed (Boolean)
- Married (Boolean)
- Number of Years at Current Job (Numeric)
- Reason for Law Suit (String)

Saving the Client's Custom Attribute Values
At this point the
~/Customers/ClientCustomAttributes.aspx
page displays the user interfaces for the custom client attributes and includes
the currently edited client's current values. The last piece of the puzzle is to update changes to the client's custom attribute values. The
~/Customers/ClientCustomAttributes.aspx
page includes an Update button that, when clicked, updates the client's custom attribute values
and then redirects the user back to the Manage Clients page (i.e., ~/Customers/Default.aspx
).
Our first order of business is to get the values the user entered into the various custom attribute user interfaces. This is accomplished by
querying the database to retrieve the list of custom client attributes for the client and then enumerating the attributes and using the
FindControl(controlId)
method to programmatically access the control and get its value.
In the following code snippet (which is found in the Update Button's Click
event handler) gets the custom client attributes from the
database, loops through them and for each attribute it calls the GetValueForCustomAttribute
method to retrieve the user's input into the user interface.
We'll examine this method momentarily.
'Get the custom attributes for this customer
|
The values returned from the GetValueForCustomAttribute
method are stored in a Dictionary
object named
AttributeValues
. A Dictionary
object is useful for storing a collection of items that are indexed by some value other
than an ordinal number. In this case I want to have a collection of attributes, accessible by AttributeId
(a Guid
value), whose values are
SqlParameter
objects with the appropriate value entered by the user. Therefore, I created a Dictionary
object with
keys of type Guid
and values of type SqlParameter
- Dim AttributeValues As New Dictionary(Of Guid, SqlParameter)
.
(If this doesn't make too much sense it should become clearer once we look at the code later on in the Click
event handler that actually updates the database.)
The GetValueForCustomAttribute
method returns a SqlParameter
object with a ParameterName
value of
@DynamicValue
and appropriately set Value
and DbType
properties. If the user does not enter any value, the
SqlParameter
's Value
is set a database NULL value (DBNull.Value
). Moreover, the DbType
property
is set according to the data type of the custom client attribute. The user interface used for a particular custom client attribute is retrieved via
the FindControl(controlId)
method (namely, CustomUITable.FindControl(controlId)
). Recall that when creating
the user interface controls for the custom client attributes we set the ID
of the control to the value of the
DynamicAttributeId
column, formatted using the GetID
method. This same logic is used to programmatically find the control so
that the value entered by the user can be retrieved.
That
Private Function GetValueForCustomAttribute(ByVal DynamicAttributeId As Guid, ByVal DataTypeId As DataTypeIdEnum) As SqlParameter
|
Like with the code used to create the user interface, logic used in the GetValueForCustomAttribute
method to retrieve the user's
value is hard-coded in the ASP.NET code-behind class.
Once we have all of the custom client attribute values loaded into the AttributeValues
Dictionary
object we are ready
to update the database. Each custom client attribute value is stored as a record in the DynamicValuesForClients
table. If there are
five custom attributes each client whose custom attribute values have been saved will have five corresponding records in
DynamicValuesForClients
. When saving a client's custom attribute values we may need to:
- Insert records into
DynamicValuesForClients
- if the client has not yet had any custom attribute values specified, or if a new custom client attribute has been added to the system, then there is no corresponding record inDynamicValuesForClients
for a particular attribute's value. Therefore we'll need to add a new record toDynamicValuesForClients
. - Update records in
DynamicValuesForClients
- if a client already has a record inDynamicValuesForClients
for a particular custom attribute, updating the attribute value requires updating the corresponding database record.
DynamicValuesForClients
table for the
ClientId
and AttributeId
pair. I created a stored procedure named lawfirm_AddOrUpdateDynamicValueForClient
to handle this decision. This makes the ASP.NET code very simple: just loop through the AttributeValues
Dictionary
object
and for each item call the lawfirm_AddOrUpdateDynamicValueForClient
stored procedure, passing in the ClientId
, AttributeId
,
and the value for that attribute. (Because the items in the AttributeValues
Dictionary
object are SqlParameter
objects we
can add them to the SqlCommand
's Parameters
collection using myCommand.Parameters.Add(AttributeValues(attributeId))
.)
For Each AttributeId As Guid In AttributeValues.Keys
|
The lawfirm_AddOrUpdateDynamicValueForClient
stored procedure follows. It is pretty simple - it checks to see if there is already a record
for the ClientId
and AttributeId
pair passed in. If so, then the record already exists in the table so an UPDATE
statement is used; if not, the record needs to be added, so an INSERT
statement is used instead.
CREATE PROCEDURE dbo.lawfirm_AddOrUpdateDynamicValueForClient
|
That's it! I encourage you to download the code demo at the end of this article and closely examine the code in the page. One thing you'll notice
is that the entire set of SQL statements in the Update Button's Click
event handler are wrapped up in a transaction. This technique
ensures that the set of INSERT
and/or UPDATE
statements that occur as each custom client attribute value is updated
are treated as an atomic operation. For more on implementing transactional support in your .NET code, see
Maintaining Database Consistency with Transactions.

Looking Forward...
We now have a fully functional dynamic, data-drive web application! Customers can log on to the site and define custom client attributes and manage values for those custom attributes. Of course, there is much room for improvement and some lingering questions, like what happens if a customer redefines an existing custom attribute, and how can we extend the system to include more data types, and how do we add extra attributes to these data types, such as indicating that a particular string field is required or that a date value must be between some range of legal values? Part 4 discusses questions like these.
Until then... Happy Programming!
Attachments
Further Reading
sql_variant
Data Type