From 4b6620336f7f68abc90e38468b646d9e2d794039 Mon Sep 17 00:00:00 2001 From: jtkech Date: Fri, 24 Nov 2023 09:10:29 +0100 Subject: [PATCH 1/5] Azure DP Initializer --- .../BlobOptions.cs | 9 +++ .../Startup.cs | 63 +++++++++++-------- 2 files changed, 46 insertions(+), 26 deletions(-) create mode 100644 src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptions.cs diff --git a/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptions.cs b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptions.cs new file mode 100644 index 00000000000..d42f751041f --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptions.cs @@ -0,0 +1,9 @@ +namespace OrchardCore.DataProtection.Azure; + +public class BlobOptions +{ + public string ConnectionString { get; set; } + public string ContainerName { get; set; } = "dataprotection"; + public string BlobName { get; set; } + public bool CreateContainer { get; set; } = true; +} diff --git a/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs index 957820a2d29..78f83c78a78 100644 --- a/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs @@ -16,6 +16,7 @@ namespace OrchardCore.DataProtection.Azure { public class Startup : StartupBase { + private readonly IShellConfiguration _configuration; private readonly ShellOptions _shellOptions; private readonly ShellSettings _shellSettings; private readonly ILogger _logger; @@ -24,10 +25,12 @@ public class Startup : StartupBase private readonly FluidParser _fluidParser = new(); public Startup( + IShellConfiguration configuration, IOptions shellOptions, ShellSettings shellSettings, ILogger logger) { + _configuration = configuration; _shellOptions = shellOptions.Value; _shellSettings = shellSettings; _logger = logger; @@ -35,33 +38,43 @@ public Startup( public override void ConfigureServices(IServiceCollection services) { - services.Initialize(async sp => - { - var configuration = sp.GetRequiredService(); - - var connectionString = configuration.GetValue("OrchardCore_DataProtection_Azure:ConnectionString"); - - if (!string.IsNullOrWhiteSpace(connectionString)) - { - var containerName = await GetBlobContainerNameAsync(configuration, connectionString); + var options = new BlobOptions(); - services.AddDataProtection() - .PersistKeysToAzureBlobStorage(connectionString, containerName, await GetBlobNameAsync(configuration)); - } - else + _configuration.Bind("OrchardCore_DataProtection_Azure", options); + if (!string.IsNullOrWhiteSpace(options.ConnectionString)) + { + services + .AddSingleton(options) + .AddDataProtection() + .PersistKeysToAzureBlobStorage(sp => + { + var options = sp.GetRequiredService(); + return new BlobClient( + options.ConnectionString, + options.ContainerName, + options.BlobName); + }); + + services.Initialize(async sp => { - _logger.LogCritical("No connection string was supplied for OrchardCore.DataProtection.Azure. Ensure that an application setting containing a valid Azure Storage connection string is available at `Modules:OrchardCore.DataProtection.Azure:ConnectionString`."); - } - }); + var options = sp.GetRequiredService(); + var configuration = sp.GetRequiredService(); + options.ContainerName = await GetBlobContainerNameAsync(options); + options.BlobName = await GetBlobNameAsync(options); + }); + } + else + { + _logger.LogCritical("No connection string was supplied for OrchardCore.DataProtection.Azure. Ensure that an application setting containing a valid Azure Storage connection string is available at `Modules:OrchardCore.DataProtection.Azure:ConnectionString`."); + } } - private async Task GetBlobContainerNameAsync(IShellConfiguration configuration, string connectionString) + private async Task GetBlobContainerNameAsync(BlobOptions options) { - var containerName = configuration.GetValue("OrchardCore_DataProtection_Azure:ContainerName", "dataprotection"); - - // Use Fluid directly as the service provider has not been built. + var containerName = options.ContainerName; try { + // Use Fluid directly as the service provider has not been built. var templateOptions = new TemplateOptions(); templateOptions.MemberAccessStrategy.Register(); var templateContext = new TemplateContext(templateOptions); @@ -79,13 +92,12 @@ private async Task GetBlobContainerNameAsync(IShellConfiguration configu throw; } - var createContainer = configuration.GetValue("OrchardCore_DataProtection_Azure:CreateContainer", true); - if (createContainer) + if (options.CreateContainer) { try { _logger.LogDebug("Testing data protection container {ContainerName} existence", containerName); - var _blobContainer = new BlobContainerClient(connectionString, containerName); + var _blobContainer = new BlobContainerClient(options.ConnectionString, containerName); var response = await _blobContainer.CreateIfNotExistsAsync(PublicAccessType.None); _logger.LogDebug("Data protection container {ContainerName} created.", containerName); } @@ -100,10 +112,9 @@ private async Task GetBlobContainerNameAsync(IShellConfiguration configu return containerName; } - private async Task GetBlobNameAsync(IShellConfiguration configuration) + private async Task GetBlobNameAsync(BlobOptions options) { - var blobName = configuration.GetValue("OrchardCore_DataProtection_Azure:BlobName"); - + var blobName = options.BlobName; if (string.IsNullOrEmpty(blobName)) { blobName = $"{_shellOptions.ShellsContainerName}/{_shellSettings.Name}/DataProtectionKeys.xml"; From df9c89df2e63c6ac40125aa3577c8d08bc72a81d Mon Sep 17 00:00:00 2001 From: jtkech Date: Fri, 24 Nov 2023 10:14:41 +0100 Subject: [PATCH 2/5] tweak --- .../Startup.cs | 79 +++++++++---------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs index 78f83c78a78..9310397062e 100644 --- a/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs @@ -17,22 +17,14 @@ namespace OrchardCore.DataProtection.Azure public class Startup : StartupBase { private readonly IShellConfiguration _configuration; - private readonly ShellOptions _shellOptions; - 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 Startup( - IShellConfiguration configuration, - IOptions shellOptions, - ShellSettings shellSettings, - ILogger logger) + public Startup(IShellConfiguration configuration, ILogger logger) { _configuration = configuration; - _shellOptions = shellOptions.Value; - _shellSettings = shellSettings; _logger = logger; } @@ -57,10 +49,15 @@ public override void ConfigureServices(IServiceCollection services) services.Initialize(async sp => { - var options = sp.GetRequiredService(); - var configuration = sp.GetRequiredService(); - options.ContainerName = await GetBlobContainerNameAsync(options); - options.BlobName = await GetBlobNameAsync(options); + var blobOptions = sp.GetRequiredService(); + var shellOptions = sp.GetRequiredService>().Value; + var shellSettings = sp.GetRequiredService(); + + var fluidParser = new FluidParser(); + var logger = sp.GetRequiredService>(); + + await ConfigureContainerNameAsync(blobOptions, shellSettings, fluidParser, logger); + await ConfigureBlobNameAsync(blobOptions, shellOptions, shellSettings, fluidParser, logger); }); } else @@ -69,55 +66,59 @@ public override void ConfigureServices(IServiceCollection services) } } - private async Task GetBlobContainerNameAsync(BlobOptions options) + private static async Task ConfigureContainerNameAsync( + BlobOptions blobOptions, + ShellSettings shellSettings, + FluidParser fluidParser, + ILogger logger) { - var containerName = options.ContainerName; try { // Use Fluid directly as the service provider has not been built. var templateOptions = new TemplateOptions(); templateOptions.MemberAccessStrategy.Register(); var templateContext = new TemplateContext(templateOptions); - templateContext.SetValue("ShellSettings", _shellSettings); + templateContext.SetValue("ShellSettings", shellSettings); - var template = _fluidParser.Parse(containerName); + var template = fluidParser.Parse(blobOptions.ContainerName); // container name must be lowercase - containerName = template.Render(templateContext, NullEncoder.Default).ToLower(); - containerName = containerName.Replace("\r", string.Empty).Replace("\n", string.Empty); + var containerName = template.Render(templateContext, NullEncoder.Default).ToLower(); + blobOptions.ContainerName = containerName.Replace("\r", string.Empty).Replace("\n", string.Empty); } catch (Exception e) { - _logger.LogCritical(e, "Unable to parse data protection connection string."); + logger.LogCritical(e, "Unable to parse data protection connection string."); throw; } - if (options.CreateContainer) + if (blobOptions.CreateContainer) { try { - _logger.LogDebug("Testing data protection container {ContainerName} existence", containerName); - var _blobContainer = new BlobContainerClient(options.ConnectionString, containerName); - var response = await _blobContainer.CreateIfNotExistsAsync(PublicAccessType.None); - _logger.LogDebug("Data protection container {ContainerName} created.", containerName); + logger.LogDebug("Testing data protection container {ContainerName} existence", blobOptions.ContainerName); + var blobContainer = new BlobContainerClient(blobOptions.ConnectionString, blobOptions.ContainerName); + var response = await blobContainer.CreateIfNotExistsAsync(PublicAccessType.None); + logger.LogDebug("Data protection container {ContainerName} created.", blobOptions.ContainerName); } catch (Exception) { - _logger.LogCritical("Unable to connect to Azure Storage to configure data protection storage. Ensure that an application setting containing a valid Azure Storage connection string is available at `Modules:OrchardCore.DataProtection.Azure:ConnectionString`."); - + logger.LogCritical("Unable to connect to Azure Storage to configure data protection storage. Ensure that an application setting containing a valid Azure Storage connection string is available at `Modules:OrchardCore.DataProtection.Azure:ConnectionString`."); throw; } } - - return containerName; } - private async Task GetBlobNameAsync(BlobOptions options) + private static async Task ConfigureBlobNameAsync( + BlobOptions blobOptions, + ShellOptions shellOptions, + ShellSettings shellSettings, + FluidParser fluidParser, + ILogger logger) { - var blobName = options.BlobName; - if (string.IsNullOrEmpty(blobName)) + if (string.IsNullOrEmpty(blobOptions.BlobName)) { - blobName = $"{_shellOptions.ShellsContainerName}/{_shellSettings.Name}/DataProtectionKeys.xml"; + blobOptions.BlobName = $"{shellOptions.ShellsContainerName}/{shellSettings.Name}/DataProtectionKeys.xml"; } else { @@ -127,21 +128,19 @@ private async Task GetBlobNameAsync(BlobOptions options) var templateOptions = new TemplateOptions(); var templateContext = new TemplateContext(templateOptions); templateOptions.MemberAccessStrategy.Register(); - templateContext.SetValue("ShellSettings", _shellSettings); + templateContext.SetValue("ShellSettings", shellSettings); - var template = _fluidParser.Parse(blobName); + var template = fluidParser.Parse(blobOptions.BlobName); - blobName = await template.RenderAsync(templateContext, NullEncoder.Default); - blobName = blobName.Replace("\r", string.Empty).Replace("\n", string.Empty); + var blobName = await template.RenderAsync(templateContext, NullEncoder.Default); + blobOptions.BlobName = blobName.Replace("\r", string.Empty).Replace("\n", string.Empty); } catch (Exception e) { - _logger.LogCritical(e, "Unable to parse data protection blob name."); + logger.LogCritical(e, "Unable to parse data protection blob name."); throw; } } - - return blobName; } // Assume that this module will override default configuration, so set the Order to a value above the default. From e4b81469535555561d4a2a8aa58bb8d90f762440 Mon Sep 17 00:00:00 2001 From: jtkech Date: Fri, 24 Nov 2023 21:50:48 +0100 Subject: [PATCH 3/5] Refactoring --- .../BlobOptionsSetup.cs | 101 ++++++++++++++++++ .../Startup.cs | 98 +---------------- 2 files changed, 104 insertions(+), 95 deletions(-) create mode 100644 src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptionsSetup.cs diff --git a/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptionsSetup.cs b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptionsSetup.cs new file mode 100644 index 00000000000..1c9f77923c6 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptionsSetup.cs @@ -0,0 +1,101 @@ +using System; +using System.Threading.Tasks; +using Azure.Storage.Blobs; +using Azure.Storage.Blobs.Models; +using Fluid; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using OrchardCore.Environment.Shell; + +namespace OrchardCore.DataProtection.Azure +{ + public class BlobOptionsSetup + { + private readonly ShellOptions _shellOptions; + private readonly ShellSettings _shellSettings; + private readonly ILogger _logger; + + private readonly FluidParser _fluidParser = new(); + + public BlobOptionsSetup(IOptions shellOptions, ShellSettings shellSettings, ILogger logger) + { + _shellOptions = shellOptions.Value; + _shellSettings = shellSettings; + _logger = logger; + } + + public async Task ConfigureAsync(BlobOptions options) + { + await ConfigureContainerNameAsync(options); + await ConfigureBlobNameAsync(options); + } + + private async Task ConfigureContainerNameAsync(BlobOptions options) + { + try + { + // Use Fluid directly as the service provider has not been built. + var templateOptions = new TemplateOptions(); + templateOptions.MemberAccessStrategy.Register(); + var templateContext = new TemplateContext(templateOptions); + templateContext.SetValue("ShellSettings", _shellSettings); + + var template = _fluidParser.Parse(options.ContainerName); + + // container name must be lowercase + var containerName = template.Render(templateContext, NullEncoder.Default).ToLower(); + options.ContainerName = containerName.Replace("\r", string.Empty).Replace("\n", string.Empty); + } + catch (Exception e) + { + _logger.LogCritical(e, "Unable to parse data protection connection string."); + throw; + } + + if (options.CreateContainer) + { + try + { + _logger.LogDebug("Testing data protection container {ContainerName} existence", options.ContainerName); + var blobContainer = new BlobContainerClient(options.ConnectionString, options.ContainerName); + var response = await blobContainer.CreateIfNotExistsAsync(PublicAccessType.None); + _logger.LogDebug("Data protection container {ContainerName} created.", options.ContainerName); + } + catch (Exception) + { + _logger.LogCritical("Unable to connect to Azure Storage to configure data protection storage. Ensure that an application setting containing a valid Azure Storage connection string is available at `Modules:OrchardCore.DataProtection.Azure:ConnectionString`."); + throw; + } + } + } + + private async Task ConfigureBlobNameAsync(BlobOptions options) + { + if (string.IsNullOrEmpty(options.BlobName)) + { + options.BlobName = $"{_shellOptions.ShellsContainerName}/{_shellSettings.Name}/DataProtectionKeys.xml"; + } + else + { + try + { + // Use Fluid directly as the service provider has not been built. + var templateOptions = new TemplateOptions(); + var templateContext = new TemplateContext(templateOptions); + templateOptions.MemberAccessStrategy.Register(); + templateContext.SetValue("ShellSettings", _shellSettings); + + var template = _fluidParser.Parse(options.BlobName); + + var blobName = await template.RenderAsync(templateContext, NullEncoder.Default); + options.BlobName = blobName.Replace("\r", string.Empty).Replace("\n", string.Empty); + } + catch (Exception e) + { + _logger.LogCritical(e, "Unable to parse data protection blob name."); + throw; + } + } + } + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs index 9310397062e..2f1f0bc4a4f 100644 --- a/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs @@ -1,14 +1,8 @@ -using System; -using System.Threading.Tasks; using Azure.Storage.Blobs; -using Azure.Storage.Blobs.Models; -using Fluid; using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using OrchardCore.Environment.Shell; using OrchardCore.Environment.Shell.Configuration; using OrchardCore.Modules; @@ -19,9 +13,6 @@ public class Startup : StartupBase private readonly IShellConfiguration _configuration; private readonly ILogger _logger; - // Local instance since it can be discarded once the startup is over. - private readonly FluidParser _fluidParser = new(); - public Startup(IShellConfiguration configuration, ILogger logger) { _configuration = configuration; @@ -49,15 +40,9 @@ public override void ConfigureServices(IServiceCollection services) services.Initialize(async sp => { - var blobOptions = sp.GetRequiredService(); - var shellOptions = sp.GetRequiredService>().Value; - var shellSettings = sp.GetRequiredService(); - - var fluidParser = new FluidParser(); - var logger = sp.GetRequiredService>(); - - await ConfigureContainerNameAsync(blobOptions, shellSettings, fluidParser, logger); - await ConfigureBlobNameAsync(blobOptions, shellOptions, shellSettings, fluidParser, logger); + var options = sp.GetRequiredService(); + var setup = sp.GetRequiredService(); + await setup.ConfigureAsync(options); }); } else @@ -66,83 +51,6 @@ public override void ConfigureServices(IServiceCollection services) } } - private static async Task ConfigureContainerNameAsync( - BlobOptions blobOptions, - ShellSettings shellSettings, - FluidParser fluidParser, - ILogger logger) - { - try - { - // Use Fluid directly as the service provider has not been built. - var templateOptions = new TemplateOptions(); - templateOptions.MemberAccessStrategy.Register(); - var templateContext = new TemplateContext(templateOptions); - templateContext.SetValue("ShellSettings", shellSettings); - - var template = fluidParser.Parse(blobOptions.ContainerName); - - // container name must be lowercase - var containerName = template.Render(templateContext, NullEncoder.Default).ToLower(); - blobOptions.ContainerName = containerName.Replace("\r", string.Empty).Replace("\n", string.Empty); - } - catch (Exception e) - { - logger.LogCritical(e, "Unable to parse data protection connection string."); - throw; - } - - if (blobOptions.CreateContainer) - { - try - { - logger.LogDebug("Testing data protection container {ContainerName} existence", blobOptions.ContainerName); - var blobContainer = new BlobContainerClient(blobOptions.ConnectionString, blobOptions.ContainerName); - var response = await blobContainer.CreateIfNotExistsAsync(PublicAccessType.None); - logger.LogDebug("Data protection container {ContainerName} created.", blobOptions.ContainerName); - } - catch (Exception) - { - logger.LogCritical("Unable to connect to Azure Storage to configure data protection storage. Ensure that an application setting containing a valid Azure Storage connection string is available at `Modules:OrchardCore.DataProtection.Azure:ConnectionString`."); - throw; - } - } - } - - private static async Task ConfigureBlobNameAsync( - BlobOptions blobOptions, - ShellOptions shellOptions, - ShellSettings shellSettings, - FluidParser fluidParser, - ILogger logger) - { - if (string.IsNullOrEmpty(blobOptions.BlobName)) - { - blobOptions.BlobName = $"{shellOptions.ShellsContainerName}/{shellSettings.Name}/DataProtectionKeys.xml"; - } - else - { - try - { - // Use Fluid directly as the service provider has not been built. - var templateOptions = new TemplateOptions(); - var templateContext = new TemplateContext(templateOptions); - templateOptions.MemberAccessStrategy.Register(); - templateContext.SetValue("ShellSettings", shellSettings); - - var template = fluidParser.Parse(blobOptions.BlobName); - - var blobName = await template.RenderAsync(templateContext, NullEncoder.Default); - blobOptions.BlobName = blobName.Replace("\r", string.Empty).Replace("\n", string.Empty); - } - catch (Exception e) - { - logger.LogCritical(e, "Unable to parse data protection blob name."); - throw; - } - } - } - // Assume that this module will override default configuration, so set the Order to a value above the default. public override int Order => 10; } From 95f402213572800533806093dd56f055290a8415 Mon Sep 17 00:00:00 2001 From: jtkech Date: Fri, 24 Nov 2023 22:02:55 +0100 Subject: [PATCH 4/5] tweaks --- .../BlobOptionsSetup.cs | 11 ++++++++++- .../OrchardCore.DataProtection.Azure/Startup.cs | 9 ++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptionsSetup.cs b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptionsSetup.cs index 1c9f77923c6..ccdb88dd325 100644 --- a/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptionsSetup.cs +++ b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptionsSetup.cs @@ -3,22 +3,30 @@ using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; 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.DataProtection.Azure { public class BlobOptionsSetup { + private readonly IShellConfiguration _configuration; private readonly ShellOptions _shellOptions; private readonly ShellSettings _shellSettings; private readonly ILogger _logger; private readonly FluidParser _fluidParser = new(); - public BlobOptionsSetup(IOptions shellOptions, ShellSettings shellSettings, ILogger logger) + public BlobOptionsSetup( + IShellConfiguration configuration, + IOptions shellOptions, + ShellSettings shellSettings, + ILogger logger) { + _configuration = configuration; _shellOptions = shellOptions.Value; _shellSettings = shellSettings; _logger = logger; @@ -26,6 +34,7 @@ public BlobOptionsSetup(IOptions shellOptions, ShellSettings shell public async Task ConfigureAsync(BlobOptions options) { + _configuration.Bind("OrchardCore_DataProtection_Azure", options); await ConfigureContainerNameAsync(options); await ConfigureBlobNameAsync(options); } diff --git a/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs index 2f1f0bc4a4f..a22546a5fc6 100644 --- a/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs @@ -21,13 +21,12 @@ public Startup(IShellConfiguration configuration, ILogger logger) public override void ConfigureServices(IServiceCollection services) { - var options = new BlobOptions(); - - _configuration.Bind("OrchardCore_DataProtection_Azure", options); - if (!string.IsNullOrWhiteSpace(options.ConnectionString)) + var connectionString = _configuration.GetValue("OrchardCore_DataProtection_Azure:ConnectionString"); + if (!string.IsNullOrWhiteSpace(connectionString)) { services - .AddSingleton(options) + .AddSingleton(new BlobOptions()) + .AddTransient() .AddDataProtection() .PersistKeysToAzureBlobStorage(sp => { From cfc1327b16eafb4aa2ebc623e00f483f63cab802 Mon Sep 17 00:00:00 2001 From: Mike Alhayek Date: Sun, 26 Nov 2023 09:35:59 -0800 Subject: [PATCH 5/5] Cleanup --- .../BlobOptions.cs | 3 + .../BlobOptionsSetup.cs | 145 +++++++++--------- .../Startup.cs | 72 ++++----- 3 files changed, 111 insertions(+), 109 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptions.cs b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptions.cs index d42f751041f..1db47d2a1e2 100644 --- a/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptions.cs +++ b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptions.cs @@ -3,7 +3,10 @@ namespace OrchardCore.DataProtection.Azure; public class BlobOptions { public string ConnectionString { get; set; } + public string ContainerName { get; set; } = "dataprotection"; + public string BlobName { get; set; } + public bool CreateContainer { get; set; } = true; } diff --git a/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptionsSetup.cs b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptionsSetup.cs index ccdb88dd325..a1652835ad0 100644 --- a/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptionsSetup.cs +++ b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptionsSetup.cs @@ -9,102 +9,101 @@ using OrchardCore.Environment.Shell; using OrchardCore.Environment.Shell.Configuration; -namespace OrchardCore.DataProtection.Azure +namespace OrchardCore.DataProtection.Azure; + +public class BlobOptionsSetup { - public class BlobOptionsSetup + private readonly FluidParser _fluidParser = new(); + + private readonly IShellConfiguration _configuration; + private readonly ShellOptions _shellOptions; + private readonly ShellSettings _shellSettings; + private readonly ILogger _logger; + + public BlobOptionsSetup( + IShellConfiguration configuration, + IOptions shellOptions, + ShellSettings shellSettings, + ILogger logger) { - private readonly IShellConfiguration _configuration; - private readonly ShellOptions _shellOptions; - private readonly ShellSettings _shellSettings; - private readonly ILogger _logger; + _configuration = configuration; + _shellOptions = shellOptions.Value; + _shellSettings = shellSettings; + _logger = logger; + } - private readonly FluidParser _fluidParser = new(); + public async Task ConfigureAsync(BlobOptions options) + { + _configuration.Bind("OrchardCore_DataProtection_Azure", options); + await ConfigureContainerNameAsync(options); + await ConfigureBlobNameAsync(options); + } - public BlobOptionsSetup( - IShellConfiguration configuration, - IOptions shellOptions, - ShellSettings shellSettings, - ILogger logger) + private async Task ConfigureContainerNameAsync(BlobOptions options) + { + try { - _configuration = configuration; - _shellOptions = shellOptions.Value; - _shellSettings = shellSettings; - _logger = logger; - } + // Use Fluid directly as the service provider has not been built. + var templateOptions = new TemplateOptions(); + templateOptions.MemberAccessStrategy.Register(); + var templateContext = new TemplateContext(templateOptions); + templateContext.SetValue("ShellSettings", _shellSettings); + + var template = _fluidParser.Parse(options.ContainerName); - public async Task ConfigureAsync(BlobOptions options) + // Container name must be lowercase. + var containerName = template.Render(templateContext, NullEncoder.Default).ToLower(); + options.ContainerName = containerName.Replace("\r", string.Empty).Replace("\n", string.Empty); + } + catch (Exception e) { - _configuration.Bind("OrchardCore_DataProtection_Azure", options); - await ConfigureContainerNameAsync(options); - await ConfigureBlobNameAsync(options); + _logger.LogCritical(e, "Unable to parse data protection connection string."); + throw; } - private async Task ConfigureContainerNameAsync(BlobOptions options) + if (options.CreateContainer) { try { - // Use Fluid directly as the service provider has not been built. - var templateOptions = new TemplateOptions(); - templateOptions.MemberAccessStrategy.Register(); - var templateContext = new TemplateContext(templateOptions); - templateContext.SetValue("ShellSettings", _shellSettings); - - var template = _fluidParser.Parse(options.ContainerName); - - // container name must be lowercase - var containerName = template.Render(templateContext, NullEncoder.Default).ToLower(); - options.ContainerName = containerName.Replace("\r", string.Empty).Replace("\n", string.Empty); + _logger.LogDebug("Testing data protection container {ContainerName} existence", options.ContainerName); + var blobContainer = new BlobContainerClient(options.ConnectionString, options.ContainerName); + var response = await blobContainer.CreateIfNotExistsAsync(PublicAccessType.None); + _logger.LogDebug("Data protection container {ContainerName} created.", options.ContainerName); } catch (Exception e) { - _logger.LogCritical(e, "Unable to parse data protection connection string."); + _logger.LogCritical(e, "Unable to connect to Azure Storage to configure data protection storage. Ensure that an application setting containing a valid Azure Storage connection string is available at `Modules:OrchardCore.DataProtection.Azure:ConnectionString`."); throw; } + } + } - if (options.CreateContainer) - { - try - { - _logger.LogDebug("Testing data protection container {ContainerName} existence", options.ContainerName); - var blobContainer = new BlobContainerClient(options.ConnectionString, options.ContainerName); - var response = await blobContainer.CreateIfNotExistsAsync(PublicAccessType.None); - _logger.LogDebug("Data protection container {ContainerName} created.", options.ContainerName); - } - catch (Exception) - { - _logger.LogCritical("Unable to connect to Azure Storage to configure data protection storage. Ensure that an application setting containing a valid Azure Storage connection string is available at `Modules:OrchardCore.DataProtection.Azure:ConnectionString`."); - throw; - } - } + private async Task ConfigureBlobNameAsync(BlobOptions options) + { + if (string.IsNullOrEmpty(options.BlobName)) + { + options.BlobName = $"{_shellOptions.ShellsContainerName}/{_shellSettings.Name}/DataProtectionKeys.xml"; + + return; } - private async Task ConfigureBlobNameAsync(BlobOptions options) + try { - if (string.IsNullOrEmpty(options.BlobName)) - { - options.BlobName = $"{_shellOptions.ShellsContainerName}/{_shellSettings.Name}/DataProtectionKeys.xml"; - } - else - { - try - { - // Use Fluid directly as the service provider has not been built. - var templateOptions = new TemplateOptions(); - var templateContext = new TemplateContext(templateOptions); - templateOptions.MemberAccessStrategy.Register(); - templateContext.SetValue("ShellSettings", _shellSettings); + // Use Fluid directly as the service provider has not been built. + var templateOptions = new TemplateOptions(); + var templateContext = new TemplateContext(templateOptions); + templateOptions.MemberAccessStrategy.Register(); + templateContext.SetValue("ShellSettings", _shellSettings); - var template = _fluidParser.Parse(options.BlobName); + var template = _fluidParser.Parse(options.BlobName); - var blobName = await template.RenderAsync(templateContext, NullEncoder.Default); - options.BlobName = blobName.Replace("\r", string.Empty).Replace("\n", string.Empty); - } - catch (Exception e) - { - _logger.LogCritical(e, "Unable to parse data protection blob name."); - throw; - } - } + var blobName = await template.RenderAsync(templateContext, NullEncoder.Default); + options.BlobName = blobName.Replace("\r", string.Empty).Replace("\n", string.Empty); + } + catch (Exception e) + { + _logger.LogCritical(e, "Unable to parse data protection blob name."); + throw; } } } diff --git a/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs index a22546a5fc6..55eae14677c 100644 --- a/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs @@ -6,51 +6,51 @@ using OrchardCore.Environment.Shell.Configuration; using OrchardCore.Modules; -namespace OrchardCore.DataProtection.Azure +namespace OrchardCore.DataProtection.Azure; + +public class Startup : StartupBase { - public class Startup : StartupBase + private readonly IShellConfiguration _configuration; + private readonly ILogger _logger; + + public Startup(IShellConfiguration configuration, ILogger logger) { - private readonly IShellConfiguration _configuration; - private readonly ILogger _logger; + _configuration = configuration; + _logger = logger; + } - public Startup(IShellConfiguration configuration, ILogger logger) - { - _configuration = configuration; - _logger = logger; - } + // Assume that this module will override default configuration, so set the Order to a value above the default. + public override int Order => 10; - public override void ConfigureServices(IServiceCollection services) - { - var connectionString = _configuration.GetValue("OrchardCore_DataProtection_Azure:ConnectionString"); - if (!string.IsNullOrWhiteSpace(connectionString)) - { - services - .AddSingleton(new BlobOptions()) - .AddTransient() - .AddDataProtection() - .PersistKeysToAzureBlobStorage(sp => - { - var options = sp.GetRequiredService(); - return new BlobClient( - options.ConnectionString, - options.ContainerName, - options.BlobName); - }); + public override void ConfigureServices(IServiceCollection services) + { + var connectionString = _configuration.GetValue("OrchardCore_DataProtection_Azure:ConnectionString"); - services.Initialize(async sp => + if (!string.IsNullOrWhiteSpace(connectionString)) + { + services + .AddSingleton(new BlobOptions()) + .AddTransient() + .AddDataProtection() + .PersistKeysToAzureBlobStorage(sp => { var options = sp.GetRequiredService(); - var setup = sp.GetRequiredService(); - await setup.ConfigureAsync(options); + return new BlobClient( + options.ConnectionString, + options.ContainerName, + options.BlobName); }); - } - else + + services.Initialize(async sp => { - _logger.LogCritical("No connection string was supplied for OrchardCore.DataProtection.Azure. Ensure that an application setting containing a valid Azure Storage connection string is available at `Modules:OrchardCore.DataProtection.Azure:ConnectionString`."); - } + var options = sp.GetRequiredService(); + var setup = sp.GetRequiredService(); + await setup.ConfigureAsync(options); + }); + } + else + { + _logger.LogCritical("No connection string was supplied for OrchardCore.DataProtection.Azure. Ensure that an application setting containing a valid Azure Storage connection string is available at `Modules:OrchardCore.DataProtection.Azure:ConnectionString`."); } - - // Assume that this module will override default configuration, so set the Order to a value above the default. - public override int Order => 10; } }