In the previous post, I crafted two content types and created records for home pages and each specific whisky item from my collection, populating them with actual data. Now let’s create a client “head” app to consume and display that content from Content Hub ONE tenant.
Content
- Part 1: Mastering Content
- Part 2: Developing Client App
- Part 3: Feedback and Afterthoughts
There is the documentation for the developers, a good start at least.
CLI
Content Hub One comes with helpful CLI and useful documentation. It has support for docker installation, but when speaking about local installation I personally enjoy support for installing using my favorite chocolatey package management tool:
choco install Sitecore.ContentHubOne.Cli --source https://nuget.sitecore.com/resources/v2
With CLI you execute commands against the tenants with only one active at the moment. Adding a tenant is easy, but in order to do you must provide the following four parameters:
organization-id
tenant-id
client-id
client-secret
Using CLI you can do serialization the same as with XP/XM platforms and see the difference and that is a pretty important feature here. I pulled all my content into a folder using ch-one-cli serialization pull content-item -c pdp
command where pdp
is my type for whisky items:
The serialized item looks as below:
id: kghzWaTk20i2ZZO3USdEaQ name: Glenkinchie fields: vendor: value: 'Glenkinchie ' type: ShortText brand: value: type: ShortText years: value: 12 type: Integer description: value: > The flagship expression from the Glenkinchie distillery, one of the stalwarts of the Lowlands. A fantastic introduction to the region, Glenkinchie 12 Year Old shows off the characteristic lightness and grassy elements that Lowland whiskies are known for, with nods to cooked fruit and Sauternes wine along the way. A brilliant single malt to enjoy as an aperitif on a warm evening. type: LongText picture: value: - >- { "type": "Link", "relatedType": "Media", "id": "lMMd0sL2mE6MkWxFPWiJqg", "uri": "http://content-api-weu.sitecorecloud.io/api/content/v1/media/lMMd0sL2mE6MkWxFPWiJqg" } type: Media video: value: - >- { "type": "Link", "relatedType": "Media", "id": "Vo5NteSyGUml53YH67qMTA", "uri": "http://content-api-weu.sitecorecloud.io/api/content/v1/media/Vo5NteSyGUml53YH67qMTA" } type: Media
After modifying it locally and saving the changes, it is possible to validate and promote these changes back to Content Hub One CMS. With that in mind, you can automate all the things for your CI/CD pipelines using PowerShell, for example. I would also recommend watching this walkthrough video to familiarize yourself with Content Hub ONE CLI in action.
SDK
There is a client SDK available with the support of two languages: JavaScript and C#. For the sake of simplicity and speed, I decided to use C# SDK for my ASP.NET head application. At a first glance, SDK looked decent and promising:
And quite easy to deal with:
var content = await _client.ContentItems.GetAsync(); var collection = content.Data .FirstOrDefault(i => i.System.ContentType.Id == "collection"); var whiskies = content.Data .Where(i => i.System.ContentType.Id == "pdp") .ToList();
X-GQL-Token
with a value, you obtain from the settings menu.The advantage of GraphQL is that you can query against the endpoints specifying quite complex structures of what you want to get back as a single response and receive only that without any unwanted overhead. I ended up having two queries:
For the whole collection:
{ collection(id: ""zTa0ARbEZ06uIGNABSCIvw"") { intro rich archive { results { fileUrl name } } items{ results{ ... on Pdp { id vendor brand years description picture { results { fileUrl name } } } } } } }
And for specific whisky record items requested from a whisky PDP page:
{ pdp(id: $id) { id vendor brand years description picture { results { fileUrl name } } video { results { fileUrl name } } } }
The last query results get easily retrieved in the code as:
var response = await Client.SendQueryAsync<Data>(request); var whiskyItem = response.Data.pdp;
Rich Text Challenges
When dealing with rich text
fields, you have to come up with building your own logic (my inline oversimplified example, lines 9-50) for rendering HTML output from a JSON structure you got for that field. The good news is that .NET gets it nicely deserialized so that you can at least iterate through this markup:
Sitecore provided an extremely helpful GraphQL IDE tool for us to test and craft queries, so below is how the same Rich text filed value looks in a JSON format:
You may end up wrapping all clumsy business logic for rendering rich text
fields into a single HTML helper producing HTML output for the entire rich text
field, which may accept several customization parameters. I did not do that as it is labor-heavy, but for the sake of example, produced such a helper for long text
field type:
public static class TextHelper { public static IHtmlContent ToParagraphs(this IHtmlHelper htmlHelper, string text) { var modifiedText = text.Replace("\n", "<br>"); var p = new TagBuilder("p"); p.InnerHtml.AppendHtml(modifiedText); return p; } }
which can be called from the view: @Html.ToParagraphs(Model.Description)
.
Supporting ZIP downloads
On the home page, there is a download link sitting within rich text content. This link references a controller action that returns a zip archive with the correct mime types.
public async Task<IActionResult> Download() { // that method id overkill, ideally var collection = await _graphQl.GetCollection(); if (collection.Archive.Results.Any()) { var url = collection.Archive.Results[0].FileUrl; var name = collection.Archive.Results[0].Name; name = Path.GetFileNameWithoutExtension(name); // gets actual bytes from ZIP binary stored as CH1 media var binaryData = await Download(url); if (binaryData != null) { // Set the correct MIME type for a zip file Response.Headers.Add("Content-Disposition", $"attachment; filename={name}"); Response.ContentType = "application/zip"; // Return the binary data as a FileContentResult return File(binaryData, "application/zip"); } } return StatusCode(404); }
Supporting video
For the sake of a demo, I simply embedded a video player to a page and referenced the URL of published media from CDN:
<video width="100%" style="margin-top: 20px;" controls> <source src="@Model.Video.Results[0].FileUrl" type="video/mp4"> Your browser does not support the video tag. </video>
Bringing it all together
I built and deployed the demo at https://whisky.martinmiles.net. You can also find the source code of the resulting .NET 7 head application project at this GitHub link.
Now, run it in a browser. All the content seen on a page is editable from Content Hub ONE, as modeled and submitted earlier. Here’s what it looks like:
That concludes the second part of this series. The final part will share some of my thoughts and feedback with the team.