ASP.NET 3.5

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
ASP.NET 3.5
Sunday, September 7, 2008PrintSubscribe
Aquarium Express Primer

Aquarium Express is a new standard code generator project provided as an introduction to the capabilities of Data Aquarium Framework. This project can be used as a solid foundation for Web 2.0 applications of any complexity. Your application will be built with the state-of-the-art ASP.NET 3.5 and Ajax Control Toolkit.

Aquarium Express Primer will showcase the capabilities of Data Aquarium Framework and includes complete source code in VB.NET and Visual C#.  We will be developing a web order management application for a mail order company Northwind with Microsoft SQL Server 2005 and Code OnTime Generator. You have to have Visual Studio 2008 or free Visual Web Developer Express 2008 installed on your computer to work with the generated application.

Click here to see a live demo of Aquarium Express Primer.

image

Start the code generator and create new Aquarium Express project named Northwind. Leave default settings and complete project source code generation. A browser window will open up with a default sample page. Close the browser and run your version of Visual Studio. Select File | Open Web Site menu option and browse to [My Documents]\Code OnTime\Aquarium Express\Northwind folder. Click Open button to open the project.

Selection of Customers

Create Forms folder in the root of the web site. Right-click  Forms folder and select Add New Item option. Create a web form with name Customers and make sure to select its master page MasterPage.master available in the root of the web site. Change the page as shown below.

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

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="Header1Placeholder" runat="Server">
    Customers
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="Header2Placeholder" runat="Server">
    Northwind
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="BodyPlaceholder" runat="Server">
    <div id="CustomerList" runat="server" />
    <aquarium:DataViewExtender ID="CustomerExtender" runat="server" Controller="Customers"
        View="grid1" TargetControlID="CustomerList" />
</asp:Content>

Control DataViewExtender is a cornerstone of web applications created with Data Aquarium Framework.  Two key properties Controller and TargetControlID are providing data controller descriptor and presentation container target accordingly.  If you view the page in a browser then the following presentation is rendered.

image

Notice that whenever you click on any link in the left-most column the grid view is switch into form mode and customer details are presented. Let's change this behavior and have the page redirect to another web form and pass along ID of a selected customer. Open ~/Controllers/Customers.aspx page and change the actions with Grid scope to read as shown in example.

<actionGroup scope="Grid">
  <action commandName="Navigate" headerText="Show Orders" commandArgument="Orders.aspx?CustomerID={CustomerID}"/>
  <action commandName="Select" commandArgument="editForm1" headerText="View Customer Information"/>
  <action commandName="Edit" />
  <action commandName="Delete" confirmation="Delete?" />
  <action whenLastCommandName="Edit" commandName="Update" headerText="Save" />
  <action whenLastCommandName="Edit" commandName="Cancel" />
</actionGroup>

Reference to the CustomerID field in the curly brackets will be replaced with an actual value when the first action is executed in a web browser. This happens if user clicks on a link or selects Show Orders context menu option.

image

Order List

Add Orders web form to the ~/Forms folder while following the same routine described above. Make the page markup look like this.

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

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="Header1Placeholder" runat="Server">
    Orders
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="Header2Placeholder" runat="Server">
    Northwind
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="BodyPlaceholder" runat="Server">
    <div id="Customer" runat="server" class="CustomerInfo" />
    <aquarium:DataViewExtender ID="CustomerExtender" runat="server" Controller="Customers"
        View="editForm1" TargetControlID="Customer" ShowActionBar="false" />
    <div id="OrderList" runat="server" />
    <aquarium:DataViewExtender ID="OrderExtender" runat="server" Controller="Orders"
        View="grid1" TargetControlID="OrderList" />
</asp:Content>

Open Customers form in a web browser and select any customer. The following views will be presented.

image

The form automatically filters orders to match the customer ID in the URL of the browser. You can immediately edit customer information and make all sorts of changes to the orders . As good as this screen looks there are quite a few deficiencies. The form is too long and we don't know what is the total amount of each order. Plus we don't really want to allow any order changes and would rather use a dedicated order edit form.

First, we will make the form shorter. Add the following markup anywhere in the Content4  in Orders web form.

<div id="Customers_editForm1" style="display: none">
    <table style="width: 100%">
        <tr>
            <td>
                {CustomerID}
            </td>
            <td style="font-weight: bold">
                {CompanyName}
            </td>
            <td>
                {ContactName}
            </td>
            <td>
                {ContactTitle}
            </td>
            <td>
                {Phone}
            </td>
            <td>
                {City}
            </td>
            <td>
                {Country}
            </td>
        </tr>
    </table>
</div>

This creates an invisible form templates, which is automatically used by Web.DataView java script component of Data Aquarium Framework. The component will use the field names in the curly brackets as placeholders for the data fields rendered in a browser. Refresh Orders page and the following view will be displayed.

image

Notice that template ID is matched to editForm1 view when rendered for Customers controller. You can read additional discussion about form templates here.

This new look is a substantial improvement over the previous version. Let's take out the row with the buttons under the customer form and descriptive text above the form. This will leave a clean panel with customer information above the orders.

Create a CSS style sheet with name Northwind.css in ~/App_Themes/MyCompany folder. Enter the following text in the style sheet body.

.CustomerInfo .HeaderTextRow, .CustomerInfo .BottomButtonsRow
{
    display:none;
}

The first Div element of the form is defined with a CSS class name.

    <div id="Customer" runat="server" class="CustomerInfo" />

