Connector Development v4.1 Sample User Interface Implementation

In the following example, we assume that the service side components have already been implemented. We will be implementing all of the user-interface components required to describe our Employee agent and the two connectors (Terminations and Details). Finally we'll show a sample Identity Broker configuration with the implemented solution.

Developing the Agent UI 

The plugin architecture on the service side acts by registering a series of ExtensibleControllers. These extensible controllers serve as the entry point from the core product, and then handle all subsequent operations required to CreateUpdate or Display an agent/connector. In this case, we'll need an ExtensibleAgentController to represent our Agent on the user interface.

public class EmployeesAgentController : ExtensibleAgentController
{
    private const string DefaultConnectionString = "Server=localhost;Database=Employees;Trusted_Connection=true";
    private readonly IPlugInFactory<EmployeesAgentConfiguration, XElement> _ConfigurationFactory;
    private readonly IValueAdapter<XElement, EmployeesAgentConfiguration> _ConfigurationAdapter;
 
    /// <summary>Creates an instance of this controller.</summary>
    public EmployeesAgentController()
    {
        _ConfigurationFactory = new EmployeesAgentConfigurationFactory();
        _ConfigurationAdapter = new EmployeesAgentConfigurationAdapter();
    }
 
    /// <summary>Action to create a new agent using the base details provided in the <paramref name="createInformation"/>.</summary>
    /// <param name="createInformation">Information to create the agent.</param>
    /// <returns>A view to handle agent creation.</returns>
    public override ActionResult Create(CreateAgentInformation createInformation)
    {
        return createInformation.NavigateToCreate<EmployeesAgentViewInformation>(
            view =>
            {
                view.ConnectionString = DefaultConnectionString;
            },
            view => View("CreateOrEdit", view));
    }
 
    /// <summary>Action to edit an existing agent described in the <paramref name="editInformation"/>.</summary>
    /// <param name="editInformation">Information describing the agent to edit.</param>
    /// <returns>A view to handle the editing of an agent.</returns>
    public override ActionResult Edit(EditAgentInformation editInformation)
    {
        return editInformation.NavigateToEdit<EmployeesAgentConfiguration, EmployeesAgentViewInformation>(
            AgentEngine,
            _ConfigurationFactory.CreateComponent,
            (config, view) =>
            {
                view.ConnectionString = config.ConnectionString;
            },
            view => View("CreateOrEdit", view));
    }
 
    /// <summary>Action to handle submission of employees agents.</summary>
    /// <param name="viewInformation">View information for the employee agent being configured.</param>
    /// <returns>The create or edit view.</returns>
    [HttpPost]
    [UnifySiteNode("EmployeesAgentCreateOrEdit", typeof(SiteMapResources), "AgentIndex")]
    public ActionResult CreateOrEdit(EmployeesAgentViewInformation viewInformation)
    {
        if (!ModelState.IsValid)
            return View("CreateOrEdit", viewInformation);
        var configuration = new EmployeesAgentConfiguration { ConnectionString = viewInformation.ConnectionString };
        XElement agentXml = _ConfigurationAdapter.Transform(configuration);
        AgentEngine.CommitAgent(viewInformation, agentXml);
        return GoToAgent(viewInformation.AgentDetails.Id);
    }
 
    /// <summary>Action to display an existing agent described in the <paramref name="displayInformation"/>.</summary>
    /// <param name="displayInformation">Information describing an agent to display.</param>
    /// <returns>A partial view to handle the display of an agent's important information.</returns>
    public override PartialViewResult Display(DisplayAgentInformation displayInformation)
    {
        AgentRemotingConfiguration agent = AgentEngine.GetAgent(displayInformation.AgentId);
        EmployeesAgentConfiguration configuration = _ConfigurationFactory.CreateComponent(agent.Extended);
        return PartialView(
            "Display",
            new EmployeesAgentViewInformation
            {
                ConnectionString = configuration.ConnectionString
            });
    }
}

In the above, we sufficiently implement the agent controller. There's a lot to cover in the above, so for a more in-depth description of the components used check out the IdentityBroker Framework (Web) page. But simply put, the contract defined by the ExtensibleAgentController defines the entry points into the extensible controller (Create, Update, and Display). An extension is provided for Create and Update to orchestrate a standard implementation. Configuration factories/adapters are used to encapsulate the XML logic that we wrote manually in the sample service implementation.

Developing the Connector UI

As with the agent we need to implement the contract through an ExtensibleConnectorController. Below is just the terminations connector, but it will be a similar implementation for the details connector:

