Class Components in QBO 3

posted Sep 30, 2011, 11:24 AM by Eric Patrick   [ updated Sep 30, 2011, 2:11 PM ]

Background

The components required to fully implement a class in QBO 3 comprise:
  • Data Tier (SQL)
    • {Class}.Table.sql
    • {Class}.Keys.sql
    • {Class}.Indexes.sql
    • {Class}.Triggers.sql
  • Application Tier (C#)
    • {Class}.cs: this is the basic class file, including customize methods
    • {Class}.Field.cs: this is a partial class file, generated purely from SQL Server system tables
  • Web Tier
    • {Class}.ashx: an HttpHandler that is the web service endpoint for the class
    • {Class}.config: configuration file, include all DbStatements, for the class
    • {Class}.*.xslt: XSLT files for presentation of class-related data
    • {Class}.js: a javascript class file

Quick Example: Adding a new "Method"

Assume you wish to extend the Debt class to support showing a panel of Debt Portfolios, including Debt balances.  In QBO 2.0, we have to:
  1. Create a query (stored procedure, perhaps named pDebtPortfolioList) to return this data, 
  2. Create an XSLT to display this data,
  3. Create a custom report, or extend qbo.Debt.cs, qbo.DebtService.asmx.cs, qbo.Debt.js
In QBO 3, we have to:
  • Create a query (DbStatement in Debt.config, perhaps named PortfolioList), 
  • Create an XSLT to display this data
The DbStatement in Debt.config might look like this:

<Statement Name="PortfolioList" ReturnType="XmlReader" Query="
SELECT ...
FROM    Collection ...
    INNER JOIN Debt ...
WHERE    Collection.CollectionTemplateID = @CollectionTemplateID
FOR XML PATH('DebtItem')
"/>

The existing Debt.ashx file and DebtObject classes will automatically support this new DbStatement without any modifications, like this:
  • Debt.ashx/PortfolioList?CollectionTemplateID=4 will return raw XML
  • Debt.ashx/PortfolioList?CollectionTemplateID=4&Template=Debt.Portfolio.List.xslt will return the transformed data
In fact, you can configured the PortfolioList DbStatement to use the Debt.Portfolio.List.xslt

<Statement Name="PortfolioList" Transform="Debt.Portfolio.List.xslt" ReturnType="XmlReader" Query="
SELECT ...
FROM    Collection ...
    INNER JOIN Debt ...
WHERE    Collection.CollectionTemplateID = @CollectionTemplateID
FOR XML PATH('DebtItem')
"/>

Then, the following will return the transformed Xhtml:
  • Debt.ashx/PortfolioList?CollectionTemplateID=4 
The qbo3.DebtObject javascript class would comprise:

// Debt class.
qbo3.DebtObject = new Class({
Extends: qbo3.AbstractObject,
options: { url: 'Debt.ashx/' }
});

That's it. No need to implement javascript methods for each possible method / statement being run on the server.

Abstract Classes

Base functionality for most classes is build into three abstract classes:
  • qbo.Application.AbstractObject (C# class),
  • qbo.Application.HttpHandler (C# class), and
  • qbo3.AbstractObject (javascript class)
Invoking a method or DbStatement from javascript works like this:

var debt = new qbo3.DebtObject({target: 'myDivTag', ...some other options...});
debt.invoke('PortfolioList', {CollectionTemplateID: 4});

  • this causes the browser's XmlHttp object to get Debt.ashx/PortfolioList?CollectionTemplateID=4,
  • calling Debt.ashx's ProcessRequest() method,
  • calling HttpHandler's ProcessQuery() method, which sees the DbStatement's return type is an XmlReader and a Transform is defined
  • calling HttpHandler's ProcessQueryXhtml() method, 
  • calling AbstractObject's InvokeXmlReader() method,
  • calling AbstractObject's ExecuteXmlReader() method,
  • building a SQL statement from the configuration file, and executing it
This is powerful in the sense that you can simply define a new query and matching XSLT, and you need not wire anything else.

Of course, the downside is that the call stack is a bit longer than it would be if we had "dedicated" methods defined in Debt.ashx. If you have very frequently used methods and are worried about the performance impact of this, you are welcome to extend Debt.ashx's ProcessRequest() method to bypass this stack. For example, the current implementation has:

public override void ProcessRequest(HttpContext context) 
    switch (Operation) 
    
        default: 
            base.ProcessRequest(context); 
            break; 
    }
}

An override would appear as follows:

public override void ProcessRequest(HttpContext context) 
    switch (Operation) 
    
        case "PortfolioList":
            PortfolioList(context);
            break;
        case "MyFrequentlyUsedCustomMethod":
            MyFrequentlyUsedCustomMethod(context);
            break;
        default: 
            base.ProcessRequest(context); 
            break; 
    }
}


Comments