Class CustomerInfo allows us to customize presentation of the header row with descriptive text and the bottom action buttons row. Customization is quite simple - we are not displaying either of them. Refresh the page to see it like that.

image

Now let's add a total amount to each order line.  Data Aquarium Framework automatically parses the queries in your data controller to construct select, update, insert, and delete SQL statements. We will create an additional view in Northwind database and join this view to the query in ~/Controllers/Orders.xml file. Please create the following SQL view with SQL Management Studio or your favorite database management tool.

CREATE view [dbo].[OrderTotals]
as
select
    "OrderDetails"."OrderID", 
    sum("OrderDetails"."UnitPrice" * "OrderDetails"."Quantity" * 
        (1 - "OrderDetails"."Discount")) "ItemsTotal"
from "dbo"."Order Details" "OrderDetails"
group by "OrderDetails"."OrderID"

Then open the data controller file and change the text of command1 as shown below.

      <text>
        <![CDATA[
select
    "Orders"."OrderID" "OrderID"
    ,"Orders"."CustomerID" "CustomerID"
    ,"Customer"."CompanyName" "CustomerCompanyName"
    ,"Orders"."EmployeeID" "EmployeeID"
    ,"Employee"."LastName" "EmployeeLastName"
    ,"Orders"."OrderDate" "OrderDate"
    ,"Orders"."RequiredDate" "RequiredDate"
    ,"Orders"."ShippedDate" "ShippedDate"
    ,"Orders"."ShipVia" "ShipVia"
    ,"ShipVia"."CompanyName" "ShipViaCompanyName"
    ,"Orders"."Freight" "Freight"
    ,"Orders"."ShipName" "ShipName"
    ,"Orders"."ShipAddress" "ShipAddress"
    ,"Orders"."ShipCity" "ShipCity"
    ,"Orders"."ShipRegion" "ShipRegion"
    ,"Orders"."ShipPostalCode" "ShipPostalCode"
    ,"Orders"."ShipCountry" "ShipCountry"
    ,(cast("OrderTotals"."ItemsTotal" as money)) "ItemsTotal"
    ,(cast("OrderTotals"."ItemsTotal" + "Orders"."Freight" as money)) "OrderTotal"
from "dbo"."Orders" "Orders"
    left join "dbo"."Customers" "Customer" on "Orders"."CustomerID" = "Customer"."CustomerID"
    left join "dbo"."Employees" "Employee" on "Orders"."EmployeeID" = "Employee"."EmployeeID"
    left join "dbo"."Shippers" "ShipVia" on "Orders"."ShipVia" = "ShipVia"."ShipperID"
    left join "OrderTotals" on "Orders"."OrderID" = "OrderTotals"."OrderID"
]]>
      </text>

The main points of interest are the left join at the bottom of the query and two fields at the end of the field clause. Both fields are defined as expression, which must be enclosed with parenthesis to help the regular expressions of Data Aquarium Framework to correctly identify the expression and the corresponding alias. The left join is linking in the new view.

Add two new fields into /dataController/fields element.

<field name="ItemsTotal" type="Decimal" readOnly="true" label="Items Total" dataFormatString="c"/>
<field name="OrderTotal" type="Decimal" readOnly="true" label="Total" dataFormatString="c"/>

Make sure to mark both fields as read-only. The data format string will ensure that both fields are displayed as currency. You can use alternative syntax for the data format string: {0:c}, which is compatible with String.Format function that you know and love.

Move the grid view grid1 to be after the form view editForm1 to ensure that the edit form will become the default presentation view for Orders controller. Format fields in grid1 as displayed here.

<view id="grid1" type="Grid" commandId="command1" label="Orders">
  <headerText>This is a list of orders. </headerText>
  <dataFields>
    <dataField fieldName="OrderID" />
    <dataField fieldName="CustomerID" aliasFieldName="CustomerCompanyName" />
    <dataField fieldName="EmployeeID" aliasFieldName="EmployeeLastName" />
    <dataField fieldName="OrderDate" columns="10" />
    <dataField fieldName="RequiredDate" columns="10" />
    <dataField fieldName="ShippedDate" columns="10" />
    <dataField fieldName="ShipVia" aliasFieldName="ShipViaCompanyName" />
    <dataField fieldName="Freight" dataFormatString="c" columns="15" />
    <dataField fieldName="ItemsTotal"/>
    <dataField fieldName="OrderTotal"/>
  </dataFields>
</view>

We have introduce the primary key field OrderID, which you would likely want to serve as a visual reference for end users. Fields with item and order totals at at the end of the list. Refresh Orders page to see the following. Make sure that you can sort by order and items total.

image

The last step is to change the actions available in the context menu of the grid view. Also take out all actions that are supposed to be displayed on action bar with header text Actions.

<actionGroup scope="Grid">
  <action commandName="Navigate" commandArgument="~/Forms/Details.aspx?OrderID={OrderID}" headerText="Order Details"/>
</actionGroup>

This will present itself as illustrated in the picture.

image

Order Details

Add Details.aspx web form following the same steps that were explained when we were creating Customers.aspx and Orders.aspx forms. Format the page as explained in the example.

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

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="Header1Placeholder" runat="Server">
    Order Details
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="Header2Placeholder" runat="Server">
    Northwind
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="BodyPlaceholder" runat="Server">
    <div id="Order" runat="server" class="Order" />
    <aquarium:DataViewExtender ID="OrderExtender" runat="server" Controller="Orders"
        View="editForm1" TargetControlID="Order" ShowActionBar="false" />
    <div id="Details" runat="server" />
    <aquarium:DataViewExtender ID="DetailsExtender" runat="server" Controller="OrderDetails"
        View="grid1" TargetControlID="Details" />
