Sample: Share data between widgets

Overview

Use the Share data between widgets sample to create two widgets that work together in the following way:

  • The CategorySelector widget displays a list of categories.
  • The DataToFilter displays filtered sample data, based on the selected category.

NOTE: You must place the widgets on the same page.

IRequestPreparation interface

This sample shows how two widgets can communicate with each other in the early stage of the pipeline.

The sample takes advantage of the IRequestPreparation class, which is executed prior to the page execution. This is useful in the following cases:

  • To share data between widgets.
  • To perform some initialization based on the current page and widgets.
  • To prefetch data using the IRestClient interface that is passed as an argument.

NOTE: The IRequestPreparation implementation must be registered in the DI container during startup.

Implementation

The implementation in this sample monitors the unresolved segments in the URL – for example:

  • For localhost/home/cat1 » [ cat1 ] will be the parameter in the UrlParameters collection
  • For localhost/home/cat1/cat2 » [ cat1, cat2 ] will be the parameters in the UrlParameters collection

If there is a match with the existing segments, then MarkUrlParametersResolved() method is called to notify the underlying framework that the additional parameters are resolved and this is a valid URL. If an invalid URL is requested, for example - /cat4 or /cat1/cat2, then the Renderer will return a 404 response. When a match is successful, the resolved parameter is added to the state of the CategorySelector and DataToFilter widgets, so they can display their views according to the current selection.

PREREQUISITES: You must set up a Sitefinity renderer application and connect it to your Sitefinity CMS application. For more information, see Install Sitefinity in ASP.NET Core mode.

NOTE: The instructions in this sample use Visual Studio 2022 and a Sitefinity renderer project named Renderer.

Create the folder structure

Under your Renderer project, you must create the following folders:

  • Entities/CategorySelector
  • Entities/DataToFilter
  • ViewComponents
  • ViewModels
  • Views/Shared/Components/CategorySelector
  • Views/Shared/Components/DataToFilter

Implement the IRequestPreparation interface

The example takes advantage of the IRequestPreparation class.

  • In the context menu of the Renderer project, click Add » Class…
  • In Name, enter CategoryPreparation.cs and click Add.
  • In the class, paste the following code and save your changes:

using System.Linq;
using System.Threading.Tasks;
using Entities.CategorySelector;
using Entities.DataToFilter;
using Microsoft.AspNetCore.Http;
using Progress.Sitefinity.AspNetCore.Models;
using Progress.Sitefinity.AspNetCore.Preparations;
using Progress.Sitefinity.AspNetCore.ViewComponents;
using Progress.Sitefinity.RestSdk;
using ViewComponents;
namespace Renderer
{
public class CategoryPreparation : IRequestPreparation
{
public const string SelectedCategory = "selected-category";
public Task Prepare(PageModel pageModel, IRestClient restClient, HttpContext context)
{
// get the category selector widget
var categorySelectorWidget = pageModel.AllViewComponentsFlat.FirstOrDefault(x => typeof(IViewComponentContext<CategorySelectorEntity>).IsAssignableFrom(x.GetType()));
if (categorySelectorWidget != null)
{
// if more parameters are found in the url, they will be present in the UrlParameters collection
// e.g.
// localhost/home/cat1 -> [cat1] will be the parameter in the UrlParameters collection
// localhost/home/cat1/cat2 -> [cat1, cat2] will be the parameters in the UrlParameters collection
var categories = CategorySelectorViewComponent.Categories.Keys.ToList();
if (pageModel.UrlParameters.Count == 1)
{
// if the additional url parameter matches one of the category urls, we mark the parameters as resolved
// so there is no 404 thrown
var parameterWithForwardSlash = "/" + pageModel.UrlParameters[0];
if (categories.Contains(parameterWithForwardSlash))
{
// add the selected category to the state so we can highlight it in the front-end
categorySelectorWidget.State.Add(SelectedCategory, parameterWithForwardSlash);
var dataToFilterWidget = pageModel.AllViewComponentsFlat.FirstOrDefault(x => typeof(IViewComponentContext<DataToFilterEntity>).IsAssignableFrom(x.GetType()));
if (dataToFilterWidget != null)
{
// add the selected category to the state so we can filter our data based on the selected url parameter
dataToFilterWidget.State.Add(SelectedCategory, parameterWithForwardSlash);
}
pageModel.MarkUrlParametersResolved();
}
}
}
return Task.CompletedTask;
}
}
}

