Blog: Posts from March, 2009

Labels
AJAX(112) App Studio(7) Apple(1) Application Builder(245) Application Factory(207) ASP.NET(95) ASP.NET 3.5(45) ASP.NET Code Generator(72) ASP.NET Membership(28) Azure(18) Barcode(2) Barcodes(3) BLOB(18) Business Rules(1) Business Rules/Logic(140) BYOD(13) Caching(2) Calendar(5) Charts(29) Cloud(14) Cloud On Time(2) Cloud On Time for Windows 7(2) Code Generator(54) Collaboration(11) command line(1) Conflict Detection(1) Content Management System(12) COT Tools for Excel(26) CRUD(1) Custom Actions(1) Data Aquarium Framework(122) Data Sheet(9) Data Sources(22) Database Lookups(50) Deployment(22) Designer(177) Device(1) DotNetNuke(12) EASE(20) Email(6) Features(101) Firebird(1) Form Builder(14) Globalization and Localization(6) How To(1) Hypermedia(2) Inline Editing(1) Installation(5) JavaScript(20) Kiosk(1) Low Code(3) Mac(1) Many-To-Many(4) Maps(6) Master/Detail(36) Microservices(4) Mobile(63) Mode Builder(3) Model Builder(3) MySQL(10) Native Apps(5) News(18) OAuth(9) OAuth Scopes(1) OAuth2(13) Offline(20) Offline Apps(4) Offline Sync(5) Oracle(11) PKCE(2) Postgre SQL(1) PostgreSQL(2) PWA(2) QR codes(2) Rapid Application Development(5) Reading Pane(2) Release Notes(183) Reports(48) REST(29) RESTful(29) RESTful Workshop(15) RFID tags(1) SaaS(7) Security(81) SharePoint(12) SPA(6) SQL Anywhere(3) SQL Server(26) SSO(1) Stored Procedure(4) Teamwork(15) Tips and Tricks(87) Tools for Excel(2) Touch UI(93) Transactions(5) Tutorials(183) Universal Windows Platform(3) User Interface(338) Video Tutorial(37) Web 2.0(100) Web App Generator(101) Web Application Generator(607) Web Form Builder(40) Web.Config(9) Workflow(28)
Archive
Blog
Posts from March, 2009
Sunday, March 1, 2009PrintSubscribe
Business Rules: ControllerAction Attribute

We continue discussion of business rules started with posts about RowBuilder attribute. We will create a method that will handle processing of employee territories that were check-marked by user in editForm1.

image

Field Territories  is a placeholder field of String type. Business rule method PrepareExistingEmployeeRow() retrieves a list of territories that an employee is responsible for.

Now it is time to implement saving of new checked territories and deletion of unchecked ones.

Handling of Custom Fields

We will need two methods in our business rule class to process Territories field value.

First method will hide that fact of changes to Territories field value from Data Aquarium Framework.

C#:

[ControllerAction("Employees", "editForm1", "Update", ActionPhase.Before)]
protected void BeforeEmployeeUpdate(int employeeId, 
    FieldValue territories, FieldValue lastName)
{
    territories.Modified = false;
    lastName.Modified = true;
}

VB:

<ControllerAction("Employees", "editForm1", "Update", ActionPhase.Before)> _
Protected Sub BeforeEmployeeUpdate(ByVal employeeId As Integer, _
    ByVal territories As FieldValue, ByVal lastName As FieldValue)
    territories.Modified = False
    lastName.Modified = True
End Sub

The name of the method implies that we want this method to execute just before an employee update. But method name means nothing to the framework. Data Aquarium Framework relies on attribute ControllerAction to decide if it has to invoke a business rule method.

Our ControllerAction attribute specifies that method BeforeEmployeeUpdate() shall execute when command Update is issued in form editForm1 of data controller Employees. There is also an execution phase information. We want this method to be invoked before the framework tries to perform an update.

There are three parameters: employeeId, territories, and lastName.

Data Aquarium Framework assumes that parameter names are matching the values of data fields specified for editForm1 in the data controller descriptor ~/Controllers/Employees.xml.

The native data field value information is held in FieldValue class instances. If you specify a parameter of type FieldValue then you gain access to its properties OldValue, NewValue, Value, and Modified.

If you don't care about the nature of changes of the field then you can simply specify an exact type that is matched to the data field definition in data controller descriptor.

We know that employeeId has not changed and that is why we specify System.Int32 as the type of the field. We do want to perform certain manipulations with the state of changes to territories and lastName fields. That requires FieldValue type.

The order of the fields and name capitalization does not have to match the definitions in data controller descriptor. Specify only the fields that you need.

