Business Rules/Logic

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
Business Rules/Logic
Saturday, April 4, 2009PrintSubscribe
ExternalFilter And Modal Views

New ExternalFilter property is available to business rules developers and allows implementing code that is aware of the external master-detail relationships without mixing business logic and user interface code and markup.

image

Sample Project

You can see this sample live at http://dev.codeontime.com/demo/externalfilter. The source code is available at http://dev.codeontime.com/demo/externalfilter/Source.zip.

Generate a Data Aquarium application from Northwind database with business objects enabled and add ~/EmployeeTerritories.aspx page to the root of the web site created by Code OnTime Generator.

Open ~/Controllers/EmployeeTerritories.xml and create its copy named ~/Controllers/MyEmployeeTerritories.xml. Change view grid1 to reveal the territory code and add territory description as a separate field.

<view id="grid1" type="Grid" commandId="command1" label="Employee Territories">
    <headerText>This is a list of employee territories. </headerText>
    <dataFields>
        <dataField fieldName="EmployeeID"/>
        <dataField fieldName="TerritoryID" />
        <dataField fieldName="TerritoryTerritoryDescription"/>
        <dataField fieldName="TerritoryRegionRegionDescription"/>
    </dataFields>
</view>

Modify EmployeeTerritories.aspx as shown in this snippet.

<%@ Page Title="" Language="C#" MasterPageFile="~/MasterPage.master" 
    AutoEventWireup="true" CodeFile="EmployeeTerritories.aspx.cs" 
    Inherits="EmployeeTerritories" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="Header1Placeholder" runat="Server">
    Employee Territories
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="Header2Placeholder" runat="Server">
    Northwind
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="BodyPlaceholder" runat="Server">
    <div id="EmployeesDiv" runat="server">
    </div>
    <aquarium:DataViewExtender ID="EmployeesExtender" runat="server" 
        Controller="Employees"
        PageSize="5" TargetControlID="EmployeesDiv" />
    <div id="EmployeeTerritoriesDiv" runat="server" />
    <aquarium:DataViewExtender ID="EmployeeTerritoriesExtender" runat="server" 
        Controller="MyEmployeeTerritories" 
        TargetControlID="EmployeeTerritoriesDiv" FilterFields="EmployeeID" 
        FilterSource="EmployeesExtender"
        PageSize="20" />
</asp:Content>

Open the page in the browser. The following user interface will be presented.

image

Task

Users can quickly associate selected employee and territories by clicking New | New Employee Territories option on the action bar of Employee Territories view.

We will add a new option to New menu to allow simultaneous creation of a new territory and association of this territory with the selected employee. We will be using modal dialog and ExternalFilter property for this task.

Implementation

Add the new action to MyEmployeeTerritories.xml.

<actionGroup scope="ActionBar" headerText="New">
    <action commandName="New" commandArgument="createForm1" 
        headerText="New Employee Territories" 
        description="Create a new Employee Territories record." />
    <action commandName="Custom" commandArgument="CreateAndLinkTerritory" 
        headerText="Create and Link Territory" 
        description="Create a new Territory and link it to employee." 
        cssClass="NewLargeIcon"/>
</actionGroup>

image 

Modify command1 and add new command2 as follows.

        <command id="command1" type="Text">
            <text>
                <![CDATA[
select
    "EmployeeTerritories"."EmployeeID" "EmployeeID"
    ,"Employee"."LastName" "EmployeeLastName"
    ,"EmployeeTerritories"."TerritoryID" "TerritoryID"
    ,"Territory"."TerritoryDescription" "TerritoryTerritoryDescription"
    ,"TerritoryRegion"."RegionDescription" "TerritoryRegionRegionDescription"
    ,null "TerritoryCode"
    ,null "TerritoryDescription"
    ,null "RegionID"
from "dbo"."EmployeeTerritories" "EmployeeTerritories"
    left join "dbo"."Employees" "Employee" on "EmployeeTerritories"."EmployeeID" = "Employee"."EmployeeID"
    left join "dbo"."Territories" "Territory" on "EmployeeTerritories"."TerritoryID" = "Territory"."TerritoryID"
    left join "dbo"."Region" "TerritoryRegion" on "Territory"."RegionID" = "TerritoryRegion"."RegionID"
]]>
            </text>
        </command>
        <command id="command2" type="Text">
            <text>
                <![CDATA[
select
    null "EmployeeID"
    ,null "EmployeeLastName"
    ,null "TerritoryID"
    ,null "TerritoryTerritoryDescription"
    ,null "TerritoryRegionRegionDescription"
    ,null "TerritoryCode"
    ,null "TerritoryDescription"
    ,null "RegionID"
    
]]>
            </text>
        </command>
    </commands>
    <fields>

