Since a major goal of Dynamic Data is to separate the business logic from the user interface, let’s look at a few areas where these concepts still blend and their alternatives.

Today we look at the…

ValidationAttribute

The System.ComponentModel.DataAnnotations.ValidationAttribute is the base class for attributes that describe validation rules. Its children include RequiredAttribute, RangeAttribute, and RegularExpressionAttribute.

Validation has crossover with the user interface in that a validation error must be presented to the user. Therefore the business logic which specifies ValidationAttributes needs to expect that its communications will be handled in the User Interface layer.

Yet the ValidationAttribute lets you define an error message in its ErrorMessage property.

[Required(ErrorMessage="The Price is required.")]
public decimal UnitPrice { get; set; }

The ErrorMessage property violates the separation between the business logic and user interface in several ways:

  • There is a single message for a property. It lacks context assigned by the user interface. While “The Price is required” may make sense initially, the user interface may need “Go to the Price box and enter a value.”
  • If you want formatting, you have to avoid using UI specific tokens like HTML or RTF because the Entity class is in the middle tier.
  • While the many attributes support localization, care must be taken to insure that the Entity class has the same culture settings and string storage mechanism.

How do you fix this?

I see nothing wrong with a standard error message in the Entity layer, void of any formatting. It handles the cases where the user interface is less demanding, such as when a Web Service needs to report an error to the user.

The user interface developer overrides the error message from the business layer in the Field Templates by assigning the ErrorMessage property on individual Validators.

Here is a modified version of the Integer_Edit.ascx Field Template.

<%@ Control Language="C#" CodeFile="Integer_Edit.ascx.cs" Inherits="Integer_EditField" %>

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

<asp:RequiredFieldValidator runat="server" ID="RequiredFieldValidator1" CssClass="DDControl DDValidator" ControlToValidate="TextBox1" Display="Dynamic" Enabled="false"
ErrorMessage='<%# String.Format("Go to the {0} box and enter a value.", Column.DisplayName) %>' />
<asp:CompareValidator runat="server" ID="CompareValidator1" CssClass="DDControl DDValidator" ControlToValidate="TextBox1" Display="Dynamic"
Operator="DataTypeCheck" Type="Integer"
ErrorMessage=='<%# String.Format("The {0} box must only contain numbers.", Column.DisplayName) %>' />
<asp:RangeValidator runat="server" ID="RangeValidator1" CssClass="DDControl DDValidator" ControlToValidate="TextBox1" Type="Integer"
Enabled="false" EnableClientScript="true" MinimumValue="0" MaximumValue="100" Display="Dynamic"
ErrorMessage='<%# String.Format("Enter a number between {0} and {1}.", MinimumValue, MaximumValue) %>' />
<asp:DynamicValidator runat="server" ID="DynamicValidator1" CssClass="DDControl DDValidator" ControlToValidate="TextBox1" Display="Dynamic" />

Here are a few guidelines:

  • To insert the property’s “DisplayName” (which comes from the DisplayNameAttribute or DisplayAttribute), use the Column.DisplayName property. You can see this in the RequiredFieldValidator.
  • Don’t set up the ErrorMessage of the DynamicValidator. Its the one validator that will use the actual error message from the business logic. But it doesn’t come from a ValidationAttribute. Instead, it comes from exceptions thrown within your business logic as it validates a property.
  • The RangeValidator’s MinimumValue and MaximumValue properties are established from the RangeAttribute.

Peter’s Soapbox

Personally I don’t think validators are the place for context specific error messages any more than the ValidationAttributes. Field Templates are data type specific, not situation specific. Instead the page developer should be able to specify their desired error messages.

Suppose you have a web form where you need the date fields to show a different error message than other date fields throughout the application. You would have to duplicate your Date_Edit.ascx Field Template, giving it new error messages and a new name like “DateVal001_Edit.ascx”. I don’t like the idea of creating a new Field Template just to modify something as minor as a string, style sheet class, or other visual element.

In my DES Dynamic Data, I introduced a new control called Customizer. It exposes numerous properties found on Field Templates, including validator error messages. My Field Templates are coded to look for an override found on the Customizer and update their Validator controls. This gives a context specific solution.

