diff --git a/src/Ayaka.MultiTenancy.Abstractions/Ayaka.MultiTenancy.Abstractions.csproj b/src/Ayaka.MultiTenancy.Abstractions/Ayaka.MultiTenancy.Abstractions.csproj index f21c75b..3d9eeed 100644 --- a/src/Ayaka.MultiTenancy.Abstractions/Ayaka.MultiTenancy.Abstractions.csproj +++ b/src/Ayaka.MultiTenancy.Abstractions/Ayaka.MultiTenancy.Abstractions.csproj @@ -14,4 +14,8 @@ <PackageTags>$(PackageCommonTags);multi-tenancy</PackageTags> </PropertyGroup> + <ItemGroup> + <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" /> + </ItemGroup> + </Project> diff --git a/src/Ayaka.MultiTenancy.Abstractions/DependencyInjection/IMultiTenancyBuilder.cs b/src/Ayaka.MultiTenancy.Abstractions/DependencyInjection/IMultiTenancyBuilder.cs new file mode 100644 index 0000000..2ae72a8 --- /dev/null +++ b/src/Ayaka.MultiTenancy.Abstractions/DependencyInjection/IMultiTenancyBuilder.cs @@ -0,0 +1,16 @@ +// Copyright (c) Raphael Strotz. All rights reserved. + +namespace Ayaka.MultiTenancy.DependencyInjection; + +using Microsoft.Extensions.DependencyInjection; + +/// <summary> +/// Provides functionality to configure multi-tenancy services. +/// </summary> +public interface IMultiTenancyBuilder +{ + /// <summary> + /// Gets the <see cref="IServiceCollection"/> where multi-tenancy services are configured. + /// </summary> + IServiceCollection Services { get; } +} diff --git a/src/Ayaka.MultiTenancy.Abstractions/PublicAPI.Unshipped.txt b/src/Ayaka.MultiTenancy.Abstractions/PublicAPI.Unshipped.txt index 1f7914b..117efb3 100644 --- a/src/Ayaka.MultiTenancy.Abstractions/PublicAPI.Unshipped.txt +++ b/src/Ayaka.MultiTenancy.Abstractions/PublicAPI.Unshipped.txt @@ -1,4 +1,6 @@ #nullable enable +Ayaka.MultiTenancy.DependencyInjection.IMultiTenancyBuilder +Ayaka.MultiTenancy.DependencyInjection.IMultiTenancyBuilder.Services.get -> Microsoft.Extensions.DependencyInjection.IServiceCollection! Ayaka.MultiTenancy.ITenantContextAccessor Ayaka.MultiTenancy.ITenantContextAccessor.TenantContext.get -> Ayaka.MultiTenancy.TenantContext? Ayaka.MultiTenancy.ITenantContextAccessor.TenantContext.set -> void diff --git a/src/Ayaka.MultiTenancy/Ayaka.MultiTenancy.csproj b/src/Ayaka.MultiTenancy/Ayaka.MultiTenancy.csproj index fc65be6..d885c69 100644 --- a/src/Ayaka.MultiTenancy/Ayaka.MultiTenancy.csproj +++ b/src/Ayaka.MultiTenancy/Ayaka.MultiTenancy.csproj @@ -4,15 +4,15 @@ <TargetFramework>$(DotNetTargetFramework)</TargetFramework> </PropertyGroup> + <ItemGroup> + <InternalsVisibleTo Include="Ayaka.MultiTenancy.Tests" /> + </ItemGroup> + <PropertyGroup> <PackageDescription>Provides functionality for creating multi-tenanted applications. $(PackageDescriptionAppendix)</PackageDescription> <PackageTags>$(PackageCommonTags);multi-tenancy</PackageTags> </PropertyGroup> - <ItemGroup> - <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="..\Ayaka.MultiTenancy.Abstractions\Ayaka.MultiTenancy.Abstractions.csproj" /> </ItemGroup> diff --git a/src/Ayaka.MultiTenancy/DependencyInjection/MultiTenancyBuilder.cs b/src/Ayaka.MultiTenancy/DependencyInjection/MultiTenancyBuilder.cs new file mode 100644 index 0000000..56dd52d --- /dev/null +++ b/src/Ayaka.MultiTenancy/DependencyInjection/MultiTenancyBuilder.cs @@ -0,0 +1,23 @@ +// Copyright (c) Raphael Strotz. All rights reserved. + +namespace Ayaka.MultiTenancy.DependencyInjection; + +using Microsoft.Extensions.DependencyInjection; + +/// <summary> +/// Allows configuration of multi-tenancy services. +/// </summary> +internal sealed class MultiTenancyBuilder : IMultiTenancyBuilder +{ + /// <summary> + /// Initializes a new instance of the <see cref="MultiTenancyBuilder"/> class. + /// </summary> + /// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param> + public MultiTenancyBuilder(IServiceCollection services) + { + Services = services; + } + + /// <inheritdoc /> + public IServiceCollection Services { get; } +} diff --git a/src/Ayaka.MultiTenancy/PublicAPI.Unshipped.txt b/src/Ayaka.MultiTenancy/PublicAPI.Unshipped.txt index 189070d..fc674dd 100644 --- a/src/Ayaka.MultiTenancy/PublicAPI.Unshipped.txt +++ b/src/Ayaka.MultiTenancy/PublicAPI.Unshipped.txt @@ -4,4 +4,5 @@ Ayaka.MultiTenancy.AsyncLocalTenantContextAccessor.AsyncLocalTenantContextAccess Ayaka.MultiTenancy.AsyncLocalTenantContextAccessor.TenantContext.get -> Ayaka.MultiTenancy.TenantContext? Ayaka.MultiTenancy.AsyncLocalTenantContextAccessor.TenantContext.set -> void Ayaka.MultiTenancy.ServiceCollectionExtensions +static Ayaka.MultiTenancy.ServiceCollectionExtensions.AddMultiTenancy(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Ayaka.MultiTenancy.DependencyInjection.IMultiTenancyBuilder! static Ayaka.MultiTenancy.ServiceCollectionExtensions.AddTenantContextAccessor(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! diff --git a/src/Ayaka.MultiTenancy/ServiceCollectionExtensions.cs b/src/Ayaka.MultiTenancy/ServiceCollectionExtensions.cs index 16ac808..a194ac9 100644 --- a/src/Ayaka.MultiTenancy/ServiceCollectionExtensions.cs +++ b/src/Ayaka.MultiTenancy/ServiceCollectionExtensions.cs @@ -2,6 +2,7 @@ namespace Ayaka.MultiTenancy; +using Ayaka.MultiTenancy.DependencyInjection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -10,6 +11,22 @@ namespace Ayaka.MultiTenancy; /// </summary> public static class ServiceCollectionExtensions { + /// <summary> + /// Adds multi-tenancy services to the specified <see cref="IServiceCollection"/>. + /// </summary> + /// <remarks> + /// In order to fine-tune the multi-tenancy configuration, use the <see cref="IMultiTenancyBuilder"/> returned by + /// this method. + /// </remarks> + /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param> + /// <returns>A <see cref="IMultiTenancyBuilder"/> that can be used to further configure multi-tenancy.</returns> + public static IMultiTenancyBuilder AddMultiTenancy(this IServiceCollection services) + { + ConfigureDefaultServices(services); + + return new MultiTenancyBuilder(services); + } + /// <summary> /// Adds the default implementation for the <see cref="ITenantContextAccessor"/> service. /// </summary> @@ -20,4 +37,11 @@ public static IServiceCollection AddTenantContextAccessor(this IServiceCollectio services.TryAddSingleton<ITenantContextAccessor, AsyncLocalTenantContextAccessor>(); return services; } + + [SuppressMessage("Style", "IDE0058:Expression value is never used")] + private static void ConfigureDefaultServices(this IServiceCollection services) + { + // The heart of multi-tenancy + services.AddTenantContextAccessor(); + } } diff --git a/test/Ayaka.MultiTenancy.Tests/ServiceCollectionExtensionsTest.cs b/test/Ayaka.MultiTenancy.Tests/ServiceCollectionExtensionsTest.cs index 72d694c..8b73e1a 100644 --- a/test/Ayaka.MultiTenancy.Tests/ServiceCollectionExtensionsTest.cs +++ b/test/Ayaka.MultiTenancy.Tests/ServiceCollectionExtensionsTest.cs @@ -2,10 +2,57 @@ namespace Ayaka.MultiTenancy.Tests; +using Ayaka.MultiTenancy.DependencyInjection; using Microsoft.Extensions.DependencyInjection; public sealed class ServiceCollectionExtensionsTest { + public sealed class AddMultiTenancy + { + [Fact] + public void Does_return_instance_of_MultiTenancyBuilder() + { + var services = new ServiceCollection(); + + var builder = services.AddMultiTenancy(); + + builder.Should().NotBeNull(); + builder.Should().BeOfType<MultiTenancyBuilder>(); + } + + [Fact] + public void Does_use_specified_service_collection() + { + var services = new ServiceCollection(); + + var builder = services.AddMultiTenancy(); + + builder.Services.Should().BeSameAs(services); + } + + [Fact] + public void Does_add_default_services() + { + var services = new ServiceCollection(); + + services.AddMultiTenancy(); + + var accessor = services.FirstOrDefault(x => x.ServiceType == typeof(ITenantContextAccessor)); + accessor.Should().NotBeNull("ITenantContextAccessor should be registered"); + } + + [Fact] + public void Does_not_add_default_services_twice() + { + var services = new ServiceCollection(); + + services.AddMultiTenancy(); + services.AddMultiTenancy(); + + services.Count(x => x.ServiceType == typeof(ITenantContextAccessor)).Should().Be(1); + } + } + public sealed class AddTenantContextAccessor { [Fact]