Jesper Mayntzhusen
Posted on September 13, 2021
Let's say you are creating an Umbraco site that has fx a product catalogue that is imported. You may have a lot of different products that need images as well, more than you are interested in going through one by one.
Here is an example on how you can automate it, this example will use the Umbraco Starter kit as it already has a product catalogue with SKU's:
First thing first - let's limit the automation to a "Product Uploads" folder in the media library:
We will save the ID of this folder in a constants file:
namespace AutoImages.Constants
{
public static class Content
{
public const int MediaUploadFolder = 1145;
}
}
Then we will create a media event to listen for uploads to that folder. For this we can use the cacherefresher event - it is the last event after media saving, plus this way we can trigger it for everything by rebuilding the cache:
using Umbraco.Core.Composing;
using Umbraco.Core.Services;
using Umbraco.Web.Cache;
namespace AutoImages.Events
{
public class MediaEvents : IComponent
{
public void Initialize()
{
MediaCacheRefresher.CacheUpdated += MediaCacheRefresher_CacheUpdated;
}
public void Terminate()
{
MediaCacheRefresher.CacheUpdated -= MediaCacheRefresher_CacheUpdated;
}
private void MediaCacheRefresher_CacheUpdated(MediaCacheRefresher sender, Umbraco.Core.Cache.CacheRefresherEventArgs args)
{
// do stuff
}
}
}
Before diving into the event code we need to make sure it runs on the site, for that we need to register it in a composer:
using AutoImages.Events;
using Umbraco.Core;
using Umbraco.Core.Composing;
namespace AutoImages.Composers
{
public class SiteComposer : IUserComposer
{
public void Compose(Composition composition)
{
composition.Components().Append<MediaEvents>();
}
}
}
Alright so now that the basics are laid out - what we actually want to happen is something like this:
- Image is uploaded to our uploads folder
- That image has some sort of naming convention we can use to match it with the product nodes
- We search the product nodes to find a match
- If one is found we "select" the image on that node
To make it easy we will just say we will overwrite the product image if the name matches the sku:
using Examine;
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Changes;
using Umbraco.Core.Sync;
using Umbraco.Web.Cache;
namespace AutoImages.Events
{
public class MediaEvents : IComponent
{
private readonly IContentService _contentService;
private readonly IMediaService _mediaService;
private readonly IExamineManager _examineManager;
public MediaEvents(IContentService contentService, IMediaService mediaService, IExamineManager examineManager)
{
_contentService = contentService;
_mediaService = mediaService;
_examineManager = examineManager;
}
private void MediaCacheRefresher_CacheUpdated(MediaCacheRefresher sender, Umbraco.Core.Cache.CacheRefresherEventArgs args)
{
if (args.MessageType != MessageType.RefreshByPayload)
throw new NotSupportedException();
var products = new Dictionary<string, string>();
// for each media item the cache has refreshed
foreach (var payload in (MediaCacheRefresher.JsonPayload[])args.MessageObject)
{
// only when the node is refreshed - so not on delete, move, etc
if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshNode))
{
var media = _mediaService.GetById(payload.Id);
if (media == null || media.Trashed) continue;
// only continue if the parent is the uploads folder
if (media.ParentId == Constants.Content.MediaUploadFolder)
{
// example name: UMB-BIKER-JACKET.png
var splitName = media.Name.Split('.');
// we save the sku and the media udi for later
products.Add(splitName[0], media.GetUdi().ToString());
}
}
}
_examineManager.TryGetIndex(Umbraco.Core.Constants.UmbracoIndexes.ExternalIndexName, out IIndex index);
var searcher = index.GetSearcher();
foreach (var product in products)
{
var searchResults = searcher.CreateQuery().NativeQuery($"__NodeTypeAlias: product +sku: {product.Key}").Execute();
if (searchResults.Any())
{
foreach (var searchResult in searchResults)
{
if (!int.TryParse(searchResult.Id, out int contentId))
continue;
var node = _contentService.GetById(contentId);
// MediaPicker v2 saves the udi of the image, which was saved in the value of the product dictionary already
node.SetValue("photos", product.Value);
_contentService.SaveAndPublish(node);
}
}
}
}
public void Initialize()
{
MediaCacheRefresher.CacheUpdated += MediaCacheRefresher_CacheUpdated;
}
public void Terminate()
{
MediaCacheRefresher.CacheUpdated -= MediaCacheRefresher_CacheUpdated;
}
}
}
And here is a gif of it all working π
That's it for now! I am not entirely happy with having to use services as much as I do, so if anyone know of a more performant way to do this please let me know!
It is however still way faster than having to do it all manually!
Posted on September 13, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.