This series looks at the Business Logic Layer of an application from how my Peter’s Business Logic Driven UI (“BLD”) implements it. Use the “Understanding the BLL” Category to see all articles.

Validation is the most prominent business rule. I have been building validation objects for years with my Peter’s Data Entry Suite which is for ASP.NET Web Forms. BLD is actually a module of Peter’s Data Entry Suite, using my “DES Validation Framework” for the validation infrastructure of both the Business Logic and User Interface Layers. It was inspired by the work done by Microsoft for ASP.NET 3.5, which included the System.ComponentModel.DataAnnotation namespace and ASP.NET Dynamic Data. It was also inspired by my customers who wanted some way to define validation rules in the Business Logic Layer.

Given my 10+ years of experience around validation, I had a different level of expectations for what a “Validation DataAnnotation attribute” should do than what came with Microsoft’s System.ComponentModel.DataAnnotations.ValidationAttribute. So I built a better ValidationAttribute.

Here’s a key concept: the ValidationAttribute should not house the logic that evaluates some value and returns “Success” or “Failed”. Instead, you should have objects designed around that evaluation which can work in numerous places, not just in a business logic layer, such as a Range validator object, Compare to value validator object, and Regular expression validator object. The ValidationAttribute is merely a front end that knows how to create one of your validation objects and if needed, invoke its evaluation code in its Validate() method.

ValidatorObject

I’ll dig into how the Validator object is constructed later in this post.

ValidationAttributes are a thin layer around Validator objects

The ValidationAttribute is a thin layer that has the same properties found on the actual Validator object. It contains two vital methods: IsValid() and PrepareValidator().

IsValid() is defined by the base class and is used by the Business Logic Layer to validate.

PrepareValidator() creates a validator control for use by the UI Layer. The following interface should be added to expose PrepareValidator():

