Release Notes

qbo.Message Substitution

posted Jan 6, 2017, 5:48 PM by Eric Patrick

The qbo.Message module supports curly-brace substitution for Subject, BodyHTML and BodyText fields. For example:

Message/Save?Object=Contact&ObjectID=1&Subject=Hello {Suffix} {LastName}

will replace Suffix and LastName with the corresponding fields from the Contact row for ContactID = 1. Technically, each curly brace expression is evaluated against the Message's parent's summary data. If a full XPath expression is not used, we use //{expression} (e.g. //Suffix or //LastName in the example above).

There are some use cases where this can be inappropriate. For example, email body's may contains text that include curly-brace expressions that are not intended to be XPath expressions. In such situations there are two application settings that apply:
  • qbo.Message.Properties.Settings (maintained from Design > Configuration > Modules > Message, Settings panel)
    • MergeSubstitution: boolean, defaults to true
    • SuppressSubstitutionErrors: boolean, defaults to true
If SuppressSubstitutionErrors is true, Message/SetDefualts will ignore any errors encountered when trying to evaluate an XPath expression during substitution. For example:

Message/Save?Object=Contact&ObjectID=1&BodyText=This is not valid xpath: {Bad XPath Here}

In this case, this is the same as:

Message/Save?Object=Contact&ObjectID=1&BodyText=This is not valid xpath:

If SuppressSubstitutionErrors is false, the same call will raise an error.

In some cases, you may have content that uses curly-brace expressions that are not intended to be XPath expressions. For example, I might post the contents of this blog post as a Message. To prevent the Message module from performing the substitution, I can override the default MergeSubstitution setting with a parameter:

Message/Save?Object=Contact&ObjectID=1&BodyText=This is a lesson on {curly braces}!&MergeSubstitution=false

Note that if the message is template-based, MergeSubstitution parameters do nothing; substitution is always attempted. For example:

Message/Save?Object=Contact&ObjectID=1&Template=MyTemplate&BodyText=This is a lesson on {curly braces}!&MergeSubstitution=false

is the same as:

Message/Save?Object=Contact&ObjectID=1&Template=MyTemplate&BodyText=This is a lesson on {curly braces}!

qbo.Attachment Enhancements

posted Dec 22, 2016, 7:07 AM by Eric Patrick   [ updated Dec 22, 2016, 7:15 AM ]


The standard Document Search panel has been extended to support new features, including:
  • Toggle Description: a Description, if present, can be displayed. OCR'd documents populate the Description be default.
    • Description may also be toggled by double-clicking on the white space of a document row

  • Zip and Download: select multiple documents, zip them, and download the zip file. This does not create a new Attachment.

  • Zip and Save: same as Zip and Download, but creates a new Attachment

  • Zip and Email: same as Zip and Save, including a password to protected the zip file with, and a prompt for email addresses to email the zip file to

  • Convert to PDF: convert selected document(s) (such as TIFF images) to PDF

  • Merge to Single PDF: merges selected documents into a single PDF file

  • OCR: OCR the text of a document, saving the text to the Description field. This operation is queued, as it is typically long-running.
    • If there is no description present, the Descirption field (if visible) offers an OCR button

Technical Details

These new features require the installation of three plugins:
  1. qbo.Attachment.ABCPDF: handles Convert to PDF, Merge to Single PDF (see Plugins > ABCPDF > qbo.ABCPDF.sln)
  2. qbo.Attachment.GoogleDrive: handles OCR (see Plugins > Google > qbo.Google.sln)
  3. qbo.Attachment.DotNetZip: handles ZIP functionality (see Plugins > DotNetZip > qbo.DotNetZip.sln)
If you do not want to install these plugins for any reason, you can disable these advanced UI features via the EnableStandardServicesFromUI application setting:
  • Design > Configuration > Modules > Attachment
  • Click on the Setting tab
  • Edit the 'EnableStandardServicesFromUI' setting, and set it to false

qbo.Import: ImportFile/Apply

posted Dec 21, 2016, 12:28 PM by Eric Patrick   [ updated Dec 21, 2016, 12:42 PM ]


