Version: 2020.1.3

Plugins Developer manual

Introduction

inPoint as a large framework consists of multiple layers (in a nutshell):

  • A Server Module Layer running a WCF Service under a host (currently IIS)
  • Multiple Clients running in Windows: Office/Windows Integration tools, Stand Alone Client, etc.

The Clients are usually made of:

  • Front-End Core -- the main "glue" between all the components:
    • In Standalone using the WPF Framework (inPoint.Forms.dll)
    • In Office using a VSTO Wrapper (inPoint.Outlook.dll)
    • In Explorer using the LogicNP NamespaceExtensions library (inPoint.Shell.dll)
  • Middleware Service Layer -- aka HierarchyProvider -- to contact the standard Server Layer via WCF (inPoint.API.dll)
  • Extension Plugins -- features implemented as extension points, either as Core Plugins or for prototyping (sort of beta-testing)

Plugins are a way to:

  • Implement new functionality into inPoint, whether as a customer requested feature (e.g. Project specific) or as a tool for Prototyping a future Core feature
  • Replace / Extend existing functionality implemented in inPoint Core (or any other Plugin) (e.g. Project specific)

MEF

inPoint uses MEF - The Managed Extensibility Framework - available as a built-in feature in .NET since v4.0, to allow us to create zero-footprint, no configuration, discoverable components that can either be created and used by the Core team itself or by anyone that wants to extends inPoint functionality.
A lot of the Core features are also implemented using MEF (e.g. all CommandActions, ArchiveDialog suggestions, etc…), included directly in the main library (inPoint.Forms.dll).

A simple explanation of how inPoint uses MEF:

  • there are some predefined Interfaces which specify what inPoint is expecting the "part" (Plugin) to do, whether via input parameters or by output methods and with metadata (e.g. Name, Description)
  • those "parts" can either be assembled in one library (dll) or multiple ones
  • during inPoint start-up an AggregateCatalog is initialized which will compose those "parts" making them available to the Core library, using the inPoint.API.Plugins.PluginManager
  • depending on Configuration the required "parts" will be used across the Application (most are loaded only when required)

Client Plugins

Most of the provided Plugins are Client based, meaning they will run only in an active inPoint Client with a User in front; as such they are obviously mainly limited to GUI Plugins and less for background data management (although possible, they would not be activated in case of 3rd party access directly to the Server WCF Layer).

Server Plugins

Currently only two "kinds" of Server Plugins are included, the inPoint.Jobs (scheduled Tasks) and PamArchive Plugins (activated during archiving / retrieval of documents).

General Guidelines

All Interface definitions are included in the inPoint.API.Plugins namespace.
All Plugin Metadata include at the minimum required a Name (unique identifier) and a Description (human readable). In the future, this will include also optional Versioning details, and some specific Plugins require additional metadata (e.g. PropertyTab define to which Items they apply, etc…).

Deployment

All Client Plugins reside on the Server, see Plugins File Structure
They are automatically downloaded during inPoint Startup, and as such allow for "easy" deployment across all Clients in the Enterprise.
NOTE: There is no "push" notification for Plugin changes, the Clients must manually restart.

Versioning

Currently there is no Versioning, but such a feature is planned for the future (at least as an optional feature).

Error Handling

Error Handling MUST be correctly handled by each Plugin, the Core library will simply log the error and show a MessageBox to the user for not handled Exceptions.

Logging

inPoint uses log4net across all it's components, but it is the Plugin's implementation that needs to take care of referencing/initializing the Logger.

Settings and Configuration

Currently only the Classification Plugins have built-in Settings management (PluginInfo), saved as part of the ElementFormat directly in the Database.
AttributeFlow StepActions also have some based on the same concept.
In the future we will extend this to all inPoint plugins (primarily for CommandActions), using a consolidated framework for saving/loading those Settings (based on the CascadingSettings feature).

inPoint Object Model

