The plugin provider is a framework for creating, modifying, and extending code modules. The modules can range from a single block of code to complex workflows executed by the Windows Workflow Foundation runtime. The provider allows modules to be easily replaced and reconfigured through the web.config or app.config.

Components

The plugin framework consists of the following parts:

  • Manager (PluginManager class) – responsible for registering the providers and executing plugins
  • Provider (PluginProvider class) – responsible for instantiating plugin objects and supplying higher level static parameters to the generated plugins
  • Context (IPluginContext interface) – in addition to the parameters provided by the provider, the manager can also supply a context containing parameters specific to the current execution
  • Plugin (IPlugin interface) – a plugin instance contains the execution behavior as well as the context passed in by the manager

Types of Plugins

The plugin provider is configured to produce plugins that implement the IPlugin interface. There are several IPlugin implementations already provided that take various approaches to representing execution behavior. The easiest method of producing a custom plugin is to create a subclass of a base plugin implementation.

All plugin providers share a common set of attributes that are used to initialize every plugin created:

  • name – a provider identifier
  • description – further details of the plugin type
  • type – the fully qualified name of the PluginProvider class. In most cases the default Adxstudio.Workflow.PluginProvider class is sufficient but a custom provider can be configured
  • pluginType – the fully qualified name of the custom IPlugin class

Plugin Class

The Plugin class represents the most basic plugin for executing custom managed code written in a CLR language of choice. Simply implement the abstract Execute() method to provide custom behavior.

AsyncPlugin Class

This class is the equivalent of the Plugin class where the implementation is inherently asynchronous. That is, the custom implementation employs the IAysncResult and AsyncCallback pattern. Custom code is provided by overriding the BeginExecute() and EndExecute() abstract methods.

Attributes

  • defaultTimeout – the time to wait before resuming execution. The default value is 5 mins.

WorkflowPlugin Class

Inherit this class in order to implement a plugin through a Windows Workflow Foundation workflow. This plugin forwards the execution onto the configured workflow which is handled by the workflow runtime. Execution can occur synchronously where the manager waits for the workflow to complete or can occur in a “fire and forget” behavior where the manager continues without waiting.

Attributes

  • workflowType – the fully qualified name of the Windows Workflow component
  • mode – can be “Synchronous” if the execution output (results) are needed by the caller or “Asynchronous” if the output can be ignored

ServicePlugin Class

Plugin execution can be remotely performed through a WCF service that implements the IPluginService service contract. The provider is configured with the connection information and the execution is provided by the WCF service.

  • remoteAddress – the address where the service can be found
  • endpointConfigurationName – the configuration section where further connection settings can be found

Configuration

The basic configuration consists of the following:

<configuration>
    <configSections>
        <section name="adxstudio.web" type="Adxstudio.Configuration.WebSection"/>
    </configSections>
    <adxstudio.web>
        <pluginManager>
            <providers>
                <!-- basic code plugin -->
                <add
                    name="ExamplePlugin"
                    description="An example plugin."
                    pluginType="Examples.ExamplePlugin, App_Code"
                    type="Adxstudio.Workflow.PluginProvider"/>
                <!-- Windows Workflow Foundation plugin -->
                <add
                    name="LdapDocumentCreate"
                    description="Custom workflow after a document is created."
                    mode="Synchronous"
                    pluginType="Adxstudio.Workflow.WorkflowPlugin"
                    object="Document"
                    event="Load"
                    stage="Post"
                    workflowType="Adxstudio.Workflow.Activities.LdapDocumentCreate"
                    type="Adxstudio.Workflow.PluginProvider"/>
                <!-- Windows Communications Foundation plugin -->
                <add
                    name="MyServicePlugin"
                    remoteAddress="http://mydomain.com/MyPlugin.svc"
                    endpointConfigurationName="WSHttpBinding_IPluginService"
                    pluginType="Adxstudio.Workflow.ServicePlugin"
                    type="Adxstudio.Workflow.PluginProvider"/>
                <!-- document validator plugin -->
                <add
                    name="VisibleDocumentValidator"
                    description="Validates a document for rendering."
                    pluginType="Adxstudio.Cms.VisibleDocumentValidator"
                    type="Adxstudio.Cms.DocumentValidatorPluginProvider"/>
            </providers>
        </pluginManager>
    </adxstudio.web>
</configuration>

Executing Plugins

Execution of plugins is handled by the PluginManager class. The manager takes care of selecting the desired provider and creating plugin instances for each call. It is common to provide a context for transferring parameter values.

PluginContext context = new PluginContext();
context.InputParameters = new Dictionary<string, object>();
context.InputParameters["MyParam"] = "MyValue";

if (PluginManager.Execute("ExamplePlugin", context))
{
    Dictionary<string, object> output = context.OutputParameters;
}
else
{
    // plugin failed to execute
}

Execute by Name

To call a single plugin, the following line can be used:

PluginManager.Execute("ExamplePlugin", context);

The first parameter uniquely identifies a particular registered plugin.

Execute by Match

Matching allows multiple registered plugins to be executed that satisfy the match condition. The following executes all plugins that are configured with the attribute MyAttrib=”MyMatch”.

PluginManager.Execute(
    delegate(PluginProvider provider)
    {
        return provider.Attributes["MyAttrib"] == "MyMatch
    },
    context);