The qbo.Import module has been extended to support bulk-applying a method signature to every row returned by a statement. Examples include:
  • Add a workflow to all loans matching some search criteria
  • Add a task to all foreclosures matching some search criteria
  • Send an email to all contacts matching some search criteria

User Interface

The Loan.Search.xslt has been extended to support adding tasks and workflows. For example, to add a workflow to all loans in CA with a UPB greater than $500,000
  • Navigate to Loans, click the advanced search dropdown, and choose State: CA and UPB: 500,000-10,000,000 and search
  • From the result's Option menu, choose Add Workflow
  • From the Choose Workflow popup, choose apply to 'All Matching Records', choose your workflow, and click Save
The popup that invokes ImportFile/Apply is GenericTemplate.popup.xslt, and makes the following assumptions:
  • If you choose 'Selected Records', Apply will only be called for the selected rows, regardless of how many rows where returned
    • in this case, Apply is executed in real time, and the user must wait for the results to finish processing before navigating away from the page
    • the user will get an alert '{X} row affected', where X is the number of rows that the method signature was applied to
  • If you choose 'All Matching Records', Apply will be called for every row returned by the query, regardless of any rows that are checked (or not checked)
    • in this case, Apply is queued, so the user does not need to wait for the results
    • the user will get an alert that the request was queued, and a confirmation number (the Queue.MessageID)

API Details

The method signature for ImportFile/Apply is:

ImportFile/Apply?ClassName={ClassName}&Operation={Operation}&{Parameters to pass to ClassName/Operation}&Signature={Method Signature}

The Signature parameter may use substitution in it's query string of parameters. If substitution is specified, the parameters are substituted against the row of data returned by ClassName/Operation. For example:

ImportFile/Apply?ClassName=Loan&Operation=SmartSearch&SmartSearch=123123&Signature=Decision/Save?Object=Loan%26ObjectID={LoanID}%26Template=Hello World

does the following:
  • Executes Loan/SmartSearch?SmartSearch=123123
  • For each row returned, builds a method signature from Decision/Save?Object=Loan&ObjectID={LoanID}&Template=Hello World
    • this will replace {LoanID} with the LoanID column returned by Loan/SmartSearch
    • if the Hello World workflow already exists for a loan returned by the operation, normal repeatability rules apply
    • any column from Loan/SmartSearch may be used in this method signature
A more complex example is:

ImportFile/Apply?ClassName=Contact&Operation=Search&State=MA&Signature=Message/Send?ToAddress={Email}%26Subject=Hello {FirstName}%26BodyText=Welcome to QBO, {Suffix} {LastName}!

does the following:
  • Executes Contact/Search?State=MA
  • For each row returned, builds a method signature from Message/Send?ToAddress={Email}&Subject=Hello {FirstName}&BodyText=Welcome to QBO, {Suffix} {LastName}!
    • this will replace {Email}, {FirstName}, {Suffix} and {LastName} with the matching column returned by Contact/Search
Beware of the ampersand Gotcha!

The Signature parameter is itself a query string. If you're typing the full URL to leverage ImportFile.ashx/Apply, you must replace & with %26 manually. For example:
  • Signature=Decision/Save?Object=Loan&ObjectID={LoanID}: the value of Signature parsed on the server will be Decision/Save?Object=Loan
  • Signature=Decision/Save?Object=Loan%26ObjectID={LoanID}: the value of Signature parsed on the server will be Decision/Save?Object=Loan&ObjectID={LoanID}
If you are not typing the Signature query string directly, but use javascript to calculate the value being passed over the wire, javascript will take care of this substitution for you! For example:

  <input type="hidden" name="ClassName" value="Loan"/>
  <input type="hidden" name="Operation" value="Search"/>
  <input type="hidden" name="State" value="MA"/>
  <input type="hidden" name="Signature" value="Decision/Save?Object=Loan&ObjectID={LoanID}"/>

When parsing this data to pass to the server, the browser / javascript will recognize that the value being passed for the Signature parameter includes ampersands, and will automatically substitute & with %26 for you. See Templates/Application/GenericTemplate.Popup.xslt for an example.

