Index to this series of articles

Business logic is applied to your Entity classes (objects that describe individual tables where columns are the properties) through metadata. Typically this metadata is defined by applying attributes from the System.ComponentModel.DataAnnotations namespace.

To successfully let business logic drive the user interface, you need an extensive library of business rules. Here are the additional business rules available as attributes. In my “Peter’s Soapbox”, you will find attributes that I feel need to be provided to complete the library.

  • DefaultValueAttribute – Supplies the initial value for the column when creating a new record. Generally this value is assigned to the user interface to establish a default. ASP.NET Dynamic Data supports it in the FieldValue property of FieldTemplateUserControl class. FieldValue is generally assigned to the data entry control when generating the HTML output. If in Insert mode, it uses the default. Otherwise it uses the value previously stored.
  • DisplayFormatAttribute – Column values that need to be displayed as strings use the DisplayFormatAttribute to assist in converting from its native type to the string.
  • DisplayColumnAttribute – Applied to the Entity class definition, it specifies which column to display in lists, such as those shown in filters or in foreign key links. For example, you may want to show the Last Name column in lists. By default, Dynamic Data uses the first column of type string that it finds. It also lets you change the default sorting, to specify a different column and sort order.
  • EditableAttribute - Introduced in .net 4, it's a bit like a security oriented attribute. It tells the client application whether a column can be made editable or not. It also tells the client application whether insert mode allows an entry. Typically its used to define a read only column and perhaps to express that the user can still enter a value in insert mode.
  • KeyAttribute – When your Entity class is not generated by Entity Framework or LINQ to SQL, use this attribute to identify columns that are primary keys. Primary keys have special behaviors in the user interface.

Peter’s Soapbox

Is that it? A handful of Validation attributes, 2 DataTypeAttributes, and the rest mentioned in the last few postings? I’ve already mentioned in those posts where I felt the attributes were lacking. Here’s some more.

Security roles attributes

Role-based security restrictions to tables and columns is industry standard stuff. Where’s the support?

My next release of Peter’s Data Entry Suite introduces the ColumnRestrictionAttribute and TableRestrictionAttribute. For example:

[DES.ColumnRestriction("Admin", DES.DenyAccess.None)]
[DES.ColumnRestriction("Sales", DES.DenyAccess.Insert | DES.DenyAccess.Edit)]
[DES.ColumnRestriction("Support", DES.DenyAccess.View)]
public object Photo { get; set; }

Dependency attributes

Often on field is required based on the state of another. For example, when the column “CustomerType” has a value of “Other”, the OtherDescription field should require a value. The user interface would enable and disable both the textbox and validator associated with the OtherDescription field based on the setting of the CustomerType DropDownList. It’s a pretty standard thing and not too difficult to create attributes to describe.

This is already supported in my Peter’s Data Entry Suite.

[DES.RequiredDependency(ColumnNames="PrinterType|FaxType",
MultipleMode=PeterBlum.DES.MultipleRequiredControlsMode.AtLeastOne)]
public object OutputHeading {get; set; }

Column is sortable attribute

The column headers of the ListView and GridView can usually be clicked to sort them. Another user interface would be to offer a dropdownlist of sortable fields. These user interfaces should be driven by the business logic through a SortableColumnAttribute. It not only determines if sorting is active, but also specifies the Sort Expression to apply.

Again, already supported in my Peter’s Data Entry Suite.

[DES.SortableColumn(false)]
public object Notes { get; set; }

Using the CategoryAttribute

The System.ComponentModel.CategoryAttribute can be used in creative ways. Take a look at my earlier posting on the subject: The CategoryAttribute and Dynamic Data.

Text entry attributes

Often the only difference between string column values is the pattern and character set permitted. Both can be used by textboxes to enhance entry. In addition, they are used by validators. Here’s how.

  • Pattern. Think of the “masked textbox” concept. Let’s suppose that you declare:
    [TextMask("999-999-9999")]
    public object SocialSecurityNumber { get; set; }

    The Text_Edit.ascx Field Template can use this to establish the MaskedEdit Extender control on it’s textbox. With a converter routine, it can also be converted into a regular expression that is applied to the RegularExpressionValidator in the Field Template. (Or the Field Template can create a MaskedEditValidator.)

    I think the TextMaskAttribute should be a subclass of DataTypeAttribute so there can be a standard Field Template called TextMask_Edit.ascx.

  • Character set. Many strings don’t have a pattern, but have a limited character set. For example, a person’s first name may be limited to upper and lower case letters. Again the Field Template establishes the necessary javascript to apply the character set, and sets up the RegularExpressionValidator to limit to those characters.
    [CharacterSet(Digits=true, Othercharacters="-")]
    public object SocialSecurityNumber { get; set; }

     

Injection attack attributes

Business logic determines what’s legal within strings. Hackers employ SQL Injection and Cross Site Scripting attacks through your web form’s inputs. String type fields should determine what HTML patterns and SQL statements are allowed or rejected.

Field Templates should use these attributes to generate a CustomValidator control that invokes your Injection detection code (server side only). What? You don’t have one? There’s one in my Peter’s Data Entry Suite called Peter’s Input Security.

Once again: “Is that all?”

I highly doubt that’s all of the business rules for entity objects. Feel free to describe your own cases in the comments.

 

 

Index to this series of articles

Index to this series of articles

Business logic is applied to your Entity classes (objects that describe individual tables where columns are the properties) through metadata. Typically this metadata is defined by applying attributes from the System.ComponentModel.DataAnnotations namespace.

Scaffolding is a term that describes letting the business logic determine the list of columns and tables displayed in your user interface. You see it in action when using Dynamic Data’s Page Templates. Yet, it is fine to explicitly define the list of columns in your user interface code, especially when you need flexibility in your layout from column to column.

