Browse Source

DefaultTenantResolveContributor instead of ConfigurationTenantResolveContributor

pull/23162/head
Suhaib Mousa 1 year ago
parent
commit
58cbd450ed
  1. 36
      docs/en/framework/architecture/multi-tenancy/index.md
  2. 1
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs
  3. 6
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyOptions.cs
  4. 24
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/DefaultTenantResolveContributor.cs
  5. 27
      framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ConfigurationTenantResolveContributor.cs
  6. 17
      framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ConfigurationTenantResolveOptionsExtensions.cs
  7. 20
      framework/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/MultiTenancy/AspNetCoreMultiTenancy_Without_DomainResolver_Tests.cs
  8. 94
      framework/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/ConfigurationTenantResolveContributor_Tests.cs

36
docs/en/framework/architecture/multi-tenancy/index.md

@ -206,6 +206,7 @@ The following resolvers are provided and configured by default;
- `RouteTenantResolveContributor`: Tries to find current tenant id from route (URL path). The variable name is `__tenant` by default. If you defined a route with this variable, then it can determine the current tenant from the route.
- `HeaderTenantResolveContributor`: Tries to find current tenant id from HTTP headers. The header name is `__tenant` by default.
- `CookieTenantResolveContributor`: Tries to find current tenant id from cookie values. The cookie name is `__tenant` by default.
- `DefaultTenantResolveContributor`: Resolves a fallback tenant from configuration if none of the above resolvers succeed. This resolver is automatically registered and runs last by default. It should be configured via `AbpAspNetCoreMultiTenancyOptions.DefaultTenant`, as described in the `Default Tenant Resolver` section below.
###### Problems with the NGINX
@ -346,41 +347,34 @@ context.Services
```
##### ConfigurationTenantResolveContributor
##### Default Tenant Resolver
This tenant resolver reads the tenant identifier from application configuration `appsettings.json`.
In some cases, especially in **development environments**, resolving a tenant based on domain may not be practical (e.g., due to use of `localhost`). In such cases, a default fallback tenant can be configured using `AbpAspNetCoreMultiTenancyOptions.DefaultTenant`.
It is intended primarily for **development or testing environments** where setting the tenant manually is useful (e.g., without headers, route parameters, or query strings).
This fallback is resolved by the `DefaultTenantResolveContributor`, which attempts to set a default tenant if none of the other resolvers succeed. This contributor is automatically added at the **end** of the tenant resolver list.
###### **Configuration Example:**
###### Configuration
Set the default tenant value via code or `appsettings.json`:
```json
{
"MultiTenancy": {
"Tenant": "my-tenant-name"
}
"MultiTenancy": {
"DefaultTenant": "acme" // can be tenant name or ID
}
}
```
The value can be either the **tenant name** or the **tenant ID**.
###### **How to Use:**
This resolver is not registered by default. You can add it manually in your module's `ConfigureServices`:
**Startup Configuration:**
```csharp
var env = context.Services.GetHostingEnvironment();
if (env.IsDevelopment()) // Optional but preferred: only register in development
Configure<AbpAspNetCoreMultiTenancyOptions>(options =>
{
Configure<AbpTenantResolveOptions>(options =>
{
options.AddConfigurationTenantResolver();
});
}
options.DefaultTenant = configuration["MultiTenancy:DefaultTenant"];
});
```
> Recommended to limit this resolver to development to avoid static tenant resolution in production.
> The `DefaultTenantResolveContributor` must be configured via options as shown above. It is included by default and is evaluated only if all other resolvers fail.
##### Custom Tenant Resolvers

1
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs

@ -17,6 +17,7 @@ public class AbpAspNetCoreMultiTenancyModule : AbpModule
options.TenantResolvers.Add(new RouteTenantResolveContributor());
options.TenantResolvers.Add(new HeaderTenantResolveContributor());
options.TenantResolvers.Add(new CookieTenantResolveContributor());
options.TenantResolvers.Add(new DefaultTenantResolveContributor());
});
}
}

6
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyOptions.cs

@ -26,6 +26,12 @@ namespace Volo.Abp.AspNetCore.MultiTenancy;
public class AbpAspNetCoreMultiTenancyOptions
{
/// <summary>
/// Used by <see cref="DefaultTenantResolveContributor"/> to resolve a fallback tenant
/// when no other tenant resolvers return a value.
/// </summary>
public string? DefaultTenant { get; set; }
/// <summary>
/// Default: <see cref="TenantResolverConsts.DefaultTenantKey"/>.
/// </summary>

24
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/DefaultTenantResolveContributor.cs

@ -0,0 +1,24 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.AspNetCore.MultiTenancy;
public class DefaultTenantResolveContributor : TenantResolveContributorBase
{
public const string ContributorName = "Default";
public override string Name => ContributorName;
public override Task ResolveAsync(ITenantResolveContext context)
{
var defaultTenant = context.GetAbpAspNetCoreMultiTenancyOptions().DefaultTenant;
if (!string.IsNullOrWhiteSpace(defaultTenant))
{
context.TenantIdOrName = defaultTenant;
context.Handled = true;
}
return Task.CompletedTask;
}
}

27
framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ConfigurationTenantResolveContributor.cs

@ -1,27 +0,0 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Threading.Tasks;
using Volo.Abp;
namespace Volo.Abp.MultiTenancy;
public class ConfigurationTenantResolveContributor : TenantResolveContributorBase
{
public const string ContributorName = "Configuration";
public override string Name => ContributorName;
public override async Task ResolveAsync(ITenantResolveContext context)
{
var configuration = context.ServiceProvider.GetRequiredService<IConfiguration>();
var tenantIdOrName = configuration["MultiTenancy:Tenant"];
if (!tenantIdOrName.IsNullOrEmpty())
{
context.TenantIdOrName = tenantIdOrName;
}
await Task.CompletedTask;
}
}

17
framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ConfigurationTenantResolveOptionsExtensions.cs

@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Volo.Abp.MultiTenancy;
public static class ConfigurationTenantResolveOptionsExtensions
{
public static void AddConfigurationTenantResolver(
this AbpTenantResolveOptions options)
{
options.TenantResolvers.InsertAfter(
r => r is CurrentUserTenantResolveContributor,
new ConfigurationTenantResolveContributor()
);
}
}

20
framework/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/MultiTenancy/AspNetCoreMultiTenancy_Without_DomainResolver_Tests.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@ -69,4 +70,23 @@ public class AspNetCoreMultiTenancy_Without_DomainResolver_Tests : AspNetCoreMul
var result = await GetResponseAsObjectAsync<Dictionary<string, string>>("http://abp.io");
result["TenantId"].ShouldBe(_testTenantId.ToString());
}
[Fact]
public async Task Should_Use_DefaultTenant_If_No_Other_Resolvers_Succeed()
{
_options.DefaultTenant = _testTenantName;
var result = await GetResponseAsObjectAsync<Dictionary<string, string>>("http://abp.io");
result["TenantId"].ShouldBe(_testTenantId.ToString());
}
[Fact]
public async Task Should_Return_404_If_DefaultTenant_Is_Invalid()
{
_options.DefaultTenant = "non-existent-tenant";
// This method asserts the status code internally using ShouldBe(...)
await GetResponseAsync("http://abp.io", HttpStatusCode.NotFound);
}
}

94
framework/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/ConfigurationTenantResolveContributor_Tests.cs

@ -1,94 +0,0 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using System.Collections.Generic;
using System.Threading.Tasks;
using System;
using Volo.Abp.MultiTenancy;
using Xunit;
using Shouldly;
public class ConfigurationTenantResolveContributor_Tests : MultiTenancyTestBase
{
[Fact]
public async Task Should_Resolve_Tenant_From_Configuration()
{
var context = CreateContext("acme");
var contributor = new ConfigurationTenantResolveContributor();
await contributor.ResolveAsync(context);
context.TenantIdOrName.ShouldBe("acme");
}
[Fact]
public async Task Should_Not_Resolve_If_Configuration_Is_Missing()
{
var context = CreateContext(null); // No tenant set
var contributor = new ConfigurationTenantResolveContributor();
await contributor.ResolveAsync(context);
context.TenantIdOrName.ShouldBeNull();
}
// Reusable setup
private static ITenantResolveContext CreateContext(string? tenantName)
{
var services = new ServiceCollection();
services.AddSingleton<IHostEnvironment>(new FakeHostEnvironment());
var configBuilder = new ConfigurationBuilder();
if (!string.IsNullOrWhiteSpace(tenantName))
{
configBuilder.AddInMemoryCollection(
[
new KeyValuePair<string, string>("MultiTenancy:Tenant", tenantName)
]);
}
services.AddSingleton<IConfiguration>(configBuilder.Build());
var provider = services.BuildServiceProvider();
return new FakeTenantResolveContext(provider);
}
// Fake context
private class FakeTenantResolveContext : ITenantResolveContext
{
public IServiceProvider ServiceProvider { get; }
public string? TenantIdOrName { get; set; }
public bool Handled { get; set; }
public FakeTenantResolveContext(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
public bool HasResolvedTenantOrHost()
{
return Handled || !string.IsNullOrWhiteSpace(TenantIdOrName);
}
}
private class FakeHostEnvironment : IHostEnvironment
{
public string EnvironmentName { get; set; } = Environments.Development;
public string ApplicationName { get; set; } = "TestApp";
public string ContentRootPath { get; set; } = "";
public IFileProvider ContentRootFileProvider { get; set; } = new NullFileProvider();
}
private class NullFileProvider : IFileProvider
{
public IDirectoryContents GetDirectoryContents(string subpath) => new NotFoundDirectoryContents();
public IFileInfo GetFileInfo(string subpath) => new NotFoundFileInfo(subpath);
public Microsoft.Extensions.Primitives.IChangeToken Watch(string filter) => NullChangeToken.Singleton;
}
}
Loading…
Cancel
Save