Skip to main content
Skip table of contents

3.3 SDK: Developer

Introduction

The SDK is how you retrieve your Forrit One content.

It is a private Nuget package called Forrit.Consumers, and any application using this package to retrieve content is said to be a Consumer Application.

Consumer Applications must be written in .NET to use the SDK.

Quickstart

In this quickstart you’ll learn how to use the SDK by:

  1. Implementing a Locale Provider ILocaleProvider

  2. Implementing a Variant Provider IVariantProvider

  3. Using the Manifests Service IManifestsService to retrieve content

  4. Configuring application routing to display the content

Prerequisites

Before we begin, you must:

  1. Set up Visual Studio

  2. Install the SDK

  3. Obtain information from Azure portal

Set up Visual Studio

Install Visual Studio 2022 – ensure all tools required for developing .Net Core 6 are installed.

From within Visual Studio, create a new Solution using the ASP .NET Core Web App template.

Name the solution mvc-sample.

If you use a different name, ensure you also edit the namespaces in the sample code later.

Select the .Net Core 6 framework.

Install the SDK

Install version 3.2.1 of the Forrit.Consumers and Forrit.Consumers.Types Nuget packages.

SDK version 3.2.1 corresponds to CMS version 3.3.

You require access to the private Forrit Nuget repository to install this package.

Obtain Information from Azure Portal

In Azure Blob Storage, obtain Shared Access Signatures for:

  1. The Manifests Container

  2. The Releases table

Note the URL of the Service Bus for Forrit CMS.

This is used to automatically update the Release when a new one is published to the Consumer App.

You will also require the GUID of the Consumer Application being published to.

1. Set up a Locale Provider

Locale Providers conform to the ILocaleProvider interface.

A Locale Provider implements the logic that determines which Locale is returned for each Page request.

In the following steps we create a simple ‘mock’ Locale Provider that always returns the same Locale shortcode value, e.g. en-GB – note that this must exist as a Locale in the published Release.

A Locale Provider must be set up before using the Manifests Service.

Steps

1️⃣ Add a new class to the project, called MockLocaleProvider.

2️⃣ Paste the code below into the new class, replacing the entire class definition:

Remember to change the namespace if you chose a different Solution name earlier.

C#
using Forrit.Consumers.Providers;
using System.Diagnostics;

namespace mvc_sample.Providers
{
    public class MockLocaleProvider : ILocaleProvider
    {
        public ValueTask<string> GetLocaleAsync(CancellationToken cancellationToken)
        {
            string locale = "{default-locale}";
            Trace.WriteLine(string.Format("MockLocaleProvider: GetLocaleAsync: Returning locale '{0}'", locale));
            return new ValueTask<string>(locale);
        }
    }
}

3️⃣ Replace the placeholder "{default-locale}" on line 10 with a Locale shortcode that exists in the Release being published, e.g. “en-GB".

2. Set up a Variant Provider

Variant Providers conform to the IVariantProvider interface.

A Variant Provider implements the logic that determines which Variant is returned for each Page request.

You don’t have to return a specific Variant that exists in your Release for this example because you can use the None Variant that always exists by default by returning a null GUID.

A Variant Provider must be set up before using the Manifests Service.

Steps

1️⃣ Similar to the Locale Provider example above, create a MockVariantProvider class that returns a null GUID, which corresponds to the default None Variant:

C#
using Forrit.Consumers.Providers;
using System.Diagnostics;

namespace mvc_sample.Providers
{
    public class MockVariantProvider : IVariantProvider
	{
        public ValueTask<Guid?> GetVariantAsync(CancellationToken cancellationToken)
        {
            Guid? variant = null;
            Trace.WriteLine(string.Format("MockVariantProvider: GetVariantAsync: Returning variant '{0}'", variant));
            return new ValueTask<Guid?>(variant);
        }
    }
}

3. Connect to the Manifests Service

The Manifests Service is reponsible for retrieving your content.

Here we configure a Manifest Service and ensure that it connects successfully to your Manifests Container in Azure Blob Storage.

Steps

1️⃣ In the application appSettings.json file, add the following settings:

JSON
  "ConsumerKey": "{consumer-app-uid}",
  "ConnectionStrings": {
    "ManifestsContainer": "{SAS-to-access-manifests}",
    "Releases": "{SAS-to-access-releases}",
    "ServiceBus": "{Endpoint-for-service-bus}"
  }

2️⃣ Replace the placeholders with the information you saved earlier:

  1. the GUID of the Consumer Application

  2. the Shared Access Signatures

  3. the Service Bus URL

3️⃣ Add the following code to Program.cs:

