From 057153697262246a083b7e7efe755a5d3fabc3bc Mon Sep 17 00:00:00 2001 From: Clay Brinlee Date: Mon, 6 May 2024 14:45:00 -0500 Subject: [PATCH] support for .net 6 LTS --- .../MultiTenantServiceScopeFactory.cs | 22 +++++++++--- ...ContextAccessorMiddleware.StartupFilter.cs | 2 +- .../MultiTenantContextAccessorMiddleware.cs | 30 ++++++++++++---- .../Middleware/MultiTenantMiddleware.cs | 11 +++++- ...RequestServicesMiddleware.StartupFilter.cs | 2 +- .../MultiTenantRequestServicesMiddleware.cs | 17 +++++++--- .../Options/MultiTenantOptionsCache.cs | 9 +++-- .../Options/MultiTenantOptionsManager.cs | 10 +++++- .../Strategies/HostResolutionStrategy.cs | 8 +++-- src/MultiTenant.AspNetCore.csproj | 2 +- src/Registration/TenantBuilder.cs | 34 ++++++++++++------- .../AsyncLocalMultiTenantContextAccessor.cs | 2 +- src/Services/InMemoryLookupService.cs | 10 ++++-- test/MultiTenant.AspNetCore.Tests.csproj | 6 ++-- test/TestTenant.cs | 10 ++++-- 15 files changed, 128 insertions(+), 47 deletions(-) diff --git a/src/Infrastructure/DependencyInjection/MultiTenantServiceScopeFactory.cs b/src/Infrastructure/DependencyInjection/MultiTenantServiceScopeFactory.cs index f07c16d..339039c 100644 --- a/src/Infrastructure/DependencyInjection/MultiTenantServiceScopeFactory.cs +++ b/src/Infrastructure/DependencyInjection/MultiTenantServiceScopeFactory.cs @@ -9,11 +9,18 @@ namespace MultiTenant.AspNetCore.Infrastructure.DependencyInjection /// Factory for creating tenant specific service providers /// /// - internal class MultiTenantServiceProviderFactory(IServiceCollection containerBuilder, Action tenantServiceConfiguration) where T : ITenantInfo + internal class MultiTenantServiceProviderFactory where T : ITenantInfo { - + + public MultiTenantServiceProviderFactory(IServiceCollection containerBuilder, Action tenantServiceConfiguration) + { + this.containerBuilder = containerBuilder; + this.tenantServiceConfiguration = tenantServiceConfiguration; + } //Cache compiled providers private readonly ConcurrentDictionary> CompiledProviders = new(); + private readonly IServiceCollection containerBuilder; + private readonly Action tenantServiceConfiguration; public IServiceProvider GetServiceProviderForTenant(T tenant) { @@ -36,9 +43,16 @@ public IServiceProvider GetServiceProviderForTenant(T tenant) /// Factory wrapper for creating service scopes /// /// - internal class MultiTenantServiceScopeFactory(MultiTenantServiceProviderFactory ServiceProviderFactory, IMultiTenantContextAccessor multiTenantContextAccessor) : IMultiTenantServiceScopeFactory where T : ITenantInfo + internal class MultiTenantServiceScopeFactory : IMultiTenantServiceScopeFactory where T : ITenantInfo { + private readonly MultiTenantServiceProviderFactory serviceProviderFactory; + private readonly IMultiTenantContextAccessor multiTenantContextAccessor; + public MultiTenantServiceScopeFactory(MultiTenantServiceProviderFactory ServiceProviderFactory, IMultiTenantContextAccessor multiTenantContextAccessor) + { + serviceProviderFactory = ServiceProviderFactory; + this.multiTenantContextAccessor = multiTenantContextAccessor; + } /// /// Create scope /// @@ -46,7 +60,7 @@ internal class MultiTenantServiceScopeFactory(MultiTenantServiceProviderFacto public IServiceScope CreateScope() { var tenant = multiTenantContextAccessor.TenantInfo ?? throw new InvalidOperationException("Tenant context is not available"); - return ServiceProviderFactory.GetServiceProviderForTenant(tenant).CreateScope(); + return serviceProviderFactory.GetServiceProviderForTenant(tenant).CreateScope(); } } } diff --git a/src/Infrastructure/Middleware/MultiTenantContextAccessorMiddleware.StartupFilter.cs b/src/Infrastructure/Middleware/MultiTenantContextAccessorMiddleware.StartupFilter.cs index 4c8c0f9..56bb185 100644 --- a/src/Infrastructure/Middleware/MultiTenantContextAccessorMiddleware.StartupFilter.cs +++ b/src/Infrastructure/Middleware/MultiTenantContextAccessorMiddleware.StartupFilter.cs @@ -8,7 +8,7 @@ namespace MultiTenant.AspNetCore.Infrastructure.Middleware /// Register the multitenant context accessor middleware with the app pipeline. /// /// - internal class MultiTenantContextAccessorStartupFilter() : IStartupFilter where T : ITenantInfo + internal class MultiTenantContextAccessorStartupFilter : IStartupFilter where T : ITenantInfo { /// /// Adds the multitenant request services middleware to the app pipeline. diff --git a/src/Infrastructure/Middleware/MultiTenantContextAccessorMiddleware.cs b/src/Infrastructure/Middleware/MultiTenantContextAccessorMiddleware.cs index 20a9bd3..e6758ea 100644 --- a/src/Infrastructure/Middleware/MultiTenantContextAccessorMiddleware.cs +++ b/src/Infrastructure/Middleware/MultiTenantContextAccessorMiddleware.cs @@ -8,14 +8,30 @@ namespace MultiTenant.AspNetCore.Infrastructure.Middleware /// /// /// - internal class MultiTenantContextAccessorMiddleware( + internal class MultiTenantContextAccessorMiddleware where T : ITenantInfo + { + private readonly RequestDelegate next; + private readonly IHttpContextAccessor httpContextAccessor; + private readonly IMultiTenantContextAccessor tenantAccessor; + private readonly ITenantLookupService tenantResolver; + private readonly ITenantResolutionStrategy tenantResolutionStrategy; + private readonly IOptions> options; + + public MultiTenantContextAccessorMiddleware( RequestDelegate next, IHttpContextAccessor httpContextAccessor, IMultiTenantContextAccessor TenantAccessor, ITenantLookupService TenantResolver, ITenantResolutionStrategy TenantResolutionStrategy, - IOptions> Options) where T : ITenantInfo - { + IOptions> Options) + { + this.next = next; + this.httpContextAccessor = httpContextAccessor; + tenantAccessor = TenantAccessor; + tenantResolver = TenantResolver; + tenantResolutionStrategy = TenantResolutionStrategy; + options = Options; + } /// /// Set the services for the tenant to be our specific tenant services @@ -24,13 +40,13 @@ internal class MultiTenantContextAccessorMiddleware( /// public async Task Invoke(HttpContext context) { - var options = Options.Value!; + var options = this.options.Value!; //Set context if missing so it can be used by the tenant services to resolve the tenant httpContextAccessor.HttpContext ??= context; //Get the tenant identifier - var identifier = await TenantResolutionStrategy.GetTenantIdentifierAsync(); + var identifier = await tenantResolutionStrategy.GetTenantIdentifierAsync(); if(identifier == null && options.MissingTenantBehavior == MissingTenantBehavior.ThrowException) throw new InvalidOperationException("Tenant identifier could not be resolved using configured strategy"); if(identifier == null && options.MissingTenantBehavior == MissingTenantBehavior.UseDefault) @@ -39,13 +55,13 @@ public async Task Invoke(HttpContext context) //Set the tenant context if (identifier != null) { - var tenant = await TenantResolver.GetTenantAsync(identifier); + var tenant = await tenantResolver.GetTenantAsync(identifier); if(tenant == null && options.MissingTenantBehavior == MissingTenantBehavior.ThrowException) throw new InvalidOperationException($"No tenant found matching '{identifier}'"); if(tenant == null && options.MissingTenantBehavior == MissingTenantBehavior.UseDefault) tenant = options.DefaultTenant; - TenantAccessor.TenantInfo ??= tenant; + tenantAccessor.TenantInfo ??= tenant; } await next.Invoke(context); diff --git a/src/Infrastructure/Middleware/MultiTenantMiddleware.cs b/src/Infrastructure/Middleware/MultiTenantMiddleware.cs index 3392ced..e2c1532 100644 --- a/src/Infrastructure/Middleware/MultiTenantMiddleware.cs +++ b/src/Infrastructure/Middleware/MultiTenantMiddleware.cs @@ -13,11 +13,20 @@ namespace MultiTenant.AspNetCore.Infrastructure.Middleware /// /// /// - internal class MultiTenantMiddleware(RequestDelegate next, IApplicationBuilder builder, Action configurePipeline) + internal class MultiTenantMiddleware where T : ITenantInfo { + public MultiTenantMiddleware(RequestDelegate next, IApplicationBuilder builder, Action configurePipeline) + { + this.next = next; + this.builder = builder; + this.configurePipeline = configurePipeline; + } //Cache compiled pipelines private readonly ConcurrentDictionary> _pipelinesCache = new(); + private readonly RequestDelegate next; + private readonly IApplicationBuilder builder; + private readonly Action configurePipeline; /// /// Set the services for the tenant to be our specific tenant services diff --git a/src/Infrastructure/Middleware/MultiTenantRequestServicesMiddleware.StartupFilter.cs b/src/Infrastructure/Middleware/MultiTenantRequestServicesMiddleware.StartupFilter.cs index 521eb2c..72885d4 100644 --- a/src/Infrastructure/Middleware/MultiTenantRequestServicesMiddleware.StartupFilter.cs +++ b/src/Infrastructure/Middleware/MultiTenantRequestServicesMiddleware.StartupFilter.cs @@ -8,7 +8,7 @@ namespace MultiTenant.AspNetCore.Infrastructure.Middleware /// /// The tenant specific tenant services configuration. /// - internal class MultitenantRequestServicesStartupFilter() : IStartupFilter where T : ITenantInfo + internal class MultitenantRequestServicesStartupFilter : IStartupFilter where T : ITenantInfo { /// /// Adds the multitenant request services middleware to the app pipeline. diff --git a/src/Infrastructure/Middleware/MultiTenantRequestServicesMiddleware.cs b/src/Infrastructure/Middleware/MultiTenantRequestServicesMiddleware.cs index 3809cf3..3fbab11 100644 --- a/src/Infrastructure/Middleware/MultiTenantRequestServicesMiddleware.cs +++ b/src/Infrastructure/Middleware/MultiTenantRequestServicesMiddleware.cs @@ -10,12 +10,21 @@ namespace MultiTenant.AspNetCore.Infrastructure.Middleware /// /// /// - internal class MultiTenantRequestServicesMiddleware( - RequestDelegate next, - IMultiTenantServiceScopeFactory multiTenantServiceProviderScopeFactory, - IHttpContextAccessor httpContextAccessor) where T : ITenantInfo + internal class MultiTenantRequestServicesMiddleware where T : ITenantInfo { + private readonly RequestDelegate next; + private readonly IMultiTenantServiceScopeFactory multiTenantServiceProviderScopeFactory; + private readonly IHttpContextAccessor httpContextAccessor; + public MultiTenantRequestServicesMiddleware( + RequestDelegate next, + IMultiTenantServiceScopeFactory multiTenantServiceProviderScopeFactory, + IHttpContextAccessor httpContextAccessor) + { + this.next = next; + this.multiTenantServiceProviderScopeFactory = multiTenantServiceProviderScopeFactory; + this.httpContextAccessor = httpContextAccessor; + } /// /// Set the services for the tenant to be our specific tenant services /// diff --git a/src/Infrastructure/Options/MultiTenantOptionsCache.cs b/src/Infrastructure/Options/MultiTenantOptionsCache.cs index 330d6d1..90a211a 100644 --- a/src/Infrastructure/Options/MultiTenantOptionsCache.cs +++ b/src/Infrastructure/Options/MultiTenantOptionsCache.cs @@ -8,12 +8,15 @@ namespace MultiTenant.AspNetCore.Infrastructure.Options /// /// /// - internal class MultiTenantOptionsCache(IMultiTenantContextAccessor multiTenantContextAccessor) : IOptionsMonitorCache + internal class MultiTenantOptionsCache : IOptionsMonitorCache where TOptions : class where T : ITenantInfo { - - private readonly IMultiTenantContextAccessor multiTenantContextAccessor = multiTenantContextAccessor ?? + public MultiTenantOptionsCache(IMultiTenantContextAccessor multiTenantContextAccessor) + { + this.multiTenantContextAccessor = multiTenantContextAccessor ?? throw new ArgumentNullException(nameof(multiTenantContextAccessor)); + } + private readonly IMultiTenantContextAccessor multiTenantContextAccessor; private readonly ConcurrentDictionary> tenantCaches = new(); /// diff --git a/src/Infrastructure/Options/MultiTenantOptionsManager.cs b/src/Infrastructure/Options/MultiTenantOptionsManager.cs index f361624..32e198d 100644 --- a/src/Infrastructure/Options/MultiTenantOptionsManager.cs +++ b/src/Infrastructure/Options/MultiTenantOptionsManager.cs @@ -2,8 +2,16 @@ namespace MultiTenant.AspNetCore.Infrastructure.Options { - internal class MultiTenantOptionsManager(IOptionsFactory factory, IOptionsMonitorCache cache) : IOptionsSnapshot where TOptions : class + internal class MultiTenantOptionsManager : IOptionsSnapshot where TOptions : class { + private readonly IOptionsFactory factory; + private readonly IOptionsMonitorCache cache; + + public MultiTenantOptionsManager(IOptionsFactory factory, IOptionsMonitorCache cache) + { + this.factory = factory; + this.cache = cache; + } public TOptions Value => Get(Microsoft.Extensions.Options.Options.DefaultName); public TOptions Get(string? name) diff --git a/src/Infrastructure/Strategies/HostResolutionStrategy.cs b/src/Infrastructure/Strategies/HostResolutionStrategy.cs index 4398ff3..2481564 100644 --- a/src/Infrastructure/Strategies/HostResolutionStrategy.cs +++ b/src/Infrastructure/Strategies/HostResolutionStrategy.cs @@ -5,9 +5,13 @@ namespace MultiTenant.AspNetCore.Infrastructure.Strategies /// /// Resolve the host to a tenant identifier /// - internal class HostResolutionStrategy(IHttpContextAccessor httpContextAccessor) : ITenantResolutionStrategy + internal class HostResolutionStrategy : ITenantResolutionStrategy { - private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor; + public HostResolutionStrategy(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + private readonly IHttpContextAccessor _httpContextAccessor; /// /// Get the tenant identifier diff --git a/src/MultiTenant.AspNetCore.csproj b/src/MultiTenant.AspNetCore.csproj index b816084..97acfd6 100644 --- a/src/MultiTenant.AspNetCore.csproj +++ b/src/MultiTenant.AspNetCore.csproj @@ -1,7 +1,7 @@  - net8.0 + net6.0 enable enable MultiTenant.AspNetCore diff --git a/src/Registration/TenantBuilder.cs b/src/Registration/TenantBuilder.cs index ec14084..8c8afbd 100644 --- a/src/Registration/TenantBuilder.cs +++ b/src/Registration/TenantBuilder.cs @@ -15,8 +15,16 @@ namespace MultiTenant.AspNetCore.Builder /// Tenant builder /// /// - public class TenantBuilder(IServiceCollection Services, MultiTenantOptions options) where T : ITenantInfo + public class TenantBuilder where T : ITenantInfo { + private readonly IServiceCollection services; + private readonly MultiTenantOptions options; + + public TenantBuilder(IServiceCollection Services, MultiTenantOptions options) + { + services = Services; + this.options = options; + } /// /// Register the tenant resolver implementation /// @@ -25,8 +33,8 @@ public class TenantBuilder(IServiceCollection Services, MultiTenantOptions /// public TenantBuilder WithResolutionStrategy() where V : class, ITenantResolutionStrategy { - Services.TryAddSingleton(); - Services.TryAddSingleton(typeof(ITenantResolutionStrategy), typeof(V)); + services.TryAddSingleton(); + services.TryAddSingleton(typeof(ITenantResolutionStrategy), typeof(V)); return this; } @@ -47,7 +55,7 @@ public TenantBuilder WithHostResolutionStrategy() /// public TenantBuilder WithTenantLookupService() where V : class, ITenantLookupService { - Services.TryAddSingleton, V>(); + services.TryAddSingleton, V>(); return this; } @@ -60,7 +68,7 @@ public TenantBuilder WithTenantLookupService() where V : class, ITenantLoo public TenantBuilder WithInMemoryTenantLookupService(IEnumerable tenants) { var service = new InMemoryLookupService(tenants); - Services.TryAddSingleton>(service); + services.TryAddSingleton>(service); return this; } @@ -74,11 +82,11 @@ public TenantBuilder WithTenantedServices(Action conf { //Replace the default service provider with a multitenant service provider if (!options.DisableAutomaticPipelineRegistration) - Services.Insert(0, ServiceDescriptor.Transient(provider => new MultitenantRequestServicesStartupFilter())); + services.Insert(0, ServiceDescriptor.Transient(provider => new MultitenantRequestServicesStartupFilter())); //Register the multi-tenant service provider - Services.AddSingleton>(); - Services.AddSingleton(new MultiTenantServiceProviderFactory(Services, configuration)); + services.AddSingleton>(); + services.AddSingleton(new MultiTenantServiceProviderFactory(services, configuration)); return this; } @@ -91,19 +99,19 @@ public TenantBuilder WithTenantedServices(Action conf /// public TenantBuilder WithTenantedConfigure(Action tenantOptionsConfiguration) where TOptions : class { - Services.AddOptions(); + services.AddOptions(); - Services.TryAddSingleton, MultiTenantOptionsCache>(); - Services.TryAddScoped>((sp) => + services.TryAddSingleton, MultiTenantOptionsCache>(); + services.TryAddScoped>((sp) => { return new MultiTenantOptionsManager(sp.GetRequiredService>(), sp.GetRequiredService>()); }); - Services.TryAddSingleton>((sp) => + services.TryAddSingleton>((sp) => { return new MultiTenantOptionsManager(sp.GetRequiredService>(), sp.GetRequiredService>()); }); - Services.AddSingleton, ConfigureOptions>((IServiceProvider sp) => + services.AddSingleton, ConfigureOptions>((IServiceProvider sp) => { var tenantAccessor = sp.GetRequiredService>(); return new ConfigureOptions((options) => tenantOptionsConfiguration(options, tenantAccessor.TenantInfo)); diff --git a/src/Services/AsyncLocalMultiTenantContextAccessor.cs b/src/Services/AsyncLocalMultiTenantContextAccessor.cs index b2c465e..418d6e0 100644 --- a/src/Services/AsyncLocalMultiTenantContextAccessor.cs +++ b/src/Services/AsyncLocalMultiTenantContextAccessor.cs @@ -41,7 +41,7 @@ public W? TenantInfo /// /// https://github.com/aspnet/HttpAbstractions/pull/1066 /// - private class TenantInfoHolder() + private class TenantInfoHolder { public W? Context; } diff --git a/src/Services/InMemoryLookupService.cs b/src/Services/InMemoryLookupService.cs index b464660..1be0e5e 100644 --- a/src/Services/InMemoryLookupService.cs +++ b/src/Services/InMemoryLookupService.cs @@ -1,10 +1,16 @@ namespace MultiTenant.AspNetCore.Services { - internal class InMemoryLookupService(IEnumerable Tenants) : ITenantLookupService where T : ITenantInfo + internal class InMemoryLookupService : ITenantLookupService where T : ITenantInfo { + private readonly IEnumerable tenants; + + public InMemoryLookupService(IEnumerable Tenants) + { + tenants = Tenants; + } public Task GetTenantAsync(string identifier) { - return Task.FromResult(Tenants.SingleOrDefault(t => t.Identifier == identifier)); + return Task.FromResult(tenants.SingleOrDefault(t => t.Identifier == identifier)); } } } diff --git a/test/MultiTenant.AspNetCore.Tests.csproj b/test/MultiTenant.AspNetCore.Tests.csproj index adec023..3271eda 100644 --- a/test/MultiTenant.AspNetCore.Tests.csproj +++ b/test/MultiTenant.AspNetCore.Tests.csproj @@ -1,7 +1,7 @@ - + - net8.0 + net6.0 enable enable @@ -10,7 +10,7 @@ - + diff --git a/test/TestTenant.cs b/test/TestTenant.cs index dea832b..db5b3c4 100644 --- a/test/TestTenant.cs +++ b/test/TestTenant.cs @@ -1,8 +1,12 @@ -namespace MultiTenant.AspNetCore.Tests +using System.ComponentModel.DataAnnotations; + +namespace MultiTenant.AspNetCore.Tests { internal class TestTenant : ITenantInfo { - public required string Id { get; set; } - public required string Identifier { get; set; } + [Required] + public string Id { get; set; } + [Required] + public string Identifier { get; set; } } }