public class TerminationsConnectorController : ExtensibleConnectorController
{
    /// <summary>Action to create a new connector using the base details provided in the <paramref name="createInformation"/>.</summary>
    /// <param name="createInformation">Information to create the connector.</param>
    /// <returns>A view to handle the creation of a connector.</returns>
    public override ActionResult Create(CreateConnectorInformation createInformation)
    {
        return createInformation.NavigateToCreate<TerminationsConnectorViewInformation>(
                view =>
                    {
                        view.AvailableAgents = AvailableAgents;
                    },
                view => View("CreateOrEdit", view));
    }
 
    /// <summary>Action to edit an existing connector described in the <paramref name="editInformation"/>.</summary>
    /// <param name="editInformation">Information describing the connector to edit.</param>
    /// <returns>A view to handle the editing of a connector.</returns>
    public override ActionResult Edit(EditConnectorInformation editInformation)
    {
        return editInformation.NavigateToEdit<XElement, TerminationsConnectorViewInformation>(
            ConnectorEngine,
            element => element,
            (connector, element, view) =>
            {
                view.AvailableAgents = AvailableAgents;
                view.AgentId = connector.Agents[EmployeesAgentConfigurationAttributes.EmployeesAgentKey];
            },
            view => View("CreateOrEdit", view));
    }
 
    /// <summary>Action to display an existing connector described in the <paramref name="displayInformation"/>.</summary>
    /// <param name="displayInformation">Information describing the connector to display.</param>
    /// <returns>A partial view to handle the display of a connector's important information.</returns>
    public override PartialViewResult Display(DisplayConnectorInformation displayInformation)
    {
        ConnectorEngineConfiguration connector =ConnectorEngine.GetConnector(displayInformation.ConnectorId);
        Guid employeeAgentId = connector.Agents[EmployeesAgentConfigurationAttributes.EmployeesAgentKey];
        return PartialView(
            "Display",
            new TerminationsConnectorViewInformation
            {
                AgentId = employeeAgentId,
                AvailableAgents = AvailableAgents
            });
    }
 
    /// <summary>Action to handle submission of terminations connector.</summary>
    /// <param name="viewInformation">View information of the terminations connector being configured.</param>
    /// <returns>Redirection based on success status.</returns>
    [HttpPost]
    [UnifySiteNode("TerminationsConnectorCreateOrEdit", typeof(SiteMapResources), "ConnectorIndex")]
    public ActionResult CreateOrEdit(TerminationsConnectorViewInformation viewInformation)
    {
        if (!ModelState.IsValid)
        {
            viewInformation.AvailableAgents = AvailableAgents;
            return View("CreateOrEdit", viewInformation);
        }
        ConnectorEngine.CommitConnector(
            viewInformation,
            XElementExtensions.CreateExtendedElement(),
            new Dictionary<string, Guid>
            {
                { EmployeesAgentConfigurationAttributes.EmployeesAgentKey, viewInformation.AgentId }      
            });
 
        return NavigateToConnector(viewInformation.ConnectorDetails.ConnectorId);
    }
 
    private IDictionary<Guid, string> AvailableAgents
    {
        get
        {
            return AgentEngine.GetAllAgents()
                .Where(agent => agent.Type == EmployeesAgentConfigurationAttributes.EmployeesAgentFactoryName)
                .ToDictionary(agent => agent.AgentId, agent => agent.DisplayName);
        }
    }
}

Similar to the Agent controller, there is enough going on that the IdentityBroker Framework (Web) page should be referred to. Of particular note is the AvailableAgents property which is manually added. We include this so that it can be injected into the Model for a drop down on the UI. This must be re-injected on the CreateOrEdit submission action as it is not serialized in the form.

Registering the Components

As with most components in the UNIFY stack, there is a factory interface that needs to be implemented for the controllers to be registered. In the case of controllers this is the IUnifyStudioExtensibilityPlugInFactory.

[UnifyExtensibilityController(EmployeesConnectorConfigurationAttributes.DetailsConnectorFactoryName)]
public class DetailsConnectorControllerFactory : IUnifyStudioExtensibilityPlugInFactory
{
    /// <summary>Creates a instance of the target type from the information provided by <paramref name="factoryInformation"/>.</summary>
    /// <param name="factoryInformation">The information used to create the plugin object.</param>
    /// <returns>An instance of the plug in.</returns>
    public UnifyController CreateComponent(IUnifyStudioExtensibilityPlugInInformation factoryInformation)
    {
        return new DetailsConnectorController();
    }
}

Configuring our Implementation

Once everything has been registered, we can configure our original use case of describing an Employee from its details and termination information.

First, we create the agent to describe where the system is:

With the agent created, we can now create our terminations connector and details connector, and it will be visible on the drop down.

 

And also the schemas will need to be populated manually as in our implementation they weren't registered as defaults:

Now that our subsets of data have been described, we need to glue them together into the Employee object class we originally wanted to manage. This is achieved through an Adapter.

And the termination information is glued in through a Join transformation.

And with that, we can start importing data for the Employee object class.

Is this article helpful for you?