AI agents: the content index is at /llms.txt. Every page is also available as Markdown — send the request header Accept: text/markdown, or append .md to the page URL.
Xperience Docs
  • Community Questions & Answers (opens in a new tab)
  • Contact support (opens in a new tab)

Cookie consent

We use necessary cookies (opens in a new tab) to run our website and improve your experience while browsing. Additional cookies are only used with your consent. You may revoke your consent on the Cookies Policy (opens in a new tab) page or in your browser at any time.

Configure (opens in a new tab)
  • Documentation
    Administration interface basicsDevelopers and adminsBusiness usersChangelogGlossary
  • Guides
    Welcome to Xperience by KenticoAdministration interface basicsCustomer journeysDigital marketingConfigurationDevelopmentArchitectureUpgrade to Xperience by Kentico
  • Training
    For AdminsFor Business usersFor DevelopersFor Solution architects
  • API Examples
    ConfigurationContent managementDevelopmentDigital marketing
  • Changelog
    Documentation updatesSecurity advisories

Email Builder

  • Email Builder overview
  • Prerequisites
  • Prepare your project for the Starter Kit
  • Configure the Starter Kit and Email Builder
  • Set up properties for an article widget
  • Work with the widget model
  • Build the widget Razor component
  • Design a template for structured data
  • Implement the structured data template
  • Prepare styling options for a flexible template
  • Build a reusable columns component
  • Complete the flexible template and its section
  • Learn more about Email Builder

Module: Email Builder

6 of 13 Pages

Work with the widget model

Define the widget model

With the article widget properties in place, we need to define the data the widget will display. Create a file called ArticleEmailWidgetModel.cs in the ~/Features/Articles/EmailWidgets folder.

The widget should not render the full text of the article directly in the email, because it is meant to drive traffic to the full version on the website. Properties for the title, teaser image, summary, and URL should be enough.

Since we are using the Email Builder Starter Kit, we can take advantage of the pre-existing ImageWidgetModel class to represent the URL and alt text of the article teaser image.

C#ArticleEmailWidgetModel.cs
using Kentico.Xperience.Mjml.StarterKit.Rcl.Widgets;

namespace TrainingGuides.Web.Features.Articles.EmailWidgets;

public class ArticleEmailWidgetModel
{
    public string ArticleTitle { get; set; } = string.Empty;
    public ImageWidgetModel ArticleTeaserImage { get; set; } = new();
    public string ArticleSummary { get; set; } = string.Empty;
    public string ArticleUrl { get; set; } = string.Empty;

}

Map content types to the widget model

Now, we need a mapper to populate the widget model based on the selected article. We'll create a class that queries the TrainingGuides.ArticlePage content type and converts the resulting pages into ArticleEmailWidgetModel objects.

This process follows the conventions of the Email Builder Starter Kit⁠ (opens in a new tab) and makes our widget more portable.

It separates the widget from the content types of any specific project, easing the process of using the widget elsewhere.

If you do not need a portable widget, you can include your data access logic directly in the code-behind of the widget's view component, populating the widget model there, or even directly using your content type classes instead of a widget model.

Implement the mapper

Add a file called ArticleEmailWidgetModelMapper.cs in the ~/Features/Articles/EmailWidgets folder. Implement the IComponentModelMapper<TModel> interface from the Starter Kit.

The Article page content type of the Training guides repository can link to multiple reusable article types— Article and IArticleSchema.

These types exist to show the process of handling content model expansion over time in the advanced content module.

We'll need to handle both potential scenarios in our mapping class.

C#ArticleEmailWidgetModelMapper.cs
using Kentico.Xperience.Mjml.StarterKit.Rcl.Mapping;
using Kentico.Xperience.Mjml.StarterKit.Rcl.Widgets;
using Microsoft.Extensions.Localization;
using TrainingGuides.Web.Features.Shared.Services;

namespace TrainingGuides.Web.Features.Articles.EmailWidgets;

public class ArticleEmailWidgetModelMapper : IComponentModelMapper<ArticleEmailWidgetModel>
{
    private readonly IContentItemRetrieverService contentItemRetrieverService;
    private readonly IStringLocalizer<SharedResources> stringLocalizer;

    private ArticleEmailWidgetModel DefaultModel => new()
    {
        ArticleTitle = stringLocalizer["No article selected"],
        ArticleTeaserImage = new ImageWidgetModel(),
        ArticleSummary = stringLocalizer["Please select an article."],
        ArticleUrl = string.Empty,
    };


    public ArticleEmailWidgetModelMapper(
        IContentItemRetrieverService contentItemRetrieverService,
        IStringLocalizer<SharedResources> stringLocalizer)
    {
        this.contentItemRetrieverService = contentItemRetrieverService;
        this.stringLocalizer = stringLocalizer;
    }

    public async Task<ArticleEmailWidgetModel> Map(Guid contentItemGuid, string languageName)
    {
        if (contentItemGuid == Guid.Empty)
        {
            return DefaultModel;
        }

        var articlePage = await contentItemRetrieverService.RetrieveWebPageByContentItemGuid<ArticlePage>(contentItemGuid, 2, true, languageName);

        string articlePageUrl = articlePage.GetUrl().AbsoluteUrl;

        var schemaArticle = articlePage?.ArticlePageArticleContent.FirstOrDefault();
        var oldArticle = articlePage?.ArticlePageContent.FirstOrDefault();

        var imageAsset = schemaArticle?.ArticleSchemaTeaser?.FirstOrDefault()
                ?? oldArticle?.ArticleTeaser?.FirstOrDefault();
        string imageUrl = imageAsset?.AssetFile?.Url ?? string.Empty;

        return (articlePage is not null && (schemaArticle is not null || oldArticle is not null))
            ? new ArticleEmailWidgetModel()
            {
                ArticleTitle = schemaArticle?.ArticleSchemaTitle
                    ?? oldArticle?.ArticleTitle
                    ?? string.Empty,
                ArticleSummary = schemaArticle?.ArticleSchemaSummary
                    ?? oldArticle?.ArticleSummary
                    ?? string.Empty,
                ArticleTeaserImage = new ImageWidgetModel
                {
                    ImageUrl = imageUrl.ToString(),
                    AltText = imageAsset?.AssetAltText ?? string.Empty,
                },
                ArticleUrl = articlePageUrl
            }
            : DefaultModel;
    }
}

Register the mapper

This new mapper won't work unless we register it with the dependency injection container. Add the mapper to the AddTrainingGuidesServices method in ServiceCollectionExtensions.cs as a scoped service.

C#ServiceCollectionExtensions.cs
...
services.AddScoped<IComponentModelMapper<ArticleEmailWidgetModel>, ArticleEmailWidgetModelMapper>();
...
← Previous page

6 of 13

Mark complete and continue →