- Kentico Xperience 13
Sync form submissions with Integration bus in Kentico Xperience 13
In this post, I will provide a code snippet and tips on how to synchronize new form submissions with an external system via its REST API using the Integration Bus in Kentico Xperience 13.
Problem
When you need to react to events in Kentico Xperience 13 and communicate with external systems, you have several implementation options. One of the most popular approaches is using Global Event Handlers. However, they are synchronous, and you may lose data if the external system is unavailable at the time of synchronization.
Another approach is using the Integration Bus, which can be implemented asynchronously. If the external system is unavailable, Xperience retries the synchronization task, preventing data loss. Additionally, Xperience provides a user-friendly UI for managing synchronization tasks.
In one of my projects, I chose to use the Integration Bus to synchronize form submissions through a REST API. Despite Xperience’s extensive documentation, I encountered some challenges during implementation, which I will discuss. I will also provide a code snippet that accomplishes this task.
Solution
I love code examples, and the best way to start implementing the Integration Bus in your project is by following the example outlined in the Xperience documentation. However, after implementing the example on my machine, I ended up with a partially working solution. The form submission tasks were not tracked by the Integration Bus because I did not reference the new class library in both the live site and Xperience administration projects.
Another issue was finding the object type codename for form submissions. By specifying the object type, the Integration Bus can subscribe to object events. Object types registered in Xperience can be found in the System > Object Types application. Unfortunately, there is no mention of an object type representing a form submission. Eventually, I discovered that the object type codename has the following format: bizformitem.bizform.<your_form_codename_lowercased>
. This detail is not mentioned in the documentation.
The following code snippet outlines an Integration Bus class library that:
- Subscribes to the Create object event for your form submissions.
- When the event occurs, the form data is serialized into a JSON structure and sent through a REST API.
- The snippet also demonstrates how to:
- Access form attachments and convert them to Base64.
- Log messages in the Event Log.
using System;
using System.Net;
using System.Text;
using System.IO;
using CMS;
using CMS.Core;
using CMS.Base;
using CMS.SynchronizationEngine;
using CMS.DataEngine;
using CMS.Synchronization;
using CMS.OnlineForms;
using CMS.Helpers;
using CMS.FormEngine;
using Newtonsoft.Json;
[assembly: RegisterCustomClass("IntegrationConnector", typeof(IntegrationConnector))]
public class IntegrationConnector : BaseIntegrationConnector
{
protected const string FORM_OBJECTTYPE = PredefinedObjectType.BIZFORM_ITEM_PREFIX + "bizform.<your_form_codename_lowercased>";
public override void Init()
{
ConnectorName = GetType().Name;
SubscribeToObjects(TaskProcessTypeEnum.AsyncSimpleSnapshot, FORM_OBJECTTYPE, TaskTypeEnum.CreateObject);
}
public override IntegrationProcessResultEnum ProcessInternalTaskAsync(GeneralizedInfo infoObj, TranslationHelper translations, TaskTypeEnum taskType, TaskDataTypeEnum dataType, string siteName, out string errorMessage)
{
errorMessage = null;
try
{
if (infoObj.TypeInfo.ObjectType == FORM_OBJECTTYPE)
{
ProcessSubmission(infoObj);
return IntegrationProcessResultEnum.OK;
}
else
{
errorMessage = "Unknown object type: " + infoObj.TypeInfo.ObjectType;
return IntegrationProcessResultEnum.ErrorAndSkip;
}
}
catch (Exception ex)
{
errorMessage = ex.Message;
Service.Resolve<IEventLogService>().LogError(ConnectorName, taskType.ToString(), errorMessage);
return IntegrationProcessResultEnum.Error;
}
finally
{
ClearInternalTranslations();
}
}
internal void ProcessSubmission(ISimpleDataContainer infoObj)
{
int itemId = ValidationHelper.GetInteger(infoObj.GetValue("FormID"), 0);
string column = JsonConvert.ToString(ValidationHelper.GetString(infoObj.GetValue("ColumnName"), ""));
string file = GetFileValue(itemId);
string fileBase64 = GetFileBase64(file);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("<your_endpoint_url>");
string body = "{ \"columnName\": " + column + ", \"file\": \"" + fileBase64 + "\" }";
Service.Resolve<IEventLogService>().LogInformation(ConnectorName, "HTTP_REQUEST", body);
byte[] data = Encoding.ASCII.GetBytes(body);
request.Method = "POST";
request.ContentType = "application/json";
request.ContentLength = data.Length;
using (var stream = request.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
string responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
Service.Resolve<IEventLogService>().LogInformation(ConnectorName, "HTTP_RESPONSE", responseString);
}
internal string GetFileValue(int itemId)
{
BizFormInfo formObject = BizFormInfo.Provider.Get("<your_form_codename>", <your_site_id>);
DataClassInfo formClass = DataClassInfoProvider.GetDataClassInfo(formObject.FormClassID);
string className = formClass.ClassName;
ObjectQuery<BizFormItem> data = BizFormItemProvider.GetItems(className);
if (!DataHelper.DataSourceIsEmpty(data))
{
foreach (BizFormItem item in data)
{
if (item.GetIntegerValue("FormID", 0) == itemId)
{
return item.GetStringValue("<form_attachment_field_codename>", "");
}
}
}
return "";
}
internal virtual string GetFileBase64(string file)
{
if (!String.IsNullOrEmpty(file))
{
byte[] attachmentData = GetFormAttachment(file);
return Convert.ToBase64String(attachmentData);
}
return "";
}
protected byte[] GetFormAttachment(string attachmentFieldValue)
{
string fileName = FormHelper.GetGuidFileName(attachmentFieldValue);
string filePath = FormHelper.GetFilePhysicalPath("<your_site_name>", fileName);
if (File.Exists(filePath))
{
byte[] bytes = File.ReadAllBytes(filePath);
return bytes;
}
return null;
}
}
Syncing Form Submissions from Multiple Forms
If you need to synchronize form submissions from multiple forms, and you cannot specify them by form codename, there is a solution. This approach also applies to forms that will be created in the future. The implementation of the Init
method differs in this case. You need to create an ObjectIntegrationSubscription
object and then call the SubscribeTo
method with that object as a parameter.
The crucial aspect is the object type, which uses a wildcard to ensure that form submissions from all forms are observed. Here is the code snippet:
public override void Init()
{
ConnectorName = GetType().Name;
ObjectIntegrationSubscription objSub = new ObjectIntegrationSubscription(ConnectorName, TaskProcessTypeEnum.AsyncSimpleSnapshot, TaskTypeEnum.CreateObject, null, BizFormItemProvider.BIZFORM_ITEM_PREFIX + "%", null);
SubscribeTo(objSub);
}
About the author
Milan Lund is a Freelance Web Developer with Kentico Expertise. He specializes in building and maintaining websites in Xperience by Kentico. Milan writes articles based on his project experiences to assist both his future self and other developers.
Find out more