Access custom channel-specific configurations
Custom modules series
This guide is a part of the Custom modules series and a direct continuation of the Add channel-specific configuration to a module guide.
When you build custom module UIs with Xperience by Kentico, chances are you’ll need to access and react to the data stored in the modules’ classes.
This guide will cover the process of accessing and utilizing the values of module classes that are associated with specific web channels with practical examples. It will demonstrate how to access custom channel-specific settings objects both with and without Microsoft’s options pattern.
This guide will show how to access the channel-specific settings demonstrated here:
The previous guide goes over the process of creating these settings and their UI.
Before you start
This guide requires the following:
- Familiarity with C#, .NET Core, Dependency injection, and the MVC pattern.
- A running instance of Xperience by Kentico, preferably 29.6.1 or higher.Some features covered in the Training guides may not work in older versions.
The examples in this guide require that you:
- Have followed along with the samples from the previous guide about creating the UI for channel-specific configurations.
Code samples
You can find a project with completed, working versions of code samples from this guide and others in the finished branch of the Training guides repository.
The main branch of the repository provides a starting point to code along with the guides.
The code samples in this guide are for .NET 8 only.
They come from a project that uses implicit using directives. You may need to add additional using
directives to your code if your project does not use this feature.
Work with the Options pattern (Serve robots.txt)
As with the global configurations described in the earlier guide, you can use channel-specific custom module configurations with the options pattern.
To put this into practice, let’s create functionality to serve the robots.txt file specified in the SEO settings of the previous guide.
Populate the data
Start by adding data for the code to retrieve.
For this guide’s example, navigate to Project settings → Channel settings → Training guides pages → SEO settings in the admin UI.
Save a value to the Robots.txt text box, for example:
ignore: *
Set up options
Moving to the code files, create an options class to hold all the data your front-end needs, and a corresponding options setup class to populate that data.
To retrieve the value of your setting, use an IWebsiteChannelContext
instance to determine which channel’s module data to retrieve, and an IInfoProvider<TInfo>
for each module class you need to access.
Then, register the setup class with the dependency injection container during startup, for example in an IServiceCollection
extension method.
If you’re following along with the example, add a folder called SEO within the Features directory of the TrainingGuides.Web project.
Create options and options setup classes for robots.txt.
namespace TrainingGuides.Web.Features.SEO;
public class RobotsOptions
{
public string RobotsText { get; set; } = string.Empty;
}
using CMS.DataEngine;
using CMS.Websites.Routing;
using Microsoft.Extensions.Options;
using TrainingGuides.ProjectSettings;
namespace TrainingGuides.Web.Features.SEO;
public class RobotsOptionsSetup : IConfigureOptions<RobotsOptions>
{
private readonly IInfoProvider<SeoSettingsInfo> seoSettingsInfoProvider;
private readonly IWebsiteChannelContext websiteChannelContext;
private readonly IInfoProvider<WebChannelSettingsInfo> webChannelSettingsInfoProvider;
public RobotsOptionsSetup(IInfoProvider<SeoSettingsInfo> seoSettingsInfoProvider, IWebsiteChannelContext websiteChannelContext, IInfoProvider<WebChannelSettingsInfo> webChannelSettingsInfoProvider)
{
this.seoSettingsInfoProvider = seoSettingsInfoProvider;
this.websiteChannelContext = websiteChannelContext;
this.webChannelSettingsInfoProvider = webChannelSettingsInfoProvider;
}
public void Configure(RobotsOptions options)
{
int currentChannelID = websiteChannelContext.WebsiteChannelID;
var channelSettings = webChannelSettingsInfoProvider
.Get()
.WhereEquals(nameof(WebChannelSettingsInfo.WebChannelSettingsChannelID), currentChannelID)
.FirstOrDefault();
var seoSettings = seoSettingsInfoProvider.Get()
.WhereEquals(nameof(SeoSettingsInfo.SeoSettingsWebChannelSettingID), channelSettings?.WebChannelSettingsID ?? 0)
.FirstOrDefault();
options.RobotsText = seoSettings?.SeoSettingsRobots ?? string.Empty;
}
}
Then, in the ServiceCollectionExtensions.AddTrainingGuidesOptions
method from the earlier guide, use the IServiceCollection.ConfigureOptions
method to register your options.
...
public static void AddTrainingGuidesOptions(this IServiceCollection services)
{
...
services.ConfigureOptions<RobotsOptionsSetup>();
}
...
You can find the complete file in the finished branch of the Training guides repository for reference.
Utilize the options in your project
Once you have set up and registered, you can utilize them anywhere in your site that allows dependency injection.
Choose which Options interface to use
In cases where the values of the options do not change during the life of the application, you can use IOptions<TOptions>
to retrieve the value. It is a singleton service that evaluates the options when the application stops.
In this case, you’ll most likely want to use IOptionsSnapshot<TOptions>
instead, as it is a scoped service that re-evaluates the options whenever it is constructed. This ensures that if users make changes to your module objects after the application starts, the new values will be included in the options.
For our example, let’s create a controller that returns a text response with the value of the robots.txt setting from the injected options.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace TrainingGuides.Web.Features.SEO;
public class RobotsController : Controller
{
private readonly IOptionsSnapshot<RobotsOptions> robotsOptions;
public RobotsController(IOptionsSnapshot<RobotsOptions> robotsOptions)
{
this.robotsOptions = robotsOptions;
}
[HttpGet("/robots.txt")]
public IActionResult Index() => Content(robotsOptions.Value.RobotsText, "text/plain");
}
See the result
If you’ve followed along with the example, try visiting the /robots.txt path of the live site.
You should see a response containing the value you saved in the SEO settings.
Feel free to change the value of the setting, and see that it is reflected in this response.
Access data direcly (Render snippets to the site)
We recommend using the options pattern to follow .NET best practices, but if it does not fit your team’s code style, it is not necessary.
In the previous guide, we created a module that allows users to define code snippets associated with a given channel. For this guide’s sample, let’s create a view component that retrieves the code snippets for the current channel and renders them to the layout page.
Enter the data
Once again we need to add some data, so that our code has something to work with.
In the admin UI, navigate to Project settings → Channel settings → Training guides pages → Channel snippets in the Xperience admin UI.
Add one of each type of snippet. If you don’t have specific tags you want to include, you can use:
- First snippet
- Label: Test meta
- Identifiers → Code name: TestMeta
- Type: Metadata
- Code:HTML
<meta name="this is a test" content="12345">
- Second snippet
- Label: Test CSS
- Identifiers → Code name: TestCSS
- Type: CSS
- Code:HTML
<style> .newclass{ display:inline; } </style>
- Third snippet
- Label: Test javascript
- Identifiers → Code name: TestJavascript
- Type: Javascript
- Code:
HTML<script> var toInsert = document.createElement("div"); toInsert.innerHTML = "This text was added through a javascript snippet in the <b>Project settings</b> application."; document.body.appendChild(toInsert); </script>
The CSS and JavaScript snippets do not need to be inline code, as in this example. They can link to other resources.
Add supporting files
Our example view component needs some additional foundational code:
First, create a CodeSnippets folder beneath the Features folder of the TrainingGuides.Web project.
Then add a CodeSnippetType
enumeration that corresponds to the WebChannelSnippetType property that we defined in the class.
public enum CodeSnippetType
{
Metadata,
CSS,
Javascript,
}
Finally, define a view model with properties for the code, type, and label of a code snippet.
using Microsoft.AspNetCore.Html;
namespace TrainingGuides.Web.Features.CodeSnippets;
public class CodeSnippetViewModel
{
public HtmlString CodeSnippet { get; set; } = new HtmlString(string.Empty);
public string CodeSnippetType { get; set; } = string.Empty;
public string CodeSnippetLabel { get; set; } = string.Empty;
}
Retrieve the values
In general, retrieving channel-specific module class values without the options pattern is the same approach you used in the options setup file. You can use an IWebsiteChannelContext
object to get the current channel, and an IInfoProvider<TInfo>
object or dedicated provider to access objects of your module class.
For our example, add an asynchronous view component to the CodeSnippets folder. Add parameters to InvokeAsync
that specify which type of snippet should be retrieved, and whether the snippet should be labelled with an HTML comment.
using CMS.DataEngine;
using CMS.Websites.Routing;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc;
using TrainingGuides.ProjectSettings;
namespace TrainingGuides.Web.Features.CodeSnippets;
public class CodeSnippetsViewComponent : ViewComponent
{
private readonly IInfoProvider<WebChannelSnippetInfo> webChannelSnippetInfoProvider;
private readonly IInfoProvider<WebChannelSettingsInfo> webChannelSettingsInfoProvider;
private readonly IWebsiteChannelContext websiteChannelContext;
public CodeSnippetsViewComponent(IInfoProvider<WebChannelSnippetInfo> webChannelSnippetInfoProvider, IInfoProvider<WebChannelSettingsInfo> webChannelSettingsInfoProvider, IWebsiteChannelContext websiteChannelContext)
{
this.webChannelSnippetInfoProvider = webChannelSnippetInfoProvider;
this.webChannelSettingsInfoProvider = webChannelSettingsInfoProvider;
this.websiteChannelContext = websiteChannelContext;
}
public async Task<IViewComponentResult> InvokeAsync(CodeSnippetType codeSnippetType, bool addLabelComments = false)
{
// Retrieve the current channel
int currentChannelID = websiteChannelContext.WebsiteChannelID;
// Use the channel to get the associated web channel settings
var settings = webChannelSettingsInfoProvider
.Get()
.WhereEquals(nameof(WebChannelSettingsInfo.WebChannelSettingsChannelID), currentChannelID)
.AsSingleColumn(nameof(WebChannelSettingsInfo.WebChannelSettingsID));
// Aquire snippets associated with the web channel settings
var snippets = await webChannelSnippetInfoProvider
.Get()
.WhereIn(nameof(WebChannelSnippetInfo.WebChannelSnippetWebChannelSettingsID), settings)
.WhereEquals(nameof(WebChannelSnippetInfo.WebChannelSnippetType), codeSnippetType.ToString())
.GetEnumerableTypedResultAsync();
// Return an IEnumerable collection of CodeSnippetViewModel objects
var model = snippets.Select(snippet => new CodeSnippetViewModel
{
CodeSnippet = new HtmlString(snippet.WebChannelSnippetCode),
CodeSnippetType = snippet.WebChannelSnippetType,
// If the view component is configured to add comment labels, use the display name
CodeSnippetLabel = addLabelComments
? snippet.WebChannelSnippetDisplayName
: string.Empty
});
return View("~/Features/CodeSnippets/CodeSnippetsViewComponent.cshtml", model);
}
}
Utilize the data
Now that you have your custom module data, you can put it into practice. For example, use it in your business logic, or to control how your application renders parts of the display.
In the case of our example, it means creating the view for our view component, and using it to render code snippets to the page.
Add a new view, matching the path and name specified in the view component. For each code snippet in the provided model, render it to the page.
@using TrainingGuides.Web.Features.CodeSnippets
@model IEnumerable<CodeSnippetViewModel>
@foreach (var codeSnippet in Model)
{
if (!string.IsNullOrWhiteSpace(codeSnippet.CodeSnippetLabel))
{
<!-- code snippet for @codeSnippet.CodeSnippetLabel -->
}
@codeSnippet.CodeSnippet
}
Finally, utilize this view component in the _Layout.cshtml view, found in the ~/Views/Shared folder of the TrainingGuides.Web project.
Place three instances of the view component into the view:
- one after the
meta
tags, withcode-snippet-type
set to@CodeSnippetType.Metadata
- one after the stylesheet links in the header, with
code-snippet-type
set to@CodeSnippetType.CSS
- one after the
script
tags at the bottom of the body, withcode-snippet-type
set to@CodeSnippetType.Javascript
Try setting add-label-comments
to true
on one of them, so you can compare the results.
...
<html>
<head>
...
<vc:code-snippets code-snippet-type="@CodeSnippetType.Metadata" add-label-comments="true"/>
...
<vc:code-snippets code-snippet-type="@CodeSnippetType.CSS" />
</head>
<body>
...
<vc:code-snippets code-snippet-type="@CodeSnippetType.Javascript" />
</body>
</html>
For reference, you can find the completed layout file in the finished branch of the Training guides repo.
Check the result
If you’ve followed the example, you should be able to find the tags in the source view of the page, and see the message rendered by the javascript at the very bottom.
What’s next?
Throughout the Custom module series, you’ve seen how to create custom module, including data associated with specific channels, and how to put the module data into practice.
Use this experience to expand upon the samples, or to build your your own complex module.
Check out this guide to see how to add custom pages to existing system applications.