Extensibility

The Integration Framework can be extended in several places, depending on what you want to accomplish.

Below, the following extensibility points are described:

  • Custom Connectors for the Dynamicweb Connector Service
  • Live Integration notifications

If you are looking to integrate with a remote system with an active component (like Ax2012 or D365), you can also modify the official code unit to return more or less data than the default option.

A Connector is an add-in used by the Connector Service and Connector TestTool to connect to and communicate with a particular remote system in a manner which it understands. The Connector for Dynamics AX, for instance, connects to a plugin installed in AX, which then extracts data, wraps it in XML, and returns it to the Connector Service again. Other Connectors simply query a web API and return the responses to Dynamicweb as is, where they are then processed by an XSLT file on the import job.

Basically, a Connector:

  • Receives requests from Dynamicweb
  • Relays them to a remote system
  • Receives responses from the remote system
  • Relays the responses to Dynamicweb

Since Connectors are system-specific, a custom connector is typically only used when you want to create an integration to a system which is not supported by Dynamicweb by default. They can be as complicated or simple as you want them to be – maybe you want to transform your response data in the Connector instead of on either side of the integration. That’s totally up to you.

To create a custom Connector:

  • Download and install the Visual Studio templates for Dynamicweb 9 from the Downloads section
  • Create a new project in VS and select Class Library as the project template
  • Select the Dynamicweb 9 > Data Integration > DynamicwebConnectorAddin template (Figure 2.1)
  • Add references to System.Configuration and Dynamicweb.Ecommerce.Integration.Connectors.dll (which is found in the DynamicwebConnectorService folder)

In the ProcessRequest method, you can write your code for handling the XML requests from Dynamicweb to the remote system. See more below.

When the Connector Service receives a request, it is handled in the ProcessRequest method, relayed to the remote system, and the response is returned to Dynamicweb.

If you use one of the built-in batch integration task add-ins to request data, the standard requests & responses can be browsed here.

You can also use the import data with paging add-in to submit custom requests to the Connector Service, or even create a custom batch integration task add-in.

In the example add-in, the ProcessRequest method breaks down the request XML:

<GetEcomData> <tables> <Users type="all"/> </tables> </GetEcomData>

…and translates it into an SQL statement, which is then used to query an SQL database specified in a connectionString parameter from the settings in the DynamicwebConnectorService config file:

public override string ProcessRequest(string request) { string result = ""; NameValueCollection connectorSettings = (NameValueCollection)ConfigurationManager.GetSection("DynamicwebConnectorAddin"); string connectionString = connectorSettings["ConnectionString"]; using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); XmlDocument doc = new XmlDocument(); doc.LoadXml(request); if (doc.DocumentElement.Name == "GetEcomData") { result = "<tables>"; foreach (XmlNode node in doc.SelectNodes("//tables/*")) { switch (node.Name) { case "Users": string sqlCommand = "select * from Users"; SqlCommand command = new SqlCommand(sqlCommand, connection); var reader = command.ExecuteReader(); result = result + WrapInXml("AccessUser", reader); break; default: throw new Exception("request for table '" + node.Name + "' was not recognized by the Connector"); } } result = result + "</tables>"; } else { throw new Exception("request of type '" + doc.DocumentElement.Name + "' was not recognized by the Connector"); } } return result; }

As you can see, this Connector retrieves all users from the [Users] table of an SQL server database, then runs the response through a the WrapInXml method to wrap the data in the format understood by Dynamicweb: 

public static string WrapInXml(string tableName, SqlDataReader reader) { StringWriter sw = new StringWriter(); XmlWriter xml = new XmlTextWriter(sw); xml.WriteStartElement("table"); xml.WriteAttributeString("tableName", tableName); while (reader.Read()) { xml.WriteStartElement("item"); xml.WriteAttributeString("table", tableName); for (int i = 0; i < reader.FieldCount; i++) { if (reader[i] != DBNull.Value) { xml.WriteStartElement("column"); xml.WriteAttributeString("columnName", reader.GetName(i)); xml.WriteValue(reader[i]); xml.WriteEndElement(); } else { xml.WriteStartElement("column"); xml.WriteAttributeString("columnName", reader.GetName(i)); xml.WriteAttributeString("isNull", "true"); xml.WriteEndElement(); } } xml.WriteEndElement(); } xml.WriteEndElement(); return sw.ToString(); }

To use the custom DynamicwebConnectorService add-in, save and build your project – then copy the .dll to the DynamicwebConnectorService folder.

Open the DynamicwebConnectorService config file and create the relevant Connector changes:

<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="DynamicwebConnectorAddin" type="System.Configuration.NameValueSectionHandler"/> </configSections> <DynamicwebConnectorAddin> <add key="ConnectionString" value="YOUR CONNECTION STRING" /> </DynamicwebConnectorAddin> <appSettings> <add key="ServiceName" value="DynamicwebService" /> <add key="testMode" value="False" /> <add key="TestOutputFile" value="c:\exportContent.xml" /> <add key="Secret" value="test" /> <add key="WebserviceURI" value="http://localhost:8090/DynamicwebService"/> <add key="ErpConnectorType" value="DataIntegration.Examples.DynamicwebConnectorAddin"/> </appSettings> </configuration>

You must:

  • In the <configSections> node specify the name of your Connector
  • Create a section for settings – here the <DynamicwebConnectorAddin> section – with the appropriate nodes, in this case the ConnectionString parameter used to connect to an SQL database
  • In the <appSettings> section, point the ErpConnectorType node to your custom Connector

Next you should start the Dynamicweb service and check your connector is working using the TestTool.

In earlier versions of the integration framework, you could only extend the live integration by basically copying the code for the live integration project and building a custom live integration assembly from that. In other words, there were no real extensibility points, which made it difficult to e.g. reuse a project elsewhere or update the live integration down the road.

