From da10a2120b5ad7353e1716267b8cc10eac4781c8 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 3 Nov 2025 20:14:49 +0100 Subject: [PATCH 1/6] Fix memory leak with IOptionsMonitor.OnChange and non-singleton registered components. --- .../PropertyEditors/FileUploadPropertyValueEditor.cs | 8 ++++++-- .../PropertyEditors/ImageCropperPropertyEditor.cs | 8 ++++++-- .../PropertyEditors/ImageCropperPropertyValueEditor.cs | 8 ++++++-- .../PropertyEditors/UploadFileTypeValidator.cs | 8 ++++++-- .../ValueConverters/RteMacroRenderingValueConverter.cs | 9 +++++++-- src/Umbraco.Web.BackOffice/Controllers/HelpController.cs | 9 +++------ .../Controllers/ImagesController.cs | 4 +--- 7 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs index 34a4d33fd913..85469caf9d9d 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs @@ -16,11 +16,13 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// /// The value editor for the file upload property editor. /// -internal class FileUploadPropertyValueEditor : DataValueEditor +internal class FileUploadPropertyValueEditor : DataValueEditor, IDisposable { private readonly MediaFileManager _mediaFileManager; private readonly IFileStreamSecurityValidator _fileStreamSecurityValidator; + private ContentSettings _contentSettings; + private readonly IDisposable? _contentSettingsChangeSubscription; public FileUploadPropertyValueEditor( DataEditorAttribute attribute, @@ -36,7 +38,7 @@ public FileUploadPropertyValueEditor( _mediaFileManager = mediaFileManager ?? throw new ArgumentNullException(nameof(mediaFileManager)); _fileStreamSecurityValidator = fileStreamSecurityValidator; _contentSettings = contentSettings.CurrentValue ?? throw new ArgumentNullException(nameof(contentSettings)); - contentSettings.OnChange(x => _contentSettings = x); + _contentSettingsChangeSubscription = contentSettings.OnChange(x => _contentSettings = x); } /// @@ -163,4 +165,6 @@ public FileUploadPropertyValueEditor( return filepath; } + + public void Dispose() => _contentSettingsChangeSubscription?.Dispose(); } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs index 0be7cd4f72bb..a00cfac722d5 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs @@ -33,7 +33,7 @@ namespace Umbraco.Cms.Core.PropertyEditors; public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator, INotificationHandler, INotificationHandler, INotificationHandler, INotificationHandler, - INotificationHandler + INotificationHandler, IDisposable { private readonly UploadAutoFillProperties _autoFillProperties; private readonly IContentService _contentService; @@ -42,7 +42,9 @@ public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator, private readonly IIOHelper _ioHelper; private readonly ILogger _logger; private readonly MediaFileManager _mediaFileManager; + private ContentSettings _contentSettings; + private readonly IDisposable? _contentSettingsChangeSubscription; // Scheduled for removal in v12 [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] @@ -93,7 +95,7 @@ public ImageCropperPropertyEditor( _editorConfigurationParser = editorConfigurationParser; _logger = loggerFactory.CreateLogger(); - contentSettings.OnChange(x => _contentSettings = x); + _contentSettingsChangeSubscription = contentSettings.OnChange(x => _contentSettings = x); SupportsReadOnly = true; } @@ -352,4 +354,6 @@ private void AutoFillProperties(IContentBase model) } } } + + public void Dispose() => _contentSettingsChangeSubscription?.Dispose(); } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs index 1c0567bce24e..e02950c13022 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -23,13 +23,15 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// /// The value editor for the image cropper property editor. /// -internal class ImageCropperPropertyValueEditor : DataValueEditor // TODO: core vs web? +internal class ImageCropperPropertyValueEditor : DataValueEditor, IDisposable { private readonly IDataTypeConfigurationCache _dataTypeConfigurationCache; private readonly IFileStreamSecurityValidator _fileStreamSecurityValidator; private readonly ILogger _logger; private readonly MediaFileManager _mediaFileManager; + private ContentSettings _contentSettings; + private readonly IDisposable? _contentSettingsChangeSubscription; public ImageCropperPropertyValueEditor( DataEditorAttribute attribute, @@ -49,7 +51,7 @@ public ImageCropperPropertyValueEditor( _contentSettings = contentSettings.CurrentValue; _dataTypeConfigurationCache = dataTypeConfigurationCache; _fileStreamSecurityValidator = fileStreamSecurityValidator; - contentSettings.OnChange(x => _contentSettings = x); + _contentSettingsChangeSubscription = contentSettings.OnChange(x => _contentSettings = x); } /// @@ -252,4 +254,6 @@ public override string ConvertDbToString(IPropertyType propertyType, object? val return filepath; } + + public void Dispose() => _contentSettingsChangeSubscription?.Dispose(); } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs b/src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs index 847ac9cff5ca..6a39d82c576d 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs @@ -11,10 +11,12 @@ namespace Umbraco.Cms.Core.PropertyEditors; -internal class UploadFileTypeValidator : IValueValidator +internal class UploadFileTypeValidator : IValueValidator, IDisposable { private readonly ILocalizedTextService _localizedTextService; + private ContentSettings _contentSettings; + private readonly IDisposable? _contentSettingsChangeSubscription; public UploadFileTypeValidator( ILocalizedTextService localizedTextService, @@ -23,7 +25,7 @@ public UploadFileTypeValidator( _localizedTextService = localizedTextService; _contentSettings = contentSettings.CurrentValue; - contentSettings.OnChange(x => _contentSettings = x); + _contentSettingsChangeSubscription = contentSettings.OnChange(x => _contentSettings = x); } public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration) @@ -103,4 +105,6 @@ internal static bool TryGetFileExtension(string? fileName, [MaybeNullWhen(false) extension = fileName.GetFileExtension().TrimStart("."); return true; } + + public void Dispose() => _contentSettingsChangeSubscription?.Dispose(); } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs index c8785d20538e..23b442d02e4a 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs @@ -32,7 +32,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; /// used dynamically. /// [DefaultPropertyValueConverter] -public class RteMacroRenderingValueConverter : SimpleTinyMceValueConverter, IDeliveryApiPropertyValueConverter +public class RteMacroRenderingValueConverter : SimpleTinyMceValueConverter, IDeliveryApiPropertyValueConverter, IDisposable { private readonly HtmlImageSourceParser _imageSourceParser; private readonly HtmlLocalLinkParser _linkParser; @@ -47,7 +47,9 @@ public class RteMacroRenderingValueConverter : SimpleTinyMceValueConverter, IDel private readonly ILogger _logger; private readonly IApiElementBuilder _apiElementBuilder; private readonly RichTextBlockPropertyValueConstructorCache _constructorCache; + private DeliveryApiSettings _deliveryApiSettings; + private readonly IDisposable? _deliveryApiSettingsChangeSubscription; [Obsolete("Please use the constructor that takes all arguments. Will be removed in V14.")] public RteMacroRenderingValueConverter(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, @@ -107,8 +109,9 @@ public RteMacroRenderingValueConverter(IUmbracoContextAccessor umbracoContextAcc _apiElementBuilder = apiElementBuilder; _constructorCache = constructorCache; _logger = logger; + _deliveryApiSettings = deliveryApiSettingsMonitor.CurrentValue; - deliveryApiSettingsMonitor.OnChange(settings => _deliveryApiSettings = settings); + _deliveryApiSettingsChangeSubscription = deliveryApiSettingsMonitor.OnChange(settings => _deliveryApiSettings = settings); } public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => @@ -314,4 +317,6 @@ private class RichTextEditorIntermediateValue : IRichTextEditorIntermediateValue public required RichTextBlockModel? RichTextBlockModel { get; set; } } + + public void Dispose() => _deliveryApiSettingsChangeSubscription?.Dispose(); } diff --git a/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs b/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs index da3ac9013f2f..eb47f4acd33b 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/HelpController.cs @@ -6,7 +6,6 @@ using Newtonsoft.Json; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Web.Common.Attributes; namespace Umbraco.Cms.Web.BackOffice.Controllers; @@ -16,7 +15,8 @@ public class HelpController : UmbracoAuthorizedJsonController { private static HttpClient? _httpClient; private readonly ILogger _logger; - private HelpPageSettings? _helpPageSettings; + + private readonly HelpPageSettings? _helpPageSettings; [ActivatorUtilitiesConstructor] public HelpController( @@ -25,12 +25,9 @@ public HelpController( { _logger = logger; - ResetHelpPageSettings(helpPageSettings.CurrentValue); - helpPageSettings.OnChange(ResetHelpPageSettings); + _helpPageSettings = helpPageSettings.CurrentValue; } - private void ResetHelpPageSettings(HelpPageSettings settings) => _helpPageSettings = settings; - public async Task> GetContextHelpForPage(string section, string tree, string baseUrl = "https://our.umbraco.com") { diff --git a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs index b70661c0af6e..bcdc9ac713e6 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs @@ -23,7 +23,7 @@ public class ImagesController : UmbracoAuthorizedApiController { private readonly MediaFileManager _mediaFileManager; private readonly IImageUrlGenerator _imageUrlGenerator; - private ContentSettings _contentSettings; + private readonly ContentSettings _contentSettings; [Obsolete("Use non obsolete-constructor. Scheduled for removal in Umbraco 13.")] public ImagesController( @@ -45,8 +45,6 @@ public ImagesController( _mediaFileManager = mediaFileManager; _imageUrlGenerator = imageUrlGenerator; _contentSettings = contentSettingsMonitor.CurrentValue; - - contentSettingsMonitor.OnChange(x => _contentSettings = x); } /// From a212ae1991ee0760ca5ccc1822e9e4fd9371c5cb Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 3 Nov 2025 23:11:46 +0100 Subject: [PATCH 2/6] Added XML docs. --- .../PropertyEditors/FileUploadPropertyValueEditor.cs | 4 ++++ .../PropertyEditors/ImageCropperPropertyEditor.cs | 4 ++++ .../PropertyEditors/ImageCropperPropertyValueEditor.cs | 4 ++++ .../PropertyEditors/UploadFileTypeValidator.cs | 7 +++++++ .../ValueConverters/RteMacroRenderingValueConverter.cs | 4 ++++ 5 files changed, 23 insertions(+) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs index 85469caf9d9d..e2b60a322433 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs @@ -16,6 +16,10 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// /// The value editor for the file upload property editor. /// +/// +/// As this class is not registered with DI as a singleton, it must be disposed to release +/// the settings change subscription and avoid a memory leak. +/// internal class FileUploadPropertyValueEditor : DataValueEditor, IDisposable { private readonly MediaFileManager _mediaFileManager; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs index a00cfac722d5..3b89e47d09ba 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs @@ -21,6 +21,10 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// /// Represents an image cropper property editor. /// +/// +/// As this class is not registered with DI as a singleton, it must be disposed to release +/// the settings change subscription and avoid a memory leak. +/// [DataEditor( Constants.PropertyEditors.Aliases.ImageCropper, "Image Cropper", diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs index e02950c13022..32b4e0402c4b 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -23,6 +23,10 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// /// The value editor for the image cropper property editor. /// +/// +/// As this class is not registered with DI as a singleton, it must be disposed to release +/// the settings change subscription and avoid a memory leak. +/// internal class ImageCropperPropertyValueEditor : DataValueEditor, IDisposable { private readonly IDataTypeConfigurationCache _dataTypeConfigurationCache; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs b/src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs index 6a39d82c576d..51a120c29ea0 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs @@ -11,6 +11,13 @@ namespace Umbraco.Cms.Core.PropertyEditors; +/// +/// The value editor for file upload property editors. +/// +/// +/// As this class is not registered with DI as a singleton, it must be disposed to release +/// the settings change subscription and avoid a memory leak. +/// internal class UploadFileTypeValidator : IValueValidator, IDisposable { private readonly ILocalizedTextService _localizedTextService; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs index 23b442d02e4a..7709c42d8768 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs @@ -31,6 +31,10 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; /// A value converter for TinyMCE that will ensure any macro content is rendered properly even when /// used dynamically. /// +/// +/// As this class is not registered with DI as a singleton, it must be disposed to release +/// the settings change subscription and avoid a memory leak. +/// [DefaultPropertyValueConverter] public class RteMacroRenderingValueConverter : SimpleTinyMceValueConverter, IDeliveryApiPropertyValueConverter, IDisposable { From 5dcec87823fa8e5fb857be39e8ce6f0bfe5b1aab Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 4 Nov 2025 09:42:08 +0100 Subject: [PATCH 3/6] Apply fix to DeliveryApiContentIndexingNotificationHandler. --- .../Search/IndexingNotificationHandler.DeliveryApi.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Infrastructure/Search/IndexingNotificationHandler.DeliveryApi.cs b/src/Umbraco.Infrastructure/Search/IndexingNotificationHandler.DeliveryApi.cs index 1750310e7159..40c671513749 100644 --- a/src/Umbraco.Infrastructure/Search/IndexingNotificationHandler.DeliveryApi.cs +++ b/src/Umbraco.Infrastructure/Search/IndexingNotificationHandler.DeliveryApi.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; @@ -17,7 +17,7 @@ internal sealed class DeliveryApiContentIndexingNotificationHandler : { private readonly IDeliveryApiIndexingHandler _deliveryApiIndexingHandler; private readonly ILogger _logger; - private DeliveryApiSettings _deliveryApiSettings; + private readonly DeliveryApiSettings _deliveryApiSettings; public DeliveryApiContentIndexingNotificationHandler( IDeliveryApiIndexingHandler deliveryApiIndexingHandler, @@ -27,7 +27,6 @@ public DeliveryApiContentIndexingNotificationHandler( _deliveryApiIndexingHandler = deliveryApiIndexingHandler; _logger = logger; _deliveryApiSettings = deliveryApiSettings.CurrentValue; - deliveryApiSettings.OnChange(settings => _deliveryApiSettings = settings); } public void Handle(ContentCacheRefresherNotification notification) From b1c3e6b0c945af1b62522d6a7c76919097319265 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 4 Nov 2025 11:22:36 +0100 Subject: [PATCH 4/6] Dispose disposable data editors in ValueEditorCache. --- src/Umbraco.Core/Cache/ValueEditorCache.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Core/Cache/ValueEditorCache.cs b/src/Umbraco.Core/Cache/ValueEditorCache.cs index 358134ab14f3..29b34c335799 100644 --- a/src/Umbraco.Core/Cache/ValueEditorCache.cs +++ b/src/Umbraco.Core/Cache/ValueEditorCache.cs @@ -51,6 +51,11 @@ public void ClearCache(IEnumerable dataTypeIds) { foreach (Dictionary editors in _valueEditorCache.Values) { + if (editors[id] is IDisposable disposable) + { + disposable.Dispose(); + } + editors.Remove(id); } } From 486ace211d846ab9017629b49f277c8886146985 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 6 Nov 2025 12:03:08 +0100 Subject: [PATCH 5/6] Removed unnecessary refactoring and clarified code comments. --- .../PropertyEditors/FileUploadPropertyValueEditor.cs | 4 +++- .../PropertyEditors/ImageCropperPropertyEditor.cs | 7 ++----- .../PropertyEditors/ImageCropperPropertyValueEditor.cs | 3 ++- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs index e2b60a322433..5478498d2f7c 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs @@ -2,6 +2,7 @@ // See LICENSE for more details. using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models.Editors; @@ -17,7 +18,8 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// The value editor for the file upload property editor. /// /// -/// As this class is not registered with DI as a singleton, it must be disposed to release +/// As this class is loaded into which can be cleared, it needs +/// to be disposable in order to properly clean up resources such as /// the settings change subscription and avoid a memory leak. /// internal class FileUploadPropertyValueEditor : DataValueEditor, IDisposable diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs index 3b89e47d09ba..bb4202fb0b10 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs @@ -37,7 +37,7 @@ namespace Umbraco.Cms.Core.PropertyEditors; public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator, INotificationHandler, INotificationHandler, INotificationHandler, INotificationHandler, - INotificationHandler, IDisposable + INotificationHandler { private readonly UploadAutoFillProperties _autoFillProperties; private readonly IContentService _contentService; @@ -48,7 +48,6 @@ public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator, private readonly MediaFileManager _mediaFileManager; private ContentSettings _contentSettings; - private readonly IDisposable? _contentSettingsChangeSubscription; // Scheduled for removal in v12 [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] @@ -99,7 +98,7 @@ public ImageCropperPropertyEditor( _editorConfigurationParser = editorConfigurationParser; _logger = loggerFactory.CreateLogger(); - _contentSettingsChangeSubscription = contentSettings.OnChange(x => _contentSettings = x); + contentSettings.OnChange(x => _contentSettings = x); SupportsReadOnly = true; } @@ -358,6 +357,4 @@ private void AutoFillProperties(IContentBase model) } } } - - public void Dispose() => _contentSettingsChangeSubscription?.Dispose(); } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs index 32b4e0402c4b..4080c1caefe4 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -24,7 +24,8 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// The value editor for the image cropper property editor. /// /// -/// As this class is not registered with DI as a singleton, it must be disposed to release +/// As this class is loaded into which can be cleared, it needs +/// to be disposable in order to properly clean up resources such as /// the settings change subscription and avoid a memory leak. /// internal class ImageCropperPropertyValueEditor : DataValueEditor, IDisposable From dbfa54cf4decedf0b6f10ccaec5e0fa349e1b3fe Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 6 Nov 2025 15:36:41 +0100 Subject: [PATCH 6/6] Fix cache removal error. --- src/Umbraco.Core/Cache/ValueEditorCache.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Cache/ValueEditorCache.cs b/src/Umbraco.Core/Cache/ValueEditorCache.cs index 29b34c335799..4347047c6b4b 100644 --- a/src/Umbraco.Core/Cache/ValueEditorCache.cs +++ b/src/Umbraco.Core/Cache/ValueEditorCache.cs @@ -51,12 +51,15 @@ public void ClearCache(IEnumerable dataTypeIds) { foreach (Dictionary editors in _valueEditorCache.Values) { - if (editors[id] is IDisposable disposable) + if (editors.TryGetValue(id, out IDataValueEditor? editor)) { - disposable.Dispose(); - } + if (editor is IDisposable disposable) + { + disposable.Dispose(); + } - editors.Remove(id); + editors.Remove(id); + } } } }