<desDD:Customizer ID="Customizer1" runat="server" 
CurrencyTextBox_AlignRight="True" CurrencyTextBox_ShowSpinner="True"
Text_ShowEmailsAsHyperlinks="Default" Text_ShowUrlsAsHyperlinks="Default">
<ErrorMessages>
<desDD:ValidatorErrorMessages DataTypeName="Integer"
NameOfValidatorType="RequiredTextValidator"
ErrorMessage="Please enter only &lt;b&gt;digits&lt;/b&gt;."
SummaryErrorMessage="Please enter only digits on the {LABEL} field." />
</ErrorMessages>
</desDD:Customizer>

Peter's Polling Package has been updated to v1.1.4.

It includes several small enhancements inspired by customer feedback.
This is a free service release for PPP customers.

Get this Update

Enhancements

  • Documentation reformatted. Installation directions improved.
  • Assembly supports partial trust environments
  • The question can now be positioned to the left of the answers by using the xQuestionToLeftOnEntryB and xQuestionToLeftOnResultsB properties.
  • The xRotatingPollsEntryOnlyB property is an expansion of the xRotatingPollsB property. When true, each vote is followed Entry mode showing the next question. It's basically a way to go through a series of questions one-by-one like a survey.
  • New event: PollCreating. Use it to update the Poll control’s properties or take another action immediately prior to when a poll record is converted into the content on the page. This event supplies you with the current view, poll record, and poll count amongst multiple polls shown, all in the Args parameter.
  • RecentPolls now identifies the record ID of the currently shown Poll within its xPollID property
Bug fixes
  • The Poll's PreRender event was not supported.
  • ASP.NET 2 legacy mode could sometimes prevent saving the value of a vote. The poll would re-appear in Entry mode after submission.

 

Since a major goal of Dynamic Data is to separate the business logic from the user interface, let’s look at a few areas where these concepts still blend and their alternatives.

Today we look at the…

UIHintAttribute

Attributes like System.ComponentModel.DataAnnotations.UIHintAttribute are placed on the properties of your Entity class. That’s the business layer. Yet UIHintAttribute explicitly determines which Field Template is used. The Field Template is in the UI layer.

The business layer should influence the user interface into picking a Field Template, but it shouldn’t know anything about specific Field Templates. It’s influence comes from identifying the data type of the property (table column), such as a date or integer. The business layer has two ways to identify the type:

  • The DataTypeAttribute
  • The property’s type (when there is no DataTypeAttribute assigned)
public class Products
{
public string ProductName { get; set; }

[DataType(DataType.Currency)]
public decimal UnitPrice { get; set; }

[DataType(DataType.Date)]
public DateTime RestockDate { get; set; }
}

Your app will have many Field Template files whose file name matches a data type, like “Text.ascx” (for string types), “Currency.ascx” and “Date.ascx”. These are the default user interfaces for a data type. If you need something different from the default, create a new Field Template file and give it a unique name. Perhaps you want a Date label to be formatted using the long date pattern. You might name your new Field Template “DateLongPattern.ascx”.

How do you fix this?

Here’s how to replace the UIHintAttribute.

  1. Use the DataTypeAttribute to specify a general type. What about the case when the enumerated type System.ComponentModel.DataAnnotations.DataType does not have an element to match your needs? Initially you would think to use the UIHintAttribute. Instead, pass a string name of the type to the DataTypeAttribute.
    [DataType("Measurement")]
    public decimal Distance { get; set; }

  2. The user interface developer specifies the Field Template’s name in the UIHint property found on the DynamicControl and DynamicField objects.
    <asp:DynamicControl id="RestockDate" runat="server" UIHint="DateLongPattern" />

 



This is part of a series of postings asking for improvements to Visual Studio and ASP.NET.

As a commercial web control developer, I have to include extensions to Visual Studio's design mode, such as UITypeEditors and TypeConverters. Both of these classes augment the Properties Editor for an individual property. Both can provide a dropdownlist or popup a window such as the CollectionEditor.

Here the UITypeEditor DataTemplateNameEditor provides a dropdownlist of stored "Pattern Template" file names to the FieldsPatternTemplateName property.

public class DynamicFormView : FormView
{
[Editor(typeof(DataTemplateNameEditor), typeof(System.Drawing.Design.UITypeEditor))]
public virtual string FieldsPatternTemplateName
{
get { return fFieldsPatternTemplateName; }
set { fFieldsPatternTemplateName = value.Trim(); }
}
protected string fFieldsPatternTemplateName = "DetailsView";
}

 