A common scenario is to define a particular class (ie. an object) with specific behavior events (such as “create”, “load”, “update”, and “delete) where it is desirable to allow customizable code to execute. Similar to event handlers, plugins can subscribe to these events through the configuration. The object may call a series of statements in the following pattern:

  1. Instantiate object
  2. Call pre-processing plugin on object
  3. Perform action
  4. Call post-processing plugin on object

A specialized form of matching execution is available for building such code.

BusinessObject obj = new BusinessObject();
PluginContext context = new PluginContext();
context.InputParameters = new Dictionary<string, object>();
context.InputParameters["MyBusinessObject"] = obj;
PluginManager.Execute("BusinessObject", "Update", "Pre", context);
obj.Update();
PluginManager.Execute("BusinessObject", "Update", "Post", context);

In this case, the plugin provider is configured with matching attributes.

<add
    name="BusinessObjectPreUpdate1"
    pluginType="MyPlugins.BusinessObjectPreUpdate1"
    type="Adxstudio.Workflow.PluginProvider
    object="BusinessObject"
    event="Update"
    stage="Pre""/>
<add
    name="BusinessObjectPreUpdate2"
    pluginType="MyPlugins.BusinessObjectPreUpdate2"
    type="Adxstudio.Workflow.PluginProvider"
    object="BusinessObject"
    event="Update"
    stage="Pre"/>
<add
    name="BusinessObjectPostUpdate"
    pluginType="MyPlugins.BusinessObjectPostUpdate"
    type="Adxstudio.Workflow.PluginProvider"
    object="BusinessObject"
    event="Load"
    stage="Post"/>

Creating Custom Plugins

Basic Plugin

Custom basic plugins are created by inheriting and implementing the Execute() method of the Plugin class and reading the Context member property.

Asynchronous plugin implementations override the BeginExecute() and EndExecute() methods of the AsyncPlugin class. Web services, asynchronous delegates, and asynchronous webforms are good candidates for asynchronous implementations.

Workflow Plugin

Windows Workflow Foundation based plugins are commonly sequential workflows that are built in a separate assembly project and referenced into the application. The WorkflowPlugin class is the bridge between the PluginManager execution calls and the workflow. In most cases the WorkflowPlugin class does not need to be inherited. Instead, the focus is on building a workflow activity.

Start by creating a new Sequential Workflow Library to contain the desired workflows. Create a new SequentialWorkflowActivity class and add public properties to correspond to the plugin context and its input parameters. The WorkflowPlugin class performs the work of mapping the plugin context to the workflow by looking for the following special properties:

  • ProviderName - Assigned the IPlugin.ProviderName property
  • PluginContext - Assigned the IPlugin.Context property
  • PluginAttributes - Assigned the PluginProvider.Attributes property

Note: The actual names of these workflow properties can be configured during plugin configuration by specifying custom property names:

<add
    name="MyWorkflowPlugin"
    workflowType="MyPlugins.MyWorkflowPlugin"
    pluginType=" Adxstudio.Workflow.WorkflowPlugin"
    type="Adxstudio.Workflow.PluginProvider"
providerNameParameterName="MyProviderName"
pluginContextParameterName="MyPluginContext"
pluginAttributesParameterName="MyPluginAttributes"/>

In addition, properties may be added to the workflow with names corresponding to the keys of the IPluginContext.InputParameters dictionary. Each input parameter entry is then mapped to the corresponding workflow property.

public sealed partial class MyWorkflowPlugin : SequentialWorkflowActivity
{
    private string _providerName;
    public string ProviderName
    {
        get { return _providerName; }
        set { _providerName = value; }
    }
    private PluginContext _pluginContext;
    public PluginContext PluginContext
    {
        get { return _pluginContext; }
        set { _pluginContext = value; }
    }
    private NameValueCollection _pluginAttributes;
    public NameValueCollection PluginAttributes
    {
        get { return _pluginAttributes; }
        set { _pluginAttributes = value; }
    }
    private string _myParam;
    public string MyParam
    {
        get { return _ myParam; }
        set { _ myParam = value; }
    }
    // ...
}

When the workflow completes, the WorkflowPlugin class maps the same set of workflow properties into the IPluginContext.OutputParameters to update the plugin context.

Service Plugin

A service plugin is essentially a WCF client configured to make web service requests to a service implementing the IPluginService contract. The provided ServicePlugin class along with a proper web/app.config setting will satisfy most situations so customization is not needed here.

First register the service plugin:

<add
    name="MyServicePlugin"
    remoteAddress="http://mydomain.com/MyPlugin.svc"
    endpointConfigurationName="WSHttpBinding_IPluginService"
    pluginType="Adxstudio.Workflow.ServicePlugin"
    type="Adxstudio.Workflow.PluginProvider"/>

Next, add the WCF client configuration:

<system.serviceModel>
    <bindings>
        <wsHttpBinding>
            <binding name="WSHttpBinding_IPluginService">
            <!-- ... -->
            </binding>
        </wsHttpBinding>
    </bindings>
    <client>
        <endpoint address="http://mydomain.com/MyPlugin.svc"
            binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IPluginService"
            contract="Adxstudio.Workflow.ServiceReference.IPluginService"
            name="WSHttpBinding_IPluginService">
            <!-- ... -->
        </endpoint>
    </client>
</system.serviceModel>

Customization comes in the form of an IPluginService provided by a remote host.

public class MyPlugin : IPluginService
{
    public Dictionary<string, object> Execute(PluginContext context)
    {
        // ...
        Dictionary<string, object> outputParameters = new Dictionary<string, object>();
        
        // ...
        return outputParameters;
    }
}