Module: Custom modules: custom settings

8 of 9 Pages

Access channel-specific settings directly

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.

Earlier in this series, 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:

  1. First snippet
    • Label: Test meta
    • Identifiers → Code name: TestMeta
    • Type: Metadata
    • Code:
      HTML
      
        <meta name="this is a test" content="12345">
        
  2. Second snippet
    • Label: Test CSS
    • Identifiers → Code name: TestCSS
    • Type: CSS
    • Code:
      HTML
      
        <style>
            .newclass{
                display:inline;
            }
        </style>
        
  3. 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.

C#
CodeSnippetType.cs

public enum CodeSnippetType
{
    Metadata,
    CSS,
    Javascript,
}

Finally, define a view model with properties for the code, type, and label of a code snippet.

C#
CodeSnippetViewModel.cs

using Microsoft.AspNetCore.Html;

namespace TrainingGuides.Web.Features.CodeSnippets;

public class CodeSnippetViewModel
{
    public HtmlString CodeSnippetHtml { 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.

C#
CodeSnippetsViewComponent.cs

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
        {
            CodeSnippetHtml = 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.
Make sure to include a comment label if the model provides one.
cshtml
CodeSnippetsViewComponent.cshtml

@using TrainingGuides.Web.Features.CodeSnippets
@model IEnumerable<CodeSnippetViewModel>

@foreach (var codeSnippet in Model)
{
    if (!string.IsNullOrWhiteSpace(codeSnippet.CodeSnippetLabel))
    {
        <!-- code snippet for @codeSnippet.CodeSnippetLabel -->
    }
    @codeSnippet.CodeSnippetHtml
}

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, with code-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, with code-snippet-type set to @CodeSnippetType.Javascript

Try setting add-label-comments to true on one of them, so you can compare the results.

cshtml
_Layout.cshtml

...
<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.

View component output for metadata and CSS

View component output for Javascript