UITypeEditorDropDownList

I can also use these attributes in the SmartTag attached to the web control in design mode.

Now it's time for Intellisense to support the UITypeEditor and TypeConverter classes. Why shouldn’t that dropdownlist appear to help setup the property? I have numerous cases where they could really assist the user. Some examples:

* In the ControlToValidate property on Validator controls, show a list of available controls.

* In the properties to choose the type of the Entity or DataContext in DataSource controls.

Why shouldn’t the user be able to popup a model dialog editor for a property? For example, in the Validationexpression property of the RegularExpressionvalidator to provide a tool to test your expression.

I have learned that I can do some of this today by using the TypeConverter and its GetStandardValues() method. See “How to: Implement a Type Converter”.

Unfortunately Intellisense gathers the list from GetStandardValues() once, very early in the design mode preparation. At that time, there is no “context” which provides design mode information that you can use to get a list of controls on the page. My own situations nearly always need data that is available at a later time.

So I request Microsoft to improve the GetStandardValues to allow it to be retrieved at the moment its needed. Perhaps there can be a new property or attribute assigned to the Typeconverter class to indicate this?

I understand why UITypeEditors and Typeconverters are not implemented in Intellisense today. They require that “context” I mentioned earlier. The UITypeEditor is hugely connected to design mode’s objects, which are associated with the context. Visual Studio would have to spend time to convert the markup into the control class instance and build a number of design mode support objects. That will slow down Intellisense. Yet I’d like Microsoft to try. Perhaps they can introduce a similar class to UITypeEditor that is specific to Intellisense.

In any case, I can’t wait to enhance my web controls so that you markup users can benefit.

And if someone out there knows of a way to do this now, let me know!

According to users comments in my recent posting “What do you think of Dynamic Data?”, they are under the impression that it is faster developing a web application when using ASP.NET Dynamic Data than with traditional web form development.

Is it really faster? Now remember that I’m a Dynamic Data advocate, so you’d think I’d say “yes”. My answer is “Once you learn the technology and have built several pieces, it is. But before that, it will take just as much work as traditional web form development.”

Dynamic Data offloads a bunch of work to the business logic classes and its control generator. You will spend time developing those business logic classes, adding attributes from the System.ComponentModel.DataAnnotations namespace and validation code. The control generator uses Field Templates to describe the user interface of an individual data type. You will spend time preparing your library of Field Templates. Believe it or not, you have to do the same work if you built traditional web forms, only your business logic is worked into the web form’s code and Field Templates are replaced by dropping controls onto the page.

The speed comes from:

  • Reusing business logic classes in different forms; also reusing them in other situations like web services
  • A change to the business logic is automatically adapted by your web forms
  • A change to a Field Template impacts all consumers of it
  • If you use the “automatic scaffolding” feature, the control generator can tell the GridView or DetailsView the exact list of data fields to show. You can change the business logic very easily to show a new field or hide an existing one.

The perception that developing DD apps is faster comes from:

  • The ease of generating Entity classes in LINQ to SQL or Entity Framework because of their code generator
  • Dynamic Data only needs one line of code to interpret those Entity classes (call the MetaModel.RegisterContext() method in Application_Start).
  • Working with the tutorials, you have not gone through the effort of building real world business logic. Your Entity classes are incomplete. Still Dynamic Data gives a pretty good first impression.
  • Page Templates are used in the standard example application. Page Templates predefine web forms for each CRUD action, and use the business logic, Url Routing, and automatic scaffolding to create an application that explores your tables. Page Templates may be a model for further page development, but you will often replace them with pages that have the exact features you need. With the introduction of “Entity Templates” (and my own Pattern Templates in DES Dynamic Data), some of that process has been simplified.
  • The tutorials and standard example application use LinqDataSource and EntityDataSource. Neither provides a full separation of concerns where CRUD business logic is relocated to a Data Access Object. So you end up writing queries directly in the web forms instead of the Data Access Objects. Also, because the samples are built around Page Templates, Page Templates do not use any special query. Your real world apps will need additional queries. When using Data Access Objects, use the DomainDataSource or my EntityDAODataSource.


I am not trying to put down Dynamic Data here. I hope to establish reasonable expectations for a great tool. Its greatness comes from allowing you to separate concerns between business logic and UI, for which it creates elements of the UI from that business logic. It's a natural pattern for UI development that Microsoft has finally addressed.

 

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.