</asp:Content>

As we've seen before, the page is fully functional but is somewhat difficult to use.

image

We will start with the template for the order form.

<div id="Orders_editForm1" style="display: none">
    <table style="width: 100%">
        <tr>
            <td colspan="3">
                <table style="width: 100%; border: solid 1px silver;">
                    <tr>
                        <td style="font-weight: bold">
                            {OrderID}
                        </td>
                        <td style="font-weight: bold">
                            {CustomerID}
                        </td>
                        <td style="font-weight: bold">
                            {EmployeeID}
                        </td>
                        <td style="font-weight: bold">
                            {ShipVia}
                        </td>
                        <td style="font-weight: bold">
                            {Freight}
                        </td>
                        <td style="font-weight: bold">
                            {ItemsTotal}
                        </td>
                        <td style="font-weight: bold">
                            {OrderTotal}
                        </td>
                    </tr>
                </table>
            </td>
        </tr>
        <tr>
            <td valign="top">
                <div>
                    {OrderDate}</div>
                <div>
                    {RequiredDate}</div>
                <div>
                    {ShippedDate}</div>
            </td>
            <td valign="top">
                <div>
                    {ShipName}</div>
                <div>
                    {ShipAddress}</div>
                <div>
                    {ShipCity}</div>
            </td>
            <td valign="top">
                <div>
                    {ShipRegion}</div>
                <div>
                    {ShipPostalCode}</div>
                <div>
                    {ShipCountry}</div>
            </td>
        </tr>
    </table>
</div>

This template breaks the order fields in three groups. This provides instant improvement in presentation.

image

Fields ItemsTotal and OrderTotal are missing in the rendered presentation. Make the following changes to editForm1 in Orders.xml to add the missing fields to the form.

<view id="editForm1" type="Form" commandId="command1" label="Order">
  <headerText>Please review orders information below. Click Edit to change this record, click Delete to delete the record, or click Cancel/Close to return back.</headerText>
  <categories>
    <category headerText="Orders">
      <description>These are the fields of the orders record that can be edited.</description>
      <dataFields>
        <dataField fieldName="CustomerID" aliasFieldName="CustomerCompanyName" />
        <dataField fieldName="EmployeeID" aliasFieldName="EmployeeLastName" />
        <dataField fieldName="OrderDate" columns="10" />
        <dataField fieldName="RequiredDate" columns="10" />
        <dataField fieldName="ShippedDate" columns="10" />
        <dataField fieldName="ShipVia" aliasFieldName="ShipViaCompanyName" />
        <dataField fieldName="Freight" dataFormatString="c" columns="15" />
        <dataField fieldName="ShipName" columns="40" />
        <dataField fieldName="ShipAddress" />
        <dataField fieldName="ShipCity" columns="15" />
        <dataField fieldName="ShipRegion" columns="15" />
        <dataField fieldName="ShipPostalCode" columns="10" />
        <dataField fieldName="ShipCountry" columns="15" />
        <dataField fieldName="ItemsTotal"/>
        <dataField fieldName="OrderTotal"/>
      </dataFields>
    </category>
  </categories>
</view>

We will change behavior of the action buttons by replacing definitions for Cancel and Close commands.

<actionGroup scope="Form">
  <action commandName="Edit" />
  <action commandName="Delete" confirmation="Delete?" />
  <action commandName="Back" headerText="Close"/>
  <action whenLastCommandName="Edit" commandName="Update" headerText="OK" />
  <action whenLastCommandName="Edit" commandName="Delete" confirmation="Delete?" />
  <action whenLastCommandName="Edit" commandName="Select" commandArgument="editForm1" headerText="Cancel" />
  <action whenLastCommandName="New" commandName="Insert" headerText="OK" />
  <action whenLastCommandName="New" commandName="Cancel" />
</actionGroup>

Close button simply directs the browser to go back one step in the history, which will bring user back to the list of orders. Button with Cancel header selects the current record and displays it in editForm1, which replaces the standard behavior of displaying the first view defined in the data controller.

Also change the item presentation style of field ShipVia from Lookup to DropDownList. There are only three shippers in the database and it makes little sense to display a popup window to select a shipper. Cosmetic changes to the labels of the lookup fields will make the form similar to the one in the picture when you edit any order.

image

Next we will introduce ExtendedPrice field to Order Details and get rid of order customer, employee, and shipping information, which is already being displayed above the details. Open ~/Controllers/OrderDetails.xml data controller descriptor and make the following changes.