The first line of the method resets Modified property to hide any field changes. Here is how Territories field was defined in data controller descriptor:

        <command id="command1" type="Text">
            <text>
                <![CDATA[
select
    "Employees"."EmployeeID" "EmployeeID"
    ,"Employees"."LastName" "LastName"
    ,"Employees"."FirstName" "FirstName"
. . . . . . . . . . . . . . . . . ,"ReportsTo"."LastName" "ReportsToLastName" ,"Employees"."PhotoPath" "PhotoPath", ,null "Territories" <<< Territories Field Definition from "dbo"."Employees" "Employees" left join "dbo"."Employees" "ReportsTo" on "Employees"."ReportsTo" = "ReportsTo"."EmployeeID"
]]> </text> </command>

We are returning null as a field value. The field is not based on any actual database table field. The framework does not know that and will try to update null with a new value. Resetting Modified property to false will prevent any update attempts that would happen by default otherwise.

We do want the framework to perform an SQL update for any other fields that might have changed. If the only field that have changed is Territories then no update will be performed. That is why we always "pretend" that field lastName has been changed. An alternative is to write more code and execute an update on our own. You will have to call PreventDefault() method of your business rules class to prevent any default update logic from execution if you do your own updates.

Post-Action Business Logic

Here is how we will update the territories that an employee is responsible for.

C#:

[ControllerAction("Employees", "editForm1", "Update", ActionPhase.After)]
protected void AfterEmployeeUpdate(FieldValue territories, int employeeId)
{
    string[] newTerritories = 
        Convert.ToString(territories.NewValue).Split(',');
    string[] oldTerritories = 
        Convert.ToString(territories.OldValue).Split(',');
    // remove "unchecked" territories
    foreach (string territoryId in oldTerritories)
        if (!(String.IsNullOrEmpty(territoryId)) && 
            (Array.IndexOf(newTerritories, territoryId) == -1))
        {
            EmployeeTerritories et = 
                EmployeeTerritories.SelectSingle(employeeId, territoryId);
            et.Delete();
        }
    // add new "checked" territories
    foreach (string territoryId in newTerritories)
        if (!(String.IsNullOrEmpty(territoryId)) && 
            (Array.IndexOf(oldTerritories, territoryId) == -1))
        {
            EmployeeTerritories et = new EmployeeTerritories();
            et.EmployeeID = employeeId;
            et.TerritoryID = territoryId;
            et.Insert();
        }
}

VB:

<ControllerAction("Employees", "editForm1", "Update", ActionPhase.After)> _
Protected Sub AfterEmployeeUpdate(ByVal territories As FieldValue, _
    ByVal employeeId As Integer)
    Dim newTerritories As String() = _
        Convert.ToString(territories.NewValue).Split(",")
    Dim oldTerritories As String() = _
        Convert.ToString(territories.OldValue).Split(",")
    ' remove "unchecked" territores
    For Each territoryId In oldTerritories
        If Not String.IsNullOrEmpty(territoryId) AndAlso _
            Array.IndexOf(newTerritories, territories) = -1 Then
            Dim et As EmployeeTerritories = _
                EmployeeTerritories.SelectSingle(employeeId, territoryId)
            et.Delete()
        End If
    Next
    ' add new "checked" territories
    For Each territoryId In newTerritories
        If Not String.IsNullOrEmpty(territoryId) AndAlso _
            Array.IndexOf(oldTerritories, territories) = -1 Then
            Dim et As EmployeeTerritories = New EmployeeTerritories()
            et.EmployeeID = employeeId
            et.TerritoryID = territoryId
            et.Insert()
        End If
    Next
End Sub

Parameter territories is defined as a variable of type FieldValue since we want to know the previous value of the field. We split new and old value of the field by comma.

Next we scan old territories and if any territory is not in the list of new ones then we delete the territory with the help of EmployeeTerritories class that was automatically generated for us by ASP.NET code generator Code OnTime Generator.

Finally we scan new territories and for any "new" territories that were not in the list of old ones we are executing an employee territory insertion. Again we do that we the help of automatically generated business object EmployeeTerritories.

Conclusion

A simple and easy to understand server programming model for business rules is a crown jewel of Data Aquarium Framework.  Business rules are built on top custom action handlers and provide a truly simple and easy to use programming mode to enhance your AJAX ASP.NET application with server code.

In our next post we will discuss programmatic data filtering via business rules.

Thursday, February 26, 2009PrintSubscribe
Business Rules: RowBuilder Attribute and Existing Rows

Northwind database has a cross-reference table EmployeeTerritories that link together an employee and a territory. Here is a user interface generated for this table by Data Aquarium premium project.

