Skip to content

Commit

Permalink
Use Stashbox 5.17.0 / Add service collection configuration option to …
Browse files Browse the repository at this point in the history
…multitenant
  • Loading branch information
z4kn4fein committed Dec 20, 2024
1 parent 4f373f3 commit ce6bbdb
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 13 deletions.
25 changes: 16 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,19 +92,27 @@ var builder = WebApplication.CreateBuilder(args);
builder.Host.UseStashboxMultitenant<HttpHeaderTenantIdExtractor>(
options => // Multi-tenant configuration options.
{
// Root container configuration through the IStashboxContainer interface.
options.RootContainer.Configure(opts => { /* configure the root container */ });

// The default service registration, it registers into the root container.
// It also could be registered into the default
// service collection with the ConfigureServices() API.
options.RootContainer.Register<IDependency, DefaultDependency>();
options.ConfigureRootServices(services =>
services.AddTransient<IDependency, DefaultDependency>());

// Configure tenants.
options.ConfigureTenant("TenantA", tenant =>
// Register tenant specific service override
tenant.Register<IDependency, TenantASpecificDependency>());

options.ConfigureTenant("TenantB", tenant =>
// Register tenant specific service override
tenant.Register<IDependency, TenantBSpecificDependency>());
options.ConfigureTenant("TenantA", tenantContainer =>
tenantContainer.Configure(opts => { /* configure the tenant container */ }))
// Register tenant specific service overrides
.ConfigureServices(services =>
services.AddTransient<IDependency, TenantASpecificDependency>());

options.ConfigureTenant("TenantB", tenantContainer =>
tenantContainer.Configure(opts => { /* configure the tenant container */ }))
// Register tenant specific service overrides
.ConfigureServices(services =>
services.AddTransient<IDependency, TenantBSpecificDependency>());
});

// The container parameter is the tenant distributor itself.
Expand All @@ -117,7 +125,6 @@ builder.Host.ConfigureContainer<IStashboxContainer>((context, container) =>
});
```


With this example setup, you can differentiate tenants in a per-request basis identified by a HTTP header, where every tenant gets their overridden services.

### Testing
Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
environment:
build_version: 5.6.0
build_version: 5.7.0

version: $(build_version)-{build}

Expand Down
40 changes: 38 additions & 2 deletions src/stashbox.aspnetcore.multitenant/StashboxMultitenantOptions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Microsoft.Extensions.DependencyInjection;

namespace Stashbox.AspNetCore.Multitenant;

Expand Down Expand Up @@ -27,8 +28,43 @@ public StashboxMultitenantOptions(IStashboxContainer rootContainer)
/// <param name="tenantId">The identifier of the tenant.</param>
/// <param name="tenantConfig">The service configuration of the tenant.</param>
/// <param name="attachTenantToRoot">If true, the new tenant will be attached to the lifecycle of the root container. When the root is being disposed, the tenant will be disposed with it.</param>
public void ConfigureTenant(object tenantId, Action<IStashboxContainer> tenantConfig, bool attachTenantToRoot = true)
/// <returns>A service configurator used to configure services for the tenant.</returns>
public ITenantServiceConfigurator ConfigureTenant(object tenantId, Action<IStashboxContainer>? tenantConfig = null, bool attachTenantToRoot = true)
{
this.RootContainer.CreateChildContainer(tenantId, tenantConfig, attachTenantToRoot);
var child = this.RootContainer.CreateChildContainer(tenantId, tenantConfig, attachTenantToRoot);
return new TenantServiceConfigurator(child);
}

/// <summary>
/// Registers services into the root container from a <see cref="IServiceCollection"/>
/// </summary>
/// <param name="configuration">The configuration delegate.</param>
public void ConfigureRootServices(Action<IServiceCollection> configuration)
{
var collection = new ServiceCollection();
configuration(collection);
this.RootContainer.RegisterServiceDescriptors(collection);
}
}

/// <summary>
/// Describes a utility class to register services into a tenant container from a <see cref="IServiceCollection"/>.
/// </summary>
public interface ITenantServiceConfigurator
{
/// <summary>
/// Registers services into the tenant from a <see cref="IServiceCollection"/>
/// </summary>
/// <param name="configuration">The configuration delegate.</param>
void ConfigureServices(Action<IServiceCollection> configuration);
}

internal class TenantServiceConfigurator(IStashboxContainer tenantContainer) : ITenantServiceConfigurator
{
public void ConfigureServices(Action<IServiceCollection> configuration)
{
var collection = new ServiceCollection();
configuration(collection);
tenantContainer.RegisterServiceDescriptors(collection);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="Stashbox" Version="5.16.0" />
<PackageReference Include="Stashbox" Version="5.17.0" />
</ItemGroup>

<ItemGroup>
Expand Down
50 changes: 50 additions & 0 deletions test/stashbox.aspnetcore.multitenant.tests/MultitenantTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Stashbox.Resolution;
using Xunit;

namespace Stashbox.AspNetCore.Multitenant.Tests;
Expand Down Expand Up @@ -103,6 +106,30 @@ public async Task MultitenantTests_Works_With_ScopeFactory()
Assert.True(configureCalled);
Assert.True(TestStartup.ConfigureCalled);
}

[Fact]
public async Task MultitenantTests_OptionsFactory_Works()
{
using var host = await new HostBuilder()
.UseStashboxMultitenant<TestTenantIdExtractor>(c =>
{
c.ConfigureRootServices(services => services.Configure<TestOption>(opt => opt.Prop = "FromRoot"));
c.ConfigureTenant("A")
.ConfigureServices(services => services.Configure<TestOption>(opt => opt.Prop = "FromA"));
})
.ConfigureWebHost(builder =>
{
builder
.UseTestServer()
.UseStartup<TestStartup>();
})
.StartAsync();

var client = host.GetTestClient();

await this.AssertResult(client,"api/test3/value", "NONEXISTING", "FromRoot");
await this.AssertResult(client,"api/test3/value", "A", "FromA");
}

private async Task AssertResult(HttpClient client, string route, string tenantId, string expectedResult)
{
Expand Down Expand Up @@ -149,6 +176,24 @@ public string GetValue()
}
}

[Route("api/test3")]
public class Test3Controller : ControllerBase
{
private readonly IOptionsMonitor<TestOption> optionsMonitor;

public Test3Controller(IOptionsMonitor<TestOption> optionsMonitor)
{
this.optionsMonitor = optionsMonitor;
}

[HttpGet("value")]
public string GetValue()
{
var option = this.optionsMonitor.Get(null);
return option.Prop;
}
}

public class TestTenantIdExtractor : ITenantIdExtractor
{
public const string TENANT_HEADER = "TENANT-ID";
Expand Down Expand Up @@ -225,4 +270,9 @@ public ValueTask DisposeAsync()

return new ValueTask(Task.CompletedTask);
}
}

public class TestOption
{
public string Prop { get; set; }
}

0 comments on commit ce6bbdb

Please sign in to comment.