In contrast, Integration Framework v2 contains several extensibility hook which makes everything a bit easier:

  • It fires notifications that can be subscribed to, which allows you to inspect and alter the data being sent to the remote system, and to handle connectivity issues
  • Product Providers allow you to modify requests & responses for live product data, e.g. prices, stock levels, or descriptions

The notifications for the live integration fall into three categories:

  • XML generation – these fire before and after the XML for a document or child element is rendered. This allows you to inspect an alter the data before it’s turned into XML, and to inspect and alter the final XML before it’s sent to the remote system. A typical use case for this is adding extra information to a request, e.g. data from a payment gateway.
  • Orders – these fire before and after a cart or order is sent to the remote system. This allows you to inspect an order and alter or cancel it being sent altogether. After the order is returned, you can then inspect the data returned.
  • ERP communication – these fire before and after the framework communicates with the remote system, and when exceptions occur

These are explained below - or browse the API documentation:

The notifications for XML generation all come in pairs of two – a Before and an After notification:

  • Before provides you with a reference to the object for which XML is about to be generated. This is your last chance to inspect and alter the object before it is converted to XML.
  • After provides you with a reference to the same object, as well as to the generated XML document or node. This is an excellent place to modify the generated XML or to append your own nodes.

Each notification exposes an associated Args class (that inherits NotificationArgs) which provides additional information about the object for which XML is being generated. The subscribers also expose a property called GeneratorSettings whose type inherits XmlGeneratorSettings.

The XMLGeneratorSettings base class exposes properties that determine which data is included in the XML and how it’s formatted. The properties are:

Property

Description

Beautify

A Boolean that determines whether or not to format the output XML with line breaks and indenting. Off by default for integration, but it’s turned on for the export XML options for orders and users, resulting in a more human-friendly format.

LiveIntegrationSubmitType

Defines the source of the submit to the ERP. Some options include LiveOrderOrCart (as part of the user interaction at the frontend), ScheduledTask (the XML was submitted from a scheduled task) and ManualSubmit (a user (re)submitted an order from the Dynamicweb backend. See the API documentation for all options.

This value serves mostly as a human-friendly identifier to see why a specific XML request was sent or created.

ReferenceName

A string indicating the nature of the request. Contains values like OrderPut or UserPut so can see the XML was created as part of a PUT / create request.

Child classes of XmlGeneratorSettings add additional properties that determine which data to include. For instance, the OrderXmlGeneratorSettings class includes the properties AddOrderFieldsToRequest and AddOrderLineFieldsToRequest to control whether or not to include order fields and order line fields.

The Live Integration Framework sends order XML to the remote system in several different situations:

  • When a user interacts with a cart in frontend
  • When an order is completed in frontend
  • When a backend user (re)submits an order to the remote system
  • When the scheduled task QueuedOrdersSyncScheduledTask submits an order to the remote system

In all of these cases, Dynamicweb fires the OnBeforeSendingOrderToErp and OnAfterSendingOrderToErp notifications. The Before notification is cancellable, so you can prevent the order from being submitted programmatically if required.

The integration framework communicates with the remote system at various times – such as:

  • When a cart is calculated
  • When an order is saved
  • When a request for live prices is made

By subscribing to these notifications, you can keep an eye on this communication and take some action whenever a connection is made, lost, or restored.

To put this all together, imagine the following example. In order to prioritize shipping, you like to send an indication on an order about the user’s group membership, When the user is a Gold Customer, you aim to ship the order the same day, before all other orders. To accomplish this, you can add a custom field to the order, that indicates whether the current user is a member of the group Gold Customers.

The IsGoldCustomer isn’t implemented in this example as it’s not relevant. What’s important about the sample code is how it subscribes OnAfterGenerateOrderXml, then uses the Order property of the OnAfterGenerateOrderXmlArgs class to find the ID of the user that placed the order and then uses the XmlDocument to find the order elements and finally appends a custom node with the group status:

C#
using System; using System.Xml; using Dynamicweb.Ecommerce.LiveIntegration.Notifications; using Dynamicweb.Extensibility.Notifications; namespace Dynamicweb.Samples { [Subscribe(Order.OnAfterGenerateOrderXml)] public class AddInfoAboutGoldGroupMembershipToOrder : NotificationSubscriber { public override void OnNotify(string notification, NotificationArgs args) { var myArgs = (Order.OnAfterGenerateOrderXmlArgs)args; var document = myArgs?.Document; // Order can be found in tables/table[@tableName = "EcomOrders"]/item[@table = "EcomOrders"] var orderNode = document?.SelectSingleNode( "tables/table[@tableName = \"EcomOrders\"]/item[@table = \"EcomOrders\"]"); if (orderNode == null) { return; } var userId = myArgs.Order.CustomerAccessUserId; var isGoldMember = IsGoldMember(userId); AddChildXmlNode(orderNode, "isGoldMember", isGoldMember.ToString()); } private static void AddChildXmlNode(XmlNode parent, string nodeName, string nodeValue) { var node = parent.OwnerDocument.CreateElement("column"); node.SetAttribute("columnName", nodeName); node.SetAttribute("isCustomField", true.ToString()); node.InnerText = nodeValue; parent.AppendChild(node); } private bool IsGoldMember(int userId) { return true; } } }

If you this class into a Class Library and drop its assembly in your site’s bin folder, the next time that XML is generated for an order, it’ll contain a custom element for the group status:

XML
<column columnName="isGoldMember" isCustomField="True">True</column>

Note that because of the way XML is generated internally, this applies to all places where order XML is generated: for a cart, a real order, when the order is resubmitted from the backend or scheduled task and when you download a copy of the XML from within the backend of Dynamicweb.