Some Core objects are passed as input parameters and sometimes expected as return values, so you need to know what they are all about. Here is a very high-level definition (all defined in inPoint.API or inPoint.Common):

  • HierarchyProvider
    The Service layer to contact the Server. In 99% of the cases you do not need to create a proxy to PamWCF or UnifyWCF, the methods would be already implemented here, as Async methods always returning a TaskResult wrapper that will let you know whether the .OperationResult was successful (.Result will have any outputs) or failed (.Exception will know why).
  • UIStateService
    This is what the Core uses to identify the current UI state (as the name implies ;) Basically which Folder is currently selected, which Documents, etc…
  • EventAggregator
    inPoint uses Prism’s generic EventAggregator to pass messages across all it's modules, whether those are info events (e.g. SelectedHierarchyElementChangedEvent) or to request it to do something (e.g. OpenItemURIRequestedEvent or ArchiveDocumentRequestedEvent) – the latter allows for easy no hard-linking of Core inPoint features.
  • ElementFormat
    This class contains all Folder/Document/Plugins definitions, named and specific to a certain Site/Category combination
  • HierarchyItem
    Base class extending into HierarchyElement and HierarchyDocument for representing Folders and Documents respectively.

Templates

NuGet inPoint Repository

Packages Source: \\hs.local\dfs\vie\development\Nuget
Note that these need to be manually configured in your Visual Studio as a NuGet source!

Visual Studio Templates

inPoint.Plugins.SDK is a VSIX Install Package which will pre-install all required templates and configurations in your Visual Studio installation, including the NuGet packages already pre-installed.

Project Template

Visual Studio New Project

The ProjectTemplate includes (you might need to manually adjust those!):

  • A PostBuild script to deploy the .DLL to the target inPoint Server Plugin folder
  • A Startup command-line in order to automatically start inPoint (with F5)

Item Templates

Visual Studio New Item

Classification Plugins

Scope

Classification Plugins are manually configured by the user directly in the ElementFormat ("FieldChooser"), and follow this strategy:

  • Currently only ONE instance per Plugin can be used (e.g. no multiple ExtraAttributes)
  • Each can be enabled/disabled (without the need to remove them) directly in the FieldChooser
  • They are single-instanced by MEF – each time recreated and reloaded (so no static caching between Folders and/or Documents!)
  • Each can have it's own Configuration Window (IPluginConfigWindow)
  • All the settings are stored directly in the ElementFormat as part of the PluginInfo Object – the loading/saving is managed automatically
  • They are fired for ANY item (Folders and Documents), it's up to the Plugin to decide on which one to act
  • They are fired for: right panel, Edit Attributes, New and Edit
  • There are no borders, margins, styles, etc… ANY UI settings whatsoever – it's up to the Plugin to decide how to integrate into the main app
  • They should NOT reference Pam.Unify.Forms!

Getting a Plugins configuration

This is just an example, there is also a Helpers.Form.GetPlugInfo().
TODO: move to a Common library!

private ExtraAttributes.Config GetEAConfig()
{
var pinfo = elementFormat.Plugins.FirstOrDefault(p => p.PluginName.Equals("ExtraAttributes", StringComparison.InvariantCultureIgnoreCase));
if (pinfo != null)
return new ExtraAttributes.Config(pinfo.PluginConfig);
return null;
}

String Tokens

Often you want the user to be able to configure templates, e.g. for mails or setting other attributes. e.g. ExtraAttributes.FolderTemplateName = "{ContractId} – {LastModifiedName}"

There is a helper class under Helpers.Stuff.GetTokens that uses RegularExpressions to retrieve those, you can then loop through them and replace whatever you need.

TODO: move to a Common library!

IClassificationPlugin

public interface IClassification : INotifyPropertyChanged
{
/// <summary>
/// Called to initialize the plugin.
/// </summary>
/// <param name="provider">The provider.</param>
/// <param name="UIStateInstance">The UI state instance.</param>
/// <param name="EventAggregator">The event aggregator.</param>
void Initialize(IHierarchyProvider provider, IUIStateService UIStateInstance, IEventAggregator EventAggregator);

/// <summary>
/// Gets the Viewer control used in a Panel when viewing an existing item.
/// </summary>
/// <param name="ElementFormat">The element format.</param>
/// <param name="HierarchyItem">The hierarchy item (folder or document).</param>
/// <returns></returns>
object GetViewControl(ElementFormat ElementFormat, HierarchyItem HierarchyItem);

/// <summary>
/// Gets the Edit Attributes control used in a Window usually started from a Context menu.
/// </summary>
/// <param name="ElementFormat">The element format.</param>
/// <param name="HierarchyItem">The hierarchy item (folder or document).</param>
/// <returns></returns>
object GetEditAttributesControl(ElementFormat ElementFormat, HierarchyItem HierarchyItem);

/// <summary>
/// Gets the control used in a Window when creating a new item.
/// </summary>
/// <param name="ElementFormat">The element format.</param>
/// <param name="HierarchyItem">The hierarchy item (folder or document).</param>
/// <returns></returns>
object GetCreateNewControl(HierarchyElement parentHierarchyElement, ElementFormat ElementFormat, HierarchyItem HierarchyItem);

/// <summary>
/// Called when user decides to save his changes to the Classification form.
/// </summary>
/// <param name="HierarchyItem">The hierarchy item (folder or document).</param>
/// <returns>null if no changes, or </returns>
HierarchyItem SaveClassification(HierarchyItem hierarchyItem);

/// <summary>
/// Returns a Window used to configure the Plugin.
/// </summary>
/// <param name="elementFormat">The current element format.</param>
/// <param name="pluginInfo">The plugin information.</param>
/// <returns></returns>
/// <remarks>
/// any configuration should be stored directly in pluginInfo.PluginConfig
/// </remarks>
object GetConfigWindow(ElementFormat elementFormat, ElementFormatPluginInfo pluginInfo);

string IconPathForFormat(ElementFormat ElementFormat, string ItemUri);
string IconOverlayPathForFormat(ElementFormat ElementFormat, string ItemUri);
}