[C#]

public interface IPrepareValidatorForUI
{
   IValidator PrepareValidator(DataTypeDescriptor dataTypeDescriptor, ContextInfo contextInfo);
}

[VB]

Public Interface IPrepareValidatorForUI
   Function PrepareValidator(dataTypeDescriptor As DataTypeDescriptor, contextInfo As ContextInfo) As IValidator
End Interface
  • IValidator is an interface applied to all validator objects. It has methods for Validate(), GetErrorMessage(), GetSummaryErrorMessage() and these properties: IsValid, Enabled, HasValidated, and CultureInfo. BLD’s BaseValidatorAction class and each DES Validator web control implement it. Any validators you create using BLD for another UI Layer should implement it.
  • DataTypeDescriptor describes all kinds of details about a single DataField (property on the Entity class.) See “Descriptor objects : Exposing the structure and business logic to consumers”.
  • ContextInfo lets the UI layer identify itself so PrepareValidator() can create the correct class for the validator through a factory. Each UI Layer registers its validator classes with that factory so PrepareValidator() (which is in the BLL) never knows its dealing with a class defined in the UI Layer.

(In BLD, I have implemented this differently. The PrepareValidator() method and this interface simplify things to show the concept. If you are using BLD, explore the PeterBlum.DES.DataAnnotations.IDESValidationAttribute interface.)

Here is pseudocode for a RangeAttribute that uses the RangeValidator object defined elsewhere.

[C#]

using System.ComponentModel.DataAnnotations.ValidationAttribute;
public class RangeAttribute : ValidationAttribute, IPrepareValidatorForUI
{
   public string SummaryErrorMessage { get; set; } // base class defines ErrorMessage property
   public object MinimumAsNative { get; set; }
   public object MaximumAsNative { get; set; }
 
   public virtual IValidator PrepareValidator(BaseDataFieldDescriptor dataFieldDescriptor, ContextInfo contextInfo)
   {
      IRangeValidator vValidator = ValidatorFactory.Create<IRangeValidator>(contextInfo); 
      vValidator.ErrorMessage = this.ErrorMessage;
      vValidator.SummaryErrorMessage = this.SummaryErrorMessage;
      vValidator.MinimumAsNative = this.MinimumAsNative;
      vValidator.MaximumAsNative = this.MaximumAsNative;
      vValidator. TypeConverter = CreateTypeConverter(dataFieldDescriptor);
      return vValidator;
   }
   protected override ValidationResult IsValid(object value, ValidationContext validationContext)
   {
// caller is expected to have added DataFieldDescriptor and ContextInfo to validationContext.Items
      BaseDataFieldDescriptor vDataFieldDescriptor = GetDataFieldDescriptor(validationContext); 
      ContextInfo vContextInfo = GetContextInfo(validationContext);
      IValidator vValidator = PrepareValidator(vDataFieldDescriptor, vContextInfo);
      if (vValidator.Validate())
         return ValidationResult.Success;
      return new EntityValidationResult(this, vValidator.GetErrorMessage(true), 
         validationContext.ObjectType, validationContext.MemberName, value);
   }
}

[VB]

Imports System.ComponentModel.DataAnnotations.ValidationAttribute
Public Class RangeAttribute
   Inherits ValidationAttribute
   Implements IPrepareValidatorForUI
' base class defines ErrorMessage property
   Public Property SummaryErrorMessage() As String 
      ...
   End Property
   
   Public Property MinimumAsNative() As Object
      ...
   End Property
 
   Public Property MaximumAsNative() As Object
      ...
   End Property
 
   Public Overridable Function PrepareValidator(
         dataFieldDescriptor As BaseDataFieldDescriptor, contextInfo As ContextInfo) As IValidator
      Dim vValidator As IRangeValidator = ValidatorFactory.Create(Of IRangeValidator)(contextInfo)
      vValidator.ErrorMessage = Me.ErrorMessage
      vValidator.SummaryErrorMessage = Me.SummaryErrorMessage
      vValidator.MinimumAsNative = Me.MinimumAsNative
      vValidator.MaximumAsNative = Me.MaximumAsNative
      vValidator.TypeConverter = CreateTypeConverter(dataFieldDescriptor)
      Return vValidator
   End Function
   Protected Overrides Function IsValid(
         value As Object, validationContext As ValidationContext) As ValidationResult
      ' caller is expected to have added DataFieldDescriptor and ContextInfo to validationContext.Items
      Dim vDataFieldDescriptor As BaseDataFieldDescriptor = GetDataFieldDescriptor(validationContext)
      Dim vContextInfo As ContextInfo = GetContextInfo(validationContext)
      Dim vValidator As IValidator = PrepareValidator(vDataFieldDescriptor, vContextInfo)
      If vValidator.Validate() Then
         Return ValidationResult.Success
      End If
      Return New EntityValidationResult(Me, vValidator.GetErrorMessage(True), 
         validationContext.ObjectType, validationContext.MemberName, value)
   End Function
End Class 

Please keep in mind the above is not the real code in BLD, nor will it compile and work.

Let’s look at this more carefully.

  • The base class, System.ComponentModel.DataAnnotations.ValidationAttribute, defines ErrorMessage and localization support for it. The pseudocode doesn’t go into the details of localization, but assume the above code applies it.
  • All Validator objects should declare an interface unique to it that is applied to the validator control for each UI layer. In this case, I’ve used IRangeValidator. Assume it’s applied to the RangeValidator control defined in Web Forms, MVC, Silverlight, etc. The ValidatorFactory.Create<TValidator>() method uses that interface to ask a factory to create the appropriate validator control. Each new UI layer should create a set of supported Validator controls and register them with the factory. (BLD has done this for Web Forms and a UI neutral form.)
  • Many validators need to know the type of the data to be evaluated. They use a specialized Type Converter object to ensure the data passed is correct. The CreateTypeConverter() method handles this, getting the type of data from the DataFieldDescriptor object. The Type Converter is described later in this post.
  • The IsValid() method is declared on the ValidationAttribute class and must be overridden to supply the validation used by the Business Logic Layer. EntityValidationResult is a subclass of System.ComponentModel.DataAnnotations.ValidatonResult that contains the actual ValidationAttribute. With the ValidationAttribute passed back, the UI Layer has enough details to show ErrorMessage text next to the actual field with the error and the SummaryErrorMessage to the ValidationSummary control.

How a Validator object is constructed

When I designed the Validator web controls for Peter’s Data Entry Suite, I identified a number of unique classes that must come together to complete the control. The main elements of a validator do not involve the user interface and can be reused in many places. Here is a look at those classes:

ValidatorObject construction

Of these classes, the Condition is the most important and most versatile. So let’s define it first.

Condition classes

Condition classes evaluate something about your page and return “success”, “failed”, or “cannot evaluate”. They are the heart of validation. Each time you have a validation rule, you create a new Condition class. DES has over 30 of them.

Here is a very stripped down version of the base condition class.

[C#]

public class BaseCondition
{
   public bool Enabled { get; set; }
   public virtual bool CanEvaluate() { }
   public virtual int EvaluateCondition() {}
}

[VB]

Public Class BaseCondition
   Public Property Enabled As Boolean
   End Property
   Public Overridable Function CanEvaluate() As Boolean
   End Function
   Public Overridable Function EvaluateCondition () As Integer
   End Function
End Class  

A Condition is used for much more than just validation. It effectively lets you create “if then” logic. For example, “if CheckBox1 is checked, then validate.” This case is implemented by adding a new property to your Validator control which DES calls the “Enabler”. This property is of type Condition. Here’s DES’s RangeValidator web control in markup using the CheckStateCondition which handles “if CheckBox1 is checked”.

<des:RangeValidator id="RangeValidator1" runat="server" ErrorMessage="Must be between {MINIMUM} and {MAXIMUM}"
    Minimum="1" Maximum="10" DataType="Integer" ControlIDToEvaluate="TextBox1">
   <EnablerContainer>
      <des:CheckStateCondition ControlIDToEvaluate="CheckBox1" Checked="true" />
   </EnablerContainer>
</des:RangeValidator>

DES employs the Enabler property on several of its controls.

Another great usage of the Condition involves building a validator with complex boolean logic. Suppose you want to validate “TextBox1 has text AND TextBox2 has text”. This is really a boolean expression using the AND operator and two RequiredTextConditions. DES’s MultiConditionValidator lets you construct these boolean expressions. Here’s markup for the above logic.

<des:MultiConditionValidator id="MCV1" runat="server" Operator="And" ErrorMessage="Both must have text">
   <Conditions>
      <des:RequiredTextCondition ControlIDToEvaluate="TextBox1" />
      <des:RequiredTextCondition ControlIDToEvaluate="TextBox2" />
   </Conditions>
</des:MultiConditionValidator>

If you like, explore several more cases implemented in DES’s CalculationController and FieldStateController controls.

Type Converter classes

The .net framework defines the System.ComponentModel.TypeConverter class as a way to convert data between two forms, usually between string and a native type. This is very important to allow users to evaluate data that isn’t the expected (native) type. DES has declared its own implementation of this concept (based on PeterBlum.DES.DESTypeConverter) to provide a richer set of options for converting between strings and native types. For example, the CurrencyTypeConverter has properties to allow a currency symbol, thousands separator, and extra decimal digits.

The BaseCondition class can be assigned a DESTypeConverter object to its DESTypeConverter property. Once setup, any string you want the Condition to evaluate will be processed first by the DESTypeConverter. If conversion to the native type is successful, the evaluation continues. If it fails, most Conditions report back “cannot evaluate” to the caller.

ValidatorAction classes

The ValidatorAction class owns a Condition object and knows how to use it to validate.

It has the following features:

  • Error message properties. There are two cases: shown at the location of the Validator UI element and shown in a ValidationSummary list. Error messages can support tokens specific to the Validator class, such as “{MINIMUM}” and “{MAXIMUM}” previously shown on the RangeValidator.
  • Enabler property to use a Condition to enable the validator based on the Condition’s rule.
  • The Validate() method, IsValid property, and validation group name property. The Validate() function checks the validation group name passed into the function, the Condition’s CanEvaluate() method, and the Enabler condition before attempting to validate. To evaluate it calls the Condition’s EvaluateCondition() method and sets IsValid based on the result.

Here is a very stripped down version of the base ValidatorAction class.

[C#]

public class BaseValidatorAction
{
   protected IBaseCondition Condition { get; }
   public IBaseCondition Enabler { get; set; }
   public virtual bool CanDoAction() 
   { 
   // checks the Enabler and Condition.CanEvaluate()
   } 
 
   public string ErrorMessage { get; set; }
   public string SummaryErrorMessage { get; set; }
   public virtual string GetErrorMessage()
   {
   // replaces tokens on ErrorMessage and returns the result
   }
   public virtual string GetSummaryErrorMessage()
   {
   // replaces tokens on SummaryErrorMessage and returns the result
   }
 
   public string Group { get; set; }
   public bool IsValid { get; set; } // defaults to true
   public virtual bool Validate(string group)
   { // pseudocode
      IsValid = true;
      if ((group != Group) || !CanDoAction())
         return true;
      IsValid = Condition.EvaluateCondition() != 0;
      return IsValid;
   }
   public virtual bool Validate()
   { // pseudocode
      IsValid = true;
      if (!CanDoAction())
         return true;
      IsValid = Condition.EvaluateCondition() != 0;
      return IsValid;
   }
}

[VB]

Public Class BaseValidatorAction
   Protected Property Condition As IBaseCondition
   End Property
   Public Property Enabler As IBaseCondition
   End Property
   Public Overridable Function CanDoAction() As Boolean
   ' checks the Enabler and Condition.CanEvaluate()
   End Function 
 
   Public Property ErrorMessage As String
   End Property
   Public Property SummaryErrorMessage As String
   End Property
   Public Overridable Function GetErrorMessage() As String
   ' replaces tokens on ErrorMessage and returns the result
   End Function
   Public Overridable Function GetSummaryErrorMessage() As String
   ' replaces tokens on SummaryErrorMessage and returns the result
   End Function
 
   Public Property Group As String
   End Property
   Public Property IsValid As Boolean ' defaults to true
   End Property
   Public Overridable Function Validate(group As String) As Boolean ' pseudocode
      IsValid = True
      If (group <> Group) Or Not CanDoAction() Then
         Return True
      End If
      IsValid = Condition.EvaluateCondition() <> 0
      Return IsValid
   End Function
   Public Overridable Function Validate(group As String) As Boolean ' pseudocode
      IsValid = True
      If Not CanDoAction() Then
         Return True
      End If
      IsValid = Condition.EvaluateCondition() <> 0
      Return IsValid
   End Function
End Class 
ErrorFormatter classes

Now we get into the UI layer which means these classes must be recreated for each UI framework (BLD already handles Web Forms). The job of the UI layer validator control is to invoke the ValidatorAction object’s Validate() method and create whatever presentation is needed for communicating the error at the location of the control. There are many ways to present an error message. The native ASP.NET Web Forms validator controls do it with a <span> tag containing the error message. This inserts text into the page. You might prefer a popup element, a tooltip, or an alert to show the error.

The ErrorFormatter class defines each way to present the error message to the user. The user can add a Validator control and select the desired UI in the ErrorFormatter property.

For example:

<des:RequiredTextValidator id="RTV" runat="server" ControlIDToEvaluate="TextBox1" ErrorMessage="Required" >
   <ErrorFormatterContainer>
      <des:PopupErrorFormatter />
   </ErrorFormatterContainer>
</des:RequiredTextValidator>

You can check out the 5 ErrorFormatter classes included with DES here.

Validator control class

Finally we get to the object actually added to the UI layer. It’s really a lightweight object exposing all of the properties and methods on Validator, Condition, and TypeConverter objects that describe the validation rule.

This is pseudocode for the base Validator web control class:

[C#]

public class BaseValidator : WebControl
{
   protected BaseValidatorAction ValidatorAction
   {
      get
      {
         if (fValidatorAction == null)
            fValidatorAction = CreateValidatorAction();
         return fValidatorAction;
      }
   }
   private BaseValidatorAction fValidatorAction;
   protected abstract BaseValidatorAction CreateValidatorAction();
 
   public BaseErrorFormatter ErrorFormatter
   {
      get { return fErrorFormatter; }
      set { fErrorFormatter = value; }
   }
   private BaseErrorFormatter fErrorFormatter;
 
   public bool IsValid
   {
      get { return ValidatorAction.IsValid; }
   }
   public bool Validate(string group)
   { return ValidatorAction.Validate(group); }
 
   public string ErrorMessage
   {
      get { return ValidatorAction.ErrorMessage; }
      set { ValidatorAction.ErrorMessage = value; }
   }
   public string SummaryErrorMessage
   {
      get { return ValidatorAction.SummaryErrorMessage; }
      set { ValidatorAction.SummaryErrorMessage = value; }
   }
   
   public bool Enabled
   {
      get { return ValidatorAction.Condition.Enabled; }
      set { ValidatorAction.Condition.Enabled = value; }
   }
}

[VB]

Public Class BaseValidator
   Inherits WebControl
   Protected ReadOnly Property ValidatorAction() As BaseValidatorAction
      Get
         If fValidatorAction Is Nothing Then
            fValidatorAction = CreateValidatorAction()
         End If
         Return fValidatorAction
      End Get
   End Property
   Private fValidatorAction As BaseValidatorAction
   Protected MustOverride Function CreateValidatorAction() As BaseValidatorAction
 
   Public Property ErrorFormatter As BaseErrorFormatter
      Get
         Return fErrorFormatter
      End Get
      Set
         fErrorFormatter = value
      End Set
   End Property
   Private fErrorFormatter As BaseErrorFormatter
 
   Public ReadOnly Property IsValid() As Boolean
      Get
         Return ValidatorAction.IsValid
      End Get
   End Property
   Public Function Validate(group As String) As Boolean
      Return ValidatorAction.Validate(group)
   End Function
 
   Public Property ErrorMessage() As String
      Get
         Return ValidatorAction.ErrorMessage
      End Get
      Set
         ValidatorAction.ErrorMessage = value
      End Set
   End Property
   Public Property SummaryErrorMessage() As String
      Get
         Return ValidatorAction.SummaryErrorMessage
      End Get
      Set
         ValidatorAction.SummaryErrorMessage = value
      End Set
   End Property
 
   Public Property Enabled() As Boolean
      Get
         Return ValidatorAction.Condition.Enabled
      End Get
      Set
         ValidatorAction.Condition.Enabled = value
      End Set
   End Property
End Class

This series looks at the Business Logic Layer of an application from how my Peter’s Business Logic Driven UI (“BLD”) implements it. Use the “Understanding the BLL” Category to see all articles.

It’s time to dive into a big subject: the business rules on individual DataFields (properties defined in the Entity class). Business rules include validation, declaring the real-world data type, and so much more as shown in the chart below. Like any good object oriented implementation, business rules are described using objects. Microsoft introduced the approach: DataAnnotation attributes for its ASP.NET Dynamic Data, Silverlight, and MVC offerings.

To review, Attributes apply metadata to properties and classes. They are created by subclassing System.Attribute. They are used by adding a language specific syntax above the declaration of a class, property, method, etc. In the case of applying business rules to DataFields, these Attributes are applied to properties within the Entity class and Entity metadata class.

In this example, you see the RequiredAttribute, StringLengthAttribute, and CustomValidationAttribute, all supplied in the .net framework’s System.ComponentModel.DataAnnotations namespace.

[C#]

public class CategoryMetadata
{
   [Required()]
   [StringLength(30)]
   [CustomValidation(MethodName="CheckForDuplicateCategoryNames")]
   public object CategoryName { get; set; }
}

[VB]

Public Class CategoryMetadata
   <Required()> _
   <StringLength(30)> _
  <CustomValidation(MethodName := "CheckForDuplicateCategoryNames")> _
  Public Property CategoryName As Object
    Get
      Return Nothing
    End Get
    Set
    End Set
  End Property
End Class

Despite being shown here as metadata, business rules really don’t care where they come from. It’s perfectly fine to create the RequiredAttribute programmatically instead of attaching it to a property. BLD provides a mechanism to do this through its DataFieldDescriptor objects which host all business rules. As DataFieldDescriptors are created, they either get the DataAnnotation attributes from metadata or your event handler which creates them programmatically. DataAnnotation attributes can also be created as the situation demands. See “Customizing Business Rules at runtime”.

Let’s boil it down to this:

  • DataAnnotation attributes are objects which host values for business rules.
  • You can create them.
  • Microsoft has created a few of them for you.

BLD has also created some for you. Let’s introduce you to the many predefined business rules you have when using BLD.

Validation rules – When you think of business logic, validation comes to mind. The .net framework gets us started with a standard way to create validation rules, by subclassing System.ComponentModel.DataAnnotations.ValidationAttribute.

Due to DES’s very powerful Validation Framework, BLD introduces richer versions of those supplied in .net, and adds many more. See “Understanding ValidationAttributes”.

Even though they supply validation, ValidationAttributes can offer other features. For example:

  • Use the DESDA.InjectionSecurityAttribute to determine illegal content supplied by hackers, log it, and block further access to the page. (Normally validation only reports back to the user an issue.)
  • Use the DESDA.CharacterSetAttribute not only to define the character set allowed by a string-type field, but also to inform the TextBox control how to create keyboard filtering rules to match.

Data types – Each property has a type, such as string, integer or boolean, that mimics the type from the Column in the table (nvarchar, integer). That type is often not specific enough for developing a user interface or validation rules. For example, a double can be used as a currency, percentage, duration, longitude, etc. DataTypeAttributes let you redefine the type and provide more specific validation and formatting rules.

Once again, the .net framework gets us started with a standard way to describe a data type: the System.ComponentModel.DataAnnotations.DataTypeAttribute. BLD handles many new cases, like Measurement, DbImage, Duration, and Enumerated. These attributes have a rich set of tools to create your business rules. Most DataTypeAttributes have validation rules too. For example, the DESDA.EmailAddressAttribute creates a regular expression for the email address pattern.

See “Understanding DataTypeAttributes”.

Textual rules – Provide user friendly and localized names for fields, default strings for descriptions (such as used in a tooltip or hint) and for prompts with the DESDA.DisplayNameAttribute and DESDA.DescriptionAttribute.

Data value rules – Provide a default value for the DataField when creating a new record with the DefaultValueAttribute. Provide string formatting rules (to convert a date or number) with the DisplayFormatAttribute. (Both of these are from System.ComponentModel.DataAnnotations.)

Security rules – Associate restrictions based on user roles to Entity classes and DataFields by adding the DESDA.TableRestrictionAttribute and DESDA.ColumnRestrictionAttribute to your business logic. The user interface changes as a restricted feature is requested, whether to hide the feature or provide an alternative. (The DESDA.TableRestrictionAttribute is an example of a DataAnnotation attribute that goes on the Entity class itself, not a DataField. DESDA.DisplayNameAttribute and a few others can also provide business rules this way.)

Calculation rules – Use the DESDA.CalculationDescriptorAttribute to describe the underlying calculation on a property. The user interface can interpret it to create interactive calculations on screen.

Foreign key rules – Use the DESDA.ForeignKeyQueryAttribute to define filters and sort order on the lists that show elements of a foreign table. To avoid querying the same data every time, establish caching requirements with the DESDA.CacheListAttribute.

Dependency rules – Marks a DataField as dependant on another with a specific rule defined with a DependencyAttribute. Dependencies enable validation rules, such as a field is only required when another field has a value. The user interface can also use dependencies to change the appearance of, or hide, a field based on the dependency’s rules.

Sorting rules – Use the DESDA.SortableColumnAttribute to identify if a DataField can be sorted or not. The user interface uses this to establish its sorting interface, such as column titles that change sort order when clicked.

Scaffolding rules – “Scaffolding” allows a user interface to be populated with DataFields automatically, using the same layout for each DataField shown. The DESDA.ScaffoldColumnAttribute indicates if the DataField is shown when using Automatic Scaffolding and in what order to place it.

DataField categories – Use the System.ComponentModel.CategoryAttribute on DataFields to assign one or more category names with the DataField. BLD’s user interface controls support categories with Automatic Scaffolding and to select a custom CSS class or style for an element of its Template (called “Named Styles”).

Filtering rules – The DESDA.FilterAttribute determines if and when a DataField can be added to a filtering user interface. The DESDA.RangeEntityFilterPickerAttributes describe pick lists of values for numeric and date DataFields seen so often in sales websites (like pick a price range: $0-$19, $20-$49, $50-$99.) See “FilterAttributes”.

Even with over 50 DataAnnotation attributes defined between BLD and the .net framework, this doesn’t cover all cases. You should create anything you don’t find here.

In the next few topics, we’ll look more carefully at some of the attribute classes that you may want to subclass for expansion.

This series looks at the Business Logic Layer of an application from how my Peter’s Business Logic Driven UI (“BLD”) implements it. Use the “Understanding the BLL” Category to see all articles.

In the last article, you were introduced to Descriptor objects. Now you will see what I think is their most important feature: the ability to customize the business rules at runtime.

DataFieldDescriptor objects may describe a business rule and structure of a DataField, but are globally defined and must not be modified at runtime. BLD introduces the PeterBlum.DES.DataAnnotations.ActiveDataField class as a wrapper around the global DataFieldDescriptor. It exposes properties and methods that let you edit the business rules. For example, you can assign its DisplayName property to override the one in DataFieldDescriptor. If you don’t override it, DisplayName returns the localized name from the DataFieldDescriptor.

The Business Logic Layer (“BLL”) uses the PeterBlum.DES.DataAnnotations.ICustomizeDataField interface to let your BLL Entity class edit the ActiveDataField object. ICustomizeDataField adds a single method, CustomizeDataField(), which is passed the ActiveDataField object.

In the previous article, you saw how a DataFieldDescriptor for the CategoryName DataField was used to establish a disabled textbox and the text of a label. Let’s see how BLD lets you modify the IsReadOnly and DisplayName business rules of the ActiveDataField object.

[C#]

using DESDA=PeterBlum.DES.DataAnnotations;
public partial class Category : DESDA.ICustomizeDataField
{
   public void CustomizeDataField(DESDA.ActiveDataField activeDataField)
   {
      switch (activeDataField.DataField)
      {
         case "CategoryName":
            activeDataField.IsReadOnly = !CanUserEdit(); // left for you to write
            activeDataField.DisplayName = GetDisplayName(); // left for you to write
            break;
 
      }
   }
}

[VB]

Imports DESDA=PeterBlum.DES.DataAnnotations
Public Partial Class Category
  Implements DESDA.ICustomizeDataField
  Public Sub CustomizeDataField(activeDataField As DESDA.ActiveDataField) _
      Implements DESDA.ICustomizeDataField.CustomizeDataField
    Select Case activeDataField.DataField
      Case "CategoryName"
            activeDataField.IsReadOnly = Not CanUserEdit() ' left for you to write
            activeDataField.DisplayName = GetDisplayName()  ' left for you to write
        Exit Select
    End Select
  End Sub
End Class

Your code does not edit the actual DataFieldDescriptor, but it can access it through the ActiveDataField.GlobalDataFieldDescriptor property to help build the actual business rule.

Now let’s utilize this in your UI layer. BLD boxes anything specific to a single DataField in a Field Template object. The PeterBlum.DES.BLD.BaseFieldTemplate class from which all Field Templates are created makes a fully prepared ActiveDataField available as a property. Supposing the data editing control for this Field Template is a TextBox, you might write this code:

[C#]

((TextBox)DataControl).Enabled = !ActiveDataField.IsReadOnly;

[VB]

CType(DataControl, TextBox).Enabled = Not ActiveDataField.IsReadOnly

The ActiveDataField.DisplayName property is used by the Field Template for its validator error messages, when they contain the token “{LABEL}”. If you want the customized DisplayName to appear in a Label control, use the BLDLabel control (a subclass of the Label control) and set its AssociatedControlID property to the ID of the BLDDataField control (the host control that loads the Field Template). BLDLabel will get the ActiveDataField object through the BLDDataField.

Here’s roughly what the code of the BLDLabel control looks like as it assigns its Text property.

[C#]

Control vAssociatedControl = FindControl(AssociatedControlID);
if ((vAssociatedControl != null) && (vAssociatedControl is BLDDataField))
   this.Text = ((BLDDataField)vAssociatedControl).FieldTemplate.ActiveDataField.DisplayName;

[VB]

Dim vAssociatedControl As Control = FindControl(AssociatedControlID)
If (Not vAssociatedControl Is Nothing) And (vAssociatedControl is BLDDataField)) Then
   Me.Text = CType(vAssociatedControl, BLDDataField).FieldTemplate.ActiveDataField.DisplayName
End If

For more on this subject, see the topics in the BLD User’s Guide.

This series looks at the Business Logic Layer of an application from how my Peter’s Business Logic Driven UI (“BLD”) implements it. Use the “Understanding the BLL” Category to see all articles.

So far, we’ve learned about the Entity classes and how they maintain your business rules. Business rules are only useful if your code can retrieve them. Since most business rules are DataAnnotation attributes assigned to properties as metadata, its pretty easy to envision how to retrieve them: call the PropertyInfo.GetCustomAttributes(type) method.

[C#]

using System.Reflection;
PropertyInfo vPropertyInfo = typeof(Category).GetProperty("CategoryName");
object[] vAttributes = vPropertyInfo.GetCustomAttributes(typeof(System.ComponentModel.DataAnnotations.RequiredAttribute));
if (vAttributes.Length > 0)
{
    RequiredAttribute vRequiredAttribute = (RequiredAttribute)vAttributes[0];
// use vRequiredAttribute
}

[VB]

Imports System.Reflection
Dim vPropertyInfo As PropertyInfo = GetType(Category).GetProperty("CategoryName")
Dim vAttributes As Object() = vPropertyInfo.GetCustomAttributes(GetType(System.ComponentModel.DataAnnotations.RequiredAttribute))
If vAttributes.Length > 0 Then
 
   Dim vRequiredAttribute As RequiredAttribute = CType(vAttributes(0), RequiredAttribute)
' use vRequiredAttribute
End If

There are an enormous number of details about a single property in an Entity class. They include what the DataAnnotation attributes supply and structural information such as the property name, the associated column name, and its type. BLD uses Descriptor classes to capture all of these details once and make them easily accessible to you. There are four types of Descriptor classes:

  • DataFieldDescriptor – Describes a DataField (property on the Entity class) gathering information from both DAL and BLL Entity classes. The DAL supplies structural information, like the property name, column name, and type. The BLL supplies its list of DataAnnotation attributes.
  • RelationshipDataFieldDescriptor – Some DataFields describe relationships and are not actually columns in the table. These DataFields are described with this specialized DataFieldDescriptor class that identifies the destination Entity class of the relationship and how the relationship is structured.
  • EntityDescriptor – Describes an Entity class. It keeps a list of all DataFieldDescriptors for the Entity class. It knows information about the actual table and it has a list of DataAnnotation attributes defined on the Entity class itself.
  • DataContextDescriptor – Describes the DataContext class (a.k.a. “domain model” and “ObjectContext”). It keeps a list of all EntityDescriptors. It hosts the RestrictionManager, a class that defines how security on tables and columns is implemented by user role. It also has event handlers, including one to override how DataAnnotation attributes are identified so you can get them from a database or XML file instead of the Entity Metadata class.

I think the best way to see what Descriptors can offer your code is to see them in action. Please visit: http://learningbld.peterblum.com/DescriptorsBrowser.aspx. This page uses the Descriptors defined by BLD’s Learning site. Start with the two-table database I provide for BLD’s Walkthrough:

  • Select PeterBlum.Walkthrough.Appointment.AppointmentDemoDataContext from the Registered DataContext dropdownlist.
  • Click AppointmentDemo from the EntityTypes column.
  • Start playing around, clicking on items in the DataFields and List of attributes columns.

Descriptors are defined in the Business Logic Layer and are used by the UI as it applies the business rules. They are available to your CRUD code, which is located in the BLL. For example, the UI may pass a list of DataFields and values that it wants to update. Your Update method (part of the CRUD) creates its code based on that list, using the DataFieldDescriptor to know the Column name and database type of each value.

Here is an example of how to retrieve the DataFieldDescriptor for the CategoryName DataField on the Category Entity class. Let’s suppose it’s used in the user interface to disable a TextBox control when the field is read-only and assigns text to the Label associated with the textbox from the DisplayNameAttribute (a DataAnnotation attribute that provides a user friendly name for the column).

[C#]

using PeterBlum.DES.DataAnnotations.Descriptors;
BaseDataFieldDescriptor vDataFieldDescriptor = DescriptorManager.GetDataFieldDescriptor(typeof(Category), "CategoryName");
if (vDataFieldDescriptor.IsReadOnly)
   TextBox1.Enabled = false;
Label1.Text = vDataFieldDescriptor.GetLocalizedDisplayName(System.Globalization.CultureInfo.CurrentUICulture);

[VB]

Imports PeterBlum.DES.DataAnnotations.Descriptors
Dim vDataFieldDescriptor As BaseDataFieldDescriptor = DescriptorManager.GetDataFieldDescriptor(GetType(Category), "CategoryName")
If vDataFieldDescriptor.IsReadOnly Then
   TextBox1.Enabled = False
End If
Label1.Text = vDataFieldDescriptor.GetLocalizedDisplayName(System.Globalization.CultureInfo.CurrentUICulture)

Digging a little deeper

Normally you would get and set a property in a class using the System.Reflection.PropertyInfo object’s GetValue() and SetValue() method. The DataFieldDescriptor offers its own GetValue() and SetValue() methods, which are wrappers around the PropertyInfo methods by default. Your UI and CRUD methods should always use the DataFieldDescriptor’s methods because it allows DataFieldDescriptors to handle special cases.

Calculated fields are good examples. Suppose you want a calculated DataField that creates a full address string from columns that make up the parts (Address, City, Country, etc). BLD lets you create a custom DataFieldDescriptor. By overriding the GetValue() method, your code can create this string.

The DataFieldDescriptor also knows how to validate with its Validate() method. This method goes through all validation-oriented DataAnnotation attributes and populates a list of errors. The EntityDescriptor also has a Validate() method. It goes through all DataFieldDescriptors, calling their Validate() method to create a complete list of errors. Use the Validate() method in your Update and Insert methods in the BLL.

Here are some additional methods of interest on DataFieldDescriptor:

  • ChangeType() – Pass it a value. If its not identical to the property’s type, it will attempt to convert it to the correct type. It even handles string to native type conversion.
  • ConvertNativeToFormattedString() – Converts a value into a string, applying localization and business rules such as those from the System.ComponentModel.DataAnnotations.DisplayFormatAttribute.
  • CanTakeActionOnColumn() – Determines if the current user is allowed to take the action passed in on the column. For example, if the user’s role is “Sales”, can they edit the “UnitPrice” column on the Product table? There is a similar method on the EntityDescriptor for actions on Tables.
  • GetLocalizedDisplayName(), GetLocalizedShortDisplayName(), GetLocalizedDescription(), GetLocalizedPrompt() – Returns the localized string associated with the business rules for DisplayName, ShortDisplayName, Description, and Prompt.

For more on Descriptors, start with the "Descriptor classes: The structure and business rules of your database" topic in the Learning BLD site. To go deeper, see the topics in the BLD User’s Guide.

A significant portion of my new Peter’s Business Logic Driven UI (“BLD”) module is in the Business Logic Layer (“BLL”). Having been a UI tools guy for a long time, I learned a lot about the BLL during this project. This article starts a series that digs into the BLL as I’ve implemented it for BLD.

These articles may apply to different audiences: novices, users looking to fix the flaws of their BLL, and certainly BLD users. Use the “Understanding the BLL” Category to see all articles.

Let’s get started on the concept of the Entity class.

The Entity class describes the structure of the table, where properties map to columns in the database. The properties are called DataFields throughout BLD.

There are actually three Entity class definitions for each table.

DAL Entity class

The Data Access Layer (“DAL”) defines the actual class that maps properties to columns. It is the purest form of the Entity class. The technologies behind the Data Access Layers each know how to generate these classes automatically. Those technologies include the ADO.NET Entity Framework, LINQ to SQL, nHibernate, LLBLGen Pro, and any other O/RM.

Here is a simplified version of the Category table from the Northwind database as generated by Entity Framework:

[C#]

[EdmEntityTypeAttribute(NamespaceName="NorthwindModel", Name="Category")]
public partial class Category : EntityObject
{
  [EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
  public System.Int32 CategoryID
  { … }
  [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
  public System.String CategoryName
  { … }
  [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
  public System.String Description
  { … }
}

[VB]

<EdmEntityTypeAttribute(NamespaceName:= "NorthwindModel", Name:="Category") >_
Public Partial Class Category 
  Inherits EntityObject
  <EdmScalarPropertyAttribute(EntityKeyProperty:=True, IsNullable:=False)>_
  Public Property CategoryID As System.Int32 
  End Property
  <EdmScalarPropertyAttribute(EntityKeyProperty:=False, IsNullable:=False)>_
  Public Property CategoryName As System.String 
  End Property
  <EdmScalarPropertyAttribute(EntityKeyProperty:=False, IsNullable:=True)>_
  Public Property Description As System.String 
  End Property
End Class

BLL Entity class

The Business Logic Layer defines a companion class where business rules associated with the Entity are defined. This class uses the same namespace and class name as the DAL Entity class. Both classes use the partial keyword to merge with each other. While it handles business rules, it cannot handle all of them. (That’s where the final Entity class comes in, as describe later.) BLL Entity handles custom properties (calculated fields for example) and any methods you need, such as custom validation code.

Here is the Category Entity class in the BLL with a custom property and validation method.

[C#]

using System.ComponentModel.DataAnnotations;
public partial class Category  
{
    public string ShortName
    {
        get 
        { 
            if (this.CategoryName.Length > 4)
                return this.CategoryName.SubString(0, 4); 
            else
                return this.CategoryName;
        }
    }
    public ValidationResult CheckForDuplicateCategoryNames(
      string newName, ValidationContext validationContext)
   {
        if (IsDuplicateName(newName)) // this code is left to the user
            return new ValidationResult("Duplicate name");
      return ValidationResult.Success;
   }
 
}

[VB]

Imports System.ComponentModel.DataAnnotations
Public Partial Class Category
    Public ReadOnly Property ShortName() As String
        Get
            If Me.CategoryName.Length > 4 Then
                Return Me.CategoryName.SubString(0, 4)
            Else
                Return Me.CategoryName
            End If
        End Get
    End Property
    Public Function CheckForDuplicateCategoryNames(newName As String, validationContext As ValidationContext) As ValidationResult
        If IsDuplicateName(newName) Then
            ' this code is left to the user
            Return New ValidationResult("Duplicate name")
        End If
        Return ValidationResult.Success
    End Function
 
End Class

Entity Metadata class

The third class hosts DataAnnotation attributes on the DataFields (properties of the DAL Entity class). DataAnnotation attributes are a significant tool in defining your business rules. Attributes apply metadata to properties and classes. DataAnnotation attributes are attribute classes describing business rules. We’ll cover various DataAnnotation attributes in detail later in this series.

The easiest approach for adding DataAnnotation attributes to properties is directly in the DAL Entity class because it already declares each property name. However, business rules belong in the BLL, not the DAL. So don’t do that!

The next natural place for this data is in the BLL Entity class, but .net is not quite up to the task because we want to attach the attributes to properties of the same name of the columns in our table. Those properties are defined in the DAL Entity. While we are using the partial keyword to merge BLL and DAL Entities, the partial keyword does not work on individual properties.

Microsoft solved this by defining the Entity Metadata class where you add properties with names matching the column names. It is connected to the BLL Entity class through the System.ComponentModel.DataAnnotations.MetadataTypeAttribute.

As you need to add DataAnnotation attributes to a property already defined in the DAL Entity, define a new property of the same name. This property is merely a placeholder for metadata, so its type and code are not used. This example shows how these properties are typically defined, with the type of Object (instead of the real type) and empty GET and SET clauses.

[C#]

[MetadataType(typeof(CategoryMetadata))]
public partial class Category  
{
    // see above
}
 
public class CategoryMetadata
{
    [Required()]
    [StringLength(30)]
   [CustomValidation(MethodName="CheckForDuplicateCategoryNames")]
   public object CategoryName { get; set; }
}

[VB]

<MetadataType(typeof(CategoryMetadata))> _
Public Partial Class Category
    ' see above
End Category
 
Public Class CategoryMetadata
    <Required()> _
    <StringLength(30)> _
  <CustomValidation(MethodName := "CheckForDuplicateCategoryNames")> _
  Public Property CategoryName As Object
    Get
      Return Nothing
    End Get
    Set
    End Set
  End Property
End Class

The Entity Metadata class is just one way to supply DataAnnotation attributes to BLD. As explained in the next topic, BLD defines the DataFieldDescriptor class to hold instances of all DataAnnotation attributes per field. The default DataFieldDescriptors know to look in the Entity Metadata class for this data. But you can also create your own system, such as a database or XML file that defines each business rule. BLD will call an event handler where you can create the list of DataAnnotation attributes from your resource.

ASP.NET Dynamic Data is a technology in ASP.NET 3.5 and higher that attempts to solve the same problem as Peter’s Business Logic Driven UI (“BLD”). In fact, BLD’s origins were as an extension to Dynamic Data.

I wanted to get the controls of Peter’s Data Entry Suite into Field Templates of Dynamic Data and started on that before ASP.NET 3.5 shipped. Early on, I found the Field Template class required reworking in order to support the DES Validation Framework. It took off from there, where class by class, I found design problems or desired to do something different. That resulted in almost completely rewriting Dynamic Data. These days, BLD uses very little of Dynamic Data’s code. Almost everything has a different name. Only “Field Templates” and “Filter Templates” have survived, although without using the original code.

This process occurred over four years of iterative discovery and resolution. BLD would not have been built this way without these issues I determined are in Dynamic Data:

  1. The goal is to allow separating business logic from the user interface. However, it defines three key classes which describe the business logic within the user interface layer. Those classes are MetaModel, MetaTable, and MetaColumn. Explore any of them and you will find UI concepts within them, dealing with URL Routing.

    BLD knows that there is a tremendous value having objects like these in the Business Logic Layer (“BLL”). The BLL should prepare and deliver a “MetaColumn” for the consumer to use. That allows the BLL to customize the business rules based on the current case, not the static metadata attached at compile time.

    This data is also invaluable for your CRUD methods. For example, if you want write a query method that is passed filtering rules, those rules need to identify a specific column, and pass along data that is compatible with that column. The MetaColumn is a great description of the column, but is not allowed in a true BLL.

    BLD has created its own classes replacing MetaModel, MetaTable, and MetaColumn. They are DataContextDescriptor, EntityDescriptor, and DataFieldDescriptor, they are defined in the BLL. BLD’s EntityFilter classes, which define filtering rules, are also elements of the BLL that are consumed by the UI to let the page visitor or Web Forms developer establish filters. These objects carry along a DataFieldDescriptor object.

  2. Dynamic Data fails to cover a significant part of the BLL: the Data Access Object (“DAO”) classes. These are where you define methods for CRUD actions based on your business rules. The most common rules are queries, such as a method called “SelectByPrice” in the Product’s DAO. Because of this omission and documentation built around using the LinqDataSource or EntityDataSource, the user ends of writing business logic for CRUD actions in the web form. That’s exactly what the user wanted to avoid!

    If the user had realized this and created Data Access Object classes, they would still have to define a DataSource control that works with Dynamic Data. The ObjectDataSource control was intended for DAOs but was not built to support Dynamic Data.

    BLD introduces its own Data Access Object classes and a supporting DataSource control. It also provides support for the user’s own Data Access Objects through the ObjectDataSource control.

  3. The Dynamic Data API has numerous members that are marked INTERNAL or PRIVATE, preventing their usage by its consumers, not to mention PROTECTED methods that are not marked VIRTUAL, preventing overriding them.

    There is no way to use a subclass of something important like MetaColumn, where all of the business rules are listed, because even if you did subclass it, the code to instantiate a MetaColumn is hardwired into the MetaTable class, and not in a way that you could override. Peter encounterd wall after wall due to these API limitations as he attempted to create BLD.

    BLD’s API assumes that users will want to subclass and replace its objects. It rarely uses INTERNAL members. PRIVATE is only used on field storage and special cases. Most PROTECTED methods are also virtualized. Finally, there is almost always a way to have your own class get instantiated, either by overriding a method where the original class was created, or through a factory.

  4. Validation is an essential business rule. When you add a ValidationAttribute (like RequiredAttribute or RangeAttribute) into your Entity class, you should not worry about updating the Field Templates to support it. Yet, that’s exactly what Dynamic Data does. Each Field Template requires that you add all possible Validator web controls and register it with the base FieldTemplate class. As you create new Field Templates, you have to be careful to cover all of them. If you create a new ValidationAttribute, you have to return to the Field Template files and update them. The user interface should never allow a validation business rule to be overlooked.

    BLD resolves this by making all ValidationAttribute objects capable of creating the appropriate class used by the UI for its validation purposes. This should raise a red flag, because a BLL object creates a UI class, but BLD uses a factory to do this. It has the UI layer register all of its Validation classes, giving each type of validation rule a standard signature. So the author of a new ValidationAttribute simply requests the appropriate object from the factory. The identity of the UI layer is passed to the ValidationAttribute which is passed onto the factory.

    The Field Template itself hosts a single control, BLDDataFieldValidators, which runs through the validation attributes for the column, asking each to create its validator.

  5. ASP.NET Dynamic Data does not appear to be prospering. Perhaps users don’t understand its value and perhaps those that do can not push it far enough to meet their needs. As a result, it makes little sense to deliver an improved Dynamic Data. Microsoft appears unlikely to grow it, addressing the issues already raised. So it’s best that BLD is not dependent on Dynamic Data.

While BLD is a different code base, Dynamic Data users will understand its user interface model. Every Dynamic Data control has an equivalent in BLD: DynamicControl is BLDDataField; DynamicDataManager is BLDPageManager; etc. The concepts of Page Templates, Field Templates, and Filter Templates remain, although they are coded differently.

If you have an existing Dynamic Data application, you cannot just rename the controls and start using BLD, but that’s probably a good thing since BLD makes so many improvements that often require doing things differently. Yet the most important control, DynamicControl, can usually be renamed to BLDDataField successfully. That allows the biggest piece of work on existing web pages – the content to show the user – migrates pretty easily.

The PeterBlum.com public message board on Yahoo Groups is being replaced by a new forums system.

Please use http://www.peterblum.com/forums.aspx to launch the new forums.

The older message board remains available for searches, but not posting.

Peter’s Data Entry Suite (“DES”) version 5 is now available after four long years of work. The focus of this version is the new module, Peter’s Business Logic Driven UI (“BLD”).

BLDlogo

BLD takes a different approach to ASP.NET Web Form development, allowing something ASP.NET MVC users are familiar with: Separating your business rules into the Business Logic Layer.

Image1

BLD introduces 30 new web controls for building a user interface that respects the business rules of your business layer.

It also provides tools for building the business layer itself.

You may have seen this approach to describing business rules by applying metadata (the attributes) to a class that mimics your table. Those attributes are called “DataAnnotation attributes” and come from both the .net framework (v4) and BLD.

Image2

Data Access Objects manage the CRUD (“create, read, update, delete”) actions applied to each table. Once again, you may be familiar with creating a class that is a companion to each Entity type with methods like this. BLD defines rich base classes for developing your classes, so most of the time, you don’t worry about details like the caching, querying, or writing records. In fact, if you inherit from “BLD DataAccessObjects”, you get all of the code shown here completed, except the SelectTopTen() method.

Image3

BLD fully utilizes the rest of Peter's Data Entry Suite, reducing the code you write for your data entry web forms. It is designed to be very expandable, with a well designed API and complete source code.

Here are resources to learn more:

What else is new?

Peter’s Data Entry Suite v5 has numerous enhancements including:

  • AJAX setup simplified when using ASP.NET 4 along with Microsoft’s ScriptManager and UpdatePanel controls.
  • New control: DropDownMenu – A button with built-in ContextMenu, providing an easy way to launch a context menu at a specific location on the page.
  • New control: HtmlList – Similar to the System.Web.UI.WebControls.BulletedList. In fact, it originated because the BulletedList could not handle a few cases. It draws a list of items that postback on click and preserves the selected item. The selected item is drawn with a different style sheet and without the ability to postback on click. Thus it works like a RadioButtonList with a postback upon selection. Use style sheets to drive appearance, such as making the selectable items look like hyperlinks.
  • Refreshed look for the Date and Time controls. To see it, look here: http://www.peterblum.com/des/dateandtime.aspx.
  • CalculationController offers new CalcItem classes: TotalingCalcItem and CheckStateCalcItem. Use TotalingCalcItem to create a total of a column within a ListView, GridView, DataGrid, or Repeater. Use CheckStateCalcItem to add one of two constant values based on the state of a checkbox or radiobutton.

Click on this PDF to get all of the details:

pdf

Additionally:

  • Source code for DES is now free, after a licensing process. BLD’s source code is actually included without additional licensing.

  • New forums site – Tech support is now available through http://www.peterblum.com/forums.aspx.

  • New online learning site – Many interactive and educational pages are available at http://learningdes.peterblum.com.

Visual Studio 2010’s Publish Web Site command offers the option “Allow this precompiled site to be updatable”. When used and your application has User Control (.ascx) files, the published application no longer contains your user controls! Instead, the \bin folder contains new files ending in the .compiled extension, one for each User Control. Your User Controls still work though, just through some hidden behaviors that I will discuss here.

First, the Page.LoadControl() method still works. LoadControl() creates a User Control on demand. In the past, it had to build it from the User Control’s file contents. Without the file present, it knows about the missing files through those new files in the .bin folder.

In my case, I was calling LoadControl after checking for a specific User Control file via a FileExists() function. I used System.Web.Hosting.VirtualPathProvider.FileExists() function. It turns out that VirtualPathProvider.FileExists() does not support the .compiled files and always returns false.

The solution comes from how ASP.NET Dynamic Data v4 does it: Use the System.Web.Compilation.BuildManager.GetObjectFactory() method. Always pass false for the ThrowException parameter and test the result for null. If not null, the file exists.

Here is a method that replaces the Page.LoadControl() method, loading a User Control if it exists. It works with the Publish Web Site feature as discussed.

public System.Web.UI.WebControls.UserControl LoadUserControlIfExists(string pVirtualFilePath)
{
    if (System.Web.Compilation.BuildManager.GetObjectFactory(pVirtualFilePath, false) != null)
        return Page.LoadControl(pVirtualFilePath);
    return null;
}

One idiosyncrasy of the ModalPopupExtender is that its HTML content is actually visible while the page is loading, until the $create() call initializes it. If you have a lengthy load time, as I did with a ListView full of records, the visibility is noticeable.

One simple solution to this is to assign the style=”display:none” to the Control containing your content.

<asp:Panel id="AdvancedFiltersContainer" runat="server" CssClass="AdvSch_PopupControl" Style="display:none;" >
    content
</asp:Panel>
<act:ModalPopupExtender id="Modal1" runat="Server" PopupControlID="AdvancedFiltersContainer" >
</act:ModalPopupExtender>

However, I’ve learned that once an Ajax Control Toolkit ComboBox control is present, IE 9 (and presumably earlier browsers) reports a JavaScript error as the Combobox is being initialized. When the HTML for the combobox is hidden, its elements have an offsetWidth, offsetHeight, clientWidth, and clientHeight of 0. The ComboBox calculates a value based on offsetWidth, subtracts the size of the border, and assigns the result to a Style.width object.

Here is a snippet from the function, _getOptionListWidth, where the issue resides.

// first, default to the clientWidth of the container
var bestWidth = this.get_comboTableControl().offsetWidth;
 
// subtract borders from the offsetWidth
bestWidth -= (leftBorder + rightBorder);
 
// in order for ths list's scrollWidth to be correct, must set its width
var originalWidth = style.width;
style.width = bestWidth + 'px';

If you are paying attention, you’ll notice that when style.width is set, the value it gets is a negative number (0 – border width). That throws a JavaScript exception in IE 9. I’ve found that even assigning style.width to “0px” throws this exception.

As a result, you cannot use style=”display:none;” to fix the problem I’ve raised, at least on IE 9.

Here’s my simple solution.

Assign style=”position:absolute;left:100000px;” instead. This will leave the ModalPopup visible, but way off screen. When ModalPopup initializes itself, it overwrites both position and left styles to fit its needs.

<asp:Panel id="AdvancedFiltersContainer" runat="server" CssClass="AdvSch_PopupControl" Style="position:absolute;left:100000px;" >
    content
</asp:Panel>
<act:ModalPopupExtender id="Modal1" runat="Server" PopupControlID="AdvancedFiltersContainer" >
</act:ModalPopupExtender>
More Posts Next page »