From d17d6700efdf353d4f88a72d448a4f3905c0a01a Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Thu, 30 Mar 2017 20:05:57 +0200 Subject: [PATCH 1/9] Strongly typed configuration for providers and scopes, proper exceptions from StorageFactory --- .../Startup.cs | 7 +-- .../appsettings.json | 17 +++++- .../AzureStorageExtensions.cs | 17 ++++++ .../AzureStorageManagerOptions.cs | 16 ----- .../AzureStorageProvider.cs | 17 ++++-- .../ProviderOptions.cs | 13 +++++ .../StoreOptions.cs | 9 +++ .../Internal/ExtendedPropertiesProvider.cs | 1 - .../FileSystemStorageServerMiddleware.cs | 11 ++-- .../FileSystemOptions.cs | 7 --- .../FileSystemStorageExtensions.cs | 25 +++++++- .../FileSystemStorageProvider.cs | 26 ++++----- .../ProviderOptions.cs | 13 +++++ .../StoreOptions.cs | 7 +++ .../Exceptions/BadStoreProviderException.cs | 12 ++++ .../Exceptions/ProviderNotFoundException.cs | 11 ++++ .../Exceptions/StoreNotFoundException.cs | 11 ++++ .../GeekLearning.Storage.csproj | 2 + src/GeekLearning.Storage/IProviderOptions.cs | 12 ++++ .../IProviderStoreOptions.cs | 6 ++ src/GeekLearning.Storage/IStorageProvider.cs | 2 + .../IStorageStoreOptions.cs | 4 +- src/GeekLearning.Storage/IStoreExtensions.cs | 36 +++--------- .../Internal/ConfigureProviderOptions.cs | 48 +++++++++++++++ .../Internal/GenericStoreProxy.cs | 58 ++++--------------- .../Internal/StorageFactory.cs | 38 ++++++++---- .../Internal/StorageProviderBase.cs | 43 ++++++++++++++ src/GeekLearning.Storage/StorageExtensions.cs | 8 +++ src/GeekLearning.Storage/StorageOptions.cs | 5 +- src/GeekLearning.Storage/StoreBase.cs | 2 +- .../TestStore.cs | 4 +- 31 files changed, 345 insertions(+), 143 deletions(-) delete mode 100644 src/GeekLearning.Storage.Azure/AzureStorageManagerOptions.cs create mode 100644 src/GeekLearning.Storage.Azure/ProviderOptions.cs create mode 100644 src/GeekLearning.Storage.Azure/StoreOptions.cs delete mode 100644 src/GeekLearning.Storage.FileSystem/FileSystemOptions.cs create mode 100644 src/GeekLearning.Storage.FileSystem/ProviderOptions.cs create mode 100644 src/GeekLearning.Storage.FileSystem/StoreOptions.cs create mode 100644 src/GeekLearning.Storage/Exceptions/BadStoreProviderException.cs create mode 100644 src/GeekLearning.Storage/Exceptions/ProviderNotFoundException.cs create mode 100644 src/GeekLearning.Storage/Exceptions/StoreNotFoundException.cs create mode 100644 src/GeekLearning.Storage/IProviderOptions.cs create mode 100644 src/GeekLearning.Storage/IProviderStoreOptions.cs create mode 100644 src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs create mode 100644 src/GeekLearning.Storage/Internal/StorageProviderBase.cs diff --git a/samples/GeekLearning.Storage.BasicSample/Startup.cs b/samples/GeekLearning.Storage.BasicSample/Startup.cs index e9fb65d..eb16301 100644 --- a/samples/GeekLearning.Storage.BasicSample/Startup.cs +++ b/samples/GeekLearning.Storage.BasicSample/Startup.cs @@ -33,15 +33,14 @@ public void ConfigureServices(IServiceCollection services) byte[] signingKey = new byte[512]; rng.GetBytes(signingKey); - services.AddStorage() + services.AddStorage(this.Configuration.GetSection("Storage")) .AddAzureStorage() .AddFileSystemStorage(HostingEnvironement.ContentRootPath) - .AddFileSystemStorageServer(options=> { + .AddFileSystemStorageServer(options => + { options.SigningKey = signingKey; options.BaseUri = new Uri("http://localhost:11149/"); }); - - services.Configure(Configuration.GetSection("Storage")); services.AddScoped(); } diff --git a/samples/GeekLearning.Storage.BasicSample/appsettings.json b/samples/GeekLearning.Storage.BasicSample/appsettings.json index 6d3d0ab..aff87c6 100644 --- a/samples/GeekLearning.Storage.BasicSample/appsettings.json +++ b/samples/GeekLearning.Storage.BasicSample/appsettings.json @@ -8,12 +8,27 @@ } }, "Storage": { + "Providers": { + "Azure": { + "DefaultConnectionString": "vdsvdz" + }, + "FileSystem": { + "RootPath": "" + } + }, "Stores": { "Templates": { "Provider": "FileSystem", "Parameters": { "Path": "Templates", - "Access" : "Public" + "Access": "Public" + } + }, + "Youpi": { + "Provider": "Azure", + "Parameters": { + "ConnectionString": "hfeioa", + "Container": "Public" } } } diff --git a/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs b/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs index 45cae55..82c2853 100644 --- a/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs +++ b/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs @@ -1,12 +1,29 @@ namespace GeekLearning.Storage { using Azure; + using GeekLearning.Storage.Internal; + using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; + using Microsoft.Extensions.Options; public static class AzureStorageExtensions { public static IServiceCollection AddAzureStorage(this IServiceCollection services) + { + return services + .AddSingleton, ConfigureProviderOptions>() + .AddAzureStorageServices(); + } + + public static IServiceCollection AddAzureStorage(this IServiceCollection services, IConfiguration configuration) + { + return services + .Configure(configuration) + .AddAzureStorageServices(); + } + + private static IServiceCollection AddAzureStorageServices(this IServiceCollection services) { services.TryAddEnumerable(ServiceDescriptor.Transient()); return services; diff --git a/src/GeekLearning.Storage.Azure/AzureStorageManagerOptions.cs b/src/GeekLearning.Storage.Azure/AzureStorageManagerOptions.cs deleted file mode 100644 index 917a72d..0000000 --- a/src/GeekLearning.Storage.Azure/AzureStorageManagerOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace GeekLearning.Storage.Azure -{ - using System.Collections.Generic; - - public class AzureStorageManagerOptions - { - public Dictionary SubStores { get; set; } - - public class SubStore - { - public string Container { get; set; } - - public string ConnectionString { get; set; } - } - } -} diff --git a/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs b/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs index b0086bc..1989964 100644 --- a/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs +++ b/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs @@ -1,14 +1,23 @@ namespace GeekLearning.Storage.Azure { + using GeekLearning.Storage.Internal; + using Microsoft.Extensions.Options; using Storage; - public class AzureStorageProvider : IStorageProvider + public class AzureStorageProvider : StorageProviderBase { - public string Name => "Azure"; + public const string ProviderName = "Azure"; - public IStore BuildStore(string storeName, IStorageStoreOptions storeOptions) + public AzureStorageProvider(IOptions options) + : base(options) { - return new AzureStore(storeName, storeOptions.Parameters["ConnectionString"], storeOptions.Parameters["Container"]); + } + + public override string Name => ProviderName; + + protected override IStore BuildStore(string storeName, StoreOptions storeOptions) + { + return new AzureStore(storeName, storeOptions.ConnectionString, storeOptions.Container); } } } diff --git a/src/GeekLearning.Storage.Azure/ProviderOptions.cs b/src/GeekLearning.Storage.Azure/ProviderOptions.cs new file mode 100644 index 0000000..a9940d6 --- /dev/null +++ b/src/GeekLearning.Storage.Azure/ProviderOptions.cs @@ -0,0 +1,13 @@ +namespace GeekLearning.Storage.Azure +{ + using System.Collections.Generic; + + public class ProviderOptions : IProviderOptions + { + public string ProviderName => AzureStorageProvider.ProviderName; + + public string DefaultConnectionString { get; set; } + + public IReadOnlyDictionary Stores { get; set; } + } +} diff --git a/src/GeekLearning.Storage.Azure/StoreOptions.cs b/src/GeekLearning.Storage.Azure/StoreOptions.cs new file mode 100644 index 0000000..6bf96a8 --- /dev/null +++ b/src/GeekLearning.Storage.Azure/StoreOptions.cs @@ -0,0 +1,9 @@ +namespace GeekLearning.Storage.Azure +{ + public class StoreOptions : IProviderStoreOptions + { + public string ConnectionString { get; set; } + + public string Container { get; set; } + } +} diff --git a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/Internal/ExtendedPropertiesProvider.cs b/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/Internal/ExtendedPropertiesProvider.cs index afdf947..b5a639c 100644 --- a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/Internal/ExtendedPropertiesProvider.cs +++ b/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/Internal/ExtendedPropertiesProvider.cs @@ -9,7 +9,6 @@ public class ExtendedPropertiesProvider : IExtendedPropertiesProvider { private readonly FileSystemExtendedPropertiesOptions options; - private readonly IStorageFactory storageFactory; public ExtendedPropertiesProvider( IOptions options) diff --git a/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs b/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs index d9387f8..c3d8d43 100644 --- a/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs +++ b/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs @@ -38,11 +38,12 @@ public async Task Invoke(HttpContext context) && storeOptions.Provider == "FileSystem") { string access; - if (!storeOptions.Parameters.TryGetValue("Access", out access) && access != "Public") - { - context.Response.StatusCode = StatusCodes.Status403Forbidden; - return; - } + // TODO: Fix options! + //if (!storeOptions.Parameters.TryGetValue("Access", out access) && access != "Public") + //{ + // context.Response.StatusCode = StatusCodes.Status403Forbidden; + // return; + //} IStore store = storageFactory.GetStore(storeName, storeOptions); diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemOptions.cs b/src/GeekLearning.Storage.FileSystem/FileSystemOptions.cs deleted file mode 100644 index 5476e6a..0000000 --- a/src/GeekLearning.Storage.FileSystem/FileSystemOptions.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace GeekLearning.Storage.FileSystem -{ - public class FileSystemOptions - { - public string RootPath { get; set; } - } -} diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs index 53d2646..cbae076 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs @@ -1,14 +1,37 @@ namespace GeekLearning.Storage { using FileSystem; + using GeekLearning.Storage.Internal; + using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; + using Microsoft.Extensions.Options; public static class FileSystemStorageExtensions { public static IServiceCollection AddFileSystemStorage(this IServiceCollection services, string rootPath) { - services.Configure(options => options.RootPath = rootPath); + return services + .Configure(options => options.RootPath = rootPath) + .AddFileSystemStorage(); + } + + public static IServiceCollection AddFileSystemStorage(this IServiceCollection services) + { + return services + .AddSingleton, ConfigureProviderOptions>() + .AddFileSystemStorageServices(); + } + + public static IServiceCollection AddFileSystemStorage(this IServiceCollection services, IConfiguration configuration) + { + return services + .Configure(configuration) + .AddFileSystemStorageServices(); + } + + private static IServiceCollection AddFileSystemStorageServices(this IServiceCollection services) + { services.TryAddEnumerable(ServiceDescriptor.Transient()); return services; } diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs index 5b7cb9d..966c513 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs @@ -1,31 +1,29 @@ namespace GeekLearning.Storage.FileSystem { - using Microsoft.Extensions.DependencyInjection; + using GeekLearning.Storage.Internal; using Microsoft.Extensions.Options; using Storage; - using System; - public class FileSystemStorageProvider : IStorageProvider + public class FileSystemStorageProvider : StorageProviderBase { - private IOptions options; - private IServiceProvider serviceProvider; + public const string ProviderName = "FileSystem"; + private readonly IPublicUrlProvider publicUrlProvider; + private readonly IExtendedPropertiesProvider extendedPropertiesProvider; - public FileSystemStorageProvider(IOptions options, IServiceProvider serviceProvider) + public FileSystemStorageProvider(IOptions options, IPublicUrlProvider publicUrlProvider = null, IExtendedPropertiesProvider extendedPropertiesProvider = null) + : base(options) { - this.options = options; - this.serviceProvider = serviceProvider; + this.publicUrlProvider = publicUrlProvider; + this.extendedPropertiesProvider = extendedPropertiesProvider; } - public string Name => "FileSystem"; + public override string Name => ProviderName; - public IStore BuildStore(string storeName, IStorageStoreOptions storeOptions) + protected override IStore BuildStore(string storeName, StoreOptions storeOptions) { - var publicUrlProvider = this.serviceProvider.GetService(); - var extendedPropertiesProvider = this.serviceProvider.GetService(); - return new FileSystemStore( storeName, - storeOptions.Parameters["Path"], + storeOptions.Path, this.options.Value.RootPath, publicUrlProvider, extendedPropertiesProvider); diff --git a/src/GeekLearning.Storage.FileSystem/ProviderOptions.cs b/src/GeekLearning.Storage.FileSystem/ProviderOptions.cs new file mode 100644 index 0000000..0864742 --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem/ProviderOptions.cs @@ -0,0 +1,13 @@ +namespace GeekLearning.Storage.FileSystem +{ + using System.Collections.Generic; + + public class ProviderOptions : IProviderOptions + { + public string ProviderName => FileSystemStorageProvider.ProviderName; + + public string RootPath { get; set; } + + public IReadOnlyDictionary Stores { get; set; } + } +} diff --git a/src/GeekLearning.Storage.FileSystem/StoreOptions.cs b/src/GeekLearning.Storage.FileSystem/StoreOptions.cs new file mode 100644 index 0000000..8205b37 --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem/StoreOptions.cs @@ -0,0 +1,7 @@ +namespace GeekLearning.Storage.FileSystem +{ + public class StoreOptions : IProviderStoreOptions + { + public string Path { get; set; } + } +} diff --git a/src/GeekLearning.Storage/Exceptions/BadStoreProviderException.cs b/src/GeekLearning.Storage/Exceptions/BadStoreProviderException.cs new file mode 100644 index 0000000..a062288 --- /dev/null +++ b/src/GeekLearning.Storage/Exceptions/BadStoreProviderException.cs @@ -0,0 +1,12 @@ +namespace GeekLearning.Storage.Exceptions +{ + using System; + + public class BadStoreProviderException : Exception + { + public BadStoreProviderException(string providerName, string storeName) + : base($"The store '{storeName}' was not configured with the provider '{providerName}'. Unable to build it.") + { + } + } +} diff --git a/src/GeekLearning.Storage/Exceptions/ProviderNotFoundException.cs b/src/GeekLearning.Storage/Exceptions/ProviderNotFoundException.cs new file mode 100644 index 0000000..1e108b9 --- /dev/null +++ b/src/GeekLearning.Storage/Exceptions/ProviderNotFoundException.cs @@ -0,0 +1,11 @@ +namespace GeekLearning.Storage.Exceptions +{ + using System; + + public class ProviderNotFoundException : Exception + { + public ProviderNotFoundException(string providerName) : base($"The configured provider '{providerName}' was not found. Did you forget to register providers in your Startup.ConfigureServices?") + { + } + } +} diff --git a/src/GeekLearning.Storage/Exceptions/StoreNotFoundException.cs b/src/GeekLearning.Storage/Exceptions/StoreNotFoundException.cs new file mode 100644 index 0000000..b161382 --- /dev/null +++ b/src/GeekLearning.Storage/Exceptions/StoreNotFoundException.cs @@ -0,0 +1,11 @@ +namespace GeekLearning.Storage.Exceptions +{ + using System; + + public class StoreNotFoundException : Exception + { + public StoreNotFoundException(string storeName) : base($"The configured store '{storeName}' was not found. Did you configure it properly in your appsettings.json?") + { + } + } +} diff --git a/src/GeekLearning.Storage/GeekLearning.Storage.csproj b/src/GeekLearning.Storage/GeekLearning.Storage.csproj index e61058f..4b03abb 100644 --- a/src/GeekLearning.Storage/GeekLearning.Storage.csproj +++ b/src/GeekLearning.Storage/GeekLearning.Storage.csproj @@ -11,8 +11,10 @@ + + diff --git a/src/GeekLearning.Storage/IProviderOptions.cs b/src/GeekLearning.Storage/IProviderOptions.cs new file mode 100644 index 0000000..73e5f50 --- /dev/null +++ b/src/GeekLearning.Storage/IProviderOptions.cs @@ -0,0 +1,12 @@ +namespace GeekLearning.Storage +{ + using System.Collections.Generic; + + public interface IProviderOptions + where TStoreOptions: class, IProviderStoreOptions + { + string ProviderName { get; } + + IReadOnlyDictionary Stores { get; set; } + } +} diff --git a/src/GeekLearning.Storage/IProviderStoreOptions.cs b/src/GeekLearning.Storage/IProviderStoreOptions.cs new file mode 100644 index 0000000..3c0f837 --- /dev/null +++ b/src/GeekLearning.Storage/IProviderStoreOptions.cs @@ -0,0 +1,6 @@ +namespace GeekLearning.Storage +{ + public interface IProviderStoreOptions + { + } +} diff --git a/src/GeekLearning.Storage/IStorageProvider.cs b/src/GeekLearning.Storage/IStorageProvider.cs index 5ac5670..b2c8dfb 100644 --- a/src/GeekLearning.Storage/IStorageProvider.cs +++ b/src/GeekLearning.Storage/IStorageProvider.cs @@ -4,6 +4,8 @@ public interface IStorageProvider { string Name { get; } + IStore BuildStore(string storeName); + IStore BuildStore(string storeName, IStorageStoreOptions storeOptions); } } diff --git a/src/GeekLearning.Storage/IStorageStoreOptions.cs b/src/GeekLearning.Storage/IStorageStoreOptions.cs index 4807f7b..0bc6855 100644 --- a/src/GeekLearning.Storage/IStorageStoreOptions.cs +++ b/src/GeekLearning.Storage/IStorageStoreOptions.cs @@ -1,11 +1,11 @@ namespace GeekLearning.Storage { - using System.Collections.Generic; + using Microsoft.Extensions.Configuration; public interface IStorageStoreOptions { string Provider { get; } - Dictionary Parameters { get; } + IConfigurationSection Parameters { get; } } } diff --git a/src/GeekLearning.Storage/IStoreExtensions.cs b/src/GeekLearning.Storage/IStoreExtensions.cs index 36fa987..e6880c2 100644 --- a/src/GeekLearning.Storage/IStoreExtensions.cs +++ b/src/GeekLearning.Storage/IStoreExtensions.cs @@ -6,48 +6,30 @@ public static class IStoreExtensions { public static Task ListAsync(this IStore store, string path, bool recursive = false, bool withMetadata = false) - { - return store.ListAsync(path, recursive: recursive, withMetadata: withMetadata); - } + => store.ListAsync(path, recursive: recursive, withMetadata: withMetadata); public static Task ListAsync(this IStore store, string path, string searchPattern, bool recursive = false, bool withMetadata = false) - { - return store.ListAsync(path, searchPattern, recursive: recursive, withMetadata: withMetadata); - } + => store.ListAsync(path, searchPattern, recursive: recursive, withMetadata: withMetadata); public static Task DeleteAsync(this IStore store, string path) - { - return store.DeleteAsync(new Internal.PrivateFileReference(path)); - } + => store.DeleteAsync(new Internal.PrivateFileReference(path)); public static Task GetAsync(this IStore store, string path, bool withMetadata = false) - { - return store.GetAsync(new Internal.PrivateFileReference(path), withMetadata: withMetadata); - } + => store.GetAsync(new Internal.PrivateFileReference(path), withMetadata: withMetadata); public static Task ReadAsync(this IStore store, string path) - { - return store.ReadAsync(new Internal.PrivateFileReference(path)); - } + => store.ReadAsync(new Internal.PrivateFileReference(path)); public static Task ReadAllBytesAsync(this IStore store, string path) - { - return store.ReadAllBytesAsync(new Internal.PrivateFileReference(path)); - } + => store.ReadAllBytesAsync(new Internal.PrivateFileReference(path)); public static Task ReadAllTextAsync(this IStore store, string path) - { - return store.ReadAllTextAsync(new Internal.PrivateFileReference(path)); - } + => store.ReadAllTextAsync(new Internal.PrivateFileReference(path)); public static Task SaveAsync(this IStore store, byte[] data, string path, string contentType) - { - return store.SaveAsync(data, new Internal.PrivateFileReference(path), contentType); - } + => store.SaveAsync(data, new Internal.PrivateFileReference(path), contentType); public static Task SaveAsync(this IStore store, Stream data, string path, string contentType) - { - return store.SaveAsync(data, new Internal.PrivateFileReference(path), contentType); - } + => store.SaveAsync(data, new Internal.PrivateFileReference(path), contentType); } } diff --git a/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs b/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs new file mode 100644 index 0000000..d6d12c2 --- /dev/null +++ b/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs @@ -0,0 +1,48 @@ +namespace GeekLearning.Storage.Internal +{ + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Options; + using System.Linq; + + public class ConfigureProviderOptions : IConfigureOptions + where TProviderOptions : class, IProviderOptions, new() + where TStoreOptions: class, IProviderStoreOptions, new() + { + private readonly IOptions storageOptions; + + public ConfigureProviderOptions(IOptions storageOptions) + { + this.storageOptions = storageOptions; + } + + public void Configure(TProviderOptions options) + { + var storageOptionsValue = this.storageOptions.Value; + + if (storageOptionsValue == null) + { + return; + } + + if (storageOptionsValue.Providers != null + && storageOptionsValue.Providers.TryGetValue(options.ProviderName, out var providerConfigurationSection)) + { + ConfigurationBinder.Bind(providerConfigurationSection, options); + } + + if (storageOptionsValue.Stores != null) + { + options.Stores = storageOptionsValue.Stores + .Where(skvp => skvp.Value.Provider == options.ProviderName) + .ToDictionary( + skvp => skvp.Key, + skvp => + { + var storeOptions = new TStoreOptions(); + ConfigurationBinder.Bind(skvp.Value.Parameters, storeOptions); + return storeOptions; + }); + } + } + } +} diff --git a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs index 228bea9..0e740f9 100644 --- a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs +++ b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs @@ -15,62 +15,26 @@ public GenericStoreProxy(IStorageFactory factory, IOptions options) this.innerStore = factory.GetStore(nameof(TOptions), options.Value); } - public string Name - { - get - { - return innerStore.Name; - } - } + public string Name => innerStore.Name; - public Task DeleteAsync(IPrivateFileReference file) - { - return innerStore.DeleteAsync(file); - } + public Task DeleteAsync(IPrivateFileReference file) => innerStore.DeleteAsync(file); - public Task GetAsync(Uri file, bool withMetadata) - { - return innerStore.GetAsync(file, withMetadata); - } + public Task GetAsync(Uri file, bool withMetadata) => innerStore.GetAsync(file, withMetadata); - public Task GetAsync(IPrivateFileReference file, bool withMetadata) - { - return innerStore.GetAsync(file, withMetadata); - } + public Task GetAsync(IPrivateFileReference file, bool withMetadata) => innerStore.GetAsync(file, withMetadata); - public Task ListAsync(string path, bool recursive, bool withMetadata) - { - return innerStore.ListAsync(path, recursive, withMetadata); - } + public Task ListAsync(string path, bool recursive, bool withMetadata) => innerStore.ListAsync(path, recursive, withMetadata); - public Task ListAsync(string path, string searchPattern, bool recursive, bool withMetadata) - { - return innerStore.ListAsync(path, searchPattern, recursive, withMetadata); - } + public Task ListAsync(string path, string searchPattern, bool recursive, bool withMetadata) => innerStore.ListAsync(path, searchPattern, recursive, withMetadata); - public Task ReadAllBytesAsync(IPrivateFileReference file) - { - return innerStore.ReadAllBytesAsync(file); - } + public Task ReadAllBytesAsync(IPrivateFileReference file) => innerStore.ReadAllBytesAsync(file); - public Task ReadAllTextAsync(IPrivateFileReference file) - { - return innerStore.ReadAllTextAsync(file); - } + public Task ReadAllTextAsync(IPrivateFileReference file) => innerStore.ReadAllTextAsync(file); - public Task ReadAsync(IPrivateFileReference file) - { - return innerStore.ReadAsync(file); - } + public Task ReadAsync(IPrivateFileReference file) => innerStore.ReadAsync(file); - public Task SaveAsync(Stream data, IPrivateFileReference file, string contentType) - { - return innerStore.SaveAsync(data, file, contentType); - } + public Task SaveAsync(Stream data, IPrivateFileReference file, string contentType) => innerStore.SaveAsync(data, file, contentType); - public Task SaveAsync(byte[] data, IPrivateFileReference file, string contentType) - { - return innerStore.SaveAsync(data, file, contentType); - } + public Task SaveAsync(byte[] data, IPrivateFileReference file, string contentType) => innerStore.SaveAsync(data, file, contentType); } } diff --git a/src/GeekLearning.Storage/Internal/StorageFactory.cs b/src/GeekLearning.Storage/Internal/StorageFactory.cs index 609e566..b8c8a5b 100644 --- a/src/GeekLearning.Storage/Internal/StorageFactory.cs +++ b/src/GeekLearning.Storage/Internal/StorageFactory.cs @@ -17,21 +17,19 @@ public StorageFactory(IEnumerable storageProviders, IOptions x.Name == configuration.Provider).BuildStore(storeName, configuration); + return this.GetProvider(configuration.Provider).BuildStore(storeName, configuration); } public IStore GetStore(string storeName) { - var conf = this.options.Value.Stores[storeName]; - return this.storageProviders.FirstOrDefault(x => x.Name == conf.Provider).BuildStore(storeName, conf); + return this.GetProvider(this.GetStoreConfiguration(storeName).Provider).BuildStore(storeName); } public bool TryGetStore(string storeName, out IStore store) { - StorageOptions.StorageStoreOptions conf; - if (this.options.Value.Stores.TryGetValue(storeName, out conf)) + if (this.options.Value.Stores.TryGetValue(storeName, out var configuration)) { - store = this.storageProviders.FirstOrDefault(x => x.Name == conf.Provider).BuildStore(storeName, conf); + store = this.GetProvider(configuration.Provider).BuildStore(storeName); return true; } @@ -41,12 +39,11 @@ public bool TryGetStore(string storeName, out IStore store) public bool TryGetStore(string storeName, out IStore store, string provider) { - StorageOptions.StorageStoreOptions conf; - if (this.options.Value.Stores.TryGetValue(storeName, out conf)) + if (this.options.Value.Stores.TryGetValue(storeName, out var configuration)) { - if (provider == conf.Provider) + if (provider == configuration.Provider) { - store = this.storageProviders.FirstOrDefault(x => x.Name == conf.Provider).BuildStore(storeName, conf); + store = this.GetProvider(configuration.Provider).BuildStore(storeName); return true; } } @@ -54,5 +51,26 @@ public bool TryGetStore(string storeName, out IStore store, string provider) store = null; return false; } + + private IStorageProvider GetProvider(string providerName) + { + var provider = this.storageProviders.FirstOrDefault(p => p.Name == providerName); + if (provider == null) + { + throw new Exceptions.ProviderNotFoundException(providerName); + } + + return provider; + } + + private StorageOptions.StorageStoreOptions GetStoreConfiguration(string storeName) + { + if (this.options.Value.Stores.TryGetValue(storeName, out var configuration)) + { + return configuration; + } + + throw new Exceptions.StoreNotFoundException(storeName); + } } } diff --git a/src/GeekLearning.Storage/Internal/StorageProviderBase.cs b/src/GeekLearning.Storage/Internal/StorageProviderBase.cs new file mode 100644 index 0000000..78143fb --- /dev/null +++ b/src/GeekLearning.Storage/Internal/StorageProviderBase.cs @@ -0,0 +1,43 @@ +namespace GeekLearning.Storage.Internal +{ + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Options; + + public abstract class StorageProviderBase : IStorageProvider + where TProviderOptions : class, IProviderOptions, new() + where TStoreOptions : class, IProviderStoreOptions, new() + { + protected readonly IOptions options; + + public StorageProviderBase(IOptions options) + { + this.options = options; + } + + public abstract string Name { get; } + + public IStore BuildStore(string storeName) + { + if (this.options.Value.Stores.TryGetValue(storeName, out var storeOptions)) + { + return this.BuildStore(storeName, storeOptions); + } + + throw new Exceptions.BadStoreProviderException(this.Name, storeName); + } + + public IStore BuildStore(string storeName, IStorageStoreOptions storageStoreOptions) + { + if (storageStoreOptions.Provider != this.Name) + { + throw new Exceptions.BadStoreProviderException(this.Name, storeName); + } + + var storeOptions = new TStoreOptions(); + ConfigurationBinder.Bind(storageStoreOptions.Parameters, storeOptions); + return this.BuildStore(storeName, storeOptions); + } + + protected abstract IStore BuildStore(string storeName, TStoreOptions storeOptions); + } +} diff --git a/src/GeekLearning.Storage/StorageExtensions.cs b/src/GeekLearning.Storage/StorageExtensions.cs index 7fabe92..b352a2a 100644 --- a/src/GeekLearning.Storage/StorageExtensions.cs +++ b/src/GeekLearning.Storage/StorageExtensions.cs @@ -1,5 +1,6 @@ namespace GeekLearning.Storage { + using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -11,5 +12,12 @@ public static IServiceCollection AddStorage(this IServiceCollection services) services.TryAdd(ServiceDescriptor.Transient(typeof(IStore<>), typeof(Internal.GenericStoreProxy<>))); return services; } + + public static IServiceCollection AddStorage(this IServiceCollection services, IConfiguration configuration) + { + return services + .Configure(configuration) + .AddStorage(); + } } } diff --git a/src/GeekLearning.Storage/StorageOptions.cs b/src/GeekLearning.Storage/StorageOptions.cs index f26e4cf..8da6e79 100644 --- a/src/GeekLearning.Storage/StorageOptions.cs +++ b/src/GeekLearning.Storage/StorageOptions.cs @@ -1,16 +1,19 @@ namespace GeekLearning.Storage { + using Microsoft.Extensions.Configuration; using System.Collections.Generic; public class StorageOptions { + public Dictionary Providers { get; set; } + public Dictionary Stores { get; set; } public class StorageStoreOptions : IStorageStoreOptions { public string Provider { get; set; } - public Dictionary Parameters { get; set; } + public IConfigurationSection Parameters { get; set; } } } } diff --git a/src/GeekLearning.Storage/StoreBase.cs b/src/GeekLearning.Storage/StoreBase.cs index 86ea12f..23a38ed 100644 --- a/src/GeekLearning.Storage/StoreBase.cs +++ b/src/GeekLearning.Storage/StoreBase.cs @@ -9,6 +9,6 @@ public StoreBase(string storeName, IStorageFactory storageFactory) this.store = storageFactory.GetStore(storeName); } - public IStore Store { get { return this.store; } } + public IStore Store => this.store; } } diff --git a/tests/GeekLearning.Storage.Integration.Test/TestStore.cs b/tests/GeekLearning.Storage.Integration.Test/TestStore.cs index 1df5a25..f4d4e7b 100644 --- a/tests/GeekLearning.Storage.Integration.Test/TestStore.cs +++ b/tests/GeekLearning.Storage.Integration.Test/TestStore.cs @@ -1,11 +1,11 @@ namespace GeekLearning.Storage.Integration.Test { - using System.Collections.Generic; + using Microsoft.Extensions.Configuration; public class TestStore : IStorageStoreOptions { public string Provider { get; set; } - public Dictionary Parameters { get; set; } + public IConfigurationSection Parameters { get; set; } } } From d6139ad7d816dfe023b596812c7d27d8315c33e0 Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Wed, 12 Apr 2017 18:18:45 +0200 Subject: [PATCH 2/9] New configuration --- README.md | 6 +- .../appsettings.json | 58 +++++++--- .../AzureStorageExtensions.cs | 6 +- .../AzureStorageProvider.cs | 9 +- src/GeekLearning.Storage.Azure/AzureStore.cs | 33 +++--- .../Configuration/AzureParsedOptions.cs | 37 ++++++ .../AzureProviderInstanceOptions.cs | 11 ++ .../Configuration/AzureScopedStoreOptions.cs | 8 ++ .../Configuration/AzureStoreOptions.cs | 11 ++ .../ProviderOptions.cs | 13 --- .../StoreOptions.cs | 9 -- .../FileSystemStorageServerMiddleware.cs | 12 +- .../Configuration/FileSystemParsedOptions.cs | 35 ++++++ .../FileSystemProviderInstanceOptions.cs | 9 ++ .../FileSystemScopedStoreOptions.cs | 8 ++ .../Configuration/FileSystemStoreOptions.cs | 28 +++++ .../FileSystemStorageExtensions.cs | 7 +- .../FileSystemStorageProvider.cs | 11 +- .../FileSystemStore.cs | 39 ++++--- .../ProviderOptions.cs | 13 --- .../StoreOptions.cs | 7 -- .../Configuration/AccessLevel.cs | 9 ++ .../Configuration/ConfigurationExtensions.cs | 108 ++++++++++++++++++ .../Configuration/INamedElementOptions.cs | 7 ++ .../Configuration/IParsedOptions.cs | 20 ++++ .../Configuration/IProviderInstanceOptions.cs | 7 ++ .../Configuration/IScopedStoreOptions.cs | 7 ++ .../Configuration/IStoreOptions.cs | 13 +++ .../Configuration/ProviderInstanceOptions.cs | 9 ++ .../Configuration/ScopedStoreOptions.cs | 7 ++ .../Configuration/StorageOptions.cs | 43 +++++++ .../Configuration/StoreOptions.cs | 15 +++ .../Exceptions/BadProviderConfiguration.cs | 17 +++ .../Exceptions/BadStoreConfiguration.cs | 17 +++ .../Exceptions/ProviderNotFoundException.cs | 3 +- .../Exceptions/StoreNotFoundException.cs | 3 +- src/GeekLearning.Storage/IProviderOptions.cs | 12 -- .../IProviderStoreOptions.cs | 6 - src/GeekLearning.Storage/IStorageFactory.cs | 4 +- src/GeekLearning.Storage/IStorageProvider.cs | 4 +- .../IStorageStoreOptions.cs | 11 -- src/GeekLearning.Storage/IStore{TOptions}.cs | 4 +- .../Internal/ConfigureProviderOptions.cs | 52 ++++----- .../Internal/GenericStoreProxy.cs | 8 +- .../Internal/StorageFactory.cs | 77 ++++++++----- .../Internal/StorageProviderBase.cs | 35 +++--- src/GeekLearning.Storage/StorageOptions.cs | 19 --- ... => StorageServiceCollectionExtensions.cs} | 3 +- .../DeleteTests.cs | 2 +- .../GenericIStoreTests.cs | 2 +- .../ListTests.cs | 14 +-- .../ReadTests.cs | 14 +-- .../StoresFixture.cs | 90 ++++++++++----- .../TestStore.cs | 20 +++- .../UpdateTests.cs | 14 +-- .../appsettings.json | 67 ++++++++--- 56 files changed, 799 insertions(+), 314 deletions(-) create mode 100644 src/GeekLearning.Storage.Azure/Configuration/AzureParsedOptions.cs create mode 100644 src/GeekLearning.Storage.Azure/Configuration/AzureProviderInstanceOptions.cs create mode 100644 src/GeekLearning.Storage.Azure/Configuration/AzureScopedStoreOptions.cs create mode 100644 src/GeekLearning.Storage.Azure/Configuration/AzureStoreOptions.cs delete mode 100644 src/GeekLearning.Storage.Azure/ProviderOptions.cs delete mode 100644 src/GeekLearning.Storage.Azure/StoreOptions.cs create mode 100644 src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs create mode 100644 src/GeekLearning.Storage.FileSystem/Configuration/FileSystemProviderInstanceOptions.cs create mode 100644 src/GeekLearning.Storage.FileSystem/Configuration/FileSystemScopedStoreOptions.cs create mode 100644 src/GeekLearning.Storage.FileSystem/Configuration/FileSystemStoreOptions.cs delete mode 100644 src/GeekLearning.Storage.FileSystem/ProviderOptions.cs delete mode 100644 src/GeekLearning.Storage.FileSystem/StoreOptions.cs create mode 100644 src/GeekLearning.Storage/Configuration/AccessLevel.cs create mode 100644 src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs create mode 100644 src/GeekLearning.Storage/Configuration/INamedElementOptions.cs create mode 100644 src/GeekLearning.Storage/Configuration/IParsedOptions.cs create mode 100644 src/GeekLearning.Storage/Configuration/IProviderInstanceOptions.cs create mode 100644 src/GeekLearning.Storage/Configuration/IScopedStoreOptions.cs create mode 100644 src/GeekLearning.Storage/Configuration/IStoreOptions.cs create mode 100644 src/GeekLearning.Storage/Configuration/ProviderInstanceOptions.cs create mode 100644 src/GeekLearning.Storage/Configuration/ScopedStoreOptions.cs create mode 100644 src/GeekLearning.Storage/Configuration/StorageOptions.cs create mode 100644 src/GeekLearning.Storage/Configuration/StoreOptions.cs create mode 100644 src/GeekLearning.Storage/Exceptions/BadProviderConfiguration.cs create mode 100644 src/GeekLearning.Storage/Exceptions/BadStoreConfiguration.cs delete mode 100644 src/GeekLearning.Storage/IProviderOptions.cs delete mode 100644 src/GeekLearning.Storage/IProviderStoreOptions.cs delete mode 100644 src/GeekLearning.Storage/IStorageStoreOptions.cs delete mode 100644 src/GeekLearning.Storage/StorageOptions.cs rename src/GeekLearning.Storage/{StorageExtensions.cs => StorageServiceCollectionExtensions.cs} (90%) diff --git a/README.md b/README.md index 920c59c..b8d5220 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.svg?style=flat-square&label=nuget:%20primitives)](https://www.nuget.org/packages/GeekLearning.Storage/) -[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.FileSystem.svg?style=flat-square&label=nuget:%20filesystem)](https://www.nuget.org/packages/GeekLearning.Storage.FileSystem/) -[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.Azure.svg?style=flat-square&label=nuget:%20azure%20 storage)](https://www.nuget.org/packages/GeekLearning.Storage.Azure/) +[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.svg?style=flat-square&label=NuGet:%20Abstractions)](https://www.nuget.org/packages/GeekLearning.Storage/) +[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.FileSystem.svg?style=flat-square&label=NuGet:%20FileSystem)](https://www.nuget.org/packages/GeekLearning.Storage.FileSystem/) +[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.Azure.svg?style=flat-square&label=NuGet:%20Azure%20Storage)](https://www.nuget.org/packages/GeekLearning.Storage.Azure/) [![Build Status](https://geeklearning.visualstudio.com/_apis/public/build/definitions/f841b266-7595-4d01-9ee1-4864cf65aa73/27/badge)](#) # Geek Learning Cloud Storage Abstraction diff --git a/samples/GeekLearning.Storage.BasicSample/appsettings.json b/samples/GeekLearning.Storage.BasicSample/appsettings.json index aff87c6..4d8074f 100644 --- a/samples/GeekLearning.Storage.BasicSample/appsettings.json +++ b/samples/GeekLearning.Storage.BasicSample/appsettings.json @@ -8,28 +8,54 @@ } }, "Storage": { + "Providers": { - "Azure": { - "DefaultConnectionString": "vdsvdz" + "FirstAzure": { + "Type": "Azure", + "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=default;AccountKey=;EndpointSuffix=core.windows.net" }, - "FileSystem": { - "RootPath": "" + "AnotherAzure": { + "Type": "Azure", + "ConnectionStringName": "ConnectionStringFromAppSettings" + }, + "FirstFileSystem": { + "Type": "FileSystem", + "RootPath": "C:/First" + }, + "AnotherFileSystem": { + "Type": "FileSystem", + "RootPath": "D:/Another" } }, + "Stores": { - "Templates": { - "Provider": "FileSystem", - "Parameters": { - "Path": "Templates", - "Access": "Public" - } + "Youpi1": { + "ProviderName": "FirstFileSystem" + }, + "Youpi2": { + "ProviderName": "FirstFileSystem", + "AccessLevel": "Public", + "FolderName": "AnotherPath" + }, + "Youpi4": { + "ProviderName": "FirstAzure", + "AccessLevel": "Private" + }, + "Youpa2": { + "ProviderType": "Azure", + "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=excpetionaccount;AccountKey=;EndpointSuffix=core.windows.net" + } + }, + + "ScopedStores": { + "Youpi3": { + "ProviderName": "AnotherFileSystem", + "FolderNameFormat": "AnotherPath-{0}" }, - "Youpi": { - "Provider": "Azure", - "Parameters": { - "ConnectionString": "hfeioa", - "Container": "Public" - } + "Youpa": { + "ProviderName": "AnotherAzure", + "AccessLevel": "Confidential", + "FolderNameFormat": "Youpa-{0}" } } } diff --git a/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs b/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs index 82c2853..cc70c51 100644 --- a/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs +++ b/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs @@ -1,6 +1,8 @@ namespace GeekLearning.Storage { using Azure; + using GeekLearning.Storage.Azure.Configuration; + using GeekLearning.Storage.Configuration; using GeekLearning.Storage.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -12,14 +14,14 @@ public static class AzureStorageExtensions public static IServiceCollection AddAzureStorage(this IServiceCollection services) { return services - .AddSingleton, ConfigureProviderOptions>() + .AddSingleton, ConfigureProviderOptions>() .AddAzureStorageServices(); } public static IServiceCollection AddAzureStorage(this IServiceCollection services, IConfiguration configuration) { return services - .Configure(configuration) + .Configure(configuration) .AddAzureStorageServices(); } diff --git a/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs b/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs index 1989964..87aca75 100644 --- a/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs +++ b/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs @@ -1,23 +1,24 @@ namespace GeekLearning.Storage.Azure { + using GeekLearning.Storage.Azure.Configuration; using GeekLearning.Storage.Internal; using Microsoft.Extensions.Options; using Storage; - public class AzureStorageProvider : StorageProviderBase + public class AzureStorageProvider : StorageProviderBase { public const string ProviderName = "Azure"; - public AzureStorageProvider(IOptions options) + public AzureStorageProvider(IOptions options) : base(options) { } public override string Name => ProviderName; - protected override IStore BuildStore(string storeName, StoreOptions storeOptions) + protected override IStore BuildStore(string storeName, AzureStoreOptions storeOptions) { - return new AzureStore(storeName, storeOptions.ConnectionString, storeOptions.Container); + return new AzureStore(storeOptions); } } } diff --git a/src/GeekLearning.Storage.Azure/AzureStore.cs b/src/GeekLearning.Storage.Azure/AzureStore.cs index ff125a7..f09da9d 100644 --- a/src/GeekLearning.Storage.Azure/AzureStore.cs +++ b/src/GeekLearning.Storage.Azure/AzureStore.cs @@ -1,5 +1,6 @@ namespace GeekLearning.Storage.Azure { + using GeekLearning.Storage.Azure.Configuration; using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Blob; using Microsoft.WindowsAzure.Storage.Core; @@ -11,28 +12,30 @@ public class AzureStore : IStore { - private Lazy client; - private Lazy container; + private readonly AzureStoreOptions storeOptions; + private readonly Lazy client; + private readonly Lazy container; - public AzureStore(string storeName, string connectionString, string containerName) + public AzureStore(AzureStoreOptions storeOptions) { - this.Name = storeName; + this.storeOptions = storeOptions; - if (string.IsNullOrWhiteSpace(connectionString)) - { - throw new ArgumentNullException("connectionString"); - } + // TODO: Create Validate method in IStoreOptions + //if (string.IsNullOrWhiteSpace(connectionString)) + //{ + // throw new ArgumentNullException("connectionString"); + //} - if (string.IsNullOrWhiteSpace(containerName)) - { - throw new ArgumentNullException("containerName"); - } + //if (string.IsNullOrWhiteSpace(containerName)) + //{ + // throw new ArgumentNullException("containerName"); + //} - this.client = new Lazy(() => CloudStorageAccount.Parse(connectionString).CreateCloudBlobClient()); - this.container = new Lazy(() => this.client.Value.GetContainerReference(containerName)); + this.client = new Lazy(() => CloudStorageAccount.Parse(storeOptions.ConnectionString).CreateCloudBlobClient()); + this.container = new Lazy(() => this.client.Value.GetContainerReference(storeOptions.FolderName)); } - public string Name { get; } + public string Name => this.storeOptions.Name; public async Task ListAsync(string path, bool recursive, bool withMetadata) { diff --git a/src/GeekLearning.Storage.Azure/Configuration/AzureParsedOptions.cs b/src/GeekLearning.Storage.Azure/Configuration/AzureParsedOptions.cs new file mode 100644 index 0000000..251f5bf --- /dev/null +++ b/src/GeekLearning.Storage.Azure/Configuration/AzureParsedOptions.cs @@ -0,0 +1,37 @@ +namespace GeekLearning.Storage.Azure.Configuration +{ + using GeekLearning.Storage.Configuration; + using System.Collections.Generic; + + public class AzureParsedOptions : IParsedOptions + { + public string Name => AzureStorageProvider.ProviderName; + + public IReadOnlyDictionary ParsedProviderInstances { get; set; } + + public IReadOnlyDictionary ParsedStores { get; set; } + + public IReadOnlyDictionary ParsedScopedStores { get; set; } + + public void BindStoreOptions(AzureStoreOptions storeOptions, AzureProviderInstanceOptions providerInstanceOptions = null) + { + storeOptions.FolderName = storeOptions.FolderName.ToLowerInvariant(); + + if (providerInstanceOptions == null + || storeOptions.ProviderName != providerInstanceOptions.Name) + { + return; + } + + if (string.IsNullOrEmpty(storeOptions.ConnectionString)) + { + storeOptions.ConnectionString = providerInstanceOptions.ConnectionString; + } + + if (string.IsNullOrEmpty(storeOptions.ConnectionStringName)) + { + storeOptions.ConnectionStringName = providerInstanceOptions.ConnectionStringName; + } + } + } +} diff --git a/src/GeekLearning.Storage.Azure/Configuration/AzureProviderInstanceOptions.cs b/src/GeekLearning.Storage.Azure/Configuration/AzureProviderInstanceOptions.cs new file mode 100644 index 0000000..b97f168 --- /dev/null +++ b/src/GeekLearning.Storage.Azure/Configuration/AzureProviderInstanceOptions.cs @@ -0,0 +1,11 @@ +namespace GeekLearning.Storage.Azure.Configuration +{ + using GeekLearning.Storage.Configuration; + + public class AzureProviderInstanceOptions : ProviderInstanceOptions + { + public string ConnectionString { get; set; } + + public string ConnectionStringName { get; set; } + } +} diff --git a/src/GeekLearning.Storage.Azure/Configuration/AzureScopedStoreOptions.cs b/src/GeekLearning.Storage.Azure/Configuration/AzureScopedStoreOptions.cs new file mode 100644 index 0000000..4c74736 --- /dev/null +++ b/src/GeekLearning.Storage.Azure/Configuration/AzureScopedStoreOptions.cs @@ -0,0 +1,8 @@ +namespace GeekLearning.Storage.Azure.Configuration +{ + using GeekLearning.Storage.Configuration; + + public class AzureScopedStoreOptions : ScopedStoreOptions + { + } +} diff --git a/src/GeekLearning.Storage.Azure/Configuration/AzureStoreOptions.cs b/src/GeekLearning.Storage.Azure/Configuration/AzureStoreOptions.cs new file mode 100644 index 0000000..4401fde --- /dev/null +++ b/src/GeekLearning.Storage.Azure/Configuration/AzureStoreOptions.cs @@ -0,0 +1,11 @@ +namespace GeekLearning.Storage.Azure.Configuration +{ + using GeekLearning.Storage.Configuration; + + public class AzureStoreOptions : StoreOptions + { + public string ConnectionString { get; set; } + + public string ConnectionStringName { get; set; } + } +} diff --git a/src/GeekLearning.Storage.Azure/ProviderOptions.cs b/src/GeekLearning.Storage.Azure/ProviderOptions.cs deleted file mode 100644 index a9940d6..0000000 --- a/src/GeekLearning.Storage.Azure/ProviderOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace GeekLearning.Storage.Azure -{ - using System.Collections.Generic; - - public class ProviderOptions : IProviderOptions - { - public string ProviderName => AzureStorageProvider.ProviderName; - - public string DefaultConnectionString { get; set; } - - public IReadOnlyDictionary Stores { get; set; } - } -} diff --git a/src/GeekLearning.Storage.Azure/StoreOptions.cs b/src/GeekLearning.Storage.Azure/StoreOptions.cs deleted file mode 100644 index 6bf96a8..0000000 --- a/src/GeekLearning.Storage.Azure/StoreOptions.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace GeekLearning.Storage.Azure -{ - public class StoreOptions : IProviderStoreOptions - { - public string ConnectionString { get; set; } - - public string Container { get; set; } - } -} diff --git a/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs b/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs index c3d8d43..e2bd947 100644 --- a/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs +++ b/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs @@ -1,5 +1,6 @@ namespace GeekLearning.Storage.FileSystem.Server { + using GeekLearning.Storage.FileSystem.Configuration; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -12,14 +13,14 @@ public class FileSystemStorageServerMiddleware private ILogger logger; private IOptions serverOptions; - private IOptions storageOptions; + private FileSystemParsedOptions fileSystemParsedOptions; public FileSystemStorageServerMiddleware(RequestDelegate next, IOptions serverOptions, ILogger logger, - IOptions storageOptions) + IOptions fileSystemParsedOptions) { - this.storageOptions = storageOptions; + this.fileSystemParsedOptions = fileSystemParsedOptions.Value; this.next = next; this.serverOptions = serverOptions; this.logger = logger; @@ -33,9 +34,8 @@ public async Task Invoke(HttpContext context) var storeName = context.Request.Path.Value.Substring(1, subPathStart - 1); var storageFactory = context.RequestServices.GetRequiredService(); - StorageOptions.StorageStoreOptions storeOptions; - if (this.storageOptions.Value.Stores.TryGetValue(storeName, out storeOptions) - && storeOptions.Provider == "FileSystem") + if (this.fileSystemParsedOptions.ParsedStores.TryGetValue(storeName, out var storeOptions) + && storeOptions.ProviderType == "FileSystem") { string access; // TODO: Fix options! diff --git a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs new file mode 100644 index 0000000..c98956e --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs @@ -0,0 +1,35 @@ +namespace GeekLearning.Storage.FileSystem.Configuration +{ + using GeekLearning.Storage.Configuration; + using System.Collections.Generic; + + public class FileSystemParsedOptions : IParsedOptions + { + public string Name => FileSystemStorageProvider.ProviderName; + + public string RootPath { get; set; } + + public IReadOnlyDictionary ParsedProviderInstances { get; set; } + + public IReadOnlyDictionary ParsedStores { get; set; } + + public IReadOnlyDictionary ParsedScopedStores { get; set; } + + public void BindStoreOptions(FileSystemStoreOptions storeOptions, FileSystemProviderInstanceOptions providerInstanceOptions = null) + { + if (string.IsNullOrEmpty(storeOptions.RootPath)) + { + if (providerInstanceOptions != null + && storeOptions.ProviderName == providerInstanceOptions.Name + && !string.IsNullOrEmpty(providerInstanceOptions.RootPath)) + { + storeOptions.RootPath = providerInstanceOptions.RootPath; + } + else + { + storeOptions.RootPath = this.RootPath; + } + } + } + } +} diff --git a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemProviderInstanceOptions.cs b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemProviderInstanceOptions.cs new file mode 100644 index 0000000..bcf5e3c --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemProviderInstanceOptions.cs @@ -0,0 +1,9 @@ +namespace GeekLearning.Storage.FileSystem.Configuration +{ + using GeekLearning.Storage.Configuration; + + public class FileSystemProviderInstanceOptions : ProviderInstanceOptions + { + public string RootPath { get; set; } + } +} diff --git a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemScopedStoreOptions.cs b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemScopedStoreOptions.cs new file mode 100644 index 0000000..035cc73 --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemScopedStoreOptions.cs @@ -0,0 +1,8 @@ +namespace GeekLearning.Storage.FileSystem.Configuration +{ + using GeekLearning.Storage.Configuration; + + public class FileSystemScopedStoreOptions : ScopedStoreOptions + { + } +} diff --git a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemStoreOptions.cs b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemStoreOptions.cs new file mode 100644 index 0000000..af93561 --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemStoreOptions.cs @@ -0,0 +1,28 @@ +namespace GeekLearning.Storage.FileSystem.Configuration +{ + using GeekLearning.Storage.Configuration; + using System.IO; + + public class FileSystemStoreOptions : StoreOptions + { + public string RootPath { get; set; } + + public string AbsolutePath + { + get + { + if (string.IsNullOrEmpty(this.RootPath)) + { + return this.FolderName; + } + + if (string.IsNullOrEmpty(this.FolderName)) + { + return this.RootPath; + } + + return Path.Combine(this.RootPath, this.FolderName); + } + } + } +} diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs index cbae076..0b85c9b 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs @@ -1,6 +1,7 @@ namespace GeekLearning.Storage { using FileSystem; + using GeekLearning.Storage.FileSystem.Configuration; using GeekLearning.Storage.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -12,21 +13,21 @@ public static class FileSystemStorageExtensions public static IServiceCollection AddFileSystemStorage(this IServiceCollection services, string rootPath) { return services - .Configure(options => options.RootPath = rootPath) + .Configure(options => options.RootPath = rootPath) .AddFileSystemStorage(); } public static IServiceCollection AddFileSystemStorage(this IServiceCollection services) { return services - .AddSingleton, ConfigureProviderOptions>() + .AddSingleton, ConfigureProviderOptions>() .AddFileSystemStorageServices(); } public static IServiceCollection AddFileSystemStorage(this IServiceCollection services, IConfiguration configuration) { return services - .Configure(configuration) + .Configure(configuration) .AddFileSystemStorageServices(); } diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs index 966c513..3f5b26f 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs @@ -1,16 +1,17 @@ namespace GeekLearning.Storage.FileSystem { + using GeekLearning.Storage.FileSystem.Configuration; using GeekLearning.Storage.Internal; using Microsoft.Extensions.Options; using Storage; - public class FileSystemStorageProvider : StorageProviderBase + public class FileSystemStorageProvider : StorageProviderBase { public const string ProviderName = "FileSystem"; private readonly IPublicUrlProvider publicUrlProvider; private readonly IExtendedPropertiesProvider extendedPropertiesProvider; - public FileSystemStorageProvider(IOptions options, IPublicUrlProvider publicUrlProvider = null, IExtendedPropertiesProvider extendedPropertiesProvider = null) + public FileSystemStorageProvider(IOptions options, IPublicUrlProvider publicUrlProvider = null, IExtendedPropertiesProvider extendedPropertiesProvider = null) : base(options) { this.publicUrlProvider = publicUrlProvider; @@ -19,12 +20,10 @@ public FileSystemStorageProvider(IOptions options, IPublicUrlPr public override string Name => ProviderName; - protected override IStore BuildStore(string storeName, StoreOptions storeOptions) + protected override IStore BuildStore(string storeName, FileSystemStoreOptions storeOptions) { return new FileSystemStore( - storeName, - storeOptions.Path, - this.options.Value.RootPath, + storeOptions, publicUrlProvider, extendedPropertiesProvider); } diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs index 32c0fd6..5983c96 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs @@ -1,5 +1,6 @@ namespace GeekLearning.Storage.FileSystem { + using GeekLearning.Storage.FileSystem.Configuration; using System; using System.Collections.Generic; using System.IO; @@ -9,33 +10,35 @@ public class FileSystemStore : IStore { + private readonly FileSystemStoreOptions storeOptions; private readonly IPublicUrlProvider publicUrlProvider; private readonly IExtendedPropertiesProvider extendedPropertiesProvider; - public FileSystemStore(string storeName, string path, string rootPath, IPublicUrlProvider publicUrlProvider, IExtendedPropertiesProvider extendedPropertiesProvider) + public FileSystemStore(FileSystemStoreOptions storeOptions, IPublicUrlProvider publicUrlProvider, IExtendedPropertiesProvider extendedPropertiesProvider) { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } - - if (Path.IsPathRooted(path)) - { - this.AbsolutePath = path; - } - else - { - this.AbsolutePath = Path.Combine(rootPath, path); - } - - this.Name = storeName; + // TODO: Implement Validate method on options + //if (string.IsNullOrEmpty(path)) + //{ + // throw new ArgumentNullException("path"); + //} + + //if (Path.IsPathRooted(path)) + //{ + // this.AbsolutePath = path; + //} + //else + //{ + // this.AbsolutePath = Path.Combine(rootPath, path); + //} + + this.storeOptions = storeOptions; this.publicUrlProvider = publicUrlProvider; this.extendedPropertiesProvider = extendedPropertiesProvider; } - public string Name { get; } + public string Name => storeOptions.Name; - internal string AbsolutePath { get; } + internal string AbsolutePath => storeOptions.AbsolutePath; public async Task ListAsync(string path, bool recursive, bool withMetadata) { diff --git a/src/GeekLearning.Storage.FileSystem/ProviderOptions.cs b/src/GeekLearning.Storage.FileSystem/ProviderOptions.cs deleted file mode 100644 index 0864742..0000000 --- a/src/GeekLearning.Storage.FileSystem/ProviderOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace GeekLearning.Storage.FileSystem -{ - using System.Collections.Generic; - - public class ProviderOptions : IProviderOptions - { - public string ProviderName => FileSystemStorageProvider.ProviderName; - - public string RootPath { get; set; } - - public IReadOnlyDictionary Stores { get; set; } - } -} diff --git a/src/GeekLearning.Storage.FileSystem/StoreOptions.cs b/src/GeekLearning.Storage.FileSystem/StoreOptions.cs deleted file mode 100644 index 8205b37..0000000 --- a/src/GeekLearning.Storage.FileSystem/StoreOptions.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace GeekLearning.Storage.FileSystem -{ - public class StoreOptions : IProviderStoreOptions - { - public string Path { get; set; } - } -} diff --git a/src/GeekLearning.Storage/Configuration/AccessLevel.cs b/src/GeekLearning.Storage/Configuration/AccessLevel.cs new file mode 100644 index 0000000..3442ada --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/AccessLevel.cs @@ -0,0 +1,9 @@ +namespace GeekLearning.Storage.Configuration +{ + public enum AccessLevel + { + Private = 0, + Confidential = 1, + Public = 2, + } +} diff --git a/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs b/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs new file mode 100644 index 0000000..a740d42 --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs @@ -0,0 +1,108 @@ +namespace GeekLearning.Storage.Configuration +{ + using Microsoft.Extensions.Configuration; + using System.Collections.Generic; + using System.Linq; + + public static class ConfigurationExtensions + { + public static IReadOnlyDictionary Parse(this Dictionary unparsedConfiguration) + where TOptions : class, INamedElementOptions, new() + { + if (unparsedConfiguration == null) + { + return new Dictionary(); + } + + return unparsedConfiguration + .ToDictionary( + kvp => kvp.Key, + kvp => BindOptions(kvp)); + } + + public static IStoreOptions GetStoreConfiguration(this IParsedOptions parsedOptions, string storeName, bool throwIfNotFound = true) + where TInstanceOptions : class, IProviderInstanceOptions + where TStoreOptions : class, IStoreOptions + where TScopedStoreOptions : class, IScopedStoreOptions + { + parsedOptions.ParsedStores.TryGetValue(storeName, out var storeOptions); + if (storeOptions != null) + { + return storeOptions; + } + + parsedOptions.ParsedScopedStores.TryGetValue(storeName, out var scopedStoreOptions); + if (scopedStoreOptions != null) + { + return scopedStoreOptions; + } + + if (throwIfNotFound) + { + throw new Exceptions.StoreNotFoundException(storeName); + } + + return null; + } + + public static void Compute(this TStoreOptions parsedStore, TParsedOptions options) + where TParsedOptions : class, IParsedOptions + where TInstanceOptions : class, IProviderInstanceOptions, new() + where TStoreOptions : class, IStoreOptions, new() + where TScopedStoreOptions : class, IScopedStoreOptions, new() + { + if (string.IsNullOrEmpty(parsedStore.FolderName)) + { + parsedStore.FolderName = parsedStore.Name; + } + + TInstanceOptions instanceOptions = null; + if (!string.IsNullOrEmpty(parsedStore.ProviderName)) + { + options.ParsedProviderInstances.TryGetValue(parsedStore.ProviderName, out instanceOptions); + if (instanceOptions == null) + { + return; + } + + parsedStore.ProviderType = instanceOptions.Type; + } + + options.BindStoreOptions(parsedStore, instanceOptions); + } + + public static TStoreOptions ParseStoreOptions(this IStoreOptions storeOptions, TParsedOptions options) + where TParsedOptions : class, IParsedOptions, new() + where TInstanceOptions : class, IProviderInstanceOptions, new() + where TStoreOptions : class, IStoreOptions, new() + where TScopedStoreOptions : class, IScopedStoreOptions, new() + { + if (!(storeOptions is TStoreOptions parsedStoreOptions)) + { + parsedStoreOptions = new TStoreOptions + { + Name = storeOptions.Name, + ProviderName = storeOptions.ProviderName, + ProviderType = storeOptions.ProviderType, + AccessLevel = storeOptions.AccessLevel, + FolderName = storeOptions.FolderName, + }; + } + + parsedStoreOptions.Compute(options); + return parsedStoreOptions; + } + + private static TOptions BindOptions(KeyValuePair kvp) + where TOptions : class, INamedElementOptions, new() + { + var options = new TOptions + { + Name = kvp.Key, + }; + + ConfigurationBinder.Bind(kvp.Value, options); + return options; + } + } +} diff --git a/src/GeekLearning.Storage/Configuration/INamedElementOptions.cs b/src/GeekLearning.Storage/Configuration/INamedElementOptions.cs new file mode 100644 index 0000000..b262f4c --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/INamedElementOptions.cs @@ -0,0 +1,7 @@ +namespace GeekLearning.Storage.Configuration +{ + public interface INamedElementOptions + { + string Name { get; set; } + } +} diff --git a/src/GeekLearning.Storage/Configuration/IParsedOptions.cs b/src/GeekLearning.Storage/Configuration/IParsedOptions.cs new file mode 100644 index 0000000..833187c --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/IParsedOptions.cs @@ -0,0 +1,20 @@ +namespace GeekLearning.Storage.Configuration +{ + using System.Collections.Generic; + + public interface IParsedOptions + where TInstanceOptions : class, IProviderInstanceOptions + where TStoreOptions : class, IStoreOptions + where TScopedStoreOptions : class, IScopedStoreOptions + { + string Name { get; } + + IReadOnlyDictionary ParsedProviderInstances { get; set; } + + IReadOnlyDictionary ParsedStores { get; set; } + + IReadOnlyDictionary ParsedScopedStores { get; set; } + + void BindStoreOptions(TStoreOptions storeOptions, TInstanceOptions providerInstanceOptions = null); + } +} diff --git a/src/GeekLearning.Storage/Configuration/IProviderInstanceOptions.cs b/src/GeekLearning.Storage/Configuration/IProviderInstanceOptions.cs new file mode 100644 index 0000000..1f8d240 --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/IProviderInstanceOptions.cs @@ -0,0 +1,7 @@ +namespace GeekLearning.Storage.Configuration +{ + public interface IProviderInstanceOptions : INamedElementOptions + { + string Type { get; } + } +} diff --git a/src/GeekLearning.Storage/Configuration/IScopedStoreOptions.cs b/src/GeekLearning.Storage/Configuration/IScopedStoreOptions.cs new file mode 100644 index 0000000..52aeed9 --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/IScopedStoreOptions.cs @@ -0,0 +1,7 @@ +namespace GeekLearning.Storage.Configuration +{ + public interface IScopedStoreOptions : IStoreOptions + { + string FolderNameFormat { get; } + } +} diff --git a/src/GeekLearning.Storage/Configuration/IStoreOptions.cs b/src/GeekLearning.Storage/Configuration/IStoreOptions.cs new file mode 100644 index 0000000..c55478e --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/IStoreOptions.cs @@ -0,0 +1,13 @@ +namespace GeekLearning.Storage.Configuration +{ + public interface IStoreOptions : INamedElementOptions + { + string ProviderName { get; set; } + + string ProviderType { get; set; } + + AccessLevel AccessLevel { get; set; } + + string FolderName { get; set; } + } +} diff --git a/src/GeekLearning.Storage/Configuration/ProviderInstanceOptions.cs b/src/GeekLearning.Storage/Configuration/ProviderInstanceOptions.cs new file mode 100644 index 0000000..ae0e77c --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/ProviderInstanceOptions.cs @@ -0,0 +1,9 @@ +namespace GeekLearning.Storage.Configuration +{ + public class ProviderInstanceOptions : IProviderInstanceOptions + { + public string Name { get; set; } + + public string Type { get; set; } + } +} diff --git a/src/GeekLearning.Storage/Configuration/ScopedStoreOptions.cs b/src/GeekLearning.Storage/Configuration/ScopedStoreOptions.cs new file mode 100644 index 0000000..8dd4fe2 --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/ScopedStoreOptions.cs @@ -0,0 +1,7 @@ +namespace GeekLearning.Storage.Configuration +{ + public class ScopedStoreOptions : StoreOptions, IScopedStoreOptions + { + public string FolderNameFormat { get; set; } + } +} diff --git a/src/GeekLearning.Storage/Configuration/StorageOptions.cs b/src/GeekLearning.Storage/Configuration/StorageOptions.cs new file mode 100644 index 0000000..821db5d --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/StorageOptions.cs @@ -0,0 +1,43 @@ +namespace GeekLearning.Storage.Configuration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.Collections.Generic; + + public class StorageOptions : IParsedOptions + { + internal const string GlobalOptionsName = "Global"; + + private readonly Lazy> parsedProviderInstances; + private readonly Lazy> parsedStores; + private readonly Lazy> parsedScopedStores; + + public StorageOptions() + { + this.parsedProviderInstances = new Lazy>( + () => this.Providers.Parse()); + this.parsedStores = new Lazy>( + () => this.Stores.Parse()); + this.parsedScopedStores = new Lazy>( + () => this.ScopedStores.Parse()); + } + + public string Name => GlobalOptionsName; + + public Dictionary Providers { get; set; } + + public Dictionary Stores { get; set; } + + public Dictionary ScopedStores { get; set; } + + public IReadOnlyDictionary ParsedProviderInstances { get => this.parsedProviderInstances.Value; set { } } + + public IReadOnlyDictionary ParsedStores { get => this.parsedStores.Value; set { } } + + public IReadOnlyDictionary ParsedScopedStores { get => this.parsedScopedStores.Value; set { } } + + public void BindStoreOptions(StoreOptions storeOptions, ProviderInstanceOptions providerInstanceOptions) + { + } + } +} diff --git a/src/GeekLearning.Storage/Configuration/StoreOptions.cs b/src/GeekLearning.Storage/Configuration/StoreOptions.cs new file mode 100644 index 0000000..c200b76 --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/StoreOptions.cs @@ -0,0 +1,15 @@ +namespace GeekLearning.Storage.Configuration +{ + public class StoreOptions : IStoreOptions + { + public string Name { get; set; } + + public string ProviderName { get; set; } + + public string ProviderType { get; set; } + + public AccessLevel AccessLevel { get; set; } + + public string FolderName { get; set; } + } +} diff --git a/src/GeekLearning.Storage/Exceptions/BadProviderConfiguration.cs b/src/GeekLearning.Storage/Exceptions/BadProviderConfiguration.cs new file mode 100644 index 0000000..95a38a8 --- /dev/null +++ b/src/GeekLearning.Storage/Exceptions/BadProviderConfiguration.cs @@ -0,0 +1,17 @@ +namespace GeekLearning.Storage.Exceptions +{ + using System; + + public class BadProviderConfiguration : Exception + { + public BadProviderConfiguration(string providerName) + : base($"The provider '{providerName}' was not properly configured.") + { + } + + public BadProviderConfiguration(string providerName, string details) + : base($"The providerName '{providerName}' was not properly configured. {details}") + { + } + } +} diff --git a/src/GeekLearning.Storage/Exceptions/BadStoreConfiguration.cs b/src/GeekLearning.Storage/Exceptions/BadStoreConfiguration.cs new file mode 100644 index 0000000..5ceb28e --- /dev/null +++ b/src/GeekLearning.Storage/Exceptions/BadStoreConfiguration.cs @@ -0,0 +1,17 @@ +namespace GeekLearning.Storage.Exceptions +{ + using System; + + public class BadStoreConfiguration : Exception + { + public BadStoreConfiguration(string storeName) + : base($"The store '{storeName}' was not properly configured.") + { + } + + public BadStoreConfiguration(string storeName, string details) + : base($"The store '{storeName}' was not properly configured. {details}") + { + } + } +} diff --git a/src/GeekLearning.Storage/Exceptions/ProviderNotFoundException.cs b/src/GeekLearning.Storage/Exceptions/ProviderNotFoundException.cs index 1e108b9..af57f98 100644 --- a/src/GeekLearning.Storage/Exceptions/ProviderNotFoundException.cs +++ b/src/GeekLearning.Storage/Exceptions/ProviderNotFoundException.cs @@ -4,7 +4,8 @@ public class ProviderNotFoundException : Exception { - public ProviderNotFoundException(string providerName) : base($"The configured provider '{providerName}' was not found. Did you forget to register providers in your Startup.ConfigureServices?") + public ProviderNotFoundException(string providerName) + : base($"The configured provider '{providerName}' was not found. Did you forget to register providers in your Startup.ConfigureServices?") { } } diff --git a/src/GeekLearning.Storage/Exceptions/StoreNotFoundException.cs b/src/GeekLearning.Storage/Exceptions/StoreNotFoundException.cs index b161382..b93f841 100644 --- a/src/GeekLearning.Storage/Exceptions/StoreNotFoundException.cs +++ b/src/GeekLearning.Storage/Exceptions/StoreNotFoundException.cs @@ -4,7 +4,8 @@ public class StoreNotFoundException : Exception { - public StoreNotFoundException(string storeName) : base($"The configured store '{storeName}' was not found. Did you configure it properly in your appsettings.json?") + public StoreNotFoundException(string storeName) + : base($"The configured store '{storeName}' was not found. Did you configure it properly in your appsettings.json?") { } } diff --git a/src/GeekLearning.Storage/IProviderOptions.cs b/src/GeekLearning.Storage/IProviderOptions.cs deleted file mode 100644 index 73e5f50..0000000 --- a/src/GeekLearning.Storage/IProviderOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace GeekLearning.Storage -{ - using System.Collections.Generic; - - public interface IProviderOptions - where TStoreOptions: class, IProviderStoreOptions - { - string ProviderName { get; } - - IReadOnlyDictionary Stores { get; set; } - } -} diff --git a/src/GeekLearning.Storage/IProviderStoreOptions.cs b/src/GeekLearning.Storage/IProviderStoreOptions.cs deleted file mode 100644 index 3c0f837..0000000 --- a/src/GeekLearning.Storage/IProviderStoreOptions.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace GeekLearning.Storage -{ - public interface IProviderStoreOptions - { - } -} diff --git a/src/GeekLearning.Storage/IStorageFactory.cs b/src/GeekLearning.Storage/IStorageFactory.cs index 4bf8398..a63dd9a 100644 --- a/src/GeekLearning.Storage/IStorageFactory.cs +++ b/src/GeekLearning.Storage/IStorageFactory.cs @@ -1,8 +1,10 @@ namespace GeekLearning.Storage { + using Configuration; + public interface IStorageFactory { - IStore GetStore(string storeName, IStorageStoreOptions configuration); + IStore GetStore(string storeName, IStoreOptions configuration); IStore GetStore(string storeName); diff --git a/src/GeekLearning.Storage/IStorageProvider.cs b/src/GeekLearning.Storage/IStorageProvider.cs index b2c8dfb..07d5c5d 100644 --- a/src/GeekLearning.Storage/IStorageProvider.cs +++ b/src/GeekLearning.Storage/IStorageProvider.cs @@ -1,11 +1,13 @@ namespace GeekLearning.Storage { + using Configuration; + public interface IStorageProvider { string Name { get; } IStore BuildStore(string storeName); - IStore BuildStore(string storeName, IStorageStoreOptions storeOptions); + IStore BuildStore(string storeName, IStoreOptions storeOptions); } } diff --git a/src/GeekLearning.Storage/IStorageStoreOptions.cs b/src/GeekLearning.Storage/IStorageStoreOptions.cs deleted file mode 100644 index 0bc6855..0000000 --- a/src/GeekLearning.Storage/IStorageStoreOptions.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace GeekLearning.Storage -{ - using Microsoft.Extensions.Configuration; - - public interface IStorageStoreOptions - { - string Provider { get; } - - IConfigurationSection Parameters { get; } - } -} diff --git a/src/GeekLearning.Storage/IStore{TOptions}.cs b/src/GeekLearning.Storage/IStore{TOptions}.cs index 709f787..c278242 100644 --- a/src/GeekLearning.Storage/IStore{TOptions}.cs +++ b/src/GeekLearning.Storage/IStore{TOptions}.cs @@ -1,7 +1,9 @@ namespace GeekLearning.Storage { + using Configuration; + public interface IStore : IStore - where TOptions : class, IStorageStoreOptions, new() + where TOptions : class, IStoreOptions, new() { } } diff --git a/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs b/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs index d6d12c2..2931fa7 100644 --- a/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs +++ b/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs @@ -1,48 +1,48 @@ namespace GeekLearning.Storage.Internal { - using Microsoft.Extensions.Configuration; + using GeekLearning.Storage.Configuration; using Microsoft.Extensions.Options; using System.Linq; - public class ConfigureProviderOptions : IConfigureOptions - where TProviderOptions : class, IProviderOptions, new() - where TStoreOptions: class, IProviderStoreOptions, new() + public class ConfigureProviderOptions : IConfigureOptions + where TParsedOptions : class, IParsedOptions + where TInstanceOptions : class, IProviderInstanceOptions, new() + where TStoreOptions : class, IStoreOptions, new() + where TScopedStoreOptions : class, IScopedStoreOptions, new() { - private readonly IOptions storageOptions; + private readonly StorageOptions storageOptions; public ConfigureProviderOptions(IOptions storageOptions) { - this.storageOptions = storageOptions; + this.storageOptions = storageOptions.Value; } - public void Configure(TProviderOptions options) + public void Configure(TParsedOptions options) { - var storageOptionsValue = this.storageOptions.Value; - - if (storageOptionsValue == null) + if (this.storageOptions == null) { return; } - if (storageOptionsValue.Providers != null - && storageOptionsValue.Providers.TryGetValue(options.ProviderName, out var providerConfigurationSection)) - { - ConfigurationBinder.Bind(providerConfigurationSection, options); - } + options.ParsedProviderInstances = this.storageOptions.Providers.Parse() + .Where(kvp => kvp.Value.Type == options.Name) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - if (storageOptionsValue.Stores != null) + var parsedStores = this.storageOptions.Stores.Parse(); + var parsedScopedStores = this.storageOptions.ScopedStores.Parse(); + + foreach (var parsedStore in parsedStores) { - options.Stores = storageOptionsValue.Stores - .Where(skvp => skvp.Value.Provider == options.ProviderName) - .ToDictionary( - skvp => skvp.Key, - skvp => - { - var storeOptions = new TStoreOptions(); - ConfigurationBinder.Bind(skvp.Value.Parameters, storeOptions); - return storeOptions; - }); + parsedStore.Value.Compute(options); } + + options.ParsedStores = parsedStores + .Where(kvp => kvp.Value.ProviderType == options.Name) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + options.ParsedScopedStores = parsedScopedStores + .Where(kvp => kvp.Value.ProviderType == options.Name) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); } } } diff --git a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs index 0e740f9..08dde70 100644 --- a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs +++ b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs @@ -1,17 +1,23 @@ namespace GeekLearning.Storage.Internal { + using Configuration; using Microsoft.Extensions.Options; using System; using System.IO; using System.Threading.Tasks; public class GenericStoreProxy : IStore, IStore - where TOptions : class, IStorageStoreOptions, new() + where TOptions : class, IStoreOptions, new() { private IStore innerStore; public GenericStoreProxy(IStorageFactory factory, IOptions options) { + if (options == null) + { + throw new ArgumentNullException("options", "Unable to build generic Store. Did you forget to configure your options?"); + } + this.innerStore = factory.GetStore(nameof(TOptions), options.Value); } diff --git a/src/GeekLearning.Storage/Internal/StorageFactory.cs b/src/GeekLearning.Storage/Internal/StorageFactory.cs index b8c8a5b..e43f178 100644 --- a/src/GeekLearning.Storage/Internal/StorageFactory.cs +++ b/src/GeekLearning.Storage/Internal/StorageFactory.cs @@ -1,49 +1,57 @@ namespace GeekLearning.Storage.Internal { + using GeekLearning.Storage.Configuration; using Microsoft.Extensions.Options; using System.Collections.Generic; using System.Linq; public class StorageFactory : IStorageFactory { - private IOptions options; - private IEnumerable storageProviders; + private StorageOptions options; + private IReadOnlyDictionary storageProviders; public StorageFactory(IEnumerable storageProviders, IOptions options) { - this.storageProviders = storageProviders; - this.options = options; + this.storageProviders = storageProviders.ToDictionary(sp => sp.Name, sp => sp); + this.options = options.Value; } - public IStore GetStore(string storeName, IStorageStoreOptions configuration) + public IStore GetStore(string storeName, IStoreOptions configuration) { - return this.GetProvider(configuration.Provider).BuildStore(storeName, configuration); + return this.GetProvider(configuration).BuildStore(storeName, configuration); } public IStore GetStore(string storeName) { - return this.GetProvider(this.GetStoreConfiguration(storeName).Provider).BuildStore(storeName); + return this.GetProvider(this.options.GetStoreConfiguration(storeName)).BuildStore(storeName); } public bool TryGetStore(string storeName, out IStore store) { - if (this.options.Value.Stores.TryGetValue(storeName, out var configuration)) + var configuration = this.options.GetStoreConfiguration(storeName, throwIfNotFound: false); + if (configuration != null) { - store = this.GetProvider(configuration.Provider).BuildStore(storeName); - return true; + var provider = this.GetProvider(configuration, throwIfNotFound: false); + if (provider != null) + { + store = provider.BuildStore(storeName); + return true; + } } store = null; return false; } - public bool TryGetStore(string storeName, out IStore store, string provider) + public bool TryGetStore(string storeName, out IStore store, string providerName) { - if (this.options.Value.Stores.TryGetValue(storeName, out var configuration)) + var configuration = this.options.GetStoreConfiguration(storeName, throwIfNotFound: false); + if (configuration != null) { - if (provider == configuration.Provider) + var provider = this.GetProvider(configuration, throwIfNotFound: false); + if (provider != null && provider.Name == providerName) { - store = this.GetProvider(configuration.Provider).BuildStore(storeName); + store = provider.BuildStore(storeName); return true; } } @@ -52,25 +60,42 @@ public bool TryGetStore(string storeName, out IStore store, string provider) return false; } - private IStorageProvider GetProvider(string providerName) + private IStorageProvider GetProvider(IStoreOptions configuration, bool throwIfNotFound = true) { - var provider = this.storageProviders.FirstOrDefault(p => p.Name == providerName); - if (provider == null) + string providerTypeName = null; + if (!string.IsNullOrEmpty(configuration.ProviderType)) + { + providerTypeName = configuration.ProviderType; + } + else if (!string.IsNullOrEmpty(configuration.ProviderName)) + { + this.options.ParsedProviderInstances.TryGetValue(configuration.ProviderName, out var providerInstanceOptions); + if (providerInstanceOptions != null) + { + providerTypeName = providerInstanceOptions.Type; + } + else if (throwIfNotFound) + { + throw new Exceptions.BadProviderConfiguration(configuration.ProviderName, "Unable to find it in the configuration."); + } + } + else if (throwIfNotFound) { - throw new Exceptions.ProviderNotFoundException(providerName); + throw new Exceptions.BadStoreConfiguration(configuration.Name, "You have to set either 'ProviderType' or 'ProviderName' on Store configuration."); } - return provider; - } + if (string.IsNullOrEmpty(providerTypeName)) + { + return null; + } - private StorageOptions.StorageStoreOptions GetStoreConfiguration(string storeName) - { - if (this.options.Value.Stores.TryGetValue(storeName, out var configuration)) + this.storageProviders.TryGetValue(providerTypeName, out var provider); + if (provider == null && throwIfNotFound) { - return configuration; + throw new Exceptions.ProviderNotFoundException(providerTypeName); } - throw new Exceptions.StoreNotFoundException(storeName); - } + return provider; + } } } diff --git a/src/GeekLearning.Storage/Internal/StorageProviderBase.cs b/src/GeekLearning.Storage/Internal/StorageProviderBase.cs index 78143fb..bb967e6 100644 --- a/src/GeekLearning.Storage/Internal/StorageProviderBase.cs +++ b/src/GeekLearning.Storage/Internal/StorageProviderBase.cs @@ -1,41 +1,38 @@ namespace GeekLearning.Storage.Internal { - using Microsoft.Extensions.Configuration; + using Configuration; using Microsoft.Extensions.Options; - public abstract class StorageProviderBase : IStorageProvider - where TProviderOptions : class, IProviderOptions, new() - where TStoreOptions : class, IProviderStoreOptions, new() + public abstract class StorageProviderBase : IStorageProvider + where TParsedOptions : class, IParsedOptions, new() + where TInstanceOptions : class, IProviderInstanceOptions, new() + where TStoreOptions : class, IStoreOptions, new() + where TScopedStoreOptions : class, IScopedStoreOptions, new() { - protected readonly IOptions options; + protected readonly TParsedOptions options; - public StorageProviderBase(IOptions options) + public StorageProviderBase(IOptions options) { - this.options = options; + this.options = options.Value; } public abstract string Name { get; } public IStore BuildStore(string storeName) { - if (this.options.Value.Stores.TryGetValue(storeName, out var storeOptions)) - { - return this.BuildStore(storeName, storeOptions); - } - - throw new Exceptions.BadStoreProviderException(this.Name, storeName); + return this.BuildStore(storeName, this.options.GetStoreConfiguration(storeName)); } - public IStore BuildStore(string storeName, IStorageStoreOptions storageStoreOptions) + public IStore BuildStore(string storeName, IStoreOptions storeOptions) { - if (storageStoreOptions.Provider != this.Name) + if (storeOptions.ProviderType != this.Name) { throw new Exceptions.BadStoreProviderException(this.Name, storeName); } - - var storeOptions = new TStoreOptions(); - ConfigurationBinder.Bind(storageStoreOptions.Parameters, storeOptions); - return this.BuildStore(storeName, storeOptions); + + return this.BuildStore( + storeName, + storeOptions.ParseStoreOptions(options)); } protected abstract IStore BuildStore(string storeName, TStoreOptions storeOptions); diff --git a/src/GeekLearning.Storage/StorageOptions.cs b/src/GeekLearning.Storage/StorageOptions.cs deleted file mode 100644 index 8da6e79..0000000 --- a/src/GeekLearning.Storage/StorageOptions.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace GeekLearning.Storage -{ - using Microsoft.Extensions.Configuration; - using System.Collections.Generic; - - public class StorageOptions - { - public Dictionary Providers { get; set; } - - public Dictionary Stores { get; set; } - - public class StorageStoreOptions : IStorageStoreOptions - { - public string Provider { get; set; } - - public IConfigurationSection Parameters { get; set; } - } - } -} diff --git a/src/GeekLearning.Storage/StorageExtensions.cs b/src/GeekLearning.Storage/StorageServiceCollectionExtensions.cs similarity index 90% rename from src/GeekLearning.Storage/StorageExtensions.cs rename to src/GeekLearning.Storage/StorageServiceCollectionExtensions.cs index b352a2a..e08e39c 100644 --- a/src/GeekLearning.Storage/StorageExtensions.cs +++ b/src/GeekLearning.Storage/StorageServiceCollectionExtensions.cs @@ -1,10 +1,11 @@ namespace GeekLearning.Storage { + using Configuration; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; - public static class StorageExtensions + public static class StorageServiceCollectionExtensions { public static IServiceCollection AddStorage(this IServiceCollection services) { diff --git a/tests/GeekLearning.Storage.Integration.Test/DeleteTests.cs b/tests/GeekLearning.Storage.Integration.Test/DeleteTests.cs index 2ddf857..2fba5d4 100644 --- a/tests/GeekLearning.Storage.Integration.Test/DeleteTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/DeleteTests.cs @@ -16,7 +16,7 @@ public DeleteTests(StoresFixture fixture) this.storeFixture = fixture; } - [Theory(DisplayName = nameof(Delete)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(Delete)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task Delete(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); diff --git a/tests/GeekLearning.Storage.Integration.Test/GenericIStoreTests.cs b/tests/GeekLearning.Storage.Integration.Test/GenericIStoreTests.cs index 94299db..5a813a3 100644 --- a/tests/GeekLearning.Storage.Integration.Test/GenericIStoreTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/GenericIStoreTests.cs @@ -7,7 +7,7 @@ using Xunit; [Collection(nameof(IntegrationCollection))] - [Trait("Kind", "Integration")] + [Trait("Operation", "GenericIStore"), Trait("Kind", "Integration")] public class GenericIStoreTests { private StoresFixture storeFixture; diff --git a/tests/GeekLearning.Storage.Integration.Test/ListTests.cs b/tests/GeekLearning.Storage.Integration.Test/ListTests.cs index 1c1c0d1..4275408 100644 --- a/tests/GeekLearning.Storage.Integration.Test/ListTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/ListTests.cs @@ -17,7 +17,7 @@ public ListTests(StoresFixture fixture) this.storeFixture = fixture; } - [Theory(DisplayName = nameof(ListRootFiles)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ListRootFiles)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ListRootFiles(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -36,7 +36,7 @@ public async Task ListRootFiles(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(ListEmptyPathFiles)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ListEmptyPathFiles)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ListEmptyPathFiles(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -55,7 +55,7 @@ public async Task ListEmptyPathFiles(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(ListSubDirectoryFiles)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ListSubDirectoryFiles)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ListSubDirectoryFiles(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -74,7 +74,7 @@ public async Task ListSubDirectoryFiles(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(ListSubDirectoryFilesWithTrailingSlash)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ListSubDirectoryFilesWithTrailingSlash)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ListSubDirectoryFilesWithTrailingSlash(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -93,7 +93,7 @@ public async Task ListSubDirectoryFilesWithTrailingSlash(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(ExtensionGlobbing)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ExtensionGlobbing)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ExtensionGlobbing(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -112,7 +112,7 @@ public async Task ExtensionGlobbing(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(FileNameGlobbing)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(FileNameGlobbing)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task FileNameGlobbing(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -131,7 +131,7 @@ public async Task FileNameGlobbing(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(FileNameGlobbingAtRoot)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(FileNameGlobbingAtRoot)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task FileNameGlobbingAtRoot(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); diff --git a/tests/GeekLearning.Storage.Integration.Test/ReadTests.cs b/tests/GeekLearning.Storage.Integration.Test/ReadTests.cs index 9b45aa0..56e0518 100644 --- a/tests/GeekLearning.Storage.Integration.Test/ReadTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/ReadTests.cs @@ -17,7 +17,7 @@ public ReadTests(StoresFixture fixture) this.storeFixture = fixture; } - [Theory(DisplayName = nameof(ReadAllTextFromRootFile)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ReadAllTextFromRootFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ReadAllTextFromRootFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -31,7 +31,7 @@ public async Task ReadAllTextFromRootFile(string storeName) Assert.Equal(expectedText, actualText); } - [Theory(DisplayName = nameof(ReadAllTextFromRootFile)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ReadAllTextFromRootFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ReadAllTextFromSubdirectoryFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -45,7 +45,7 @@ public async Task ReadAllTextFromSubdirectoryFile(string storeName) Assert.Equal(expectedText, actualText); } - [Theory(DisplayName = nameof(ReadAllBytesFromSubdirectoryFile)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ReadAllBytesFromSubdirectoryFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ReadAllBytesFromSubdirectoryFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -61,7 +61,7 @@ public async Task ReadAllBytesFromSubdirectoryFile(string storeName) } } - [Theory(DisplayName = nameof(ReadAllBytesFromSubdirectoryFileUsingFileReference)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ReadAllBytesFromSubdirectoryFileUsingFileReference)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ReadAllBytesFromSubdirectoryFileUsingFileReference(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -80,7 +80,7 @@ public async Task ReadAllBytesFromSubdirectoryFileUsingFileReference(string stor } - [Theory(DisplayName = nameof(ReadFileFromSubdirectoryFile)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ReadFileFromSubdirectoryFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ReadFileFromSubdirectoryFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -101,7 +101,7 @@ public async Task ReadFileFromSubdirectoryFile(string storeName) Assert.Equal(expectedText, actualText); } - [Theory(DisplayName = nameof(ReadAllTextFromSubdirectoryFileUsingFileReference)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ReadAllTextFromSubdirectoryFileUsingFileReference)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ReadAllTextFromSubdirectoryFileUsingFileReference(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -118,7 +118,7 @@ public async Task ReadAllTextFromSubdirectoryFileUsingFileReference(string store } - [Theory(DisplayName = nameof(ListThenReadAllTextFromSubdirectoryFile)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ListThenReadAllTextFromSubdirectoryFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ListThenReadAllTextFromSubdirectoryFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); diff --git a/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs b/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs index 3d000ee..783755f 100644 --- a/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs +++ b/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs @@ -1,7 +1,9 @@ namespace GeekLearning.Storage.Integration.Test { + using GeekLearning.Storage.Configuration; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; using Microsoft.Extensions.PlatformAbstractions; using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Blob; @@ -10,12 +12,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; + using GeekLearning.Storage.Azure.Configuration; + using GeekLearning.Storage.FileSystem.Configuration; public class StoresFixture : IDisposable { - private CloudStorageAccount cloudStorageAccount; - private CloudBlobContainer container; - public StoresFixture() { this.BasePath = PlatformServices.Default.Application.ApplicationBasePath; @@ -27,8 +28,8 @@ public StoresFixture() .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.development.json", optional: true) .AddInMemoryCollection(new KeyValuePair[] { - new KeyValuePair("Storage:Stores:azure:Parameters:Container", containerId), - new KeyValuePair("TestStore:Parameters:Container", containerId) + new KeyValuePair("Storage:Stores:Store3:FolderName", $"Store3-{containerId}"), + new KeyValuePair("Storage:Stores:Store4:FolderName", $"Store4-{containerId}") }); this.Configuration = builder.Build(); @@ -46,7 +47,10 @@ public StoresFixture() services.Configure(Configuration.GetSection("TestStore")); this.Services = services.BuildServiceProvider(); - + this.StorageOptions = this.Services.GetService>().Value; + this.AzureParsedOptions = this.Services.GetService>().Value; + this.FileSystemParsedOptions = this.Services.GetService>().Value; + this.TestStoreOptions = this.Services.GetService>().Value.ParseStoreOptions(this.FileSystemParsedOptions); ResetStores(); } @@ -58,6 +62,14 @@ public StoresFixture() public string FileSystemRootPath => Path.Combine(this.BasePath, "FileVault"); + public StorageOptions StorageOptions { get; } + + public AzureParsedOptions AzureParsedOptions { get; } + + public FileSystemParsedOptions FileSystemParsedOptions { get; } + + public FileSystemStoreOptions TestStoreOptions { get; } + public void Dispose() { this.DeleteRootResources(); @@ -65,9 +77,13 @@ public void Dispose() private void DeleteRootResources() { - if (this.container != null) + foreach (var parsedStoreKvp in this.AzureParsedOptions.ParsedStores) { - this.container.DeleteIfExistsAsync().Wait(); + var cloudStorageAccount = CloudStorageAccount.Parse(parsedStoreKvp.Value.ConnectionString); + var client = cloudStorageAccount.CreateCloudBlobClient(); + var container = client.GetContainerReference(parsedStoreKvp.Value.FolderName); + + container.DeleteIfExistsAsync().Wait(); } if (Directory.Exists(this.FileSystemRootPath)) @@ -79,53 +95,69 @@ private void DeleteRootResources() private void ResetStores() { this.DeleteRootResources(); - this.ResetAzureStore(); - this.ResetFileSystemStore(); + this.ResetAzureStores(); + this.ResetFileSystemStores(); } - private void ResetFileSystemStore() + private void ResetFileSystemStores() { if (!Directory.Exists(this.FileSystemRootPath)) { Directory.CreateDirectory(this.FileSystemRootPath); } - var directoryName = Configuration["Storage:Stores:filesystem:Parameters:Path"]; + foreach (var parsedStoreKvp in this.FileSystemParsedOptions.ParsedStores) + { + ResetFileSystemStore(parsedStoreKvp.Key, parsedStoreKvp.Value.AbsolutePath); + } + + ResetFileSystemStore(this.TestStoreOptions.Name, this.TestStoreOptions.AbsolutePath); + } + + private void ResetFileSystemStore(string storeName, string absolutePath) + { var process = Process.Start(new ProcessStartInfo("robocopy.exe") { - Arguments = $"\"{Path.Combine(this.BasePath, "SampleDirectory")}\" \"{Path.Combine(this.FileSystemRootPath, directoryName)}\" /MIR" + Arguments = $"\"{Path.Combine(this.BasePath, "SampleDirectory")}\" \"{absolutePath}\" /MIR" }); if (!process.WaitForExit(30000)) { - throw new TimeoutException("File system store was not reset properly"); + process.Kill(); + throw new TimeoutException($"FileSystem Store '{storeName}' was not reset properly."); } } - private void ResetAzureStore() + private void ResetAzureStores() { var azCopy = Path.Combine( Environment.ExpandEnvironmentVariables(Configuration["AzCopyPath"]), "AzCopy.exe"); - cloudStorageAccount = CloudStorageAccount.Parse(Configuration["Storage:Stores:azure:Parameters:ConnectionString"]); - var key = cloudStorageAccount.Credentials.ExportBase64EncodedKey(); - var containerName = Configuration["Storage:Stores:azure:Parameters:Container"]; - var dest = cloudStorageAccount.BlobStorageUri.PrimaryUri.ToString() + containerName; + foreach (var parsedStoreKvp in this.AzureParsedOptions.ParsedStores) + { + var cloudStorageAccount = CloudStorageAccount.Parse(parsedStoreKvp.Value.ConnectionString); + var cloudStoragekey = cloudStorageAccount.Credentials.ExportBase64EncodedKey(); + var containerName = parsedStoreKvp.Value.FolderName; - var client = cloudStorageAccount.CreateCloudBlobClient(); + var dest = cloudStorageAccount.BlobStorageUri.PrimaryUri.ToString() + containerName; - this.container = client.GetContainerReference(containerName); - this.container.CreateAsync().Wait(); + var client = cloudStorageAccount.CreateCloudBlobClient(); - var process = Process.Start(new ProcessStartInfo(azCopy) - { - Arguments = $"/Source:\"{Path.Combine(this.BasePath, "SampleDirectory")}\" /Dest:\"{dest}\" /DestKey:{key} /S" - }); + var container = client.GetContainerReference(containerName); + container.CreateIfNotExistsAsync().Wait(); - if (!process.WaitForExit(30000)) - { - throw new TimeoutException("Azure store was not reset properly"); + var arguments = $"/Source:\"{Path.Combine(this.BasePath, "SampleDirectory")}\" /Dest:\"{dest}\" /DestKey:{cloudStoragekey} /S /y"; + var process = Process.Start(new ProcessStartInfo(azCopy) + { + Arguments = arguments + }); + + if (!process.WaitForExit(30000)) + { + process.Kill(); + throw new TimeoutException($"Azure Store '{parsedStoreKvp.Key}' was not reset properly."); + } } } } diff --git a/tests/GeekLearning.Storage.Integration.Test/TestStore.cs b/tests/GeekLearning.Storage.Integration.Test/TestStore.cs index f4d4e7b..47f0cc9 100644 --- a/tests/GeekLearning.Storage.Integration.Test/TestStore.cs +++ b/tests/GeekLearning.Storage.Integration.Test/TestStore.cs @@ -1,11 +1,23 @@ namespace GeekLearning.Storage.Integration.Test { - using Microsoft.Extensions.Configuration; + using GeekLearning.Storage.Configuration; - public class TestStore : IStorageStoreOptions + public class TestStore : IStoreOptions { - public string Provider { get; set; } + public TestStore() + { + this.Name = "TestStore"; + this.ProviderType = "FileSystem"; + } - public IConfigurationSection Parameters { get; set; } + public string ProviderName { get; set; } + + public string ProviderType { get; set; } + + public AccessLevel AccessLevel { get; set; } + + public string FolderName { get; set; } + + public string Name { get; set; } } } diff --git a/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs b/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs index 4d5124e..2b2e7a5 100644 --- a/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs @@ -20,7 +20,7 @@ public UpdateTests(StoresFixture fixture) this.storeFixture = fixture; } - [Theory(DisplayName = nameof(WriteAllText)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(WriteAllText)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task WriteAllText(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -36,7 +36,7 @@ public async Task WriteAllText(string storeName) Assert.Equal(textToWrite, readFromWrittenFile); } - [Theory(DisplayName = nameof(ETagShouldBeTheSameWithSameContent)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ETagShouldBeTheSameWithSameContent)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ETagShouldBeTheSameWithSameContent(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -51,7 +51,7 @@ public async Task ETagShouldBeTheSameWithSameContent(string storeName) Assert.Equal(savedReference.Properties.ETag, readReference.Properties.ETag); } - [Theory(DisplayName = nameof(ETagShouldBeDifferentWithDifferentContent)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ETagShouldBeDifferentWithDifferentContent)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ETagShouldBeDifferentWithDifferentContent(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -67,7 +67,7 @@ public async Task ETagShouldBeDifferentWithDifferentContent(string storeName) Assert.NotEqual(savedReference.Properties.ETag, updatedReference.Properties.ETag); } - [Theory(DisplayName = nameof(SaveStream)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(SaveStream)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task SaveStream(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -83,7 +83,7 @@ public async Task SaveStream(string storeName) Assert.Equal(textToWrite, readFromWrittenFile); } - [Theory(DisplayName = nameof(AddMetatadaRoundtrip)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(AddMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task AddMetatadaRoundtrip(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -107,7 +107,7 @@ public async Task AddMetatadaRoundtrip(string storeName) Assert.Equal(id, actualId); } - [Theory(DisplayName = nameof(SaveMetatadaRoundtrip)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(SaveMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task SaveMetatadaRoundtrip(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -131,7 +131,7 @@ public async Task SaveMetatadaRoundtrip(string storeName) Assert.Equal(id, actualId); } - [Theory(DisplayName = nameof(ListMetatadaRoundtrip)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ListMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ListMetatadaRoundtrip(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); diff --git a/tests/GeekLearning.Storage.Integration.Test/appsettings.json b/tests/GeekLearning.Storage.Integration.Test/appsettings.json index 2c8e21c..b3e65e3 100644 --- a/tests/GeekLearning.Storage.Integration.Test/appsettings.json +++ b/tests/GeekLearning.Storage.Integration.Test/appsettings.json @@ -1,26 +1,59 @@ { + "ConnectionStrings": { + "ConnectionStringFromAppSettings": "DefaultEndpointsProtocol=https;AccountName=;AccountKey=;EndpointSuffix=core.windows.net" + }, + "AzCopyPath": "%ProgramFiles(x86)%\\Microsoft SDKs\\Azure\\AzCopy", + "Storage": { + + "Providers": { + "FirstAzure": { + "Type": "Azure", + "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=;AccountKey=;EndpointSuffix=core.windows.net" + }, + "AnotherAzure": { + "Type": "Azure", + "ConnectionStringName": "ConnectionStringFromAppSettings" + }, + "FirstFileSystem": { + "Type": "FileSystem" + }, + "AnotherFileSystem": { + "Type": "FileSystem", + "RootPath": ".." + } + }, + "Stores": { - "filesystem": { - "Provider": "FileSystem", - "Parameters": { - "Path": "Templates" - } + "Store1": { + "ProviderName": "FirstFileSystem" + }, + "Store2": { + "ProviderName": "FirstFileSystem", + "AccessLevel": "Public", + "FolderName": "AnotherPath" }, - "azure": { - "Provider": "Azure", - "Parameters": { - "ConnectionString": "YourConnectionString", - "Container": "templates" - } + "Store3": { + "ProviderName": "FirstAzure", + "AccessLevel": "Private" + }, + "Store4": { + "ProviderType": "Azure", + "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=;AccountKey=;EndpointSuffix=core.windows.net" + } + }, + + "ScopedStores": { + "ScopedStore1": { + "ProviderName": "AnotherFileSystem", + "FolderNameFormat": "AnotherPath-{0}" + }, + "ScopedStore2": { + "ProviderName": "AnotherAzure", + "AccessLevel": "Confidential", + "FolderNameFormat": "AnotherPath-{0}" } - } - }, - "TestStore": { - "Provider": "FileSystem", - "Parameters": { - "Path": "Templates" } } } From 989f7bed0c7a5b3f77b4b822370ecc58911359c7 Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Thu, 13 Apr 2017 16:23:12 +0200 Subject: [PATCH 3/9] ConnectionStringName parameter mapping --- .../Configuration/AzureParsedOptions.cs | 36 ++++++++++++++++--- .../Configuration/FileSystemParsedOptions.cs | 15 ++++++-- .../Configuration/ConfigurationExtensions.cs | 11 +++++- .../Configuration/IParsedOptions.cs | 4 +++ .../Configuration/StorageOptions.cs | 16 ++++++--- .../Internal/ConfigureProviderOptions.cs | 7 ++++ .../StorageServiceCollectionExtensions.cs | 28 +++++++++++++-- .../DeleteTests.cs | 2 +- .../ListTests.cs | 14 ++++---- .../ReadTests.cs | 14 ++++---- .../StoresFixture.cs | 2 +- .../UpdateTests.cs | 14 ++++---- .../appsettings.json | 7 ++++ 13 files changed, 131 insertions(+), 39 deletions(-) diff --git a/src/GeekLearning.Storage.Azure/Configuration/AzureParsedOptions.cs b/src/GeekLearning.Storage.Azure/Configuration/AzureParsedOptions.cs index 251f5bf..cd97a62 100644 --- a/src/GeekLearning.Storage.Azure/Configuration/AzureParsedOptions.cs +++ b/src/GeekLearning.Storage.Azure/Configuration/AzureParsedOptions.cs @@ -7,16 +7,47 @@ public class AzureParsedOptions : IParsedOptions AzureStorageProvider.ProviderName; + public IReadOnlyDictionary ConnectionStrings { get; set; } + public IReadOnlyDictionary ParsedProviderInstances { get; set; } public IReadOnlyDictionary ParsedStores { get; set; } public IReadOnlyDictionary ParsedScopedStores { get; set; } + public void BindProviderInstanceOptions(AzureProviderInstanceOptions providerInstanceOptions) + { + if (!string.IsNullOrEmpty(providerInstanceOptions.ConnectionStringName) + && string.IsNullOrEmpty(providerInstanceOptions.ConnectionString)) + { + if (!this.ConnectionStrings.ContainsKey(providerInstanceOptions.ConnectionStringName)) + { + throw new Exceptions.BadProviderConfiguration( + providerInstanceOptions.Name, + $"The ConnectionString '{providerInstanceOptions.ConnectionStringName}' cannot be found. Did you call AddStorage with the ConfigurationRoot?"); + } + + providerInstanceOptions.ConnectionString = this.ConnectionStrings[providerInstanceOptions.ConnectionStringName]; + } + } + public void BindStoreOptions(AzureStoreOptions storeOptions, AzureProviderInstanceOptions providerInstanceOptions = null) { storeOptions.FolderName = storeOptions.FolderName.ToLowerInvariant(); + if (!string.IsNullOrEmpty(storeOptions.ConnectionStringName) + && string.IsNullOrEmpty(storeOptions.ConnectionString)) + { + if (!this.ConnectionStrings.ContainsKey(storeOptions.ConnectionStringName)) + { + throw new Exceptions.BadStoreConfiguration( + storeOptions.Name, + $"The ConnectionString '{storeOptions.ConnectionStringName}' cannot be found. Did you call AddStorage with the ConfigurationRoot?"); + } + + storeOptions.ConnectionString = this.ConnectionStrings[storeOptions.ConnectionStringName]; + } + if (providerInstanceOptions == null || storeOptions.ProviderName != providerInstanceOptions.Name) { @@ -27,11 +58,6 @@ public void BindStoreOptions(AzureStoreOptions storeOptions, AzureProviderInstan { storeOptions.ConnectionString = providerInstanceOptions.ConnectionString; } - - if (string.IsNullOrEmpty(storeOptions.ConnectionStringName)) - { - storeOptions.ConnectionStringName = providerInstanceOptions.ConnectionStringName; - } } } } diff --git a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs index c98956e..91d8253 100644 --- a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs +++ b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs @@ -7,7 +7,7 @@ public class FileSystemParsedOptions : IParsedOptions FileSystemStorageProvider.ProviderName; - public string RootPath { get; set; } + public IReadOnlyDictionary ConnectionStrings { get; set; } public IReadOnlyDictionary ParsedProviderInstances { get; set; } @@ -15,13 +15,22 @@ public class FileSystemParsedOptions : IParsedOptions ParsedScopedStores { get; set; } + public string RootPath { get; set; } + + public void BindProviderInstanceOptions(FileSystemProviderInstanceOptions providerInstanceOptions) + { + if (string.IsNullOrEmpty(providerInstanceOptions.RootPath)) + { + providerInstanceOptions.RootPath = this.RootPath; + } + } + public void BindStoreOptions(FileSystemStoreOptions storeOptions, FileSystemProviderInstanceOptions providerInstanceOptions = null) { if (string.IsNullOrEmpty(storeOptions.RootPath)) { if (providerInstanceOptions != null - && storeOptions.ProviderName == providerInstanceOptions.Name - && !string.IsNullOrEmpty(providerInstanceOptions.RootPath)) + && storeOptions.ProviderName == providerInstanceOptions.Name) { storeOptions.RootPath = providerInstanceOptions.RootPath; } diff --git a/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs b/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs index a740d42..1408083 100644 --- a/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs +++ b/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs @@ -6,7 +6,7 @@ public static class ConfigurationExtensions { - public static IReadOnlyDictionary Parse(this Dictionary unparsedConfiguration) + public static IReadOnlyDictionary Parse(this IReadOnlyDictionary unparsedConfiguration) where TOptions : class, INamedElementOptions, new() { if (unparsedConfiguration == null) @@ -45,6 +45,15 @@ public static IStoreOptions GetStoreConfiguration(this TInstanceOptions parsedProviderInstance, TParsedOptions options) + where TParsedOptions : class, IParsedOptions + where TInstanceOptions : class, IProviderInstanceOptions, new() + where TStoreOptions : class, IStoreOptions, new() + where TScopedStoreOptions : class, IScopedStoreOptions, new() + { + options.BindProviderInstanceOptions(parsedProviderInstance); + } + public static void Compute(this TStoreOptions parsedStore, TParsedOptions options) where TParsedOptions : class, IParsedOptions where TInstanceOptions : class, IProviderInstanceOptions, new() diff --git a/src/GeekLearning.Storage/Configuration/IParsedOptions.cs b/src/GeekLearning.Storage/Configuration/IParsedOptions.cs index 833187c..cc7d18c 100644 --- a/src/GeekLearning.Storage/Configuration/IParsedOptions.cs +++ b/src/GeekLearning.Storage/Configuration/IParsedOptions.cs @@ -9,12 +9,16 @@ public interface IParsedOptions ConnectionStrings { get; set; } + IReadOnlyDictionary ParsedProviderInstances { get; set; } IReadOnlyDictionary ParsedStores { get; set; } IReadOnlyDictionary ParsedScopedStores { get; set; } + void BindProviderInstanceOptions(TInstanceOptions providerInstanceOptions); + void BindStoreOptions(TStoreOptions storeOptions, TInstanceOptions providerInstanceOptions = null); } } diff --git a/src/GeekLearning.Storage/Configuration/StorageOptions.cs b/src/GeekLearning.Storage/Configuration/StorageOptions.cs index 821db5d..9d02e23 100644 --- a/src/GeekLearning.Storage/Configuration/StorageOptions.cs +++ b/src/GeekLearning.Storage/Configuration/StorageOptions.cs @@ -6,7 +6,7 @@ public class StorageOptions : IParsedOptions { - internal const string GlobalOptionsName = "Global"; + public const string DefaultConfigurationSectionName = "Storage"; private readonly Lazy> parsedProviderInstances; private readonly Lazy> parsedStores; @@ -22,13 +22,15 @@ public StorageOptions() () => this.ScopedStores.Parse()); } - public string Name => GlobalOptionsName; + public string Name => DefaultConfigurationSectionName; - public Dictionary Providers { get; set; } + public IReadOnlyDictionary Providers { get; set; } - public Dictionary Stores { get; set; } + public IReadOnlyDictionary Stores { get; set; } - public Dictionary ScopedStores { get; set; } + public IReadOnlyDictionary ScopedStores { get; set; } + + public IReadOnlyDictionary ConnectionStrings { get; set; } public IReadOnlyDictionary ParsedProviderInstances { get => this.parsedProviderInstances.Value; set { } } @@ -36,6 +38,10 @@ public StorageOptions() public IReadOnlyDictionary ParsedScopedStores { get => this.parsedScopedStores.Value; set { } } + public void BindProviderInstanceOptions(ProviderInstanceOptions providerInstanceOptions) + { + } + public void BindStoreOptions(StoreOptions storeOptions, ProviderInstanceOptions providerInstanceOptions) { } diff --git a/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs b/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs index 2931fa7..1cc2e20 100644 --- a/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs +++ b/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs @@ -24,10 +24,17 @@ public void Configure(TParsedOptions options) return; } + options.ConnectionStrings = this.storageOptions.ConnectionStrings; + options.ParsedProviderInstances = this.storageOptions.Providers.Parse() .Where(kvp => kvp.Value.Type == options.Name) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + foreach (var parsedProviderInstance in options.ParsedProviderInstances) + { + parsedProviderInstance.Value.Compute(options); + } + var parsedStores = this.storageOptions.Stores.Parse(); var parsedScopedStores = this.storageOptions.ScopedStores.Parse(); diff --git a/src/GeekLearning.Storage/StorageServiceCollectionExtensions.cs b/src/GeekLearning.Storage/StorageServiceCollectionExtensions.cs index e08e39c..073d149 100644 --- a/src/GeekLearning.Storage/StorageServiceCollectionExtensions.cs +++ b/src/GeekLearning.Storage/StorageServiceCollectionExtensions.cs @@ -4,6 +4,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; + using System.Collections.Generic; + using System.Linq; public static class StorageServiceCollectionExtensions { @@ -14,10 +16,32 @@ public static IServiceCollection AddStorage(this IServiceCollection services) return services; } - public static IServiceCollection AddStorage(this IServiceCollection services, IConfiguration configuration) + public static IServiceCollection AddStorage(this IServiceCollection services, IConfigurationSection configurationSection) { return services - .Configure(configuration) + .Configure(configurationSection) + .AddStorage(); + } + + public static IServiceCollection AddStorage(this IServiceCollection services, IConfigurationRoot configurationRoot) + { + return services + .Configure(configurationRoot.GetSection(StorageOptions.DefaultConfigurationSectionName)) + .Configure(storageOptions => + { + var connectionStrings = new Dictionary(); + ConfigurationBinder.Bind(configurationRoot.GetSection("ConnectionStrings"), connectionStrings); + + if (storageOptions.ConnectionStrings != null) + { + foreach (var existingConnectionString in storageOptions.ConnectionStrings) + { + connectionStrings[existingConnectionString.Key] = existingConnectionString.Value; + } + } + + storageOptions.ConnectionStrings = connectionStrings; + }) .AddStorage(); } } diff --git a/tests/GeekLearning.Storage.Integration.Test/DeleteTests.cs b/tests/GeekLearning.Storage.Integration.Test/DeleteTests.cs index 2fba5d4..da582bb 100644 --- a/tests/GeekLearning.Storage.Integration.Test/DeleteTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/DeleteTests.cs @@ -16,7 +16,7 @@ public DeleteTests(StoresFixture fixture) this.storeFixture = fixture; } - [Theory(DisplayName = nameof(Delete)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(Delete)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task Delete(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); diff --git a/tests/GeekLearning.Storage.Integration.Test/ListTests.cs b/tests/GeekLearning.Storage.Integration.Test/ListTests.cs index 4275408..99b6615 100644 --- a/tests/GeekLearning.Storage.Integration.Test/ListTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/ListTests.cs @@ -17,7 +17,7 @@ public ListTests(StoresFixture fixture) this.storeFixture = fixture; } - [Theory(DisplayName = nameof(ListRootFiles)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ListRootFiles)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ListRootFiles(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -36,7 +36,7 @@ public async Task ListRootFiles(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(ListEmptyPathFiles)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ListEmptyPathFiles)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ListEmptyPathFiles(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -55,7 +55,7 @@ public async Task ListEmptyPathFiles(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(ListSubDirectoryFiles)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ListSubDirectoryFiles)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ListSubDirectoryFiles(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -74,7 +74,7 @@ public async Task ListSubDirectoryFiles(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(ListSubDirectoryFilesWithTrailingSlash)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ListSubDirectoryFilesWithTrailingSlash)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ListSubDirectoryFilesWithTrailingSlash(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -93,7 +93,7 @@ public async Task ListSubDirectoryFilesWithTrailingSlash(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(ExtensionGlobbing)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ExtensionGlobbing)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ExtensionGlobbing(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -112,7 +112,7 @@ public async Task ExtensionGlobbing(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(FileNameGlobbing)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(FileNameGlobbing)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task FileNameGlobbing(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -131,7 +131,7 @@ public async Task FileNameGlobbing(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(FileNameGlobbingAtRoot)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(FileNameGlobbingAtRoot)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task FileNameGlobbingAtRoot(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); diff --git a/tests/GeekLearning.Storage.Integration.Test/ReadTests.cs b/tests/GeekLearning.Storage.Integration.Test/ReadTests.cs index 56e0518..ed607fd 100644 --- a/tests/GeekLearning.Storage.Integration.Test/ReadTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/ReadTests.cs @@ -17,7 +17,7 @@ public ReadTests(StoresFixture fixture) this.storeFixture = fixture; } - [Theory(DisplayName = nameof(ReadAllTextFromRootFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ReadAllTextFromRootFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ReadAllTextFromRootFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -31,7 +31,7 @@ public async Task ReadAllTextFromRootFile(string storeName) Assert.Equal(expectedText, actualText); } - [Theory(DisplayName = nameof(ReadAllTextFromRootFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ReadAllTextFromRootFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ReadAllTextFromSubdirectoryFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -45,7 +45,7 @@ public async Task ReadAllTextFromSubdirectoryFile(string storeName) Assert.Equal(expectedText, actualText); } - [Theory(DisplayName = nameof(ReadAllBytesFromSubdirectoryFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ReadAllBytesFromSubdirectoryFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ReadAllBytesFromSubdirectoryFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -61,7 +61,7 @@ public async Task ReadAllBytesFromSubdirectoryFile(string storeName) } } - [Theory(DisplayName = nameof(ReadAllBytesFromSubdirectoryFileUsingFileReference)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ReadAllBytesFromSubdirectoryFileUsingFileReference)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ReadAllBytesFromSubdirectoryFileUsingFileReference(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -80,7 +80,7 @@ public async Task ReadAllBytesFromSubdirectoryFileUsingFileReference(string stor } - [Theory(DisplayName = nameof(ReadFileFromSubdirectoryFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ReadFileFromSubdirectoryFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ReadFileFromSubdirectoryFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -101,7 +101,7 @@ public async Task ReadFileFromSubdirectoryFile(string storeName) Assert.Equal(expectedText, actualText); } - [Theory(DisplayName = nameof(ReadAllTextFromSubdirectoryFileUsingFileReference)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ReadAllTextFromSubdirectoryFileUsingFileReference)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ReadAllTextFromSubdirectoryFileUsingFileReference(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -118,7 +118,7 @@ public async Task ReadAllTextFromSubdirectoryFileUsingFileReference(string store } - [Theory(DisplayName = nameof(ListThenReadAllTextFromSubdirectoryFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ListThenReadAllTextFromSubdirectoryFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ListThenReadAllTextFromSubdirectoryFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); diff --git a/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs b/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs index 783755f..689efb2 100644 --- a/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs +++ b/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs @@ -38,7 +38,7 @@ public StoresFixture() services.AddOptions(); - services.AddStorage() + services.AddStorage(Configuration) .AddAzureStorage() .AddFileSystemStorage(this.FileSystemRootPath) .AddFileSystemExtendedProperties(); diff --git a/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs b/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs index 2b2e7a5..50958cb 100644 --- a/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs @@ -20,7 +20,7 @@ public UpdateTests(StoresFixture fixture) this.storeFixture = fixture; } - [Theory(DisplayName = nameof(WriteAllText)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(WriteAllText)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task WriteAllText(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -36,7 +36,7 @@ public async Task WriteAllText(string storeName) Assert.Equal(textToWrite, readFromWrittenFile); } - [Theory(DisplayName = nameof(ETagShouldBeTheSameWithSameContent)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ETagShouldBeTheSameWithSameContent)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ETagShouldBeTheSameWithSameContent(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -51,7 +51,7 @@ public async Task ETagShouldBeTheSameWithSameContent(string storeName) Assert.Equal(savedReference.Properties.ETag, readReference.Properties.ETag); } - [Theory(DisplayName = nameof(ETagShouldBeDifferentWithDifferentContent)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ETagShouldBeDifferentWithDifferentContent)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ETagShouldBeDifferentWithDifferentContent(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -67,7 +67,7 @@ public async Task ETagShouldBeDifferentWithDifferentContent(string storeName) Assert.NotEqual(savedReference.Properties.ETag, updatedReference.Properties.ETag); } - [Theory(DisplayName = nameof(SaveStream)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(SaveStream)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task SaveStream(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -83,7 +83,7 @@ public async Task SaveStream(string storeName) Assert.Equal(textToWrite, readFromWrittenFile); } - [Theory(DisplayName = nameof(AddMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(AddMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task AddMetatadaRoundtrip(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -107,7 +107,7 @@ public async Task AddMetatadaRoundtrip(string storeName) Assert.Equal(id, actualId); } - [Theory(DisplayName = nameof(SaveMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(SaveMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task SaveMetatadaRoundtrip(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -131,7 +131,7 @@ public async Task SaveMetatadaRoundtrip(string storeName) Assert.Equal(id, actualId); } - [Theory(DisplayName = nameof(ListMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ListMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ListMetatadaRoundtrip(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); diff --git a/tests/GeekLearning.Storage.Integration.Test/appsettings.json b/tests/GeekLearning.Storage.Integration.Test/appsettings.json index b3e65e3..c887a81 100644 --- a/tests/GeekLearning.Storage.Integration.Test/appsettings.json +++ b/tests/GeekLearning.Storage.Integration.Test/appsettings.json @@ -41,6 +41,13 @@ "Store4": { "ProviderType": "Azure", "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=;AccountKey=;EndpointSuffix=core.windows.net" + }, + "Store5": { + "ProviderName": "AnotherAzure" + }, + "Store6": { + "ProviderType": "Azure", + "ConnectionStringName": "ConnectionStringFromAppSettings" } }, From 1ed9fc6f67648b5ebb1e67b12680ac9ef13f166a Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Thu, 13 Apr 2017 16:57:18 +0200 Subject: [PATCH 4/9] Fix tests (randomize Azure container names) --- tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs b/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs index 689efb2..bfcf5a7 100644 --- a/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs +++ b/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs @@ -29,7 +29,9 @@ public StoresFixture() .AddJsonFile($"appsettings.development.json", optional: true) .AddInMemoryCollection(new KeyValuePair[] { new KeyValuePair("Storage:Stores:Store3:FolderName", $"Store3-{containerId}"), - new KeyValuePair("Storage:Stores:Store4:FolderName", $"Store4-{containerId}") + new KeyValuePair("Storage:Stores:Store4:FolderName", $"Store4-{containerId}"), + new KeyValuePair("Storage:Stores:Store5:FolderName", $"Store5-{containerId}"), + new KeyValuePair("Storage:Stores:Store6:FolderName", $"Store6-{containerId}"), }); this.Configuration = builder.Build(); From 308a3ebf269be24a3c811a41603d7ea3ad3f9700 Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Thu, 13 Apr 2017 18:32:19 +0200 Subject: [PATCH 5/9] ScopedStores and Init stores --- .../AzureStorageExtensions.cs | 9 ---- .../AzureStorageProvider.cs | 2 +- src/GeekLearning.Storage.Azure/AzureStore.cs | 5 +++ .../Configuration/AzureScopedStoreOptions.cs | 3 +- .../Configuration/FileSystemParsedOptions.cs | 8 ++++ .../FileSystemScopedStoreOptions.cs | 3 +- .../FileSystemStorageExtensions.cs | 15 ++----- .../FileSystemStorageProvider.cs | 2 +- .../FileSystemStore.cs | 10 +++++ .../Configuration/ConfigurationExtensions.cs | 23 ++++++++--- .../Configuration/IParsedOptions.cs | 2 +- .../Exceptions/BadScopedStoreConfiguration.cs | 22 ++++++++++ src/GeekLearning.Storage/IStorageFactory.cs | 2 + src/GeekLearning.Storage/IStorageProvider.cs | 2 + src/GeekLearning.Storage/IStore.cs | 2 + .../Internal/ConfigureProviderOptions.cs | 10 +++-- .../Internal/GenericStoreProxy.cs | 24 ++++++----- .../Internal/StorageFactory.cs | 5 +++ .../Internal/StorageProviderBase.cs | 25 +++++++++-- .../ScopedStoresTests.cs | 41 +++++++++++++++++++ .../StoresFixture.cs | 7 ++++ .../appsettings.json | 2 +- 22 files changed, 175 insertions(+), 49 deletions(-) create mode 100644 src/GeekLearning.Storage/Exceptions/BadScopedStoreConfiguration.cs create mode 100644 tests/GeekLearning.Storage.Integration.Test/ScopedStoresTests.cs diff --git a/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs b/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs index cc70c51..3381d6c 100644 --- a/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs +++ b/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs @@ -2,9 +2,7 @@ { using Azure; using GeekLearning.Storage.Azure.Configuration; - using GeekLearning.Storage.Configuration; using GeekLearning.Storage.Internal; - using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; @@ -18,13 +16,6 @@ public static IServiceCollection AddAzureStorage(this IServiceCollection service .AddAzureStorageServices(); } - public static IServiceCollection AddAzureStorage(this IServiceCollection services, IConfiguration configuration) - { - return services - .Configure(configuration) - .AddAzureStorageServices(); - } - private static IServiceCollection AddAzureStorageServices(this IServiceCollection services) { services.TryAddEnumerable(ServiceDescriptor.Transient()); diff --git a/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs b/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs index 87aca75..c04dc42 100644 --- a/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs +++ b/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs @@ -16,7 +16,7 @@ public AzureStorageProvider(IOptions options) public override string Name => ProviderName; - protected override IStore BuildStore(string storeName, AzureStoreOptions storeOptions) + protected override IStore BuildStoreInternal(string storeName, AzureStoreOptions storeOptions) { return new AzureStore(storeOptions); } diff --git a/src/GeekLearning.Storage.Azure/AzureStore.cs b/src/GeekLearning.Storage.Azure/AzureStore.cs index f09da9d..7cf8cd5 100644 --- a/src/GeekLearning.Storage.Azure/AzureStore.cs +++ b/src/GeekLearning.Storage.Azure/AzureStore.cs @@ -37,6 +37,11 @@ public AzureStore(AzureStoreOptions storeOptions) public string Name => this.storeOptions.Name; + public Task InitAsync() + { + return this.container.Value.CreateIfNotExistsAsync(); + } + public async Task ListAsync(string path, bool recursive, bool withMetadata) { if (string.IsNullOrWhiteSpace(path)) diff --git a/src/GeekLearning.Storage.Azure/Configuration/AzureScopedStoreOptions.cs b/src/GeekLearning.Storage.Azure/Configuration/AzureScopedStoreOptions.cs index 4c74736..c43eded 100644 --- a/src/GeekLearning.Storage.Azure/Configuration/AzureScopedStoreOptions.cs +++ b/src/GeekLearning.Storage.Azure/Configuration/AzureScopedStoreOptions.cs @@ -2,7 +2,8 @@ { using GeekLearning.Storage.Configuration; - public class AzureScopedStoreOptions : ScopedStoreOptions + public class AzureScopedStoreOptions : AzureStoreOptions, IScopedStoreOptions { + public string FolderNameFormat { get; set; } } } diff --git a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs index 91d8253..7294bff 100644 --- a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs +++ b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs @@ -2,6 +2,7 @@ { using GeekLearning.Storage.Configuration; using System.Collections.Generic; + using System.IO; public class FileSystemParsedOptions : IParsedOptions { @@ -23,6 +24,13 @@ public void BindProviderInstanceOptions(FileSystemProviderInstanceOptions provid { providerInstanceOptions.RootPath = this.RootPath; } + else + { + if (!Path.IsPathRooted(providerInstanceOptions.RootPath)) + { + providerInstanceOptions.RootPath = Path.Combine(this.RootPath, providerInstanceOptions.RootPath); + } + } } public void BindStoreOptions(FileSystemStoreOptions storeOptions, FileSystemProviderInstanceOptions providerInstanceOptions = null) diff --git a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemScopedStoreOptions.cs b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemScopedStoreOptions.cs index 035cc73..c417df2 100644 --- a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemScopedStoreOptions.cs +++ b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemScopedStoreOptions.cs @@ -2,7 +2,8 @@ { using GeekLearning.Storage.Configuration; - public class FileSystemScopedStoreOptions : ScopedStoreOptions + public class FileSystemScopedStoreOptions : FileSystemStoreOptions, IScopedStoreOptions { + public string FolderNameFormat { get; set; } } } diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs index 0b85c9b..73825ca 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs @@ -3,7 +3,6 @@ using FileSystem; using GeekLearning.Storage.FileSystem.Configuration; using GeekLearning.Storage.Internal; - using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; @@ -14,25 +13,19 @@ public static IServiceCollection AddFileSystemStorage(this IServiceCollection se { return services .Configure(options => options.RootPath = rootPath) - .AddFileSystemStorage(); - } - - public static IServiceCollection AddFileSystemStorage(this IServiceCollection services) - { - return services - .AddSingleton, ConfigureProviderOptions>() .AddFileSystemStorageServices(); } - public static IServiceCollection AddFileSystemStorage(this IServiceCollection services, IConfiguration configuration) + public static IServiceCollection AddFileSystemStorage(this IServiceCollection services) { - return services - .Configure(configuration) + return services + .Configure(options => options.RootPath = System.IO.Directory.GetCurrentDirectory()) .AddFileSystemStorageServices(); } private static IServiceCollection AddFileSystemStorageServices(this IServiceCollection services) { + services.AddSingleton, ConfigureProviderOptions>(); services.TryAddEnumerable(ServiceDescriptor.Transient()); return services; } diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs index 3f5b26f..ae9916d 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs @@ -20,7 +20,7 @@ public FileSystemStorageProvider(IOptions options, IPub public override string Name => ProviderName; - protected override IStore BuildStore(string storeName, FileSystemStoreOptions storeOptions) + protected override IStore BuildStoreInternal(string storeName, FileSystemStoreOptions storeOptions) { return new FileSystemStore( storeOptions, diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs index 5983c96..d08b8ed 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs @@ -40,6 +40,16 @@ public FileSystemStore(FileSystemStoreOptions storeOptions, IPublicUrlProvider p internal string AbsolutePath => storeOptions.AbsolutePath; + public Task InitAsync() + { + if (!Directory.Exists(this.AbsolutePath)) + { + Directory.CreateDirectory(this.AbsolutePath); + } + + return Task.FromResult(0); + } + public async Task ListAsync(string path, bool recursive, bool withMetadata) { var directoryPath = (string.IsNullOrEmpty(path) || path == "/" || path == "\\") ? this.AbsolutePath : Path.Combine(this.AbsolutePath, path); diff --git a/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs b/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs index 1408083..79f487f 100644 --- a/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs +++ b/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs @@ -20,10 +20,10 @@ public static IReadOnlyDictionary Parse(this IReadOn kvp => BindOptions(kvp)); } - public static IStoreOptions GetStoreConfiguration(this IParsedOptions parsedOptions, string storeName, bool throwIfNotFound = true) + public static TStoreOptions GetStoreConfiguration(this IParsedOptions parsedOptions, string storeName, bool throwIfNotFound = true) where TInstanceOptions : class, IProviderInstanceOptions where TStoreOptions : class, IStoreOptions - where TScopedStoreOptions : class, IScopedStoreOptions + where TScopedStoreOptions : class, TStoreOptions, IScopedStoreOptions { parsedOptions.ParsedStores.TryGetValue(storeName, out var storeOptions); if (storeOptions != null) @@ -31,6 +31,19 @@ public static IStoreOptions GetStoreConfiguration(this IParsedOptions parsedOptions, string storeName, bool throwIfNotFound = true) + where TInstanceOptions : class, IProviderInstanceOptions + where TStoreOptions : class, IStoreOptions + where TScopedStoreOptions : class, TStoreOptions, IScopedStoreOptions + { parsedOptions.ParsedScopedStores.TryGetValue(storeName, out var scopedStoreOptions); if (scopedStoreOptions != null) { @@ -49,7 +62,7 @@ public static void Compute where TInstanceOptions : class, IProviderInstanceOptions, new() where TStoreOptions : class, IStoreOptions, new() - where TScopedStoreOptions : class, IScopedStoreOptions, new() + where TScopedStoreOptions : class, TStoreOptions, IScopedStoreOptions { options.BindProviderInstanceOptions(parsedProviderInstance); } @@ -58,7 +71,7 @@ public static void Compute where TInstanceOptions : class, IProviderInstanceOptions, new() where TStoreOptions : class, IStoreOptions, new() - where TScopedStoreOptions : class, IScopedStoreOptions, new() + where TScopedStoreOptions : class, TStoreOptions, IScopedStoreOptions { if (string.IsNullOrEmpty(parsedStore.FolderName)) { @@ -84,7 +97,7 @@ public static TStoreOptions ParseStoreOptions, new() where TInstanceOptions : class, IProviderInstanceOptions, new() where TStoreOptions : class, IStoreOptions, new() - where TScopedStoreOptions : class, IScopedStoreOptions, new() + where TScopedStoreOptions : class, TStoreOptions, IScopedStoreOptions { if (!(storeOptions is TStoreOptions parsedStoreOptions)) { diff --git a/src/GeekLearning.Storage/Configuration/IParsedOptions.cs b/src/GeekLearning.Storage/Configuration/IParsedOptions.cs index cc7d18c..942f935 100644 --- a/src/GeekLearning.Storage/Configuration/IParsedOptions.cs +++ b/src/GeekLearning.Storage/Configuration/IParsedOptions.cs @@ -5,7 +5,7 @@ public interface IParsedOptions where TInstanceOptions : class, IProviderInstanceOptions where TStoreOptions : class, IStoreOptions - where TScopedStoreOptions : class, IScopedStoreOptions + where TScopedStoreOptions : class, TStoreOptions, IScopedStoreOptions { string Name { get; } diff --git a/src/GeekLearning.Storage/Exceptions/BadScopedStoreConfiguration.cs b/src/GeekLearning.Storage/Exceptions/BadScopedStoreConfiguration.cs new file mode 100644 index 0000000..508bf3c --- /dev/null +++ b/src/GeekLearning.Storage/Exceptions/BadScopedStoreConfiguration.cs @@ -0,0 +1,22 @@ +namespace GeekLearning.Storage.Exceptions +{ + using System; + + public class BadScopedStoreConfiguration : Exception + { + public BadScopedStoreConfiguration(string storeName) + : base($"The scoped store '{storeName}' was not properly configured.") + { + } + + public BadScopedStoreConfiguration(string storeName, string details) + : base($"The scoped store '{storeName}' was not properly configured. {details}") + { + } + + public BadScopedStoreConfiguration(string storeName, string details, Exception innerException) + : base($"The scoped store '{storeName}' was not properly configured. {details}", innerException) + { + } + } +} diff --git a/src/GeekLearning.Storage/IStorageFactory.cs b/src/GeekLearning.Storage/IStorageFactory.cs index a63dd9a..48ccad4 100644 --- a/src/GeekLearning.Storage/IStorageFactory.cs +++ b/src/GeekLearning.Storage/IStorageFactory.cs @@ -8,6 +8,8 @@ public interface IStorageFactory IStore GetStore(string storeName); + IStore GetScopedStore(string storeName, params object[] args); + bool TryGetStore(string storeName, out IStore store); bool TryGetStore(string storeName, out IStore store, string provider); diff --git a/src/GeekLearning.Storage/IStorageProvider.cs b/src/GeekLearning.Storage/IStorageProvider.cs index 07d5c5d..353be9f 100644 --- a/src/GeekLearning.Storage/IStorageProvider.cs +++ b/src/GeekLearning.Storage/IStorageProvider.cs @@ -9,5 +9,7 @@ public interface IStorageProvider IStore BuildStore(string storeName); IStore BuildStore(string storeName, IStoreOptions storeOptions); + + IStore BuildScopedStore(string storeName, params object[] args); } } diff --git a/src/GeekLearning.Storage/IStore.cs b/src/GeekLearning.Storage/IStore.cs index 889d16f..eb869de 100644 --- a/src/GeekLearning.Storage/IStore.cs +++ b/src/GeekLearning.Storage/IStore.cs @@ -8,6 +8,8 @@ public interface IStore { string Name { get; } + Task InitAsync(); + Task ListAsync(string path, bool recursive, bool withMetadata); Task ListAsync(string path, string searchPattern, bool recursive, bool withMetadata); diff --git a/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs b/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs index 1cc2e20..556b92e 100644 --- a/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs +++ b/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs @@ -8,7 +8,7 @@ public class ConfigureProviderOptions where TInstanceOptions : class, IProviderInstanceOptions, new() where TStoreOptions : class, IStoreOptions, new() - where TScopedStoreOptions : class, IScopedStoreOptions, new() + where TScopedStoreOptions : class, TStoreOptions, IScopedStoreOptions, new() { private readonly StorageOptions storageOptions; @@ -36,8 +36,6 @@ public void Configure(TParsedOptions options) } var parsedStores = this.storageOptions.Stores.Parse(); - var parsedScopedStores = this.storageOptions.ScopedStores.Parse(); - foreach (var parsedStore in parsedStores) { parsedStore.Value.Compute(options); @@ -47,6 +45,12 @@ public void Configure(TParsedOptions options) .Where(kvp => kvp.Value.ProviderType == options.Name) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + var parsedScopedStores = this.storageOptions.ScopedStores.Parse(); + foreach (var parsedScopedStore in parsedScopedStores) + { + parsedScopedStore.Value.Compute(options); + } + options.ParsedScopedStores = parsedScopedStores .Where(kvp => kvp.Value.ProviderType == options.Name) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); diff --git a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs index 08dde70..8dd2508 100644 --- a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs +++ b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs @@ -21,26 +21,28 @@ public GenericStoreProxy(IStorageFactory factory, IOptions options) this.innerStore = factory.GetStore(nameof(TOptions), options.Value); } - public string Name => innerStore.Name; + public string Name => this.innerStore.Name; - public Task DeleteAsync(IPrivateFileReference file) => innerStore.DeleteAsync(file); + public Task InitAsync() => this.innerStore.InitAsync(); - public Task GetAsync(Uri file, bool withMetadata) => innerStore.GetAsync(file, withMetadata); + public Task DeleteAsync(IPrivateFileReference file) => this.innerStore.DeleteAsync(file); - public Task GetAsync(IPrivateFileReference file, bool withMetadata) => innerStore.GetAsync(file, withMetadata); + public Task GetAsync(Uri file, bool withMetadata) => this.innerStore.GetAsync(file, withMetadata); - public Task ListAsync(string path, bool recursive, bool withMetadata) => innerStore.ListAsync(path, recursive, withMetadata); + public Task GetAsync(IPrivateFileReference file, bool withMetadata) => this.innerStore.GetAsync(file, withMetadata); - public Task ListAsync(string path, string searchPattern, bool recursive, bool withMetadata) => innerStore.ListAsync(path, searchPattern, recursive, withMetadata); + public Task ListAsync(string path, bool recursive, bool withMetadata) => this.innerStore.ListAsync(path, recursive, withMetadata); - public Task ReadAllBytesAsync(IPrivateFileReference file) => innerStore.ReadAllBytesAsync(file); + public Task ListAsync(string path, string searchPattern, bool recursive, bool withMetadata) => this.innerStore.ListAsync(path, searchPattern, recursive, withMetadata); - public Task ReadAllTextAsync(IPrivateFileReference file) => innerStore.ReadAllTextAsync(file); + public Task ReadAllBytesAsync(IPrivateFileReference file) => this.innerStore.ReadAllBytesAsync(file); - public Task ReadAsync(IPrivateFileReference file) => innerStore.ReadAsync(file); + public Task ReadAllTextAsync(IPrivateFileReference file) => this.innerStore.ReadAllTextAsync(file); - public Task SaveAsync(Stream data, IPrivateFileReference file, string contentType) => innerStore.SaveAsync(data, file, contentType); + public Task ReadAsync(IPrivateFileReference file) => this.innerStore.ReadAsync(file); - public Task SaveAsync(byte[] data, IPrivateFileReference file, string contentType) => innerStore.SaveAsync(data, file, contentType); + public Task SaveAsync(Stream data, IPrivateFileReference file, string contentType) => this.innerStore.SaveAsync(data, file, contentType); + + public Task SaveAsync(byte[] data, IPrivateFileReference file, string contentType) => this.innerStore.SaveAsync(data, file, contentType); } } diff --git a/src/GeekLearning.Storage/Internal/StorageFactory.cs b/src/GeekLearning.Storage/Internal/StorageFactory.cs index e43f178..8ff8cc4 100644 --- a/src/GeekLearning.Storage/Internal/StorageFactory.cs +++ b/src/GeekLearning.Storage/Internal/StorageFactory.cs @@ -26,6 +26,11 @@ public IStore GetStore(string storeName) return this.GetProvider(this.options.GetStoreConfiguration(storeName)).BuildStore(storeName); } + public IStore GetScopedStore(string storeName, params object[] args) + { + return this.GetProvider(this.options.GetScopedStoreConfiguration(storeName)).BuildScopedStore(storeName, args); + } + public bool TryGetStore(string storeName, out IStore store) { var configuration = this.options.GetStoreConfiguration(storeName, throwIfNotFound: false); diff --git a/src/GeekLearning.Storage/Internal/StorageProviderBase.cs b/src/GeekLearning.Storage/Internal/StorageProviderBase.cs index bb967e6..96acdaa 100644 --- a/src/GeekLearning.Storage/Internal/StorageProviderBase.cs +++ b/src/GeekLearning.Storage/Internal/StorageProviderBase.cs @@ -1,5 +1,6 @@ namespace GeekLearning.Storage.Internal { + using System; using Configuration; using Microsoft.Extensions.Options; @@ -7,7 +8,7 @@ public abstract class StorageProviderBase, new() where TInstanceOptions : class, IProviderInstanceOptions, new() where TStoreOptions : class, IStoreOptions, new() - where TScopedStoreOptions : class, IScopedStoreOptions, new() + where TScopedStoreOptions : class, TStoreOptions, IScopedStoreOptions { protected readonly TParsedOptions options; @@ -20,7 +21,7 @@ public StorageProviderBase(IOptions options) public IStore BuildStore(string storeName) { - return this.BuildStore(storeName, this.options.GetStoreConfiguration(storeName)); + return this.BuildStoreInternal(storeName, this.options.GetStoreConfiguration(storeName)); } public IStore BuildStore(string storeName, IStoreOptions storeOptions) @@ -30,11 +31,27 @@ public IStore BuildStore(string storeName, IStoreOptions storeOptions) throw new Exceptions.BadStoreProviderException(this.Name, storeName); } - return this.BuildStore( + return this.BuildStoreInternal( storeName, storeOptions.ParseStoreOptions(options)); } - protected abstract IStore BuildStore(string storeName, TStoreOptions storeOptions); + public IStore BuildScopedStore(string storeName, params object[] args) + { + var scopedStoreOptions = this.options.GetScopedStoreConfiguration(storeName); + + try + { + scopedStoreOptions.FolderName = string.Format(scopedStoreOptions.FolderNameFormat, args); + } + catch (Exception ex) + { + throw new Exceptions.BadScopedStoreConfiguration(storeName, "Cannot format folder name. See InnerException for details.", ex); + } + + return this.BuildStoreInternal(storeName, scopedStoreOptions.ParseStoreOptions(options)); + } + + protected abstract IStore BuildStoreInternal(string storeName, TStoreOptions storeOptions); } } diff --git a/tests/GeekLearning.Storage.Integration.Test/ScopedStoresTests.cs b/tests/GeekLearning.Storage.Integration.Test/ScopedStoresTests.cs new file mode 100644 index 0000000..ea01d2b --- /dev/null +++ b/tests/GeekLearning.Storage.Integration.Test/ScopedStoresTests.cs @@ -0,0 +1,41 @@ +namespace GeekLearning.Storage.Integration.Test +{ + using Microsoft.Extensions.DependencyInjection; + using Storage; + using System; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + [Collection(nameof(IntegrationCollection))] + [Trait("Operation", "ScopedStores"), Trait("Kind", "Integration")] + public class ScopedStoresTests + { + private StoresFixture storeFixture; + + public ScopedStoresTests(StoresFixture fixture) + { + this.storeFixture = fixture; + } + + [Theory(DisplayName = nameof(ScopedStoreUpdate)), InlineData("ScopedStore1"), InlineData("ScopedStore2")] + public async Task ScopedStoreUpdate(string storeName) + { + var storageFactory = this.storeFixture.Services.GetRequiredService(); + + var formatArg = Guid.NewGuid(); + var store = storageFactory.GetScopedStore(storeName, formatArg); + + await store.InitAsync(); + + var textToWrite = "The answer is 42"; + var filePath = "Update/42.txt"; + + await store.SaveAsync(Encoding.UTF8.GetBytes(textToWrite), filePath, "text/plain"); + + var readFromWrittenFile = await store.ReadAllTextAsync(filePath); + + Assert.Equal(textToWrite, readFromWrittenFile); + } + } +} diff --git a/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs b/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs index bfcf5a7..4017599 100644 --- a/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs +++ b/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs @@ -64,6 +64,8 @@ public StoresFixture() public string FileSystemRootPath => Path.Combine(this.BasePath, "FileVault"); + public string FileSystemSecondaryRootPath => Path.Combine(this.BasePath, "FileVault2"); + public StorageOptions StorageOptions { get; } public AzureParsedOptions AzureParsedOptions { get; } @@ -92,6 +94,11 @@ private void DeleteRootResources() { Directory.Delete(this.FileSystemRootPath, true); } + + if (Directory.Exists(this.FileSystemSecondaryRootPath)) + { + Directory.Delete(this.FileSystemSecondaryRootPath, true); + } } private void ResetStores() diff --git a/tests/GeekLearning.Storage.Integration.Test/appsettings.json b/tests/GeekLearning.Storage.Integration.Test/appsettings.json index c887a81..3876bad 100644 --- a/tests/GeekLearning.Storage.Integration.Test/appsettings.json +++ b/tests/GeekLearning.Storage.Integration.Test/appsettings.json @@ -21,7 +21,7 @@ }, "AnotherFileSystem": { "Type": "FileSystem", - "RootPath": ".." + "RootPath": "../FileVault2" } }, From cf5d398dafc7413382b2481447f9f16fb9baf52a Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Thu, 13 Apr 2017 18:43:37 +0200 Subject: [PATCH 6/9] Check new AccessLevel parameter in FileSystem.Server --- .../FileSystemStorageServerMiddleware.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs b/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs index e2bd947..a2a515a 100644 --- a/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs +++ b/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs @@ -35,15 +35,13 @@ public async Task Invoke(HttpContext context) var storageFactory = context.RequestServices.GetRequiredService(); if (this.fileSystemParsedOptions.ParsedStores.TryGetValue(storeName, out var storeOptions) - && storeOptions.ProviderType == "FileSystem") + && storeOptions.ProviderType == FileSystemStorageProvider.ProviderName) { - string access; - // TODO: Fix options! - //if (!storeOptions.Parameters.TryGetValue("Access", out access) && access != "Public") - //{ - // context.Response.StatusCode = StatusCodes.Status403Forbidden; - // return; - //} + if (storeOptions.AccessLevel != Storage.Configuration.AccessLevel.Public) + { + context.Response.StatusCode = StatusCodes.Status403Forbidden; + return; + } IStore store = storageFactory.GetStore(storeName, storeOptions); From e6a290eb170126491e6f68a8b14cb6a4571e98af Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Fri, 19 May 2017 11:19:35 +0200 Subject: [PATCH 7/9] Update framework packages and fix sample project --- .../GeekLearning.Storage.BasicSample.csproj | 26 +++++----- .../appsettings.json | 49 ++----------------- .../GeekLearning.Storage.Azure.csproj | 8 +-- ...ystem.ExtendedProperties.FileSystem.csproj | 2 +- ...kLearning.Storage.FileSystem.Server.csproj | 6 +-- .../GeekLearning.Storage.FileSystem.csproj | 6 +-- .../GeekLearning.Storage.csproj | 8 +-- ...ekLearning.Storage.Integration.Test.csproj | 22 ++++----- 8 files changed, 42 insertions(+), 85 deletions(-) diff --git a/samples/GeekLearning.Storage.BasicSample/GeekLearning.Storage.BasicSample.csproj b/samples/GeekLearning.Storage.BasicSample/GeekLearning.Storage.BasicSample.csproj index d9d597a..ceb2ee2 100644 --- a/samples/GeekLearning.Storage.BasicSample/GeekLearning.Storage.BasicSample.csproj +++ b/samples/GeekLearning.Storage.BasicSample/GeekLearning.Storage.BasicSample.csproj @@ -1,4 +1,4 @@ - + netcoreapp1.1 @@ -6,7 +6,7 @@ GeekLearning.Storage.BasicSample Exe GeekLearning.Storage.BasicSample - 1.1.1 + 1.1.2 $(PackageTargetFallback);portable-net45+win8 @@ -24,17 +24,17 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/samples/GeekLearning.Storage.BasicSample/appsettings.json b/samples/GeekLearning.Storage.BasicSample/appsettings.json index 4d8074f..614aef4 100644 --- a/samples/GeekLearning.Storage.BasicSample/appsettings.json +++ b/samples/GeekLearning.Storage.BasicSample/appsettings.json @@ -8,54 +8,11 @@ } }, "Storage": { - - "Providers": { - "FirstAzure": { - "Type": "Azure", - "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=default;AccountKey=;EndpointSuffix=core.windows.net" - }, - "AnotherAzure": { - "Type": "Azure", - "ConnectionStringName": "ConnectionStringFromAppSettings" - }, - "FirstFileSystem": { - "Type": "FileSystem", - "RootPath": "C:/First" - }, - "AnotherFileSystem": { - "Type": "FileSystem", - "RootPath": "D:/Another" - } - }, - "Stores": { - "Youpi1": { - "ProviderName": "FirstFileSystem" - }, - "Youpi2": { - "ProviderName": "FirstFileSystem", + "Templates": { + "ProviderType": "FileSystem", "AccessLevel": "Public", - "FolderName": "AnotherPath" - }, - "Youpi4": { - "ProviderName": "FirstAzure", - "AccessLevel": "Private" - }, - "Youpa2": { - "ProviderType": "Azure", - "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=excpetionaccount;AccountKey=;EndpointSuffix=core.windows.net" - } - }, - - "ScopedStores": { - "Youpi3": { - "ProviderName": "AnotherFileSystem", - "FolderNameFormat": "AnotherPath-{0}" - }, - "Youpa": { - "ProviderName": "AnotherAzure", - "AccessLevel": "Confidential", - "FolderNameFormat": "Youpa-{0}" + "FolderName": "Templates" } } } diff --git a/src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.csproj b/src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.csproj index 9165231..05046db 100644 --- a/src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.csproj +++ b/src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.csproj @@ -16,10 +16,10 @@ - - - - + + + + diff --git a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem.csproj b/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem.csproj index 4aac9ec..a49d417 100644 --- a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem.csproj +++ b/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.csproj b/src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.csproj index d82a11c..01b1a2a 100644 --- a/src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.csproj +++ b/src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.csproj @@ -15,9 +15,9 @@ - - - + + + diff --git a/src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.csproj b/src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.csproj index 24c0baa..bae7d7f 100644 --- a/src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.csproj +++ b/src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.csproj @@ -15,9 +15,9 @@ - - - + + + diff --git a/src/GeekLearning.Storage/GeekLearning.Storage.csproj b/src/GeekLearning.Storage/GeekLearning.Storage.csproj index 4b03abb..2e70721 100644 --- a/src/GeekLearning.Storage/GeekLearning.Storage.csproj +++ b/src/GeekLearning.Storage/GeekLearning.Storage.csproj @@ -11,10 +11,10 @@ - - - - + + + + diff --git a/tests/GeekLearning.Storage.Integration.Test/GeekLearning.Storage.Integration.Test.csproj b/tests/GeekLearning.Storage.Integration.Test/GeekLearning.Storage.Integration.Test.csproj index 9dd8e42..2a5b84e 100644 --- a/tests/GeekLearning.Storage.Integration.Test/GeekLearning.Storage.Integration.Test.csproj +++ b/tests/GeekLearning.Storage.Integration.Test/GeekLearning.Storage.Integration.Test.csproj @@ -6,7 +6,7 @@ GeekLearning.Storage.Integration.Test true $(PackageTargetFallback);dotnet;portable-net45+win8 - 1.0.4 + 1.1.2 @@ -23,18 +23,18 @@ - + - - - + + + - - - - - - + + + + + + From 73f50c014c3ff79415c9548de3ddbdc7f09ada54 Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Fri, 19 May 2017 15:30:55 +0200 Subject: [PATCH 8/9] Validate options --- .../appsettings.json | 3 +- src/GeekLearning.Storage.Azure/AzureStore.cs | 31 +++++++------ .../Configuration/AzureStoreOptions.cs | 21 +++++++++ .../Configuration/FileSystemStoreOptions.cs | 21 +++++++++ .../FileSystemStore.cs | 15 +----- .../Configuration/IOptionError.cs | 9 ++++ .../Configuration/IStoreOptions.cs | 4 ++ .../Configuration/OptionError.cs | 9 ++++ .../Configuration/StoreOptions.cs | 46 +++++++++++++++++++ .../Exceptions/BadStoreConfiguration.cs | 10 ++++ .../TestStore.cs | 7 +++ 11 files changed, 147 insertions(+), 29 deletions(-) create mode 100644 src/GeekLearning.Storage/Configuration/IOptionError.cs create mode 100644 src/GeekLearning.Storage/Configuration/OptionError.cs diff --git a/samples/GeekLearning.Storage.BasicSample/appsettings.json b/samples/GeekLearning.Storage.BasicSample/appsettings.json index 614aef4..22ffc9d 100644 --- a/samples/GeekLearning.Storage.BasicSample/appsettings.json +++ b/samples/GeekLearning.Storage.BasicSample/appsettings.json @@ -11,8 +11,7 @@ "Stores": { "Templates": { "ProviderType": "FileSystem", - "AccessLevel": "Public", - "FolderName": "Templates" + "AccessLevel": "Public" } } } diff --git a/src/GeekLearning.Storage.Azure/AzureStore.cs b/src/GeekLearning.Storage.Azure/AzureStore.cs index 7cf8cd5..0151860 100644 --- a/src/GeekLearning.Storage.Azure/AzureStore.cs +++ b/src/GeekLearning.Storage.Azure/AzureStore.cs @@ -18,19 +18,9 @@ public class AzureStore : IStore public AzureStore(AzureStoreOptions storeOptions) { - this.storeOptions = storeOptions; - - // TODO: Create Validate method in IStoreOptions - //if (string.IsNullOrWhiteSpace(connectionString)) - //{ - // throw new ArgumentNullException("connectionString"); - //} - - //if (string.IsNullOrWhiteSpace(containerName)) - //{ - // throw new ArgumentNullException("containerName"); - //} + storeOptions.Validate(); + this.storeOptions = storeOptions; this.client = new Lazy(() => CloudStorageAccount.Parse(storeOptions.ConnectionString).CreateCloudBlobClient()); this.container = new Lazy(() => this.client.Value.GetContainerReference(storeOptions.FolderName)); } @@ -39,7 +29,22 @@ public AzureStore(AzureStoreOptions storeOptions) public Task InitAsync() { - return this.container.Value.CreateIfNotExistsAsync(); + BlobContainerPublicAccessType accessType; + switch (this.storeOptions.AccessLevel) + { + case Storage.Configuration.AccessLevel.Public: + accessType = BlobContainerPublicAccessType.Container; + break; + case Storage.Configuration.AccessLevel.Confidential: + accessType = BlobContainerPublicAccessType.Blob; + break; + case Storage.Configuration.AccessLevel.Private: + default: + accessType = BlobContainerPublicAccessType.Off; + break; + } + + return this.container.Value.CreateIfNotExistsAsync(accessType, null, null); } public async Task ListAsync(string path, bool recursive, bool withMetadata) diff --git a/src/GeekLearning.Storage.Azure/Configuration/AzureStoreOptions.cs b/src/GeekLearning.Storage.Azure/Configuration/AzureStoreOptions.cs index 4401fde..c841405 100644 --- a/src/GeekLearning.Storage.Azure/Configuration/AzureStoreOptions.cs +++ b/src/GeekLearning.Storage.Azure/Configuration/AzureStoreOptions.cs @@ -1,11 +1,32 @@ namespace GeekLearning.Storage.Azure.Configuration { using GeekLearning.Storage.Configuration; + using System.Collections.Generic; + using System.Linq; public class AzureStoreOptions : StoreOptions { public string ConnectionString { get; set; } public string ConnectionStringName { get; set; } + + public override IEnumerable Validate(bool throwOnError = true) + { + var baseErrors = base.Validate(throwOnError); + var optionErrors = new List(); + + if (string.IsNullOrEmpty(this.ConnectionString)) + { + this.PushMissingPropertyError(optionErrors, nameof(this.ConnectionString)); + } + + var finalErrors = baseErrors.Concat(optionErrors); + if (throwOnError && finalErrors.Any()) + { + throw new Exceptions.BadStoreConfiguration(this.Name, finalErrors); + } + + return finalErrors; + } } } diff --git a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemStoreOptions.cs b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemStoreOptions.cs index af93561..7b3ad91 100644 --- a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemStoreOptions.cs +++ b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemStoreOptions.cs @@ -2,6 +2,8 @@ { using GeekLearning.Storage.Configuration; using System.IO; + using System.Collections.Generic; + using System.Linq; public class FileSystemStoreOptions : StoreOptions { @@ -24,5 +26,24 @@ public string AbsolutePath return Path.Combine(this.RootPath, this.FolderName); } } + + public override IEnumerable Validate(bool throwOnError = true) + { + var baseErrors = base.Validate(throwOnError); + var optionErrors = new List(); + + if (string.IsNullOrEmpty(this.AbsolutePath)) + { + this.PushMissingPropertyError(optionErrors, nameof(this.AbsolutePath)); + } + + var finalErrors = baseErrors.Concat(optionErrors); + if (throwOnError && finalErrors.Any()) + { + throw new Exceptions.BadStoreConfiguration(this.Name, finalErrors); + } + + return finalErrors; + } } } diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs index d08b8ed..724cfd4 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs @@ -16,20 +16,7 @@ public class FileSystemStore : IStore public FileSystemStore(FileSystemStoreOptions storeOptions, IPublicUrlProvider publicUrlProvider, IExtendedPropertiesProvider extendedPropertiesProvider) { - // TODO: Implement Validate method on options - //if (string.IsNullOrEmpty(path)) - //{ - // throw new ArgumentNullException("path"); - //} - - //if (Path.IsPathRooted(path)) - //{ - // this.AbsolutePath = path; - //} - //else - //{ - // this.AbsolutePath = Path.Combine(rootPath, path); - //} + storeOptions.Validate(); this.storeOptions = storeOptions; this.publicUrlProvider = publicUrlProvider; diff --git a/src/GeekLearning.Storage/Configuration/IOptionError.cs b/src/GeekLearning.Storage/Configuration/IOptionError.cs new file mode 100644 index 0000000..8647dc6 --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/IOptionError.cs @@ -0,0 +1,9 @@ +namespace GeekLearning.Storage.Configuration +{ + public interface IOptionError + { + string PropertyName { get; } + + string ErrorMessage { get; } + } +} diff --git a/src/GeekLearning.Storage/Configuration/IStoreOptions.cs b/src/GeekLearning.Storage/Configuration/IStoreOptions.cs index c55478e..099b390 100644 --- a/src/GeekLearning.Storage/Configuration/IStoreOptions.cs +++ b/src/GeekLearning.Storage/Configuration/IStoreOptions.cs @@ -1,5 +1,7 @@ namespace GeekLearning.Storage.Configuration { + using System.Collections.Generic; + public interface IStoreOptions : INamedElementOptions { string ProviderName { get; set; } @@ -9,5 +11,7 @@ public interface IStoreOptions : INamedElementOptions AccessLevel AccessLevel { get; set; } string FolderName { get; set; } + + IEnumerable Validate(bool throwOnError = true); } } diff --git a/src/GeekLearning.Storage/Configuration/OptionError.cs b/src/GeekLearning.Storage/Configuration/OptionError.cs new file mode 100644 index 0000000..b8447c3 --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/OptionError.cs @@ -0,0 +1,9 @@ +namespace GeekLearning.Storage.Configuration +{ + public class OptionError : IOptionError + { + public string PropertyName { get; set; } + + public string ErrorMessage { get; set; } + } +} diff --git a/src/GeekLearning.Storage/Configuration/StoreOptions.cs b/src/GeekLearning.Storage/Configuration/StoreOptions.cs index c200b76..66f292c 100644 --- a/src/GeekLearning.Storage/Configuration/StoreOptions.cs +++ b/src/GeekLearning.Storage/Configuration/StoreOptions.cs @@ -1,7 +1,13 @@ namespace GeekLearning.Storage.Configuration { + using System; + using System.Collections.Generic; + using System.Linq; + public class StoreOptions : IStoreOptions { + private const string MissingPropertyErrorMessage = "{0} should be defined."; + public string Name { get; set; } public string ProviderName { get; set; } @@ -11,5 +17,45 @@ public class StoreOptions : IStoreOptions public AccessLevel AccessLevel { get; set; } public string FolderName { get; set; } + + public virtual IEnumerable Validate(bool throwOnError = true) + { + var optionErrors = new List(); + + if (string.IsNullOrEmpty(this.Name)) + { + this.PushMissingPropertyError(optionErrors, nameof(this.Name)); + } + + if (string.IsNullOrEmpty(this.ProviderName) && string.IsNullOrEmpty(this.ProviderType)) + { + optionErrors.Add(new OptionError + { + PropertyName = "Provider", + ErrorMessage = $"You should set either a {nameof(this.ProviderType)} or a {nameof(this.ProviderName)} for each Store." + }); + } + + if (string.IsNullOrEmpty(this.FolderName)) + { + this.PushMissingPropertyError(optionErrors, nameof(this.FolderName)); + } + + if (throwOnError && optionErrors.Any()) + { + throw new Exceptions.BadStoreConfiguration(this.Name, optionErrors); + } + + return optionErrors; + } + + protected void PushMissingPropertyError(List optionErrors, string propertyName) + { + optionErrors.Add(new OptionError + { + PropertyName = propertyName, + ErrorMessage = string.Format(MissingPropertyErrorMessage, propertyName) + }); + } } } diff --git a/src/GeekLearning.Storage/Exceptions/BadStoreConfiguration.cs b/src/GeekLearning.Storage/Exceptions/BadStoreConfiguration.cs index 5ceb28e..caae3eb 100644 --- a/src/GeekLearning.Storage/Exceptions/BadStoreConfiguration.cs +++ b/src/GeekLearning.Storage/Exceptions/BadStoreConfiguration.cs @@ -1,6 +1,8 @@ namespace GeekLearning.Storage.Exceptions { using System; + using System.Collections.Generic; + using System.Linq; public class BadStoreConfiguration : Exception { @@ -13,5 +15,13 @@ public BadStoreConfiguration(string storeName, string details) : base($"The store '{storeName}' was not properly configured. {details}") { } + + public BadStoreConfiguration(string storeName, IEnumerable errors) + : this(storeName, string.Join(" | ", errors.Select(e => e.ErrorMessage))) + { + this.Errors = errors; + } + + public IEnumerable Errors { get; } } } diff --git a/tests/GeekLearning.Storage.Integration.Test/TestStore.cs b/tests/GeekLearning.Storage.Integration.Test/TestStore.cs index 47f0cc9..ab5c465 100644 --- a/tests/GeekLearning.Storage.Integration.Test/TestStore.cs +++ b/tests/GeekLearning.Storage.Integration.Test/TestStore.cs @@ -1,6 +1,8 @@ namespace GeekLearning.Storage.Integration.Test { using GeekLearning.Storage.Configuration; + using System.Collections.Generic; + using System.Linq; public class TestStore : IStoreOptions { @@ -19,5 +21,10 @@ public TestStore() public string FolderName { get; set; } public string Name { get; set; } + + public IEnumerable Validate(bool throwOnError = true) + { + return Enumerable.Empty(); + } } } From bd662612b22e5dbfe6cb6c39bc0ebdbed4087921 Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Fri, 19 May 2017 16:05:41 +0200 Subject: [PATCH 9/9] Update Readme.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b8d5220..932882b 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,4 @@ We don't support for Amazon S3, but it is one of our high priority objective. You can head to our introduction [blog post](http://geeklearning.io/dotnet-core-storage-cloud-or-file-system-storage-made-easy/), or to the [wiki](https://github.com/geeklearningio/gl-dotnet-storage/wiki). +