1.  Change text element to command1 to calculate the extended item price by taking into account item quantity and discount. Make sure to use parenthesis around the expression.

      <text>
    <![CDATA[
select
    "OrderDetails"."OrderID" "OrderID"
    ,"Order"."CustomerID" "OrderCustomerID"
    ,"OrderCustomer"."CompanyName" "OrderCustomerCompanyName"
    ,"OrderEmployee"."LastName" "OrderEmployeeLastName"
    ,"OrderShipVia"."CompanyName" "OrderShipViaCompanyName"
    ,"OrderDetails"."ProductID" "ProductID"
    ,"Product"."ProductName" "ProductProductName"
    ,"ProductCategory"."CategoryName" "ProductCategoryCategoryName"
    ,"ProductSupplier"."CompanyName" "ProductSupplierCompanyName"
    ,"OrderDetails"."UnitPrice" "UnitPrice"
    ,"OrderDetails"."Quantity" "Quantity"
    ,"OrderDetails"."Discount" "Discount"
    ,("OrderDetails"."UnitPrice" * "OrderDetails"."Quantity" * (1 - "OrderDetails"."Discount")) "ExtendedPrice"
from "dbo"."Order Details" "OrderDetails"
    left join "dbo"."Orders" "Order" on "OrderDetails"."OrderID" = "Order"."OrderID"
    left join "dbo"."Customers" "OrderCustomer" on "Order"."CustomerID" = "OrderCustomer"."CustomerID"
    left join "dbo"."Employees" "OrderEmployee" on "Order"."EmployeeID" = "OrderEmployee"."EmployeeID"
    left join "dbo"."Shippers" "OrderShipVia" on "Order"."ShipVia" = "OrderShipVia"."ShipperID"
    left join "dbo"."Products" "Product" on "OrderDetails"."ProductID" = "Product"."ProductID"
    left join "dbo"."Categories" "ProductCategory" on "Product"."CategoryID" = "ProductCategory"."CategoryID"
    left join "dbo"."Suppliers" "ProductSupplier" on "Product"."SupplierID" = "ProductSupplier"."SupplierID"
]]></text>

2. Add extender price field to fields element in OrderDetails.xml.

<field name="ExtendedPrice" type="Decimal" allowNulls="true" readOnly="true" label="Extended Price" dataFormatString="c"/>

3. Change view grid1 to display the data fields as in this snippet.

<view id="grid1" type="Grid" commandId="command1" label="Order Details">
  <headerText>This is a list of order details. </headerText>
  <dataFields>
    <dataField fieldName="OrderID" aliasFieldName="OrderCustomerID" />
    <dataField fieldName="ProductID" aliasFieldName="ProductProductName" />
    <dataField fieldName="UnitPrice" dataFormatString="c" columns="15" />
    <dataField fieldName="Quantity" columns="15" />
    <dataField fieldName="Discount" columns="15" />
    <dataField fieldName="ProductCategoryCategoryName" columns="15" />
    <dataField fieldName="ProductSupplierCompanyName" columns="40" />
    <dataField fieldName="ExtendedPrice"/>
  </dataFields>
</view>

4. Add the following CSS class definition to Northwind.css to hide the top row of action buttons. Here we are taking advantage of Orders class name attribute of the first div element of the form.

.Order .TopButtonsRow
{
    display: none;
}

The order details are nicely presented to the viewer with the extended price in the last column.

image

Automatic Refreshing When Order Details are Changed

Changes to the order freight are instantly reflected on screen when you save changes. Not so when order details are changed. Order totals must be recalculated whenever a new item is ordered, existing item is modified, or deleted. The product price is not being transferred to the UnitPrice of Order Details record when you add a new item . You have enter the product unit price in on your own. It will default to zero if you leave the field blank.

Field UnitPrice can be safely omitted from the view createForm1 if we automatically copy the product price as soon as order details record has been inserted into database. Change createForm1 in OrderDetails.xml as follows.

view id="createForm1" type="Form" commandId="command1" label="New Order Details">
  <headerText>Please fill this form and click OK button to create a new order details record. Click Cancel to return to the previous screen.</headerText>
  <categories>
    <category headerText="New Order Details">
      <description>Complete the form. Make sure to enter all required fields.</description>
      <dataFields>
        <dataField fieldName="OrderID" aliasFieldName="OrderCustomerID" />
        <dataField fieldName="ProductID" aliasFieldName="ProductProductName" />
        <!--<dataField fieldName="UnitPrice" dataFormatString="c" columns="15" />-->
        <dataField fieldName="Quantity" columns="15" />
        <dataField fieldName="Discount" columns="15" />
      </dataFields>
    </category>
  </categories>
</view>

This will result in the following presentation when you add a new item. Note that field OrderID is not displayed since it is being filtered by in Details.aspx via OrderID parameter in the page URL.

image

Next we will write some code. Right-click App_Code folder in the project tree and add Class1 class to the web site code base. This class will implement a custom action handler for OrderDetails data controller. Change OrderDetails.xml to link the custom action handler to Data Aquarium Framework.

<dataController .... xmlns="urn:schemas-codeontime-com:data-aquarium" actionHandlerType="Class1">

Here is C# code that you need to enter.

using System;
using MyCompany.Data;

public class Class1 : ActionHandlerBase
{
    protected override void AfterSqlAction(ActionArgs args, ActionResult result)
    {
        if (args.CommandName == "Insert")
        {
            double price = 0;
            using (SqlText findPrice = new SqlText(
                "select UnitPrice from Products where ProductId = @ProductID"))
            {
                findPrice.AddParameter("@ProductId", args["ProductID"].Value);
                price = Convert.ToDouble(findPrice.ExecuteScalar());
            }
            using (SqlText updatePrice = new SqlText(
                "update [Order Details] set UnitPrice=@UnitPrice where OrderID=@OrderID and ProductId=@ProductID"))
            {
                updatePrice.AddParameter("@UnitPrice", price);
                updatePrice.AddParameter("@OrderID", args["OrderID"].Value);
                updatePrice.AddParameter("@ProductID", args["ProductID"].Value);
                updatePrice.ExecuteNonQuery();
            }
        }
        if (args.ContextKey.EndsWith("DetailsExtender"))
            result.ClientScript =
                "Web.DataView.find('OrderExtender').goToPage(-1);" +
                "this.set_lastCommandName(null);" +
                "this.goToView('grid1');";
    }
}