This is part of a series of posts offering ideas to improve ASP.NET.

I cringe when I see users would write recursive search code to find controls in the Page’s control tree. For example:


public void UpdateTextBoxCss(Control parentControl, string css)
{
foreach (Control child in parentControl.Controls)
{
if (child is TextBox)
((TextBox)child).CssClass = css;
else
UpdateTextBoxCss(child, css); // RECURSION
}
}

These control trees can be huge. Don’t forget that many controls that you add have their own child controls which also get searched (what a waste of CPU time!).

I would like to see a new event added to the Page class, OnControlAdded. Its called each time a control is added anywhere in the control tree. At the time this event handler is called, the control may not be fully prepared. Control properties still have to be assigned. So I would use this to create a collection of the exact controls I want to process. Then run through the collection at a later stage in the page cycle, such as in PreRender.


private List<TextBox> _TextBoxes = new List<TextBox>();

protected void Page_ControlAdded(object sender, EventArgs e) // sender is assumed to be the added control
{
if (sender is TextBox)
_TextBoxes.Add((TextBox)sender);
}
protected void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
foreach (TextBox textBox in _TextBoxes)
textBox.CssClass = "value";
}

From a third party control development standpoint, I have written controls that would use this. In the previous post, I described how FindControl is inefficient. When my control is looking for other controls of a specific type, it could search a much shorter list instead of using FindControl.

The truth is that the calls to Page_ControlAdded will have to process as many controls as the recursive search code shown earlier. But you only have a single search that collects controls into all lists once and the consumers of those lists will have much less to search. When my DateTextBox control is searching for a companion DateTextBox, I’m looking through a list reduced to just a few DateTextBoxes.

Does it need to be said that providing an OnControlRemoved event on Page is needed too?

 

This is part of a series of posts offering ideas to improve ASP.NET.

I’m not a big fan of the FindControl() method (found on System.Web.UI.Control) for these reasons:

  • To prepare for the search, it needs to build a sorted collection of all controls found in the NamingContainer for which its invoked. Fortunately, this happens once per NamingContainer. Unfortunately, this work has to occur with each page request.
  • It looks through the list of all available controls in the naming container. Many controls contain their own child controls which are never assigned IDs by the user. So why would you want to search through those child controls? There should be a way to narrow the search.
  • And the usual: it only searches within one NamingContainer (except when you use the $ syntax, but that’s a special case.)

You may not think you are using FindControl, but each web control that has a property taking an ID, like RangeValidator.ControlToValidate, uses it to retrieve the actual control instance. In my opinion, the user is better served by avoiding FindControl and attaching the control instance directly. I do this throughout the controls of Peter’s Data Entry Suite. Let’s look at how my validator gets the ID to your textbox.


public class BaseValidator : WebControl
{
public string ControlIDToEvaluate { get; set; }
public Control ControlToEvaluate { get; set; }
}

This allows two ways to attach the TextBox to the validator. Users would work with the ASP.NET markup must use ControlIDToEvaluate (and FindControl), but those who are willing to write code can avoid FindControl.


<asp:TextBox id="TextBox1" runat="server" />
<des:RangeValidator id="RangeValidator1" runat="server" />

 

RangeValidator1.ControlToEvaluate = TextBox1;

Keep in mind that ASP.NET already has references to controls found on the page (in this case, RangeValidator1 and TextBox1). The difference is significant in terms of how much CPU time is spent. One assignment as opposed to a search that must prepare a sorted collection before it runs.

I would like to see ASP.NET web controls to offer the same capabilities.

I have been heavily invested in ASP.NET Dynamic Data since pre Beta 1 of ASP.NET 3.5 SP1. To me, this technology is significant and clever, although with its flaws. (My products DES Dynamic Data and Versatile DataSources are intended to address those flaws.)

My impression is that there is a poor adoption rate for ASP.NET Dynamic Data, and I want to better understand why. Please take a moment and post a comment on these questions.

1. Have you used ASP.NET Dynamic Data?

2. What do you like about it?

3. What do you dislike about it?

4. If you have evaluated it but not used it, why?

Your answers may help both the ASP.NET team and myself as we attempt to improve both the technology and user education.

