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:
Implementing a Locale Provider
ILocaleProvider
Implementing a Variant Provider
IVariantProvider
Using the Manifests Service
IManifestsService
to retrieve contentConfiguring application routing to display the content
Prerequisites
Before we begin, you must:
Set up Visual Studio
Install the SDK
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:
The Manifests Container
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.
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:
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:
"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:
the GUID of the Consumer Application
the Shared Access Signatures
the Service Bus URL
3️⃣ Add the following code to Program.cs
:
The interface classes within can be found in the Forrit.Consumers
library.
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:
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
:
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:
<!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
:
builder.Services.AddSingleton<IRoutingService, RoutingService>();
builder.Services.AddSingleton<IStaticUrlService, StaticUrlService>();
2️⃣ And the following line:
app.UseForritRoutingMiddleware();
Implementing Your Own Routing Service
To write and use your own Routing Service:
Don’t call
UseForritRoutingMiddleware
Implement your own instance of the
IRoutingService
andIStaticUrlService
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
:
public class TimeOfDayVariantProvider : IVariantProvider
2️⃣ In TimeOfDayVariantProvider
, Implement the single required method of IVariantProvider
:
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:
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:
Create three Variants in Forrit, called "Morning", "Afternoon" and "Night"
Add a Component to a Page in Forrit One, ensuring that the Component is enabled for the three Variants completed above
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"
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.
Glossary
See the Glossary for more information.