List of employee territories is filtered by a region.

image

Employee territory is displayed in edit form.

image

While completely function this type of user interface will still benefit if we display a collection of all territories right on the employee screen and allow user to check mark territories that is user is responsible for.

Creating a Placeholder Field For a List of Territories

Open ~/Controllers/Employees.xml data controller and modify the text of command1 as shown below. Notice the new field Territories just before the from clause. This field is used just as placeholder and an actual value is going to be provided by a business rule.

        <command id="command1" type="Text">
            <text>
                <![CDATA[
select
    "Employees"."EmployeeID" "EmployeeID"
    ,"Employees"."LastName" "LastName"
    ,"Employees"."FirstName" "FirstName"
    ,"Employees"."Title" "Title"
    ,"Employees"."TitleOfCourtesy" "TitleOfCourtesy"
    ,"Employees"."BirthDate" "BirthDate"
    ,"Employees"."HireDate" "HireDate"
    ,"Employees"."Address" "Address"
    ,"Employees"."City" "City"
    ,"Employees"."Region" "Region"
    ,"Employees"."PostalCode" "PostalCode"
    ,"Employees"."Country" "Country"
    ,"Employees"."HomePhone" "HomePhone"
    ,"Employees"."Extension" "Extension"
    ,"Employees"."Photo" "Photo"
    ,"Employees"."Notes" "Notes"
    ,"Employees"."ReportsTo" "ReportsTo"
    ,"ReportsTo"."LastName" "ReportsToLastName"
    ,"Employees"."PhotoPath" "PhotoPath",
    ,null "Territories"
from "dbo"."Employees" "Employees"
    left join "dbo"."Employees" "ReportsTo" on "Employees"."ReportsTo" = "ReportsTo"."EmployeeID"
]]>
            </text>
        </command>

Add new field to the list of fields.

<field name="Territories" type="String">
    <items style="CheckBoxList" dataController="Territories" 
        dataTextField="TerritoryDescription"/>
</field>

Modify editForm1 to include a reference to the field in the user interface of the form right after the FirstName field. We have specified 3 as number of columns for the field, which is supposed to be displayed as a check box list.

<dataField fieldName="LastName" columns="20" />
<dataField fieldName="FirstName" columns="10" />
<dataField fieldName="Territories" columns="3"/>

Run the sample application and start editing any employee record in form mode. Here is what you will likely see.

image

None of the check boxes is checked. We will add a business rule to populate the check boxes.

Creating a Business Rule to Populate an Existing Row

Open business rules class ~/App_Code/Class1.cs(.vb) that was created as described in the RowBuilder attribute introduction. Add the following method to the class.

C#:

[RowBuilder("Employees", "editForm1", RowKind.Existing)]
protected void PrepareExistingEmployeeRow()
{
    int employeeId = Convert.ToInt32(SelectFieldValue("EmployeeID"));
    List<EmployeeTerritories> territories = 
        EmployeeTerritories.Select(employeeId, null, null, null, null);
    StringBuilder sb = new StringBuilder();
    foreach (EmployeeTerritories et in territories)
    {
        if (sb.Length > 0)
            sb.Append(",");
        sb.Append(et.TerritoryID);
    }
    UpdateFieldValue("Territories", sb.ToString());
}

VB:

Protected Sub PrepareExistingEmployeeRow()
    Dim employeeId As Integer = Convert.ToInt32(SelectFieldValue("EmployeeID"))
    Dim territories As List(Of EmployeeTerritories) = _
        EmployeeTerritories.Select(employeeId, Nothing, Nothing, Nothing, Nothing)
    Dim sb As StringBuilder = New StringBuilder()
    For Each et As EmployeeTerritories In territories
        If sb.Length > 0 Then
            sb.Append(",")
        End If
        sb.Append(et.TerritoryID)
    Next
    UpdateFieldValue("Territories", sb.ToString())
End Sub

The name of the business rule method is irrelevant. The rule will be automatically invoked when data controller Employees is preparing data for editForm1 view and the form will be displaying an existing row. This method is called for each row returned to the client. Data Aquarium Framework only returns the exact number of rows that are requested by a client view.

Method SelectFieldValue is inherited from the base class BusinessRules and allows to access values that will be returned for a row that is being built at this moment. We are obtaining a list of employee territories via business object EmployeeTerritories.

Next we are creating a comma-separated list of territory IDs and update the value of the Territories field with the result accumulated in an instance of System.Text.StringBuilder. Please make sure to add System.Text namespace in the list of imported namespaces.

Lookup item style CheckBoxList is designed to automatically handle comma separated lists of values.