Here is VB.NET equivalent of the same class.

Imports Microsoft.VisualBasic
Imports MyCompany.Data

Public Class Class1
    Inherits ActionHandlerBase

    Protected Overrides Sub AfterSqlAction(ByVal args As MyCompany.Data.ActionArgs, ByVal result As MyCompany.Data.ActionResult)
        If args.CommandName = "Insert" Then
            Dim price As Double = 0
            Using findPrice As SqlText = New SqlText( _
                "select UnitPrice from Products where ProductId = @ProductID")
                findPrice.AddParameter("@ProductID", args("ProductID").Value)
                price = Convert.ToDouble(findPrice.ExecuteScalar())
            End Using
            Using updatePrice As SqlText = New SqlText( _
                "update [Order Details] set UnitPrice=@UnitPrice where OrderID=@OrderID and ProductId=@ProductID")
                updatePrice.AddParameter("@UnitPrice", price)
                updatePrice.AddParameter("@OrderID", args("OrderID").Value)
                updatePrice.AddParameter("@ProductID", args("ProductID").Value)
                updatePrice.ExecuteNonQuery()
            End Using
        End If
        If args.ContextKey.Contains("DetailsExtender") Then
            result.ClientScript = _
                "Web.DataView.find('OrderExtender').goToPage(-1);" + _
                "this.set_lastCommandName(null);" + _
                "this.goToView('grid1');"
        End If
    End Sub

End Class

Let's analyze the code.

Our action handler is automatically invoked by the framework whenever an action execution is requested by java script Web.DataView component in a browser and OrderDetails data controller is involved.

We want to intercept each Insert command and will copy UnitPrice from dbo.Products table to dbo.[Order Details] table. Data Aquarium Framework provides two utility classes, SqlStoredProcedure and SqlText, to simplify execution of SQL stored procedures and arbitrary SQL commands. You can use your favorite data access library to interact with the database. 

The purpose of the second conditional statement is to intercept any actions coming from client java script Web.DataView component that was instantiated by DataViewExtender with the name DetailsExtender. This conditional statement simply assigns client-side java script statements to the CientScript property of the result parameter. This java script will be executed in the browser when the action returns control from the server code.

The first java script statement will find OrderExtender and ask it to navigate to page with index -1. This will cause order summary view to refresh itself.

Next the last executed command is cleared to allow action state machine of Web.DataView to continue correct operation.  Actions are always executed in the context of last action command name. Here we want to clear the last action command name as it is being done by default.

The last java script statement will force the order details view to display the grid view grid1. Insert and Update commands are initiated from the form view and this statement is required to make sure that the form view is switched to the grid presentation style.

If there is no client script returned form the server upon completion of action execution then java script statements two and three are automatically performed by Web.DataView. We have decided to force the order view to be refreshed and now we have to take care of the low level details on our own.

Try playing with order details to make that order summary is automatically updated to reflect the order changes.

Inventory Manager

Now we will put together an inventory manager for our mail order company. Create a new page Inventory.aspx in ~/Forms folder of the web site. Format the page to include a TabContainer with a few TabPanel components. Both controls are a part of Ajax Control Toolkit. Tag prefix act is registered in web.config file of your web site. Data Aquarium Framework relies on the toolkit for a significant portion of its client-side functionality.

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

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="Header1Placeholder" runat="Server">
    Inventory
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="Header2Placeholder" runat="Server">
    Northwind
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="BodyPlaceholder" runat="Server">
    <act:TabContainer ID="InventoryTabs" runat="server">
        <act:TabPanel ID="ProductsTab" runat="server" HeaderText="Products">
            <ContentTemplate>
                <div id="Products" runat="server" />
                <aquarium:DataViewExtender ID="ProductsExtender" runat="server" 
                    Controller="Products"
                    TargetControlID="Products" />
            </ContentTemplate>
        </act:TabPanel>
        <act:TabPanel ID="SuppliersTab" runat="server" HeaderText="Suppliers">
            <ContentTemplate>
                <div id="Suppliers" runat="server" />
                <aquarium:DataViewExtender ID="SuppliersExtender" runat="server" 
                    Controller="Suppliers"
                    TargetControlID="Suppliers" PageSize="5" />
                <br />
                <div id="SupplierProducts" runat="server" />
                <aquarium:DataViewExtender ID="SupplierProductsExtender" runat="server" 
                    Controller="Products"
                    TargetControlID="SupplierProducts" FilterSource="SuppliersExtender" 
                    FilterFields="SupplierID" PageSize="5" />
            </ContentTemplate>
        </act:TabPanel>
        <act:TabPanel ID="CategoriesTab" runat="server" HeaderText="Categories">
            <ContentTemplate>
                <div id="Categories" runat="server" />
                <aquarium:DataViewExtender ID="CategoriesExtender" runat="server" 
                    Controller="Categories" TargetControlID="Categories" PageSize="5" />
                <br />
                <div id="CategoryProducts" runat="server" />
                <aquarium:DataViewExtender ID="CategoryProductsExtender" runat="server" 
                    Controller="Products" TargetControlID="CategoryProducts" 
                    FilterSource="CategoriesExtender" FilterFields="CategoryID"
                    PageSize="5" />
            </ContentTemplate>
        </act:TabPanel>
    </act:TabContainer>
</asp:Content>

