Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add HasResolvedTenant to IMultiTenantContext #650

Merged
merged 1 commit into from
Mar 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/Finbuckle.MultiTenant/Abstractions/IMultiTenantContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ namespace Finbuckle.MultiTenant
public interface IMultiTenantContext
{
ITenantInfo? TenantInfo { get; }
bool HasResolvedTenant => TenantInfo != null;

StrategyInfo? StrategyInfo { get; }
}

public interface IMultiTenantContext<T>
where T : class, ITenantInfo, new()
{
T? TenantInfo { get; set; }
bool HasResolvedTenant => TenantInfo != null;

StrategyInfo? StrategyInfo { get; set; }
StoreInfo<T>? StoreInfo { get; set; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ public FinbuckleMultiTenantBuilder(IServiceCollection services)
return this;
}

// TODO consider per tenant AllOptions variation

private static MultiTenantOptionsManager<TOptions> BuildOptionsManager<TOptions>(IServiceProvider sp)
where TOptions : class, new()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public static FinbuckleMultiTenantBuilder<T> AddMultiTenant<T>(this IServiceColl
services.AddScoped<T>(sp => sp.GetRequiredService<IMultiTenantContextAccessor<T>>().MultiTenantContext?.TenantInfo!);
services.AddScoped<ITenantInfo>(sp => sp.GetService<T>()!);

services.AddSingleton<IMultiTenantContextAccessor<T>, MultiTenantContextAccessor<T>>();
services.AddSingleton<IMultiTenantContextAccessor<T>, AsyncLocalMultiTenantContextAccessor<T>>();
services.AddSingleton<IMultiTenantContextAccessor>(sp => (IMultiTenantContextAccessor)sp.GetRequiredService<IMultiTenantContextAccessor<T>>());

services.Configure<MultiTenantOptions>(config);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,16 @@

namespace Finbuckle.MultiTenant.Core
{
public class MultiTenantContextAccessor<T> : IMultiTenantContextAccessor<T>, IMultiTenantContextAccessor
public class AsyncLocalMultiTenantContextAccessor<T> : IMultiTenantContextAccessor<T>, IMultiTenantContextAccessor
where T : class, ITenantInfo, new()
{
internal static AsyncLocal<IMultiTenantContext<T>?> _asyncLocalContext = new AsyncLocal<IMultiTenantContext<T>?>();
private static readonly AsyncLocal<IMultiTenantContext<T>?> _asyncLocalContext = new();

public IMultiTenantContext<T>? MultiTenantContext
{
get
{
return _asyncLocalContext.Value;
}
get => _asyncLocalContext.Value;

set
{
_asyncLocalContext.Value = value;
}
set => _asyncLocalContext.Value = value;
}

IMultiTenantContext? IMultiTenantContextAccessor.MultiTenantContext
Expand Down
1 change: 1 addition & 0 deletions src/Finbuckle.MultiTenant/MultiTenantContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class MultiTenantContext<T> : IMultiTenantContext<T>, IMultiTenantContext
where T : class, ITenantInfo, new()
{
public T? TenantInfo { get; set; }

public StrategyInfo? StrategyInfo { get; set; }
public StoreInfo<T>? StoreInfo { get; set; }

Expand Down
41 changes: 27 additions & 14 deletions src/Finbuckle.MultiTenant/Options/MultiTenantOptionsFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,30 @@ public class MultiTenantOptionsFactory<TOptions, TTenantInfo> : IOptionsFactory<
/// <summary>
/// Initializes a new instance with the specified options configurations.
/// </summary>
public MultiTenantOptionsFactory(IEnumerable<IConfigureOptions<TOptions>> configureOptions, IEnumerable<IPostConfigureOptions<TOptions>> postConfigureOptions, IEnumerable<IValidateOptions<TOptions>> validations, IEnumerable<ITenantConfigureOptions<TOptions, TTenantInfo>> tenantConfigureOptions, IEnumerable<ITenantConfigureNamedOptions<TOptions, TTenantInfo>> tenantConfigureNamedOptions, IMultiTenantContextAccessor<TTenantInfo> multiTenantContextAccessor)
public MultiTenantOptionsFactory(IEnumerable<IConfigureOptions<TOptions>> configureOptions,
IEnumerable<IPostConfigureOptions<TOptions>> postConfigureOptions,
IEnumerable<IValidateOptions<TOptions>> validations,
IEnumerable<ITenantConfigureOptions<TOptions, TTenantInfo>> tenantConfigureOptions,
IEnumerable<ITenantConfigureNamedOptions<TOptions, TTenantInfo>> tenantConfigureNamedOptions,
IMultiTenantContextAccessor<TTenantInfo> multiTenantContextAccessor)
{
// The default DI container uses arrays under the covers. Take advantage of this knowledge
// by checking for an array and enumerate over that, so we don't need to allocate an enumerator.
// When it isn't already an array, convert it to one, but don't use System.Linq to avoid pulling Linq in to
// small trimmed applications.

_configureOptions = configureOptions as IConfigureOptions<TOptions>[] ?? new List<IConfigureOptions<TOptions>>(configureOptions).ToArray();
_postConfigureOptions = postConfigureOptions as IPostConfigureOptions<TOptions>[] ?? new List<IPostConfigureOptions<TOptions>>(postConfigureOptions).ToArray();
_validations = validations as IValidateOptions<TOptions>[] ?? new List<IValidateOptions<TOptions>>(validations).ToArray();
_tenantConfigureOptions = tenantConfigureOptions as ITenantConfigureOptions<TOptions, TTenantInfo>[] ?? new List<ITenantConfigureOptions<TOptions, TTenantInfo>>(tenantConfigureOptions).ToArray();
_tenantConfigureNamedOptions = tenantConfigureNamedOptions as ITenantConfigureNamedOptions<TOptions, TTenantInfo>[] ?? new List<ITenantConfigureNamedOptions<TOptions, TTenantInfo>>(tenantConfigureNamedOptions).ToArray();
_configureOptions = configureOptions as IConfigureOptions<TOptions>[] ??
new List<IConfigureOptions<TOptions>>(configureOptions).ToArray();
_postConfigureOptions = postConfigureOptions as IPostConfigureOptions<TOptions>[] ??
new List<IPostConfigureOptions<TOptions>>(postConfigureOptions).ToArray();
_validations = validations as IValidateOptions<TOptions>[] ??
new List<IValidateOptions<TOptions>>(validations).ToArray();
_tenantConfigureOptions = tenantConfigureOptions as ITenantConfigureOptions<TOptions, TTenantInfo>[] ??
new List<ITenantConfigureOptions<TOptions, TTenantInfo>>(tenantConfigureOptions)
.ToArray();
_tenantConfigureNamedOptions =
tenantConfigureNamedOptions as ITenantConfigureNamedOptions<TOptions, TTenantInfo>[] ??
new List<ITenantConfigureNamedOptions<TOptions, TTenantInfo>>(tenantConfigureNamedOptions).ToArray();
_multiTenantContextAccessor = multiTenantContextAccessor;
}

Expand All @@ -60,23 +72,23 @@ public TOptions Create(string name)
}

// Configure tenant options.
if (_multiTenantContextAccessor?.MultiTenantContext?.TenantInfo != null)
if (_multiTenantContextAccessor?.MultiTenantContext?.HasResolvedTenant ?? false)
{
foreach (var tenantConfigureOption in _tenantConfigureOptions)
tenantConfigureOption.Configure(options, _multiTenantContextAccessor.MultiTenantContext.TenantInfo);
}
tenantConfigureOption.Configure(options, _multiTenantContextAccessor.MultiTenantContext.TenantInfo!);

// Configure tenant named options.
if (_multiTenantContextAccessor?.MultiTenantContext?.TenantInfo != null)
{
// Configure tenant named options.
foreach (var tenantConfigureNamedOption in _tenantConfigureNamedOptions)
tenantConfigureNamedOption.Configure(name, options, _multiTenantContextAccessor.MultiTenantContext.TenantInfo);
tenantConfigureNamedOption.Configure(name, options,
_multiTenantContextAccessor.MultiTenantContext.TenantInfo!);
}

foreach (var post in _postConfigureOptions)
{
post.PostConfigure(name, options);
}

// TODO consider per tenant post configure

if (_validations.Length > 0)
{
Expand All @@ -89,6 +101,7 @@ public TOptions Create(string name)
failures.AddRange(result.Failures);
}
}

if (failures.Count > 0)
{
throw new OptionsValidationException(name, typeof(TOptions), failures);
Expand All @@ -98,4 +111,4 @@ public TOptions Create(string name)
return options;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public void GetTenantContextIfExists()
tc.TenantInfo = ti;

var services = new ServiceCollection();
services.AddScoped<IMultiTenantContextAccessor<TenantInfo>>(_ => new MultiTenantContextAccessor<TenantInfo>{ MultiTenantContext = tc });
services.AddScoped<IMultiTenantContextAccessor<TenantInfo>>(_ => new AsyncLocalMultiTenantContextAccessor<TenantInfo>{ MultiTenantContext = tc });
var sp = services.BuildServiceProvider();

var httpContextMock = new Mock<HttpContext>();
Expand All @@ -35,7 +35,7 @@ public void GetTenantContextIfExists()
public void ReturnNullIfNoMultiTenantContext()
{
var services = new ServiceCollection();
services.AddScoped<IMultiTenantContextAccessor<TenantInfo>>(_ => new MultiTenantContextAccessor<TenantInfo>());
services.AddScoped<IMultiTenantContextAccessor<TenantInfo>>(_ => new AsyncLocalMultiTenantContextAccessor<TenantInfo>());
var sp = services.BuildServiceProvider();

var httpContextMock = new Mock<HttpContext>();
Expand Down
40 changes: 40 additions & 0 deletions test/Finbuckle.MultiTenant.Test/MultiTenantContextShould.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Xunit;

namespace Finbuckle.MultiTenant.Test;

public class MultiTenantContextShould
{
[Fact]
public void ReturnFalseInHasResolvedTenantIfTenantInfoIsNull()
{
IMultiTenantContext<TenantInfo> context = new MultiTenantContext<TenantInfo>();
Assert.False(context.HasResolvedTenant);
}

[Fact]
public void ReturnTrueInHasResolvedTenantIfTenantInfoIsNotNull()
{
IMultiTenantContext<TenantInfo> context = new MultiTenantContext<TenantInfo>();
context.TenantInfo = new TenantInfo();
Assert.True(context.HasResolvedTenant);
}

[Fact]
public void ReturnFalseInHasResolvedTenantIfTenantInfoIsNull_NonGeneric()
{
IMultiTenantContext context = new MultiTenantContext<TenantInfo>();
Assert.False(context.HasResolvedTenant);
}

[Fact]
public void ReturnTrueInHasResolvedTenantIfTenantInfoIsNotNull_NonGeneric()
{
var context = new MultiTenantContext<TenantInfo>
{
TenantInfo = new TenantInfo()
};

IMultiTenantContext iContext = context;
Assert.True(iContext.HasResolvedTenant);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void AddNamedOptionsForCurrentTenantOnlyOnAdd(string name)
var ti = new TenantInfo { Id = "test-id-123" };
var tc = new MultiTenantContext<TenantInfo>();
tc.TenantInfo = ti;
var tca = new MultiTenantContextAccessor<TenantInfo>();
var tca = new AsyncLocalMultiTenantContextAccessor<TenantInfo>();
tca.MultiTenantContext = tc;
var cache = new MultiTenantOptionsCache<TestOptions, TenantInfo>(tca);

Expand All @@ -45,7 +45,7 @@ public void AddNamedOptionsForCurrentTenantOnlyOnAdd(string name)
[Fact]
public void HandleNullMultiTenantContextOnAdd()
{
var tca = new MultiTenantContextAccessor<TenantInfo>();
var tca = new AsyncLocalMultiTenantContextAccessor<TenantInfo>();
var cache = new MultiTenantOptionsCache<TestOptions, TenantInfo>(tca);

var options = new TestOptions();
Expand All @@ -58,7 +58,7 @@ public void HandleNullMultiTenantContextOnAdd()
[Fact]
public void HandleNullMultiTenantContextOnGetOrAdd()
{
var tca = new MultiTenantContextAccessor<TenantInfo>();
var tca = new AsyncLocalMultiTenantContextAccessor<TenantInfo>();
var cache = new MultiTenantOptionsCache<TestOptions, TenantInfo>(tca);

var options = new TestOptions();
Expand All @@ -77,7 +77,7 @@ public void GetOrAddNamedOptionForCurrentTenantOnly(string name)
var ti = new TenantInfo { Id = "test-id-123"};
var tc = new MultiTenantContext<TenantInfo>();
tc.TenantInfo = ti;
var tca = new MultiTenantContextAccessor<TenantInfo>();
var tca = new AsyncLocalMultiTenantContextAccessor<TenantInfo>();
tca.MultiTenantContext = tc;
var cache = new MultiTenantOptionsCache<TestOptions, TenantInfo>(tca);

Expand All @@ -102,7 +102,7 @@ public void GetOrAddNamedOptionForCurrentTenantOnly(string name)
public void ThrowsIfGetOrAddFactoryIsNull()
{
var tc = new MultiTenantContext<TenantInfo>();
var tca = new MultiTenantContextAccessor<TenantInfo>();
var tca = new AsyncLocalMultiTenantContextAccessor<TenantInfo>();
tca.MultiTenantContext = tc;
var cache = new MultiTenantOptionsCache<TestOptions, TenantInfo>(tca);

Expand All @@ -113,7 +113,7 @@ public void ThrowsIfGetOrAddFactoryIsNull()
public void ThrowIfContructorParamIsNull()
{
var tc = new MultiTenantContext<TenantInfo>();
var tca = new MultiTenantContextAccessor<TenantInfo>();
var tca = new AsyncLocalMultiTenantContextAccessor<TenantInfo>();
tca.MultiTenantContext = tc;

Assert.Throws<ArgumentNullException>(() => new MultiTenantOptionsCache<TestOptions, TenantInfo>(null!));
Expand All @@ -128,7 +128,7 @@ public void RemoveNamedOptionsForCurrentTenantOnly(string name)
var ti = new TenantInfo { Id = "test-id-123" };
var tc = new MultiTenantContext<TenantInfo>();
tc.TenantInfo = ti;
var tca = new MultiTenantContextAccessor<TenantInfo>();
var tca = new AsyncLocalMultiTenantContextAccessor<TenantInfo>();
tca.MultiTenantContext = tc;
var cache = new MultiTenantOptionsCache<TestOptions, TenantInfo>(tca);

Expand Down Expand Up @@ -172,7 +172,7 @@ public void ClearOptionsForCurrentTenantOnly()
var ti = new TenantInfo { Id = "test-id-123" };
var tc = new MultiTenantContext<TenantInfo>();
tc.TenantInfo = ti;
var tca = new MultiTenantContextAccessor<TenantInfo>();
var tca = new AsyncLocalMultiTenantContextAccessor<TenantInfo>();
tca.MultiTenantContext = tc;
var cache = new MultiTenantOptionsCache<TestOptions, TenantInfo>(tca);

Expand Down Expand Up @@ -213,7 +213,7 @@ public void ClearOptionsForTenantIdOnly()
var ti = new TenantInfo { Id = "test-id-123" };
var tc = new MultiTenantContext<TenantInfo>();
tc.TenantInfo = ti;
var tca = new MultiTenantContextAccessor<TenantInfo>();
var tca = new AsyncLocalMultiTenantContextAccessor<TenantInfo>();
tca.MultiTenantContext = tc;
var cache = new MultiTenantOptionsCache<TestOptions, TenantInfo>(tca);

Expand Down Expand Up @@ -252,7 +252,7 @@ public void ClearAllOptionsForClearAll()
var ti = new TenantInfo { Id = "test-id-123" };
var tc = new MultiTenantContext<TenantInfo>();
tc.TenantInfo = ti;
var tca = new MultiTenantContextAccessor<TenantInfo>();
var tca = new AsyncLocalMultiTenantContextAccessor<TenantInfo>();
tca.MultiTenantContext = tc;
var cache = new MultiTenantOptionsCache<TestOptions, TenantInfo>(tca);

Expand Down
12 changes: 8 additions & 4 deletions test/Finbuckle.MultiTenant.Test/TenantInfoShould.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ public void ThrowIfIdSetWithLengthAboveTenantIdMaxLength()
// OK
// ReSharper disable once ObjectCreationAsStatement
new TenantInfo { Id = "".PadRight(Constants.TenantIdMaxLength, 'a') };

Assert.Throws<MultiTenantException>(() => new TenantInfo{ Id = "".PadRight(Constants.TenantIdMaxLength + 1, 'a') });
Assert.Throws<MultiTenantException>(() => new TenantInfo{ Id = "".PadRight(Constants.TenantIdMaxLength
+ 999, 'a') });

Assert.Throws<MultiTenantException>(() => new TenantInfo
{ Id = "".PadRight(Constants.TenantIdMaxLength + 1, 'a') });
Assert.Throws<MultiTenantException>(() => new TenantInfo
{
Id = "".PadRight(Constants.TenantIdMaxLength
+ 999, 'a')
});
}
}
}