qbo.Attachment Generator.config Changes

posted Dec 2, 2016, 8:05 AM by Eric Patrick

The Generator.config had several references to commonly deployed plugins, such as ABCPDF, Adobe, and Xceed. The default configuration file now only includes generators that are part of qbo.Attachment (and thus always deployed). All other generators should be installed via a setup package, which is included with the appropriate plugin.

Upon deploying qbo.AttachmentWeb, you may find that Generator entries which you previously used are now missing from you configuration. This will typically be reflected when:
  • You attempt to create a new Attachment Template, and a previously used Generator plugin is not available in the dropdown list, or
  • You attempt to merge a document, and get an error indicating that a Type is not found
This can be fixed simply by re-deploying the plugin and running the appropriate setup package, from Design > Configuration > Packages. Plugins to consider re-deploying include:
  • qbo.Core.sln > 
    • qbo.Attachment.PDF (PDF Form package)
    • qbo.Attachment.Xceed (Xceed Zipfile package)
  • qbo.ABCPDF.sln > qbo.Attachment.ABCPDF (ABCPDF package)
  • qbo.Aspose.sln > qbo.Attachment.Aspose (Aspose Words package)
  • qbo.Google.sln > qbo.Attachment.GoogleDrive (OCR package)
Note that the qbo.Attachment.ABCPDF.Screenscrape plugin should only be deployed if you need the screen scrape to PDF feature. This plugin includes a full FireFox installation (XUL_Runner), which is a lot of stuff to add to a server unless you really need it. 

ABCPDF Plugin Enhancements

posted Nov 18, 2016, 8:46 AM by Eric Patrick   [ updated Nov 18, 2016, 10:02 AM ]


The ABCPDF plugin (qbo.Attachment.ABCPDF) has been enhanced to support the following features:
  • PdfConvert: an IService that attempts to convert files to PDF
  • PdfMerge: now supports merging Image files, marks invalid documents with a 'Invalid Source' status
  • All: supports async/await pattern for scalability
  • Updated to ABCPDF version 10

PDF Convert

This supports several file formats, including text, TIFF, HTML, PNG, JPEG, BMP, and PDF. The method signature to call is:


Optional Parameters:
  • ThrowOnError: if true, encountering an invalid document will throw an error. If false, no error is thrown
  • Overwrite: if true, the original file is overwritten with the PDF version. If false, a new Attachment is created
Application Settings:
  • ConvertOverwriteDefault: (True) if the Overwrite parameter is not specified, this default is used
  • ConvertOverwritePDFWithPDF: (True) if true, an attempt is made to convert a .PDF file to .PDF. This essentially validates the PDF.
  • ConvertThrowOnError: (False) if the ThrowOnError parameter is not specified, this default is used

PDF Merge

The PdfMerge plugin merges multiple documents into a single PDF. It expects a DataSet, DataReader, or XmlReader containing AttachmentIDs, and attempts to merge each Attachment into a single resulting PDF. The method signature to call is:


where {AttachmentTemplate} used the plugin PDFMerge.

Optional Parameters:
  • ThrowOnError: if true, encountering an invalid document will throw an error. If false, no error is thrown
Application settings:
  • MergeThrowOnError: (False) if true, encountering an invalid document will throw an error. If false, no error is thrown
  • MergeErrorSourceStatus: (Invalid Format) the status to set a source document to if the source document cannot be merged into a PDF
  • MergeErrorStatus: (Invalid Format) the status to set the merged document to if any source documents cannot be merged into a PDF

Technical notes

For developers doing deployments:
  • Breaking Change: the URLtoPDF plugin has been moved to it's own project / plugin, since the install includes Firefox
    • Config/Generators.config may reference the old URLToPDF plugin. It must be removed.
    • Deploying FireFox for installs that don't use URLtoPDF is unnecessary overhead
  • The references to ABCPDF now uses NuGET, instead of needing to install the software outside of a build process
  • These plugins have been removed from the qbo.Core.sln, migrated to qbo.ABCPDF
    • Getting the ABCPDF NuGET package should be done from the ABCPDF solution
    • The folder structure has been cleaned up

