diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Help/GetHelpController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Help/GetHelpController.cs index 9c8b4a8c43..f40339b78f 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Help/GetHelpController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Help/GetHelpController.cs @@ -17,7 +17,7 @@ public class GetHelpController : HelpControllerBase { private readonly ILogger _logger; private readonly IJsonSerializer _jsonSerializer; - private HelpPageSettings _helpPageSettings; + private readonly HelpPageSettings _helpPageSettings; public GetHelpController( IOptionsMonitor helpPageSettings, @@ -27,11 +27,8 @@ public class GetHelpController : HelpControllerBase _logger = logger; _jsonSerializer = jsonSerializer; _helpPageSettings = helpPageSettings.CurrentValue; - helpPageSettings.OnChange(UpdateHelpPageSettings); } - private void UpdateHelpPageSettings(HelpPageSettings settings) => _helpPageSettings = settings; - [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/BuildModelsBuilderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/BuildModelsBuilderController.cs index 4282de3697..e2aed78b53 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/BuildModelsBuilderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/BuildModelsBuilderController.cs @@ -1,22 +1,18 @@ -using System; -using System.Threading.Tasks; using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Infrastructure.ModelsBuilder; using Umbraco.Cms.Infrastructure.ModelsBuilder.Building; -using Umbraco.Extensions; namespace Umbraco.Cms.Api.Management.Controllers.ModelsBuilder; [ApiVersion("1.0")] public class BuildModelsBuilderController : ModelsBuilderControllerBase { - private ModelsBuilderSettings _modelsBuilderSettings; + private readonly ModelsBuilderSettings _modelsBuilderSettings; private readonly ModelsGenerationError _mbErrors; private readonly IModelsGenerator _modelGenerator; @@ -28,8 +24,6 @@ public class BuildModelsBuilderController : ModelsBuilderControllerBase _mbErrors = mbErrors; _modelGenerator = modelGenerator; _modelsBuilderSettings = modelsBuilderSettings.CurrentValue; - - modelsBuilderSettings.OnChange(x => _modelsBuilderSettings = x); } [HttpPost("build")] diff --git a/src/Umbraco.Cms.Api.Management/Factories/ModelsBuilderPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/ModelsBuilderPresentationFactory.cs index b021e0b6ce..4a90d700c1 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/ModelsBuilderPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/ModelsBuilderPresentationFactory.cs @@ -1,30 +1,24 @@ -using System.Text; using Microsoft.Extensions.Options; +using Umbraco.Cms.Api.Management.ViewModels.ModelsBuilderDashboard; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Infrastructure.ModelsBuilder; -using Umbraco.Cms.Api.Management.ViewModels.ModelsBuilderDashboard; -using Umbraco.Extensions; namespace Umbraco.Cms.Api.Management.Factories; public class ModelsBuilderPresentationFactory : IModelsBuilderPresentationFactory { - private ModelsBuilderSettings _modelsBuilderSettings; private readonly ModelsGenerationError _mbErrors; private readonly OutOfDateModelsStatus _outOfDateModels; + private readonly ModelsBuilderSettings _modelsBuilderSettings; public ModelsBuilderPresentationFactory(IOptionsMonitor modelsBuilderSettings, ModelsGenerationError mbErrors, OutOfDateModelsStatus outOfDateModels) { _mbErrors = mbErrors; _outOfDateModels = outOfDateModels; _modelsBuilderSettings = modelsBuilderSettings.CurrentValue; - - modelsBuilderSettings.OnChange(x => _modelsBuilderSettings = x); } - public ModelsBuilderResponseModel Create() => new() { diff --git a/src/Umbraco.Core/Cache/ValueEditorCache.cs b/src/Umbraco.Core/Cache/ValueEditorCache.cs index 85623d30fc..e1ec12e5ef 100644 --- a/src/Umbraco.Core/Cache/ValueEditorCache.cs +++ b/src/Umbraco.Core/Cache/ValueEditorCache.cs @@ -51,7 +51,15 @@ public class ValueEditorCache : IValueEditorCache { foreach (Dictionary editors in _valueEditorCache.Values) { - editors.Remove(id); + if (editors.TryGetValue(id, out IDataValueEditor? editor)) + { + if (editor is IDisposable disposable) + { + disposable.Dispose(); + } + + editors.Remove(id); + } } } } diff --git a/src/Umbraco.Core/DeliveryApi/ApiPublishedContentCache.cs b/src/Umbraco.Core/DeliveryApi/ApiPublishedContentCache.cs index 3bf450a9ec..dd91b4ced1 100644 --- a/src/Umbraco.Core/DeliveryApi/ApiPublishedContentCache.cs +++ b/src/Umbraco.Core/DeliveryApi/ApiPublishedContentCache.cs @@ -1,10 +1,7 @@ -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Services; using Umbraco.Extensions; namespace Umbraco.Cms.Core.DeliveryApi; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs index 8441967376..6de52fd1af 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; @@ -22,14 +23,22 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// /// The value editor for the file upload property editor. /// -internal sealed class FileUploadPropertyValueEditor : DataValueEditor +/// +/// 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 sealed class FileUploadPropertyValueEditor : DataValueEditor, IDisposable { private readonly MediaFileManager _mediaFileManager; private readonly ITemporaryFileService _temporaryFileService; private readonly IScopeProvider _scopeProvider; private readonly IFileStreamSecurityValidator _fileStreamSecurityValidator; private readonly FileUploadValueParser _valueParser; + private ContentSettings _contentSettings; + private readonly IDisposable? _contentSettingsChangeSubscription; + /// /// Initializes a new instance of the class. @@ -46,14 +55,14 @@ internal sealed class FileUploadPropertyValueEditor : DataValueEditor IFileStreamSecurityValidator fileStreamSecurityValidator) : base(shortStringHelper, jsonSerializer, ioHelper, attribute) { - _mediaFileManager = mediaFileManager ?? throw new ArgumentNullException(nameof(mediaFileManager)); + _mediaFileManager = mediaFileManager; _temporaryFileService = temporaryFileService; _scopeProvider = scopeProvider; _fileStreamSecurityValidator = fileStreamSecurityValidator; _valueParser = new FileUploadValueParser(jsonSerializer); - _contentSettings = contentSettings.CurrentValue ?? throw new ArgumentNullException(nameof(contentSettings)); - contentSettings.OnChange(x => _contentSettings = x); + _contentSettings = contentSettings.CurrentValue; + _contentSettingsChangeSubscription = contentSettings.OnChange(x => _contentSettings = x); Validators.Add(new TemporaryFileUploadValidator( () => _contentSettings, @@ -216,4 +225,6 @@ internal sealed class FileUploadPropertyValueEditor : DataValueEditor // in case we are using the old path scheme, try to re-use numbers (bah...) return _mediaFileManager.GetMediaPath(file.FileName, contentKey, propertyTypeKey); } + + public void Dispose() => _contentSettingsChangeSubscription?.Dispose(); } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs index fe978283c4..9dcd48fc30 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs @@ -23,9 +23,12 @@ namespace Umbraco.Cms.Core.PropertyEditors; Constants.PropertyEditors.Aliases.ImageCropper, ValueType = ValueTypes.Json, ValueEditorIsReusable = true)] -public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator, - INotificationHandler, INotificationHandler, - INotificationHandler, INotificationHandler, +public class ImageCropperPropertyEditor : DataEditor, + IMediaUrlGenerator, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, INotificationHandler { private readonly UploadAutoFillProperties _autoFillProperties; @@ -33,9 +36,10 @@ public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator, private readonly IIOHelper _ioHelper; private readonly ILogger _logger; private readonly MediaFileManager _mediaFileManager; - private ContentSettings _contentSettings; private readonly IJsonSerializer _jsonSerializer; + private ContentSettings _contentSettings; + /// /// Initializes a new instance of the class. /// @@ -50,15 +54,14 @@ public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator, IJsonSerializer jsonSerializer) : base(dataValueEditorFactory) { - _mediaFileManager = mediaFileManager ?? throw new ArgumentNullException(nameof(mediaFileManager)); - _contentSettings = contentSettings.CurrentValue ?? throw new ArgumentNullException(nameof(contentSettings)); - _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); - _autoFillProperties = - uploadAutoFillProperties ?? throw new ArgumentNullException(nameof(uploadAutoFillProperties)); + _mediaFileManager = mediaFileManager; + _ioHelper = ioHelper; + _autoFillProperties = uploadAutoFillProperties; _contentService = contentService; _jsonSerializer = jsonSerializer; _logger = loggerFactory.CreateLogger(); + _contentSettings = contentSettings.CurrentValue; contentSettings.OnChange(x => _contentSettings = x); SupportsReadOnly = true; } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs index 869f10eac7..17d6d00d51 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -22,17 +22,24 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// /// The value editor for the image cropper property editor. /// -internal sealed class ImageCropperPropertyValueEditor : DataValueEditor // TODO: core vs web? +/// +/// 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 sealed class ImageCropperPropertyValueEditor : DataValueEditor, IDisposable { private readonly IDataTypeConfigurationCache _dataTypeConfigurationCache; private readonly IFileStreamSecurityValidator _fileStreamSecurityValidator; private readonly ILogger _logger; private readonly MediaFileManager _mediaFileManager; private readonly IJsonSerializer _jsonSerializer; - private ContentSettings _contentSettings; private readonly ITemporaryFileService _temporaryFileService; private readonly IScopeProvider _scopeProvider; + private ContentSettings _contentSettings; + private readonly IDisposable? _contentSettingsChangeSubscription; + public ImageCropperPropertyValueEditor( DataEditorAttribute attribute, ILogger logger, @@ -50,12 +57,13 @@ internal sealed class ImageCropperPropertyValueEditor : DataValueEditor // TODO: _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _mediaFileManager = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); _jsonSerializer = jsonSerializer; - _contentSettings = contentSettings.CurrentValue; _temporaryFileService = temporaryFileService; _scopeProvider = scopeProvider; _fileStreamSecurityValidator = fileStreamSecurityValidator; _dataTypeConfigurationCache = dataTypeConfigurationCache; - contentSettings.OnChange(x => _contentSettings = x); + + _contentSettings = contentSettings.CurrentValue; + _contentSettingsChangeSubscription = contentSettings.OnChange(x => _contentSettings = x); Validators.Add(new TemporaryFileUploadValidator(() => _contentSettings, TryParseTemporaryFileKey, TryGetTemporaryFile)); } @@ -267,4 +275,6 @@ internal sealed class ImageCropperPropertyValueEditor : DataValueEditor // TODO: return filepath; } + + public void Dispose() => _contentSettingsChangeSubscription?.Dispose(); } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteBlockRenderingValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteBlockRenderingValueConverter.cs index 65c6835f6b..9e3c719f5e 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteBlockRenderingValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteBlockRenderingValueConverter.cs @@ -26,7 +26,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; /// used dynamically. /// [DefaultPropertyValueConverter] -public class RteBlockRenderingValueConverter : SimpleRichTextValueConverter, IDeliveryApiPropertyValueConverter +public class RteBlockRenderingValueConverter : SimpleRichTextValueConverter, IDeliveryApiPropertyValueConverter, IDisposable { private readonly HtmlImageSourceParser _imageSourceParser; private readonly HtmlLocalLinkParser _linkParser; @@ -41,7 +41,9 @@ public class RteBlockRenderingValueConverter : SimpleRichTextValueConverter, IDe private readonly RichTextBlockPropertyValueConstructorCache _constructorCache; private readonly IVariationContextAccessor _variationContextAccessor; private readonly BlockEditorVarianceHandler _blockEditorVarianceHandler; + private DeliveryApiSettings _deliveryApiSettings; + private readonly IDisposable? _deliveryApiSettingsChangeSubscription; public RteBlockRenderingValueConverter(HtmlLocalLinkParser linkParser, HtmlUrlParser urlParser, HtmlImageSourceParser imageSourceParser, IApiRichTextElementParser apiRichTextElementParser, IApiRichTextMarkupParser apiRichTextMarkupParser, @@ -62,8 +64,9 @@ public class RteBlockRenderingValueConverter : SimpleRichTextValueConverter, IDe _logger = logger; _variationContextAccessor = variationContextAccessor; _blockEditorVarianceHandler = blockEditorVarianceHandler; + _deliveryApiSettings = deliveryApiSettingsMonitor.CurrentValue; - deliveryApiSettingsMonitor.OnChange(settings => _deliveryApiSettings = settings); + _deliveryApiSettingsChangeSubscription = deliveryApiSettingsMonitor.OnChange(settings => _deliveryApiSettings = settings); } public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => @@ -250,4 +253,6 @@ public class RteBlockRenderingValueConverter : SimpleRichTextValueConverter, IDe public required RichTextBlockModel? RichTextBlockModel { get; set; } } + + public void Dispose() => _deliveryApiSettingsChangeSubscription?.Dispose(); } diff --git a/src/Umbraco.Infrastructure/Search/IndexingNotificationHandler.DeliveryApi.cs b/src/Umbraco.Infrastructure/Search/IndexingNotificationHandler.DeliveryApi.cs index 1750310e71..40c6715137 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 @@ internal sealed class DeliveryApiContentIndexingNotificationHandler : _deliveryApiIndexingHandler = deliveryApiIndexingHandler; _logger = logger; _deliveryApiSettings = deliveryApiSettings.CurrentValue; - deliveryApiSettings.OnChange(settings => _deliveryApiSettings = settings); } public void Handle(ContentCacheRefresherNotification notification) diff --git a/src/Umbraco.Infrastructure/Security/EmailUserForgotPasswordSender.cs b/src/Umbraco.Infrastructure/Security/EmailUserForgotPasswordSender.cs index 6c276a21bb..e64f22b886 100644 --- a/src/Umbraco.Infrastructure/Security/EmailUserForgotPasswordSender.cs +++ b/src/Umbraco.Infrastructure/Security/EmailUserForgotPasswordSender.cs @@ -16,8 +16,8 @@ public class EmailUserForgotPasswordSender : IUserForgotPasswordSender { private readonly IEmailSender _emailSender; private readonly ILocalizedTextService _localizedTextService; - private GlobalSettings _globalSettings; - private SecuritySettings _securitySettings; + private readonly GlobalSettings _globalSettings; + private readonly SecuritySettings _securitySettings; public EmailUserForgotPasswordSender( IEmailSender emailSender, @@ -29,9 +29,6 @@ public class EmailUserForgotPasswordSender : IUserForgotPasswordSender _localizedTextService = localizedTextService; _globalSettings = globalSettings.CurrentValue; _securitySettings = securitySettings.CurrentValue; - - globalSettings.OnChange(settings => _globalSettings = settings); - securitySettings.OnChange(settings => _securitySettings = settings); } public async Task SendForgotPassword(UserForgotPasswordMessage messageModel)