The ScaffoldColumnAttribute and DisplayAttribute are both used to identify if a column is included or excluded. ScaffoldColumnAttribute was introduced first and has fewer capabilities. DisplayNameAttribute is new to .net 4, adding the Order and GroupName properties to the scaffolding rules.

[ScaffoldColumn(true)]
public object Picture { get; set; }

 

or

[Display(AutoGenerateField=true)]
public object Picture { get; set; }

 

You can read about my complaints of the DisplayAttribute’s design in the previous post. See the “Peter’s Soapbox” section.

When no attribute is supplied

When neither of these attributes are assigned, the Dynamic Data scaffolding engine determines if the field shows based on the following rules:

  • Included: A Field Template name is specified in the UIHintAttribute.
  • Excluded: MetaColumn.IsForeignKeyComponent = true (Part of a ForeignKeyColumn)
  • Excluded: MetaColumn.IsGenerated = true (The column’s data is generated by the database)
  • Included: MetaColumn.IsPrimaryKey = true (The column is a primary key)
  • Excluded: MetaColumn.IsCustomProperty = true (The column was created in the EntityTable class, but does not exist in the database)
  • Included: MetaColumnType is one of these: string, char, integer, floating point, bool, DateTime, TimeSpan, or DateTimeOffset.
  • Excluded: Anything not listed above.

Ordering the scaffolded columns

By default, Dynamic Data shows the columns in the same order they appear in the Entity class. If you want to change the order, use the DisplayAttribute. Its Order and GroupName properties are used by the scaffolding engine to sort the columns.

Scaffolding Tables

Like columns, Dynamic Data can provide a specific list of tables to show. Use the ScaffoldTableAttribute to exclude a table. When initializing Dynamic Data in Application_Start(), use the ScaffoldAllTables property to include all tables that are not set to [ScaffoldTableAttribute(false)].

DefaultModel.RegisterContext(typeof(NorthwindModel.Entities), 
new ContextConfiguration() { ScaffoldAllTables = true });

 

This attribute impacts two features of Dynamic Data:

  • The “entry point” web form, which is Default.aspx, initially, can show this table. It internally uses the MetaModel.VisibleTables property to get a list of tables. Be aware that this list is ordered by how the Tables were defined by the ModelProvider class to the MetaModel class. If you want to control the ordering yourself, create a list of MetaTables in the desired order instead of using MetaModel.VisibleTables.
  • Url Routing will prevent Urls that contain table names that are not scaffolded.

Scaffolding for Filters

List oriented interfaces often have filters to let the user modify the query used to generate the list. Your business logic can assist your user interface in building filters with these two attributes:

  • DisplayAttribute – Use the AutoGenerateFilter property to include or exclude the column from your filter interface.
  • FilterUIHintAttribute – Specifies an alternative Filter Template file (a violation of the separation of concerns).

ASP.NET Dynamic Data uses this with its QueryableFilterRepeater control to generate a user interface based on business logic.

When no DisplayAttribute is supplied

When the DisplayAttribute is not assigned, the Dynamic Data scaffolding engine determines if the filter shows based on the following rules:

  • Included: A Filter Template name is specified in the FilterUIHintAttribute.
  • Excluded: The column is not scaffolded (see the earlier scaffolding rules for fields)
  • Excluded: MetaColumn.IsCustomProperty = true (The column was created in the EntityTable class, but does not exist in the database)
  • Included: MetaColumnType is one of these: bool, enumerated type
  • Excluded: Anything not listed above.

 

Index to this series of articles

Index to this series of articles

Business logic is applied to your Entity classes (objects that describe individual tables where columns are the properties) through metadata. Typically this metadata is defined by applying attributes from the System.ComponentModel.DataAnnotations namespace.

Each column has a name taken from the database. Yet there are many ways to display text about the column which does not match the column’s name. The DisplayAttribute (introduced in ASP.NET 4) provides the following properties that are available to your user interface:

  • Name – The column’s name corrected to reflect language and formatting issues.
  • ShortName – A shorter version of the Name.
  • Description – A description that may appear in a hint, tooltip or help label.
  • Prompt – A description that works well as a prompt, such as in a wizard or the label preceding a textbox.

This attribute supports resource based localization.

[Display(Name="Category", Prompt="Enter the category name")]
public object CategoryName { get; set; }

If you are still using ASP.NET 3.5, you can define the Name and Description from the System.ComponentModel.DisplayNameAttribute and System.ComponentModel.DescriptionAttribute classes. The rest are not available.

Naming for tables

Like a column, each table has a name taken from the database. If you want to display the table name, it may need an alternative version that reflects language and formatting issues.

[DisplayName("Categories")]
public class Category
{
}

Peter’s Soapbox

I have mixed feelings about the DisplayAttribute introduced in .net 4. I feel it mixes too many concepts together and these concepts already had their own attributes.

  • Text – Already in DisplayNameAttribute and DescriptionAttribute. No support for ShortName and Prompt in the past though.
  • Scaffolding (the “AutogenerateField” property) – Allows business logic to dictate if the column appears. There already exists ScaffoldColumnAttribute, although this new version introduces the Order and GroupName properties to determine the column’s position.

    I think that the new properties should have been added to the ScaffoldColumnAttribute. I foresee are additional rules for scaffolding which should appear in the framework, such as defining a list of columns that match a specific category. The ScaffoldColumnAttribute makes sense for this kind of growth.

  • Filter scaffolding (the “AutoGenerateFilter” property). Its another aspect to scaffolding that impacts generating a list of filters in the QueryableFilterRepeater control. This probably belongs in the ScaffoldColumnAttribute so it can share the Order and GroupName properties.

    Again, there are probably other rules for scaffolding to come for filters. One is already defined in a separate attribute, FilterUIHintAttribute, which helps select the user interface used for filtering, such as setting up a range or a checkboxlist.

 

 

 

Index to this series of articles

Index to this series of articles

Business logic is applied to your Entity classes (objects that describe individual tables where columns are the properties) through metadata. Typically this metadata is defined by applying attributes from the System.ComponentModel.DataAnnotations namespace.