Finally I’ve built code using Generics. Sure Generics has been around since .net 2.0. But my thing has been to code my commercial products. Peter’s Data Entry Suite had its origins in ASP.NET 1.0 and still can be compiled for ASP.NET 1.x customers. (There still are a few.) The new Versatile DataSources project allowed me to work with Generics and I had a blast. Yet I ran into some challenges that I’m sure everyone runs into. I figured I’d write about how I solved them.

Testing type compatibility (polymorphism)

The usual way to determine if an object is a specific type is through this syntax in C#:

if (objectinstance is type)

The “is” keyword does all of the work. Unfortunately this doesn’t work with generic types when you want to compare to the unbounded type. The unbounded type is the initial class declaration, like class MyClass<T>, where T has yet to be assigned. Its legal to test

if (myEntity is BaseEntity<Product>)

But if you want to test for BaseEntity<> without regard to Product, expect a compiler error:

if (myEntity is BaseEntity<>) // Illegal!

I found the solution here: http://stackoverflow.com/questions/457676/c-reflection-check-if-a-class-is-derived-from-a-generic-class. I used the answer posted on May 22.

It employs the Extension method concept, extending the System.Type class to offer HasGenericDefinition().

Use it like this:


Type objecttype = objectinstance.GetType();
if (objecttype.HasGenericDefinition(typeof(type<>)))

For example, to see if a MethodInfo object (describes a method using Reflection) has the return type of IEnumerable<>:

if (methodInfo.ReturnType.HasGenericDefinition(typeof(IEnumerable<>))

Also take from this example that the typeof() operator can be passed an unbounded type.

I would like to see something like this built into the .net framework and the C# language.

Typecasting to an unbounded type

Here’s some code that has a lot of problems.


public void TypeCasting(IEnumerable<> list) // illegal!
{
IEnumerable<> newlist = (IEnumerable<>) list.Clone();// illegal!
}

You cannot use unbounded types in all 3 locations shown above. If you want to share a type without regards to the bounded type, use a separate interface that does not use generics. The .net framework does this frequently: IEnumerable<> has IEnumerable; IQueryable<> has IQueryable.

Your own classes should define a non-generic interface too. For example:


public class MyGenericType<T> : IMyGenericType
{
}

The members of your generic type that need to be available to other consumers that are not interested in the bounded type should be declared in the interface.

For example:


public interface IMyGenericType
{
string Value { get; set; }
}
public class MyGenericType<T>:IMyGenericType
{
public string Value { get; set; }
}

Finally, the consumer method for MyGenericType would look like this:


public string UpdateText(IMyGenericType genType)
{
IMyGenericType newGenType = (IMyGenericType) genType.Clone(); // assumes Clone() is implemented
newGenType.Value = "Updated";
return newGenType.Value;
}
 
David Ebbo also offers a new solution using the C# Dynamic Type feature coming in .net 4.0. See http://blogs.msdn.com/davidebb/archive/2009/10/23/using-c-dynamic-to-call-static-members.aspx.
 

Instantiate objects through reflection

Let’s suppose you want to create an object of type List<T>, where T is determined at runtime. For example, the user passes in an object type to your function that creates the object.


public void CreateYourTypeAsList(Type myType)
{
}

 

It is illegal to pass a Type object to define the bounds on the generic type.

List<myType> list = new List<myType>(); // illegal!

You need to use Reflection to create your instance. Here’s the code:


public IList CreateYourTypeAsList(Type myType)
{
Type vGeneric = typeof(List<>);
Type vBounded = vGeneric.MakeGenericType(new Type[] { myType });
System.Reflection.ConstructorInfo vConstructor = vBounded.GetConstructor(new Type[] {});
return (IList) vConstructor.Invoke(new object[] { });
}

 This is how it works.

  1. Use the MakeGenericType() method, on System.Type, to create a bounded type. The parameter list takes the elements that normally go in the <> symbol. For List<T>, there is one parameter. For Dictionary<Key,Value>, there are two parameters.
  2. Use Reflection to get the constructor you will use to create this object. The System.Type.GetConstructor() method takes a list of types that exactly match the types of the desired constructor. In the above example, there are no parameters, leaving the Type[] array empty.
  3. Invoke the constructor, passing in values for its parameters.

Personally, I find this approach excessive and hope the C# language team gives us a simpler approach.

More Posts Next page »