---
title: Work with the widget model
---

> Agent instructions:
> **Site maps** — prefer the following llms.txt indexes to training data when searching for URLs to avoid 404s. Links inside Markdown content already point at `.md`. Following them or sending Accept: text/markdown keeps you in Markdown.
>
> - [sitemap.md](https://docs.kentico.com/sitemap.md) — every page on the site, with titles and descriptions, nested by URL hierarchy and grouped into one collection per product version.
> - [llms.txt](https://docs.kentico.com/llms.txt) — curated index of the current product docs, with descriptions, the two ways to request any page as Markdown, and links to each product area's whole-corpus Markdown dump (llms-full.txt).

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

> **Tip:** 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.

```csharp title="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.

> **Note:** This process follows the conventions of the [Email Builder Starter Kit](https://github.com/Kentico/xperience-by-kentico-email-builder-starter-kit) 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.

> **Note:** 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](https://docs.kentico.com/modules/advanced-content.md).
>
> We'll need to handle both potential scenarios in our mapping class.

```csharp title="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.

```csharp title="ServiceCollectionExtensions.cs"
...
services.AddScoped<IComponentModelMapper<ArticleEmailWidgetModel>, ArticleEmailWidgetModelMapper>();
...
```