Open Inventory.aspx in a web browser.

image

Products tab is displaying a list of products, while Suppliers and Categories tabs are presenting the corresponding entities with products linked to them in master-detail relationship. Users can quickly create new inventory entities and browse them as they wish.

image

We can make few more enhancements to the product management by introducing additional product views. Open ~/Controllers/Products.xml and add two commands just after command1.

    <command id="command2" type="Text">
      <text>
        <![CDATA[
select
    "Products"."ProductID" "ProductID"
    ,"Products"."ProductName" "ProductName"
    ,"Products"."SupplierID" "SupplierID"
    ,"Supplier"."CompanyName" "SupplierCompanyName"
    ,"Products"."CategoryID" "CategoryID"
    ,"Category"."CategoryName" "CategoryCategoryName"
    ,"Products"."QuantityPerUnit" "QuantityPerUnit"
    ,"Products"."UnitPrice" "UnitPrice"
    ,"Products"."UnitsInStock" "UnitsInStock"
    ,"Products"."UnitsOnOrder" "UnitsOnOrder"
    ,"Products"."ReorderLevel" "ReorderLevel"
    ,"Products"."Discontinued" "Discontinued"
from "dbo"."Products" "Products"
    left join "dbo"."Suppliers" "Supplier" on "Products"."SupplierID" = "Supplier"."SupplierID"
    left join "dbo"."Categories" "Category" on "Products"."CategoryID" = "Category"."CategoryID"
where
  "Products"."Discontinued" = 0
]]>
      </text>
    </command>
    <command id="command3" type="Text">
      <text>
        <![CDATA[
select
    "Products"."ProductID" "ProductID"
    ,"Products"."ProductName" "ProductName"
    ,"Products"."SupplierID" "SupplierID"
    ,"Supplier"."CompanyName" "SupplierCompanyName"
    ,"Products"."CategoryID" "CategoryID"
    ,"Category"."CategoryName" "CategoryCategoryName"
    ,"Products"."QuantityPerUnit" "QuantityPerUnit"
    ,"Products"."UnitPrice" "UnitPrice"
    ,"Products"."UnitsInStock" "UnitsInStock"
    ,"Products"."UnitsOnOrder" "UnitsOnOrder"
    ,"Products"."ReorderLevel" "ReorderLevel"
    ,"Products"."Discontinued" "Discontinued"
from "dbo"."Products" "Products"
    left join "dbo"."Suppliers" "Supplier" on "Products"."SupplierID" = "Supplier"."SupplierID"
    left join "dbo"."Categories" "Category" on "Products"."CategoryID" = "Category"."CategoryID"
where
  "Products"."Discontinued" = 0 and
  "Products"."UnitsInStock" <= "Products"."ReorderLevel"
]]>
      </text>
    </command>

Command command2 selects products that are not discontinued. Command command3 selects current products with low stock. Notice that all three commands are having the same set of output columns. This is very important and allows Web.DataView component and supporting server code of the framework to correctly display multiple views of the same data. The where clause allows you to limit the data according to some predefined criteria. You can also add an order by clause to provide a default sort order for each command if you wish.

Next insert two views with type grid just above the grid view grid1.

<view id="grid2" type="Grid" commandId="command2" label="Current Products">
  <headerText>This is a list of current products. </headerText>
  <dataFields>
    <dataField fieldName="ProductName" columns="40" />
    <dataField fieldName="SupplierID" aliasFieldName="SupplierCompanyName" />
    <dataField fieldName="CategoryID" aliasFieldName="CategoryCategoryName" />
    <dataField fieldName="QuantityPerUnit" columns="20" />
    <dataField fieldName="UnitPrice" dataFormatString="c" columns="15" />
    <dataField fieldName="UnitsInStock" columns="15" />
    <dataField fieldName="UnitsOnOrder" columns="15" />
    <dataField fieldName="ReorderLevel" columns="15" />
  </dataFields>
</view>
<view id="grid3" type="Grid" commandId="command3" label="Products To Order">
  <headerText>This is a list of products with low stock. </headerText>
  <dataFields>
    <dataField fieldName="ProductName" columns="40" />
    <dataField fieldName="SupplierID" aliasFieldName="SupplierCompanyName" />
    <dataField fieldName="CategoryID" aliasFieldName="CategoryCategoryName" />
    <dataField fieldName="QuantityPerUnit" columns="20" />
    <dataField fieldName="UnitPrice" dataFormatString="c" columns="15" />
    <dataField fieldName="UnitsInStock" columns="15" />
    <dataField fieldName="UnitsOnOrder" columns="15" />
    <dataField fieldName="ReorderLevel" columns="15" />
  </dataFields>
</view>

Both views do not display Discontinued field since only active products are included in the record set produced by corresponding commands. We have also provided alternative header text and label for each view to make it easier to understand the purpose of each of them. Data views can have a different set of fields unlike the commands that must return the same set of columns at all times.

This is how the view selector is presented now to users on all tabs of the inventory manager.

image

The same views are also available in order management screens when user adds a new product or changes existing order details.

image

Conclusion

Few products on the market allow you to create modern AJAX applications as Aquarium Express does. Data Aquarium Framework provides additional tools for professional developers making it possible to use the framework for many aspects of web application development.

Sunday, August 17, 2008PrintSubscribe
Features of Master/Detail Presentation

Excellent master/detail support in Data Aquarium Framework comes with some great premium features, which typically require a lot of custom coding of high complexity.

Simple Markup