Notice that command1 was enhanced with three additional fields TerritoryCode, TerritoryDescription, and RegionID that are being selected as empty values. Command command2 mirrors all fields of command1 with one exception - all fields are returned as NULL values. The second command returns a single row of empty values.

Let's add definitions of the new fields to the fields element of the data controller.

<!-- new fields to support "createTerritory" view -->
<field name="TerritoryCode" type="String" allowNulls="false" label="Code"/>
<field name="TerritoryDescription" type="String" allowNulls="false" label="Description"/>
<field name="RegionID" type="Int32" allowNulls="false" label="Region">
    <items style="ListBox" dataController="Region" />
</field>

Finally, we will add createTerritory view. This view will be displayed in response to a selection of Create and Link Territory action bar menu option and is relying on command2 for its data.

<view id="createTerritory" type="Form" commandId="command2" 
    label="Create New Territory">
    <headerText>Please fill this form and click OK button to create 
        a new territory and link it to the employee. Click Cancel 
        to return to the previous screen.</headerText>
    <categories>
        <category headerText="Employee Information">
            <description>New territory will be linked to this employee.</description>
            <dataFields>
                <dataField fieldName="EmployeeLastName"/>
            </dataFields>
        </category>
        <category headerText="Territory">
            <description><![CDATA[
                Please enter the territory code, territory description, and 
                select a territory region. <br/><br/>You can use zip code 
                of the territory as code and city or county name as 
                territory description.]]></description>
            <dataFields>
                <dataField fieldName="TerritoryCode" columns="5"/>
                <dataField fieldName="TerritoryDescription" />
                <dataField fieldName="RegionID" />
            </dataFields>
        </category>
    </categories>
</view>

Now we are ready to write some code. Data Aquarium Framework promotes business logic reusability. Custom code is implemented in independent classes that are hooked to data controllers via @handler attribute.

Create a new class Class1 and link the class to data controller.

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

Enter the following code in the class definition.

C#

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

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

    [ControllerAction("MyEmployeeTerritories", 
        "Custom", "CreateAndLinkTerritory")]
    protected void NewTerritory()
    {
        if (Arguments.ExternalFilter.Length > 0 
            && Arguments.ExternalFilter[0].Value != null)
        {
            Employees emp = Employees.SelectSingle(
                Convert.ToInt32(Arguments.ExternalFilter[0].Value));
            Context.Session["Employee"] = emp;
            Context.Session["ControllerID"] = Arguments.ContextKey;
            Result.ShowModal("MyEmployeeTerritories", "createTerritory", 
                "New", "createTerritory");
        }

        else
            Result.ShowAlert("Please select an employee.");
    }

    [RowBuilder("MyEmployeeTerritories", 
        "createTerritory", RowKind.New)]
    protected void CreateTerritoryNewRow()
    {
        Employees emp = (Employees)Context.Session["Employee"];
        UpdateFieldValue("EmployeeLastName", emp.LastName);
    }

}

VB

Imports Microsoft.VisualBasic
Imports MyCompany.Data
Imports MyCompany.Data.Objects

Public Class Class1
    Inherits BusinessRules

    <ControllerAction("MyEmployeeTerritories", _
        "Custom", "CreateAndLinkTerritory")> _
    Protected Sub NewTerritory()
        If (Arguments.ExternalFilter.Length > 0 _
                AndAlso Not Arguments.ExternalFilter(0).Value Is Nothing) Then
            Dim emp As Employees = Employees.SelectSingle( _
                Convert.ToInt32(Arguments.ExternalFilter(0).Value))
            Context.Session("Employee") = emp
            Context.Session("ControllerID") = Arguments.ContextKey
            Result.ShowModal("MyEmployeeTerritories", "createTerritory", _
                             "New", "createTerritory")
        Else
            Result.ShowAlert("Please select an employee.")
        End If
    End Sub

    <RowBuilder("MyEmployeeTerritories", _
                "createTerritory", RowKind.New)> _
    Protected Sub CreateTerritoryNewRow()
        Dim emp As Employees = CType(Context.Session("Employee"), Employees)
        UpdateFieldValue("EmployeeLastName", emp.LastName)
    End Sub

End Class

Class Class1 is inherited from BusinessRules base. Business rules are recognized by the framework. Business rules class members are treated as serving a specific purpose when adorned with certain attributes.

Attribute ControllerAction on method NewTerritory will instruct the framework to invoke this method whenever a custom command with argument CreateAndLinkTerritory is invoked in a client-side JavaScript component Web.DataView.