Here is how editForm1 looks when we select a row. Text corresponding to each territory of selected employee is automatically matched to an ID of each employee territory.

image

Here is how eidtForm1 is transformed when user clicks on Edit button.

image

Conclusion

RowBuilder attribute allows providing calculated field values for new and existing rows returned to a client script running in a browser. You can create fields that don't actually exist in your database and figure their values on-the-fly.

There is still work to do when you need to save values of calculated fields. We will review this in the next post dedicated to ControllerAction attribute.

Wednesday, February 25, 2009PrintSubscribe
Business Rules: RowBuilder Attribute

Latest update of Data Aquarium Framework introduces a new and much improved method of extending framework with custom business logic. You can take full advantage of declarative user interface of data controllers custom form templates, while having a significant control over data processing.

About Business Rules

ASP.NET development model makes it very easy for developers to mix user interface code and business logic code. It is just too tempting to write a few lines of a business rule that will be executed in response to a user interface control event. This leads to expensive maintenance.

We have put a significant effort into preventing such code blending from happening in applications built with Data Aquarium Framework. The first step was to introduce custom action handlers and data filters.

The new release builds on top of these two features and uses attribute-based approach to business rule development.

We will start review of business rules with RowBuilder attribute.

Sample Web application

Generate a sample Data Aquarium application from Northwind database.  Leave MyCompany as a default namespace. Make sure to request generation of business logic layer. We will use generated business objects to enhance our business rules whenever we need to access data. Note that if you generate your application with Membership support enabled then you will have to sign into your application in order to see any data presentation.

Open generated project in Visual Studio 2008 or Visual Web Developer 2008 and add a new class Class1 to ~/App_Code folder of your application. Our new class will inherit its base functionality from  BusinessRules class of MyCompany.Data namespace.

C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MyCompany.Data;

public class Class1: BusinessRules
{
    public Class1()
    {
    }
}

VB:

Imports Microsoft.VisualBasic
Imports MyCompany.Data

Public Class Class1
    Inherits BusinessRules

End Class

Open file ~/Controllers/Employees.xml and add attribute handler to dataController element.

<dataController name="Employees" conflictDetection="overwriteChanges" 
    label="Employees" xmlns="urn:schemas-codeontime-com:data-aquarium"
    handler="Class1">

This will hook your business rules to Employees data controller. Your business rules will be universally applied whenever a reference to a data controller is made. You business rules will be invoked when your application is utilizing DataViewExtender or ControllerDataSource, when you export data or create new employees in data lookups.

The same set of business rules can be applied to multiple data controllers.

Building New Rows

The first rule will be assigning employee ID of company's CEO to ReportsTo field of any new employee record.

Add the following method to Class1.

C#:

[RowBuilder("Employees", "createForm1", RowKind.New)]
protected void PrepareNewEmployeeRow()
{
    using (SqlText findCEO = new SqlText(
        "select EmployeeID, LastName from Employees where ReportsTo is null"))
    {
        if (findCEO.Read())
        {
            UpdateFieldValue("ReportsTo", findCEO["EmployeeID"]);
            UpdateFieldValue("ReportsToLastName", findCEO["LastName"]);
        }
    }
}

VB:

<RowBuilder("Employees", "createForm1", RowKind.New)> _
Protected Sub PrepareNewEmployeeRow()
    Using findCEO As SqlText = New SqlText( _
        "select EmployeeID, LastName from Employees where ReportsTo is null")
        If findCEO.Read() Then
            UpdateFieldValue("ReportsTo", findCEO("EmployeeID"))
            UpdateFieldValue("ReportsToLastName", findCEO("LastName"))
        End If
    End Using
End Sub

The protection level of this method is only important if you are planning to create hierarchies of business rule classes.

The name of the method plays no role at all. Name your business rules methods to reflect their purpose.

The method is automatically called whenever a new row is about to be returned to user interface components by Employees data controller. Typically this will happen when end user is creating a new record. There is also a restriction on the presentation view that will ensure method execution when createForm1 is presenting data. You can apply multiple RowBuilder attributes to the same method if the same business logic is executed by other controllers and/or views.

Use method UpdateFieldValue to assign any default values to fields of a new row.

Class SqlText is a utility provided with Data Aquarium Framework to give you a simple and efficient way of querying a database.

Run the sample application and start creating a new employee record. Scroll to the bottom of the screen to see the result of our effort.

image 

Next post will show how to manipulate fields of existing rows and demonstrate new look item style CheckBoxList.

The new lookup item style is featured in Membership user manager. Select any user and start editing a record. A menu of check boxes is presented to help you select user roles.