From 1d1d03aa0a5949f670cd704fc2f6a864b7e7ae67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Tue, 9 Jan 2024 21:58:47 +0100 Subject: [PATCH 01/50] Adding basic prototype --- src/OrchardCore.Build/Dependencies.props | 2 + .../OrchardCore.Media.Azure/Manifest.cs | 11 +++++ .../OrchardCore.Media.Azure.csproj | 4 ++ .../OrchardCore.Media.Azure/Startup.cs | 46 +++++++++++++++++++ 4 files changed, 63 insertions(+) diff --git a/src/OrchardCore.Build/Dependencies.props b/src/OrchardCore.Build/Dependencies.props index 07300f6f7a1..971ca0daeca 100644 --- a/src/OrchardCore.Build/Dependencies.props +++ b/src/OrchardCore.Build/Dependencies.props @@ -57,6 +57,8 @@ + + diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Manifest.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Manifest.cs index 3a101368e7e..7cb4819d1db 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Manifest.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Manifest.cs @@ -17,3 +17,14 @@ }, Category = "Hosting" )] + +[assembly: Feature( + Id = "OrchardCore.Media.Azure.ImageSharpImageCache", + Name = "Azure Media ImageSharp Image Cache", + Description = "Enables support for storing cached images resized via ImageSharp in Microsoft Azure Blob Storage.", + Dependencies = new[] + { + "OrchardCore.Media" + }, + Category = "Hosting" +)] diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/OrchardCore.Media.Azure.csproj b/src/OrchardCore.Modules/OrchardCore.Media.Azure/OrchardCore.Media.Azure.csproj index ec4a798ea28..e468f717649 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/OrchardCore.Media.Azure.csproj +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/OrchardCore.Media.Azure.csproj @@ -25,4 +25,8 @@ + + + + diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs index 9d818de66ca..7451fc937fa 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs @@ -1,10 +1,12 @@ using System; using System.IO; +using Azure.Storage.Blobs.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; @@ -21,6 +23,8 @@ using OrchardCore.Mvc.Core.Utilities; using OrchardCore.Navigation; using OrchardCore.Security.Permissions; +using SixLabors.ImageSharp.Web.Caching; +using SixLabors.ImageSharp.Web.Caching.Azure; namespace OrchardCore.Media.Azure { @@ -152,4 +156,46 @@ private static bool CheckOptions(string connectionString, string containerName, return optionsAreValid; } } + + [Feature("OrchardCore.Media.Azure.ImageSharpImageCache")] + public class ImageSharpAzureBlobCacheStartup : Modules.StartupBase + { + private static readonly object _containerCreateLock = new(); + + private readonly IShellConfiguration _configuration; + + private static bool _containerCreated; + + // This is needed to be greater than OrchardCore.Media's 0 to replace the IImageCache implementation registered + // there. + public override int Order => 5; + + public ImageSharpAzureBlobCacheStartup(IShellConfiguration configuration) + { + _configuration = configuration; + } + + public override void ConfigureServices(IServiceCollection services) + { + // Following https://docs.sixlabors.com/articles/imagesharp.web/imagecaches.html we'd use + // SetCache() but that's only available on IImageSharpBuilder after AddImageSharp(), + // what happens in OrchardCore.Media. Thus, an explicit Replace() is necessary. + services.Configure(options => + { + _configuration + .GetSection("OrchardCore_Media_Azure").GetSection("ImageSharp").GetSection("AzureBlobStorageCacheOptions") + .Bind(options); + + lock (_containerCreateLock) + { + if (!_containerCreated) + { + AzureBlobStorageCache.CreateIfNotExists(options, PublicAccessType.None); + _containerCreated = true; + } + } + }) + .Replace(ServiceDescriptor.Singleton()); + } + } } From 831a55e1f45af94bf1702c9b15cf0b176a6d7124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Tue, 9 Jan 2024 23:00:25 +0100 Subject: [PATCH 02/50] Adding the first draft of the Azure Media ImageSharp Image Cache feature --- src/OrchardCore.Cms.Web/appsettings.json | 9 +- .../ImageSharpImageCacheOptions.cs | 24 +++++ ...ureBlobStorageCacheOptionsConfiguration.cs | 22 +++++ ...mageSharpImageCacheOptionsConfiguration.cs | 65 ++++++++++++++ .../ImageSharpImageCacheTenantEvents.cs | 87 +++++++++++++++++++ .../MediaBlobContainerTenantEvents.cs | 11 ++- .../MediaBlobStorageOptionsConfiguration.cs | 6 +- .../OrchardCore.Media.Azure/Startup.cs | 65 +++++++++----- 8 files changed, 254 insertions(+), 35 deletions(-) create mode 100644 src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheTenantEvents.cs rename src/OrchardCore.Modules/OrchardCore.Media.Azure/{ => Services}/MediaBlobContainerTenantEvents.cs (87%) rename src/OrchardCore.Modules/OrchardCore.Media.Azure/{ => Services}/MediaBlobStorageOptionsConfiguration.cs (96%) diff --git a/src/OrchardCore.Cms.Web/appsettings.json b/src/OrchardCore.Cms.Web/appsettings.json index 5d2534a599a..a004d32652a 100644 --- a/src/OrchardCore.Cms.Web/appsettings.json +++ b/src/OrchardCore.Cms.Web/appsettings.json @@ -68,14 +68,19 @@ // "BucketName": "" //}, // See https://docs.orchardcore.net/en/latest/docs/reference/modules/Media.Azure/#configuration to configure media storage in Azure Blob Storage. - //"OrchardCore_Media_Azure": - //{ + //"OrchardCore_Media_Azure": { // "ConnectionString": "", // Set to your Azure Storage account connection string. // "ContainerName": "somecontainer", // Set to the Azure Blob container name. Templatable, refer docs. // "BasePath": "some/base/path", // Optionally, set to a path to store media in a subdirectory inside your container. Templatable, refer docs. // "CreateContainer": true, // Activates an event to create the container if it does not already exist. // "RemoveContainer": true // Whether the 'Container' is deleted if the tenant is removed, false by default. //}, + //"OrchardCore_Media_Azure_ImageSharp_Cache": { + // "ConnectionString": "", // Set to your Azure Storage account connection string. + // "ContainerName": "somecontainer", // Set to the Azure Blob container name. Templatable, refer docs. + // "CreateContainer": true, // Activates an event to create the container if it does not already exist. + // "RemoveContainer": true // Whether the 'Container' is deleted if the tenant is removed, false by default. + //}, // See https://stackexchange.github.io/StackExchange.Redis/Configuration.html //"OrchardCore_Redis": { // "Configuration": "192.168.99.100:6379,allowAdmin=true", // Redis Configuration string. diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs new file mode 100644 index 00000000000..1282db0e998 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs @@ -0,0 +1,24 @@ +namespace OrchardCore.Media.Azure; + +public class ImageSharpImageCacheOptions +{ + /// + /// The Azure Blob connection string. + /// + public string ConnectionString { get; set; } + + /// + /// The Azure Blob container name. + /// + public string ContainerName { get; set; } + + /// + /// Create blob container on startup if it does not exist. + /// + public bool CreateContainer { get; set; } + + /// + /// Remove blob container on tenant removal if it exists. + /// + public bool RemoveContainer { get; set; } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs new file mode 100644 index 00000000000..409c664cd17 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using SixLabors.ImageSharp.Web.Caching.Azure; + +namespace OrchardCore.Media.Azure.Services; + +// Configuration for ImageSharp's own configuration object. We just pass the settings from the Orchard Core +// configuration. +internal class AzureBlobStorageCacheOptionsConfiguration : IConfigureOptions +{ + private readonly IOptions _options; + + public AzureBlobStorageCacheOptionsConfiguration(IOptions options) + { + _options = options; + } + + public void Configure(AzureBlobStorageCacheOptions options) + { + options.ConnectionString = _options.Value.ConnectionString; + options.ContainerName = _options.Value.ContainerName; + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs new file mode 100644 index 00000000000..bcc1c4e9cc2 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs @@ -0,0 +1,65 @@ +using System; +using Fluid; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using OrchardCore.Environment.Shell; +using OrchardCore.Environment.Shell.Configuration; + +namespace OrchardCore.Media.Azure.Services; + +internal class ImageSharpImageCacheOptionsConfiguration : IConfigureOptions +{ + private readonly IShellConfiguration _shellConfiguration; + private readonly ShellSettings _shellSettings; + private readonly ILogger _logger; + + // Local instance since it can be discarded once the startup is over. + private readonly FluidParser _fluidParser = new(); + + public ImageSharpImageCacheOptionsConfiguration( + IShellConfiguration shellConfiguration, + ShellSettings shellSettings, + ILogger logger) + { + _shellConfiguration = shellConfiguration; + _shellSettings = shellSettings; + _logger = logger; + } + + public void Configure(ImageSharpImageCacheOptions options) + { + var section = _shellConfiguration.GetSection("OrchardCore_Media_Azure_ImageSharp_Cache"); + + options.ContainerName = section.GetValue(nameof(options.ContainerName), string.Empty); + options.ConnectionString = section.GetValue(nameof(options.ConnectionString), string.Empty); + options.CreateContainer = section.GetValue(nameof(options.CreateContainer), true); + options.RemoveContainer = section.GetValue(nameof(options.RemoveContainer), false); + + var templateOptions = new TemplateOptions(); + var templateContext = new TemplateContext(templateOptions); + templateOptions.MemberAccessStrategy.Register(); + templateOptions.MemberAccessStrategy.Register(); + templateContext.SetValue("ShellSettings", _shellSettings); + + ParseContainerName(options, templateContext); + } + + private void ParseContainerName(ImageSharpImageCacheOptions options, TemplateContext templateContext) + { + // Use Fluid directly as this is transient and cannot invoke _liquidTemplateManager. + try + { + var template = _fluidParser.Parse(options.ContainerName); + + // Container name must be lowercase. + options.ContainerName = template.Render(templateContext, NullEncoder.Default).ToLower(); + options.ContainerName = options.ContainerName.Replace("\r", string.Empty).Replace("\n", string.Empty); + } + catch (Exception ex) + { + _logger.LogCritical(ex, "Unable to parse Azure Media ImageSharp Image Cache container name."); + throw; + } + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheTenantEvents.cs new file mode 100644 index 00000000000..b6da2fa41a4 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheTenantEvents.cs @@ -0,0 +1,87 @@ +using System.Threading.Tasks; +using Azure; +using Azure.Storage.Blobs; +using Azure.Storage.Blobs.Models; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using OrchardCore.Environment.Shell; +using OrchardCore.Environment.Shell.Removing; +using OrchardCore.Modules; + +namespace OrchardCore.Media.Azure.Services; + +internal class ImageSharpImageCacheTenantEvents : ModularTenantEvents +{ + private readonly ImageSharpImageCacheOptions _options; + private readonly ShellSettings _shellSettings; + private readonly ILogger _logger; + + protected readonly IStringLocalizer S; + + public ImageSharpImageCacheTenantEvents( + IOptions options, + ShellSettings shellSettings, + ILogger logger, + IStringLocalizer localizer) + { + _options = options.Value; + _shellSettings = shellSettings; + _logger = logger; + S = localizer; + } + + public override async Task ActivatingAsync() + { + // Only create container if options are valid. + if (_shellSettings.IsUninitialized() || + string.IsNullOrEmpty(_options.ConnectionString) || + string.IsNullOrEmpty(_options.ContainerName) || + !_options.CreateContainer) + { + return; + } + + _logger.LogDebug("Testing Azure Media ImageSharp Image Cache container {ContainerName} existence", _options.ContainerName); + + try + { + var blobContainer = new BlobContainerClient(_options.ConnectionString, _options.ContainerName); + var response = await blobContainer.CreateIfNotExistsAsync(PublicAccessType.None); + + _logger.LogDebug("Azure Media ImageSharp Image Cache container {ContainerName} created.", _options.ContainerName); + } + catch (RequestFailedException ex) + { + _logger.LogError(ex, "Unable to create Azure Media ImageSharp Image Cache Container."); + } + } + + public override async Task RemovingAsync(ShellRemovingContext context) + { + // Only remove container if options are valid. + if (!_options.RemoveContainer || + string.IsNullOrEmpty(_options.ConnectionString) || + string.IsNullOrEmpty(_options.ContainerName)) + { + return; + } + + try + { + var blobContainer = new BlobContainerClient(_options.ConnectionString, _options.ContainerName); + var response = await blobContainer.DeleteIfExistsAsync(); + if (!response.Value) + { + _logger.LogError("Unable to remove the Azure Media ImageSharp Image Cache Container {ContainerName}.", _options.ContainerName); + context.ErrorMessage = S["Unable to remove the Azure Media ImageSharp Image Cache Container '{0}'.", _options.ContainerName]; + } + } + catch (RequestFailedException ex) + { + _logger.LogError(ex, "Failed to remove the Azure Media ImageSharp Image Cache Container {ContainerName}.", _options.ContainerName); + context.ErrorMessage = S["Failed to remove the Azure Media ImageSharp Image Cache Container '{0}'.", _options.ContainerName]; + context.Error = ex; + } + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobContainerTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs similarity index 87% rename from src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobContainerTenantEvents.cs rename to src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs index e4f706d62b3..5b7e1810b02 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobContainerTenantEvents.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs @@ -1,4 +1,3 @@ -using System; using System.Threading.Tasks; using Azure; using Azure.Storage.Blobs; @@ -10,7 +9,7 @@ using OrchardCore.Environment.Shell.Removing; using OrchardCore.Modules; -namespace OrchardCore.Media.Azure +namespace OrchardCore.Media.Azure.Services { public class MediaBlobContainerTenantEvents : ModularTenantEvents { @@ -48,8 +47,8 @@ public override async Task ActivatingAsync() try { - var _blobContainer = new BlobContainerClient(_options.ConnectionString, _options.ContainerName); - var response = await _blobContainer.CreateIfNotExistsAsync(PublicAccessType.None); + var blobContainer = new BlobContainerClient(_options.ConnectionString, _options.ContainerName); + var response = await blobContainer.CreateIfNotExistsAsync(PublicAccessType.None); _logger.LogDebug("Azure Media Storage container {ContainerName} created.", _options.ContainerName); } @@ -71,9 +70,9 @@ public override async Task RemovingAsync(ShellRemovingContext context) try { - var _blobContainer = new BlobContainerClient(_options.ConnectionString, _options.ContainerName); + var blobContainer = new BlobContainerClient(_options.ConnectionString, _options.ContainerName); - var response = await _blobContainer.DeleteIfExistsAsync(); + var response = await blobContainer.DeleteIfExistsAsync(); if (!response.Value) { _logger.LogError("Unable to remove the Azure Media Storage Container {ContainerName}.", _options.ContainerName); diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs similarity index 96% rename from src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptionsConfiguration.cs rename to src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs index 522b5b5db25..56f8953d3c9 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs @@ -6,7 +6,7 @@ using OrchardCore.Environment.Shell; using OrchardCore.Environment.Shell.Configuration; -namespace OrchardCore.Media.Azure +namespace OrchardCore.Media.Azure.Services { public class MediaBlobStorageOptionsConfiguration : IConfigureOptions { @@ -14,7 +14,7 @@ public class MediaBlobStorageOptionsConfiguration : IConfigureOptions logger) + { + _configuration = configuration; + _logger = logger; + } // This is needed to be greater than OrchardCore.Media's 0 to replace the IImageCache implementation registered // there. public override int Order => 5; - public ImageSharpAzureBlobCacheStartup(IShellConfiguration configuration) - { - _configuration = configuration; - } - public override void ConfigureServices(IServiceCollection services) { + services.AddTransient, ImageSharpImageCacheOptionsConfiguration>(); + services.AddTransient, AzureBlobStorageCacheOptionsConfiguration>(); + + // Only replace default implementation if options are valid. + var connectionString = _configuration[$"OrchardCore_Media_Azure_ImageSharp_Cache:{nameof(ImageSharpImageCacheOptions.ConnectionString)}"]; + var containerName = _configuration[$"OrchardCore_Media_Azure_ImageSharp_Cache:{nameof(ImageSharpImageCacheOptions.ContainerName)}"]; + + if (!CheckOptions(connectionString, containerName)) + { + return; + } + // Following https://docs.sixlabors.com/articles/imagesharp.web/imagecaches.html we'd use // SetCache() but that's only available on IImageSharpBuilder after AddImageSharp(), // what happens in OrchardCore.Media. Thus, an explicit Replace() is necessary. - services.Configure(options => + services.Replace(ServiceDescriptor.Singleton()); + + services.AddScoped(); + } + + private bool CheckOptions(string connectionString, string containerName) + { + var optionsAreValid = true; + + if (string.IsNullOrWhiteSpace(connectionString)) { - _configuration - .GetSection("OrchardCore_Media_Azure").GetSection("ImageSharp").GetSection("AzureBlobStorageCacheOptions") - .Bind(options); + _logger.LogError( + "Azure Media ImageSharp Image Cache is enabled but not active because the 'ConnectionString' is missing or empty in application configuration."); + optionsAreValid = false; + } - lock (_containerCreateLock) - { - if (!_containerCreated) - { - AzureBlobStorageCache.CreateIfNotExists(options, PublicAccessType.None); - _containerCreated = true; - } - } - }) - .Replace(ServiceDescriptor.Singleton()); + if (string.IsNullOrWhiteSpace(containerName)) + { + _logger.LogError( + "Azure Media ImageSharp Image Cache is enabled but not active because the 'ContainerName' is missing or empty in application configuration."); + optionsAreValid = false; + } + + return optionsAreValid; } } } From f63afd5c1ef1f19c514780068d763cceb1cf854f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Tue, 9 Jan 2024 23:28:29 +0100 Subject: [PATCH 03/50] Docs --- .../reference/modules/Media.Azure/README.md | 55 ++++++++++++++++--- src/docs/releases/1.9.0.md | 4 ++ 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/docs/reference/modules/Media.Azure/README.md b/src/docs/reference/modules/Media.Azure/README.md index 411a2393c63..f4e93806fda 100644 --- a/src/docs/reference/modules/Media.Azure/README.md +++ b/src/docs/reference/modules/Media.Azure/README.md @@ -1,6 +1,8 @@ -# Azure Media Storage (`OrchardCore.Media.Azure`) +# Microsoft Azure Media (`OrchardCore.Media.Azure`) -The Azure Media Storage feature enables support for storing assets in Microsoft Azure Blob Storage. +The Microsoft Azure Media module enables support for storing assets in Microsoft Azure Blob Storage. + +## Azure Media Storage (`OrchardCore.Media.Azure.Storage`) The feature replaces the default `App_Data` file based media store with an Azure Media Storage Provider. @@ -11,13 +13,13 @@ This allows the Azure Media Storage feature to support image resizing on the fly The url generated by the `AssetUrl` helpers, points to the Orchard Core web site. -## Configuration +### Configuration The following configuration values are used by default and can be customized: ```json { - "OrchardCore": { + "OrchardCore": { "OrchardCore_Media_Azure": { // Set to your Azure Storage account connection string. "ConnectionString": "", @@ -25,7 +27,10 @@ The following configuration values are used by default and can be customized: "ContainerName": "somecontainer", // Optionally, set to a path to store media in a subdirectory inside your container. "BasePath": "some/base/path", - "CreateContainer": true + // Activates an event to create the container if it does not already exist. + "CreateContainer": true, + // Whether the 'Container' is deleted if the tenant is removed, false by default. + "RemoveContainer": true } } } @@ -40,7 +45,7 @@ Set `CreateContainer` to `false` to disable this check if your container already If these are not present in `appSettings.json`, it will not enable the feature, and report an error message in the log file. -## Templating Configuration +### Templating Configuration Optionally you may use liquid templating to further configure Azure Media Storage, perhaps creating a container per tenant, or a single container with a base path per tenant. @@ -51,7 +56,7 @@ The `ContainerName` property and the `BasePath` property are the only templatabl !!! note When templating the `ContainerName` using `{{ ShellSettings.Name }}`, the tenant's name will be automatically lowercased, however, you must also make sure the `ContainerName` conforms to other Azure Blob naming conventions as set out in Azure's documentation. -### Configuring a container per tenant +#### Configuring a container per tenant ```json { @@ -69,7 +74,7 @@ The `ContainerName` property and the `BasePath` property are the only templatabl } ``` -### Configuring a single container, with a base folder per tenant +#### Configuring a single container, with a base folder per tenant ```json { @@ -91,7 +96,7 @@ The `ContainerName` property and the `BasePath` property are the only templatabl Only the default Liquid filters and tags are available during parsing of the Liquid template. Extra filters like `slugify` will not be available. -## Media Cache +### Media Cache The Media Cache feature will automatically be enabled when Azure Media Storage is enabled. @@ -115,3 +120,35 @@ re-fetch the source file, as and when required, which the Media Cache Module wil !!! note The Media Feature is designed to support one storage provider at a time, whether that is local File Storage (the default), Azure Blob Storage, or Amazon S3 Storage. + +## Azure Media ImageSharp Image Cache (`OrchardCore.Media.Azure.ImageSharpImageCache`) + +The feature replaces the default `PhysicalFileSystemCache` of ImageSharp that stores resized images in the `App_Data` folder with [`AzureBlobStorageImageCache`](https://docs.sixlabors.com/articles/imagesharp.web/imagecaches.html#azureblobstorageimagecache) that stores them in Azure Blob Storage. Depending on your use case, this can provide the following advantages: + +- Persistent image cache not to have to repeatedly resize images, even if the local file system of the webserver is ephemeral. This helps if you e.g. use containers to host the app, or do clean deployments to the webserver that remove all previously existing files. +- Better performance if disk IO is a bottleneck: The local storage of the webserver may be slow or access to it deliberately throttled, like it is the case with Azure App Services. Using Blob Storage can alleviate pressure on the local disk, leaving more resources available to serve other requests, as well as offer a higher request per second limit for image requests. + +Note that since at the moment configuring a sub-path [is not supported by `AzureBlobStorageImageCache`](https://github.com/SixLabors/ImageSharp.Web/discussions/351), the cached files for all tenants are stored in the same Azure Blob Container. This means that purging the cache of just one tenant is not supported. Furthermore, cache files are only removed for a tenant when removing the tenant itself if you use a separate container for each tenant. + +### Configuration + +The following configuration values are used by default and can be customized: + +```json +{ + "OrchardCore": { + "OrchardCore_Media_Azure_ImageSharp_Cache": { + // Set to your Azure Storage account connection string. + "ConnectionString": "", + // Set to the Azure Blob container name. A container name must be a valid DNS name and conform to Azure container naming rules eg. lowercase only. + "ContainerName": "somecontainer", + // Activates an event to create the container if it does not already exist. + "CreateContainer": true, + // Whether the 'Container' is deleted if the tenant is removed, false by default. + "RemoveContainer": true + } + } +} +``` + +Templating the configuration and configuring a container per tenant works the same way as for Azure Media Storage; follow its documentation above. \ No newline at end of file diff --git a/src/docs/releases/1.9.0.md b/src/docs/releases/1.9.0.md index 36fe171d5a2..a7c7da8e49d 100644 --- a/src/docs/releases/1.9.0.md +++ b/src/docs/releases/1.9.0.md @@ -17,3 +17,7 @@ If you needed to enable indexing for other extensions like (`.docx`, or `.pptx`) ### Azure AI Search module Introducing a new "Azure AI Search" module, designed to empower you in the administration of Azure AI Search indices. When enabled with the "Search" module, it facilitates frontend full-text search capabilities through Azure AI Search. For more info read the [Azure AI Search](../reference/modules/AzureAISearch/README.md) docs. + +### Azure Media ImageSharp Image Cache + +The Microsoft Azure Media module has a new feature, Azure Media ImageSharp Image Cache. This replaces the default `PhysicalFileSystemCache` of ImageSharp that stores resized images in the `App_Data` folder with [`AzureBlobStorageImageCache`](https://docs.sixlabors.com/articles/imagesharp.web/imagecaches.html#azureblobstorageimagecache) that stores them in Azure Blob Storage. Depending on your use case, this can provide various advantages. Check out [the docs](../reference/modules/Media.Azure/README.md) for details. From b97171c6b9c7fcaad2ec2847bc8deec7a3dd0c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 10 Jan 2024 00:05:27 +0100 Subject: [PATCH 04/50] Refactoring --- .../Helpers/FluidParserHelper.cs | 43 +++++++++++++++++++ ...mageSharpImageCacheOptionsConfiguration.cs | 24 ++--------- .../MediaBlobStorageOptionsConfiguration.cs | 37 +++------------- 3 files changed, 54 insertions(+), 50 deletions(-) create mode 100644 src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/FluidParserHelper.cs diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/FluidParserHelper.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/FluidParserHelper.cs new file mode 100644 index 00000000000..6d03db98800 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/FluidParserHelper.cs @@ -0,0 +1,43 @@ +using Fluid; +using OrchardCore.Environment.Shell; + +namespace OrchardCore.Media.Azure.Helpers; + +internal class FluidParserHelper where TOptions : class +{ + // Local instance since it can be discarded once the startup is over. + private readonly FluidParser _fluidParser = new(); + private readonly ShellSettings _shellSettings; + + private TemplateContext _templateContext; + + public FluidParserHelper(ShellSettings shellSettings) + { + _shellSettings = shellSettings; + } + + public string ParseAndFormat(string template) + { + EnsureInitialized(); + + // Use Fluid directly as this is transient and cannot invoke _liquidTemplateManager. + var parsedTemplate = _fluidParser.Parse(template); + return parsedTemplate.Render(_templateContext, NullEncoder.Default) + .Replace("\r", string.Empty) + .Replace("\n", string.Empty); + } + + private void EnsureInitialized() + { + if (_templateContext != null) + { + return; + } + + var templateOptions = new TemplateOptions(); + _templateContext = new TemplateContext(templateOptions); + templateOptions.MemberAccessStrategy.Register(); + templateOptions.MemberAccessStrategy.Register(); + _templateContext.SetValue("ShellSettings", _shellSettings); + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs index bcc1c4e9cc2..8a783fe1d1e 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs @@ -1,10 +1,10 @@ using System; -using Fluid; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OrchardCore.Environment.Shell; using OrchardCore.Environment.Shell.Configuration; +using OrchardCore.Media.Azure.Helpers; namespace OrchardCore.Media.Azure.Services; @@ -13,9 +13,7 @@ internal class ImageSharpImageCacheOptionsConfiguration : IConfigureOptions _fluidParserHelper; public ImageSharpImageCacheOptionsConfiguration( IShellConfiguration shellConfiguration, @@ -25,6 +23,7 @@ public ImageSharpImageCacheOptionsConfiguration( _shellConfiguration = shellConfiguration; _shellSettings = shellSettings; _logger = logger; + _fluidParserHelper = new(shellSettings); } public void Configure(ImageSharpImageCacheOptions options) @@ -36,25 +35,10 @@ public void Configure(ImageSharpImageCacheOptions options) options.CreateContainer = section.GetValue(nameof(options.CreateContainer), true); options.RemoveContainer = section.GetValue(nameof(options.RemoveContainer), false); - var templateOptions = new TemplateOptions(); - var templateContext = new TemplateContext(templateOptions); - templateOptions.MemberAccessStrategy.Register(); - templateOptions.MemberAccessStrategy.Register(); - templateContext.SetValue("ShellSettings", _shellSettings); - - ParseContainerName(options, templateContext); - } - - private void ParseContainerName(ImageSharpImageCacheOptions options, TemplateContext templateContext) - { - // Use Fluid directly as this is transient and cannot invoke _liquidTemplateManager. try { - var template = _fluidParser.Parse(options.ContainerName); - // Container name must be lowercase. - options.ContainerName = template.Render(templateContext, NullEncoder.Default).ToLower(); - options.ContainerName = options.ContainerName.Replace("\r", string.Empty).Replace("\n", string.Empty); + options.ContainerName = _fluidParserHelper.ParseAndFormat(options.ContainerName).ToLower(); } catch (Exception ex) { diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs index 56f8953d3c9..858557fb31c 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs @@ -1,31 +1,30 @@ using System; -using Fluid; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OrchardCore.Environment.Shell; using OrchardCore.Environment.Shell.Configuration; +using OrchardCore.Media.Azure.Helpers; namespace OrchardCore.Media.Azure.Services { - public class MediaBlobStorageOptionsConfiguration : IConfigureOptions + internal class MediaBlobStorageOptionsConfiguration : IConfigureOptions { private readonly IShellConfiguration _shellConfiguration; private readonly ShellSettings _shellSettings; private readonly ILogger _logger; - - // Local instance since it can be discarded once the startup is over. - private readonly FluidParser _fluidParser = new(); + private readonly FluidParserHelper _fluidParserHelper; public MediaBlobStorageOptionsConfiguration( IShellConfiguration shellConfiguration, ShellSettings shellSettings, ILogger logger - ) + ) { _shellConfiguration = shellConfiguration; _shellSettings = shellSettings; _logger = logger; + _fluidParserHelper = new(shellSettings); } public void Configure(MediaBlobStorageOptions options) @@ -38,42 +37,20 @@ public void Configure(MediaBlobStorageOptions options) options.CreateContainer = section.GetValue(nameof(options.CreateContainer), true); options.RemoveContainer = section.GetValue(nameof(options.RemoveContainer), false); - var templateOptions = new TemplateOptions(); - var templateContext = new TemplateContext(templateOptions); - templateOptions.MemberAccessStrategy.Register(); - templateOptions.MemberAccessStrategy.Register(); - templateContext.SetValue("ShellSettings", _shellSettings); - - ParseContainerName(options, templateContext); - ParseBasePath(options, templateContext); - } - - private void ParseContainerName(MediaBlobStorageOptions options, TemplateContext templateContext) - { - // Use Fluid directly as this is transient and cannot invoke _liquidTemplateManager. try { - var template = _fluidParser.Parse(options.ContainerName); - // Container name must be lowercase. - options.ContainerName = template.Render(templateContext, NullEncoder.Default).ToLower(); - options.ContainerName = options.ContainerName.Replace("\r", string.Empty).Replace("\n", string.Empty); + options.ContainerName = _fluidParserHelper.ParseAndFormat(options.ContainerName).ToLower(); } catch (Exception e) { _logger.LogCritical(e, "Unable to parse Azure Media Storage container name."); throw; } - } - private void ParseBasePath(MediaBlobStorageOptions options, TemplateContext templateContext) - { try { - var template = _fluidParser.Parse(options.BasePath); - - options.BasePath = template.Render(templateContext, NullEncoder.Default); - options.BasePath = options.BasePath.Replace("\r", string.Empty).Replace("\n", string.Empty); + options.BasePath = _fluidParserHelper.ParseAndFormat(options.BasePath); } catch (Exception e) { From d92ce8201677e92f9a1a933a87330842ad538c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 10 Jan 2024 02:57:05 +0100 Subject: [PATCH 05/50] Fixing the docs link in CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 86ef04bce68..49b2116e169 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,7 +20,7 @@ Our team members also monitor other discussion forums: We accept fixes and features! Here are some resources to help you get started on how to contribute code or new content. -* Look at the [documentation](/src/docs/). +* Look at the [documentation](https://docs.orchardcore.net/). * ["Help wanted" issues](https://github.com/orchardcms/orchardcore/labels/help%20wanted) - these issues are up for grabs. Comment on an issue if you want to create a fix. * ["Good first issue" issues](https://github.com/orchardcms/orchardcore/labels/good%20first%20issue) - we think these are a good for newcomers. From 930d41d7c78fd231694a22ea315673dfdc07bbba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 17 Jan 2024 17:32:08 +0100 Subject: [PATCH 06/50] MD syntax fix --- src/docs/releases/1.9.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/releases/1.9.0.md b/src/docs/releases/1.9.0.md index aacce507869..493b6fc063c 100644 --- a/src/docs/releases/1.9.0.md +++ b/src/docs/releases/1.9.0.md @@ -29,7 +29,7 @@ Introducing a new "Azure AI Search" module, designed to empower you in the admin The Microsoft Azure Media module has a new feature, Azure Media ImageSharp Image Cache. This replaces the default `PhysicalFileSystemCache` of ImageSharp that stores resized images in the `App_Data` folder with [`AzureBlobStorageImageCache`](https://docs.sixlabors.com/articles/imagesharp.web/imagecaches.html#azureblobstorageimagecache) that stores them in Azure Blob Storage. Depending on your use case, this can provide various advantages. Check out [the docs](../reference/modules/Media.Azure/README.md) for details. -## Deployment Module +### Deployment Module Added new extensions to make registering custom deployment step easier From 20beeaa3ede01ce0ce3d2107431e631f4568cc0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 17 Jan 2024 20:24:33 +0100 Subject: [PATCH 07/50] TemplateContext factory method instead of initialization method --- .../Helpers/FluidParserHelper.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/FluidParserHelper.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/FluidParserHelper.cs index 6d03db98800..b8723739056 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/FluidParserHelper.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/FluidParserHelper.cs @@ -18,26 +18,26 @@ public FluidParserHelper(ShellSettings shellSettings) public string ParseAndFormat(string template) { - EnsureInitialized(); + var templateContext = GetTemplateContext(); // Use Fluid directly as this is transient and cannot invoke _liquidTemplateManager. var parsedTemplate = _fluidParser.Parse(template); - return parsedTemplate.Render(_templateContext, NullEncoder.Default) + return parsedTemplate.Render(templateContext, NullEncoder.Default) .Replace("\r", string.Empty) .Replace("\n", string.Empty); } - private void EnsureInitialized() + private TemplateContext GetTemplateContext() { - if (_templateContext != null) + if (_templateContext == null) { - return; + var templateOptions = new TemplateOptions(); + _templateContext = new TemplateContext(templateOptions); + templateOptions.MemberAccessStrategy.Register(); + templateOptions.MemberAccessStrategy.Register(); + _templateContext.SetValue("ShellSettings", _shellSettings); } - var templateOptions = new TemplateOptions(); - _templateContext = new TemplateContext(templateOptions); - templateOptions.MemberAccessStrategy.Register(); - templateOptions.MemberAccessStrategy.Register(); - _templateContext.SetValue("ShellSettings", _shellSettings); + return _templateContext; } } From 572412a1f4b05fbd343c30247aa21dd8b14620f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 17 Jan 2024 20:25:10 +0100 Subject: [PATCH 08/50] Code styling --- .../OrchardCore.Media.Azure/Manifest.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Manifest.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Manifest.cs index 7cb4819d1db..87e116e8343 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Manifest.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Manifest.cs @@ -11,10 +11,10 @@ Id = "OrchardCore.Media.Azure.Storage", Name = "Azure Media Storage", Description = "Enables support for storing media files in Microsoft Azure Blob Storage.", - Dependencies = new[] - { + Dependencies = + [ "OrchardCore.Media.Cache" - }, + ], Category = "Hosting" )] @@ -22,9 +22,9 @@ Id = "OrchardCore.Media.Azure.ImageSharpImageCache", Name = "Azure Media ImageSharp Image Cache", Description = "Enables support for storing cached images resized via ImageSharp in Microsoft Azure Blob Storage.", - Dependencies = new[] - { + Dependencies = + [ "OrchardCore.Media" - }, + ], Category = "Hosting" )] From 254848bb39d560f4408fb1faeb3e337a83ee5cd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 17 Jan 2024 20:27:33 +0100 Subject: [PATCH 09/50] Eager option access --- .../Services/AzureBlobStorageCacheOptionsConfiguration.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs index 409c664cd17..4b1b0962e88 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs @@ -7,16 +7,16 @@ namespace OrchardCore.Media.Azure.Services; // configuration. internal class AzureBlobStorageCacheOptionsConfiguration : IConfigureOptions { - private readonly IOptions _options; + private readonly ImageSharpImageCacheOptions _options; public AzureBlobStorageCacheOptionsConfiguration(IOptions options) { - _options = options; + _options = options.Value; } public void Configure(AzureBlobStorageCacheOptions options) { - options.ConnectionString = _options.Value.ConnectionString; - options.ContainerName = _options.Value.ContainerName; + options.ConnectionString = _options.ConnectionString; + options.ContainerName = _options.ContainerName; } } From eabc330611d78eb0114804bbec8250a4b2156e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 17 Jan 2024 20:30:54 +0100 Subject: [PATCH 10/50] Lazy initialization for FluidParserHelper --- .../Services/ImageSharpImageCacheOptionsConfiguration.cs | 6 +++--- .../Services/MediaBlobStorageOptionsConfiguration.cs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs index 8a783fe1d1e..f2467be034c 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs @@ -13,7 +13,6 @@ internal class ImageSharpImageCacheOptionsConfiguration : IConfigureOptions _fluidParserHelper; public ImageSharpImageCacheOptionsConfiguration( IShellConfiguration shellConfiguration, @@ -23,7 +22,6 @@ public ImageSharpImageCacheOptionsConfiguration( _shellConfiguration = shellConfiguration; _shellSettings = shellSettings; _logger = logger; - _fluidParserHelper = new(shellSettings); } public void Configure(ImageSharpImageCacheOptions options) @@ -37,8 +35,10 @@ public void Configure(ImageSharpImageCacheOptions options) try { + var fluidParserHelper = new FluidParserHelper(_shellSettings); + // Container name must be lowercase. - options.ContainerName = _fluidParserHelper.ParseAndFormat(options.ContainerName).ToLower(); + options.ContainerName = fluidParserHelper.ParseAndFormat(options.ContainerName).ToLower(); } catch (Exception ex) { diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs index 858557fb31c..6f367e6aff9 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs @@ -13,7 +13,6 @@ internal class MediaBlobStorageOptionsConfiguration : IConfigureOptions _fluidParserHelper; public MediaBlobStorageOptionsConfiguration( IShellConfiguration shellConfiguration, @@ -24,7 +23,6 @@ ILogger logger _shellConfiguration = shellConfiguration; _shellSettings = shellSettings; _logger = logger; - _fluidParserHelper = new(shellSettings); } public void Configure(MediaBlobStorageOptions options) @@ -37,10 +35,12 @@ public void Configure(MediaBlobStorageOptions options) options.CreateContainer = section.GetValue(nameof(options.CreateContainer), true); options.RemoveContainer = section.GetValue(nameof(options.RemoveContainer), false); + var fluidParserHelper = new FluidParserHelper(_shellSettings); + try { // Container name must be lowercase. - options.ContainerName = _fluidParserHelper.ParseAndFormat(options.ContainerName).ToLower(); + options.ContainerName = fluidParserHelper.ParseAndFormat(options.ContainerName).ToLower(); } catch (Exception e) { @@ -50,7 +50,7 @@ public void Configure(MediaBlobStorageOptions options) try { - options.BasePath = _fluidParserHelper.ParseAndFormat(options.BasePath); + options.BasePath = fluidParserHelper.ParseAndFormat(options.BasePath); } catch (Exception e) { From 60f16e3b1622a81c70e3a8be0a579b005d856261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 17 Jan 2024 20:42:55 +0100 Subject: [PATCH 11/50] Simplified option binding --- .../OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs | 2 +- .../OrchardCore.Media.Azure/MediaBlobStorageOptions.cs | 2 +- .../Services/ImageSharpImageCacheOptionsConfiguration.cs | 6 +----- .../Services/MediaBlobStorageOptionsConfiguration.cs | 7 +------ .../BlobStorageOptions.cs | 2 +- 5 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs index 1282db0e998..c9a2d51fdb9 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs @@ -15,7 +15,7 @@ public class ImageSharpImageCacheOptions /// /// Create blob container on startup if it does not exist. /// - public bool CreateContainer { get; set; } + public bool CreateContainer { get; set; } = true; /// /// Remove blob container on tenant removal if it exists. diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptions.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptions.cs index 46e1e1945a9..05ed2e95893 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptions.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptions.cs @@ -7,7 +7,7 @@ public class MediaBlobStorageOptions : BlobStorageOptions /// /// Create blob container on startup if it does not exist. /// - public bool CreateContainer { get; set; } + public bool CreateContainer { get; set; } = true; /// /// Remove blob container on tenant removal if it exists. diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs index f2467be034c..a2258e3be77 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs @@ -27,11 +27,7 @@ public ImageSharpImageCacheOptionsConfiguration( public void Configure(ImageSharpImageCacheOptions options) { var section = _shellConfiguration.GetSection("OrchardCore_Media_Azure_ImageSharp_Cache"); - - options.ContainerName = section.GetValue(nameof(options.ContainerName), string.Empty); - options.ConnectionString = section.GetValue(nameof(options.ConnectionString), string.Empty); - options.CreateContainer = section.GetValue(nameof(options.CreateContainer), true); - options.RemoveContainer = section.GetValue(nameof(options.RemoveContainer), false); + section.Bind(options); try { diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs index 6f367e6aff9..6c8c978f7bc 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs @@ -28,12 +28,7 @@ ILogger logger public void Configure(MediaBlobStorageOptions options) { var section = _shellConfiguration.GetSection("OrchardCore_Media_Azure"); - - options.BasePath = section.GetValue(nameof(options.BasePath), string.Empty); - options.ContainerName = section.GetValue(nameof(options.ContainerName), string.Empty); - options.ConnectionString = section.GetValue(nameof(options.ConnectionString), string.Empty); - options.CreateContainer = section.GetValue(nameof(options.CreateContainer), true); - options.RemoveContainer = section.GetValue(nameof(options.RemoveContainer), false); + section.Bind(options); var fluidParserHelper = new FluidParserHelper(_shellSettings); diff --git a/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs index 24130b7f512..b0b62776746 100644 --- a/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs +++ b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs @@ -15,6 +15,6 @@ public abstract class BlobStorageOptions /// /// The base directory path to use inside the container for this stores contents. /// - public string BasePath { get; set; } + public string BasePath { get; set; } = ""; } } From d7fb2ec800cd6dff8702e7730796ea6281008c6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 17 Jan 2024 20:46:56 +0100 Subject: [PATCH 12/50] Using ToLowerInvariant() to generate the container names instead of the culture-sensitive version --- .../Services/ImageSharpImageCacheOptionsConfiguration.cs | 2 +- .../Services/MediaBlobStorageOptionsConfiguration.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs index a2258e3be77..9a99fcb19f8 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs @@ -34,7 +34,7 @@ public void Configure(ImageSharpImageCacheOptions options) var fluidParserHelper = new FluidParserHelper(_shellSettings); // Container name must be lowercase. - options.ContainerName = fluidParserHelper.ParseAndFormat(options.ContainerName).ToLower(); + options.ContainerName = fluidParserHelper.ParseAndFormat(options.ContainerName).ToLowerInvariant(); } catch (Exception ex) { diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs index 6c8c978f7bc..38f5bac5008 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs @@ -35,7 +35,7 @@ public void Configure(MediaBlobStorageOptions options) try { // Container name must be lowercase. - options.ContainerName = fluidParserHelper.ParseAndFormat(options.ContainerName).ToLower(); + options.ContainerName = fluidParserHelper.ParseAndFormat(options.ContainerName).ToLowerInvariant(); } catch (Exception e) { From 73aa34dfe983fac4210e63c310b6048a39efd444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 17 Jan 2024 20:57:17 +0100 Subject: [PATCH 13/50] Factoring out option validation check to method --- .../ImageSharpImageCacheOptions.cs | 9 +++++++++ .../Services/ImageSharpImageCacheTenantEvents.cs | 6 ++---- .../Services/MediaBlobContainerTenantEvents.cs | 6 ++---- .../BlobStorageOptions.cs | 9 +++++++++ 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs index c9a2d51fdb9..defb3ec6750 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs @@ -21,4 +21,13 @@ public class ImageSharpImageCacheOptions /// Remove blob container on tenant removal if it exists. /// public bool RemoveContainer { get; set; } + + /// + /// Returns a value indicating whether the basic state of the configuration is valid. + /// + /// + public bool IsValid() + { + return !string.IsNullOrEmpty(ConnectionString) && !string.IsNullOrEmpty(ContainerName); + } } diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheTenantEvents.cs index b6da2fa41a4..a6b4752f8ed 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheTenantEvents.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheTenantEvents.cs @@ -35,8 +35,7 @@ public override async Task ActivatingAsync() { // Only create container if options are valid. if (_shellSettings.IsUninitialized() || - string.IsNullOrEmpty(_options.ConnectionString) || - string.IsNullOrEmpty(_options.ContainerName) || + !_options.IsValid() || !_options.CreateContainer) { return; @@ -61,8 +60,7 @@ public override async Task RemovingAsync(ShellRemovingContext context) { // Only remove container if options are valid. if (!_options.RemoveContainer || - string.IsNullOrEmpty(_options.ConnectionString) || - string.IsNullOrEmpty(_options.ContainerName)) + !_options.IsValid()) { return; } diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs index 5b7e1810b02..075c3f58f2b 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs @@ -35,8 +35,7 @@ public override async Task ActivatingAsync() { // Only create container if options are valid. if (_shellSettings.IsUninitialized() || - string.IsNullOrEmpty(_options.ConnectionString) || - string.IsNullOrEmpty(_options.ContainerName) || + !_options.IsValid() || !_options.CreateContainer ) { @@ -62,8 +61,7 @@ public override async Task RemovingAsync(ShellRemovingContext context) { // Only remove container if options are valid. if (!_options.RemoveContainer || - string.IsNullOrEmpty(_options.ConnectionString) || - string.IsNullOrEmpty(_options.ContainerName)) + !_options.IsValid()) { return; } diff --git a/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs index b0b62776746..0458d8ba4d3 100644 --- a/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs +++ b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs @@ -16,5 +16,14 @@ public abstract class BlobStorageOptions /// The base directory path to use inside the container for this stores contents. /// public string BasePath { get; set; } = ""; + + /// + /// Returns a value indicating whether the basic state of the configuration is valid. + /// + /// + public virtual bool IsValid() + { + return !string.IsNullOrEmpty(ConnectionString) && !string.IsNullOrEmpty(ContainerName); + } } } From 6b05f4e867905513051cc076cd3704b15767f8fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 17 Jan 2024 21:04:11 +0100 Subject: [PATCH 14/50] File-scoped namespaces --- .../MediaBlobStorageOptions.cs | 23 ++-- .../MediaBlobContainerTenantEvents.cs | 115 +++++++++--------- .../MediaBlobStorageOptionsConfiguration.cs | 77 ++++++------ 3 files changed, 106 insertions(+), 109 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptions.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptions.cs index 05ed2e95893..2e1aa3a2597 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptions.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptions.cs @@ -1,17 +1,16 @@ using OrchardCore.FileStorage.AzureBlob; -namespace OrchardCore.Media.Azure +namespace OrchardCore.Media.Azure; + +public class MediaBlobStorageOptions : BlobStorageOptions { - public class MediaBlobStorageOptions : BlobStorageOptions - { - /// - /// Create blob container on startup if it does not exist. - /// - public bool CreateContainer { get; set; } = true; + /// + /// Create blob container on startup if it does not exist. + /// + public bool CreateContainer { get; set; } = true; - /// - /// Remove blob container on tenant removal if it exists. - /// - public bool RemoveContainer { get; set; } - } + /// + /// Remove blob container on tenant removal if it exists. + /// + public bool RemoveContainer { get; set; } } diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs index 075c3f58f2b..296176135da 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs @@ -9,80 +9,79 @@ using OrchardCore.Environment.Shell.Removing; using OrchardCore.Modules; -namespace OrchardCore.Media.Azure.Services +namespace OrchardCore.Media.Azure.Services; + +public class MediaBlobContainerTenantEvents : ModularTenantEvents { - public class MediaBlobContainerTenantEvents : ModularTenantEvents + private readonly MediaBlobStorageOptions _options; + private readonly ShellSettings _shellSettings; + protected readonly IStringLocalizer S; + private readonly ILogger _logger; + + public MediaBlobContainerTenantEvents( + IOptions options, + ShellSettings shellSettings, + IStringLocalizer localizer, + ILogger logger + ) { - private readonly MediaBlobStorageOptions _options; - private readonly ShellSettings _shellSettings; - protected readonly IStringLocalizer S; - private readonly ILogger _logger; + _options = options.Value; + _shellSettings = shellSettings; + S = localizer; + _logger = logger; + } - public MediaBlobContainerTenantEvents( - IOptions options, - ShellSettings shellSettings, - IStringLocalizer localizer, - ILogger logger + public override async Task ActivatingAsync() + { + // Only create container if options are valid. + if (_shellSettings.IsUninitialized() || + !_options.IsValid() || + !_options.CreateContainer ) { - _options = options.Value; - _shellSettings = shellSettings; - S = localizer; - _logger = logger; + return; } - public override async Task ActivatingAsync() - { - // Only create container if options are valid. - if (_shellSettings.IsUninitialized() || - !_options.IsValid() || - !_options.CreateContainer - ) - { - return; - } + _logger.LogDebug("Testing Azure Media Storage container {ContainerName} existence", _options.ContainerName); - _logger.LogDebug("Testing Azure Media Storage container {ContainerName} existence", _options.ContainerName); - - try - { - var blobContainer = new BlobContainerClient(_options.ConnectionString, _options.ContainerName); - var response = await blobContainer.CreateIfNotExistsAsync(PublicAccessType.None); + try + { + var blobContainer = new BlobContainerClient(_options.ConnectionString, _options.ContainerName); + var response = await blobContainer.CreateIfNotExistsAsync(PublicAccessType.None); - _logger.LogDebug("Azure Media Storage container {ContainerName} created.", _options.ContainerName); - } - catch (RequestFailedException ex) - { - _logger.LogError(ex, "Unable to create Azure Media Storage Container."); - } + _logger.LogDebug("Azure Media Storage container {ContainerName} created.", _options.ContainerName); } + catch (RequestFailedException ex) + { + _logger.LogError(ex, "Unable to create Azure Media Storage Container."); + } + } - public override async Task RemovingAsync(ShellRemovingContext context) + public override async Task RemovingAsync(ShellRemovingContext context) + { + // Only remove container if options are valid. + if (!_options.RemoveContainer || + !_options.IsValid()) { - // Only remove container if options are valid. - if (!_options.RemoveContainer || - !_options.IsValid()) - { - return; - } + return; + } - try - { - var blobContainer = new BlobContainerClient(_options.ConnectionString, _options.ContainerName); + try + { + var blobContainer = new BlobContainerClient(_options.ConnectionString, _options.ContainerName); - var response = await blobContainer.DeleteIfExistsAsync(); - if (!response.Value) - { - _logger.LogError("Unable to remove the Azure Media Storage Container {ContainerName}.", _options.ContainerName); - context.ErrorMessage = S["Unable to remove the Azure Media Storage Container '{0}'.", _options.ContainerName]; - } - } - catch (RequestFailedException ex) + var response = await blobContainer.DeleteIfExistsAsync(); + if (!response.Value) { - _logger.LogError(ex, "Failed to remove the Azure Media Storage Container {ContainerName}.", _options.ContainerName); - context.ErrorMessage = S["Failed to remove the Azure Media Storage Container '{0}'.", _options.ContainerName]; - context.Error = ex; + _logger.LogError("Unable to remove the Azure Media Storage Container {ContainerName}.", _options.ContainerName); + context.ErrorMessage = S["Unable to remove the Azure Media Storage Container '{0}'.", _options.ContainerName]; } } + catch (RequestFailedException ex) + { + _logger.LogError(ex, "Failed to remove the Azure Media Storage Container {ContainerName}.", _options.ContainerName); + context.ErrorMessage = S["Failed to remove the Azure Media Storage Container '{0}'.", _options.ContainerName]; + context.Error = ex; + } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs index 38f5bac5008..8a941262df9 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs @@ -6,52 +6,51 @@ using OrchardCore.Environment.Shell.Configuration; using OrchardCore.Media.Azure.Helpers; -namespace OrchardCore.Media.Azure.Services +namespace OrchardCore.Media.Azure.Services; + +internal class MediaBlobStorageOptionsConfiguration : IConfigureOptions { - internal class MediaBlobStorageOptionsConfiguration : IConfigureOptions + private readonly IShellConfiguration _shellConfiguration; + private readonly ShellSettings _shellSettings; + private readonly ILogger _logger; + + public MediaBlobStorageOptionsConfiguration( + IShellConfiguration shellConfiguration, + ShellSettings shellSettings, + ILogger logger + ) { - private readonly IShellConfiguration _shellConfiguration; - private readonly ShellSettings _shellSettings; - private readonly ILogger _logger; + _shellConfiguration = shellConfiguration; + _shellSettings = shellSettings; + _logger = logger; + } - public MediaBlobStorageOptionsConfiguration( - IShellConfiguration shellConfiguration, - ShellSettings shellSettings, - ILogger logger - ) + public void Configure(MediaBlobStorageOptions options) + { + var section = _shellConfiguration.GetSection("OrchardCore_Media_Azure"); + section.Bind(options); + + var fluidParserHelper = new FluidParserHelper(_shellSettings); + + try { - _shellConfiguration = shellConfiguration; - _shellSettings = shellSettings; - _logger = logger; + // Container name must be lowercase. + options.ContainerName = fluidParserHelper.ParseAndFormat(options.ContainerName).ToLowerInvariant(); } - - public void Configure(MediaBlobStorageOptions options) + catch (Exception e) { - var section = _shellConfiguration.GetSection("OrchardCore_Media_Azure"); - section.Bind(options); - - var fluidParserHelper = new FluidParserHelper(_shellSettings); - - try - { - // Container name must be lowercase. - options.ContainerName = fluidParserHelper.ParseAndFormat(options.ContainerName).ToLowerInvariant(); - } - catch (Exception e) - { - _logger.LogCritical(e, "Unable to parse Azure Media Storage container name."); - throw; - } + _logger.LogCritical(e, "Unable to parse Azure Media Storage container name."); + throw; + } - try - { - options.BasePath = fluidParserHelper.ParseAndFormat(options.BasePath); - } - catch (Exception e) - { - _logger.LogCritical(e, "Unable to parse Azure Media Storage base path."); - throw; - } + try + { + options.BasePath = fluidParserHelper.ParseAndFormat(options.BasePath); + } + catch (Exception e) + { + _logger.LogCritical(e, "Unable to parse Azure Media Storage base path."); + throw; } } } From 57b7c84e126aa76057659e6b8dac3aa77241e932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 17 Jan 2024 21:11:15 +0100 Subject: [PATCH 15/50] Using method lookups for configuration values instead of the dictionary-like one --- .../OrchardCore.Media.Azure/Startup.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs index 90be1df56ce..602e7780491 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; @@ -60,8 +61,9 @@ public override void ConfigureServices(IServiceCollection services) services.AddTransient, MediaBlobStorageOptionsConfiguration>(); // Only replace default implementation if options are valid. - var connectionString = _configuration[$"OrchardCore_Media_Azure:{nameof(MediaBlobStorageOptions.ConnectionString)}"]; - var containerName = _configuration[$"OrchardCore_Media_Azure:{nameof(MediaBlobStorageOptions.ContainerName)}"]; + var section = _configuration.GetSection("OrchardCore_Media_Azure"); + var connectionString = section.GetValue(nameof(MediaBlobStorageOptions.ConnectionString)); + var containerName = section.GetValue(nameof(MediaBlobStorageOptions.ContainerName)); if (CheckOptions(connectionString, containerName, _logger)) { @@ -178,8 +180,9 @@ public override void ConfigureServices(IServiceCollection services) services.AddTransient, AzureBlobStorageCacheOptionsConfiguration>(); // Only replace default implementation if options are valid. - var connectionString = _configuration[$"OrchardCore_Media_Azure_ImageSharp_Cache:{nameof(ImageSharpImageCacheOptions.ConnectionString)}"]; - var containerName = _configuration[$"OrchardCore_Media_Azure_ImageSharp_Cache:{nameof(ImageSharpImageCacheOptions.ContainerName)}"]; + var section = _configuration.GetSection("OrchardCore_Media_Azure_ImageSharp_Cache"); + var connectionString = section.GetValue(nameof(MediaBlobStorageOptions.ConnectionString)); + var containerName = section.GetValue(nameof(MediaBlobStorageOptions.ContainerName)); if (!CheckOptions(connectionString, containerName)) { From bea7554353de74719fe028b13db11408a473fb34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Fri, 8 Mar 2024 19:53:22 +0100 Subject: [PATCH 16/50] Adding BasePath support for Azure Blob ImageSharp cache --- src/OrchardCore.Build/Dependencies.props | 4 ++-- src/OrchardCore.Cms.Web/appsettings.json | 1 + .../ImageSharpImageCacheOptions.cs | 5 +++++ .../AzureBlobStorageCacheOptionsConfiguration.cs | 1 + .../ImageSharpImageCacheOptionsConfiguration.cs | 14 ++++++++++++-- .../BlobStorageOptions.cs | 2 +- src/docs/reference/modules/Media.Azure/README.md | 2 ++ 7 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/OrchardCore.Build/Dependencies.props b/src/OrchardCore.Build/Dependencies.props index 8626c59761d..d3ed32691d9 100644 --- a/src/OrchardCore.Build/Dependencies.props +++ b/src/OrchardCore.Build/Dependencies.props @@ -62,8 +62,8 @@ - - + + diff --git a/src/OrchardCore.Cms.Web/appsettings.json b/src/OrchardCore.Cms.Web/appsettings.json index 2b1352ee56b..424ff43a7be 100644 --- a/src/OrchardCore.Cms.Web/appsettings.json +++ b/src/OrchardCore.Cms.Web/appsettings.json @@ -78,6 +78,7 @@ //"OrchardCore_Media_Azure_ImageSharp_Cache": { // "ConnectionString": "", // Set to your Azure Storage account connection string. // "ContainerName": "somecontainer", // Set to the Azure Blob container name. Templatable, refer docs. + // "BasePath": "some/base/path", // Optionally, set to a path to store media in a subdirectory inside your container. Templatable, refer docs. // "CreateContainer": true, // Activates an event to create the container if it does not already exist. // "RemoveContainer": true // Whether the 'Container' is deleted if the tenant is removed, false by default. //}, diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs index defb3ec6750..3b867335a8e 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs @@ -17,6 +17,11 @@ public class ImageSharpImageCacheOptions /// public bool CreateContainer { get; set; } = true; + /// + /// The base directory path to use inside the container for this store's content. + /// + public string BasePath { get; set; } = ""; + /// /// Remove blob container on tenant removal if it exists. /// diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs index 4b1b0962e88..106802b107a 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs @@ -18,5 +18,6 @@ public void Configure(AzureBlobStorageCacheOptions options) { options.ConnectionString = _options.ConnectionString; options.ContainerName = _options.ContainerName; + options.CacheFolder = _options.BasePath; } } diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs index 9a99fcb19f8..062cdca71ea 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs @@ -29,10 +29,10 @@ public void Configure(ImageSharpImageCacheOptions options) var section = _shellConfiguration.GetSection("OrchardCore_Media_Azure_ImageSharp_Cache"); section.Bind(options); + var fluidParserHelper = new FluidParserHelper(_shellSettings); + try { - var fluidParserHelper = new FluidParserHelper(_shellSettings); - // Container name must be lowercase. options.ContainerName = fluidParserHelper.ParseAndFormat(options.ContainerName).ToLowerInvariant(); } @@ -41,5 +41,15 @@ public void Configure(ImageSharpImageCacheOptions options) _logger.LogCritical(ex, "Unable to parse Azure Media ImageSharp Image Cache container name."); throw; } + + try + { + options.BasePath = fluidParserHelper.ParseAndFormat(options.BasePath); + } + catch (Exception e) + { + _logger.LogCritical(e, "Unable to parse Azure Media ImageSharp Image Cache base path."); + throw; + } } } diff --git a/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs index 0458d8ba4d3..094216567d3 100644 --- a/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs +++ b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs @@ -13,7 +13,7 @@ public abstract class BlobStorageOptions public string ContainerName { get; set; } /// - /// The base directory path to use inside the container for this stores contents. + /// The base directory path to use inside the container for this store's content. /// public string BasePath { get; set; } = ""; diff --git a/src/docs/reference/modules/Media.Azure/README.md b/src/docs/reference/modules/Media.Azure/README.md index f4e93806fda..4b79ebd38ee 100644 --- a/src/docs/reference/modules/Media.Azure/README.md +++ b/src/docs/reference/modules/Media.Azure/README.md @@ -142,6 +142,8 @@ The following configuration values are used by default and can be customized: "ConnectionString": "", // Set to the Azure Blob container name. A container name must be a valid DNS name and conform to Azure container naming rules eg. lowercase only. "ContainerName": "somecontainer", + // Optionally, set to a path to store media in a subdirectory inside your container. + "BasePath": "some/base/path", // Activates an event to create the container if it does not already exist. "CreateContainer": true, // Whether the 'Container' is deleted if the tenant is removed, false by default. From 2f1378c4da6390bcc0ac3b05c7c9ee88dd05aace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Tue, 19 Mar 2024 22:01:25 +0100 Subject: [PATCH 17/50] "Blob" typos Co-authored-by: Mike Alhayek --- .../OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs | 4 ++-- .../OrchardCore.Media.Azure/MediaBlobStorageOptions.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs index 3b867335a8e..bee72c20bdd 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs @@ -13,7 +13,7 @@ public class ImageSharpImageCacheOptions public string ContainerName { get; set; } /// - /// Create blob container on startup if it does not exist. + /// Create Blob container on startup if one does not exist. /// public bool CreateContainer { get; set; } = true; @@ -23,7 +23,7 @@ public class ImageSharpImageCacheOptions public string BasePath { get; set; } = ""; /// - /// Remove blob container on tenant removal if it exists. + /// Remove Blob container on tenant removal if it exists. /// public bool RemoveContainer { get; set; } diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptions.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptions.cs index 2e1aa3a2597..06c6f595986 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptions.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptions.cs @@ -5,12 +5,12 @@ namespace OrchardCore.Media.Azure; public class MediaBlobStorageOptions : BlobStorageOptions { /// - /// Create blob container on startup if it does not exist. + /// Create a Blob container on startup if one does not exist. /// public bool CreateContainer { get; set; } = true; /// - /// Remove blob container on tenant removal if it exists. + /// Remove Blob container on tenant removal if it exists. /// public bool RemoveContainer { get; set; } } From 9f77673419b263648d9feac60abe9fb4886753d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Tue, 19 Mar 2024 22:03:27 +0100 Subject: [PATCH 18/50] Removing empty docs --- .../OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs | 1 - .../OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs index bee72c20bdd..1c09b90cfb5 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs @@ -30,7 +30,6 @@ public class ImageSharpImageCacheOptions /// /// Returns a value indicating whether the basic state of the configuration is valid. /// - /// public bool IsValid() { return !string.IsNullOrEmpty(ConnectionString) && !string.IsNullOrEmpty(ContainerName); diff --git a/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs index 094216567d3..bc577d2de04 100644 --- a/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs +++ b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs @@ -20,7 +20,6 @@ public abstract class BlobStorageOptions /// /// Returns a value indicating whether the basic state of the configuration is valid. /// - /// public virtual bool IsValid() { return !string.IsNullOrEmpty(ConnectionString) && !string.IsNullOrEmpty(ContainerName); From d33b463c5fea670dbb690643e0aeb0c80df212cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Tue, 19 Mar 2024 22:09:43 +0100 Subject: [PATCH 19/50] Code styling --- .../Services/ImageSharpImageCacheTenantEvents.cs | 7 ++----- .../Services/MediaBlobContainerTenantEvents.cs | 11 ++++------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheTenantEvents.cs index a6b4752f8ed..a32ab58c8f6 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheTenantEvents.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheTenantEvents.cs @@ -34,9 +34,7 @@ public ImageSharpImageCacheTenantEvents( public override async Task ActivatingAsync() { // Only create container if options are valid. - if (_shellSettings.IsUninitialized() || - !_options.IsValid() || - !_options.CreateContainer) + if (_shellSettings.IsUninitialized() || !_options.IsValid() || !_options.CreateContainer) { return; } @@ -59,8 +57,7 @@ public override async Task ActivatingAsync() public override async Task RemovingAsync(ShellRemovingContext context) { // Only remove container if options are valid. - if (!_options.RemoveContainer || - !_options.IsValid()) + if (!_options.RemoveContainer || !_options.IsValid()) { return; } diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs index 296176135da..20bd861f532 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs @@ -15,9 +15,10 @@ public class MediaBlobContainerTenantEvents : ModularTenantEvents { private readonly MediaBlobStorageOptions _options; private readonly ShellSettings _shellSettings; - protected readonly IStringLocalizer S; private readonly ILogger _logger; + protected readonly IStringLocalizer S; + public MediaBlobContainerTenantEvents( IOptions options, ShellSettings shellSettings, @@ -34,10 +35,7 @@ ILogger logger public override async Task ActivatingAsync() { // Only create container if options are valid. - if (_shellSettings.IsUninitialized() || - !_options.IsValid() || - !_options.CreateContainer - ) + if (_shellSettings.IsUninitialized() || !_options.IsValid() || !_options.CreateContainer) { return; } @@ -60,8 +58,7 @@ public override async Task ActivatingAsync() public override async Task RemovingAsync(ShellRemovingContext context) { // Only remove container if options are valid. - if (!_options.RemoveContainer || - !_options.IsValid()) + if (!_options.RemoveContainer || !_options.IsValid()) { return; } From 3042c8d90c05d9f0a311077ccadc8e36a6f46201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Tue, 19 Mar 2024 22:14:25 +0100 Subject: [PATCH 20/50] Shorter config binding Co-authored-by: Mike Alhayek --- .../Services/MediaBlobStorageOptionsConfiguration.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs index 8a941262df9..fae09c72032 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs @@ -27,8 +27,7 @@ ILogger logger public void Configure(MediaBlobStorageOptions options) { - var section = _shellConfiguration.GetSection("OrchardCore_Media_Azure"); - section.Bind(options); + _shellConfiguration.GetSection("OrchardCore_Media_Azure").Bind(options); var fluidParserHelper = new FluidParserHelper(_shellSettings); From 171b686accf1e8cdb977ef9b2cfccf64e92f002b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Tue, 19 Mar 2024 22:14:36 +0100 Subject: [PATCH 21/50] Formatting Co-authored-by: Mike Alhayek --- src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs index efa386c7231..434ff919dd2 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs @@ -148,7 +148,9 @@ public class ImageSharpAzureBlobCacheStartup : Modules.StartupBase private readonly IShellConfiguration _configuration; private readonly ILogger _logger; - public ImageSharpAzureBlobCacheStartup(IShellConfiguration configuration, ILogger logger) + public ImageSharpAzureBlobCacheStartup( + IShellConfiguration configuration, + ILogger logger) { _configuration = configuration; _logger = logger; From 8609f532d4f6593763987c8b1331c14fa617907e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Tue, 19 Mar 2024 22:21:43 +0100 Subject: [PATCH 22/50] Removing the "Microsoft" prefix from some Azure wording --- src/OrchardCore.Modules/OrchardCore.Media.Azure/Manifest.cs | 2 +- src/docs/reference/modules/Media.Azure/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Manifest.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Manifest.cs index 87e116e8343..696ac3c848c 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Manifest.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Manifest.cs @@ -1,7 +1,7 @@ using OrchardCore.Modules.Manifest; [assembly: Module( - Name = "Microsoft Azure Media", + Name = "Azure Media", Author = ManifestConstants.OrchardCoreTeam, Website = ManifestConstants.OrchardCoreWebsite, Version = ManifestConstants.OrchardCoreVersion diff --git a/src/docs/reference/modules/Media.Azure/README.md b/src/docs/reference/modules/Media.Azure/README.md index 4b79ebd38ee..cab803b4c35 100644 --- a/src/docs/reference/modules/Media.Azure/README.md +++ b/src/docs/reference/modules/Media.Azure/README.md @@ -1,6 +1,6 @@ -# Microsoft Azure Media (`OrchardCore.Media.Azure`) +# Azure Media (`OrchardCore.Media.Azure`) -The Microsoft Azure Media module enables support for storing assets in Microsoft Azure Blob Storage. +The Azure Media module enables support for storing assets in Microsoft Azure Blob Storage. ## Azure Media Storage (`OrchardCore.Media.Azure.Storage`) From cf3abea3f96204ea4eb33bcb269bba50d1eaef32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Sat, 23 Mar 2024 17:51:52 +0100 Subject: [PATCH 23/50] Docs wording Co-authored-by: Mike Alhayek --- src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs index 434ff919dd2..116c50e0f4c 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs @@ -156,7 +156,7 @@ public ImageSharpAzureBlobCacheStartup( _logger = logger; } - // This is needed to be greater than OrchardCore.Media's 0 to replace the IImageCache implementation registered + // The order should exceed that of the 'OrchardCore.Media' module to substitute the default implementation of 'IImageCache'. // there. public override int Order => 5; From bb7f327ac59244e53201fd9f2b34bdf1d9bf9cfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Sun, 24 Mar 2024 23:56:11 +0100 Subject: [PATCH 24/50] Moving S3 services to a namespace in anticipation of new services --- .../{ => Services}/AwsStorageOptionsConfiguration.cs | 2 +- .../{ => Services}/MediaS3BucketTenantEvents.cs | 2 +- src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) rename src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/{ => Services}/AwsStorageOptionsConfiguration.cs (98%) rename src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/{ => Services}/MediaS3BucketTenantEvents.cs (98%) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs similarity index 98% rename from src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsConfiguration.cs rename to src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs index 7c3229a4b56..0f699f92f51 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs @@ -6,7 +6,7 @@ using OrchardCore.Environment.Shell.Configuration; using OrchardCore.FileStorage.AmazonS3; -namespace OrchardCore.Media.AmazonS3; +namespace OrchardCore.Media.AmazonS3.Services; public class AwsStorageOptionsConfiguration : IConfigureOptions { diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/MediaS3BucketTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/MediaS3BucketTenantEvents.cs similarity index 98% rename from src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/MediaS3BucketTenantEvents.cs rename to src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/MediaS3BucketTenantEvents.cs index bed1647e533..15fe054ab44 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/MediaS3BucketTenantEvents.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/MediaS3BucketTenantEvents.cs @@ -10,7 +10,7 @@ using OrchardCore.FileStorage.AmazonS3; using OrchardCore.Modules; -namespace OrchardCore.Media.AmazonS3; +namespace OrchardCore.Media.AmazonS3.Services; public class MediaS3BucketTenantEvents : ModularTenantEvents { diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs index 3a30f3a99de..2eb4edab121 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs @@ -13,6 +13,7 @@ using OrchardCore.Environment.Shell.Configuration; using OrchardCore.FileStorage; using OrchardCore.FileStorage.AmazonS3; +using OrchardCore.Media.AmazonS3.Services; using OrchardCore.Media.Core; using OrchardCore.Media.Core.Events; using OrchardCore.Media.Events; From 548a8df45ccc8551fc9d7618368073696e24e543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Mon, 25 Mar 2024 00:03:36 +0100 Subject: [PATCH 25/50] Code styling --- src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs index 116c50e0f4c..290970b4817 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs @@ -149,8 +149,8 @@ public class ImageSharpAzureBlobCacheStartup : Modules.StartupBase private readonly ILogger _logger; public ImageSharpAzureBlobCacheStartup( - IShellConfiguration configuration, - ILogger logger) + IShellConfiguration configuration, + ILogger logger) { _configuration = configuration; _logger = logger; From e47985ce7449389104b6a0354abc1395b6d420a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Mon, 25 Mar 2024 00:29:50 +0100 Subject: [PATCH 26/50] Docs link --- src/OrchardCore.Cms.Web/appsettings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/OrchardCore.Cms.Web/appsettings.json b/src/OrchardCore.Cms.Web/appsettings.json index 424ff43a7be..8f3dd7d1105 100644 --- a/src/OrchardCore.Cms.Web/appsettings.json +++ b/src/OrchardCore.Cms.Web/appsettings.json @@ -75,6 +75,7 @@ // "CreateContainer": true, // Activates an event to create the container if it does not already exist. // "RemoveContainer": true // Whether the 'Container' is deleted if the tenant is removed, false by default. //}, + // See http://127.0.0.1:8000/docs/reference/modules/Media.Azure/#configuration_1 //"OrchardCore_Media_Azure_ImageSharp_Cache": { // "ConnectionString": "", // Set to your Azure Storage account connection string. // "ContainerName": "somecontainer", // Set to the Azure Blob container name. Templatable, refer docs. From 502cd27ef8306718538b5298a77d4dbc0bbd04f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Sat, 6 Apr 2024 22:30:48 +0200 Subject: [PATCH 27/50] Better name for Azure Blob services --- ...Options.cs => ImageSharpBlobImageCacheOptions.cs} | 2 +- .../AzureBlobStorageCacheOptionsConfiguration.cs | 4 ++-- ... BlobImageSharpImageCacheOptionsConfiguration.cs} | 10 +++++----- ...ts.cs => BlobImageSharpImageCacheTenantEvents.cs} | 12 ++++++------ 4 files changed, 14 insertions(+), 14 deletions(-) rename src/OrchardCore.Modules/OrchardCore.Media.Azure/{ImageSharpImageCacheOptions.cs => ImageSharpBlobImageCacheOptions.cs} (95%) rename src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/{ImageSharpImageCacheOptionsConfiguration.cs => BlobImageSharpImageCacheOptionsConfiguration.cs} (81%) rename src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/{ImageSharpImageCacheTenantEvents.cs => BlobImageSharpImageCacheTenantEvents.cs} (87%) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpBlobImageCacheOptions.cs similarity index 95% rename from src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs rename to src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpBlobImageCacheOptions.cs index 1c09b90cfb5..74c266ac544 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpImageCacheOptions.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpBlobImageCacheOptions.cs @@ -1,6 +1,6 @@ namespace OrchardCore.Media.Azure; -public class ImageSharpImageCacheOptions +public class ImageSharpBlobImageCacheOptions { /// /// The Azure Blob connection string. diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs index 106802b107a..7b43eda21ad 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs @@ -7,9 +7,9 @@ namespace OrchardCore.Media.Azure.Services; // configuration. internal class AzureBlobStorageCacheOptionsConfiguration : IConfigureOptions { - private readonly ImageSharpImageCacheOptions _options; + private readonly ImageSharpBlobImageCacheOptions _options; - public AzureBlobStorageCacheOptionsConfiguration(IOptions options) + public AzureBlobStorageCacheOptionsConfiguration(IOptions options) { _options = options.Value; } diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/BlobImageSharpImageCacheOptionsConfiguration.cs similarity index 81% rename from src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs rename to src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/BlobImageSharpImageCacheOptionsConfiguration.cs index 062cdca71ea..66a6e90da3b 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/BlobImageSharpImageCacheOptionsConfiguration.cs @@ -8,28 +8,28 @@ namespace OrchardCore.Media.Azure.Services; -internal class ImageSharpImageCacheOptionsConfiguration : IConfigureOptions +internal class BlobImageSharpImageCacheOptionsConfiguration : IConfigureOptions { private readonly IShellConfiguration _shellConfiguration; private readonly ShellSettings _shellSettings; private readonly ILogger _logger; - public ImageSharpImageCacheOptionsConfiguration( + public BlobImageSharpImageCacheOptionsConfiguration( IShellConfiguration shellConfiguration, ShellSettings shellSettings, - ILogger logger) + ILogger logger) { _shellConfiguration = shellConfiguration; _shellSettings = shellSettings; _logger = logger; } - public void Configure(ImageSharpImageCacheOptions options) + public void Configure(ImageSharpBlobImageCacheOptions options) { var section = _shellConfiguration.GetSection("OrchardCore_Media_Azure_ImageSharp_Cache"); section.Bind(options); - var fluidParserHelper = new FluidParserHelper(_shellSettings); + var fluidParserHelper = new FluidParserHelper(_shellSettings); try { diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/BlobImageSharpImageCacheTenantEvents.cs similarity index 87% rename from src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheTenantEvents.cs rename to src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/BlobImageSharpImageCacheTenantEvents.cs index a32ab58c8f6..01056465d47 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpImageCacheTenantEvents.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/BlobImageSharpImageCacheTenantEvents.cs @@ -11,19 +11,19 @@ namespace OrchardCore.Media.Azure.Services; -internal class ImageSharpImageCacheTenantEvents : ModularTenantEvents +internal class BlobImageSharpImageCacheTenantEvents : ModularTenantEvents { - private readonly ImageSharpImageCacheOptions _options; + private readonly ImageSharpBlobImageCacheOptions _options; private readonly ShellSettings _shellSettings; private readonly ILogger _logger; protected readonly IStringLocalizer S; - public ImageSharpImageCacheTenantEvents( - IOptions options, + public BlobImageSharpImageCacheTenantEvents( + IOptions options, ShellSettings shellSettings, - ILogger logger, - IStringLocalizer localizer) + ILogger logger, + IStringLocalizer localizer) { _options = options.Value; _shellSettings = shellSettings; From 96abf7211b8a1d80427644e22553b384ac7c6828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Sat, 6 Apr 2024 23:04:40 +0200 Subject: [PATCH 28/50] Even better names --- ...cs => ImageSharpBlobImageCacheOptionsConfiguration.cs} | 6 +++--- ...tEvents.cs => ImageSharpBlobImageCacheTenantEvents.cs} | 8 ++++---- .../OrchardCore.Media.Azure/Startup.cs | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) rename src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/{BlobImageSharpImageCacheOptionsConfiguration.cs => ImageSharpBlobImageCacheOptionsConfiguration.cs} (90%) rename src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/{BlobImageSharpImageCacheTenantEvents.cs => ImageSharpBlobImageCacheTenantEvents.cs} (92%) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/BlobImageSharpImageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheOptionsConfiguration.cs similarity index 90% rename from src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/BlobImageSharpImageCacheOptionsConfiguration.cs rename to src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheOptionsConfiguration.cs index 66a6e90da3b..3dc066ccdce 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/BlobImageSharpImageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheOptionsConfiguration.cs @@ -8,16 +8,16 @@ namespace OrchardCore.Media.Azure.Services; -internal class BlobImageSharpImageCacheOptionsConfiguration : IConfigureOptions +internal class ImageSharpBlobImageCacheOptionsConfiguration : IConfigureOptions { private readonly IShellConfiguration _shellConfiguration; private readonly ShellSettings _shellSettings; private readonly ILogger _logger; - public BlobImageSharpImageCacheOptionsConfiguration( + public ImageSharpBlobImageCacheOptionsConfiguration( IShellConfiguration shellConfiguration, ShellSettings shellSettings, - ILogger logger) + ILogger logger) { _shellConfiguration = shellConfiguration; _shellSettings = shellSettings; diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/BlobImageSharpImageCacheTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheTenantEvents.cs similarity index 92% rename from src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/BlobImageSharpImageCacheTenantEvents.cs rename to src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheTenantEvents.cs index 01056465d47..f99780a7a76 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/BlobImageSharpImageCacheTenantEvents.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheTenantEvents.cs @@ -11,7 +11,7 @@ namespace OrchardCore.Media.Azure.Services; -internal class BlobImageSharpImageCacheTenantEvents : ModularTenantEvents +internal class ImageSharpBlobImageCacheTenantEvents : ModularTenantEvents { private readonly ImageSharpBlobImageCacheOptions _options; private readonly ShellSettings _shellSettings; @@ -19,11 +19,11 @@ internal class BlobImageSharpImageCacheTenantEvents : ModularTenantEvents protected readonly IStringLocalizer S; - public BlobImageSharpImageCacheTenantEvents( + public ImageSharpBlobImageCacheTenantEvents( IOptions options, ShellSettings shellSettings, - ILogger logger, - IStringLocalizer localizer) + ILogger logger, + IStringLocalizer localizer) { _options = options.Value; _shellSettings = shellSettings; diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs index 290970b4817..887c4587354 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs @@ -162,7 +162,7 @@ public ImageSharpAzureBlobCacheStartup( public override void ConfigureServices(IServiceCollection services) { - services.AddTransient, ImageSharpImageCacheOptionsConfiguration>(); + services.AddTransient, ImageSharpBlobImageCacheOptionsConfiguration>(); services.AddTransient, AzureBlobStorageCacheOptionsConfiguration>(); // Only replace default implementation if options are valid. @@ -180,7 +180,7 @@ public override void ConfigureServices(IServiceCollection services) // what happens in OrchardCore.Media. Thus, an explicit Replace() is necessary. services.Replace(ServiceDescriptor.Singleton()); - services.AddScoped(); + services.AddScoped(); } private bool CheckOptions(string connectionString, string containerName) From 564fec13784e71c065ae4310d4ebb6f32277bdb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Sun, 7 Apr 2024 03:51:39 +0200 Subject: [PATCH 29/50] Basics of AWS ImageSharp cache --- src/OrchardCore.Cms.Web/appsettings.json | 18 ++- .../AwsStorageOptionsExtension.cs | 12 +- .../OrchardCore.Media.AmazonS3/Constants.cs | 11 +- .../OrchardCore.Media.AmazonS3/Manifest.cs | 13 +- .../OrchardCore.Media.AmazonS3.csproj | 4 + .../AWSS3StorageCacheOptionsConfiguration.cs | 29 ++++ ...mageSharpImageCacheOptionsConfiguration.cs | 80 +++++++++++ .../AwsStorageOptionsConfiguration.cs | 2 +- ...mageSharpS3ImageCacheBucketTenantEvents.cs | 134 ++++++++++++++++++ .../OrchardCore.Media.AmazonS3/Startup.cs | 58 +++++++- .../AwsImageSharpImageCacheOptions.cs | 10 ++ .../AwsStorageOptions.cs | 28 +--- .../AwsStorageOptionsBase.cs | 25 ++++ .../IAwsStorageOptions.cs | 34 +++++ 14 files changed, 417 insertions(+), 41 deletions(-) create mode 100644 src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AWSS3StorageCacheOptionsConfiguration.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/ImageSharpS3ImageCacheBucketTenantEvents.cs create mode 100644 src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsImageSharpImageCacheOptions.cs create mode 100644 src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsStorageOptionsBase.cs create mode 100644 src/OrchardCore/OrchardCore.FileStorage.AmazonS3/IAwsStorageOptions.cs diff --git a/src/OrchardCore.Cms.Web/appsettings.json b/src/OrchardCore.Cms.Web/appsettings.json index 8f3dd7d1105..941d0e693a5 100644 --- a/src/OrchardCore.Cms.Web/appsettings.json +++ b/src/OrchardCore.Cms.Web/appsettings.json @@ -62,10 +62,24 @@ // "SecretKey": "", // "AccessKey": "" // }, - // "BasePath": "/media", + // "BasePath": "/{{ ShellSettings.Name }}", // "CreateBucket": true, // "RemoveBucket": true, // Whether the 'Bucket' is deleted if the tenant is removed, false by default. - // "BucketName": "" + // "BucketName": "media" + //}, + // See https://docs.orchardcore.net/en/latest/docs/reference/modules/Media.AmazonS3/#configuration to configure media storage in Amazon S3 Storage. + //"OrchardCore_Media_AmazonS3_ImageSharp_Cache": { + // "Region": "eu-central-1", + // "Profile": "default", + // "ProfilesLocation": "", + // "Credentials": { + // "SecretKey": "", + // "AccessKey": "" + // }, + // "BasePath": "/{{ ShellSettings.Name }}", + // "CreateBucket": true, + // "RemoveBucket": true, // Whether the 'Bucket' is deleted if the tenant is removed, false by default. + // "BucketName": "imagesharp" //}, // See https://docs.orchardcore.net/en/latest/docs/reference/modules/Media.Azure/#configuration to configure media storage in Azure Blob Storage. //"OrchardCore_Media_Azure": { diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsExtension.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsExtension.cs index 4c45b3d30e2..a6146fc2045 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsExtension.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsExtension.cs @@ -11,7 +11,7 @@ namespace OrchardCore.Media.AmazonS3; public static class AwsStorageOptionsExtension { - public static IEnumerable Validate(this AwsStorageOptions options) + public static IEnumerable Validate(this IAwsStorageOptions options) { if (string.IsNullOrWhiteSpace(options.BucketName)) { @@ -20,16 +20,16 @@ public static IEnumerable Validate(this AwsStorageOptions opti if (options.AwsOptions is not null) { - if (options.AwsOptions.Region is null) + if (options.AwsOptions.Region is null && options.AwsOptions.DefaultClientConfig.ServiceURL is null) { - yield return new ValidationResult(Constants.ValidationMessages.RegionEndpointIsEmpty); + yield return new ValidationResult(Constants.ValidationMessages.RegionAndServiceUrlAreEmpty); } } } - public static AwsStorageOptions BindConfiguration(this AwsStorageOptions options, IShellConfiguration shellConfiguration, ILogger logger) + public static IAwsStorageOptions BindConfiguration(this IAwsStorageOptions options, string configSection, IShellConfiguration shellConfiguration, ILogger logger) { - var section = shellConfiguration.GetSection("OrchardCore_Media_AmazonS3"); + var section = shellConfiguration.GetSection(configSection); if (!section.Exists()) { @@ -44,7 +44,7 @@ public static AwsStorageOptions BindConfiguration(this AwsStorageOptions options try { // Binding AWS Options. - options.AwsOptions = shellConfiguration.GetAWSOptions("OrchardCore_Media_AmazonS3"); + options.AwsOptions = shellConfiguration.GetAWSOptions(configSection); // In case Credentials sections was specified, trying to add BasicAWSCredential to AWSOptions // since by design GetAWSOptions skips Credential section while parsing config. diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Constants.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Constants.cs index 9816984574d..c0a15f093ca 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Constants.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Constants.cs @@ -4,9 +4,8 @@ internal static class Constants { internal static class ValidationMessages { - public const string BucketNameIsEmpty = "BucketName is required attribute for S3 Media"; - - public const string RegionEndpointIsEmpty = "Region is required attribute for S3 Media"; + public const string BucketNameIsEmpty = "BucketName is required attribute for S3 storage."; + public const string RegionAndServiceUrlAreEmpty = "Region or ServiceURL is a required attribute for S3 storage."; } internal static class AwsCredentialParamNames @@ -14,4 +13,10 @@ internal static class AwsCredentialParamNames public const string SecretKey = "SecretKey"; public const string AccessKey = "AccessKey"; } + + internal static class ConfigSections + { + public const string AmazonS3 = "OrchardCore_Media_AmazonS3"; + public const string AmazonS3ImageSharpCache = "OrchardCore_Media_AmazonS3_ImageSharp_Cache"; + } } diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Manifest.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Manifest.cs index 8b80f659975..fcf327df928 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Manifest.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Manifest.cs @@ -10,10 +10,21 @@ [assembly: Feature( Id = "OrchardCore.Media.AmazonS3", Name = "Amazon Media Storage", - Description = "Enables support for storing media files in Amazon S3 Bucket.", + Description = "Enables support for storing media files in Amazon S3.", Dependencies = [ "OrchardCore.Media.Cache" ], Category = "Hosting" )] + +[assembly: Feature( + Id = "OrchardCore.Media.AmazonS3.ImageSharpImageCache", + Name = "Amazon Media ImageSharp Image Cache", + Description = "Enables support for storing cached images resized via ImageSharp in Amazon S3.", + Dependencies = + [ + "OrchardCore.Media" + ], + Category = "Hosting" +)] diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/OrchardCore.Media.AmazonS3.csproj b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/OrchardCore.Media.AmazonS3.csproj index ce30d21a95b..fa21db633b8 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/OrchardCore.Media.AmazonS3.csproj +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/OrchardCore.Media.AmazonS3.csproj @@ -26,4 +26,8 @@ + + + + diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AWSS3StorageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AWSS3StorageCacheOptionsConfiguration.cs new file mode 100644 index 00000000000..86bd597c524 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AWSS3StorageCacheOptionsConfiguration.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Options; +using SixLabors.ImageSharp.Web.Caching.AWS; + +namespace OrchardCore.Media.Azure.Services; + +// Configuration for ImageSharp's own configuration object. We just pass the settings from the Orchard Core +// configuration. +internal class AWSS3StorageCacheOptionsConfiguration : IConfigureOptions +{ + private readonly AwsImageSharpImageCacheOptions _options; + + public AWSS3StorageCacheOptionsConfiguration(IOptions options) + { + _options = options.Value; + } + + public void Configure(AWSS3StorageCacheOptions options) + { + var credentials = _options.AwsOptions.Credentials.GetCredentials(); + + // Only Endpoint or Region is necessary. + options.Endpoint = _options.AwsOptions.DefaultClientConfig.ServiceURL; + options.Region = _options.AwsOptions.Region?.SystemName; + options.BucketName = _options.BucketName; + options.AccessKey = credentials.AccessKey; + options.AccessSecret = credentials.SecretKey; + options.CacheFolder = _options.BasePath; + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs new file mode 100644 index 00000000000..33b2f2378c1 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs @@ -0,0 +1,80 @@ +using System; +using Fluid; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using OrchardCore.Environment.Shell; +using OrchardCore.Environment.Shell.Configuration; +using OrchardCore.Media.Azure; + +namespace OrchardCore.Media.AmazonS3.Services; + +public class AwsImageSharpImageCacheOptionsConfiguration : IConfigureOptions +{ + private readonly IShellConfiguration _shellConfiguration; + private readonly ShellSettings _shellSettings; + private readonly ILogger _logger; + + // Local instance since it can be discarded once the startup is over. + private readonly FluidParser _fluidParser = new(); + + public AwsImageSharpImageCacheOptionsConfiguration( + IShellConfiguration shellConfiguration, + ShellSettings shellSettings, + ILogger logger) + { + _shellConfiguration = shellConfiguration; + _shellSettings = shellSettings; + _logger = logger; + } + + public void Configure(AwsImageSharpImageCacheOptions options) + { + options.BindConfiguration(Constants.ConfigSections.AmazonS3ImageSharpCache, _shellConfiguration, _logger); + + var templateOptions = new TemplateOptions(); + var templateContext = new TemplateContext(templateOptions); + templateOptions.MemberAccessStrategy.Register(); + templateOptions.MemberAccessStrategy.Register(); + templateContext.SetValue("ShellSettings", _shellSettings); + + ParseBucketName(options, templateContext); + ParseBasePath(options, templateContext); + } + + private void ParseBucketName(AwsImageSharpImageCacheOptions options, TemplateContext templateContext) + { + // Use Fluid directly as this is transient and cannot invoke _liquidTemplateManager. + try + { + var template = _fluidParser.Parse(options.BucketName); + + options.BucketName = template + .Render(templateContext, NullEncoder.Default) + .Replace("\r", string.Empty) + .Replace("\n", string.Empty) + .Trim(); + } + catch (Exception e) + { + _logger.LogCritical(e, "Unable to parse Amazon S3 ImageSharp Image Cache bucket name."); + } + } + + private void ParseBasePath(AwsImageSharpImageCacheOptions options, TemplateContext templateContext) + { + try + { + var template = _fluidParser.Parse(options.BasePath); + + options.BasePath = template + .Render(templateContext, NullEncoder.Default) + .Replace("\r", string.Empty) + .Replace("\n", string.Empty) + .Trim(); + } + catch (Exception e) + { + _logger.LogCritical(e, "Unable to parse Amazon S3 ImageSharp Image Cache base path."); + } + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs index 0f699f92f51..2e32df656ae 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs @@ -29,7 +29,7 @@ public AwsStorageOptionsConfiguration( public void Configure(AwsStorageOptions options) { - options.BindConfiguration(_shellConfiguration, _logger); + options.BindConfiguration(Constants.ConfigSections.AmazonS3, _shellConfiguration, _logger); var templateOptions = new TemplateOptions(); var templateContext = new TemplateContext(templateOptions); diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/ImageSharpS3ImageCacheBucketTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/ImageSharpS3ImageCacheBucketTenantEvents.cs new file mode 100644 index 00000000000..24e12d4db18 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/ImageSharpS3ImageCacheBucketTenantEvents.cs @@ -0,0 +1,134 @@ +using System.Threading.Tasks; +using Amazon.S3; +using Amazon.S3.Model; +using Amazon.S3.Util; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using OrchardCore.Environment.Shell; +using OrchardCore.Environment.Shell.Removing; +using OrchardCore.FileStorage.AmazonS3; +using OrchardCore.Media.Azure; +using OrchardCore.Modules; + +namespace OrchardCore.Media.AmazonS3.Services; + +public class ImageSharpS3ImageCacheBucketTenantEvents : ModularTenantEvents +{ + private readonly ShellSettings _shellSettings; + private readonly AwsImageSharpImageCacheOptions _options; + private readonly IAmazonS3 _amazonS3Client; + protected readonly IStringLocalizer S; + private readonly ILogger _logger; + + public ImageSharpS3ImageCacheBucketTenantEvents( + ShellSettings shellSettings, + IAmazonS3 amazonS3Client, + IOptions options, + IStringLocalizer localizer, + ILogger logger) + { + _shellSettings = shellSettings; + _amazonS3Client = amazonS3Client; + _options = options.Value; + S = localizer; + _logger = logger; + } + + public override async Task ActivatingAsync() + { + if (!_options.CreateBucket || + _shellSettings.IsUninitialized() || + string.IsNullOrEmpty(_options.BucketName)) + { + return; + } + + _logger.LogDebug("Testing Amazon S3 Bucket {BucketName} existence", _options.BucketName); + + try + { + var bucketExists = await AmazonS3Util.DoesS3BucketExistV2Async(_amazonS3Client, _options.BucketName); + if (bucketExists) + { + _logger.LogInformation("Amazon S3 Bucket {BucketName} already exists.", _options.BucketName); + + return; + } + + var bucketRequest = new PutBucketRequest + { + BucketName = _options.BucketName, + UseClientRegion = true + }; + + // Trying to create bucket. + var response = await _amazonS3Client.PutBucketAsync(bucketRequest); + + if (!response.IsSuccessful()) + { + _logger.LogError("Unable to create Amazon S3 Bucket {BucketName}", _options.BucketName); + + return; + } + + // Blocking public access for the newly created bucket. + var blockConfiguration = new PublicAccessBlockConfiguration + { + BlockPublicAcls = true, + BlockPublicPolicy = true, + IgnorePublicAcls = true, + RestrictPublicBuckets = true + }; + + await _amazonS3Client.PutPublicAccessBlockAsync(new PutPublicAccessBlockRequest + { + PublicAccessBlockConfiguration = blockConfiguration, + BucketName = _options.BucketName + }); + + _logger.LogDebug("Amazon S3 Bucket {BucketName} created.", _options.BucketName); + } + catch (AmazonS3Exception ex) + { + _logger.LogError(ex, "Unable to create Amazon S3 Bucket."); + } + } + + public override async Task RemovingAsync(ShellRemovingContext context) + { + if (!_options.RemoveBucket || string.IsNullOrEmpty(_options.BucketName)) + { + return; + } + + try + { + var bucketExists = await AmazonS3Util.DoesS3BucketExistV2Async(_amazonS3Client, _options.BucketName); + if (!bucketExists) + { + return; + } + + var bucketRequest = new DeleteBucketRequest + { + BucketName = _options.BucketName, + UseClientRegion = true + }; + + // Trying to delete bucket. + var response = await _amazonS3Client.DeleteBucketAsync(bucketRequest); + if (!response.IsSuccessful()) + { + _logger.LogError("Failed to remove the Amazon S3 Bucket {BucketName}", _options.BucketName); + context.ErrorMessage = S["Failed to remove the Amazon S3 Bucket '{0}'.", _options.BucketName]; + } + } + catch (AmazonS3Exception ex) + { + _logger.LogError(ex, "Failed to remove the Amazon S3 Bucket {BucketName}", _options.BucketName); + context.ErrorMessage = S["Failed to remove the Amazon S3 Bucket '{0}'.", _options.BucketName]; + context.Error = ex; + } + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs index 2eb4edab121..4d2459abc24 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs @@ -14,12 +14,16 @@ using OrchardCore.FileStorage; using OrchardCore.FileStorage.AmazonS3; using OrchardCore.Media.AmazonS3.Services; +using OrchardCore.Media.Azure; +using OrchardCore.Media.Azure.Services; using OrchardCore.Media.Core; using OrchardCore.Media.Core.Events; using OrchardCore.Media.Events; using OrchardCore.Modules; using OrchardCore.Navigation; using OrchardCore.Security.Permissions; +using SixLabors.ImageSharp.Web.Caching; +using SixLabors.ImageSharp.Web.Caching.AWS; namespace OrchardCore.Media.AmazonS3; @@ -39,7 +43,7 @@ public override void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddTransient, AwsStorageOptionsConfiguration>(); - var storeOptions = new AwsStorageOptions().BindConfiguration(_configuration, _logger); + var storeOptions = new AwsStorageOptions().BindConfiguration(Constants.ConfigSections.AmazonS3, _configuration, _logger); var validationErrors = storeOptions.Validate().ToList(); var stringBuilder = new StringBuilder(); @@ -133,3 +137,55 @@ public override void ConfigureServices(IServiceCollection services) private static string GetMediaCachePath(IWebHostEnvironment hostingEnvironment, ShellSettings shellSettings, string assetsPath) => PathExtensions.Combine(hostingEnvironment.WebRootPath, shellSettings.Name, assetsPath); } + +[Feature("OrchardCore.Media.AmazonS3.ImageSharpImageCache")] +public class ImageSharpAmazonS3CacheStartup : Modules.StartupBase +{ + private readonly IShellConfiguration _configuration; + private readonly ILogger _logger; + + public ImageSharpAmazonS3CacheStartup( + IShellConfiguration configuration, + ILogger logger) + { + _configuration = configuration; + _logger = logger; + } + + // The order should exceed that of the 'OrchardCore.Media' module to substitute the default implementation of 'IImageCache'. + // there. + public override int Order => 5; + + public override void ConfigureServices(IServiceCollection services) + { + services.AddTransient, AwsImageSharpImageCacheOptionsConfiguration>(); + services.AddTransient, AWSS3StorageCacheOptionsConfiguration>(); + + var storeOptions = new AwsStorageOptions().BindConfiguration(Constants.ConfigSections.AmazonS3ImageSharpCache, _configuration, _logger); + var validationErrors = storeOptions.Validate().ToList(); + var stringBuilder = new StringBuilder(); + + if (validationErrors.Count > 0) + { + foreach (var error in validationErrors) + { + stringBuilder.Append(error.ErrorMessage); + } + + _logger.LogError("S3 ImageSharp Image Cache configuration validation failed with errors: {Errors} fallback to local file storage.", stringBuilder); + } + else + { + _logger.LogInformation( + "Starting with ImageSharp Image Cache configuration. BucketName: {BucketName}; BasePath: {BasePath}", storeOptions.BucketName, storeOptions.BasePath); + + // Following https://docs.sixlabors.com/articles/imagesharp.web/imagecaches.html we'd use + // SetCache() but that's only available on IImageSharpBuilder after AddImageSharp(), + // what happens in OrchardCore.Media. Thus, an explicit Replace() is necessary. + services.Replace(ServiceDescriptor.Singleton()); + + services.AddScoped(); + } + + } +} diff --git a/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsImageSharpImageCacheOptions.cs b/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsImageSharpImageCacheOptions.cs new file mode 100644 index 00000000000..91bb05eb50a --- /dev/null +++ b/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsImageSharpImageCacheOptions.cs @@ -0,0 +1,10 @@ +using OrchardCore.FileStorage.AmazonS3; + +namespace OrchardCore.Media.Azure; + +/// +/// Configuration for ImageSharp's AWSS3StorageCache. +/// +public class AwsImageSharpImageCacheOptions : AwsStorageOptionsBase +{ +} diff --git a/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsStorageOptions.cs b/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsStorageOptions.cs index a7c8022fd67..dc7fa85c2db 100644 --- a/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsStorageOptions.cs +++ b/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsStorageOptions.cs @@ -1,34 +1,8 @@ -using Amazon.Extensions.NETCore.Setup; - namespace OrchardCore.FileStorage.AmazonS3; /// /// AWS storage options. /// -public class AwsStorageOptions +public class AwsStorageOptions : AwsStorageOptionsBase { - /// - /// AWS S3 bucket name. - /// - public string BucketName { get; set; } - - /// - /// The base directory path to use inside the container for this store's contents. - /// - public string BasePath { get; set; } - - /// - /// Indicates if bucket should be created on module startup. - /// - public bool CreateBucket { get; set; } - - /// - /// Gets or sets the AWS Options. - /// - public AWSOptions AwsOptions { get; set; } - - /// - /// Indicates if bucket should be removed on tenant removal. - /// - public bool RemoveBucket { get; set; } } diff --git a/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsStorageOptionsBase.cs b/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsStorageOptionsBase.cs new file mode 100644 index 00000000000..768d0c24c13 --- /dev/null +++ b/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsStorageOptionsBase.cs @@ -0,0 +1,25 @@ +using Amazon.Extensions.NETCore.Setup; + +namespace OrchardCore.FileStorage.AmazonS3; + +public abstract class AwsStorageOptionsBase : IAwsStorageOptions +{ + /// + public string BucketName { get; set; } + + /// + + public string BasePath { get; set; } + + /// + + public bool CreateBucket { get; set; } + + /// + + public AWSOptions AwsOptions { get; set; } + + /// + + public bool RemoveBucket { get; set; } +} diff --git a/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/IAwsStorageOptions.cs b/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/IAwsStorageOptions.cs new file mode 100644 index 00000000000..81f406e87f3 --- /dev/null +++ b/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/IAwsStorageOptions.cs @@ -0,0 +1,34 @@ +using Amazon.Extensions.NETCore.Setup; + +namespace OrchardCore.FileStorage.AmazonS3; + +/// +/// Contract for all types providing options to AWS storage. +/// +public interface IAwsStorageOptions +{ + /// + /// AWS S3 bucket name. + /// + string BucketName { get; set; } + + /// + /// The base directory path to use inside the container for this store's contents. + /// + string BasePath { get; set; } + + /// + /// Indicates if bucket should be created on module startup. + /// + bool CreateBucket { get; set; } + + /// + /// Gets or sets the AWS Options. + /// + AWSOptions AwsOptions { get; set; } + + /// + /// Indicates if bucket should be removed on tenant removal. + /// + bool RemoveBucket { get; set; } +} From 23af590cb24175f8df509fa9e3d57e854e2e15d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Sun, 7 Apr 2024 21:19:13 +0200 Subject: [PATCH 30/50] Making it possible to use local S3 emulators --- .../AwsStorageOptionsExtension.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsExtension.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsExtension.cs index 4c45b3d30e2..40b1a8b9dca 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsExtension.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsExtension.cs @@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations; using Amazon.Extensions.NETCore.Setup; using Amazon.Runtime; +using Amazon.S3; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using OrchardCore.Environment.Shell.Configuration; @@ -20,7 +21,7 @@ public static IEnumerable Validate(this AwsStorageOptions opti if (options.AwsOptions is not null) { - if (options.AwsOptions.Region is null) + if (options.AwsOptions.Region is null && options.AwsOptions.DefaultClientConfig.ServiceURL is null) { yield return new ValidationResult(Constants.ValidationMessages.RegionEndpointIsEmpty); } @@ -43,8 +44,9 @@ public static AwsStorageOptions BindConfiguration(this AwsStorageOptions options try { - // Binding AWS Options. - options.AwsOptions = shellConfiguration.GetAWSOptions("OrchardCore_Media_AmazonS3"); + // Binding AWS Options. Using the AmazonS3Config type parameter is necessary to be able to configure + // S3-specific properties like ForcePathStyle via the configuration provider. + options.AwsOptions = shellConfiguration.GetAWSOptions("OrchardCore_Media_AmazonS3"); // In case Credentials sections was specified, trying to add BasicAWSCredential to AWSOptions // since by design GetAWSOptions skips Credential section while parsing config. From aa1330f42aca261523a013564d61e141b76db867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Sun, 7 Apr 2024 21:19:32 +0200 Subject: [PATCH 31/50] Fixing S3 file uploads --- .../Controllers/AdminController.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media/Controllers/AdminController.cs b/src/OrchardCore.Modules/OrchardCore.Media/Controllers/AdminController.cs index 4085af7b85d..2344f85c752 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/Controllers/AdminController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/Controllers/AdminController.cs @@ -215,7 +215,19 @@ public async Task Upload(string path, string extensions) var mediaFile = await _mediaFileStore.GetFileInfoAsync(mediaFilePath); - stream.Position = 0; + // The .NET AWS SDK, and only that form the built-in ones (but others maybe too), disposes + // the stream. There's no better way to check for that than handling the exception. An + // alternative would be to re-read the file for every other storage provider as well but + // that would be wasteful. + try + { + stream.Position = 0; + } + catch (ObjectDisposedException) + { + stream = null; + } + await PreCacheRemoteMedia(mediaFile, stream); result.Add(CreateFileResult(mediaFile)); From d0f41ab64ad645edb3b08ea06116ebb9465415a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Sun, 7 Apr 2024 21:22:30 +0200 Subject: [PATCH 32/50] Documenting using local S3 emulators --- .../modules/Media.AmazonS3/README.md | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/docs/reference/modules/Media.AmazonS3/README.md b/src/docs/reference/modules/Media.AmazonS3/README.md index 540e106ec8a..f2d8e003638 100644 --- a/src/docs/reference/modules/Media.AmazonS3/README.md +++ b/src/docs/reference/modules/Media.AmazonS3/README.md @@ -154,6 +154,51 @@ The `BucketName` property and the `BasePath` property are the only templatable p } ``` +## Configuring a Local Emulator + +During development, instead of using an online S3 resource, you can use a local storage emulator too. This is especially important for development teams, since you don't want to step on each others' feet by using a shared storage. + +For emulators, you'll need to configure a `ServiceURL`. Instead of the default [virtual host addressing of buckets](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html), you'll also need to enable path-style addressing (i.e. instead of `http://mybucket.localhost`, the local bucket will be accessible under `http://localhost/mybucket`). + +```json +{ + "OrchardCore": { + "OrchardCore_Media_AmazonS3": { + // Note how we use ServiceURL, just with emulators. There should be no Region specified. + "ServiceURL": "http://localhost:9444/", + "Profile": "default", + "ProfilesLocation": "", + // Providing some credentials is required but emulators don't actually use them. + "Credentials": { + "SecretKey": "dummy", + "AccessKey": "dummy" + }, + // BasePath and BucketName can be templated too, as usual. + "BasePath": "/media", + "CreateBucket": true, + "RemoveBucket": true, + "BucketName": "media", + // This is required for all emulators. + "ForcePathStyle": true + } + } +} +``` + +The following tools are known to be working with the above settings. Be sure to explore further configuration of these tools, the commands below are just provided for your convenience as a general recommendation. + +- [LocalS3](https://github.com/Robothy/local-s3) with Docker: + +``` +docker run -d -e MODE=IN_MEMORY -p 9444:80 luofuxiang/local-s3 +``` + +- [S3Mock](https://github.com/adobe/S3Mock) with Docker: + +``` +docker run -p 9444:9090 -t adobe/s3mock +``` + ## Media Cache The Media Cache feature will automatically be enabled when Amazon Media Storage is enabled. From 657e660678561105b996528d4dd07cc25af7a956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Sun, 7 Apr 2024 21:42:26 +0200 Subject: [PATCH 33/50] Adding dependency on the storage feature due to IAmazonS3 --- src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Manifest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Manifest.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Manifest.cs index fcf327df928..04cbabec722 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Manifest.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Manifest.cs @@ -24,7 +24,8 @@ Description = "Enables support for storing cached images resized via ImageSharp in Amazon S3.", Dependencies = [ - "OrchardCore.Media" + "OrchardCore.Media", + "OrchardCore.Media.AmazonS3" ], Category = "Hosting" )] From 94b6fca39d518cc34733417feca8eaede63c9edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Sun, 7 Apr 2024 23:44:59 +0200 Subject: [PATCH 34/50] Dry config Fluid parsing --- .../Helpers/FluidParserHelper.cs | 44 +++++++++++++++++++ ...mageSharpImageCacheOptionsConfiguration.cs | 37 ++-------------- .../AwsStorageOptionsConfiguration.cs | 37 ++-------------- .../Helpers/FluidParserHelper.cs | 1 + 4 files changed, 53 insertions(+), 66 deletions(-) create mode 100644 src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/FluidParserHelper.cs diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/FluidParserHelper.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/FluidParserHelper.cs new file mode 100644 index 00000000000..45dbab5ecb3 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/FluidParserHelper.cs @@ -0,0 +1,44 @@ +using Fluid; +using OrchardCore.Environment.Shell; + +namespace OrchardCore.Media.AmazonS3.Helpers; + +// This is the same as in OrchardCore.Media.Azure but there isn't really a good common place for it. +internal class FluidParserHelper where TOptions : class +{ + // Local instance since it can be discarded once the startup is over. + private readonly FluidParser _fluidParser = new(); + private readonly ShellSettings _shellSettings; + + private TemplateContext _templateContext; + + public FluidParserHelper(ShellSettings shellSettings) + { + _shellSettings = shellSettings; + } + + public string ParseAndFormat(string template) + { + var templateContext = GetTemplateContext(); + + // Use Fluid directly as this is transient and cannot invoke _liquidTemplateManager. + var parsedTemplate = _fluidParser.Parse(template); + return parsedTemplate.Render(templateContext, NullEncoder.Default) + .Replace("\r", string.Empty) + .Replace("\n", string.Empty); + } + + private TemplateContext GetTemplateContext() + { + if (_templateContext == null) + { + var templateOptions = new TemplateOptions(); + _templateContext = new TemplateContext(templateOptions); + templateOptions.MemberAccessStrategy.Register(); + templateOptions.MemberAccessStrategy.Register(); + _templateContext.SetValue("ShellSettings", _shellSettings); + } + + return _templateContext; + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs index 33b2f2378c1..33b3fd20e1e 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs @@ -1,9 +1,9 @@ using System; -using Fluid; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OrchardCore.Environment.Shell; using OrchardCore.Environment.Shell.Configuration; +using OrchardCore.Media.AmazonS3.Helpers; using OrchardCore.Media.Azure; namespace OrchardCore.Media.AmazonS3.Services; @@ -14,9 +14,6 @@ public class AwsImageSharpImageCacheOptionsConfiguration : IConfigureOptions(); - templateOptions.MemberAccessStrategy.Register(); - templateContext.SetValue("ShellSettings", _shellSettings); - - ParseBucketName(options, templateContext); - ParseBasePath(options, templateContext); - } + var fluidParserHelper = new FluidParserHelper(_shellSettings); - private void ParseBucketName(AwsImageSharpImageCacheOptions options, TemplateContext templateContext) - { - // Use Fluid directly as this is transient and cannot invoke _liquidTemplateManager. try { - var template = _fluidParser.Parse(options.BucketName); - - options.BucketName = template - .Render(templateContext, NullEncoder.Default) - .Replace("\r", string.Empty) - .Replace("\n", string.Empty) - .Trim(); + options.BucketName = fluidParserHelper.ParseAndFormat(options.BucketName).Trim(); } catch (Exception e) { _logger.LogCritical(e, "Unable to parse Amazon S3 ImageSharp Image Cache bucket name."); } - } - private void ParseBasePath(AwsImageSharpImageCacheOptions options, TemplateContext templateContext) - { try { - var template = _fluidParser.Parse(options.BasePath); - - options.BasePath = template - .Render(templateContext, NullEncoder.Default) - .Replace("\r", string.Empty) - .Replace("\n", string.Empty) - .Trim(); + options.BasePath = fluidParserHelper.ParseAndFormat(options.BasePath).Trim(); } catch (Exception e) { diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs index 2e32df656ae..ef04a23bcb4 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs @@ -1,10 +1,10 @@ using System; -using Fluid; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OrchardCore.Environment.Shell; using OrchardCore.Environment.Shell.Configuration; using OrchardCore.FileStorage.AmazonS3; +using OrchardCore.Media.AmazonS3.Helpers; namespace OrchardCore.Media.AmazonS3.Services; @@ -14,9 +14,6 @@ public class AwsStorageOptionsConfiguration : IConfigureOptions(); - templateOptions.MemberAccessStrategy.Register(); - templateContext.SetValue("ShellSettings", _shellSettings); - - ParseBucketName(options, templateContext); - ParseBasePath(options, templateContext); - } + var fluidParserHelper = new FluidParserHelper(_shellSettings); - private void ParseBucketName(AwsStorageOptions options, TemplateContext templateContext) - { - // Use Fluid directly as this is transient and cannot invoke _liquidTemplateManager. try { - var template = _fluidParser.Parse(options.BucketName); - - options.BucketName = template - .Render(templateContext, NullEncoder.Default) - .Replace("\r", string.Empty) - .Replace("\n", string.Empty) - .Trim(); + options.BucketName = fluidParserHelper.ParseAndFormat(options.BucketName).Trim(); } catch (Exception e) { _logger.LogCritical(e, "Unable to parse Amazon S3 Media Storage bucket name."); } - } - private void ParseBasePath(AwsStorageOptions options, TemplateContext templateContext) - { try { - var template = _fluidParser.Parse(options.BasePath); - - options.BasePath = template - .Render(templateContext, NullEncoder.Default) - .Replace("\r", string.Empty) - .Replace("\n", string.Empty) - .Trim(); + options.BasePath = fluidParserHelper.ParseAndFormat(options.BasePath).Trim(); } catch (Exception e) { diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/FluidParserHelper.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/FluidParserHelper.cs index b8723739056..85aeac2672a 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/FluidParserHelper.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/FluidParserHelper.cs @@ -3,6 +3,7 @@ namespace OrchardCore.Media.Azure.Helpers; +// This is the same as in OrchardCore.Media.Azure but there isn't really a good common place for it. internal class FluidParserHelper where TOptions : class { // Local instance since it can be discarded once the startup is over. From ce2a097654d13ce2948aac9eaf9ca918aaa71b99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Sun, 7 Apr 2024 23:45:46 +0200 Subject: [PATCH 35/50] Rename --- .../{FluidParserHelper.cs => OptionsFluidParserHelper.cs} | 4 ++-- .../Services/AwsImageSharpImageCacheOptionsConfiguration.cs | 2 +- .../Services/AwsStorageOptionsConfiguration.cs | 2 +- .../{FluidParserHelper.cs => OptionsFluidParserHelper.cs} | 4 ++-- .../Services/ImageSharpBlobImageCacheOptionsConfiguration.cs | 2 +- .../Services/MediaBlobStorageOptionsConfiguration.cs | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) rename src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/{FluidParserHelper.cs => OptionsFluidParserHelper.cs} (91%) rename src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/{FluidParserHelper.cs => OptionsFluidParserHelper.cs} (91%) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/FluidParserHelper.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/OptionsFluidParserHelper.cs similarity index 91% rename from src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/FluidParserHelper.cs rename to src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/OptionsFluidParserHelper.cs index 45dbab5ecb3..e325c2c8f57 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/FluidParserHelper.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/OptionsFluidParserHelper.cs @@ -4,7 +4,7 @@ namespace OrchardCore.Media.AmazonS3.Helpers; // This is the same as in OrchardCore.Media.Azure but there isn't really a good common place for it. -internal class FluidParserHelper where TOptions : class +internal class OptionsFluidParserHelper where TOptions : class { // Local instance since it can be discarded once the startup is over. private readonly FluidParser _fluidParser = new(); @@ -12,7 +12,7 @@ internal class FluidParserHelper where TOptions : class private TemplateContext _templateContext; - public FluidParserHelper(ShellSettings shellSettings) + public OptionsFluidParserHelper(ShellSettings shellSettings) { _shellSettings = shellSettings; } diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs index 33b3fd20e1e..54692c32dc9 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs @@ -28,7 +28,7 @@ public void Configure(AwsImageSharpImageCacheOptions options) { options.BindConfiguration(Constants.ConfigSections.AmazonS3ImageSharpCache, _shellConfiguration, _logger); - var fluidParserHelper = new FluidParserHelper(_shellSettings); + var fluidParserHelper = new OptionsFluidParserHelper(_shellSettings); try { diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs index ef04a23bcb4..409ef39f5c1 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs @@ -28,7 +28,7 @@ public void Configure(AwsStorageOptions options) { options.BindConfiguration(Constants.ConfigSections.AmazonS3, _shellConfiguration, _logger); - var fluidParserHelper = new FluidParserHelper(_shellSettings); + var fluidParserHelper = new OptionsFluidParserHelper(_shellSettings); try { diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/FluidParserHelper.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/OptionsFluidParserHelper.cs similarity index 91% rename from src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/FluidParserHelper.cs rename to src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/OptionsFluidParserHelper.cs index 85aeac2672a..0db3b037e22 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/FluidParserHelper.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/OptionsFluidParserHelper.cs @@ -4,7 +4,7 @@ namespace OrchardCore.Media.Azure.Helpers; // This is the same as in OrchardCore.Media.Azure but there isn't really a good common place for it. -internal class FluidParserHelper where TOptions : class +internal class OptionsFluidParserHelper where TOptions : class { // Local instance since it can be discarded once the startup is over. private readonly FluidParser _fluidParser = new(); @@ -12,7 +12,7 @@ internal class FluidParserHelper where TOptions : class private TemplateContext _templateContext; - public FluidParserHelper(ShellSettings shellSettings) + public OptionsFluidParserHelper(ShellSettings shellSettings) { _shellSettings = shellSettings; } diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheOptionsConfiguration.cs index 3dc066ccdce..e8e2de58146 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheOptionsConfiguration.cs @@ -29,7 +29,7 @@ public void Configure(ImageSharpBlobImageCacheOptions options) var section = _shellConfiguration.GetSection("OrchardCore_Media_Azure_ImageSharp_Cache"); section.Bind(options); - var fluidParserHelper = new FluidParserHelper(_shellSettings); + var fluidParserHelper = new OptionsFluidParserHelper(_shellSettings); try { diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs index fae09c72032..05af692d9d8 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs @@ -29,7 +29,7 @@ public void Configure(MediaBlobStorageOptions options) { _shellConfiguration.GetSection("OrchardCore_Media_Azure").Bind(options); - var fluidParserHelper = new FluidParserHelper(_shellSettings); + var fluidParserHelper = new OptionsFluidParserHelper(_shellSettings); try { From 6d25a3230e7c416e4d3f14a0c2796807b3c43deb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Sun, 7 Apr 2024 23:56:43 +0200 Subject: [PATCH 36/50] DRY Trim() --- .../Helpers/OptionsFluidParserHelper.cs | 5 +++-- .../Services/AwsImageSharpImageCacheOptionsConfiguration.cs | 4 ++-- .../Services/AwsStorageOptionsConfiguration.cs | 4 ++-- .../Helpers/OptionsFluidParserHelper.cs | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/OptionsFluidParserHelper.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/OptionsFluidParserHelper.cs index e325c2c8f57..e986c250242 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/OptionsFluidParserHelper.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/OptionsFluidParserHelper.cs @@ -3,7 +3,7 @@ namespace OrchardCore.Media.AmazonS3.Helpers; -// This is the same as in OrchardCore.Media.Azure but there isn't really a good common place for it. +// This is almost the same as in OrchardCore.Media.Azure but there isn't really a good common place for it. internal class OptionsFluidParserHelper where TOptions : class { // Local instance since it can be discarded once the startup is over. @@ -25,7 +25,8 @@ public string ParseAndFormat(string template) var parsedTemplate = _fluidParser.Parse(template); return parsedTemplate.Render(templateContext, NullEncoder.Default) .Replace("\r", string.Empty) - .Replace("\n", string.Empty); + .Replace("\n", string.Empty) + .Trim(); } private TemplateContext GetTemplateContext() diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs index 54692c32dc9..566d265033a 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs @@ -32,7 +32,7 @@ public void Configure(AwsImageSharpImageCacheOptions options) try { - options.BucketName = fluidParserHelper.ParseAndFormat(options.BucketName).Trim(); + options.BucketName = fluidParserHelper.ParseAndFormat(options.BucketName); } catch (Exception e) { @@ -41,7 +41,7 @@ public void Configure(AwsImageSharpImageCacheOptions options) try { - options.BasePath = fluidParserHelper.ParseAndFormat(options.BasePath).Trim(); + options.BasePath = fluidParserHelper.ParseAndFormat(options.BasePath); } catch (Exception e) { diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs index 409ef39f5c1..a42bc95d22a 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs @@ -32,7 +32,7 @@ public void Configure(AwsStorageOptions options) try { - options.BucketName = fluidParserHelper.ParseAndFormat(options.BucketName).Trim(); + options.BucketName = fluidParserHelper.ParseAndFormat(options.BucketName); } catch (Exception e) { @@ -41,7 +41,7 @@ public void Configure(AwsStorageOptions options) try { - options.BasePath = fluidParserHelper.ParseAndFormat(options.BasePath).Trim(); + options.BasePath = fluidParserHelper.ParseAndFormat(options.BasePath); } catch (Exception e) { diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/OptionsFluidParserHelper.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/OptionsFluidParserHelper.cs index 0db3b037e22..5b7e8afed08 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/OptionsFluidParserHelper.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/OptionsFluidParserHelper.cs @@ -3,7 +3,7 @@ namespace OrchardCore.Media.Azure.Helpers; -// This is the same as in OrchardCore.Media.Azure but there isn't really a good common place for it. +// This is almost the same as in OrchardCore.Media.Azure but there isn't really a good common place for it. internal class OptionsFluidParserHelper where TOptions : class { // Local instance since it can be discarded once the startup is over. From d44be337a1263bd70565bd813cfb350b8895d843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Sun, 7 Apr 2024 23:59:56 +0200 Subject: [PATCH 37/50] Fix type parameters --- .../Services/ImageSharpS3ImageCacheBucketTenantEvents.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/ImageSharpS3ImageCacheBucketTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/ImageSharpS3ImageCacheBucketTenantEvents.cs index 24e12d4db18..7449d50f766 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/ImageSharpS3ImageCacheBucketTenantEvents.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/ImageSharpS3ImageCacheBucketTenantEvents.cs @@ -25,8 +25,8 @@ public ImageSharpS3ImageCacheBucketTenantEvents( ShellSettings shellSettings, IAmazonS3 amazonS3Client, IOptions options, - IStringLocalizer localizer, - ILogger logger) + IStringLocalizer localizer, + ILogger logger) { _shellSettings = shellSettings; _amazonS3Client = amazonS3Client; From 0704d810646e0bc01dbee330bc8c549764bad021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Mon, 8 Apr 2024 00:01:32 +0200 Subject: [PATCH 38/50] Fixing copy-paste errors --- .../Services/AWSS3StorageCacheOptionsConfiguration.cs | 3 ++- .../Services/AwsImageSharpImageCacheOptionsConfiguration.cs | 2 +- .../Services/ImageSharpS3ImageCacheBucketTenantEvents.cs | 1 - src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs | 2 -- .../AwsImageSharpImageCacheOptions.cs | 4 +--- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AWSS3StorageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AWSS3StorageCacheOptionsConfiguration.cs index 86bd597c524..1a803f7a595 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AWSS3StorageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AWSS3StorageCacheOptionsConfiguration.cs @@ -1,7 +1,8 @@ using Microsoft.Extensions.Options; +using OrchardCore.FileStorage.AmazonS3; using SixLabors.ImageSharp.Web.Caching.AWS; -namespace OrchardCore.Media.Azure.Services; +namespace OrchardCore.Media.AmazonS3.Services; // Configuration for ImageSharp's own configuration object. We just pass the settings from the Orchard Core // configuration. diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs index 566d265033a..87c4de5a591 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs @@ -3,8 +3,8 @@ using Microsoft.Extensions.Options; using OrchardCore.Environment.Shell; using OrchardCore.Environment.Shell.Configuration; +using OrchardCore.FileStorage.AmazonS3; using OrchardCore.Media.AmazonS3.Helpers; -using OrchardCore.Media.Azure; namespace OrchardCore.Media.AmazonS3.Services; diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/ImageSharpS3ImageCacheBucketTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/ImageSharpS3ImageCacheBucketTenantEvents.cs index 7449d50f766..3919c9a5b1d 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/ImageSharpS3ImageCacheBucketTenantEvents.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/ImageSharpS3ImageCacheBucketTenantEvents.cs @@ -8,7 +8,6 @@ using OrchardCore.Environment.Shell; using OrchardCore.Environment.Shell.Removing; using OrchardCore.FileStorage.AmazonS3; -using OrchardCore.Media.Azure; using OrchardCore.Modules; namespace OrchardCore.Media.AmazonS3.Services; diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs index 4d2459abc24..62117fae805 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs @@ -14,8 +14,6 @@ using OrchardCore.FileStorage; using OrchardCore.FileStorage.AmazonS3; using OrchardCore.Media.AmazonS3.Services; -using OrchardCore.Media.Azure; -using OrchardCore.Media.Azure.Services; using OrchardCore.Media.Core; using OrchardCore.Media.Core.Events; using OrchardCore.Media.Events; diff --git a/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsImageSharpImageCacheOptions.cs b/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsImageSharpImageCacheOptions.cs index 91bb05eb50a..cb7396ef974 100644 --- a/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsImageSharpImageCacheOptions.cs +++ b/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsImageSharpImageCacheOptions.cs @@ -1,6 +1,4 @@ -using OrchardCore.FileStorage.AmazonS3; - -namespace OrchardCore.Media.Azure; +namespace OrchardCore.FileStorage.AmazonS3; /// /// Configuration for ImageSharp's AWSS3StorageCache. From f297e996146743ca44b965f25bbff2fa15ed1e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Mon, 8 Apr 2024 00:42:09 +0200 Subject: [PATCH 39/50] TenantEvents DRY --- .../Services/AwsTenantEventsBase.cs | 133 ++++++++++++++++++ ...mageSharpS3ImageCacheBucketTenantEvents.cs | 116 +-------------- .../Services/MediaS3BucketTenantEvents.cs | 116 +-------------- 3 files changed, 137 insertions(+), 228 deletions(-) create mode 100644 src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsTenantEventsBase.cs diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsTenantEventsBase.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsTenantEventsBase.cs new file mode 100644 index 00000000000..78f38b9ceee --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsTenantEventsBase.cs @@ -0,0 +1,133 @@ +using System.Threading.Tasks; +using Amazon.S3; +using Amazon.S3.Model; +using Amazon.S3.Util; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging; +using OrchardCore.Environment.Shell; +using OrchardCore.Environment.Shell.Removing; +using OrchardCore.FileStorage.AmazonS3; +using OrchardCore.Modules; + +namespace OrchardCore.Media.AmazonS3.Services; + +public abstract class AwsTenantEventsBase : ModularTenantEvents +{ + private readonly ShellSettings _shellSettings; + private readonly IAwsStorageOptions _options; + private readonly IAmazonS3 _amazonS3Client; + private readonly IStringLocalizer S; + private readonly ILogger _logger; + + protected AwsTenantEventsBase( + ShellSettings shellSettings, + IAmazonS3 amazonS3Client, + IAwsStorageOptions options, + IStringLocalizer localizer, + ILogger logger) + { + _shellSettings = shellSettings; + _amazonS3Client = amazonS3Client; + _options = options; + S = localizer; + _logger = logger; + } + + public override async Task ActivatingAsync() + { + if (!_options.CreateBucket || + _shellSettings.IsUninitialized() || + string.IsNullOrEmpty(_options.BucketName)) + { + return; + } + + _logger.LogDebug("Testing Amazon S3 Bucket {BucketName} existence", _options.BucketName); + + try + { + var bucketExists = await AmazonS3Util.DoesS3BucketExistV2Async(_amazonS3Client, _options.BucketName); + if (bucketExists) + { + _logger.LogInformation("Amazon S3 Bucket {BucketName} already exists.", _options.BucketName); + + return; + } + + var bucketRequest = new PutBucketRequest + { + BucketName = _options.BucketName, + UseClientRegion = true + }; + + // Trying to create bucket. + var response = await _amazonS3Client.PutBucketAsync(bucketRequest); + + if (!response.IsSuccessful()) + { + _logger.LogError("Unable to create Amazon S3 Bucket {BucketName}", _options.BucketName); + + return; + } + + // Blocking public access for the newly created bucket. + var blockConfiguration = new PublicAccessBlockConfiguration + { + BlockPublicAcls = true, + BlockPublicPolicy = true, + IgnorePublicAcls = true, + RestrictPublicBuckets = true + }; + + await _amazonS3Client.PutPublicAccessBlockAsync(new PutPublicAccessBlockRequest + { + PublicAccessBlockConfiguration = blockConfiguration, + BucketName = _options.BucketName + }); + + _logger.LogDebug("Amazon S3 Bucket {BucketName} created.", _options.BucketName); + } + catch (AmazonS3Exception ex) + { + _logger.LogError(ex, "Unable to create Amazon S3 Bucket."); + } + } + + public override async Task RemovingAsync(ShellRemovingContext context) + { + if (!_options.RemoveBucket || string.IsNullOrEmpty(_options.BucketName)) + { + return; + } + + try + { + var bucketExists = await AmazonS3Util.DoesS3BucketExistV2Async(_amazonS3Client, _options.BucketName); + if (!bucketExists) + { + return; + } + + var bucketRequest = new DeleteBucketRequest + { + BucketName = _options.BucketName, + UseClientRegion = true + }; + + // Trying to delete bucket. + var response = await _amazonS3Client.DeleteBucketAsync(bucketRequest); + if (!response.IsSuccessful()) + { + _logger.LogError("Failed to remove the Amazon S3 Bucket {BucketName}", _options.BucketName); + context.ErrorMessage = S["Failed to remove the Amazon S3 Bucket '{0}'.", _options.BucketName]; + } + } + catch (AmazonS3Exception ex) + { + _logger.LogError(ex, "Failed to remove the Amazon S3 Bucket {BucketName}", _options.BucketName); + context.ErrorMessage = S["Failed to remove the Amazon S3 Bucket '{0}'.", _options.BucketName]; + context.Error = ex; + } + } +} + diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/ImageSharpS3ImageCacheBucketTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/ImageSharpS3ImageCacheBucketTenantEvents.cs index 3919c9a5b1d..e1ffe2d9be3 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/ImageSharpS3ImageCacheBucketTenantEvents.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/ImageSharpS3ImageCacheBucketTenantEvents.cs @@ -1,133 +1,21 @@ -using System.Threading.Tasks; using Amazon.S3; -using Amazon.S3.Model; -using Amazon.S3.Util; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OrchardCore.Environment.Shell; -using OrchardCore.Environment.Shell.Removing; using OrchardCore.FileStorage.AmazonS3; -using OrchardCore.Modules; namespace OrchardCore.Media.AmazonS3.Services; -public class ImageSharpS3ImageCacheBucketTenantEvents : ModularTenantEvents +public class ImageSharpS3ImageCacheBucketTenantEvents : AwsTenantEventsBase { - private readonly ShellSettings _shellSettings; - private readonly AwsImageSharpImageCacheOptions _options; - private readonly IAmazonS3 _amazonS3Client; - protected readonly IStringLocalizer S; - private readonly ILogger _logger; - public ImageSharpS3ImageCacheBucketTenantEvents( ShellSettings shellSettings, IAmazonS3 amazonS3Client, IOptions options, IStringLocalizer localizer, ILogger logger) + : base(shellSettings, amazonS3Client, options.Value, localizer, logger) { - _shellSettings = shellSettings; - _amazonS3Client = amazonS3Client; - _options = options.Value; - S = localizer; - _logger = logger; - } - - public override async Task ActivatingAsync() - { - if (!_options.CreateBucket || - _shellSettings.IsUninitialized() || - string.IsNullOrEmpty(_options.BucketName)) - { - return; - } - - _logger.LogDebug("Testing Amazon S3 Bucket {BucketName} existence", _options.BucketName); - - try - { - var bucketExists = await AmazonS3Util.DoesS3BucketExistV2Async(_amazonS3Client, _options.BucketName); - if (bucketExists) - { - _logger.LogInformation("Amazon S3 Bucket {BucketName} already exists.", _options.BucketName); - - return; - } - - var bucketRequest = new PutBucketRequest - { - BucketName = _options.BucketName, - UseClientRegion = true - }; - - // Trying to create bucket. - var response = await _amazonS3Client.PutBucketAsync(bucketRequest); - - if (!response.IsSuccessful()) - { - _logger.LogError("Unable to create Amazon S3 Bucket {BucketName}", _options.BucketName); - - return; - } - - // Blocking public access for the newly created bucket. - var blockConfiguration = new PublicAccessBlockConfiguration - { - BlockPublicAcls = true, - BlockPublicPolicy = true, - IgnorePublicAcls = true, - RestrictPublicBuckets = true - }; - - await _amazonS3Client.PutPublicAccessBlockAsync(new PutPublicAccessBlockRequest - { - PublicAccessBlockConfiguration = blockConfiguration, - BucketName = _options.BucketName - }); - - _logger.LogDebug("Amazon S3 Bucket {BucketName} created.", _options.BucketName); - } - catch (AmazonS3Exception ex) - { - _logger.LogError(ex, "Unable to create Amazon S3 Bucket."); - } - } - - public override async Task RemovingAsync(ShellRemovingContext context) - { - if (!_options.RemoveBucket || string.IsNullOrEmpty(_options.BucketName)) - { - return; - } - - try - { - var bucketExists = await AmazonS3Util.DoesS3BucketExistV2Async(_amazonS3Client, _options.BucketName); - if (!bucketExists) - { - return; - } - - var bucketRequest = new DeleteBucketRequest - { - BucketName = _options.BucketName, - UseClientRegion = true - }; - - // Trying to delete bucket. - var response = await _amazonS3Client.DeleteBucketAsync(bucketRequest); - if (!response.IsSuccessful()) - { - _logger.LogError("Failed to remove the Amazon S3 Bucket {BucketName}", _options.BucketName); - context.ErrorMessage = S["Failed to remove the Amazon S3 Bucket '{0}'.", _options.BucketName]; - } - } - catch (AmazonS3Exception ex) - { - _logger.LogError(ex, "Failed to remove the Amazon S3 Bucket {BucketName}", _options.BucketName); - context.ErrorMessage = S["Failed to remove the Amazon S3 Bucket '{0}'.", _options.BucketName]; - context.Error = ex; - } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/MediaS3BucketTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/MediaS3BucketTenantEvents.cs index 15fe054ab44..e604617f736 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/MediaS3BucketTenantEvents.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/MediaS3BucketTenantEvents.cs @@ -1,133 +1,21 @@ -using System.Threading.Tasks; using Amazon.S3; -using Amazon.S3.Model; -using Amazon.S3.Util; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OrchardCore.Environment.Shell; -using OrchardCore.Environment.Shell.Removing; using OrchardCore.FileStorage.AmazonS3; -using OrchardCore.Modules; namespace OrchardCore.Media.AmazonS3.Services; -public class MediaS3BucketTenantEvents : ModularTenantEvents +public class MediaS3BucketTenantEvents : AwsTenantEventsBase { - private readonly ShellSettings _shellSettings; - private readonly AwsStorageOptions _options; - private readonly IAmazonS3 _amazonS3Client; - protected readonly IStringLocalizer S; - private readonly ILogger _logger; - public MediaS3BucketTenantEvents( ShellSettings shellSettings, IAmazonS3 amazonS3Client, IOptions options, IStringLocalizer localizer, ILogger logger) + : base(shellSettings, amazonS3Client, options.Value, localizer, logger) { - _shellSettings = shellSettings; - _amazonS3Client = amazonS3Client; - _options = options.Value; - S = localizer; - _logger = logger; - } - - public override async Task ActivatingAsync() - { - if (!_options.CreateBucket || - _shellSettings.IsUninitialized() || - string.IsNullOrEmpty(_options.BucketName)) - { - return; - } - - _logger.LogDebug("Testing Amazon S3 Bucket {BucketName} existence", _options.BucketName); - - try - { - var bucketExists = await AmazonS3Util.DoesS3BucketExistV2Async(_amazonS3Client, _options.BucketName); - if (bucketExists) - { - _logger.LogInformation("Amazon S3 Bucket {BucketName} already exists.", _options.BucketName); - - return; - } - - var bucketRequest = new PutBucketRequest - { - BucketName = _options.BucketName, - UseClientRegion = true - }; - - // Trying to create bucket. - var response = await _amazonS3Client.PutBucketAsync(bucketRequest); - - if (!response.IsSuccessful()) - { - _logger.LogError("Unable to create Amazon S3 Bucket {BucketName}", _options.BucketName); - - return; - } - - // Blocking public access for the newly created bucket. - var blockConfiguration = new PublicAccessBlockConfiguration - { - BlockPublicAcls = true, - BlockPublicPolicy = true, - IgnorePublicAcls = true, - RestrictPublicBuckets = true - }; - - await _amazonS3Client.PutPublicAccessBlockAsync(new PutPublicAccessBlockRequest - { - PublicAccessBlockConfiguration = blockConfiguration, - BucketName = _options.BucketName - }); - - _logger.LogDebug("Amazon S3 Bucket {BucketName} created.", _options.BucketName); - } - catch (AmazonS3Exception ex) - { - _logger.LogError(ex, "Unable to create Amazon S3 Bucket."); - } - } - - public override async Task RemovingAsync(ShellRemovingContext context) - { - if (!_options.RemoveBucket || string.IsNullOrEmpty(_options.BucketName)) - { - return; - } - - try - { - var bucketExists = await AmazonS3Util.DoesS3BucketExistV2Async(_amazonS3Client, _options.BucketName); - if (!bucketExists) - { - return; - } - - var bucketRequest = new DeleteBucketRequest - { - BucketName = _options.BucketName, - UseClientRegion = true - }; - - // Trying to delete bucket. - var response = await _amazonS3Client.DeleteBucketAsync(bucketRequest); - if (!response.IsSuccessful()) - { - _logger.LogError("Failed to remove the Amazon S3 Bucket {BucketName}", _options.BucketName); - context.ErrorMessage = S["Failed to remove the Amazon S3 Bucket '{0}'.", _options.BucketName]; - } - } - catch (AmazonS3Exception ex) - { - _logger.LogError(ex, "Failed to remove the Amazon S3 Bucket {BucketName}", _options.BucketName); - context.ErrorMessage = S["Failed to remove the Amazon S3 Bucket '{0}'.", _options.BucketName]; - context.Error = ex; - } } } From 13f5b37f3e3495ed292590c65882a48bc22a8cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Mon, 8 Apr 2024 01:06:14 +0200 Subject: [PATCH 40/50] Appsettings docs --- src/OrchardCore.Cms.Web/appsettings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OrchardCore.Cms.Web/appsettings.json b/src/OrchardCore.Cms.Web/appsettings.json index f6f808c9fc5..54ef4593164 100644 --- a/src/OrchardCore.Cms.Web/appsettings.json +++ b/src/OrchardCore.Cms.Web/appsettings.json @@ -62,7 +62,7 @@ // "SecretKey": "", // "AccessKey": "" // }, - // "BasePath": "/{{ ShellSettings.Name }}", + // "BasePath": "/media", // Optionally, set to a path to store media in a subdirectory inside your bucket. Templatable, refer docs. // "CreateBucket": true, // "RemoveBucket": true, // Whether the 'Bucket' is deleted if the tenant is removed, false by default. // "BucketName": "media" @@ -76,7 +76,7 @@ // "SecretKey": "", // "AccessKey": "" // }, - // "BasePath": "/{{ ShellSettings.Name }}", + // "BasePath": "/media", // Optionally, set to a path to store media in a subdirectory inside your bucket. Templatable, refer docs. // "CreateBucket": true, // "RemoveBucket": true, // Whether the 'Bucket' is deleted if the tenant is removed, false by default. // "BucketName": "imagesharp" From 5dd7d337188cefe34232e57972b53b1e683db025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Mon, 8 Apr 2024 01:29:09 +0200 Subject: [PATCH 41/50] Docs --- src/OrchardCore.Cms.Web/appsettings.json | 22 +++---- .../modules/Media.AmazonS3/README.md | 63 +++++++++++++++---- .../reference/modules/Media.Azure/README.md | 6 +- src/docs/releases/1.9.0.md | 5 +- 4 files changed, 68 insertions(+), 28 deletions(-) diff --git a/src/OrchardCore.Cms.Web/appsettings.json b/src/OrchardCore.Cms.Web/appsettings.json index 54ef4593164..6bef9e85430 100644 --- a/src/OrchardCore.Cms.Web/appsettings.json +++ b/src/OrchardCore.Cms.Web/appsettings.json @@ -28,8 +28,8 @@ // See https://docs.orchardcore.net/en/latest/docs/reference/modules/DataProtection.Azure/#configuration to configure data protection key storage in Azure Blob Storage. //"OrchardCore_DataProtection_Azure": { // "ConnectionString": "", // Set to your Azure Storage account connection string. - // "ContainerName": "dataprotection", // Default to dataprotection. Templatable, refer docs. - // "BlobName": "", // Optional, defaults to Sites/tenant_name/DataProtectionKeys.xml. Templatable, refer docs. + // "ContainerName": "dataprotection", // Default to dataprotection. Templatable, refer to docs. + // "BlobName": "", // Optional, defaults to Sites/tenant_name/DataProtectionKeys.xml. Templatable, refer to docs. // "CreateContainer": true // Creates the container during app startup if it does not already exist. //}, // See https://docs.orchardcore.net/en/latest/docs/reference/modules/Markdown/#markdown-configuration @@ -62,12 +62,12 @@ // "SecretKey": "", // "AccessKey": "" // }, - // "BasePath": "/media", // Optionally, set to a path to store media in a subdirectory inside your bucket. Templatable, refer docs. + // "BasePath": "/media", // Optionally, set to a path to store media in a subdirectory inside your bucket. Templatable, refer to docs. // "CreateBucket": true, // "RemoveBucket": true, // Whether the 'Bucket' is deleted if the tenant is removed, false by default. - // "BucketName": "media" + // "BucketName": "media" // Set the bucket's name (mandatory). Templatable, refer to docs. //}, - // See https://docs.orchardcore.net/en/latest/docs/reference/modules/Media.AmazonS3/#configuration to configure media storage in Amazon S3 Storage. + // See https://docs.orchardcore.net/en/latest/docs/reference/modules/Media.AmazonS3/#configuration_1 to configure media storage in Amazon S3 Storage. //"OrchardCore_Media_AmazonS3_ImageSharp_Cache": { // "Region": "eu-central-1", // "Profile": "default", @@ -76,24 +76,24 @@ // "SecretKey": "", // "AccessKey": "" // }, - // "BasePath": "/media", // Optionally, set to a path to store media in a subdirectory inside your bucket. Templatable, refer docs. + // "BasePath": "/media", // Optionally, set to a path to store media in a subdirectory inside your bucket. Templatable, refer to docs. // "CreateBucket": true, // "RemoveBucket": true, // Whether the 'Bucket' is deleted if the tenant is removed, false by default. - // "BucketName": "imagesharp" + // "BucketName": "imagesharp" // Set the bucket's name (mandatory). Templatable, refer to docs. //}, // See https://docs.orchardcore.net/en/latest/docs/reference/modules/Media.Azure/#configuration to configure media storage in Azure Blob Storage. //"OrchardCore_Media_Azure": { // "ConnectionString": "", // Set to your Azure Storage account connection string. - // "ContainerName": "somecontainer", // Set to the Azure Blob container name. Templatable, refer docs. - // "BasePath": "some/base/path", // Optionally, set to a path to store media in a subdirectory inside your container. Templatable, refer docs. + // "ContainerName": "somecontainer", // Set to the Azure Blob container name. Templatable, refer to docs. + // "BasePath": "some/base/path", // Optionally, set to a path to store media in a subdirectory inside your container. Templatable, refer to docs. // "CreateContainer": true, // Activates an event to create the container if it does not already exist. // "RemoveContainer": true // Whether the 'Container' is deleted if the tenant is removed, false by default. //}, // See http://127.0.0.1:8000/docs/reference/modules/Media.Azure/#configuration_1 //"OrchardCore_Media_Azure_ImageSharp_Cache": { // "ConnectionString": "", // Set to your Azure Storage account connection string. - // "ContainerName": "somecontainer", // Set to the Azure Blob container name. Templatable, refer docs. - // "BasePath": "some/base/path", // Optionally, set to a path to store media in a subdirectory inside your container. Templatable, refer docs. + // "ContainerName": "somecontainer", // Set to the Azure Blob container name. Templatable, refer to docs. + // "BasePath": "some/base/path", // Optionally, set to a path to store media in a subdirectory inside your container. Templatable, refer to docs. // "CreateContainer": true, // Activates an event to create the container if it does not already exist. // "RemoveContainer": true // Whether the 'Container' is deleted if the tenant is removed, false by default. //}, diff --git a/src/docs/reference/modules/Media.AmazonS3/README.md b/src/docs/reference/modules/Media.AmazonS3/README.md index f2d8e003638..7e8adf42f1e 100644 --- a/src/docs/reference/modules/Media.AmazonS3/README.md +++ b/src/docs/reference/modules/Media.AmazonS3/README.md @@ -1,6 +1,8 @@ -# Amazon S3 Media Storage (`OrchardCore.Media.AmazonS3`) +# Amazon S3 Media (`OrchardCore.Media.AmazonS3`) -The Amazon Media Storage feature enables support for storing assets in Amazon S3 Bucket. +The Amazon S3 Media module enables support for storing assets in Amazon S3 Buckets. + +## Amazon S3 Media Storage (`OrchardCore.Media.AmazonS3`) The feature replaces the default App_Data file-based media store with an Amazon Media Storage Provider. @@ -10,7 +12,7 @@ This allows the Amazon Media Storage feature to support image resizing on the fl The URL generated by the AssetUrl helpers points to the Orchard Core website. -## Configuration +### Configuration The following configuration values are used by default and can be customized: @@ -49,11 +51,11 @@ In case you are hosting Orchard Core outside of AWS, you should fill the `Creden You can find region endpoints in the [Official AWS S3 Documentation](https://docs.aws.amazon.com/general/latest/gr/s3.html), see Region column. For example for the Frankfurt region you should use `eu-central-1` -## AWS Credentials and its loading order +### AWS Credentials and its loading order `OrchardCore_Media_AmazonS3` is a subset of `AWSOptions` configuration and should be configured the same as a generic [AWSOptions](https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-config-netcore.html). -### Credentials loading order +#### Credentials loading order 1. Credentials property of `AWSOptions˙. 2. Shared Credentials File (Custom Location). When both the profile and profile location are specified. @@ -70,7 +72,7 @@ You can find region endpoints in the [Official AWS S3 Documentation](https://doc The AWS team wants to encourage using profiles instead of embedding credentials directly into `appsettings.X.json` files where they would accidentally get checked into source control. If you have an option to use profiles or environment variables - you should use it instead of direct credentials. -## AWS S3 Bucket Configuration +### AWS S3 Bucket Configuration If `CreateBucket` was configured as `true` and `BucketName` follows official [Bucket naming rules](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html), then a new bucket will be created. The new bucket will be created without [Access Control Lists](https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html) due to security reasons. If you create the bucket manually then you need to do it with ACLs enabled. When using a previously created bucket, you may need to configure ACLs manually: @@ -80,7 +82,7 @@ The new bucket will be created without [Access Control Lists](https://docs.aws.a 3. Edit "Block public access". 4. Tick "Block all public access". -### S3 Bucket policies +#### S3 Bucket policies By default, AWS 3S Bucket has limitations for newly uploaded files. If you want media files to be available from the outside of AWS, you should set up your bucket permissions. @@ -103,7 +105,7 @@ The simplest way of doing it is to add a policy: After this policy will be added to your bucket permissions all newly added files will have Read permission and will be available from the outside of the Amazon Cloud. -## Templating Configuration +### Templating Configuration Optionally you may use Liquid templating to further configure Amazon Media Storage, perhaps creating a bucket per tenant, or a single bucket with a base path per tenant. @@ -114,7 +116,7 @@ The `BucketName` property and the `BasePath` property are the only templatable p !!! note When templating the `BucketName` using `{{ ShellSettings.Name }}`, the tenant's name will be automatically lowercased, however, you must also make sure the `BucketName` conforms to other Amazon S3 naming conventions as set out in Amazon's documentation. -### Configuring a bucket per tenant +#### Configuring a bucket per tenant ```json { @@ -134,7 +136,7 @@ The `BucketName` property and the `BasePath` property are the only templatable p } ``` -### Configuring a single bucket, with a base folder per tenant +#### Configuring a single bucket, with a base folder per tenant ```json { @@ -154,7 +156,7 @@ The `BucketName` property and the `BasePath` property are the only templatable p } ``` -## Configuring a Local Emulator +### Configuring a Local Emulator During development, instead of using an online S3 resource, you can use a local storage emulator too. This is especially important for development teams, since you don't want to step on each others' feet by using a shared storage. @@ -199,7 +201,7 @@ docker run -d -e MODE=IN_MEMORY -p 9444:80 luofuxiang/local-s3 docker run -p 9444:9090 -t adobe/s3mock ``` -## Media Cache +### Media Cache The Media Cache feature will automatically be enabled when Amazon Media Storage is enabled. @@ -217,3 +219,40 @@ CDN providers also clear their caches at pre-determined times of their own devis !!! note The Media Feature is designed to support one storage provider at a time, whether that is local File Storage (the default), Azure Blob Storage, or Amazon S3 Storage. + +## Amazon Media ImageSharp Image Cache (`OrchardCore.Media.AmazonS3.ImageSharpImageCache`) + +The feature replaces the default `PhysicalFileSystemCache` of ImageSharp that stores resized images in the `App_Data` folder with [`AWSS3StorageCache`](https://docs.sixlabors.com/articles/imagesharp.web/imagecaches.html#awss3storagecache) that stores them in Amazon S3 storage. Depending on your use case, this can provide the following advantages: + +- Persistent image cache not to have to repeatedly resize images, even if the local file system of the webserver is ephemeral. This helps if you e.g. use containers to host the app, or do clean deployments to the webserver that remove all previously existing files. +- Better performance if disk IO is a bottleneck: The local storage of the webserver may be slow or access to it deliberately throttled. Using S3 can alleviate pressure on the local disk, leaving more resources available to serve other requests, as well as offer a higher request per second limit for image requests. + +!!! note + Cache files are only removed for a tenant when removing the tenant itself if you use a separate bucket for each tenant. + +### Configuration + +The following configuration values are used by default and can be customized: + +```json +{ + "OrchardCore": { + "OrchardCore_Media_AmazonS3_ImageSharp_Cache": { + "Region": "eu-central-1", + "Profile": "default", + "ProfilesLocation": "", + "Credentials": { + "SecretKey": "", + "AccessKey": "" + }, + "BasePath": "/media", // Optionally, set to a path to store media in a subdirectory inside your bucket. + "CreateBucket": true, + "RemoveBucket": true, // Whether the 'Bucket' is deleted if the tenant is removed, false by default. + "BucketName": "imagesharp" // Set the bucket's name (mandatory). + }, + } +} +``` + +!!! note + Templating the configuration and configuring a bucket per tenant, as well as using a local emulator work the same way as for Amazon S3 Media Storage; follow its documentation above. diff --git a/src/docs/reference/modules/Media.Azure/README.md b/src/docs/reference/modules/Media.Azure/README.md index cab803b4c35..57015578b2d 100644 --- a/src/docs/reference/modules/Media.Azure/README.md +++ b/src/docs/reference/modules/Media.Azure/README.md @@ -128,7 +128,8 @@ The feature replaces the default `PhysicalFileSystemCache` of ImageSharp that st - Persistent image cache not to have to repeatedly resize images, even if the local file system of the webserver is ephemeral. This helps if you e.g. use containers to host the app, or do clean deployments to the webserver that remove all previously existing files. - Better performance if disk IO is a bottleneck: The local storage of the webserver may be slow or access to it deliberately throttled, like it is the case with Azure App Services. Using Blob Storage can alleviate pressure on the local disk, leaving more resources available to serve other requests, as well as offer a higher request per second limit for image requests. -Note that since at the moment configuring a sub-path [is not supported by `AzureBlobStorageImageCache`](https://github.com/SixLabors/ImageSharp.Web/discussions/351), the cached files for all tenants are stored in the same Azure Blob Container. This means that purging the cache of just one tenant is not supported. Furthermore, cache files are only removed for a tenant when removing the tenant itself if you use a separate container for each tenant. +!!! note + Cache files are only removed for a tenant when removing the tenant itself if you use a separate container for each tenant. ### Configuration @@ -153,4 +154,5 @@ The following configuration values are used by default and can be customized: } ``` -Templating the configuration and configuring a container per tenant works the same way as for Azure Media Storage; follow its documentation above. \ No newline at end of file +!!! note + Templating the configuration and configuring a container per tenant work the same way as for Azure Media Storage; follow its documentation above. \ No newline at end of file diff --git a/src/docs/releases/1.9.0.md b/src/docs/releases/1.9.0.md index f00cd9fa914..9daa0cfbb09 100644 --- a/src/docs/releases/1.9.0.md +++ b/src/docs/releases/1.9.0.md @@ -88,10 +88,9 @@ Additionally, `Twilio` provider is no longer enabled by default. If you want to Introducing a new "Azure AI Search" module, designed to empower you in the administration of Azure AI Search indices. When enabled with the "Search" module, it facilitates frontend full-text search capabilities through Azure AI Search. For more info read the [Azure AI Search](../reference/modules/AzureAISearch/README.md) docs. -### Azure Media ImageSharp Image Cache - -The Microsoft Azure Media module has a new feature, Azure Media ImageSharp Image Cache. This replaces the default `PhysicalFileSystemCache` of ImageSharp that stores resized images in the `App_Data` folder with [`AzureBlobStorageImageCache`](https://docs.sixlabors.com/articles/imagesharp.web/imagecaches.html#azureblobstorageimagecache) that stores them in Azure Blob Storage. Depending on your use case, this can provide various advantages. Check out [the docs](../reference/modules/Media.Azure/README.md) for details. +### New ImageSharp Image Caches for Azure Blob and AWS S3 Storage +The Microsoft Azure Media and Amazon S3 Media modules have new features for replacing the default `PhysicalFileSystemCache` of ImageSharp that stores resized images in the local `App_Data` folder. Instead, you can now use Azure Blob Storage with the Azure Media ImageSharp Image Cache feature (that utilizes [`AzureBlobStorageImageCache`](https://docs.sixlabors.com/articles/imagesharp.web/imagecaches.html#azureblobstorageimagecache)), and AWS S3 with the Amazon Media ImageSharp Image Cache feature (that utilizes [`AWSS3StorageCache`](https://docs.sixlabors.com/articles/imagesharp.web/imagecaches.html#awss3storagecache)). Depending on your use case, this can provide various advantages. Check out [the Azure Media](../reference/modules/Media.Azure/README.md) and [the Amazon S3 Media](../reference/modules/Media.AmazonS3/README.md) docs for details. ### Deployment Module From 3a1d010cdb0321b79200f101b5a3c4d2b323bfa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Mon, 8 Apr 2024 01:33:08 +0200 Subject: [PATCH 42/50] Fixing docs navigation --- mkdocs.yml | 19 +++++++++++-------- src/docs/reference/README.md | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index abc0748e6e8..79feb22399e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -122,14 +122,14 @@ nav: - Navigate between pages: docs/topics/navigation/README.md - Query and Search data: docs/topics/search/README.md - Secure your application: docs/topics/security/README.md - # - Data: docs/topics/data/README.md - # - Configuration: docs/topics/configuration/README.md + - Data: docs/topics/data/README.md + - Configuration: docs/topics/configuration/README.md - Workflows: docs/topics/workflows/README.md - Contributing to the docs: docs/topics/docs-contributions/README.md - Publishing a new release: docs/topics/publishing-releases/README.md - Using Docker: docs/topics/docker/README.md - Using local NuGet packages: docs/topics/local-nuget-packages/README.md - - Managing the Orchard Core Red Hat Ecosystem Catalog certification: docs/topics/red-hat-ecosystem-catalog-certification/README.md + - Managing the Orchard Core Red Hat Ecosystem Catalog certification: docs/topics/red-hat-ecosystem-catalog-certification/README.md - Reference: - docs/reference/README.md - CMS Modules: @@ -156,11 +156,6 @@ nav: - Placements: docs/reference/modules/Placements/README.md - Themes: docs/reference/modules/Themes/README.md - Liquid: docs/reference/modules/Liquid/README.md - - Indexing: docs/reference/modules/Indexing/README.md - - SQL Indexing: docs/reference/modules/SQLIndexing/README.md - - Lucene: docs/reference/modules/Lucene/README.md - - Elasticsearch: docs/reference/modules/Elasticsearch/README.md - - Queries: docs/reference/modules/Queries/README.md - Media: - Media: docs/reference/modules/Media/README.md - Media Slugify: docs/reference/modules/Media.Slugify/README.md @@ -169,6 +164,13 @@ nav: - ReCaptcha: docs/reference/modules/ReCaptcha/README.md - Resources: docs/reference/modules/Resources/README.md - Rules: docs/reference/modules/Rules/README.md + - Search, Indexing, Querying: + - Azure AI Search: docs/reference/modules/AzureAISearch/README.md + - Elasticsearch: docs/reference/modules/Elasticsearch/README.md + - Indexing: docs/reference/modules/Indexing/README.md + - Lucene: docs/reference/modules/Lucene/README.md + - SQL Indexing: docs/reference/modules/SQLIndexing/README.md + - Queries: docs/reference/modules/Queries/README.md - Shortcodes: docs/reference/modules/Shortcodes/README.md - Sitemaps: docs/reference/modules/Sitemaps/README.md - SMS: docs/reference/modules/Sms/README.md @@ -206,6 +208,7 @@ nav: - Placement: docs/reference/core/Placement/README.md - Data: docs/reference/core/Data/README.md - Data Migrations: docs/reference/modules/Migrations/README.md + - Diagnostics: docs/reference/modules/Diagnostics/README.md - Dynamic Cache: docs/reference/modules/DynamicCache/README.md - Email: docs/reference/modules/Email/README.md - SMTP Provider: docs/reference/modules/Email.Smtp/README.md diff --git a/src/docs/reference/README.md b/src/docs/reference/README.md index 56dd5d5c6a6..341279e0fe6 100644 --- a/src/docs/reference/README.md +++ b/src/docs/reference/README.md @@ -138,7 +138,7 @@ Here's a categorized overview of all built-in Orchard Core features at a glance. ### Search, Indexing, Querying - [Azure AI Search](modules/AzureAISearch/README.md) -- [SQL](modules/SQLIndexing/README.md) +- [SQL Indexing](modules/SQLIndexing/README.md) - [Lucene](modules/Lucene/README.md) - [Elasticsearch](modules/Elasticsearch/README.md) - [Queries](modules/Queries/README.md) From 5fc658b01af820eb24f518bad92b024e54873f4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Mon, 8 Apr 2024 01:40:27 +0200 Subject: [PATCH 43/50] DRY --- .../Services/AwsTenantEventsBase.cs | 1 - .../ImageSharpBlobImageCacheOptions.cs | 34 +------------------ .../MediaBlobStorageOptions.cs | 13 +------ .../MediaBlobStorageOptionsBase.cs | 16 +++++++++ 4 files changed, 18 insertions(+), 46 deletions(-) create mode 100644 src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptionsBase.cs diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsTenantEventsBase.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsTenantEventsBase.cs index 78f38b9ceee..187eb392f4d 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsTenantEventsBase.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsTenantEventsBase.cs @@ -130,4 +130,3 @@ public override async Task RemovingAsync(ShellRemovingContext context) } } } - diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpBlobImageCacheOptions.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpBlobImageCacheOptions.cs index 74c266ac544..b8d3df1f91a 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpBlobImageCacheOptions.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/ImageSharpBlobImageCacheOptions.cs @@ -1,37 +1,5 @@ namespace OrchardCore.Media.Azure; -public class ImageSharpBlobImageCacheOptions +public class ImageSharpBlobImageCacheOptions : MediaBlobStorageOptionsBase { - /// - /// The Azure Blob connection string. - /// - public string ConnectionString { get; set; } - - /// - /// The Azure Blob container name. - /// - public string ContainerName { get; set; } - - /// - /// Create Blob container on startup if one does not exist. - /// - public bool CreateContainer { get; set; } = true; - - /// - /// The base directory path to use inside the container for this store's content. - /// - public string BasePath { get; set; } = ""; - - /// - /// Remove Blob container on tenant removal if it exists. - /// - public bool RemoveContainer { get; set; } - - /// - /// Returns a value indicating whether the basic state of the configuration is valid. - /// - public bool IsValid() - { - return !string.IsNullOrEmpty(ConnectionString) && !string.IsNullOrEmpty(ContainerName); - } } diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptions.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptions.cs index 06c6f595986..2d08de06bdc 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptions.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptions.cs @@ -1,16 +1,5 @@ -using OrchardCore.FileStorage.AzureBlob; - namespace OrchardCore.Media.Azure; -public class MediaBlobStorageOptions : BlobStorageOptions +public class MediaBlobStorageOptions : MediaBlobStorageOptionsBase { - /// - /// Create a Blob container on startup if one does not exist. - /// - public bool CreateContainer { get; set; } = true; - - /// - /// Remove Blob container on tenant removal if it exists. - /// - public bool RemoveContainer { get; set; } } diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptionsBase.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptionsBase.cs new file mode 100644 index 00000000000..3ed7051c8bd --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptionsBase.cs @@ -0,0 +1,16 @@ +using OrchardCore.FileStorage.AzureBlob; + +namespace OrchardCore.Media.Azure; + +public abstract class MediaBlobStorageOptionsBase : BlobStorageOptions +{ + /// + /// Create a Blob container on startup if one does not exist. + /// + public bool CreateContainer { get; set; } = true; + + /// + /// Remove Blob container on tenant removal if it exists. + /// + public bool RemoveContainer { get; set; } +} From 14750b155ffe3655eb1c11c678784c86ac3c3da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Mon, 15 Apr 2024 15:34:29 +0200 Subject: [PATCH 44/50] Fixing new analyzer violations --- .../Helpers/OptionsFluidParserHelper.cs | 2 +- .../Services/AWSS3StorageCacheOptionsConfiguration.cs | 2 +- .../Helpers/OptionsFluidParserHelper.cs | 2 +- .../Services/AzureBlobStorageCacheOptionsConfiguration.cs | 2 +- .../Services/ImageSharpBlobImageCacheOptionsConfiguration.cs | 2 +- .../Services/ImageSharpBlobImageCacheTenantEvents.cs | 4 ++-- .../Services/MediaBlobStorageOptionsConfiguration.cs | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/OptionsFluidParserHelper.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/OptionsFluidParserHelper.cs index e986c250242..3326aa8c556 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/OptionsFluidParserHelper.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Helpers/OptionsFluidParserHelper.cs @@ -4,7 +4,7 @@ namespace OrchardCore.Media.AmazonS3.Helpers; // This is almost the same as in OrchardCore.Media.Azure but there isn't really a good common place for it. -internal class OptionsFluidParserHelper where TOptions : class +internal sealed class OptionsFluidParserHelper where TOptions : class { // Local instance since it can be discarded once the startup is over. private readonly FluidParser _fluidParser = new(); diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AWSS3StorageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AWSS3StorageCacheOptionsConfiguration.cs index 1a803f7a595..9aac7e289f1 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AWSS3StorageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AWSS3StorageCacheOptionsConfiguration.cs @@ -6,7 +6,7 @@ namespace OrchardCore.Media.AmazonS3.Services; // Configuration for ImageSharp's own configuration object. We just pass the settings from the Orchard Core // configuration. -internal class AWSS3StorageCacheOptionsConfiguration : IConfigureOptions +internal sealed class AWSS3StorageCacheOptionsConfiguration : IConfigureOptions { private readonly AwsImageSharpImageCacheOptions _options; diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/OptionsFluidParserHelper.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/OptionsFluidParserHelper.cs index 5b7e8afed08..c99a83cf036 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/OptionsFluidParserHelper.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Helpers/OptionsFluidParserHelper.cs @@ -4,7 +4,7 @@ namespace OrchardCore.Media.Azure.Helpers; // This is almost the same as in OrchardCore.Media.Azure but there isn't really a good common place for it. -internal class OptionsFluidParserHelper where TOptions : class +internal sealed class OptionsFluidParserHelper where TOptions : class { // Local instance since it can be discarded once the startup is over. private readonly FluidParser _fluidParser = new(); diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs index 7b43eda21ad..873e81452dd 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/AzureBlobStorageCacheOptionsConfiguration.cs @@ -5,7 +5,7 @@ namespace OrchardCore.Media.Azure.Services; // Configuration for ImageSharp's own configuration object. We just pass the settings from the Orchard Core // configuration. -internal class AzureBlobStorageCacheOptionsConfiguration : IConfigureOptions +internal sealed class AzureBlobStorageCacheOptionsConfiguration : IConfigureOptions { private readonly ImageSharpBlobImageCacheOptions _options; diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheOptionsConfiguration.cs index e8e2de58146..45cb4eeda6f 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheOptionsConfiguration.cs @@ -8,7 +8,7 @@ namespace OrchardCore.Media.Azure.Services; -internal class ImageSharpBlobImageCacheOptionsConfiguration : IConfigureOptions +internal sealed class ImageSharpBlobImageCacheOptionsConfiguration : IConfigureOptions { private readonly IShellConfiguration _shellConfiguration; private readonly ShellSettings _shellSettings; diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheTenantEvents.cs index f99780a7a76..0a5d2c073c5 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheTenantEvents.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheTenantEvents.cs @@ -11,13 +11,13 @@ namespace OrchardCore.Media.Azure.Services; -internal class ImageSharpBlobImageCacheTenantEvents : ModularTenantEvents +internal sealed class ImageSharpBlobImageCacheTenantEvents : ModularTenantEvents { private readonly ImageSharpBlobImageCacheOptions _options; private readonly ShellSettings _shellSettings; private readonly ILogger _logger; - protected readonly IStringLocalizer S; + private readonly IStringLocalizer S; public ImageSharpBlobImageCacheTenantEvents( IOptions options, diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs index 05af692d9d8..0d5d243a116 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobStorageOptionsConfiguration.cs @@ -8,7 +8,7 @@ namespace OrchardCore.Media.Azure.Services; -internal class MediaBlobStorageOptionsConfiguration : IConfigureOptions +internal sealed class MediaBlobStorageOptionsConfiguration : IConfigureOptions { private readonly IShellConfiguration _shellConfiguration; private readonly ShellSettings _shellSettings; From 3a797abd485f00f07f7b39a79f26a85426cb54f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Mon, 15 Apr 2024 19:25:02 +0200 Subject: [PATCH 45/50] Removing unnecessary interface --- .../AwsStorageOptionsExtension.cs | 4 +-- .../Services/AwsTenantEventsBase.cs | 4 +-- .../AwsStorageOptionsBase.cs | 26 ++++++++------ .../IAwsStorageOptions.cs | 34 ------------------- 4 files changed, 20 insertions(+), 48 deletions(-) delete mode 100644 src/OrchardCore/OrchardCore.FileStorage.AmazonS3/IAwsStorageOptions.cs diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsExtension.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsExtension.cs index 2148a769742..020daabedcc 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsExtension.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsExtension.cs @@ -12,7 +12,7 @@ namespace OrchardCore.Media.AmazonS3; public static class AwsStorageOptionsExtension { - public static IEnumerable Validate(this IAwsStorageOptions options) + public static IEnumerable Validate(this AwsStorageOptionsBase options) { if (string.IsNullOrWhiteSpace(options.BucketName)) { @@ -28,7 +28,7 @@ public static IEnumerable Validate(this IAwsStorageOptions opt } } - public static IAwsStorageOptions BindConfiguration(this IAwsStorageOptions options, string configSection, IShellConfiguration shellConfiguration, ILogger logger) + public static AwsStorageOptionsBase BindConfiguration(this AwsStorageOptionsBase options, string configSection, IShellConfiguration shellConfiguration, ILogger logger) { var section = shellConfiguration.GetSection(configSection); diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsTenantEventsBase.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsTenantEventsBase.cs index 187eb392f4d..17129325272 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsTenantEventsBase.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsTenantEventsBase.cs @@ -14,7 +14,7 @@ namespace OrchardCore.Media.AmazonS3.Services; public abstract class AwsTenantEventsBase : ModularTenantEvents { private readonly ShellSettings _shellSettings; - private readonly IAwsStorageOptions _options; + private readonly AwsStorageOptionsBase _options; private readonly IAmazonS3 _amazonS3Client; private readonly IStringLocalizer S; private readonly ILogger _logger; @@ -22,7 +22,7 @@ public abstract class AwsTenantEventsBase : ModularTenantEvents protected AwsTenantEventsBase( ShellSettings shellSettings, IAmazonS3 amazonS3Client, - IAwsStorageOptions options, + AwsStorageOptionsBase options, IStringLocalizer localizer, ILogger logger) { diff --git a/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsStorageOptionsBase.cs b/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsStorageOptionsBase.cs index 768d0c24c13..7a52c29f040 100644 --- a/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsStorageOptionsBase.cs +++ b/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsStorageOptionsBase.cs @@ -2,24 +2,30 @@ namespace OrchardCore.FileStorage.AmazonS3; -public abstract class AwsStorageOptionsBase : IAwsStorageOptions +public abstract class AwsStorageOptionsBase { - /// + /// + /// AWS S3 bucket name. + /// public string BucketName { get; set; } - /// - + /// + /// The base directory path to use inside the container for this store's contents. + /// public string BasePath { get; set; } - /// - + /// + /// Indicates if bucket should be created on module startup. + /// public bool CreateBucket { get; set; } - /// - + /// + /// Gets or sets the AWS Options. + /// public AWSOptions AwsOptions { get; set; } - /// - + /// + /// Indicates if bucket should be removed on tenant removal. + /// public bool RemoveBucket { get; set; } } diff --git a/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/IAwsStorageOptions.cs b/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/IAwsStorageOptions.cs deleted file mode 100644 index 81f406e87f3..00000000000 --- a/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/IAwsStorageOptions.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Amazon.Extensions.NETCore.Setup; - -namespace OrchardCore.FileStorage.AmazonS3; - -/// -/// Contract for all types providing options to AWS storage. -/// -public interface IAwsStorageOptions -{ - /// - /// AWS S3 bucket name. - /// - string BucketName { get; set; } - - /// - /// The base directory path to use inside the container for this store's contents. - /// - string BasePath { get; set; } - - /// - /// Indicates if bucket should be created on module startup. - /// - bool CreateBucket { get; set; } - - /// - /// Gets or sets the AWS Options. - /// - AWSOptions AwsOptions { get; set; } - - /// - /// Indicates if bucket should be removed on tenant removal. - /// - bool RemoveBucket { get; set; } -} From d6efce8cd82115b775069e68cc467d804d47d282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Mon, 15 Apr 2024 19:29:02 +0200 Subject: [PATCH 46/50] Seal of approval --- .../Services/AwsImageSharpImageCacheOptionsConfiguration.cs | 2 +- .../Services/AwsStorageOptionsConfiguration.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs index 87c4de5a591..3a3ac391bf9 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs @@ -8,7 +8,7 @@ namespace OrchardCore.Media.AmazonS3.Services; -public class AwsImageSharpImageCacheOptionsConfiguration : IConfigureOptions +internal sealed class AwsImageSharpImageCacheOptionsConfiguration : IConfigureOptions { private readonly IShellConfiguration _shellConfiguration; private readonly ShellSettings _shellSettings; diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs index a42bc95d22a..650fe4a5362 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs @@ -8,7 +8,7 @@ namespace OrchardCore.Media.AmazonS3.Services; -public class AwsStorageOptionsConfiguration : IConfigureOptions +internal sealed class AwsStorageOptionsConfiguration : IConfigureOptions { private readonly IShellConfiguration _shellConfiguration; private readonly ShellSettings _shellSettings; From 988e7d20fddf936ce53b921dcb2b346a893c251b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Mon, 15 Apr 2024 19:42:42 +0200 Subject: [PATCH 47/50] Adding common const for the Media module's Startup --- .../OrchardCore.Media.AmazonS3.csproj | 1 + .../OrchardCore.Media.AmazonS3/Startup.cs | 2 +- .../OrchardCore.Media.Azure.csproj | 1 + .../OrchardCore.Media.Azure/Startup.cs | 4 ++-- src/OrchardCore.Modules/OrchardCore.Media/Constants.cs | 10 ++++++++++ src/OrchardCore.Modules/OrchardCore.Media/Startup.cs | 2 ++ 6 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 src/OrchardCore.Modules/OrchardCore.Media/Constants.cs diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/OrchardCore.Media.AmazonS3.csproj b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/OrchardCore.Media.AmazonS3.csproj index fa21db633b8..24da12ec92f 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/OrchardCore.Media.AmazonS3.csproj +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/OrchardCore.Media.AmazonS3.csproj @@ -24,6 +24,7 @@ + diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs index 62117fae805..b8b6275ac69 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs @@ -152,7 +152,7 @@ public ImageSharpAmazonS3CacheStartup( // The order should exceed that of the 'OrchardCore.Media' module to substitute the default implementation of 'IImageCache'. // there. - public override int Order => 5; + public override int Order => Media.Constants.StartupOrder + 5; public override void ConfigureServices(IServiceCollection services) { diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/OrchardCore.Media.Azure.csproj b/src/OrchardCore.Modules/OrchardCore.Media.Azure/OrchardCore.Media.Azure.csproj index e468f717649..11b796956a9 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/OrchardCore.Media.Azure.csproj +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/OrchardCore.Media.Azure.csproj @@ -23,6 +23,7 @@ + diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs index 887c4587354..497ca6d3a04 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs @@ -36,7 +36,7 @@ public Startup(ILogger logger, IShellConfiguration configuration) _configuration = configuration; } - public override int Order => 10; + public override int Order => Constants.StartupOrder + 10; public override void ConfigureServices(IServiceCollection services) { @@ -158,7 +158,7 @@ public ImageSharpAzureBlobCacheStartup( // The order should exceed that of the 'OrchardCore.Media' module to substitute the default implementation of 'IImageCache'. // there. - public override int Order => 5; + public override int Order => Constants.StartupOrder + 5; public override void ConfigureServices(IServiceCollection services) { diff --git a/src/OrchardCore.Modules/OrchardCore.Media/Constants.cs b/src/OrchardCore.Modules/OrchardCore.Media/Constants.cs new file mode 100644 index 00000000000..9a5ed9d116c --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Media/Constants.cs @@ -0,0 +1,10 @@ +namespace OrchardCore.Media; + +public static class Constants +{ + /// + /// The value the Order set by . If you want to run a Startup class before or after + /// that of the Media module's, you can use this value as the starting point. + /// + public const int StartupOrder = 0; +} diff --git a/src/OrchardCore.Modules/OrchardCore.Media/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Media/Startup.cs index 58c196e338b..5235609a883 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/Startup.cs @@ -55,6 +55,8 @@ namespace OrchardCore.Media { public class Startup : StartupBase { + public override int Order => Constants.StartupOrder; + private const string ImageSharpCacheFolder = "is-cache"; private readonly ShellSettings _shellSettings; From 5bc8efb98c828ae9cd42d5552ce9c00cd4f9f5ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Mon, 15 Apr 2024 19:46:25 +0200 Subject: [PATCH 48/50] Renaming IsValid() to IsConfigured() --- .../Services/ImageSharpBlobImageCacheTenantEvents.cs | 4 ++-- .../Services/MediaBlobContainerTenantEvents.cs | 4 ++-- .../OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheTenantEvents.cs index 0a5d2c073c5..ecd255ed6c7 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheTenantEvents.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/ImageSharpBlobImageCacheTenantEvents.cs @@ -34,7 +34,7 @@ public ImageSharpBlobImageCacheTenantEvents( public override async Task ActivatingAsync() { // Only create container if options are valid. - if (_shellSettings.IsUninitialized() || !_options.IsValid() || !_options.CreateContainer) + if (_shellSettings.IsUninitialized() || !_options.IsConfigured() || !_options.CreateContainer) { return; } @@ -57,7 +57,7 @@ public override async Task ActivatingAsync() public override async Task RemovingAsync(ShellRemovingContext context) { // Only remove container if options are valid. - if (!_options.RemoveContainer || !_options.IsValid()) + if (!_options.RemoveContainer || !_options.IsConfigured()) { return; } diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs index 20bd861f532..3fd2d6568b8 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Services/MediaBlobContainerTenantEvents.cs @@ -35,7 +35,7 @@ ILogger logger public override async Task ActivatingAsync() { // Only create container if options are valid. - if (_shellSettings.IsUninitialized() || !_options.IsValid() || !_options.CreateContainer) + if (_shellSettings.IsUninitialized() || !_options.IsConfigured() || !_options.CreateContainer) { return; } @@ -58,7 +58,7 @@ public override async Task ActivatingAsync() public override async Task RemovingAsync(ShellRemovingContext context) { // Only remove container if options are valid. - if (!_options.RemoveContainer || !_options.IsValid()) + if (!_options.RemoveContainer || !_options.IsConfigured()) { return; } diff --git a/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs index bc577d2de04..2b94e2c8971 100644 --- a/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs +++ b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs @@ -20,7 +20,7 @@ public abstract class BlobStorageOptions /// /// Returns a value indicating whether the basic state of the configuration is valid. /// - public virtual bool IsValid() + public virtual bool IsConfigured() { return !string.IsNullOrEmpty(ConnectionString) && !string.IsNullOrEmpty(ContainerName); } From 00418e768f4c26359ef23f40d7cb892967c3780a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Mon, 15 Apr 2024 21:10:05 +0200 Subject: [PATCH 49/50] More specific names for const classes --- .../{Constants.cs => AmazonS3Constants.cs} | 2 +- .../AwsStorageOptionsExtension.cs | 8 ++++---- .../AwsImageSharpImageCacheOptionsConfiguration.cs | 2 +- .../Services/AwsStorageOptionsConfiguration.cs | 2 +- .../OrchardCore.Media.AmazonS3/Startup.cs | 6 +++--- .../OrchardCore.Media.Azure/Startup.cs | 4 ++-- .../OrchardCore.Media/{Constants.cs => MediaConstants.cs} | 2 +- src/OrchardCore.Modules/OrchardCore.Media/Startup.cs | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) rename src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/{Constants.cs => AmazonS3Constants.cs} (94%) rename src/OrchardCore.Modules/OrchardCore.Media/{Constants.cs => MediaConstants.cs} (89%) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Constants.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AmazonS3Constants.cs similarity index 94% rename from src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Constants.cs rename to src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AmazonS3Constants.cs index c0a15f093ca..8c456fe0586 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Constants.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AmazonS3Constants.cs @@ -1,6 +1,6 @@ namespace OrchardCore.Media.AmazonS3; -internal static class Constants +internal static class AmazonS3Constants { internal static class ValidationMessages { diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsExtension.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsExtension.cs index 020daabedcc..c9e6d8472ee 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsExtension.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/AwsStorageOptionsExtension.cs @@ -16,14 +16,14 @@ public static IEnumerable Validate(this AwsStorageOptionsBase { if (string.IsNullOrWhiteSpace(options.BucketName)) { - yield return new ValidationResult(Constants.ValidationMessages.BucketNameIsEmpty); + yield return new ValidationResult(AmazonS3Constants.ValidationMessages.BucketNameIsEmpty); } if (options.AwsOptions is not null) { if (options.AwsOptions.Region is null && options.AwsOptions.DefaultClientConfig.ServiceURL is null) { - yield return new ValidationResult(Constants.ValidationMessages.RegionAndServiceUrlAreEmpty); + yield return new ValidationResult(AmazonS3Constants.ValidationMessages.RegionAndServiceUrlAreEmpty); } } } @@ -53,8 +53,8 @@ public static AwsStorageOptionsBase BindConfiguration(this AwsStorageOptionsBase var credentials = section.GetSection("Credentials"); if (credentials.Exists()) { - var secretKey = credentials.GetValue(Constants.AwsCredentialParamNames.SecretKey, string.Empty); - var accessKey = credentials.GetValue(Constants.AwsCredentialParamNames.AccessKey, string.Empty); + var secretKey = credentials.GetValue(AmazonS3Constants.AwsCredentialParamNames.SecretKey, string.Empty); + var accessKey = credentials.GetValue(AmazonS3Constants.AwsCredentialParamNames.AccessKey, string.Empty); if (!string.IsNullOrWhiteSpace(accessKey) || !string.IsNullOrWhiteSpace(secretKey)) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs index 3a3ac391bf9..9ef22cd195e 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsImageSharpImageCacheOptionsConfiguration.cs @@ -26,7 +26,7 @@ public AwsImageSharpImageCacheOptionsConfiguration( public void Configure(AwsImageSharpImageCacheOptions options) { - options.BindConfiguration(Constants.ConfigSections.AmazonS3ImageSharpCache, _shellConfiguration, _logger); + options.BindConfiguration(AmazonS3Constants.ConfigSections.AmazonS3ImageSharpCache, _shellConfiguration, _logger); var fluidParserHelper = new OptionsFluidParserHelper(_shellSettings); diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs index 650fe4a5362..3ee2934d4a6 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Services/AwsStorageOptionsConfiguration.cs @@ -26,7 +26,7 @@ public AwsStorageOptionsConfiguration( public void Configure(AwsStorageOptions options) { - options.BindConfiguration(Constants.ConfigSections.AmazonS3, _shellConfiguration, _logger); + options.BindConfiguration(AmazonS3Constants.ConfigSections.AmazonS3, _shellConfiguration, _logger); var fluidParserHelper = new OptionsFluidParserHelper(_shellSettings); diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs index b8b6275ac69..33b8d8cd2a2 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Startup.cs @@ -41,7 +41,7 @@ public override void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddTransient, AwsStorageOptionsConfiguration>(); - var storeOptions = new AwsStorageOptions().BindConfiguration(Constants.ConfigSections.AmazonS3, _configuration, _logger); + var storeOptions = new AwsStorageOptions().BindConfiguration(AmazonS3Constants.ConfigSections.AmazonS3, _configuration, _logger); var validationErrors = storeOptions.Validate().ToList(); var stringBuilder = new StringBuilder(); @@ -152,14 +152,14 @@ public ImageSharpAmazonS3CacheStartup( // The order should exceed that of the 'OrchardCore.Media' module to substitute the default implementation of 'IImageCache'. // there. - public override int Order => Media.Constants.StartupOrder + 5; + public override int Order => Media.MediaConstants.StartupOrder + 5; public override void ConfigureServices(IServiceCollection services) { services.AddTransient, AwsImageSharpImageCacheOptionsConfiguration>(); services.AddTransient, AWSS3StorageCacheOptionsConfiguration>(); - var storeOptions = new AwsStorageOptions().BindConfiguration(Constants.ConfigSections.AmazonS3ImageSharpCache, _configuration, _logger); + var storeOptions = new AwsStorageOptions().BindConfiguration(AmazonS3Constants.ConfigSections.AmazonS3ImageSharpCache, _configuration, _logger); var validationErrors = storeOptions.Validate().ToList(); var stringBuilder = new StringBuilder(); diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs index 497ca6d3a04..f7f37542ec4 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/Startup.cs @@ -36,7 +36,7 @@ public Startup(ILogger logger, IShellConfiguration configuration) _configuration = configuration; } - public override int Order => Constants.StartupOrder + 10; + public override int Order => MediaConstants.StartupOrder + 10; public override void ConfigureServices(IServiceCollection services) { @@ -158,7 +158,7 @@ public ImageSharpAzureBlobCacheStartup( // The order should exceed that of the 'OrchardCore.Media' module to substitute the default implementation of 'IImageCache'. // there. - public override int Order => Constants.StartupOrder + 5; + public override int Order => MediaConstants.StartupOrder + 5; public override void ConfigureServices(IServiceCollection services) { diff --git a/src/OrchardCore.Modules/OrchardCore.Media/Constants.cs b/src/OrchardCore.Modules/OrchardCore.Media/MediaConstants.cs similarity index 89% rename from src/OrchardCore.Modules/OrchardCore.Media/Constants.cs rename to src/OrchardCore.Modules/OrchardCore.Media/MediaConstants.cs index 9a5ed9d116c..3584f67cce2 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/Constants.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/MediaConstants.cs @@ -1,6 +1,6 @@ namespace OrchardCore.Media; -public static class Constants +public static class MediaConstants { /// /// The value the Order set by . If you want to run a Startup class before or after diff --git a/src/OrchardCore.Modules/OrchardCore.Media/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Media/Startup.cs index 5235609a883..01e4fd1da3a 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/Startup.cs @@ -55,7 +55,7 @@ namespace OrchardCore.Media { public class Startup : StartupBase { - public override int Order => Constants.StartupOrder; + public override int Order => MediaConstants.StartupOrder; private const string ImageSharpCacheFolder = "is-cache"; From d40f1ba45f2316e2c521c0dd3466eb21ef09de36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Mon, 15 Apr 2024 21:12:36 +0200 Subject: [PATCH 50/50] Better feature description --- src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Manifest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Manifest.cs b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Manifest.cs index 04cbabec722..88a5d8ec543 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Manifest.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.AmazonS3/Manifest.cs @@ -21,7 +21,7 @@ [assembly: Feature( Id = "OrchardCore.Media.AmazonS3.ImageSharpImageCache", Name = "Amazon Media ImageSharp Image Cache", - Description = "Enables support for storing cached images resized via ImageSharp in Amazon S3.", + Description = "Provides storage of ImageSharp-generated images within the Amazon S3 storage service.", Dependencies = [ "OrchardCore.Media",