SystemDefault.Monitor Bug Fix

posted Oct 26, 2016, 9:16 AM by Eric Patrick

The SystemDefault/Monitor method has been updated to handle null/empty values correctly. Previously, a null or empty value caused the following to be written to web.config: <value>\r\n</value>, instead of <value/>.  Upon attempting to recycle the application domain, this change was considered invalid, causing an infinite looping call to SystemDefault/Monitor. This resulted in the application pool constantly being in a JIT compilation state, killing performance.

Test cases have been created to cover these uses cases.

dbo.ObjectTree Bug Fix: Impacts ImportFile Dashboard

posted Oct 21, 2016, 10:04 AM by Eric Patrick

The dbo.ObjectTree user-defined function (UDF) in qbo.DB has been updated to handle an edge case resulting in timeouts. When uploading a file to import, the Attachment must inserted before the ImportFile is inserted. Thus, for brief period of time, Attachment.Object = 'ImportFile' and Attachment.ObjectID = NULL. Once the ImportFile has been created, the Attachment.ObjectID is updated accordingly.

If an extranet user attempts to import a document, the standard Attachment Extranet filter leverages dbo.ObjectTree, which enters an infinite loop, causing a timeout:

INSERT INTO @ObjectTree (Object, ObjectID, Generation) VALUES (@Object, @ObjectID, @Generation)
-- No matching row in Entity, so @Object is never changed
SELECT @Object = Parent, @ObjectID = ParentID, @Generation = @Generation - 1 FROM Entity WHERE Object = @Object AND ObjectID = @ObjectID

 The fix is to check for a NULL Object and ObjectID:

INSERT INTO @ObjectTree (Object, ObjectID, Generation) VALUES (@Object, @ObjectID, @Generation)
SELECT @Object = Parent, @ObjectID = ParentID, @Generation = @Generation - 1 FROM Entity WHERE Object = @Object AND ObjectID = @ObjectID

Method Signature Dropdown Lists and New Statements

posted Oct 14, 2016, 8:19 AM by Eric Patrick

The ObjectConfiguration/Update method has been extended to add newly defined Statements and IService entries to the Operations list during configuration monitoring. This fixes a bug whereby newly added Statements would not appear in method signature drop-down lists without a full application domain restart. These drop-down lists call:


The Configuration.Operations property is set when the configuration for a class is first build. Now, it is extended whenever a ConfigurationEntry row triggers a configuration monitoring update.

Report Requests and Default Email Addresses

posted Oct 7, 2016, 11:39 AM by Eric Patrick

Report requests using the Standard.Prompt.xslt will now default to a user's email address rather than their username. This allows user with non-email address to avoid re-typing their email address when requesting report runs. The prompt will also allow replacement of the user as the default recipient of the email (it was formerly read-only). Lastly, new Reports will automatically have their PromptXslt set to Standard.Prompt.xslt, so they 'play nicely' with the Report Request UI. This setting can be overridden in Design > Configuration > Modules > Report > Settings, from the PromptXsltDefault setting.

Message.BodyText from BodyHTML

posted Sep 29, 2016, 6:59 AM by Eric Patrick   [ updated Sep 29, 2016, 7:04 AM ]

The Message class populates BodyText from BodyHTML (stripped of HTML tags) if not otherwise specified. This allows use of rich HTML for messages viewed from the web tier or email clients, while also providing plain text for use in reports or data exchange where HTML is not desirable.

  • This behavior is executed in the application tier by default
    • e.g. Message/Save?BodyHTML={html} will result in BodyHTML and BodyText both being populated
    • no changes are needed to the UI (web) tier
  • If BodyHTML and BodyText are both passed to Message/Save, the BodyText passed over the wire 'wins'
  • This behavior may be disabled from Design > Configuration > Modules > Message > Settings, changing SetTextFromHtml to false.
  • The MessageObject.HtmltoText(string html) method is used to strip text of HTML tags, and may be used elsewhere as a static method.
  • Test cases for this behavior are in qbo.Message.Test > MessageTests

1-10 of 16