The standout feature is the simplicity of page definitions.

Consider a web form that displays customers and their orders. A considerable amount of markup is required to define a page like that if you are using standard ASP.NET components, such as GridView, DetailsView, and ObjectDataSource.

Here is a page fragment in a Data Aquarium Framework application that displays customers and orders:

    <!-- presentation of customers -->
    <div id="Customers" runat="server">
    </div>
    <aquarium:DataViewExtender ID="CustomersExtender" runat="server" TargetControlID="Customers"
        Controller="Customers" PageSize="3" />
    <!-- presentation of orders -->
    <div id="Orders" runat="server">
    </div>
    <aquarium:DataViewExtender ID="OrdersExtender" runat="server" TargetControlID="Orders"
        Controller="Orders" PageSize="3" FilterSource="CustomersExtender" FilterFields="CustomerID" />

A couple of div elements and a couple of DataViewExtender components is all what is needed to render user interface like the one you can see live here.

This is an example of the discussed master/detail screen. It shows Customers, Orders, and Order Details.

AJAX and server components of Data Aquarium Framework implement a centralized declarative user interface programming model. Your typical ASP.NET application is likely based on a page-centric model. Standard ASP.NET application pages host components such as GridView and DetailsView with their columns and fields predefined. The framework is relying on reusable data controller descriptors to get the metadata required to rendered user interface presentation. Follow the link to see a sample data controller.

The centralized definition of user interface elements allows unmatched flexibility in user interface development. The same views defined once in a data controller can be referred in dozens of pages. These views are displayed automatically in dynamic lookups without any coding.

You can connect data controller views in all sorts of ways without writing any code. Simply set the FilterSource and FilterFields properties of detail DataViewExtender components to create complex master/detail relationships on a web form. Use the technique shown in the sample markup above.

Properties FilterSource and FilterFields are used to construct efficient dynamic SQL statements that are executed by your database server. Data Aquarium Framework packages the results of the queries into arrays of values and delivers them to the client components running on a page. AJAX components of the framework will render an HTML markup and replace fragments of the page to provide smooth user experience.

Load-on-Demand

Try the sample tabbed presentation of tables in the Data Aquarium Framework application created from the Northwind database.

image

When you open the sample page for first time Categories and Products are dynamically loaded from the server and displayed. The act of opening a page is causing the server to render the tabbed user interface with Ajax Control Toolkit components TabContainer and TabPanel that you are seeing on the screen shot. The Web.DataView AJAX components of Data Aquarium Framework are embedded in the tabs. These components are making two additional requests to get just enough data to present customers and orders on the form.

There are many more tabs with Web.DataView component instances matched to the rest of the Northwind database tables and connected in master/detail fashion. If all of them were executing data retrieval requests at the same time then that would have created dozens of additional server interactions. Many of this requests could be totally without purpose since an application user is likely not to look at all of them.

Click on the Customers tab and notice that the customer data has been requested on-demand and displayed shortly after you have click on the tab.

image

Data Aquarium Framework components automatically determine the exact amount of data that is needed to present in the user interface views that are actually visible to a user. Physical data retrieval happens when you select a tab and bring invisible views in focus.

Automatic Hiding of Filter Fields

If you change the markup of DataViewExtender components by removing FilterSource and FilterFields properties then the runtime screen will look similar to the one below if you select the first customer and sort orders by customer company name.

image

Notice that the orders section is displaying a customer company name for each order. This is great if both views are completely independent but is not desirable if you have a master / detail presentation. All displayed orders are related to the same selected customer in that case. A repeated customer name on each order line takes up the valuable real estate of the page and should be eliminated.

Restore the FilterSource and FilterFields properties on OrdersExtender and notice that orders view automatically hides the customer company name without any extra coding.

image

If you were to link orders to employees then the employee last name column would disappear.

Primary Key Inheritance

Navigate to the sample application and try to add a new product in any category. First select a category. Sort products by name and then click on New menu option on the action bar of the products view. Click on New Products menu item. Enter A new product and press OK button.

image

The following view will be presented next.

image

The new product is automatically linked to selected master category without any coding. The primary key field values of selected master record are automatically copied to the detail record foreign key fields when the detail record is inserted into database.

This feature is automatically enabled when you set a master/detail connection via FilterSource and FilterFields properties of DataViewExtender. You can create custom action handlers to provide additional data processing before and after the SQL command.

Support For Standard Data Components

There will be times when you need to write some highly custom master/detail web forms and would rather rely on standard ASP.NET components to do so. Data Aquarium Framework provides ControllerDataSource data source component that works great with standard components or any other commercial library that you own.

Generate an application based on Data Aquarium project , open the generated code in Visual Studio 2008 or Visual Web Developer Express 2008 and and create a new page StandardMD.aspx based on the MasterPage.master in the web site root. Enter the following markup:

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

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="Header1Placeholder" runat="Server">
    Standard Master/Detail
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="Header2Placeholder" runat="Server">
    Northwind
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="BodyPlaceholder" runat="Server">
    <aquarium:ControllerDataSource ID="CustomersCDS" runat="server" DataController="Customers">
    </aquarium:ControllerDataSource>
    <asp:GridView ID="Customers" runat="server" DataSourceID="CustomersCDS" AllowPaging="true"
        AllowSorting="true" AutoGenerateDeleteButton="true" AutoGenerateEditButton="true"
        AutoGenerateSelectButton="true" DataKeyNames="CustomerID">
    </asp:GridView>
    <aquarium:ControllerDataSource ID="OrdersCDS" runat="server" DataController="Orders">
        <FilterParameters>
            <asp:ControlParameter Name="CustomerID" ControlID="Customers" PropertyName="SelectedValue" />
        </FilterParameters>
    </aquarium:ControllerDataSource>
    <asp:GridView ID="Orders" runat="server" DataSourceID="OrdersCDS" AllowPaging="true"
        AllowSorting="true" AutoGenerateDeleteButton="true" AutoGenerateEditButton="true"
        AutoGenerateSelectButton="true" DataKeyNames="OrderID">
    </asp:GridView>
