Connector Development v4.1 References
ExtensibleAgentController
Overview
The following are the subcomponents relating to the extensible agent user interface parts.
Basic Architecture
The following is the underlying core structure of the connector extensibility components.
In the following sections, each subcomponent required to implement this structure will be explored.
ExtensibleAgentController
For all the extensible components in the service, there is a corresponding ExtensibleController type that is used to manage them. In the case of agents, this is the ExtensibleAgentController. This controller exposes access to the Agent Engine through the AgentEngine property.
There are three Actions that need to be implemented for the ExtensibleAgentController, namely:
Create Agent
public abstract ActionResult Create(CreateAgentInformation createInformation);
Edit Agent
public abstract ActionResult Edit(EditAgentInformation editInformation);
Display Agent
public abstract PartialViewResult Display(DisplayAgentInformation displayInformation);
Create Agent
The create agent action should return a View maintaining a form to create a new agent of the custom type. As per the standard development practice, this should return a single view "CreateOrEdit", which is reused to edit the agent as well.
return View("CreateOrEdit", viewInformation);
As per the MVC architecture, views are provided a model. The model should represent all data required by the agent. For example, if the agent requires Uri and a Timeout, then an example model might be:
public class CustomAgentViewInformation : IAgentViewInformation
{
public AgentDetailsViewInformation AgentDetails { get; set; }
public bool EditMode { get; set; }
[Required] public string Uri { get; set; }
[Required] public TimeSpan Timeout { get; set; }
}
It is recommended that the model implement the IAgentViewInformation interface, as this will open up a number of extension methods to simplify the development process. One of these extensions is the NavigateToCreate which is sugar for returning a view in a standard manner:
@using Unify.Connect.Web
@using Unify.Framework.Web
@model CustomAgentViewInformation
@using (Html.DecorateForm(
() => Html.BeginForm(),
"Save Settings",
"SaveCustomAgentForm",
"Submits the configuration of the agent."))
{
@Html.AgentDetailsFor(model => model)
<fieldset>
<legend>Connection Information</legend>
@using (Html.MetaDataFor(model => model.Uri, new { description = "The uri describing the location of the service." }))
{
@Html.LabelFor(model => model.Uri)
@Html.TextBoxFor(model => model.Uri)
}
@using (Html.MetaDataFor(model => model.Timeout, new { description = "The timeout period after which a connection without response will be closed." }))
{
@Html.LabelFor(model => model.Timeout)
@Html.TextBoxFor(model => model.Timeout)
}
</fieldset>
}
There's a lot to cover in the above:
- Html.DecorateForm The Html.DecorateForm method is an extension for the MVC3 HtmlHelper class. It renders out a standard Identity Broker form, with a submit and cancel button, as well as a heading/title and description text. It is recommended that this be used for full-page forms to maintain consistency with the rest of the product.
Html.BeginForm('CreateOrEdit',... The Html.BeginForm extension generates the actual <form> tag in html. This is one of the standard MVC3 Html extensions. The important part is the selected action name 'CreateOrEdit'. As per standard implementation practices, a single view should be used to describe both creating and editing a component. There should also be a single action to submit the form data to. In this example, that is the CreateOrEdit or edit action.
[HttpPost] [UnifySiteNode("ExampleAgentCreateOrEdit", typeof(IdentityBrokerSiteMapResources), "AgentIndex")] public ActionResult CreateOrEdit(CustomAgentViewInformation viewInformation) { if (viewInformation.EditMode) { AgentEngine.UpdateAgent(...); } else { AgentEngine.AddAgent(...); } }
- Html.AgentDetailsFor The Html.AgentDetailsFor extension creates a series of standard form parts required to describe the agent (Display Name, and Comment).
- Html.MetaDataFor The Html.MetaDataFor extension is used to render the various standard components of a particular field. These are things like putting a Req. next to a field if it's a [Required] field, and rendering it's tool-tip (as defined in the description tag).
- <fieldset> The fieldset is a standard HTML tag, and renders out on the UI as the darker text. As per standard implementation dictates, each grouping of fields should be maintained in their own fieldset with an appropriate legend:
Edit Agent
The edit agent step reuses a lot of components from create. Namely, it should redirect back to the same view, and that view will post to the same CreateOrEdit action. The major difference is that on create we only populate the view information with reasonable defaults. On edit, the view information is populated with the corresponding data of the agent configuration.
As with the create method, there is an extension on the given information to standardise the implementation:
return editInformation.NavigateToEdit<ICustomAgentConfiguration, CustomAgentViewInformation>(
AgentEngine,
element =>
{
return new CustomAgentConfiguration
{
Uri = element.Attribute("uri").Value,
Timeout = TimeSpan.Parse(element.Attribute("timeout").Value)
};
},
(config, view) =>
{
view.Uri = config.Uri;
view.Timeout = config.Timeout;
},
view => View("CreateOrEdit", view)
);
Because we need to convert the XML of the connector to the view information there is one more step.
- First, the XML is provided, as its stored in the connector. A new intermediate configuration object is de-serialized from this xml block.
- The view information is then populated with the information from that configuration object.
- The CreateOrEdit view is then returned using the generated view information.
The rest flows on from the implementation of the CreateOrEdit view defined above.
Display Agent
The display agent step is quite a bit different to Create/Edit, in the sense that (traditionally) it doesn't involve any forms. In a standard implementation, the display agent UI part should only consist of labels/paragraphs used to describe the connector configuration.
As the standard implementation dictates, as much of the agent configuration should be displayed. The reason is, that otherwise the user would have to edit the agent just to see the configuration.
The manner in which an agent is displayed is entirely up to the implementer.
CreateOrEdit Pattern
As
mentioned earlier, the CreateOrEdit pattern is the pattern in which a
single View and Action are used to describe both Creating and Editing an
object. This will reduce the development time considerably to the
alternative, and reduces the surface area of pluggable controllers. The
implementation of the CreateOrEdit view can be seen above, but the
implementation of the action that is posted to was skipped over.
The following example uses extension methods only applicable to the view information of IAgentViewInformation
[HttpPost]
[UnifySiteNode("ExampleAgentCreateOrEdit", typeof(IdentityBrokerSiteMapResources), "AgentIndex")]
public ActionResult CreateOrEdit(CustomAgentViewInformation viewInformation)
{
if (!ModelState.IsValid)
return View("CreateOrEdit", viewInformation);
XElement agentXml = new XElement("Extended");
agentXml.Add(new XAttribute("uri", viewInformation.Uri));
agentXml.Add(new XAttribute("timeout", viewInformation.Timeout));
AgentEngine.CommitAgent(viewInformation, agentXml);
return GoToAgent(viewInformation.AgentDetails.Id);
}
There's a lot to cover in the above:
- if (!ModelState.IsValid) This is a standard MVC3 implementation method. Namely, ensure that the Model (the provided CustomAgentViewInformation in this case), is valid. If it isn't, return the view you were just on with the view information provided. This will make the validation errors render out (as defined by our MetaDataFor extensions).
- XElement agentXml = new XElement("Extended"); Custom agents may need additional configuration information to describe their purpose. These may be things such as agents, uris, connection information, etc. etc. This information is described in an xml block provided for each connector. We generate this xml based on the information provided by the UI, and send it back to the service.
- AgentEngine.Commit This will commit the agent with the configuration described the view information (display name, and comment), as well as the XML generated. This will either resolve to an Update or Add on depending on the EditMode in the provided view information.
- GoToAgent This will return a redirection to the Agent Details page.
ExtensibleConnectorController
Overview
The following are the subcomponents relating to the extensible connector user interface parts.
Basic Architecture
The following is the underlying core structure of the connector extensibility components.
In the following sections, each subcomponent required to implement this structure will be explored.
ExtensibleConnectorController
For all the extensible components in the service, there is a corresponding ExtensibleController type that is used to manage them. In the case of connectors this is the ExtensibleConnectorController. This controller exposes access to the Connector Engine through the ConnectorEngine property, and the Agent Engine through the Agent Engine property.
There are three Actions that need to be implemented for the ExtensibleConnectorController, namely:
Create Connector
public abstract ActionResult Create(CreateConnectorInformation createInformation);
Edit Connector
public abstract ActionResult Edit(EditConnectorInformation editInformation);
Display Connector
public abstract PartialViewResult Display(DisplayConnectorInformation displayInformation);
Create Connector
The create connector action should return a View maintaining a form to create a new connector of the custom type. As per the standard development practice, this should return a single view "CreateOrEdit", which is reused to edit the connector as well.
return View("CreateOrEdit", viewInformation);
As per the MVC architecture, views are provided a model. The model should represent all data required by the connector. For example, if the connector requires a Uri and a Table name, then an example model might be:
public class CustomConnectorViewInformation : IConnectorViewInformation
{
public bool EditMode { get; set; }
public ConnectorDetailsViewInformation ConnectorDetails { get; set; }
[Required] public string Uri { get; set; }
[Required] public string TableName { get; set;
}
It is recommended that the model implement the IConnectorViewInformation interface, as this will open up a number of extension methods to simplify the development process. One of these extensions is the NavigateToCreate which is sugar for returning a view in a standard manner:
public override ActionResult Create(CreateConnectorInformation createInformation)
{
return createInformation.NavigateToCreate<CustomConnectorViewInformation>(
view =>
{
view.Uri = "http://localhost:8080/"; // Example/default uri
view.TableName = string.Empty; // No example tablename to give.
},
view => View("CreateOrEdit", view));
}
Once on the view, there are again a number of extensions to simplify the process. First, look at the following example implementation of CreateOrEdit.cshtml.
@using Unify.Connect.Web
@using Unify.Framework.Web
@using Unify.IdentityBroker.Connector
@model CustomConnectorViewInformation
@using (Html.DecorateForm(
() => Html.BeginForm("CreateOrEdit", "CustomConnector", new { area = "Extensibility }),
"Save Settings",
"SaveCustomConnectorForm",
"Saves the connector with the provided configuration."))
{
@Html.ConnectorDetailsFor(model => model)
<fieldset>
<legend>Connection Information</legend>
@using (Html.MetaDataFor(model => model.Uri, new { description = "The uri defining the location of the service." }))
{
@Html.LabelFor(model => model.Uri)
@Html.TextBoxFor(model => model.Uri)
}
@using (Html.MetaDataFor(model => model.TableName, new { description = "The name of the table maintaining the identity data." }))
{
@Html.LabelFor(model => model.TableName, "Table Name")
@Html.TextBoxFor(model => model.TableName)
}
</fieldset>
}
There's a lot to cover in the above:
- Html.DecorateForm The Html.DecorateForm method is an extension for the MVC3 HtmlHelper class. It renders out a standard Identity Broker form, with a submit and cancel button, as well as a heading/title and description text. It is recommended that this be used for full-page forms to maintain consistency with the rest of the product.
Html.BeginForm('CreateOrEdit',... The Html.BeginForm extension generates the actual <form> tag in html. This is one of the standard MVC3 Html extensions. The important part is the selected action name 'CreateOrEdit'. As per standard implementation practices, a single view should be used to describe both creating and editing a component. There should also be a single action to submit the form data to. In this example, that is the CreateOrEdit or edit action.
[HttpPost] [UnifySiteNode("ExampleConnectorCreateOrEdit", typeof(IdentityBrokerSiteMapResources), "ConnectorIndex")] public ActionResult CreateOrEdit(CustomConnectorViewInformation viewInformation) { if (viewInformation.EditMode) { ConnectorEngine.UpdateConnector(...); } else { ConnectorEngine.AddConnector(...); } }
Html.ConnectorDetailsFor The Html.ConnectorDetailsFor extension creates a series of standard form parts required to describe the connector (Display Name, Queue on Block, Comment).
For this to be used the model must implement IConnectorViewInformation.
- Html.MetaDataFor The Html.MetaDataFor extension is used to render the various standard components of a particular field. These are things like putting a Req. next to a field if it's a [Required] field, and rendering it's tool-tip (as defined in the description tag).
- <fieldset> The fieldset is a standard HTML tag, and renders out on the UI as the darker text. As per standard implementation dictates, each grouping of fields should be maintained in their own fieldset with an appropriate legend:
Edit Connector
The edit connector step reuses a lot of components from create. Namely, it should redirect back to the same view, and that view will post to the same CreateOrEdit action. The major difference is that on create we only populate the view information with reasonable defaults. On edit, the view information is populated with the corresponding data of the connector configuration.
As with the create method, there is an extension on the given information to standardise the implementation:
return editInformation.NavigateToEdit<ICustomConnectorConfiguration, CustomConnectorViewInformation>(
ConnectorEngine,
connectorXml =>
{
return new CustomConnectorConfiguration
{
Uri = connectorXml.Attribute("uri").Value,
TableName = connectorXml.Attribute("tableName").Value
}
},
(config, view) => {
view.Uri = config.Uri;
view.TableName = config.TableName;
},
view => View("CreateOrEdit", view)
);
Because we need to convert the XML of the connector to the view information there is one more step.
- First, the XML is provided, as its stored in the connector. A new intermediate configuration object is de-serialized from this xml block.
- The view information is then populated with the information from that configuration object.
- The CreateOrEdit view is then returned using the generated view information.
The rest flows on from the implementation of the CreateOrEdit view defined above.
Display Connector
The display connector step is quite a bit different to Create/Edit in the sense that (traditionally) it doesn't involve any forms. In a standard implementation, the display connector UI part should only consist of labels/paragraphs used to describe the connector configuration.
As the standard implementation dictates, as much of the connector configuration should be displayed. The reason is, that otherwise the user would have to disable the connector, and go to the edit form to see the configuration.
The manner in which the connector is displayed is entirely up to the implementer.
CreateOrEdit Pattern
As
mentioned earlier, the CreateOrEdit pattern is the pattern in which a
single View and Action are used to describe both Creating and Editing an
object. This will reduce development time considerably to the
alternative, and reduces the surface area of the pluggable controllers.
The implementation of the CreateOrEdit view can be seen above, but the
implementation of the action that is posted to was skipped over.
The following example uses extension methods only applicable to a view information of IConnectorViewInformation
[HttpPost]
[UnifySiteNode("ExampleConnectorCreateOrEdit", typeof(IdentityBrokerSiteMapResources), "ConnectorIndex")]
public ActionResult CreateOrEdit(CustomConnectorViewInformation viewInformation)
{
if (!ModelState.IsValid)
return View("CreateOrEdit", viewInformation);
XElement connectorXml = new XElement("Extended");
connectorXml.Add(new XAttribute("uri", viewInformation.Uri));
connectorXml.Add(new XAttribute("tableName", viewInformation.TableName));
ConnectorEngine.Commit(viewInformation, connectorXml);
return NavigateToConnector(viewInformation.ConnectorDetails.ConnectorId);
}
There's a lot to cover in the above:
- if (!ModelState.IsValid) This is a standard MVC3 implementation method. Namely, ensure that the Model (the provided CustomConnectorViewInformation in this case), is valid. If it isn't, return the view you were just on with the view information provided. This will make the validation errors render out (as defined by our MetaDataFor extensions).
- XElement connectorXml = new XElement("Extended"); Custom connectors may need additional configuration information to describe their purpose. These may be things such as agents, uris, connection information, etc. etc. This information is described in an xml block provided for each connector. We generate this xml based on the information provided by the UI, and send it back to the service.
- ConnectorEngine.Commit This will commit the connector with the configuration described in the view information (display name, comment and queue on blocked), as well as the XML generated. This will either resolve to an Update or Add depending on the EditMode in the provided view information.
- NavigateToConnector This will return a redirection to the Connector Details page.
Customer support service by UserEcho