Data dictionary properties and configuration settings can only take Stonefield Query so far. For some things, you need to code what Stonefield Query does. You can do that with plugins. Some examples of where plugins are useful are:

  • Dynamically updating the data dictionary. For example, your application may allow the user to change the caption for some fields. You probably want the user to see the same captions in Stonefield Query; otherwise, the user might have trouble finding the field they want. In the AfterLoaded method of a data dictionary plugin, which is called just after Stonefield Query fills the data dictionary collection from the data dictionary database, you can change the caption (or any other property) of any field. You can even add or remove fields.

  • Defining data sources. If the Connection type property of a database is set to Scripted, a plugin specifies the connection information for the database. You can define multiple data sources for a database if desired.

  • Calling stored procedures. For a variety of reasons (security, performance, or because the DBA says so), you may want to access the data in a table via a stored procedure rather than by a SQL statement. Since Stonefield Query doesn't know the name of the stored procedure to call or the parameters to pass to it, you have to put code in the Select method of a plugin for the table.

  • Calculated fields. You may have a calculated field for which a simple expression won't suffice. For example, suppose the commission for a sale is set to a sliding scale, so higher sales amounts receive a higher commission percentage. In this case, you may have to use a control structure such as a switch statement to determine the amount. The code for this goes into a function plugin called from the Expression property of the field.

There are several different types of plugins in Stonefield Query:

  • Application: these plugins are called from various places in Stonefield Query. For example, after Stonefield Query has finished its startup task, the AfterSetup method of an application plugin is called.

  • Data dictionary: these plugins are called during specific data dictionary events, such as after the collections have been loaded from the data dictionary database.

  • Data engine: these plugins are called from various places during data engine processing. For example, the GetDataSources method of such a plugin can programmatically define the data sources (physical connection information) for a database.

  • Functions: these simple plugins provide methods called from various places in Stonefield Query, such as the Expression or Caption properties of a field.

  • Report engine: these plugins are called from various places during report engine processing, such as determining whether the current user can access a specific report.

  • Setup: setup plugins allow you to define additional settings that appear in the Stonefield Query Setup Wizard.

  • Value converter: these plugins convert a value stored in a field into the value you want displayed to the user. For example, some applications store dates as numeric values, so January 13, 2013 is stored as 20130113. A value converter converts the numeric value to a date value for display in a report, and a date value used as a filter into a numeric value that can be used in the WHERE clause of a SQL statement.

  • Values method: these plugins allow you to programmatically define how to display a list of distinct values for a field rather than having Stonefield Query issue a SELECT DISTINCT SQL statement when the user clicks the Values button.

  • Virtual table: these plugins return a DataTable to Stonefield Query when the user reports on a virtual table.

Stonefield Query supports any .NET language for a plugin. Stonefield Query uses version 4.5 of the .NET framework, which must be installed on both your system and the production system.

A plugin is identified by Stonefield Query by the following:

  • Its assembly is in one of the folders Stonefield Query looks in for plugins:

    • The Plugins subdirectory of the program folder. This is intended for plugins provided by Stonefield Software Inc. or plugins you create that are common to multiple projects. This folder includes ApplicationPlugin.dll, which handles support ticket creation when an error occurs; Setup.dll, which provides some features for the Setup dialog; and ValueConverters.dll, an assembly of built-in value converters.

    • The Functions subdirectory of the program folder. This is intended for plugins that provide functions you can use in expressions provided by Stonefield Software Inc. or functions you create that are common to multiple projects. This folder includes Functions.dll, an assembly of the functions listed in the Expressions and Functions Reference help topic.

    • The Plugins subdirectory of the Project_Data folder. This is intended for custom plugins you create that are specific to the project.

    • The Functions subdirectory of the Project_Data folder. This is intended for custom plugins you create that provide functions specific to the project.

  • It implements the appropriate Stonefield Query plugin interface and has an attribute providing meta data about the plugin. See the help topic for each plugin type for the interface and attribute to use.

All Stonefield Query plugin interfaces derived from a common one, IStonefieldQueryBasePlugin in Interfaces.dll:

using System;
using Stonefield.Query.Application;
 
namespace Stonefield.Query.Plugins
{
    /// <summary>
    /// All the specific plugin types must extend this interface.
    /// </summary>
    public interface IStonefieldQueryBasePlugin
    {
        ISQApplication Application
        {
            get;
            set;
        }
    }
}

Application is a reference to the Stonefield Query Application object, which means you have access to all of Stonefield Query so your plugin can do whatever tasks it needs to.

In a plugin class, you have to use the [Import] attribute on the Application member to ensure its value is set by our dependency injection framework. For example:

[Import]
public ISQApplication Application { get; set; }

Application is null in the constructor of the plugin, so your code can't use it in that method or any methods called from the constructor.

The plugin attributes have members in common:

  • The ID for the plugin. This is normally a GUID as a string, so the easiest way to provide its value is to use the Create GUID function in the Tools menu of Visual Studio.

  • The name of the plugin. This is normally the same as the class name but could be different if desired.

  • The type of plugin. This should be PluginSource.Custom.

  • The plugin version number as a string.

  • The execution priority as an integer. This is used to determine the order of execution if there's more than one plugin of a certain type. The higher the number, the earlier the execution.

The sections under this topic describe how the different types of plugins work and how to create them, and gives examples of plugins you may wish to use.

Displaying an error message

If you want to display an error message to the user from a method of a plugin, use the RaiseErrorMessage method of the Application.MessageManager object:

RaiseErrorMessage(string message, string username)

message is the text to display to the user. username is an optional parameter that, if specified, only sends the message to that user's browser. If you pass in the empty string, all users receive the message. Here's an example:

Application.MessageManager.RaiseErrorMessage("Message to be displayed",
  Application.Security.CurrentUser.Name);

Generating plugins

Rather than starting from scratch, you can have Studio generate the source code for a plugin for you. On the Calc page for fields, the Create a value converter plugin and Create a values method plugin buttons () generate the appropriate type of plugin. In the properties pane for a virtual table, the Create a virtual table plugin button generates a virtual table plugin for the selected table. The File menu has a Create Plugin function that generates the code for an application, data dictionary, data engine, or report engine plugin. See the Generating Plugin Source Code topic for details.

Plugin code

Plugin code must be thread-safe. The reason is that Stonefield Query may spin up multiple threads when retrieving data for a report, and those threads may end up calling one or more plugins. A few general rules of thumb are to try to reference only local variables when possible, avoid using static fields or properties, and use locks to access resources that may be modified in another thread.