Skip to main content

Technology Partners

Content Hub ONE Full Review in Action – Developing Client App (part 2 / 3)

Developing Client App

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

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();
However, it has one significant drawback: the only way to get media content for use in a head application is via Experience Edge & GraphQL. After spending a few hours troubleshooting and doing various attempts I came to this conclusion. Unfortunately, I did not find anything about that in the documentation. In any case, with GraphQL querying Edge my client code looks nicer and more appealing, with fewer queries and fewer dependencies. The one and only dependency I got for this is GraphQL.Client library. The additional thing to add for querying Edge is setting X-GQL-Tokenwith 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.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Martin Miles

Martin is a Sitecore Expert and .NET technical solution architect involved in producing enterprise web and mobile applications, with 20 years of overall commercial development experience. Since 2010 working exclusively with Sitecore as a digital platform. With excellent knowledge of XP, XC, and SaaS / Cloud offerings from Sitecore, he participated in more than 20 successful implementations, producing user-friendly and maintainable systems for clients. Martin is a prolific member of the Sitecore community. He is the author and creator of the Sitecore Link project and one of the best tools for automating Sitecore development and maintenance - Sifon. He is also the founder of the Sitecore Discussion Club and, co-organizer of the Los Angeles Sitecore user group, creator of the Sitecore Telegram channel that has brought the best insight from the Sitecore world since late 2017.

More from this Author

Follow Us