The interface classes within can be found in the Forrit.Consumers library.

C#
builder.Services.AddControllersWithViews();
builder.Services.AddHostedService(provider => provider.GetRequiredService<IManifestsRefreshService>());
builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton<IManifestsRefreshService>(provider => new ManifestsRefreshSubscribingService(
    builder.Configuration.GetConnectionString("ServiceBus"),
    "manifests",
    Guid.NewGuid().ToString(),
    builder.Configuration.GetValue<Guid>("ConsumerKey"),
    provider.GetRequiredService<ILoggerFactory>().CreateLogger<ManifestsRefreshSubscribingService>()));
builder.Services.AddSingleton<IManifestsRepository>(provider => new ManifestsRepository(
    builder.Configuration.GetValue<Guid>("ConsumerKey"),
    builder.Configuration.GetConnectionString("ManifestsContainer"),
    builder.Configuration.GetConnectionString("Releases"),
    provider.GetRequiredService<ILoggerFactory>().CreateLogger<ManifestsRepository>()));
builder.Services.AddSingleton<IManifestsService, ManifestsService>();
builder.Services.AddSingleton<ILocaleProvider, MockLocaleProvider>();
builder.Services.AddSingleton<IVariantProvider, MockVariantProvider>();

4️⃣ Create a folder called Controllers and add a new class called HomeController with this code:

C#
using Forrit.Consumers.Services;
using Microsoft.AspNetCore.Mvc;
using System.Threading;
using System.Threading.Tasks;

namespace mvc_sample.Controllers
{
    public class HomeController : Controller
    {
        private readonly IManifestsService _manifestsService;

        public HomeController(IManifestsService manifestsService)
        {
            _manifestsService = manifestsService;
        }

        public async Task<IActionResult> ViewAsync([FromRoute] string url, CancellationToken cancellationToken)
        {
            ViewData["Body"] = await _manifestsService.GetPageBodyHtmlAsync(url, cancellationToken: cancellationToken);
            ViewData["Head"] = await _manifestsService.GetPageHeadHtmlAsync(url, cancellationToken: cancellationToken);

            return View();
        }
    }
}

4. Configure Routing

In this step we replace the default routing of our ASP.NET application with routing that will display the retrieved Pages at the route that was defined for them in the CMS UI.

For example, a Page with CMS-defined route ‘product’ will have its content displayed at the route /product in our application.

1️⃣ Add the following code to Program.cs to use the HomeController:

C#
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute("default", "{*url}", new { controller = "Home", action = "View" });
        });

This routing passes all requests to the View action in the Home controller, and passes any path parameters in a property named url. The HomeController contains a function to handle the requests that come from our new routing – see 3.3 SDK: Developer | How-it-Works for more information.

2️⃣ Add a new Razor page called View.cshtml to the Pages/Shared folder and put this code in it:

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    @Html.Raw(@ViewData["Head"])
</head>
<body>
    @Html.Raw(@ViewData["Body"])
</body>
</html>

The Consumer Application can choose other methods for displaying Page content. However, in this example, we use ASP.NET Core MVC behaviour with Razor.

Quickstart Outcome

The SDK is now set up to retrieve Forrit One content.

As long as you have published a Page that contains some content in the default Locale returned by the MockLocaleProvider and the None variant, then that content should be visible when you route to it. You should be able to route to any Page that has been published.

This implementation is the simplest possible configuration but is adequate for the purpose of this tutorial.

Additional Steps (Optional)

Configure the Routing Service

The SDK includes a Routing Service IRoutingService that can handle the HTTP redirects and rewrites defined in the CMS Routing Manager. This includes the static URLs of Media Items, which are implemented as HTTP rewrites.

To configure the SDK’s Routing Service:

1️⃣ Add these lines to Program.cs:

C#
builder.Services.AddSingleton<IRoutingService, RoutingService>();
builder.Services.AddSingleton<IStaticUrlService, StaticUrlService>();

2️⃣ And the following line:

C#
app.UseForritRoutingMiddleware();
Implementing Your Own Routing Service

To write and use your own Routing Service:

  1. Don’t call UseForritRoutingMiddleware

  2. Implement your own instance of the IRoutingService and IStaticUrlService APIs to fetch the routes defined in the CMS Routing Manager – the Manifest Service provides APIs for fetching this routing information

Configure Caching

The SDK automatically caches all Pages and Data Feed Items it fetches. It uses the IDistributedCache interface, and it relies on the Consumer Application to specify the type of cache that should be used.

If the Consumer Application does not specify the cache type then no caching is used.