Create the widgets

  • In the context menu of folder Entities/CategorySelector, click Add » Class…
  • In Name, enter CategorySelectorEntity.cs and click Add.
  • In the class, paste the following code and save your changes:

namespace Entities.CategorySelector
{
public class CategorySelectorEntity
{
// left blank as no properties are used in the sample
}
}

  • In the context menu of folder Entities/DataToFilter, click Add » Class…
  • In Name, enter DataToFilterEntity.cs and click Add.
  • In the class, paste the following code and save your changes:

namespace Entities.DataToFilter
{
public class DataToFilterEntity
{
// left blank as no properties are used in the sample
}
}
view raw DataToFilter.cs hosted with ❤ by GitHub

  • In the context menu of folder ViewComponents, click Add » Class…
  • In Name, enter CategorySelectorViewComponent.cs and click Add.
  • In the class, paste the following code and save your changes:

using System;
using Microsoft.AspNetCore.Mvc;
using Entities.CategorySelector;
using Progress.Sitefinity.AspNetCore.ViewComponents;
using System.Collections.Generic;
using ViewModels;
using Renderer;
namespace ViewComponents
{
/// <summary>
/// Test widget with different kind of restrictions for its properties.
/// </summary>
[SitefinityWidget]
public class CategorySelectorViewComponent : ViewComponent
{
public static readonly Dictionary<string, string> Categories = new Dictionary<string, string>()
{
{ "/cat1", "Category 1" },
{ "/cat2", "Category 2" },
{ "/cat3", "Category 3" },
};
/// <summary>
/// Invokes the view.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public IViewComponentResult Invoke(IViewComponentContext<CategorySelectorEntity> context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var categoriesWithNonExisting = new Dictionary<string, string>(Categories);
categoriesWithNonExisting.Add("/cat4", "Non existing category");
if (context.State.TryGetValue(CategoryPreparation.SelectedCategory, out object selectedCategory))
return this.View(new CategorySelectorViewModel() { Categories = categoriesWithNonExisting, SelectedCategory = selectedCategory.ToString() });
return this.View(new CategorySelectorViewModel() { Categories = categoriesWithNonExisting });
}
}
}

  • In the context menu of folder ViewComponents, click Add » Class…
  • In Name, enter DataToFilterViewComponent.cs and click Add.
  • In the class, paste the following code and save your changes:

using System;
using Microsoft.AspNetCore.Mvc;
using Entities.DataToFilter;
using Progress.Sitefinity.AspNetCore.ViewComponents;
using Renderer;
namespace ViewComponents
{
/// <summary>
/// Test widget with different kind of restrictions for its properties.
/// </summary>
[SitefinityWidget]
public class DataToFilterViewComponent : ViewComponent
{
/// <summary>
/// Invokes the view.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public IViewComponentResult Invoke(IViewComponentContext<DataToFilterEntity> context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var allItems = new string[] { "Item 1", "Item 2", "Item 3" };
if (context.State.TryGetValue(CategoryPreparation.SelectedCategory, out object selectedCategory))
{
if (selectedCategory.ToString() == "/cat1")
{
return this.View(new string[] { "Item 1", "Item 2" });
}
else if (selectedCategory.ToString() == "/cat2")
{
return this.View(new string[] { "Item 2", "Item 3" });
}
else if (selectedCategory.ToString() == "/cat3")
{
return this.View(new string[] { "Item 1", "Item 3" });
}
}
return this.View(new string[] { "Item 1", "Item 2", "Item 3" });
}
}
}

  • In the context menu of folder ViewModels, click Add » Class…
  • In Name, enter CategorySelectorViewModel.cs and click Add.
  • In the class, paste the following code and save your changes:

