Following on from the previous blog post where we demonstrated a custom workflow acting as an interface between Umbraco Forms and the CRM provider HubSpot, this second article in the series introduces another new package providing a further example of such an integration.
As before, we are releasing the package, source code, and this explanatory blog post in an attempt to highlight the various ways Umbraco can be integrated with other providers, as part of a composable DXP solution. You are welcome to use the package directly, or just peruse the source code and use it as inspiration for similar integrations you may need to tackle in your own projects.
The full source code is also available here on GitHub. And just repeating the caveat from last time - we’re happy to take PRs and respond to issues, but please be aware that this isn’t a supported Umbraco product. It’s more of an MVP in this first version that we’d like to make available to the community.
Credit and thanks for much of the work here go to Søren Kottal and Ecreo.
Working with CommerceTools
CommerceTools is a provider of e-commerce solutions, integrated headlessly into web and mobile applications. Store managers will work with a back-office provided by CommerceTools, known as the “Merchant Center”, where the commerce-related administration functions such as product setup and order management are performed. The commerce data is then surfaced via an API, through which it can be integrated into customer websites and applications.
The provided API is much as you’d expect - with well documented, REST-based endpoints, accessible via an OAuth token flow.
In the example we’re providing here, we’ve used these APIs to build a custom property editor, allowing an editor to pick a CommerceTools provided product or category and associate the identifier of the selected item with an Umbraco content item. We then have a property value converter that converts that identifier into a strongly typed model, which is hydrated via a further API call and can be used for rendering to a page or including in a further API response.
Building the Custom Property Editor
When compared to the previously blogged integration where we were able to show almost all the code, we have a code-base that’s a bit more involved this time and so we will just pick out the important parts for further explanation. For the full detail, you can of course dig into the source code.
Property Editor Configuration
Let’s start though with the property editor itself, looking at the code for V8. To create one of these using C#, a class inheriting from DataEditor is required, which is attributed with various settings, and has a method for the available configuration overridden.
The configuration for the editor is exposed via a class inheriting ConfigurationEditor...
…which takes a generic type of the configuration itself, defining fields for each of the options that the developer can configure when using the property editor on a document type (page size, default ordering options, etc.).
Each field has a corresponding client-side editor - some of which are provided by Umbraco, and some have been created as custom. For example, the Entity Type field has an angularjs view as follows, which allows the same property editor to be used for picking products or categories, as appropriate:
When used in the back-office, a data type can be created from the property editor and configured as required:
When the property editor is used by an editor creating or updating content based on a document type that uses it, the angular editor defined in the property editor’s DataEditor attribute is rendered - in our case CommerceToolsPicker.html.
Initially, the collection of picked items will be empty, and the editor will click to open an overlay from where the products or categories can be selected.
It’s at this stage we need to retrieve data from the CommerceTools API. Based on the configuration and any filters provided by the editor, we can trigger a search, via the client-side CommerceToolsResource.js that in turn makes an AJAX call to a back-end controller’s action method.
An example controller method looks like this, which takes the provided search information and delegates it to a method defined on a service class CommerceToolsService.
The implementation of the relevant methods in the CommerceToolsService is likely more clearly followed on GitHub or with a cloned copy of the solution. Key things to note include the method of authorization with the Commerce ToolsAPI - where we call and cache the response from a token endpoint, having provided our credentials (client Id and secret). The token is then presented in subsequent calls as an authentication header - until such time that it expires when we need to request a new one.
This is a common approach taken for API authentication, reducing load and adding a layer of security by not having to present and validate our credentials on every request.
When products or categories are selected, we store the identifiers but display in the back-office fuller information about the selections. Again API calls mediated via the CommerceToolsService are used here, to convert the raw IDs into objects with details such as the product or category name:
When an editor selects a set of items in the picker, the only thing we store as content in Umbraco is the IDs. Whilst we could save other information such as product names, it’s only the ID that we can safely expect not to change - other data, particularly prices and availability, are likely to be volatile - so we really only want to retrieve this at the time of rendering the product or category information to a page.
The mechanism Umbraco provides for this is via a property value converter, which we can implement by inheriting from PropertyValueConverterBase. There are various methods to override, described here in the Umbraco documentation.
For our converter, implemented by CommerceToolsPickerValueConverter (source code here), in ConvertSourceToIntermediate we convert the raw stored string value into an array of Guids, representing product or category identifiers.
And in the next stage, ConvertIntermediateToObject, we take that collection and convert it into a strongly typed representation of the products or categories, populated with the latest information on names, descriptions, and prices retrieved from the CommerceTools API.
We make these requests via the same CommerceToolsService, providing re-use of the integration logic for authorization and handling the API requests and responses. Also worth noting is the mapping from the API response to a simpler model we use for a product or category within the web application. There’s no need to expose all the data we get back from the API - we can take just what we need and flatten it into a structure that’s easier to work with when rendering to views.
Migrating to V9
The current release of this package is for Umbraco V8, but if you’re digging in the source code you may find the branch feature/v9-multi-target. This version allows the same code-base to be used for both Umbraco V8 and V9 (running on .NET Framework and .NET 5 respectively). You can read (and watch) more about this approach here.
We’ve currently parked this just to align with the approaches used for the other integrations repository, and due to some issues with generating Umbraco package format files that we wanted to do for V8. We'll likely return to this later and consider whether to continue the multi-targeted approach or switch to separate branches for the different versions as we are currently for other HQ products.
We’ve shared here with this package a number of techniques involved when integrating Umbraco with other applications - in particular, custom property editors, property value converters, and working with APIs. If you are using CommerceTools for a project, then hopefully this package will prove of direct use in integrating that product with Umbraco. Even if not, the methods demonstrated are applicable to many other integration scenarios, where there is a need to surface information from third-party systems with APIs into both the Umbraco back-office and the front-end website.