</asp:Content>

The only unusual code here is the presence of ControllerDataSource instances. The standard GridView components on the page are bound to the data sources via DataSourceID property. Data source OrdersCDS is set to behave as a detail of the Customers grid view and will filter data whenever a customer is selected on the page. If you run the page and select a customer then the following user interface will be presented.

image

Nothing really fancy. But if you do look closer then you will notice that you can page and sort your records. You can edit, and delete anything on the page. The orders grid is automatically refreshed when a customer is selected.

In fact, you can have thousands of records in both grids and they will work extremely fast thanks to the on-demand data retrieval built into Data Aquarium Framework. Only the data that needs to be presented on the page is actually retrieved from the database. The exact same code is being executed on the sever in response to requests by client-side AJAX components when they need to displays data. That sort of functionality is hard to accomplish unless you resort to use ObjectDataSource components and write custom business objects to support them.

With a little bit of editing you can have this page look like the one below.

image

To speed up customization select the data source instances of this sample page and execute Refresh Schema command in the smart tag options of each data source. This will automatically create fields for your grid views.

Remember that ControllerDataSource is working with any standard or custom components that are compatible with ASP.NET data binding architecture.

Conclusion

Data Aquarium Framework provides significant productivity features to developers building master / detail web forms. Modern AJAX-based user interface components of the framework will automatically handle many complicated issues that are commonly encountered by application developers. Standard ASP.NET web forms are supported as well via ControllerDataSource component.

Saturday, August 16, 2008PrintSubscribe
Master/Detail in One Page

Most applications require a master/detail presentation of data on a form. This presentation styles has been perfected in desktop applicati0ns, but is still a difficult task in a web application. Data Aquarium Framework makes presenting master/detail data easy.

Let's set a master detail presentation in application that was generated from Northwind database. The sample application created by Code OnTime Generator already includes a master/detail demo page that presents and links all your tables simultaneously. Here is a screen shot:

MasterDetail

We will create a brand new page and link together Customers, Orders, and Order Details.

Start by generating a new Data Aquarium Framework project. Open the generated project in Visual Studio 2008 or Visual Web Developer Express 2008 and add a new web form OrderManager.aspx to the root of the site. Make sure to select a master page when you are adding this new form to the project.

Edit the page to make it look like this:

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

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="Header1Placeholder" runat="Server">
    Order Manager
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="Header2Placeholder" runat="Server">
    Northwind
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="BodyPlaceholder" runat="Server">
    <!-- presentation of customers -->
    <div id="Customers" runat="server" style="margin-bottom: 4px;">
    </div>
    <aquarium:DataViewExtender ID="CustomersExtender" runat="server" TargetControlID="Customers"
        Controller="Customers" PageSize="3" />
    <!-- presentation of orders -->
    <div id="Orders" runat="server" style="margin-bottom: 4px;">
    </div>
    <aquarium:DataViewExtender ID="OrdersExtender" runat="server" TargetControlID="Orders"
        Controller="Orders" PageSize="3" FilterSource="CustomersExtender" FilterFields="CustomerID" />
    <!-- presentation of order details -->
    <div id="OrderDetails" runat="server">
    </div>
    <aquarium:DataViewExtender ID="OrderDetailsExtender" runat="server" TargetControlID="OrderDetails"
        Controller="OrderDetails" PageSize="5" FilterSource="OrdersExtender" FilterFields="OrderID" />
</asp:Content>

Master/detail link is established between DataViewExtender components. Two properties are involved.

FilterSource property refers to the master view extender. For example, OrdersExtender is linked to CustomersExtender.

Property FilterFields identifies the fields in the data controller that will be filtered with values from the primary keys selected in the view of the master extender when rendered in a browser. The client java script component Web.DataView will try to match the fields of the detail view with selected key fields in the master by matching their names. Field CustomerID of the Orders view will easily match to the name of the primary key field in the Customers view. If the field names are different then the ordinal position of the fields in the filter is being used for matching.

You can set up unlimited number of the master detail links within the same page. No post backs are executed. The page section refreshing is smooth and the application behaves a lot like a desktop program. All views provide sorting and adaptive filtering. Filtering automatically recognizes master details links and reduces the number of available filter-by-example options.

Select FileView in Browser option in the menu of your development tool. The page will load the data and if you start interacting with the page the it may look like that.

OrderManager

The live version of this page is hosted here.

Select a company name and orders section will display relevant orders. Select an order and details of the order will show up. Select and edit an order and your screen may look like the one below. Notice that you can even add new lookup values for Employees, Shippers, and Products in place if the lookup values are not available without making a single page refresh or ever leaving the page.

OrderManagerEdit

Considering that minuscule amount of markup code that you actually have to write there is no other tool on the market that can do the same. Subscribe to the premium projects and generate your own application. Many more exciting features and code generator projects are coming up and will be released to premium project subscribers. Post your questions or request new feature on our forum.