using System.Collections.Generic;
namespace ViewModels
{
public class CategorySelectorViewModel
{
public IDictionary<string, string> Categories { get; set; }
public string SelectedCategory { get; set; }
}
}

  • In the context menu of folder Views/Shared/Components/CategorySelector, click Add » Class…
  • Select Code File.
  • In Name, enter Default.cshtml and click Add.
  • In the class, paste the following code and save your changes:

@using Entities.CategorySelector;
@using ViewModels;
@using Progress.Sitefinity.AspNetCore.Web;
@model CategorySelectorViewModel
@inject IRequestContext requestContext;
<ul>
@foreach (var category in this.Model.Categories)
{
var url = requestContext.PageNode.ViewUrl + category.Key;
<li>
@if (this.Model.SelectedCategory != null && this.Model.SelectedCategory == category.Key)
{
<b><a href="@url">@category.Value</a></b>
}
else
{
<a href="@url">@category.Value</a>
}
</li>
}
</ul>
view raw Default.cshtml hosted with ❤ by GitHub

  • In the context menu of folder Views/Shared/Components/DataToFilter, click Add » Class…
  • Select Code File.
  • In Name, enter Default.cshtml and click Add.
  • In the class, paste the following code and save your changes:

@using Entities.DataToFilter;
@model string[]
@foreach (var dataItem in this.Model)
{
<h3>@dataItem</h3>
}
view raw Default.cshtml hosted with ❤ by GitHub

Register the IRequestPreparation class

You must register the IRequestPreparation class in the Program.cs.
You must add the following line:
builder.Services.AddSingleton??IRequestPreparation, CategoryPreparation??()??column

The Program.cs file should look similar to the following:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Progress.Sitefinity.AspNetCore;
using Progress.Sitefinity.AspNetCore.FormWidgets;
using Progress.Sitefinity.AspNetCore.Preparations;
using Progress.Sitefinity.Renderer.Designers;
using share_data_between_widgets;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddSitefinity();
builder.Services.AddViewComponentModels();
builder.Services.AddSingleton<IRequestPreparation, CategoryPreparation>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStaticFiles();
app.UseRouting();
app.UseSitefinity();
app.Run();
view raw Program.cs hosted with ❤ by GitHub

Build your solution.

Result

When you open your Renderer application and open the New editor, you will see the CategorySelector and DataToFilter widgets in the widget selector. You must add the widgets on the same page and them you can use the filter on the frontend.

ShareData

Run the sample

This sample is available in Sitefinity’s GitHub repository. You can run and play with it.
To do this, perform the following:

  1. Go to Sitefinity’s GitHub repository Sitefinity ASP.NET Core samples.
  2. Expand Code and click Download ZIP.
  3. Extract the files on your computer.
  4. In the extracted folder, navigate to sitefinity-aspnetcore-mvc-samples-master/src/share-data-between-widgets folder.
  5. Open the share-data-between-widgets.sln in Visual Studio.
  6. Open the appsettings.json file.
  7. In section “Sitefinity”, change the “Url” property to the URL of your Sitefinity CMS site.
    If you have deployed Sitefinity CMS on the IIS, point to “https://localhost:<https_port>".
  8. In Visual Studio, in the context menu of share-data-between-widgets project, click View in Browser.
  9. Log in to your Sitefinity CMS instance and place the widget on a page.

Want to learn more?

Increase your Sitefinity skills by signing up for our free trainings. Get Sitefinity-certified at Progress Education Community to boost your credentials.

Get started with Integration Hub | Sitefinity Cloud | Sitefinity SaaS

This free lesson teaches administrators, marketers, and other business professionals how to use the Integration hub service to create automated workflows between Sitefinity and other business systems.

Web Security for Sitefinity Administrators

This free lesson teaches administrators the basics about protecting yor Sitefinity instance and its sites from external threats. Configure HTTPS, SSL, allow lists for trusted sites, and cookie security, among others.

Foundations of Sitefinity ASP.NET Core Development

The free on-demand video course teaches developers how to use Sitefinity .NET Core and leverage its decoupled architecture and new way of coding against the platform.

Was this article helpful?