Business rules property Arguments gives access to all information about the incoming command request. Its own new property ExternalFilter allows inspecting values of a master-detail link when a Web.DataView component is linked as a child in such a relationship. At the top of the article we have configured EmloyeeTerritoriesExtender component to be filtered by EmployeeID field. The extender injects a JavaScript snippet into the page to create an instance of Web.DataView with the appropriate filter.

Method NewTerritory verifies if an employee is selected and then stores the selected employee information in the web request session and displays a view createTerritory in a modal mode. The startup command for the view is New with an argument that points to the view itself. This will cause the view to be displayed in the "new record" mode.

image

In the screen shot you can see this in action.

Note that employee name corresponds to the name of the employee selected in the list of employees. The name is provided by method CreateTerritoryNewRow adorned with an attribute RowBuilder. This method retrieves selected employee from the session and updates the row returned with employee's last name just before the row is returned to the client.

If you set a break point in method CreateTerritoryNewRow then the debugger will stop there twice. It happens for the first time when view is initialized for modal presentation. Then the startup command is executed to switch the view in the "new record" mode, which causes the second invocation. The framework does not distinguish views to be serving any specific purpose and assumes the "new record" mode only when the New command has been executed.

The following code will create a new territory and link it to the selected employee by create a new record in EmployeeTerritories table. This code will execute when user clicks the OK button.

C#

[ControllerAction("MyEmployeeTerritories", "createTerritory", 
    "Insert", ActionPhase.Before)]
protected void NewTerritorySave(string territoryCode, 
    string territoryDescription, int regionId)
{
    PreventDefault();
    Employees emp = (Employees)Context.Session["Employee"];
    Territories t = new Territories();
    t.TerritoryID = territoryCode;
    t.TerritoryDescription = territoryDescription;
    t.RegionID = regionId;
    if (t.Insert() == 1)
    {
        EmployeeTerritories et = new EmployeeTerritories();
        et.TerritoryID = t.TerritoryID;
        et.EmployeeID = emp.EmployeeID;
        et.Insert();
    }
    Result.HideModal();
    Result.ExecuteOnClient(
        String.Format("$find('{0}').goToPage(-1)", 
        Context.Session["ControllerID"]));
}

VB

<ControllerAction("MyEmployeeTerritories", "createTerritory", _
                      "Insert", ActionPhase.Before)> _
    Protected Sub NewTerritorySave(ByVal territoryCode As String, _
        ByVal territoryDescription As String, ByVal regionId As Integer)
        PreventDefault()
        Dim emp As Employees = CType(Context.Session("Employee"), Employees)
        Dim t As Territories = New Territories()
        t.TerritoryID = territoryCode
        t.TerritoryDescription = territoryDescription
        t.RegionID = regionId
        If t.Insert() = 1 Then
            Dim et As EmployeeTerritories = New EmployeeTerritories()
            et.TerritoryID = t.TerritoryID
            et.EmployeeID = emp.EmployeeID
            et.Insert()
        End If
        Result.HideModal()
        Result.ExecuteOnClient( _
            String.Format("$find('{0}').goToPage(-1)", _
            Context.Session("ControllerID")))

    End Sub

Command command2 is not based on any real database table. We are intercepting a request from createTerritory view to insert a new record via NewTerritorySave method. Any data fields available in the view can be listed as parameters of the method.

First, we prevent any default logic from executing when our method exits by calling PreventDefault.

Then we use the automatically generated business objects Employees, Territories, and EmployeeTerritories to update the database. Business objects are not equipped with database connections or commands, which allows storing them safely in ASP.NET session. Data manipulation commands are executed by business objects via Data Aquarium Framework. Business objects rely on the same data controller architecture to promote business logic reuse.

Last, we have to hide the modal view displayed at the moment in the user's browser. The request is executed in the context of the client side view createTerritory. Many methods available via business rules Result property are producing small snippets of JavaScript that are being accumulated while the request is processed on the server. Nothing happens until the method exists and the batch is returned to the browser.

Most likely you would want the employee territories refreshed and display a new territory that employee is responsible for. The last line of the method sends a JavaScript command to the browser to refresh the view that has requested displayed of createTerritory view in the first place. Client ID of the view was available in NewTerritory method via Arguments.ContextKey property.

Conclusion

Simple and power business rules model of Data Aquarium Framework allows archiving functionality of a significant complexity with a very modest amount of code. Database interactions are greatly simplified by business objects automatically generated by Code OnTime Generator.

Business rules and objects are available to subscribers to premium projects only.

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.