Plugin DLL Transform
The Plugin DLL Transform feature in SyncNow enables operators to run custom DLLs for advanced scenarios. This allows for complex data transformations that cannot be handled by simple field mappings or scripted transforms.
📝 Overview
To develop a plugin, follow the instructions below. Once the plugin is developed, it must be registered in SyncNow.
Operators can register the plugin by editing the source field mapping in the Field Mapping Configuration window, selecting the Plugin tab, and uploading the DLL assembly.
After uploading, select the class name that will run for the field transform. Each assembly can contain multiple implementations of classes for the transform.
Additionally, operators can toggle a switch to pass source API call credentials to the plugin.
Use this feature with caution and only for trusted plugin authors, as it can grant access to sensitive credentials.
🛠️ Developing a Plugin
To develop a plugin, follow these steps:
Plugin Interface
Implement the IPlugin
interface in your class. The interface requires the following methods:
BeforeExecute(PluginContext context)
Execute(PluginContext context)
AfterExecute(PluginContext context)
Example Implementation
Here is an example implementation of an Azure DevOps IterationPath field transform:
using Lamdat.SyncNow.Common;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
namespace Lamdat.SyncNow.SyncPlugins
{
public class AzureDevOpsSprintPlugin : IPlugin
{
public async Task<PluginExecutionResult> BeforeExecute(PluginContext context)
{
return null;
}
public async Task<PluginExecutionResult> Execute(PluginContext context)
{
if (context.SourceFieldValue == null)
return new PluginExecutionResult() { Result = null, State = PluginResultStatus.Success };
var dstSprints = await context.TargetSystemAdapter.GetSystemIterations(new AdapterPluginGetIterationsPrms() { Name = "" });
var sprintPathParts = context.SourceFieldValue.ToString().Split('\\');
if (sprintPathParts.Length == 1)
{
if (dstSprints.Entities.Any())
{
var sprintPaths = dstSprints.Entities[0].Path.Split('\\');
if (sprintPaths.Any())
{
return new PluginExecutionResult() { Result = sprintPaths[0] };
}
else
{
return new PluginExecutionResult() { DoNotUpdateTargetField = true, Result = null, State = PluginResultStatus.PartiallySuccess, Error = $"Did not find a corresponding root sprint for '{context.SourceFieldValue}', please verify that all sprints exist for the default project team - current count {dstSprints.Entities.Count}" };
}
}
else
{
return new PluginExecutionResult() { DoNotUpdateTargetField = true, Result = null, State = PluginResultStatus.PartiallySuccess, Error = $"Did not find sprints in target system for source sprint '{context.SourceFieldValue}'. Please verify that all sprints exist for the default project team - current count {dstSprints.Entities.Count}" };
}
}
else
{
var lastSprint = sprintPathParts.Last();
List<string> strs = new List<string>();
var sprint = dstSprints.Entities.LastOrDefault(c =>
{
var partEnd = c.Path.Split('\\').Last();
strs.Add(partEnd);
return lastSprint.Equals(partEnd, StringComparison.OrdinalIgnoreCase);
});
if (sprint == null)
{
return new PluginExecutionResult() { DoNotUpdateTargetField = true, Result = null, State = PluginResultStatus.PartiallySuccess, Error = $"Warning - did not find a corresponding sprint for '{context.SourceFieldValue}', please verify that all sprints exist for the default project team - current count {dstSprints.Entities.Count}" };
}
else
return new PluginExecutionResult() { Result = sprint.Path };
}
return new PluginExecutionResult() { DoNotUpdateTargetField = true };
}
public async Task<PluginExecutionResult> AfterExecute(PluginContext context)
{
return null;
}
}
}
Plugin Context
The PluginContext
class provides the necessary context for the plugin execution.
Below is a sample structure of the PluginContext
class:
public class PluginContext
{
public dynamic SourceEntity { get; set; }
public IPluginAdapter SourceSystemAdapter { get; set; }
public IPluginAdapter TargetSystemAdapter { get; set; }
public SyncDirection SyncDirection { get; set; }
public SyncRun CurrentSyncRun { get; set; }
public ISyncNotify SyncErrorNotify { get; set; }
public string SourceEntityID { get; set; }
public string SourceEntityType { get; set; }
public string SourceEntityName { get; set; }
public string SourceFieldName { get; set; }
public object SourceFieldValue { get; set; }
public DataTypes SourceFieldType { get; set; }
public string SourceSystemName { get; set; }
public string TargetSystemName { get; set; }
public string TargetEntityType { get; set; }
public string TargetEntityID { get; set; }
public string TargetEntityKey { get; set; }
public string TargetFieldName { get; set; }
public DataTypes TargetFieldType { get; set; }
public AdapterType SourceSystemType { get; set; }
public AdapterType TargetSystemType { get; set; }
public AdapterInfo SourceAdapterInfo { get; set; }
public AdapterInfo TargetAdapterInfo { get; set; }
public dynamic CurrentTargetEntity { get; set; }
public List<Attachment> SourceSystemFoundAttachments { get; set; } = new List<Attachment>();
public List<Attachment> TargetSystemFoundAttachments { get; set; } = new List<Attachment>();
public List<EntityRelation> SourceSystemFoundRelations { get; set; } = new List<EntityRelation>();
public List<EntityRelation> TargetSystemFoundRelations { get; set; } = new List<EntityRelation>();
public List<Attachment> TargetSystemToSetAttachments { get; set; } = new List<Attachment>();
public List<EntityRelation> TargetSystemToSetRelations { get; set; } = new List<EntityRelation>();
public string PreviousComplexMappingValue { get; set; }
public List<TestsHierarchyDirectory> TargetTestsHierarchy { get; set; }
public List<string> SourceSystemBoardsID { get; set; }
public List<string> TargetSystemBoardsID { get; set; }
public List<SystemTestStep> TargetTestSteps { get; set; }
}
🔗 Registering the Plugin
Step-by-Step Guide
-
Navigate to the Processes Page
Go to the Processes page. -
Open Mapping Entities
Press the Mapping Entities button.
If the process inherits a global mapping, press the Edit Global Mapping button.
-
Open Field Mapping Configuration
Press the Fields Mapping button near the entity pair you want to transform.
-
Upload Plugin DLL
In the Field Mapping Configuration window, select the Plugin tab.- Upload the DLL assembly.
- Select the class name that will run for the field transform.
- Optionally, toggle the switch to pass source API call credentials to the plugin.
-
Save Changes
Press the Save button to apply the plugin.
Tip:
Use plugin DLL transforms for advanced data transformation scenarios that require custom logic, integration with external systems, or operations not possible with standard field mappings or scripts.