At it's core, QBO3 is intended to enable business "power users" to create new functionality without going through the traditional software development life cycle. QBO3 comes pre-packaged with a database including many generic tables, including:
Each of these tables has a corresponding URL, or handler:
  • Attachment/Attachment.ashx
  • Message/Message.ashx
  • Contact/Contact.ashx
  • Decision/Decision.ashx
  • Process/Process.ashx
These handlers typically expect to be passed more information than just their base URL. For example:
  • Save a Contact: Contact/Contact.ashx/Save?FirstName=John&LastName=Doe
  • Email a Message: Message/Message.ashx/Email?Subject=Hello World&
  • Mail merge a document: Attachment/Attachment.ashx/Generate?Template=My Template&Object=Contact&ObjectID=17
These URLs can be thought of as method signatures: {ClassName}/{Operation}?{Parameters}

The operations listed above (Save, Email, Generate) are build-in operations available in all QBO3 installations. A key goal of QBO3 was was enable power users to create entirely new Operations, without recompiling software. There are several ways do accomplish this, the most common being adding a new Statement. For example, let's assume a power user wants to get a list of all Contacts who have not yet been sent an email from QBO3. The URL they wish to use is Contact/Contact.ashx/ContactsMissingEmails. To accomplish this:
  • From a web browser, navigate to Design > Configuration > Modules > Contact
  • From the Statement tab, choose Options > New Statement
  • Enter the SQL query that meets the business goal:
      SELECT 1 FROM Message WHERE ObjectID=ContactID AND Object = 'Contact'
  • Click Save
The power user has just added a new operations (ContactMissingEmails) to the QBO3 system.

QBO3 Method Signatures

When the URL Contact/Contact.ashx/ContactMissingEmails is invoked, the following happens:
  • The Contact.ashx handler creates a ContactObject instance, and calls Contact.Invoke('ContactMissingEmails', {Parameters})
  • The ContactObject instance searches for ContactMissingEmails as follows:
    • if there is a matching C# method, the method is invoked using reflection
    • if there is a matching IService entry, the plugin-DLL is instantiated and invoked
    • if there is a matching Statement, the statement is executed
The implication here is that there is no C# method called ContactMissingEmails. The list of statements available to the Contact module is maintained in memory in the QBO3 application domain (and updated continuously via configuration monitoring).

Note that the 'hard work' here is not done by the Contact.ashx handler; instead it's done by the ContactObject class. (Technically, it's done by a base class called AbstractObject.) This means that the work may be done not just by calling URLs, but by anything that can instantiate a ContactObject class. This is critical to understand. The queuing, workflow and import modules are 'smart' enough to invoke the same method signatures. For example:

QBO3 vs. MVC

When designing QBO3, we considered building on top of Microsoft's MVC framework. In 2013, Microsoft's MVC framework did not easily allow extending MVC routes without recompiling the software. Since the power-user created statements are not known in advance, using MVC was problematic. Thus, we essentially re-invented our own data-driven route handling. QBO4 will leverage much of the route handling available in AspNet.Core's MVC framework.

QBO3 vs. QBO2

QBO2 was extremely capable. QBO3 is at least equally capable, but vastly simpler to configure. Key differences include:
  • Events no longer need to be created to call functionality from workflow or queuing
  • Reports (stored procedures) no longer need to be created for most power user functionality
    • The {ClassName}/Summary method returns an XmlReader over all parents and descendants of an object
  • Workflows can invoke any method signature
  • AJAX calls can invoke any method signature without extending the client-side javascript
  • Updating parent and child tables simultaneously requires just one operation
  • SubscriberIDs are still supported, but AutoBind is usually easier to use