March 2012 - Posts

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.

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.

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.

More Posts