Kentico Xperience: MVC Widget Experiments Part 1 - Widgets And Related Pages
Tags: kentico, aspnet, mvc, cms
Published: 2020-11-02 18:14:02

---
title: Kentico Xperience: MVC Widget Experiments Part 1 - Widgets And Related Pages
published: true
description: Learn how to easily reuse content from around your site with MVC Widets and Related Pages
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/rllqb1rnz0npvpa8yij2.jpg
tags: kentico, aspnet, mvc, cms
series: Kentico Xperience - MVC Widget Experiments
---

## Widget Experiments

_This series dives into Kentico Xperience MVC Widgets and the related technologies that are part of Kentico's Page Builder technology - Widget Sections, Widgets, Form Components, Inline Editors, Dialogs, and Page Templates 🧐._

_Join me 👋, as we explore the nooks and crannies of Kentico Xperience MVC Widgets and discover what might be possible with this powerful technology..._

> _If you aren't yet familiar with Xperience MVC Widgets, check out the Youtube video on [Building a Page with MVC Widgets in Xperience](https://www.youtube.com/watch?v=ljQO9on5lLM)._

> The previous version of this blog series is [Kentico EMS - MVC Widget Experiments](https://dev.to/seangwright/series/5038)

## Goals

This post is going to show how using Related Pages and MVC Widgets is a safe and easy way to help content editors reuse content on your site 😉.

## Use Case - Featured Articles

Our site has many articles written by our content editors. They want to be able to feature some of those articles in various places around the site.

Notably, they want to show a list of articles in various parts of the layout of some of our pages, but they want to be able to easily choose which articles (and how many) are displayed on each page.

The content editors want to be able to use the MVC Page Builder to add a Widget into the page which will show Articles that are supposed to be displayed for that specific page, with no specific limit on the number of Articles displayed 👍🏾.

## Problem - Multi-Select Widgets Are Difficult

Widgets in Xperience always have access to the page they were created on through the `ComponentViewModel.Page` property (this applies to [Basic View-only Widgets](https://docs.xperience.io/developing-websites/developing-xperience-applications-using-asp-net-core/page-builder-development-in-asp-net-core/developing-page-builder-widgets-in-asp-net-core#DevelopingpagebuilderwidgetsinASP.NETCore-Basicwidgets) or [View Component Widgets](https://docs.xperience.io/developing-websites/developing-xperience-applications-using-asp-net-core/page-builder-development-in-asp-net-core/developing-page-builder-widgets-in-asp-net-core#DevelopingpagebuilderwidgetsinASP.NETCore-Widgetsbasedonaviewcomponent).

But, what about when we want to access _other_ pages 🤔?

The [Page Selector](https://docs.xperience.io/developing-websites/page-builder-development/selectors-for-page-builder-components#Selectorsforpagebuildercomponents-Pageselector) form component allows for a content editor to configure a Widget property to hold the `NodeGuid` of another Page in the content tree - however this is currently restricted to selecting _a single Page_ 🤨.

There's also the [Path Selector](https://docs.xperience.io/developing-websites/page-builder-development/selectors-for-page-builder-components#Selectorsforpagebuildercomponents-Pathselector). It works just like the Page Selector, returning the selected Page's `NodeAliasPath`. It is also limited to 1 item.

Given the restriction both these selectors share, we could add multiple selectors to our Widget Properties class, 1 for each Page we wanted the Widget to have access to - but this obviously won't scale and requires a code change to update 😟.

There are more advanced options as well, like [creating a modal dialog](https://docs.xperience.io/developing-websites/page-builder-development/developing-modal-dialogs-for-builder-component-properties) where we could build out a complex custom UI for selecting multiple Pages, but it seems like there should be an easier solution 😒!

## Solution - Use Page Relationships

When we want a Widget (or a Page View) to display information from a select set of Pages, we are effectively creating a relationship between the current Page using the Widget and the Pages we want to be the source of content.

While the Page and Path Selector Form Components enable defining relationships like these, the process of defining relationships between content might better be thought of as part of the content management process 🤔.

Xperience already has many tools for performing content management outside of the Page Builder 😮.

> ⚠ Any time you find yourself doing content creation or content management in a Widget, pause and ask yourself if there is already a way to do this outside the Page Builder - it can often be a better choice.

Instead of doing our content management in the Widget, we can use the [Page Relationships](https://docs.xperience.io/managing-website-content/working-with-pages/working-with-page-relationships) feature of Xperience.

When defining content or content relationships in a Widget, that work is isolated in the Widget. By defining Page Relationships on the Page itself, those relationships can be used by multiple Widgets or by any other part of Xperience. They are reusable 💪🏽!

> 💠 I'll be using the Kentico Xperience 13.0 Dancing Goat demo site running on ASP.NET Core for the example below. You can install a trial by downloading the [Kentico Xperience Installation Manager](https://xperience.io/get-started/trial).

### Create a Relationship

To start down the path of using Related Pages with Widgets, we first need to create a Relationship by going to the Relationship Names module in Xperience.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/h7meji82j542s3mx46yl.jpg)

I like to name my relationships with names that read like sentences. This helps me later, when I'm relating pages to each other. If the Relationship 'reads' correctly then I've set it up correctly 👍🏼.

Here, we name the Relationship `HasArticle` because a given Page will 'have an article' (or more than 1) it is related to.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0he13s2tpyafrcku359e.jpg)

### Add Related Pages

Now, we navigate to the Pages module and select the Page in the Content Tree that we want to use our Relationship with.

In this case we will select the Home Page and then select the "Related Pages" option from the menu.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/dqsiuapfe3rw3mzaws1g.jpg)

Next, we create a couple Relationships between the Home Page and Article Pages.

Make sure that the Home Page is on the left side of the Relationship and each the Article Page is on the right. Again, the relationship should 'read' like a sentence - "Home has an Article On Roasts".

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/060e5sf1qso33vg14gym.jpg)

### Query for Our Content

Now that we've performed all of our content management duties, we can get to coding 🥳! Fortunately, the Dancing Goat site already has most of the pieces that we need 😎.

First, we'll add a method to the `ArticlesRepository` found under `~/Models/Articles/`.

```csharp
public IEnumerable<Article> GetRelated(int count = 0)
{
    var page = pageDataContextRetriever.Retrieve<TreeNode>().Page;

    string key = $"{nameof(ArticleRepository)}|{nameof(GetRelated)}|{page.NodeGUID}|{count}";

    string relDependency = $"nodeid|{page.NodeID}|relationships";    

    return pageRetriever.Retrieve<Article>(
        query => query
            .InRelationWith(
                page.NodeGUID, 
                "HasArticle", 
                RelationshipSideEnum.Right)
            .TopN(count)
            .OrderByDescending("DocumentPublishFrom"),
        cache => cache
            .Key(key)
            .Dependencies((_, builder) =>
                builder
                    .Pages(new[] { page })
                    .Custom(relDependency)));
}
```

`GetRelated()` looks very similar to the already existing `GetArticles()` method, with the main difference being that it uses the `pageDataContextRetriever` to get the current Page and then queries for `Article` pages related to that Page.

Notice the `.InRelationWith()` method we use to query for related pages 🧐.

```csharp
public virtual TQuery InRelationWith(
    Guid nodeGuid, 
    string relationshipName = null, 
    RelationshipSideEnum side = RelationshipSideEnum.Both);
```

One thing that often trips me up is not knowing which option of the `RelationshipSideEnum` to use 😵.

So, just to clarify, the `RelationshipSideEnum` value should be the side we expect the Page to be on that is supplying the `nodeGuid` parameter, the results being returned by the query will be on the other side.

Our query can be read as "Find me all the Articles where the Page identified by the `nodeGuid` is on the Left side of the `HasArticle` relationship" 🤓.

Notice also that we define a [custom cache dependency](https://docs.xperience.io/configuring-xperience/configuring-caching/setting-cache-dependencies) on all the relationships for the current page.

```csharp
string relDependency = $"nodeid|{page.NodeID}|relationships"; 
```

Page Relationships are stored in a database table separate from the Page's primary content 😮, so we need to be explicit that this query's cache should be cleared if any of the Page's relationships change.

### Reuse the Widget

Now that we've defined a new query for getting our content, let's add on to the existing "Latest articles" Widget, which can be found under `~/Components/Widgets/Articles/`.

First, we will add a new `UseRelatedPages` property to the `ArticlesWidgetProperties` class. It will be a property that is editable in the Widget's property dialog, so we'll use the `EditingComponent` attribute to define the form element for editing this property.

> Note: Adding `ExplanationText` is always a good idea. Although the purpose of a field might be clear to us developers (because we're building these components), it might not be clear at all to a content editor. Let's help them out with a nice description 🤗!

```csharp
public class ArticlesWidgetProperties : IWidgetProperties
{
    public int Count { get; set; } = 5;

    [EditingComponent(
        CheckBoxComponent.IDENTIFIER,
        Order = 1,
        Label = "Use Related Pages?",
        ExplanationText = "Changes the Widget to display Articles related to the Current Page")]
    public bool UseRelatedPages { get; set; } = false;
}
```

We want to pass the new `UseRelatedPages` property to our View, so we'll also add it to the `ArticlesWidgetViewModel`.

```csharp
public class ArticlesWidgetViewModel
{
    public IEnumerable<ArticleViewModel> Articles { get; set; }

    public int Count { get; set; }

    public bool UseRelatedPages { get; set; }
}
```

Finally, we can pull everything together in the `ArticlesWidgetViewComponent`'s `Invoke()` method.

We will get our Article Pages from the `GetRelated()` method if `UseRelatedPages` is `true`, otherwise we will get them from `GetArticles()`.

```csharp
public ViewViewComponentResult Invoke(
    ComponentViewModel<ArticlesWidgetProperties> viewModel)
{
    if (viewModel is null)
    {
        throw new ArgumentNullException(nameof(viewModel));
    }

    var articles = viewModel.Properties.UseRelatedPages
        ? repository.GetRelated(
              viewModel.Properties.Count)
        : repository.GetArticles(
              ContentItemIdentifiers.ARTICLES,
              viewModel.Properties.Count);

    // ...

    return View(
        "~/Components/Widgets/Articles/_ArticlesWidget.cshtml",
        new ArticlesWidgetViewModel
        {
            Articles = articlesModel,
            Count = viewModel.Properties.Count,
            UseRelatedPages = viewModel.Properties.UseRelatedPages
        });
}
```
To wrap up our code changes, we will update the `_ArticlesWidget.cshtml` View by making the Widget's title contextual, changing it based on the value of `UseRelatedPages`.

```html
@{
    var i = 1;

    var title = Model.UseRelatedPages
        ? HtmlLocalizer["Related article"]
        : HtmlLocalizer["Latest article"];
}

<!-- ... -->

<div class="articles-section">
    <div class="row">
        <div class="title-wrapper">
            <h1 class="title-tab">@title</h1>
        </div>

<!-- ... -->
```

That's it 👍! Now we can test our Widget and toggle the source of the Articles that are displayed on the Page.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/nb1490ziq2oec5usjw4h.jpg)

## Conclusion

As you can see, we now have the option to source the Article Pages that our Widget displays either from Articles related to the current page, or by listing out the Articles from the Content Tree.

Using Related Pages allows us to unlock 🔓 content relationships.

We don't always have to focus on where content is located in the content tree, or what kind of Page we are currently adding Widgets to.

Related Pages can make our Widgets more flexible and our content 😁.

Where are you using Related Pages in your Kentico Xperience sites?

Let me know in the comments below.

...

As always, thanks for reading 🙏!

---

<figcaption>Photo by <a href="https://unsplash.com/@kellysikkema">Kelly Sikkema</a> on Unsplash</figcaption>

---

We've put together a list over on [Kentico's GitHub account](https://github.com/Kentico/Home/blob/master/RESOURCES.md) of developer resources. Go check it out!

If you are looking for additional Kentico content, checkout the Kentico tag here on DEV:

{% tag kentico %}

Or my [Kentico Xperience blog series](https://dev.to/seangwright/series), like:

- [Kentico Xperience 13 Beta](https://dev.to/seangwright/series/7366)
- [Kentico Xperience Xplorations](https://dev.to/seangwright/series/8185)
- [Kentico 12 - Design Patterns](https://dev.to/search?q=Kentico%2012%3A%20Design%20Patterns)