RichardSoeteman.NET

Automatically clear old items from the Umbraco recycle bin

Published 04 October 2024

We have all been there. Opening the Recycle bin in Umbraco and there is a huge list of items just sitting there for years. Best case you can open the tree and delete some items but most of the time you get all sorts of errors. 

An item on the sprint board where a colleague got the task "Clear the recycle bin" assigned triggered me to automate this.

image
A simple background job in Umbraco will help us

I think it's safe to say that items that are longer than a month in the recycle bin are not deleted by accident and therefore can be deleted. This is an ideal task for an Umbraco Background Job  that will be executed every (x) hours (whatever you specify) on the background and is already used in Umbraco to clean log files, delete content versions etc. 

Funny fact is that the documentation sample also does something with clearing the recycle bin. I did not know before writing this post. 

In the samples below I will take only 25 Content or media items when executing the background job otherwise all Umbraco resources will only be used to delete items and Umbraco would simply hang. There are 24 hours in a day so the process will be called a few times a day to delete all those old items.

Delete Content items

Below the code of the background job to delete items older than one month from the Recycle bin in the content section.

In an ideal world hard coded settings are moved to configuration of course. Of course the magic is in RunJobAsync. But Delay and Period are also important.

Delay gives you the option to specify when the Background Job should be executed for the first time. With this you can make sure deleting will not happen at the startup.

Period will allow you to specify the interval of execution. 

public class ContentRecycleBinCleaner(
    IContentService _contentService,
    ILogger<ContentRecycleBinCleaner> _logger)
    : IRecurringBackgroundJob
{
    public Task RunJobAsync()
    {
        try
        {
            var maxItemsToDelete = 25;
            var compare = DateTime.Now.AddMonths(-1).Ticks;
            var all = _contentService.GetPagedContentInRecycleBin(0, maxItemsToDelete, out var total)
                .Where(p => p.UpdateDate.Ticks < compare).ToList();
            if (all.Count == 0)
            {
                _logger.LogInformation("Nothing to delete in the content recycle bin");
            }
            else
            {
                all.ForEach(x =>
                {
                    _logger.LogDebug("Deleting document '{docName}' from the recycle bin.", x.Name);
                    _contentService.Delete(x);
                });

                _logger.LogInformation(
                    "Content bin {total} items deleted. If more than {maxItemsToDelete} old documents exists in recycle bin more items will be deleted next run",
                    all.Count,
                    maxItemsToDelete);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex,"Error during deleting  of items in the Content Recycle Bin");
        }

        return Task.CompletedTask;
    }

    //Run this task every hour
    public TimeSpan Period => TimeSpan.FromMinutes(60);

    //Let's give the server some time to boot up
    public TimeSpan Delay => TimeSpan.FromMinutes(1);

    public event EventHandler? PeriodChanged;
}
Delete Media items

The code for media is almost the same as the content code. Only Media Service is used and I have set the Delay a bit higher so deleting media items will not happen at the same time as deleting content items.

public class MediaRecycleBinCleaner(
    IMediaService _mediaService,
    ILogger<MediaRecycleBinCleaner> _logger)
    : IRecurringBackgroundJob
{
    public Task RunJobAsync()
    {
        try
        {
            var maxItemsToDelete = 25;
            var compare = DateTime.Now.AddMonths(-1).Ticks;
            var all = _mediaService.GetPagedMediaInRecycleBin(0, maxItemsToDelete, out var total)
                .Where(p => p.UpdateDate.Ticks < compare).ToList();
            if (all.Count == 0)
            {
                _logger.LogInformation("Nothing to delete in the media recycle bin");
            }
            else
            {
                all.ForEach(x =>
                {
                    _logger.LogDebug("Deleting media item '{docName}' from the recycle bin.", x.Name);
                    _mediaService.Delete(x);
                });

                _logger.LogInformation(
                    "Media bin {total} items deleted. If more than {maxItemsToDelete} old media items exists in recycle bin more items will be deleted next run",
                    all.Count,
                    maxItemsToDelete);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex,"Error during deleting of items in the  Media Recycle Bin");
        }

        return Task.CompletedTask;
    }

    //Run this task every hour/
    public TimeSpan Period => TimeSpan.FromMinutes(60);

    //Let's give the server some time to boot up
    public TimeSpan Delay => TimeSpan.FromMinutes(2);

    public event EventHandler? PeriodChanged;
}
Compose it all together

The AddBackGroundTasksComposer  will make sure the background jobs get registered.

public class AddBackGroundTasksComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.Services.AddRecurringBackgroundJob<ContentRecycleBinCleaner>();
        builder.Services.AddRecurringBackgroundJob<MediaRecycleBinCleaner>();
    }
}
Complete Source

I made a small Repo on GitHub  where you can download the code. I've written this for V13, it will also work on V14. I used V13 because CMSImport is not ready yet for V14 and I needed a lot  of items so I imported those.