For development purposes and in production with a single server, a distributed memory cache will work. However, to provide the optimal caching behaviour a Redis cache should be used. This ensures the caching will work across the multiple servers that Azure could be using to run the Consumer Application.

The cache object is also available for the Consumer Application to store any items it needs.

Add a Variant Provider With Multiple Variants

The quickstart tutorial shows the minimum required to use the SDK using only the default Variant None.

This example, however, shows how to add a more realistic Variant Provider implementation that may return one of three different Variants: Morning, Afternoon, and Night, depending on the time of day.

Locale Providers can also be written in a similar manner.

Steps

1️⃣ In Visual Studio, create a new class called TimeOfDayVariantProvider and ensure that it implements IVariantProvider:

C#
public class TimeOfDayVariantProvider : IVariantProvider

2️⃣ In TimeOfDayVariantProvider, Implement the single required method of IVariantProvider:

C#
public ValueTask<Guid?> GetVariantAsync(CancellationToken cancellationToken = default)
{
    string variant;
    DateTime systemDateTime = DateTime.Now;

    if (systemDateTime.Hour >= 0 && systemDateTime.Hour < 12)
    {
        variant = "{UID-for-morning-variant}";
    }
    else if (systemDateTime.Hour >= 12 && systemDateTime.Hour < 18)
    {
        variant = "{UID-for-afternoon-variant}";
    }
    else
    {
        variant = "{UID-for-night-variant}";
    }

    return new ValueTask<Guid?>(new Guid(variant));
}

3️⃣ Add this line to Program.cs, and remove any existing call that sets up the IVariantProvider:

C#
builder.Services.AddSingleton<IVariantProvider, TimeOfDayVariantProvider>();

Outcome

You’ve learned how to create a realistic Variant Provider that returns different Variants depending on the time of day. Now, requesting Pages at different times of day will result in different Page Variants being returned.

These Page Variants may include different Content Items and Components for the Page, assuming they exist in the Release and have been configured for the Pages being requested.

Try it Yourself

To see this in action yourself, you could, for example:

  1. Create three Variants in Forrit, called "Morning", "Afternoon" and "Night"

  2. Add a Component to a Page in Forrit One, ensuring that the Component is enabled for the three Variants completed above

  3. Edit the Page’s Content Items for each Variant so that it displays different content for all Variants, for example "Good Morning", "Good Afternoon" and "Good Evening"

  4. Run the application and check that the correct Variant is selected and that the correct content is retrieved

Troubleshooting

If you’ve added new Components and Pages in this tutorial but the SDK requests are not returning that content, then:

  • Make sure you have created a new Release containing the updated Pages, and that the Release has been published to the correct Consumer Application

  • Check that the UIDs you used for your Variants in the Forrit Marketing Portal are the same as the UIDs your Variant Provider returns

Further Reading

How it Works

The Manifest Service maintains an up-to date local cache of all of your published Pages.

The View Action method is called each time a new route is matched. It expects the url parameter to contain a valid Page route from Forrit One. If no URL parameter is passed, a hardcoded default route is used.

If the Routing Service has been configured it intercepts requests before they reach the View Action method and checks for any redirects or rewrites that have been defined in the CMS, including the static URLs of Media Items which are implemented as rewrites.

Once a Page has been found, the GetPage.. functions are called on the Manifest Service, which return strings containing the Head and Body sections of the requested Page. If a URL parameter is passed that does not match a published Page route then the GetPage.. calls fail.

Finally, the Page Head and Body sections are passed into the Razor view as a ViewData object, and are rendered by the Razor View using the @Html.Raw() helper function.

Writing production Consumer Applications

This above quickstart tutorial is a simple example that implements a minimal, non-production Consumer Application. There are a few considerations worth emphasising when designing a production Consumer Application, detailed below.

Performance

The call to your Locale Provider and Variant Provider – MockLocaleProvider  and MockVariantProvider in the quickstart example – is called on every Page request. You should ensure an optimal implementation of your Locale/Variant selection logic.

If you call an external REST service in order to determine the Locale and Variant ensure you optimise the calls for performance – perhaps by caching the results in order to reduce load on the server for future requests. Otherwise, even though this is an asynchronous call, the overall performance and scalability of the server could be impacted.

Secrets

In the quickstart example a Secret (the Blob Storage Connection String) is stored within appSettings.json.

However, for production, it is recommended to use App Settings on the Azure App Service for configuration values. These can be easily retrieved at runtime, and can be managed from the Azure portal.

For true production Secrets (keys, certificates), use Azure KeyVault.

3.3 Excerpts: Glossary Section

Glossary

See the Glossary for more information.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.