Last active
May 27, 2025 16:32
-
-
Save PNergard/704913111bed24b6a9fdc11fe18f4567 to your computer and use it in GitHub Desktop.
Orphan content cleaner for Optimizely - Blazor style
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@using BlazorLabs.Cms.BlazorComponents | |
@using EPiServer.Framework.Web.Mvc.Html | |
@using System.Diagnostics.Metrics | |
<!DOCTYPE html> | |
<html lang="@(Model.CurrentPage.Language)"> | |
<head> | |
<meta charset="utf-8" /> | |
<meta http-equiv="X-UA-Compatible" content="IE=10" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>@Model.CurrentPage.MetaTitle</title> | |
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" /> | |
<link href="~/_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" /> | |
<base href="~/" /> | |
</head> | |
<body> | |
@RenderBody() | |
<script src="~/_framework/blazor.server.js"></script> | |
<script src="_content/MudBlazor/MudBlazor.min.js"></script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@using EPiServer.Globalization | |
@using EPiServer.Web | |
@using EPiServer.Web.Routing | |
@inject IContentLoader _contentLoader | |
@inject IContentRepository _contentRepository | |
@inject UrlResolver _urlResolver | |
@inject LanguageResolver LanguageResolver | |
<MudPaper Class="outer-paper" Width="100%" Height="100vh" Elevation="0"> | |
<MudPaper Class="inner-paper" Width="100%" Elevation="4"> | |
<MudText Typo="Typo.h5" Class="mb-2">Orphan content cleaner</MudText> | |
<MudText Typo="Typo.body1"> | |
This tool lets you find all orphan media and blocks, and you can delete specific items or all. You can select if you want to filter on media / blocks and if you | |
are looking for something specific you can search by name. | |
</MudText> | |
<MudDivider Class="mt-4 mb-4" /> | |
</MudPaper> | |
<MudPaper Class="inner-paper"> | |
<MudStack Row="true"> | |
<MudSwitch @bind-Value="SwitchMedia" Label="Media" LabelPosition="LabelPosition.Start" Color="Color.Info" /> | |
<MudSwitch @bind-Value="SwitchBlocks" Label="Blocks" LabelPosition="LabelPosition.Start" Color="Color.Info" /> | |
<MudButton OnClick="SearchForOrphanContent" Disabled="!AnyFilterSelected()" Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.Search" Color="Color.Primary">Find orphans</MudButton> | |
<MudButton OnClick="ClearResultList" Disabled="!OrphanContentResultList.Any()" Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.Search" Color="Color.Primary">Reset</MudButton> | |
</MudStack> | |
</MudPaper> | |
<MudPaper Class="inner-paper"> | |
<MudProgressLinear Color="Color.Primary" Indeterminate="@ProgressActive" Class="my-7" /> | |
<MudText>Total content: @AllContentCount</MudText> | |
<MudText Class="mb-4">Orphan contant: @OrphanContentCount</MudText> | |
<MudButton StartIcon="@Icons.Material.Filled.Delete" Variant="Variant.Filled" Color="Color.Secondary" OnClick="() => DeleteOrphans(false)">Delete selected</MudButton> | |
<MudButton StartIcon="@Icons.Material.Filled.Delete" Variant="Variant.Filled" Color="Color.Secondary" OnClick="() => DeleteOrphans(true)">Delete all</MudButton> | |
</MudPaper> | |
<MudPaper Class="inner-paper"> | |
<MudStack> | |
@foreach (var item in OrphanContentResultList) | |
{ | |
<MudCheckBox T="bool" @bind-Value="@item.Selected" Label="@item.Name" /> | |
} | |
</MudStack> | |
</MudPaper> | |
</MudPaper> | |
@code { | |
private bool SwitchMedia { get; set; } = false; | |
private bool SwitchBlocks { get; set; } = false; | |
private bool ProgressActive = false; | |
private int OrphanContentCount { get; set; } | |
private int AllContentCount { get; set; } | |
private List<OrphanItem> OrphanContentResultList { get; set; } = new List<OrphanItem>(); | |
private async Task SearchForOrphanContent() | |
{ | |
ProgressActive = !ProgressActive; | |
await Task.Yield(); | |
OrphanContentResultList.Clear(); | |
var allDescendantsOfGlobalBlockFolder = _contentLoader.GetDescendents(ContentReference.GlobalBlockFolder).Select(_contentLoader.Get<IContent>).ToList(); | |
var filteredDescendatsOfGlobalBlockFolder = allDescendantsOfGlobalBlockFolder.Where(x => (SwitchMedia && x is MediaData) || (SwitchBlocks && x is BlockData)).ToList(); | |
var orphans = filteredDescendatsOfGlobalBlockFolder.Where(item => !_contentRepository.GetReferencesToContent(item.ContentLink, false).Any()); | |
OrphanContentResultList.AddRange(orphans.Select(item => new OrphanItem { Name = item.Name, ContentLink = item.ContentLink, Selected = false }).ToList()); | |
OrphanContentCount = OrphanContentResultList.Count(); | |
AllContentCount = allDescendantsOfGlobalBlockFolder.Count(); | |
ProgressActive = !ProgressActive; | |
} | |
private void DeleteOrphans(bool deleteAll) | |
{ | |
var orphansToDelete = deleteAll ? OrphanContentResultList : OrphanContentResultList.Where(x => x.Selected).ToList(); | |
foreach (var orphan in orphansToDelete) | |
{ | |
_contentRepository.Delete(orphan.ContentLink, true, EPiServer.Security.AccessLevel.NoAccess); | |
} | |
SearchForOrphanContent(); | |
} | |
private void ClearResultList() | |
{ | |
OrphanContentResultList.Clear(); | |
} | |
private bool AnyFilterSelected() | |
{ | |
return SwitchMedia || SwitchBlocks; | |
} | |
public class OrphanItem | |
{ | |
public string Name { get; set; } | |
public ContentReference ContentLink { get; set; } | |
public bool Selected { get; set; } | |
public string EditUrl { get; set; } | |
} | |
} | |
<style> | |
.outer-paper { | |
padding: 20px; /* Adjust the padding value as needed */ | |
} | |
.inner-paper { | |
padding: 20px; /* Adjust the padding value as needed */ | |
} | |
</style> | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Optimizely Orphaned content assets in the media folder
A small Razor component for finding orphan media or blocks in the GlobalBlocksFolder. It lets you delete specific content or everything.
How to use
You need to setup Blazor SSR rendering in your CMS-solution. I have included a MVC layout in this Gist as a starter if you want to define your own content type for Blazor components implemented with MudBlazor
Two good resources to get you started with optimizely and Blazor
https://www.epinova.se/en/blog/2024/use-blazor-components-in-optimizely-cms-adminedit-interface/
https://optimizely.blog/2022/04/blazor-server-mvc-with-optimizely/
MudBlazor
https://mudblazor.com/