The DataTypeAttribute impacts the “data type” of a column. When defined in the database or a programming language, data types are integers, decimals, strings, etc. We use these to hold all kinds of real world types. Examples:

  • Integers: percentage, enumerated type, age
  • Decimals: currency, duration, distance, weight
  • Strings: phone number, postal code, email address, URL
  • DateTime: Date, time, Month/Year (credit card expiration), Birthday (day and month).

Each of these real-world data types have differences in their business rules. The user interface should respect these differences by using different data entry controls (like DateTextBox, Calendar, IntegerTextBox, etc) and validators.

Use the DataTypeAttribute to specify the real world type. Its parameter takes either a value from System.ComponentModel.DataAnnotations.DataType or a string. When the DataType enumerated list lacks the desired type, specify it as a string.

[DataType(DataType.Date)]
public object BirthDate { get; set; }
[DataType("Longitude")]
public object Longitude { get; set; }

 

Making DataTypeAttribute impact validation

Validation is a very important feature of business rules, and DataTypeAttribute is heavily involved. That’s why DataTypeAttribute is subclassed from System.ComponentModel.DataAnnotations.ValidationAttribute. Yet DataTypeAttribute’s implementation of the IsValid() method is to return true every time.

I recommend subclassing DataTypeAttribute for each data type where validation is needed and implementing the IsValid() method. For example:

public class DateAttribute : DataTypeAttribute
{
public override bool IsValid(object instance)
{
if (instance is DateTime)
return true;
if (instance is string)
{
DateTime vTemp;
return DateTime.TryParse((string)instance, out vTemp)
}
return false;
}
}
public class EmailAddressAttribute : DataTypeAttribute
{
public override bool IsValid(object instance)
{
if (instance is string)
{
return Regex.IsMatch((string) instance,
"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$");
}
return false;
}
}

How Dynamic Data uses the DataTypeAttribute

ASP.NET Dynamic Data uses the DataTypeAttribute to select a Field Template. Field Templates are User Control files that define the user interface for a specific data type in a specific situation. The situation includes the mode – read-only, edit, or insert – and other ways the user interface may change. Field Templates for edit and insert mode should create Validation web controls based on the business logic.

Here are some guidelines for creating Field Templates which respect DataTypeAttributes:

  • The file name of the Field Template should be the same as the value passed into the DataTypeAttribute’s parameter, plus “_Edit” or “_Insert” for the desired mode. For example, DataTypeAttribute(DataType.EmailAddress) should have a Field Template named “EmailAddress_Edit.ascx”.
  • The data entry control should be suitable for the data type. Generally you will use a TextBox for string-types, although its not a requirement.
  • When using a textbox, always define a Validator web control that will enforce the data type. Typically you will add the CompareValidator, RegularExpressionValidator, or CustomValidator.
    • The CompareValidator works for non-string types. Set its Operator property to the desired type. If the Operator doesn’t match the type you need, use one of the other two validators.
    • The RegularExpressionValidator works for string types that have a pattern. Your business logic may specify the RegularExpressionAttribute to deliver a regular expression to this validator, letting Dynamic Data automatically connect the two. As a result, there will be two attributes: DataTypeAttribute and RegularExpressionAttributes. It would be nice to combine these two.
    • The CustomValidator handles everything else.

The EnumDataTypeAttribute

The EnumDataTypeAttribute is a DataTypeAttribute subclass for one very common case: Enumerated types. It can be assigned to properties whose type is an enumerated type, a string, or integer.

The EnumDataTypeAttribute maps a list of strings to the value stored in the data. It gets those strings from the actual enumerated type definition. For example:

public enum Movement
{
Stop,
Slow,
Normal,
Fast,
Excessive
}

[EnumDataType(typeof(Movement))]
public int VehicleMovement { get; set; }

Dynamic Data provides the Enum.ascx and Enum_Edit.ascx Field Templates to display the list of strings from this attribute. In edit mode, it gives you a DropDownList.

Peter’s soapbox

While its easy to setup, I dislike supplying the list of strings from the enumerated type. Values in the type are usually not correct for the language and lack formatting. In my Peter’s Data Entry Suite, I created an EnumeratedAttribute to handle this. While it can be setup using the enumerated type, you can also define strings explicitly, including culture specific strings.

Index to this series of articles

Index to this series of articles

Business logic is applied to your Entity classes (objects that describe individual tables where columns are the properties) through metadata. Typically this metadata is defined by applying attributes from the System.ComponentModel.DataAnnotations namespace.

Validation attributes focus on evaluating data to report errors. The base class is System.ComponentModel.DataAnnotations.ValidationAttribute. It defines the IsValid() method, which is always overriden to evalutate the data. It also supplies the ErrorMessage property, where you can describe the error reported. (I have complaints about using the error message here. See Where does this go? Applying SoC to dynamic data – Part 2.)

