Step 4 – Writing the connector
To perform provisioning, you must write a connector. To do this, you must create a solution in Visual Studio, create a connector configuration class and implement the connector logic.
When you perform this procedure, add a Class Library project targeting .NET 4.6.1. If you do not add this Class Library, OPS will not load the connector.
Create a Visual Studio solution
-
To develop an OPS extension, in Visual Studio, you must reference the Omada.OPS.Service.Interfaces and Omada.OPS.Service.Model assemblies.
The assemblies are installed as part of the SDK. They are located in the following folder. C:\Program Files\Omada SDK\V14.0\libs.
-
Add a reference to the two assembly files from your project.
noteTo make it easier to test the extension after you have compiled your project, you can set the output path of the project to point to the location of the OPS service.
To do this on Visual Studio, go to the Build tab. In the Output path field, enter the relevant path.
Connector Configuration class
Before you implement your connector class, which is named Connector
in the Connector Class
example that follows, you must first create a connector configuration class. To do this:
- In Visual Studio, create a connector configuration class which inherits from the
AbstractConnectorConfiguration
public class. This is going to be used by your connector class.
To keep the following code sample brief, the sample configuration includes only the property member filePath
that we have already mentioned and configured in the Enterprise Server.
This property member indicates the path to the location where the data storage XML file is to be stored on your hard drive.
See the following sample code:

Connector Class
You can now implement your connector's main class. See the following sample code:
[assembly: ContainsOpsExtensions]
namespace Acme.Foo.Connector
{
/// <summary>
/// This is a sample connector, demonstrating how to implement a custom connector as extension to OPS.
/// </summary>
[ConnectorName("Acme.Foo.Connector")]
public class Connector : AbstractProvisioningConnector<ConnectorConfiguration>
{
public Connector() { }
The following line of code is used to state that the assembly in which your connector class is implemented contains OPS Extension code. This line must be somewhere in your assembly for the OPS service to load the assembly:
[assembly: ContainsOpsExtensions]
The following line of code is used to state the connector ID:
[ConnectorName("Acme.Foo.Connector")]
This is the value you specified as the connectorId in your registration.xml document:

Implementing connector logic
You must now override the ProcessTask
method because it is responsible for provisioning tasks to the target system. An OPS connector handles one task at a time, for example:
/// <summary>
/// This is the main method needed by the connector in order to provision a task to the target data source.
/// Depending on the current task's associated objectType and operation,
/// a decision is taken about which action the connector should be proceed with and afterwards it accordingly interacts with the target data source.
/// </summary>
/// <param name="provisioningTask">The current provisioning task for which the connector was called to proceed with.</param>
/// <returns>The result of the provisioning task after its execution by the connector.</returns>
public override ProvisioningTaskResult ProcessTask(Task provisioningTask) {
The workflow to implement the ProcessTask
method depends on the task's object type and operation values. OPS supports the following task operations:
public enum OperationEnum : int
{
Create = 0,
Update = 1,
Delete = 2,
CreateOrUpdate = 3,
DeleteIfExists = 4
}
In the following example, you can see that the code manages the provisioning task associated with a sample user account by using the method UserAccountOperationManagement
:
/// <summary>
/// Manages user account.
/// </summary>
/// <param name="userId">User's id.</param>
/// <param name="operation">Task's operation.</param>
/// <param name="propertyValues">User Property Values.</param>
/// <returns></returns>
private ProvisioningTaskResult UserAccountOperationManagement(string userId, OperationEnum operation, ICollection<PropertyValue> propertyValues)
{
OperationResult operationResult;
ProvisioningTaskResult provisioningTaskResult;
switch (operation)
{
case OperationEnum.Create:
operationResult = this.CreateUserAccount(userId, propertyValues);
break;
case OperationEnum.Update:
operationResult = this.UpdateUserAccount(userId, propertyValues);
break;
case OperationEnum.CreateOrUpdate:
operationResult = this.CreateOrUpdateUser(userId, propertyValues);
break;
case OperationEnum.Delete:
operationResult = this.DeleteUser(userId, propertyValues);
break;
case OperationEnum.DeleteIfExists:
operationResult = this.DeleteUserIfExists(userId, propertyValues);
break;
default:
Log.ErrorFormat("[Sample connector:{0}] Error occurred processing invalid '{1}' task for object of type: '{2}' with id '{3}'.", this.provisioningTaskId, operation, SampleUserAccount, userId);
return new ProvisioningTaskPermanentFailure(string.Format("The operation type: '{0}' is not valid.", operation));
}
if (operationResult.Success)
provisioningTaskResult = new ProvisioningTaskSuccess(operationResult.Message);
else if (!operationResult.Success && operationResult.PermanentFailure)
{
Log.ErrorFormat("[Sample connector:{0}] Error occurred processing '{1}' task for object of type: '{2}' with id '{3}': '{4}'.", this.provisioningTaskId, operation, SampleUserAccount, userId, operationResult);
provisioningTaskResult = new ProvisioningTaskPermanentFailure(operationResult.Message);
}
else
{
Log.WarnFormat("[Sample connector:{0}] Transient error occurred processing '{1}' task for object of type: '{2}' with id '{3}': '{4}'.", this.provisioningTaskId, operation, SampleUserAccount, userId, operationResult);
provisioningTaskResult = new ProvisioningTaskTransientFailure(operationResult.Message);
}
return provisioningTaskResult;
}
The UserAccountOperationManagement
method returns a ProvisioningTaskResult object, which states whether the task of provisioning to the target system process succeeded, failed permanently, or failed briefly so that OPS will attempt to handle the task again later.
Result | Description |
---|---|
ProvisioningTaskSuccess | The task is successful. |
ProvisioningTaskTransientFailure | The failure is transient (temporary), and you must retry the task after you have changed the configuration values. |
ProvisioningTaskPermanentFailure | The failure is permanent. Do not retry the task. |
The sample connector uses the OperationResult
class to retrieve information after the end of the communication between the connector and the XML file used for data storage.
In the following example, we implemented the OperationResult
class to help you populate the ProvisioningTaskResult object later:
Omada recommends that you keep track of the provisioningTask handling process, and that you monitor potential exceptions by using a logging tool to view the status and operations of OPS.
/// <summary>
/// Creates a User object derived by the given user property values and the userId.
/// </summary>
/// <param name="userId">User's id to create user object.</param>
/// <param name="userPropertyValues">User property values.</param>
/// <returns>A user object.</returns>
private User CreateUserObjectFromPropertyValues(string userId, ICollection<PropertyValue> userPropertyValues)
{
User user = new User { UserId = userId };
foreach (PropertyValue userPropertyValue in userPropertyValues)
{
string property = userPropertyValue.PropertyDefinition.Name;
// Properties for a user object supported by the Sample connector.
switch (property)
{
case "FirstName":
user.FirstName = userPropertyValue.Value;
break;
case "LastName":
user.LastName = userPropertyValue.Value;
break; }
}
return user;
}