NOTE: all methods should return NULL when no implementation is needed.

GetXXXControl

NOTE: All the GetXXXControl() methods expect a UserControl returned (and not just a simple UIElement) – otherwise nothing will be shown – this is to make sure WPF Bindings are correctly resolved.

  • GetViewControl() – used to retrieve the View to show in the Right Panel (next to the Tree). This View currently exists only for Folders
  • GetEditAttributesControl() – used to retrieve the View to show in the EditAttributes Window (first tab)
  • GetCreateNewControl() – used to retrieve the View to show when creating a new Folder or Document

SaveClassification

This method gets called when the user clicked OK and inPoint is going to create/update the item to the Server.
For existing items, it gets called before sending it to the server (itemUri is already known).

For new items, it actually gets called twice:

  • Once before actually saving – allowing you to change anything in the HierarchyItem before sending to the Server (if HierarchyItem is a HierarchyDocument then the ItemUri will include the full path to the file)
  • Once after saving – allowing you to do any post-processing once the ItemUri is known (i.e. when a new item was created)

HierarchyItem

Classification Plugins can be written both to serve Folders, Documents or both.
In order to identify for which item it is currently being called on, do a check on the input parameter HierarchyItem.

E.g. if you only want it to be available to Folders, add this at the start of each method:

if (hierarchyItem is HierarchyDocument)
return null;

If you are indeed serving HierarchyDocuments, note that the ItemUri property will contain:

  • For existing Documents, the real ItemUri of the Document
  • For new documents (e.g. when user is archiving a new document and GetCreateNewControl() is called or in SaveClassification) – the full path to the file being archived (e.g. c:\temp\myfile.docx)

IPluginConfigWindow

public interface IPluginConfigWindow
{
Dictionary<string,string> PluginConfig { get; set; }
}

NOTE: This will be refactored once Plugin's Configuration/Settings are centralized.

ExtraAttributes Validation Plugins

Validation plugins are only called for the following ExtraAttributes types:

  • MaskedText
  • MaskedNumber
  • Date
  • Currency

IExtraAttributesValidationRule

public interface IExtraAttributesValidationRule
{
ValidationResult Validate(ObservableDictionary<string,object> form, string ruleParam, object value);
}