The namespace defines the following attributes to validate your data.

  • RequiredAttribute – Marks the field as required. If your database already defines the column as “not nullable”, Dynamic Data knows that the field is required. Yet, since your Entity classes may be used outside of Dynamic Data, always consider assigning this.
    [Required()]
    public object Name { get; set; }

     

    Concerns

    This attribute is limited to testing text returned from the browser and is generally mapped to the RequiredFieldValidator web control.

    It is further limited to considering only the empty string is invalid, whereas you can use the InitialValue property on RequiredFieldValidator to recognize another case (a watermark in the textbox).

  • RegularExpressionAttribute – Evaluates text against a regular expression. Dynamic Data maps this attribute to the RegularExpressionValidator web control.
    [RegularExpression("^\d{5}$")]
    public object ZipCode { get; set; }

     

    Concerns

    Like with the RegularExpressionValidator, this attribute has no way to set the “ignore case” or “multiline” options, which are useful in developing regular expressions.

    When this expression is used with server side validation, it will use the .net framework’s regular expression engine. When it is used within the browser, it uses javascript’s regular expression engine. Javascript has a simpler syntax. Take care to define your expressions with the Javascript syntax when using Dynamic Data. See Javascript RegExp class guide.

  • StringLengthAttribute – Evaluates the length of text to confirm it has not exceeded a maximum value. If your database already defines the length on a textual column, Dynamic Data has access to that value. Yet, since your Entity classes may be used outside of Dynamic Data, always consider assigning this.
    [StringLength(10)]
    public object SocialSecurityNumber { get; set; }

     

    Concerns

    None of the Field Templates actually include a validator web control for this attribute. Those that have single-line textboxes (like Text_Edit.ascx) set their TextBox control’s MaxLength property. If you are using a multiline textbox, you will need to introduce a validator. I recommend using the RegularExpressionValidator with its expression programmatically set in the Field Template’s Page_Load method to a pattern like this:

    ^[\s\S]{0,#}$

    where # is replaced by the value from the Field Template’s Column.MaxLength property. Do NOT attach your validator to the FieldTemplateUserControl’s class by using the SetupValidator() method as that try to associate it with the RegularExpressionAttribute. It will either have its expression updated or be disabled.

    Instead, I recommend creating a method in the Field Template that sets it based on the StringLengthAttribute and if not found, sets the Validator’s Enabled property to false.

  • RangeAttribute – Confirms that the value is between a low and high value of a range. Dynamic Data maps this attribute to the RangeValidator web control.

    If you only want to compare greater than or equal to a value, just set the low (the Minimum property). Similarly set Maximum to compare less than or equal to the high value.

    [Range(0, 100)]
    public object Percentage{ get; set; }

     

  • CustomValidationAttribute – (requires ASP.NET 4). Lets you add a method to your Entity class which will be invoked to validate the column in some way. It can also evaluate multiple columns of an entity. The CustomValidationAttribute only validates on the server side. Dynamic Data uses its DynamicValidator web control to report errors.

    See my earlier posting, “The CustomValidationAttribute”.

While Dynamic Data installs validator web controls into the user interface, your Entity class should never assume that validation was handled elsewhere. It should always run a final validation against the validation rules that you have created. Use the System.ComponentModel.DataAnnotations.Validator class to run that validation. Typically you will call its TryValidateObject() method. It throws an exception for the first error

In this example, the Products table has a method called OnValidate() which invokes it. (OnValidate() is defined in Linq to SQL Entities automatically, but the call to TryValidateObject is not.)

public class Product
{
public void OnValidate(System.Data.Linq.ChangeAction pAction)
{
List<ValidationResult> vErrors = new List<ValidationResult>();
Validator.TryValidateObject(this,
new ValidatorContext(this, null, null), vErrors);
if (vErrors.Count > 0)
throw new Exception(vErrors[0].ErrorMessage)
}
}


Peter’s Soapbox

As many of you know, I have been developing an enhanced validation framework for ASP.NET for years. I’ve found and fixed numerous limitations. Not surprisingly, I find that many of my concerns remain unresolved within the Validation Attributes. I have worked to fix all of the complaints listed in this posting in my Peter’s Data Entry Suite. You’ll learn more here.

Field Templates cannot setup “compare two field” validators

One of the most common validation rules is not available as a ValidationAttribute: comparing two fields. For example, BirthDate < HireDate. This expression is easily represented with the CompareValidator in the web form level:

<asp:CompareValidator id="CompareBirthToHire" runat="server" 
ControlToValidate="BirthDateTextBox" ControlToCompare="HireDate"
Operator="LessThan" Type="Date" />

Field Templates are supposed to automatically setup Validator web controls like this based on the ValidationAttributes. I’d like to see something like:

[CompareColumn(OtherColumn="HireDate", Operator="LessThan")]
public DateTime BirthDate { get; set; }

 

Without this, you can still use the CustomValidatorAttribute on the class definition, but it is strictly server side. Field Templates do not manage a validator web control associated with it.

There are a few issues to fixing this problem, including:

  • Property-level validation only passes in the value of the property itself, not the overall object, to the IsValid() property. Therefore IsValid cannot see the second property’s value. Yet this is fixable because IsValid actually can be passed a second value, the entire instance, in the ValidationContext property. This issue requires a change to the DynamicValidator class. More importantly, if the goal is to convert the CompareColumnAttribute into a CompareValidator web control, we really don’t care about the IsValid method.
  • The two columns are converted into HTML using separate Field Templates. Each is a User Control, which means it uses a separate Naming Container. As a rule, properties on web controls take an ID to controls in the same naming container. So the CompareValidator cannot locate the two textboxes. My own Validator classes allow attaching to the textbox object as an alternative. You can learn more about my philosophy here: Improving ASP.NET: Finding Controls.
There are many more common validation rules

My validation suite includes around 25 Validator web controls. There are so many reusable ways to validate. In my Dynamic Data solution, I’ve created numerous ValidationAttributes.

Field Templates may not be coded to handle every ValidationAttribute

The Field Template must create a Validator web control for each ValidationAttribute declared on the column. Its easy to introduce a new ValidationAttribute on your business logic without it impacting the Field Template for which it was intended. For example, the default field template for strings, Text_Edit.ascx, lacks a validator for the StringLengthAttribute. Your Entity class should still catch errors prior to saving by using the System.ComponentModel.DataAnnotations.Validator class. But the Validator class is strictly server side.

In my solutions, I chose to abandon defining Validator web controls in the Field Template. Instead, the user drops in a new control called ColumnValidationManager. This control evaluates the list of ValidationAttributes on the column and creates all validators it needs. To work, the ValidationAttribute class must help out. My extensions to the ValidationAttribute class include methods to return an instance of the web control that will be used. If you do the same, be sure to also add the DynamicValidator in your validator generator code.

Here’s my version of Text_Edit.ascx Field Template:

<%@ 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" ></des:FilteredTextBox>
<desDD:ColumnValidatorManager ID="ColumnValidatorManager1" runat="server" />

 

 

 

 

Index to this series of articles

This is the index to a series of articles, to be published periodically, about the elements of ASP.NET Dynamic Data. It focuses on specific technologies, especially the web controls you use to build a Dynamic Data-based web form. As the author of Peter’s Data Entry Suite, I have identified limitations and gaps in Dynamic Data which I will also discuss. My commercial solution resolves many of those limitations and gaps.

  • Data Source controls to interact with your business logic – Discusses which DataSource web controls are best for good separation of concerns between UI and business logic.
  • Validation attributes for business logic. A look at the Validation attributes in System.ComponentModel.DataAnnotations.
  • DataTypeAttribute for business logic.
  • Textual attributes for business logic. The many ways to associate text with column and table elements.
  • Scaffolding attributes for business logic. What is scaffolding and how to manage it.
  • Other attributes for business logic. Includes my thoughts on what business logic attributes are missing.
  • Field Templates – Pending. The user interfaces for each data type.
  • DynamicControl and DynamicField – Pending. The mainstay web control for a user interface of a column in your business logic.
  • DynamicValidator control – Pending. Displays errors generated by your business logic.
  • Entity Templates – Pending. A user interface with webcontrols (including DynamicControls) and HTML to describe typical situation, such as a two column row in a detail record, an email entry, and a street address entry.
  • DataBound controls – Pending. ListView, FormView, GridView, and DetailsView are DataBound controls. Dynamic Data requires one as a container for any DynamicControl or DynamicField.
  • DynamicDataManager control – Pending. Connects Dynamic Data technology to the DataBound control.
  • DynamicFilter, FilterRepeater, and DynamicFilterRepeater – Pending. For building filtering user interfaces.
  • DataPager control – Pending. More than just for Dynamic Data, but central to managing list-based DataBound controls.
  • QueryExtender – Pending. Connects DynamicFilterRepeater with the DataSource control.
  • Page Templates – Pending. Provides nearly instant applications completely driven by scaffolding and other business logic attributes.
  • DynamicHyperlink control – Pending. Delivers a hyperlink that defines its URL based on URL routing rules.
  • What’s missing – Pending. Other elements that I’ve implemented to make Dynamic Data more robust.

As I code my commercial Dynamic Data libraries for ASP.NET 4 support, I’ve elected to deliver one assembly compiled under .net 3.5 SP 1 that also supports new features of ASP.NET 4. 3.5SP1 is the initial release of Dynamic Data. To interact with the new properties and methods in ASP.NET 4, I am using .net Reflection.

I have long used .net reflection, so I didn’t think there were many more things to learn. Today I encountered a new case, a method that has an out parameter.

Here’s the method I wanted to call. It's on the class System.Web.DynamicData.DynamicDataExtensions:

public static bool TryGetMetaTable(this INamingContainer control, out MetaTable table);
To call a function using .reflection, you take these actions:
  1. Call a GetMethod(“methodname”) method on the specific type.
  2. Call the Invoke method on the MethodInfo object that was returned by GetMethod.

If this function did not have an out parameter, the code would look like this:

Type[] vTypes = new Type[] { typeof(INamingContainer), typeof(MetaTable) };
MethodInfo vTGMTMI = typeof(DynamicDataExtensions).GetMethod("TryGetMetaTable", BindingFlags.Public | BindingFlags.Static,
null, vTypes, null);
if (vTGMTMI == null)
throw new NotImplementedException("Must use ASP.NET 4.0 or higher.");

object[] vParms = new object[] { GridView, null };
bool vResult = (bool) vTGMTMI.Invoke(null, BindingFlags.InvokeMethod, null, vParms, null);
if (vResult)
// metatable returned is in vParms[1]

The GetMethod() method gets more complex with that out parameter. You must pass the output parameter TYPE as a reference to the intended type.
 
Approach 1 - Using Type.MakeByRefType
As pointed out in the comments, the Type class has the tools needed.  Use the method MakeByRefType() like this:
Type[] vTypes = new Type[] { typeof(INamingContainer), typeof(MetaTable).MakeByRefType() };
 
Approach 2 - Using GetType("string")
Before learning of the MakeByRefType() method, I used this technique. I wanted to keep it in the post because I educates on how to use GetType("string") for an out parameter.

Pass the fully qualified type name into the Type.GetType(“string of the type”) method. The type within the string must be:
  • full name
  • end with “&” to indicate its a pointer

Here is the MetaTable class as a full qualified name, including type, &, and assembly name:

Type.GetType("System.Web.DynamicData.MetaTable&, System.Web.DynamicData, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")

Here’s the corrected code, allowing the MetaTable's type supply both full names.

Type vMetaTableP = Type.GetType(typeof(MetaTable).FullName + "&," + typeof(MetaTable).Assembly.FullName);
Type[] vTypes = new Type[] { typeof(INamingContainer), vMetaTableP };


The big trick is to avoid using typeof(MetaTable).AssemblyQualifiedName, because it omits the “&”.

Putting it all together

This is the complete code using my preferred solution, MakeByRefType().
Type[] vTypes = new Type[] { typeof(INamingContainer), typeof(MetaTable).MakeByRefType() };

MethodInfo vTGMTMI = typeof(DynamicDataExtensions).GetMethod("TryGetMetaTable", BindingFlags.Public | BindingFlags.Static,
null, vTypes, null);
if (vTGMTMI == null)
throw new NotImplementedException("Must use ASP.NET 4.0 or higher.");

object[] vParms = new object[] { GridView, null };
bool vResult = (bool) vTGMTMI.Invoke(null, BindingFlags.InvokeMethod, null, vParms, null);
if (vResult)
// metatable returned is in vParms[1]

Note that the GetMethod takes a parameter of type ParameterModifier. It certainly is involved with out parameters, but only when calling COM functions. In this case, the last parameter for GetMethod is null instead of a ParameterModifier array.

The System.ComponentModel.CategoryAttribute has long been around. It’s generally used for annotating properties on web controls so that design mode’s Property Editor can organize your properties.

I think the CategoryAttribute makes a lot of sense as business logic in your Entity classes. By categorizing properties, your user interface can get creative:

  • Automatic scaffolding can be generated based on matched categories. This requires writing a custom FieldGenerator class.
  • Field Templates can customize themselves. For example, the Text.ascx Field Template uses a default style sheet class on its <asp:Label> control. When the Category is set to “Title”, the Field Template can change the style sheet class to one that uses a different font.

The CategoryAttribute takes one value, a string. This is supposed to represent a single category.

[Category("Title")]
public object Name { get; set; }

I recommend allowing multiple categories in a pipe character delimited list:

[Category("Title|ProperName")]
public object Name { get; set; }

This allows much more flexibility.

The CategoryManager class

Add this class to your application. It checks your requested category names against those in the CategoryAttribute.

using System.Web.DynamicData;
using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Linq;
public static class CategoryManager
{
/// <summary>
/// Checks the MetaColumn's CategoryAttribute against the filtering rules to determine if there is a match.
/// </summary>
/// <remarks>
/// <para>Works as a Extender method on the MetaColumn class.</para>
/// </remarks>
/// <param name="pColumn">The MetaColumn that has a list of attributes. They are searched for the CategoryAttribute.</param>
/// <param name="pCategories">A pipe delimited list of Categories that must either be present or absent based
/// on pExcludeCategories.</param>
/// <param name="pExcludeCategories">When true, none of the category names in pCategories must be in the CategoryAttribute.</param>
/// <param name="pCategoriesWithNoMatchAreExcluded"> Determines what happens when
/// pCategories does not contain a matching name to the CategoryAttribute’s value,
/// including when the MetaColumn lacks a CategoryAttribute.</param>
/// <returns>When true, keep the column</returns>
public static bool CheckCategories(this MetaColumn pColumn, string pCategories, bool pExcludeCategories, bool pCategoriesWithNoMatchAreExcluded)
{
CategoryAttribute vCA = pColumn.Attributes.OfType<CategoryAttribute>().FirstOrDefault();
return CheckCategories(vCA, pCategories, pExcludeCategories, pCategoriesWithNoMatchAreExcluded);
} // CheckCategories
/// <summary>
/// Checks the CategoryAttribute against the filtering rules to determine if there is a match.
/// </summary>
/// <param name="pCategoryAttribute">The CategoryAttribute. Can be null.</param>
/// <param name="pCategories">A pipe delimited list of Categories that must either be present or absent based
/// on pExcludeCategories.</param>
/// <param name="pExcludeCategories">When true, none of the category names in pCategories must be in the CategoryAttribute.</param>
/// <param name="pCategoriesWithNoMatchAreExcluded"> Determines what happens when
/// pCategories does not contain a matching name to the CategoryAttribute’s value,
/// including when the CategoryAttribute is null.</param>
/// <returns>When true, the rules matched the CategoryAttribute.</returns>
public static bool CheckCategories(CategoryAttribute pCategoryAttribute, string pCategories, bool pExcludeCategories, bool pCategoriesWithNoMatchAreExcluded)
{
if (pCategoryAttribute == null)
return !pCategoriesWithNoMatchAreExcluded;
// Both CategoryAttribute.Value and pCategories are pipe delimited lists
// Convert CategoryAttribute.Value to a regex that can find one match in Categories.
string vPattern = cPipeDelimitedLeft + pCategories + cPipeDelimitedRight;
if (Regex.IsMatch(pCategoryAttribute.Category, vPattern, RegexOptions.IgnoreCase))
return !pExcludeCategories;
return pCategoriesWithNoMatchAreExcluded;
}
/// <summary>
/// When searching a pipe delimited list for a match, this regex should appear
/// first, before the string containing the list.
/// </summary>
const string cPipeDelimitedLeft = @"(^|[\|])(";
/// <summary>
/// When searching a pipe delimited list for a match, this regex should appear
/// last, after the string containing the list.
/// </summary>
const string cPipeDelimitedRight = @")($|[\|])";
}



Automatic Scaffolding using Categories

Here is a basic implementation of IAutoFieldGenerator with support for category attribute searches.

 

using System.Collections;
using System.Web.DynamicData;
using System.Web.UI;
using System.Web.UI.WebControls;

public class CategoryFieldsGenerator : c
{
private MetaTable _metaTable;

/// <summary>
/// The mode used by the caller: ReadOnly, Edit, Insert.
/// </summary>
public DataBoundControlMode Mode { get; set; }
/// <summary>
/// Is the interface a list or item?
/// </summary>
public ContainerType ContainerType { get; set; }

/// <summary>
/// When true, ignore the MetaColumn.IsScaffold property. Just use the CategoryAttribute
/// even if [ScaffoldColumnAttribute(false)].
/// When false, limit to those that are MetaColumn.IsScaffold = true
/// </summary>
public bool IgnoreScaffold { get; set; }


/// <summary>
/// A pipe delimited list of the categories either to include or exclude in the list of
/// DynamicFields returned.
/// </summary>
/// <value>
/// <para>Use a Pipe delimited list for multiple items; matching is case insensitive.</para>
/// </value>
public string Categories { get; set; }

/// <summary>
/// Determines what happens when the Categories property contains a matching name
/// to the CategoryAttribute’s value. Normally when the Category name is found
/// in the CategoryAttribute, that NamedStyle is a match. This property can make
/// that NamedStyle not match.
/// </summary>
/// <value>
/// <para>When true, exclude data fields with any of these category names.</para>
/// <para>When false, include data fields with any of these category names.</para>
/// </value>
public bool ExcludeCategories { get; set; }

/// <summary>
/// Determines what happens when the Categories property does not contain a matching name to the CategoryAttribute’s value,
/// including when the data field lacks a CategoryAttribute.
/// </summary>
/// <value>
/// <para>When true, the DynamicField is omitted.</para>
/// <para>When false, the DynamicField is included.</para>
/// </value>
public bool CategoriesWithNoMatchAreExcluded { get; set; }

public CategoryFieldsGenerator(MetaTable table, DataBoundControlMode mode, ContainerType containerType,
string categories, bool excludeCategories, bool categoriesWithNoMatchAreExcluded)
{
_metaTable = table;
Mode = mode;
ContainerType = containerType;
Categories = categories;
ExcludeCategories = excludeCategories;
CategoriesWithNoMatchAreExcluded = categoriesWithNoMatchAreExcluded;
}
public CategoryFieldsGenerator(MetaTable table, DataBoundControlMode mode, ContainerType containerType,
string categories) : this (table, mode, containerType, categories, false, true)
{
}

public ICollection GenerateFields(Control control)
{

List<DynamicField> fields = new List<DynamicField>();
IEnumerable<MetaColumn> columns = IgnoreScaffold ? _metaTable.Columns : _metaTable.GetScaffoldColumns(Mode, ContainerType);
foreach (MetaColumn column in columns)
{
if (CategoryManager.CheckCategories(column, Categories, ExcludeCategories, CategoriesWithNoMatchAreExcluded))
fields.Add(CreateField(column));
}

return fields;
}

protected virtual DynamicField CreateField(MetaColumn column)
{
DynamicField dynamicField = new DynamicField();
dynamicField.DataField = column.Name;
dynamicField.HeaderText = ContainerType == ContainerType.List ? column.ShortDisplayName : column.DisplayName;
return dynamicField;
}
}


In your web form’s Page_Init() method, create the CategoryFieldGenerator and assign it to the GridView.ColumnsGenerator or DetailsView.RowsGenerator. This example includes all columns that have the category “Small”, so long as they are also scaffoldable. (IgnoreScaffold property is false.)

table = LinqDataSource1.GetTable();
GridView1.ColumnsGenerator = new CategoryFieldGenerator(table, DataBoundControlMode.ReadOnly, ContainerType.List, "Small");


Changing the Style Sheets within Field Templates

In Page_Load, update the CssClass property on the desired controls based on the category. This example looks for “Title” and switches to the alternate style sheet.

<script runat="server">
   1:  

   2: public override Control DataControl { get { return FieldValueLabel; }}

   3: protected void Page_Load(object sender, EventArgs e)

   4: {

   5:     if (CategoryManager.CheckCategories("Title", false, false))

   6:         FieldValueLabel.CssClass = "TitleLabel";

   7:     else

   8:         FieldValueLabel.CssClass = "StndLabel";

   9: }
</script>
<asp:Label ID="FieldValueLabel" runat="server" ></asp:Label>

Peter’s Soapbox

I encourage the ASP.NET Dynamic Data team to include support for the CategoryAttribute (or some variation of it) in a future release. I have no problem with MS or anyone else using the code of this posting. I consider it public domain and does not require a license.

.net 4 introduces the CustomValidationAttribute, a member of System.ComponentModel.DataAnnotations that supports validation of your business logic. Until now, validation rules were limited based on the available attributes (required, stringlength, regex, and range) plus those you created by subclassing System.ComponentModel.DataAnnotations.ValidationAttribute. CustomValidationAttribute lets you define new validation rules directly on your Entity class (or the class of your choice) without having to subclass from ValidationAttribute.

Unfortunately at this time, the documentation on this attribute is limited. This posting is an attempt to document it better.

The CustomValidationAttribute can validate an individual property or the complete object (the Entity instance). This is an example using the Category table from Northwind DB and LINQ to SQL.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;

[MetadataType(typeof(CategoryMetadata))]
[CustomValidation(typeof(Category), "FinalCheck")]
public partial class Category
{
public class CategoryMetadata
{
[CustomValidation(typeof(Category), "TestCategoryName")]
public object CategoryName { get; set; }
}

// method defined by LINQ to SQL on every Entity class.
partial void OnValidate(System.Data.Linq.ChangeAction action)
{
List<ValidationResult> vErrors = new List<ValidationResult>();

// validate class-level attributes
// When the last parameter is true, it also evaluates all ValidationAttributes on this entity's properties
Validator.TryValidateObject(this, new ValidationContext(this, null, null), vErrors, true);

// handle errors
if (vErrors.Count > 0)
throw new ValidationException(vErrors[0], null, this);

}

public static ValidationResult FinalCheck(Category pCategory, ValidationContext pValidationContext)
{
if (pCategory.Description.Length < 10)
return new ValidationResult("Description must be at least 10 characters.", new List<string> { "Description" });
return ValidationResult.Success;
}

public static ValidationResult TestCategoryName(string pNewName, ValidationContext pValidationContext)
{
if (Regex.IsMatch(pNewName, @"^\d")) // cannot start with a digit
return new ValidationResult("Cannot start with a digit", new List<string> { "CategoryName" });
return ValidationResult.Success;
}
}

 

Understanding the CustomValidatorAttribute definition

  • It can be placed on the class or property definition. Both are shown above.
  • The class that contains your validation method must always be declared, even if the method is in the same class this attribute is assigned.
  • You may want to create a separate class for all validation rules of your entity, such as “class CategoryValidation”.
  • The method name property must be a case sensitive match your method name.

Understanding the method definition

  • The method itself must be static and public.
  • The first parameter is the value to evaluate.
    • When the method is associated with the class, it will be passed an instance of the Entity. It can be typed to match the class of the Entity, such as it is with “Category” above. It can also be typed to “object”.
    • When the method is associated with a property, it will be passed the value of the property. It can be typed to match the type of the property, such as it is with “string” above. It can also be typed to “object”.
  • The second parameter, ValidationContext, describes other details about the activity. It’s more important when validating a property since it supplies the instance of the Entity (ObjectInstance property) and the property name (MemberName property). It’s also possible that this parameter will be null, but that generally occurs when using .net 3.5 libraries. So test for that.
  • Always return an instance of System.ComponentModel.DataAnnotations.ValidationResult. When there is no error, just return the static property ValidationResult.Success, as shown above. Otherwise return a description and optionally the property name(s) with errors.

Invoking Validation

Your CustomValidation methods are not called automatically (despite what the documentation presently says in the “Remarks” section). ASP.NET Dynamic Data knows to evaluate the property-based ValidationAttributes when there is a DynamicValidator control on the page. But since your Entity class may be reused, do not assume that Dynamic Data has done the job. Create a method on your Entity class to validate both class and property-level CustomValidationAttributes. Use System.ComponentModel.DataAnnotations.Validator.TryValidateObject() to do the job, as is shown above. Always pass true as the last parameter to insure the properties are validated. (If you pass false or omit the parameter, it will only validate the RequiredAttributes on properties.)

The OnValidate() method above is part of LINQ to SQL. Each Entity class has a partial method called OnValidate(). You create the other partial method in your code and LINQ to SQL will call it automatically. If you are using Entity Framework or another system, you must create and invoke your own “OnValidate()” method.

Communicating Errors to the User Interface

Your error messages from the ValidationResult class somehow have to be shown in the user interface. A method like OnValidate() has no direct way to pass up the list of ValidationResults. Instead, it throws a System.ComponentModel.DataAnnotations.ValidationException. (Unfortunately, it does not pass the list of ValidationResults in that exception class. You may want to build another Exception class to deliver them.)

The user interface should catch the exception and route the messages to its error display. When using ASP.NET with Dynamic Data, make sure you have a ValidationSummary and DynamicValidator control on the page, and Dynamic Data will take care of the rest. The Page Templates are setup this way.


<asp:ValidationSummary ID="ValidationSummary1" runat="server"
HeaderText="List of validation errors" />
<asp:DynamicValidator runat="server" ID="DetailsViewValidator"
ControlToValidate="FormView1" Display="None" />
<asp:FormView runat="server" ID="FormView1" >

 

Suppose you are creating a new Category and enter a name starting with a digit. Your validation rules will report an error like this:

clip_image002

A common question on the www.asp.net forums asks how to validate a CheckBoxList control. There are two cases:

  • Require at least one checkbox
  • Require a specific number of checkboxes

In the past, I’ve answered this question in a way that now breaks when the new ClientIDMode property is set to Predictable. (For more, see “The impact of ClientIDMode=Predictable”.)

The old way

Look at the HTML generated by the CheckBoxList control. It has a specific pattern where every <input type="checkbox"> tag has an ID based on the ClientID of the control.

<span id="CheckBoxList1">
<input type="checkbox" id="CheckBoxList1_0" >
<input type="checkbox" id="CheckBoxList1_1">
<input type="checkbox" id="CheckBoxList1_2">
<input type="checkbox" id="CheckBoxList1_3">
</span>

 

I recommended using a CustomValidator. The server side code is easy.

protected void RequiredCBL(object source, ServerValidateEventArgs args)
{
int count = 0;
CustomValidator val = (CustomValidator) source;
CheckBoxList cbl = (CheckBoxList) val.FindControl(val.ControlToValidate);
foreach (ListItem item in cbl.Items)
if (item.Selected)
count++;
args.IsValid = (count > 0);
}

The client-side code I used to recommend looked like this:

<script type='text/javascript'>

function RequiredCBL(sender, args)
{
var count = 0;
for (var i = 0; true; i++)
{
var listitem = document.getElementById(sender.controltovalidate + "_" + i.toString());
if (!listitem)
break; //!done
if (listitem.checked)
count++;
}
args.IsValid = count > 0;
}

</script>

ClientIDMode=Predictable can change the id= attributes of the <input type='checkbox'> elements by appending “_row#” when the control is inside ListView and FormView.


<span id="CheckBoxList1">
<input type="checkbox" id="CheckBoxList1_0_0" >
<input type="checkbox" id="CheckBoxList1_1_0">
<input type="checkbox" id="CheckBoxList1_2_0">
<input type="checkbox" id="CheckBoxList1_3_0">
</span>

Unfortunately, you cannot use the previous client-side script as this breaks:

var listitem = document.getElementById(sender.controltovalidate + "_" + i.toString());

and you cannot just do this:

var listitem = document.getElementById(sender.controltovalidate + "_" + i.toString() + "_0");

because the additional component depends on the row number.

The new way

Microsoft’s web validation code has to handle this for RadioButtonLists. It does so by searching the child HTML tree of the CheckBoxList. Let’s use that technique, but my implementation creates an array of DHTML input elements once to be reused.

<script type='text/javascript'>


function RequiredCBL(sender, args)
{
var count = 0;
var checkboxes = GetBtnList(document.getElementById(sender.controltovalidate));
for (var i = 0; i < checkboxes.length; i++)
{
if (checkboxes[i].checked)
count++;
}
args.IsValid = count > 0;
}
// Returns a list of input elements contained within a CheckBoxList or RadioButtonList control's HTML
// The first call creates that list through a search of the child node tree.
// The result is an array of input elements in the order they were found.
function GetBtnList(pFld)
{
function FindChildren(array, parent)
{
for (var vI = 0; vI < parent.childNodes.length; vI++)
{
var vC = parent.childNodes[vI];
if (vC.tagName == "INPUT")
array[array.length] = vC;
else
FindChildren(array, vC); // RECURSIVE
} // for
}

var vCB = pFld.childbuttons;
if (!vCB)
{
vCB = new Array();
FindChildren(vCB, pFld);
pFld.childbuttons = vCB;
}
return vCB;
} // GetBtnList

</script>

More Posts Next page »