Following properties are passed on every user's key type (OnPropertyChanged):

  • form
    A dictionary of ALL the attributes existing in the ExtraAttributes form
  • ruleParam
    The custom Validation parameters configured in the "Validation Parameter" for that specific ExtraAttribute's attribute (in the FieldChooser config)
  • value
    The current value typed by the user (before it's actually commited to the form)

You can return ValidationResult with:

  • TRUE
    Validation successful
  • FALSE
    Validation failed and the message to the show to the user

NOTES:

  • The .Validate() will be called EVERY time a field in the form changes – not only the one you applied the ValidationRule to (otherwise it wouldn't know when other fields change) – so it's up to YOU to make sure it's quick / cached
  • although not supported, it IS currently possible to change the actual form values (others or specifically this one), e.g. for changing all to uppercase
  • currently defined only in inPoint.Plugins.dll, this will be refactored when implemented directly in Core and extended to validate also the "standard" inPoint Attributes.

ArchiveDialog Suggestion Plugins

IArchiveSuggestion

public interface IArchiveSuggestion
{
double ScoreWeight { get; }

void Initialize(IHierarchyProvider provider, IUIStateService UIStateInstance);

ArchiveSuggestionTarget[] GetTargets(Dictionary<string, object> sourceProperties);
}

CommandAction Plugins

ICommandAction

public interface ICommandAction : INotifyPropertyChanged
{
/// <summary>
/// Called when context menu gets registered.
/// </summary>
/// <param name="provider">The provider.</param>
/// <param name="UIStateInstance">The UI state instance.</param>
void Initialize(IHierarchyProvider provider, IUIStateService UIStateInstance, IEventAggregator EventAggregator);

/// <summary>
/// Gets the command verb - basically the unique identifier for this CommandAction
/// </summary>
/// <value>
/// The command verb.
/// </value>
string CommandVerb { get; }

/// <summary>
/// Gets or sets the command option as set in the PAM_CUSTOMACTIONS table (e.g. for re-using the
/// same CommandAction with multiple options)
/// </summary>
/// <value>
/// The command option.
/// </value>
string CommandOption { get; set; }

/// <summary>
/// Gets or sets the command parameter - when this CommandAction is called directly (and not
/// from a ContextMenu)
/// </summary>
/// <value>
/// The command parameter.
/// </value>
object CommandParam { get; set; }

/// <summary>
/// Action() delegate called by context menu (based on current UIStateService selection)
/// </summary>
void Execute();
/// <summary>
/// Enable/Disable Context menu item.
/// </summary>
/// <returns></returns>
bool CanExecute();

/// <summary>
/// Whether this item should be visible at all (based on current UIStateService selection)
/// </summary>
/// <returns></returns>
bool ShouldBeVisible();

List<CommandActionItem> GetItems();
}

CommandActionItem (submenus)

public class CommandActionItem
{
public string Header { get; set; }
public string ImageSource { get; set; }
public Action Execute { get; set; }
public Func<bool> CanExecute { get; set; }
public bool IsEnabled { get; set; }
}

ICommandCondition

public interface ICommandCondition
{
/// <summary>
/// Called when context menu gets registered.
/// </summary>
/// <param name="provider">The provider.</param>
/// <param name="UIStateInstance">The UI state instance.</param>
void Initialize(IHierarchyProvider provider, IUIStateService UIStateInstance, IEventAggregator EventAggregator);

/// <summary>
/// Returns whether this item should be Visible.
/// </summary>
/// <param name="conditionParam">The condition parameter.</param>
/// <returns></returns>
bool ShouldBeVisible(string conditionParam);
}

AttributeFlow StepAction Plugins (ICommandAction)

PropertyTab Plugins

IPropertyTab

public interface IPropertyTab
{
string Header { get; }
string Icon { get; }
bool IsDirty { get; set; }
bool IsValid { get; set; }

/// <summary>
/// Initializes the specified provider.
/// </summary>
/// <param name="provider">The provider.</param>
/// <param name="UIStateInstance">The UI state instance.</param>
/// <param name="EventAggregator">The event aggregator.</param>
void Initialize(IHierarchyProvider provider, IUIStateService UIStateInstance, IEventAggregator eventAggregator, PropertyTabHost currentHost);

/// <summary>
/// Enable/Disable tab item.
/// </summary>
/// <returns></returns>
bool IsEnabled(HierarchyItem hierarchyItem);

/// <summary>
/// Whether this Tab should be visible at all
/// </summary>
/// <returns></returns>
bool IsVisible(HierarchyItem hierarchyItem);

/// <summary>
/// Gets the View control to render in the Tab
/// </summary>
/// <param name="elementFormat">The element format.</param>
/// <param name="parentHierarchyElement">The parent's hierarchy element.</param>
/// <param name="hierarchyItem">The hierarchy item.</param>
/// <returns></returns>
object GetViewControl(ElementFormat elementFormat, HierarchyElement parentHierarchyElement, HierarchyItem hierarchyItem, object obj = null);

/// <summary>
/// Gets the custom commands identifiers
/// </summary>
/// <returns></returns>
List<IPropertyTabCommand> GetCustomCommands();

/// <summary>
/// Executes the command identified by ID (either Standard from Host or Custom)
/// </summary>
/// <remarks>custom IDs predefined by host: SAVE, CANCEL</remarks>
/// <param name="ID">The identifier.</param>
/// <param name="hierarchyItem">The hierarchy item.</param>
/// <returns></returns>
bool ExecuteCommand(string ID, HierarchyItem hierarchyItem);
}

inPoint Jobs

PamArchive Plugin