Skip to content

Commit eca24bf

Browse files
committedApr 21, 2024
feat: refactor DI and improve nullability
BREAKING CHANGE: (I)MultiTenantContext and (I)TenantInfo are no longer available via DI. Use IMultiTenantContextAccessor instead. Also IMultiTenantContext nullability reworked and should never be null.
1 parent e9940a7 commit eca24bf

30 files changed

+430
-411
lines changed
 

‎docs/GettingStarted.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ using Finbuckle.MultiTenant;
2626

2727
var builder = WebApplication.CreateBuilder(args);
2828

29-
// ...add app services
29+
// add app services...
3030
3131
// add Finbuckle.MultiTenant services
3232
builder.Services.AddMultiTenant<TenantInfo>()
@@ -38,7 +38,7 @@ var app = builder.Build();
3838
// add the Finbuckle.MultiTenant middleware
3939
app.UseMultiTenant();
4040

41-
// ...add other middleware
41+
// add other middleware...
4242
4343
app.Run();
4444
```

‎src/Directory.Build.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<PackageTags>finbuckle;multitenant;multitenancy;aspnet;aspnetcore;entityframework;entityframework-core;efcore</PackageTags>
1414
<PackageIcon>finbuckle-128x128.png</PackageIcon>
1515
<GenerateDocumentationFile>true</GenerateDocumentationFile>
16-
<NoWarn>CS1591</NoWarn>
16+
<!-- <NoWarn>CS1591</NoWarn>-->
1717
</PropertyGroup>
1818

1919
<ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright Finbuckle LLC, Andrew White, and Contributors.
22
// Refer to the solution LICENSE file for more information.
33

4+
using System;
5+
using Finbuckle.MultiTenant.Abstractions;
46
using Microsoft.AspNetCore.Http;
57
using Microsoft.Extensions.DependencyInjection;
68

@@ -13,38 +15,59 @@ namespace Finbuckle.MultiTenant
1315
public static class FinbuckleHttpContextExtensions
1416
{
1517
/// <summary>
16-
/// Returns the current MultiTenantContext or null if there is none.
18+
/// Returns the current MultiTenantContext.
1719
/// </summary>
20+
/// <param name="httpContext">The HttpContext instance.</param>
21+
/// <typeparam name="TTenantInfo">The ITenantInfo implementation type.</typeparam>
1822
public static IMultiTenantContext<TTenantInfo> GetMultiTenantContext<TTenantInfo>(this HttpContext httpContext)
19-
where TTenantInfo : class, ITenantInfo, new()
23+
where TTenantInfo : class, ITenantInfo, new()
2024
{
21-
var services = httpContext.RequestServices;
22-
var context = services.GetRequiredService<IMultiTenantContextAccessor<TTenantInfo>>();
23-
return context.MultiTenantContext;
25+
if (httpContext.Items.TryGetValue(typeof(IMultiTenantContext), out var mtc) && mtc is not null)
26+
return (IMultiTenantContext<TTenantInfo>)mtc;
27+
28+
mtc = new MultiTenantContext<TTenantInfo>();
29+
httpContext.Items[typeof(IMultiTenantContext)] = mtc;
30+
31+
return (IMultiTenantContext<TTenantInfo>)mtc;
2432
}
2533

34+
/// <summary>
35+
/// Returns the current generic TTenantInfo instance or null if there is none.
36+
/// </summary>
37+
/// <param name="httpContext">The HttpContext instance.</param>
38+
/// <typeparam name="TTenantInfo">The ITenantInfo implementation type.</typeparam>
39+
public static TTenantInfo? GetTenantInfo<TTenantInfo>(this HttpContext httpContext)
40+
where TTenantInfo : class, ITenantInfo, new() =>
41+
httpContext.GetMultiTenantContext<TTenantInfo>().TenantInfo;
42+
43+
2644
/// <summary>
2745
/// Sets the provided TenantInfo on the MultiTenantContext.
2846
/// Sets StrategyInfo and StoreInfo on the MultiTenant Context to null.
2947
/// Optionally resets the current dependency injection service provider.
3048
/// </summary>
31-
public static bool TrySetTenantInfo<T>(this HttpContext httpContext, T tenantInfo, bool resetServiceProviderScope)
32-
where T : class, ITenantInfo, new()
49+
/// <param name="httpContext">The HttpContext instance.</param>
50+
/// <param name="tenantInfo">The tenant info instance to set as current.</param>
51+
/// <param name="resetServiceProviderScope">Creates a new service provider scope if true.</param>
52+
/// <typeparam name="TTenantInfo">The ITenantInfo implementation type.</typeparam>
53+
public static void SetTenantInfo<TTenantInfo>(this HttpContext httpContext, TTenantInfo tenantInfo,
54+
bool resetServiceProviderScope)
55+
where TTenantInfo : class, ITenantInfo, new()
3356
{
3457
if (resetServiceProviderScope)
3558
httpContext.RequestServices = httpContext.RequestServices.CreateScope().ServiceProvider;
3659

37-
var multitenantContext = new MultiTenantContext<T>
60+
var multiTenantContext = new MultiTenantContext<TTenantInfo>
3861
{
3962
TenantInfo = tenantInfo,
4063
StrategyInfo = null,
4164
StoreInfo = null
4265
};
4366

44-
var accessor = httpContext.RequestServices.GetRequiredService<IMultiTenantContextAccessor<T>>();
45-
accessor.MultiTenantContext = multitenantContext;
67+
var setter = httpContext.RequestServices.GetRequiredService<IMultiTenantContextSetter>();
68+
setter.MultiTenantContext = multiTenantContext;
4669

47-
return true;
70+
httpContext.Items[typeof(IMultiTenantContext)] = multiTenantContext;
4871
}
4972
}
5073
}

‎src/Finbuckle.MultiTenant.AspNetCore/Internal/MultiTenantMiddleware.cs

+10-9
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
// Refer to the solution LICENSE file for more information.
33

44
using System.Threading.Tasks;
5+
using Finbuckle.MultiTenant.Abstractions;
56
using Microsoft.AspNetCore.Http;
67
using Microsoft.Extensions.DependencyInjection;
78

89
namespace Finbuckle.MultiTenant.AspNetCore
910
{
1011
/// <summary>
11-
/// Middleware for resolving the TenantContext and storing it in HttpContext.
12+
/// Middleware for resolving the MultiTenantContext and storing it in HttpContext.
1213
/// </summary>
1314
internal class MultiTenantMiddleware
1415
{
@@ -21,14 +22,14 @@ public MultiTenantMiddleware(RequestDelegate next)
2122

2223
public async Task Invoke(HttpContext context)
2324
{
24-
var accessor = context.RequestServices.GetRequiredService<IMultiTenantContextAccessor>();
25-
26-
if (accessor.MultiTenantContext == null)
27-
{
28-
var resolver = context.RequestServices.GetRequiredService<ITenantResolver>();
29-
var multiTenantContext = await resolver.ResolveAsync(context);
30-
accessor.MultiTenantContext = multiTenantContext;
31-
}
25+
var mtcAccessor = context.RequestServices.GetRequiredService<IMultiTenantContextAccessor>();
26+
var mtcSetter = context.RequestServices.GetRequiredService<IMultiTenantContextSetter>();
27+
28+
var resolver = context.RequestServices.GetRequiredService<ITenantResolver>();
29+
30+
var multiTenantContext = await resolver.ResolveAsync(context);
31+
mtcSetter.MultiTenantContext = multiTenantContext;
32+
context.Items[typeof(IMultiTenantContext)] = multiTenantContext;
3233

3334
await next(context);
3435
}

‎src/Finbuckle.MultiTenant/Abstractions/IMultiTenantConfigureNamedOptions.cs

-12
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,45 @@
11
// Copyright Finbuckle LLC, Andrew White, and Contributors.
22
// Refer to the solution LICENSE file for more information.
33

4-
// ReSharper disable once CheckNamespace
54
namespace Finbuckle.MultiTenant;
65

76
/// <summary>
8-
/// Non-generic interface for the multitenant context.
7+
/// Non-generic interface for the MultiTenantContext.
98
/// </summary>
109
public interface IMultiTenantContext
1110
{
1211
/// <summary>
1312
/// Information about the tenant for this context.
1413
/// </summary>
1514
ITenantInfo? TenantInfo { get; }
16-
15+
1716
/// <summary>
18-
/// True if a non-null tenant has been resolved.
17+
/// True if a tenant has been resolved and TenantInfo is not null.
1918
/// </summary>
20-
bool HasResolvedTenant => TenantInfo != null;
21-
19+
bool IsResolved { get; }
20+
2221
/// <summary>
23-
/// Information about the multitenant strategies for this context.
22+
/// Information about the MultiTenant strategies for this context.
2423
/// </summary>
2524
StrategyInfo? StrategyInfo { get; }
2625
}
2726

27+
28+
2829
/// <summary>
29-
/// Generic interface for the multitenant context.
30+
/// Generic interface for the multi-tenant context.
3031
/// </summary>
31-
/// <typeparam name="T">The ITenantInfo implementation type.</typeparam>
32-
public interface IMultiTenantContext<T>
33-
where T : class, ITenantInfo, new()
32+
/// <typeparam name="TTenantInfo">The ITenantInfo implementation type.</typeparam>
33+
public interface IMultiTenantContext<TTenantInfo> : IMultiTenantContext
34+
where TTenantInfo : class, ITenantInfo, new()
3435
{
3536
/// <summary>
3637
/// Information about the tenant for this context.
3738
/// </summary>
38-
T? TenantInfo { get; set; }
39+
new TTenantInfo? TenantInfo { get; }
3940

4041
/// <summary>
41-
/// Returns true if a non-null tenant has been resolved.
42-
/// </summary>
43-
bool HasResolvedTenant => TenantInfo != null;
44-
45-
/// <summary>
46-
/// Information about the multitenant strategies for this context.
47-
/// </summary>
48-
StrategyInfo? StrategyInfo { get; set; }
49-
50-
51-
/// <summary>
52-
/// Information about the multitenant store(s) for this context.
42+
/// Information about the MultiTenant stores for this context.
5343
/// </summary>
54-
StoreInfo<T>? StoreInfo { get; set; }
44+
StoreInfo<TTenantInfo>? StoreInfo { get; set; }
5545
}

‎src/Finbuckle.MultiTenant/Abstractions/IMultiTenantContextAccessor.cs

+9-8
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,25 @@
66
namespace Finbuckle.MultiTenant;
77

88
/// <summary>
9-
/// Provides access the current MultiTenantContext.
9+
/// Provides access the current MultiTenantContext.
1010
/// </summary>
1111
public interface IMultiTenantContextAccessor
1212
{
1313
/// <summary>
14-
/// Gets or sets the current MultiTenantContext.
14+
/// Gets the current MultiTenantContext.
1515
/// </summary>
16-
IMultiTenantContext? MultiTenantContext { get; set; }
16+
IMultiTenantContext MultiTenantContext { get; }
1717
}
1818

1919
/// <summary>
20-
/// Provides access the current MultiTenantContext.
20+
/// Provides access the current MultiTenantContext.
2121
/// </summary>
22-
/// <typeparam name="T">The ITenantInfo implementation type.</typeparam>
23-
public interface IMultiTenantContextAccessor<T> where T : class, ITenantInfo, new()
22+
/// <typeparam name="TTenantInfo">The ITenantInfo implementation type.</typeparam>
23+
public interface IMultiTenantContextAccessor<TTenantInfo> : IMultiTenantContextAccessor
24+
where TTenantInfo : class, ITenantInfo, new()
2425
{
2526
/// <summary>
26-
/// Gets or sets the current MultiTenantContext.
27+
/// Gets the current MultiTenantContext.
2728
/// </summary>
28-
IMultiTenantContext<T>? MultiTenantContext { get; set; }
29+
new IMultiTenantContext<TTenantInfo> MultiTenantContext { get; }
2930
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Finbuckle.MultiTenant.Abstractions;
2+
3+
/// <summary>
4+
/// Interface used to set the MultiTenantContext. This is an implementation detail and not intended for general use.
5+
/// </summary>
6+
public interface IMultiTenantContextSetter
7+
{
8+
IMultiTenantContext MultiTenantContext { set; }
9+
}

‎src/Finbuckle.MultiTenant/Abstractions/IMultiTenantStrategy.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public interface IMultiTenantStrategy
1414
/// <summary>
1515
/// Method for implementations to control how the identifier is determined.
1616
/// </summary>
17-
/// <param name="context"></param>
17+
/// <param name="context">The context object used to determine an identifier.</param>
1818
/// <returns>The found identifier or null.</returns>
1919
Task<string?> GetIdentifierAsync(object context);
2020

‎src/Finbuckle.MultiTenant/Abstractions/ITenantInfo.cs

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
// ReSharper disable once CheckNamespace
55
namespace Finbuckle.MultiTenant;
66

7+
/// <summary>
8+
/// Interface for basic tenant information.
9+
/// </summary>
710
public interface ITenantInfo
811
{
912

‎src/Finbuckle.MultiTenant/Abstractions/ITenantResolver.cs

+16-16
Original file line numberDiff line numberDiff line change
@@ -15,31 +15,31 @@ public interface ITenantResolver
1515
/// Performs tenant resolution within the given context.
1616
/// </summary>
1717
/// <param name="context">The context for tenant resolution.</param>
18-
/// <returns>The MultiTenantContext or null if none resolved.</returns>
19-
Task<IMultiTenantContext?> ResolveAsync(object context);
18+
/// <returns>The MultiTenantContext.</returns>
19+
Task<IMultiTenantContext> ResolveAsync(object context);
20+
21+
/// <summary>
22+
/// Contains a list of MultiTenant strategies used for tenant resolution.
23+
/// </summary>
24+
public IEnumerable<IMultiTenantStrategy> Strategies { get; set; }
2025
}
2126

2227
/// <summary>
2328
/// Resolves the current tenant.
2429
/// </summary>
25-
public interface ITenantResolver<T>
26-
where T : class, ITenantInfo, new()
30+
/// <typeparam name="TTenantInfo">The ITenantInfo implementation type.</typeparam>
31+
public interface ITenantResolver<TTenantInfo> : ITenantResolver
32+
where TTenantInfo : class, ITenantInfo, new()
2733
{
2834
/// <summary>
29-
/// Gets the multitenant strategies used for tenant resolution.
35+
/// Performs tenant resolution within the given context.
3036
/// </summary>
31-
IEnumerable<IMultiTenantStrategy> Strategies { get; }
32-
37+
/// <param name="context">The context for tenant resolution.</param>
38+
/// <returns>The MultiTenantContext.</returns>
39+
new Task<IMultiTenantContext<TTenantInfo>> ResolveAsync(object context);
3340

3441
/// <summary>
35-
/// Get;s the multitenant stores used for tenant resolution.
42+
/// Contains a list of MultiTenant stores used for tenant resolution.
3643
/// </summary>
37-
IEnumerable<IMultiTenantStore<T>> Stores { get; }
38-
39-
/// <summary>
40-
/// Performs tenant resolution within the given context.
41-
/// </summary>
42-
/// <param name="context">The context for tenant resolution.</param>
43-
/// <returns>The MultiTenantContext or null if none resolved.</returns>
44-
Task<IMultiTenantContext<T>?> ResolveAsync(object context);
44+
public IEnumerable<IMultiTenantStore<TTenantInfo>> Stores { get; set; }
4545
}

‎src/Finbuckle.MultiTenant/DependencyInjection/ServiceCollectionExtensions.cs

+8-12
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Refer to the solution LICENSE file for more information.
33

44
using Finbuckle.MultiTenant;
5+
using Finbuckle.MultiTenant.Abstractions;
56
using Finbuckle.MultiTenant.Internal;
67
using Finbuckle.MultiTenant.Options;
78
using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -30,20 +31,15 @@ public static FinbuckleMultiTenantBuilder<TTenantInfo> AddMultiTenant<TTenantInf
3031
services.AddScoped<ITenantResolver>(
3132
sp => (ITenantResolver)sp.GetRequiredService<ITenantResolver<TTenantInfo>>());
3233

33-
services.AddScoped<IMultiTenantContext<TTenantInfo>>(sp =>
34-
sp.GetRequiredService<IMultiTenantContextAccessor<TTenantInfo>>().MultiTenantContext!);
35-
36-
services.AddScoped<TTenantInfo>(sp =>
37-
sp.GetRequiredService<IMultiTenantContextAccessor<TTenantInfo>>().MultiTenantContext?.TenantInfo!);
38-
services.AddScoped<ITenantInfo>(sp => sp.GetService<TTenantInfo>()!);
39-
40-
// TODO this might require instance to ensure it already exists when needed
41-
services
42-
.AddSingleton<IMultiTenantContextAccessor<TTenantInfo>,
43-
AsyncLocalMultiTenantContextAccessor<TTenantInfo>>();
34+
services.AddSingleton<IMultiTenantContextAccessor<TTenantInfo>,
35+
AsyncLocalMultiTenantContextAccessor<TTenantInfo>>();
4436
services.AddSingleton<IMultiTenantContextAccessor>(sp =>
45-
(IMultiTenantContextAccessor)sp.GetRequiredService<IMultiTenantContextAccessor<TTenantInfo>>());
37+
sp.GetRequiredService<IMultiTenantContextAccessor<TTenantInfo>>());
38+
39+
services.AddSingleton<IMultiTenantContextSetter>(sp =>
40+
(IMultiTenantContextSetter)sp.GetRequiredService<IMultiTenantContextAccessor>());
4641

42+
services.Configure<MultiTenantOptions>(options => options.TenantInfoType = typeof(TTenantInfo));
4743
services.Configure(config);
4844

4945
return new FinbuckleMultiTenantBuilder<TTenantInfo>(services);
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,32 @@
11
// Copyright Finbuckle LLC, Andrew White, and Contributors.
22
// Refer to the solution LICENSE file for more information.
33

4+
using Finbuckle.MultiTenant.Abstractions;
5+
46
namespace Finbuckle.MultiTenant.Internal;
57

68
/// <summary>
79
/// Provides access the current MultiTenantContext via an AsyncLocal variable.
810
/// </summary>
9-
/// <typeparam name="T">The ITenantInfo implementation type.</typeparam>
10-
/// <remarks>
11-
/// This implementation may have performance impacts due to the use of AsyncLocal.
12-
/// </remarks>
13-
public class AsyncLocalMultiTenantContextAccessor<T> : IMultiTenantContextAccessor<T>, IMultiTenantContextAccessor
14-
where T : class, ITenantInfo, new()
11+
/// <typeparam name="TTenantInfo">The ITenantInfo implementation type.</typeparam>
12+
internal class AsyncLocalMultiTenantContextAccessor<TTenantInfo> : IMultiTenantContextSetter,
13+
IMultiTenantContextAccessor<TTenantInfo>
14+
where TTenantInfo : class, ITenantInfo, new()
1515
{
16-
private static readonly AsyncLocal<IMultiTenantContext<T>?> AsyncLocalContext = new();
16+
private static readonly AsyncLocal<IMultiTenantContext<TTenantInfo>> AsyncLocalContext = new();
1717

1818
/// <inheritdoc />
19-
public IMultiTenantContext<T>? MultiTenantContext
19+
public IMultiTenantContext<TTenantInfo> MultiTenantContext
2020
{
21-
get => AsyncLocalContext.Value;
22-
21+
get => AsyncLocalContext.Value ?? (AsyncLocalContext.Value = new MultiTenantContext<TTenantInfo>());
2322
set => AsyncLocalContext.Value = value;
2423
}
2524

2625
/// <inheritdoc />
27-
// TODO move this to the interface?
28-
// TODO should the set throw if "as" returns null?
29-
IMultiTenantContext? IMultiTenantContextAccessor.MultiTenantContext
26+
IMultiTenantContext IMultiTenantContextAccessor.MultiTenantContext => (IMultiTenantContext)MultiTenantContext;
27+
28+
IMultiTenantContext IMultiTenantContextSetter.MultiTenantContext
3029
{
31-
get => MultiTenantContext as IMultiTenantContext;
32-
set => MultiTenantContext = value as IMultiTenantContext<T> ?? MultiTenantContext;
30+
set => MultiTenantContext = (IMultiTenantContext<TTenantInfo>)value;
3331
}
3432
}

‎src/Finbuckle.MultiTenant/Extensions/TypeExtensions.cs ‎src/Finbuckle.MultiTenant/Internal/TypeExtensions.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace Finbuckle.MultiTenant;
77

8-
public static class TypeExtensions
8+
internal static class TypeExtensions
99
{
1010
public static bool ImplementsOrInheritsUnboundGeneric(this Type source, Type unboundGeneric)
1111
{
@@ -34,7 +34,7 @@ public static bool ImplementsOrInheritsUnboundGeneric(this Type source, Type unb
3434
return false;
3535
}
3636

37-
public static bool HasMultiTenantAttribute(this Type type)
37+
internal static bool HasMultiTenantAttribute(this Type type)
3838
{
3939
return type.GetCustomAttribute<MultiTenantAttribute>() != null;
4040
}

‎src/Finbuckle.MultiTenant/MultiTenantAttribute.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
namespace Finbuckle.MultiTenant;
55

66
/// <summary>
7-
/// Marks a class as multitenant. Currently only used in EFCore support but included here to reduce dependencies where
8-
/// this might be needed.
7+
/// Marks a type as multi-tenant.
98
/// </summary>
109
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
1110
public class MultiTenantAttribute : Attribute

‎src/Finbuckle.MultiTenant/MultiTenantContext.cs

+11-2
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,24 @@
44
namespace Finbuckle.MultiTenant;
55

66
/// <summary>
7-
/// Contains information for the MultiTenant tenant, store, and strategy.
7+
/// Contains contextual MultiTenant information.
88
/// </summary>
9-
public class MultiTenantContext<TTenantInfo> : IMultiTenantContext<TTenantInfo>, IMultiTenantContext
9+
/// <typeparam name="TTenantInfo">The ITenantInfo implementation type.</typeparam>
10+
public class MultiTenantContext<TTenantInfo> : IMultiTenantContext<TTenantInfo>
1011
where TTenantInfo : class, ITenantInfo, new()
1112
{
13+
/// <inheritdoc />
1214
public TTenantInfo? TenantInfo { get; set; }
1315

16+
/// <inheritdoc />
17+
public bool IsResolved => TenantInfo != null;
18+
19+
/// <inheritdoc />
1420
public StrategyInfo? StrategyInfo { get; set; }
21+
22+
/// <inheritdoc />
1523
public StoreInfo<TTenantInfo>? StoreInfo { get; set; }
1624

25+
/// <inheritdoc />
1726
ITenantInfo? IMultiTenantContext.TenantInfo => TenantInfo;
1827
}

‎src/Finbuckle.MultiTenant/MultiTenantException.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace Finbuckle.MultiTenant;
55

66
/// <summary>
7-
/// A derived Exception class for any exception generated by Finbuckle.MultiTenant.
7+
/// An exception generated by Finbuckle.MultiTenant.
88
/// </summary>
99
public class MultiTenantException : Exception
1010
{

‎src/Finbuckle.MultiTenant/MultiTenantOptions.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace Finbuckle.MultiTenant;
66

77
public class MultiTenantOptions
88
{
9-
public IList<string> IgnoredIdentifiers = new List<string>();
10-
public MultiTenantEvents Events { get; set; } = new MultiTenantEvents();
9+
public Type? TenantInfoType { get; internal set; }
10+
public IList<string> IgnoredIdentifiers { get; set; } = new List<string>();
11+
public MultiTenantEvents Events { get; set; } = new ();
1112
}

‎src/Finbuckle.MultiTenant/Stores/MultiTenantStoreWrapper.cs

+2-5
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,8 @@ public async Task<IEnumerable<TTenantInfo>> GetAllAsync()
125125
/// <inheritdoc />
126126
public async Task<bool> TryAddAsync(TTenantInfo tenantInfo)
127127
{
128-
if (tenantInfo == null)
129-
{
130-
throw new ArgumentNullException(nameof(tenantInfo));
131-
}
132-
128+
ArgumentNullException.ThrowIfNull(tenantInfo);
129+
133130
if (tenantInfo.Id == null)
134131
{
135132
throw new ArgumentNullException(nameof(tenantInfo.Id));

‎src/Finbuckle.MultiTenant/TenantResolver.cs

+40-25
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,25 @@
99

1010
namespace Finbuckle.MultiTenant;
1111

12-
public class TenantResolver<T> : ITenantResolver<T>, ITenantResolver
13-
where T : class, ITenantInfo, new()
12+
/// <summary>
13+
/// Resolves the current tenant.
14+
/// </summary>
15+
/// <typeparam name="TTenantInfo">The ITenantInfo implementation type.</typeparam>
16+
public class TenantResolver<TTenantInfo> : ITenantResolver<TTenantInfo>
17+
where TTenantInfo : class, ITenantInfo, new()
1418
{
1519
private readonly IOptionsMonitor<MultiTenantOptions> options;
1620
private readonly ILoggerFactory? loggerFactory;
1721

18-
public TenantResolver(IEnumerable<IMultiTenantStrategy> strategies, IEnumerable<IMultiTenantStore<T>> stores, IOptionsMonitor<MultiTenantOptions> options) :
22+
public TenantResolver(IEnumerable<IMultiTenantStrategy> strategies,
23+
IEnumerable<IMultiTenantStore<TTenantInfo>> stores, IOptionsMonitor<MultiTenantOptions> options) :
1924
this(strategies, stores, options, null)
2025
{
2126
}
2227

23-
public TenantResolver(IEnumerable<IMultiTenantStrategy> strategies, IEnumerable<IMultiTenantStore<T>> stores, IOptionsMonitor<MultiTenantOptions> options, ILoggerFactory? loggerFactory)
28+
public TenantResolver(IEnumerable<IMultiTenantStrategy> strategies,
29+
IEnumerable<IMultiTenantStore<TTenantInfo>> stores, IOptionsMonitor<MultiTenantOptions> options,
30+
ILoggerFactory? loggerFactory)
2431
{
2532
Stores = stores;
2633
this.options = options;
@@ -29,30 +36,39 @@ public TenantResolver(IEnumerable<IMultiTenantStrategy> strategies, IEnumerable<
2936
Strategies = strategies.OrderByDescending(s => s.Priority);
3037
}
3138

39+
/// <inheritdoc />
3240
public IEnumerable<IMultiTenantStrategy> Strategies { get; set; }
33-
public IEnumerable<IMultiTenantStore<T>> Stores { get; set; }
3441

35-
public async Task<IMultiTenantContext<T>?> ResolveAsync(object context)
42+
/// <inheritdoc />
43+
public IEnumerable<IMultiTenantStore<TTenantInfo>> Stores { get; set; }
44+
45+
/// <inheritdoc />
46+
public async Task<IMultiTenantContext<TTenantInfo>> ResolveAsync(object context)
3647
{
48+
var mtc = new MultiTenantContext<TTenantInfo>();
49+
3750
string? identifier = null;
3851
foreach (var strategy in Strategies)
3952
{
40-
var _strategy = new MultiTenantStrategyWrapper(strategy, loggerFactory?.CreateLogger(strategy.GetType()) ?? NullLogger.Instance);
41-
identifier = await _strategy.GetIdentifierAsync(context);
53+
var wrappedStrategy = new MultiTenantStrategyWrapper(strategy,
54+
loggerFactory?.CreateLogger(strategy.GetType()) ?? NullLogger.Instance);
55+
identifier = await wrappedStrategy.GetIdentifierAsync(context);
4256

4357
if (options.CurrentValue.IgnoredIdentifiers.Contains(identifier, StringComparer.OrdinalIgnoreCase))
4458
{
45-
(loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance).LogInformation("Ignored identifier: {Identifier}", identifier);
59+
(loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance).LogInformation(
60+
"Ignored identifier: {Identifier}", identifier);
4661
identifier = null;
4762
}
48-
63+
4964
if (identifier == null)
5065
continue;
5166

5267
foreach (var store in Stores)
5368
{
54-
var _store = new MultiTenantStoreWrapper<T>(store, loggerFactory?.CreateLogger(store.GetType()) ?? NullLogger.Instance);
55-
var tenantInfo = await _store.TryGetByIdentifierAsync(identifier);
69+
var wrappedStore = new MultiTenantStoreWrapper<TTenantInfo>(store,
70+
loggerFactory?.CreateLogger(store.GetType()) ?? NullLogger.Instance);
71+
var tenantInfo = await wrappedStore.TryGetByIdentifierAsync(identifier);
5672
if (tenantInfo == null)
5773
continue;
5874

@@ -63,23 +79,22 @@ await options.CurrentValue.Events.OnTenantResolved(new TenantResolvedContext
6379
StrategyType = strategy.GetType(),
6480
StoreType = store.GetType()
6581
});
66-
67-
return new MultiTenantContext<T>
68-
{
69-
StoreInfo = new StoreInfo<T> { Store = store, StoreType = store.GetType() },
70-
StrategyInfo = new StrategyInfo { Strategy = strategy, StrategyType = strategy.GetType() },
71-
TenantInfo = tenantInfo
72-
};
82+
83+
mtc.StoreInfo = new StoreInfo<TTenantInfo> { Store = store, StoreType = store.GetType() };
84+
mtc.StrategyInfo = new StrategyInfo { Strategy = strategy, StrategyType = strategy.GetType() };
85+
mtc.TenantInfo = tenantInfo;
86+
return mtc;
7387
}
7488
}
75-
76-
await options.CurrentValue.Events.OnTenantNotResolved(new TenantNotResolvedContext { Context = context, Identifier = identifier });
77-
return null;
89+
90+
await options.CurrentValue.Events.OnTenantNotResolved(new TenantNotResolvedContext
91+
{ Context = context, Identifier = identifier });
92+
return mtc;
7893
}
7994

80-
// TODO move this to the base interface?
81-
async Task<IMultiTenantContext?> ITenantResolver.ResolveAsync(object context)
95+
/// <inheritdoc />
96+
async Task<IMultiTenantContext> ITenantResolver.ResolveAsync(object context)
8297
{
83-
return (await ResolveAsync(context)) as IMultiTenantContext;
98+
return (IMultiTenantContext)(await ResolveAsync(context));
8499
}
85100
}

‎test/Directory.Build.props

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<Project>
22
<ItemGroup>
3-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
4-
<PackageReference Include="xunit" Version="2.4.2" />
5-
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" />
6-
<PackageReference Include="Moq" Version="4.18.4" />
3+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
4+
<PackageReference Include="xunit" Version="2.7.0" />
5+
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7" />
6+
<PackageReference Include="Moq" Version="4.20.70" />
77
</ItemGroup>
88
</Project>

‎test/Finbuckle.MultiTenant.AspNetCore.Test/Extensions/HttpContextExtensionShould.cs

+77-31
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Refer to the solution LICENSE file for more information.
33

44
using System;
5+
using System.Collections.Generic;
56
using Finbuckle.MultiTenant.Internal;
67
using Microsoft.AspNetCore.Http;
78
using Microsoft.Extensions.DependencyInjection;
@@ -13,37 +14,72 @@ namespace Finbuckle.MultiTenant.AspNetCore.Test.Extensions
1314
public class HttpContextExtensionShould
1415
{
1516
[Fact]
16-
public void GetTenantContextIfExists()
17+
public void GetExistingMultiTenantContext()
1718
{
1819
var ti = new TenantInfo { Id = "test" };
19-
var tc = new MultiTenantContext<TenantInfo>();
20-
tc.TenantInfo = ti;
21-
22-
var services = new ServiceCollection();
23-
services.AddScoped<IMultiTenantContextAccessor<TenantInfo>>(_ => new AsyncLocalMultiTenantContextAccessor<TenantInfo>{ MultiTenantContext = tc });
24-
var sp = services.BuildServiceProvider();
20+
var mtc = new MultiTenantContext<TenantInfo>
21+
{
22+
TenantInfo = ti
23+
};
2524

2625
var httpContextMock = new Mock<HttpContext>();
27-
httpContextMock.Setup(c => c.RequestServices).Returns(sp);
28-
29-
var mtc = httpContextMock.Object.GetMultiTenantContext<TenantInfo>();
30-
31-
Assert.Same(tc, mtc);
26+
var itemsDict = new Dictionary<object, object?>
27+
{
28+
[typeof(IMultiTenantContext)] = mtc
29+
};
30+
httpContextMock.Setup(c => c.Items).Returns(itemsDict);
31+
32+
var returnedMtc = httpContextMock.Object.GetMultiTenantContext<TenantInfo>();
33+
34+
Assert.Same(mtc, returnedMtc);
3235
}
33-
36+
3437
[Fact]
35-
public void ReturnNullIfNoMultiTenantContext()
38+
public void GetEmptyMultiTenantContextIfNoneSet()
3639
{
37-
var services = new ServiceCollection();
38-
services.AddScoped<IMultiTenantContextAccessor<TenantInfo>>(_ => new AsyncLocalMultiTenantContextAccessor<TenantInfo>());
39-
var sp = services.BuildServiceProvider();
40+
var httpContextMock = new Mock<HttpContext>();
41+
var itemsDict = new Dictionary<object, object?>();
42+
httpContextMock.Setup(c => c.Items).Returns(itemsDict);
43+
44+
var returnedMtc = httpContextMock.Object.GetMultiTenantContext<TenantInfo>();
45+
46+
Assert.False(returnedMtc.IsResolved);
47+
Assert.Null(returnedMtc.TenantInfo);
48+
Assert.Null(returnedMtc.StoreInfo);
49+
Assert.Null(returnedMtc.StrategyInfo);
50+
}
51+
52+
[Fact]
53+
public void ReturnTenantInfo()
54+
{
55+
var ti = new TenantInfo { Id = "test" };
56+
var mtc = new MultiTenantContext<TenantInfo>
57+
{
58+
TenantInfo = ti
59+
};
4060

4161
var httpContextMock = new Mock<HttpContext>();
42-
httpContextMock.Setup(c => c.RequestServices).Returns(sp);
62+
var itemsDict = new Dictionary<object, object?>
63+
{
64+
[typeof(IMultiTenantContext)] = mtc
65+
};
66+
httpContextMock.Setup(c => c.Items).Returns(itemsDict);
67+
68+
var returnedTi = httpContextMock.Object.GetTenantInfo<TenantInfo>();
69+
70+
Assert.Same(ti, returnedTi);
71+
}
4372

44-
var mtc = httpContextMock.Object.GetMultiTenantContext<TenantInfo>();
73+
[Fact]
74+
public void ReturnNullTenantInfoIfNoTenantInfo()
75+
{
76+
var httpContextMock = new Mock<HttpContext>();
77+
var itemsDict = new Dictionary<object, object?>();
78+
httpContextMock.Setup(c => c.Items).Returns(itemsDict);
4579

46-
Assert.Null(mtc);
80+
var returnedTi = httpContextMock.Object.GetTenantInfo<TenantInfo>();
81+
82+
Assert.Null(returnedTi);
4783
}
4884

4985
[Fact]
@@ -55,13 +91,15 @@ public void SetTenantInfo()
5591

5692
var httpContextMock = new Mock<HttpContext>();
5793
httpContextMock.Setup(c => c.RequestServices).Returns(sp);
94+
var itemsDict = new Dictionary<object, object?>();
95+
httpContextMock.Setup(c => c.Items).Returns(itemsDict);
5896
var context = httpContextMock.Object;
5997

6098
var ti2 = new TenantInfo { Id = "tenant2" };
61-
var res = context.TrySetTenantInfo(ti2, false);
62-
var mtc = context.GetMultiTenantContext<TenantInfo>();
63-
Assert.True(res);
64-
Assert.Same(ti2, mtc!.TenantInfo);
99+
context.SetTenantInfo(ti2, false);
100+
var ti = context.GetTenantInfo<TenantInfo>();
101+
102+
Assert.Same(ti2, ti);
65103
}
66104

67105
[Fact]
@@ -73,13 +111,15 @@ public void SetMultiTenantContextAccessor()
73111

74112
var httpContextMock = new Mock<HttpContext>();
75113
httpContextMock.Setup(c => c.RequestServices).Returns(sp);
114+
var itemsDict = new Dictionary<object, object?>();
115+
httpContextMock.Setup(c => c.Items).Returns(itemsDict);
76116
var context = httpContextMock.Object;
77117

78118
var ti2 = new TenantInfo { Id = "tenant2" };
79-
var res = context.TrySetTenantInfo(ti2, false);
119+
context.SetTenantInfo(ti2, false);
80120
var mtc = context.GetMultiTenantContext<TenantInfo>();
81121
var accessor = context.RequestServices.GetRequiredService<IMultiTenantContextAccessor<TenantInfo>>();
82-
Assert.True(res);
122+
83123
Assert.Same(mtc, accessor.MultiTenantContext);
84124
}
85125

@@ -92,14 +132,16 @@ public void SetStoreInfoAndStrategyInfoNull()
92132

93133
var httpContextMock = new Mock<HttpContext>();
94134
httpContextMock.Setup(c => c.RequestServices).Returns(sp);
135+
var itemsDict = new Dictionary<object, object?>();
136+
httpContextMock.Setup(c => c.Items).Returns(itemsDict);
95137
var context = httpContextMock.Object;
96138

97139
var ti2 = new TenantInfo { Id = "tenant2" };
98-
context.TrySetTenantInfo(ti2, false);
140+
context.SetTenantInfo(ti2, false);
99141
var mtc = context.GetMultiTenantContext<TenantInfo>();
100142

101-
Assert.Null(mtc?.StoreInfo);
102-
Assert.Null(mtc?.StrategyInfo);
143+
Assert.Null(mtc.StoreInfo);
144+
Assert.Null(mtc.StrategyInfo);
103145
}
104146

105147
[Fact]
@@ -114,9 +156,11 @@ public void ResetScopeIfApplicable()
114156
services.AddMultiTenant<TenantInfo>();
115157
var sp = services.BuildServiceProvider();
116158
httpContextMock.Object.RequestServices = sp;
159+
var itemsDict = new Dictionary<object, object?>();
160+
httpContextMock.Setup(c => c.Items).Returns(itemsDict);
117161

118162
var ti2 = new TenantInfo { Id = "tenant2" };
119-
httpContextMock.Object.TrySetTenantInfo(ti2, true);
163+
httpContextMock.Object.SetTenantInfo(ti2, true);
120164

121165
Assert.NotSame(sp, httpContextMock.Object.RequestServices);
122166
Assert.NotStrictEqual((DateTime?)sp.GetService<object>(),
@@ -135,9 +179,11 @@ public void NotResetScopeIfNotApplicable()
135179
services.AddMultiTenant<TenantInfo>();
136180
var sp = services.BuildServiceProvider();
137181
httpContextMock.Object.RequestServices = sp;
182+
var itemsDict = new Dictionary<object, object?>();
183+
httpContextMock.Setup(c => c.Items).Returns(itemsDict);
138184

139185
var ti2 = new TenantInfo { Id = "tenant2" };
140-
httpContextMock.Object.TrySetTenantInfo(ti2, false);
186+
httpContextMock.Object.SetTenantInfo(ti2, false);
141187

142188
Assert.Same(sp, httpContextMock.Object.RequestServices);
143189
Assert.StrictEqual((DateTime?)sp.GetService<object>(),

‎test/Finbuckle.MultiTenant.AspNetCore.Test/Extensions/MultiTenantBuilderExtensionsShould.cs

+70-58
Large diffs are not rendered by default.

‎test/Finbuckle.MultiTenant.AspNetCore.Test/MultiTenantAuthenticationSchemeProviderShould.cs

+11-22
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@
22
// Refer to the solution LICENSE file for more information.
33

44
using System.Threading.Tasks;
5+
using Finbuckle.MultiTenant.Abstractions;
56
using Microsoft.AspNetCore.Authentication;
6-
using Microsoft.AspNetCore.Builder;
7-
using Microsoft.AspNetCore.Hosting;
8-
using Microsoft.AspNetCore.Http;
9-
using Microsoft.AspNetCore.TestHost;
107
using Microsoft.Extensions.DependencyInjection;
118
using Xunit;
129

@@ -17,18 +14,6 @@ public class MultiTenantAuthenticationSchemeProviderShould
1714
[Fact]
1815
public async Task ReturnPerTenantAuthenticationOptions()
1916
{
20-
// var hostBuilder = GetTestHostBuilder();
21-
//
22-
// using (var server = new TestServer(hostBuilder))
23-
// {
24-
// var client = server.CreateClient();
25-
// var response = await client.GetStringAsync("/tenant1");
26-
// Assert.Equal("tenant1Scheme", response);
27-
//
28-
// response = await client.GetStringAsync("/tenant2");
29-
// Assert.Equal("tenant2Scheme", response);
30-
// }
31-
3217
var services = new ServiceCollection();
3318
services.AddAuthentication()
3419
.AddCookie("tenant1Scheme")
@@ -55,18 +40,22 @@ public async Task ReturnPerTenantAuthenticationOptions()
5540
};
5641

5742
var mtc = new MultiTenantContext<TenantInfo>();
58-
var multiTenantContextAccessor = sp.GetRequiredService<IMultiTenantContextAccessor<TenantInfo>>();
59-
multiTenantContextAccessor.MultiTenantContext = mtc;
43+
var setter = sp.GetRequiredService<IMultiTenantContextSetter>();
44+
setter.MultiTenantContext = mtc;
6045

6146
mtc.TenantInfo = tenant1;
6247
var schemeProvider = sp.GetRequiredService<IAuthenticationSchemeProvider>();
48+
49+
var option = await schemeProvider.GetDefaultChallengeSchemeAsync();
6350

64-
var option = schemeProvider.GetDefaultChallengeSchemeAsync().Result;
65-
Assert.Equal("tenant1Scheme", option?.Name);
51+
Assert.NotNull(option);
52+
Assert.Equal("tenant1Scheme", option.Name);
6653

6754
mtc.TenantInfo = tenant2;
68-
option = schemeProvider.GetDefaultChallengeSchemeAsync().Result;
69-
Assert.Equal("tenant2Scheme", option?.Name);
55+
option = await schemeProvider.GetDefaultChallengeSchemeAsync();
56+
57+
Assert.NotNull(option);
58+
Assert.Equal("tenant2Scheme", option.Name);
7059
}
7160
}
7261
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright Finbuckle LLC, Andrew White, and Contributors.
22
// Refer to the solution LICENSE file for more information.
33

4+
using System.Collections.Generic;
45
using System.Threading.Tasks;
56
using Microsoft.AspNetCore.Http;
67
using Microsoft.Extensions.DependencyInjection;
@@ -12,48 +13,100 @@ namespace Finbuckle.MultiTenant.AspNetCore.Test
1213
public class MultiTenantMiddlewareShould
1314
{
1415
[Fact]
15-
public async void UseResolver()
16+
public async void SetHttpContextItemIfTenantFound()
1617
{
1718
var services = new ServiceCollection();
1819
services.AddMultiTenant<TenantInfo>().
1920
WithStaticStrategy("initech").
2021
WithInMemoryStore();
2122
var sp = services.BuildServiceProvider();
22-
var store = sp.GetService<IMultiTenantStore<TenantInfo>>();
23-
store!.TryAddAsync(new TenantInfo { Id = "initech", Identifier = "initech" }).Wait();
23+
var store = sp.GetRequiredService<IMultiTenantStore<TenantInfo>>();
24+
await store.TryAddAsync(new TenantInfo { Id = "initech", Identifier = "initech" });
2425

2526
var context = new Mock<HttpContext>();
2627
context.Setup(c => c.RequestServices).Returns(sp);
2728

28-
var mw = new MultiTenantMiddleware(_ => {
29-
Assert.Equal("initech", context.Object.RequestServices.GetService<ITenantInfo>()!.Id);
30-
return Task.CompletedTask;
31-
});
29+
var itemsDict = new Dictionary<object, object?>();
30+
context.Setup(c => c.Items).Returns(itemsDict);
31+
32+
var mw = new MultiTenantMiddleware(_ => Task.CompletedTask);
3233

3334
await mw.Invoke(context.Object);
35+
36+
var mtc = (IMultiTenantContext<TenantInfo>?)context.Object.Items[typeof(IMultiTenantContext)];
37+
38+
Assert.NotNull(mtc?.TenantInfo);
39+
Assert.Equal("initech", mtc.TenantInfo.Id);
3440
}
3541

3642
[Fact]
37-
public async void SetMultiTenantContextAccessor()
43+
public async void SetTenantAccessor()
3844
{
3945
var services = new ServiceCollection();
4046
services.AddMultiTenant<TenantInfo>().
4147
WithStaticStrategy("initech").
4248
WithInMemoryStore();
4349
var sp = services.BuildServiceProvider();
44-
var store = sp.GetService<IMultiTenantStore<TenantInfo>>();
45-
store!.TryAddAsync(new TenantInfo { Id = "initech", Identifier = "initech" }).Wait();
50+
var store = sp.GetRequiredService<IMultiTenantStore<TenantInfo>>();
51+
await store.TryAddAsync(new TenantInfo { Id = "initech", Identifier = "initech" });
52+
53+
var context = new Mock<HttpContext>();
54+
context.Setup(c => c.RequestServices).Returns(sp);
55+
56+
var itemsDict = new Dictionary<object, object?>();
57+
context.Setup(c => c.Items).Returns(itemsDict);
58+
59+
IMultiTenantContext<TenantInfo>? mtc = null;
60+
61+
var mw = new MultiTenantMiddleware(httpContext =>
62+
{
63+
// have to check in this Async chain...
64+
var accessor = context.Object.RequestServices.GetRequiredService<IMultiTenantContextAccessor<TenantInfo>>();
65+
mtc = accessor.MultiTenantContext;
66+
67+
return Task.CompletedTask;
68+
});
69+
70+
await mw.Invoke(context.Object);
71+
72+
Assert.NotNull(mtc);
73+
Assert.True(mtc.IsResolved);
74+
Assert.NotNull(mtc.TenantInfo);
75+
}
76+
77+
[Fact]
78+
public async void NotSetTenantAccessorIfNoTenant()
79+
{
80+
var services = new ServiceCollection();
81+
services.AddMultiTenant<TenantInfo>().
82+
WithStaticStrategy("not_initech").
83+
WithInMemoryStore();
84+
var sp = services.BuildServiceProvider();
85+
var store = sp.GetRequiredService<IMultiTenantStore<TenantInfo>>();
86+
await store.TryAddAsync(new TenantInfo { Id = "initech", Identifier = "initech" });
4687

4788
var context = new Mock<HttpContext>();
4889
context.Setup(c => c.RequestServices).Returns(sp);
90+
91+
var itemsDict = new Dictionary<object, object?>();
92+
context.Setup(c => c.Items).Returns(itemsDict);
93+
94+
IMultiTenantContext<TenantInfo>? mtc = null;
95+
96+
var mw = new MultiTenantMiddleware(httpContext =>
97+
{
98+
// have to check in this Async chain...
99+
var accessor = context.Object.RequestServices.GetRequiredService<IMultiTenantContextAccessor<TenantInfo>>();
100+
mtc = accessor.MultiTenantContext;
49101

50-
var mw = new MultiTenantMiddleware(c => {
51-
var accessor = c.RequestServices.GetRequiredService<IMultiTenantContextAccessor<TenantInfo>>();
52-
Assert.NotNull(accessor.MultiTenantContext);
53102
return Task.CompletedTask;
54103
});
55104

56105
await mw.Invoke(context.Object);
106+
107+
Assert.NotNull(mtc);
108+
Assert.False(mtc.IsResolved);
109+
Assert.Null(mtc.TenantInfo);
57110
}
58111
}
59112
}

‎test/Finbuckle.MultiTenant.Test/DependencyInjection/ServiceCollectionExtensionsShould.cs

+1-40
Original file line numberDiff line numberDiff line change
@@ -34,45 +34,6 @@ public void RegisterITenantResolverGenericInDi()
3434
Assert.Equal(ServiceLifetime.Scoped, service!.Lifetime);
3535
}
3636

37-
[Fact]
38-
public void RegisterIMultiTenantContextInDi()
39-
{
40-
var services = new ServiceCollection();
41-
services.AddMultiTenant<TenantInfo>();
42-
43-
var service = services.SingleOrDefault(s => s.Lifetime == ServiceLifetime.Scoped &&
44-
s.ServiceType == typeof(IMultiTenantContext<TenantInfo>));
45-
46-
Assert.NotNull(service);
47-
Assert.Equal(ServiceLifetime.Scoped, service!.Lifetime);
48-
}
49-
50-
[Fact]
51-
public void RegisterTenantInfoInDi()
52-
{
53-
var services = new ServiceCollection();
54-
services.AddMultiTenant<TenantInfo>();
55-
56-
var service = services.SingleOrDefault(s => s.Lifetime == ServiceLifetime.Scoped &&
57-
s.ServiceType == typeof(TenantInfo));
58-
59-
Assert.NotNull(service);
60-
Assert.Equal(ServiceLifetime.Scoped, service!.Lifetime);
61-
}
62-
63-
[Fact]
64-
public void RegisterITenantInfoInDi()
65-
{
66-
var services = new ServiceCollection();
67-
services.AddMultiTenant<TenantInfo>();
68-
69-
var service = services.SingleOrDefault(s => s.Lifetime == ServiceLifetime.Scoped &&
70-
s.ServiceType == typeof(ITenantInfo));
71-
72-
Assert.NotNull(service);
73-
Assert.Equal(ServiceLifetime.Scoped, service!.Lifetime);
74-
}
75-
7637
[Fact]
7738
public void RegisterIMultiTenantContextAccessorInDi()
7839
{
@@ -106,7 +67,7 @@ public void RegisterMultiTenantOptionsInDi()
10667
var services = new ServiceCollection();
10768
services.AddMultiTenant<TenantInfo>();
10869

109-
var service = services.SingleOrDefault(s => s.Lifetime == ServiceLifetime.Singleton &&
70+
var service = services.FirstOrDefault(s => s.Lifetime == ServiceLifetime.Singleton &&
11071
s.ServiceType == typeof(IConfigureOptions<MultiTenantOptions>));
11172

11273
Assert.NotNull(service);

‎test/Finbuckle.MultiTenant.Test/Finbuckle.MultiTenant.Test.csproj

-4
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,4 @@
3939
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
4040
</None>
4141
</ItemGroup>
42-
43-
<ItemGroup>
44-
<Folder Include="Extensions\" />
45-
</ItemGroup>
4642
</Project>

‎test/Finbuckle.MultiTenant.Test/MultiTenantContextShould.cs

+13-10
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,39 @@ namespace Finbuckle.MultiTenant.Test;
55
public class MultiTenantContextShould
66
{
77
[Fact]
8-
public void ReturnFalseInHasResolvedTenantIfTenantInfoIsNull()
8+
public void ReturnFalseForIsResolvedIfTenantInfoIsNull()
99
{
1010
IMultiTenantContext<TenantInfo> context = new MultiTenantContext<TenantInfo>();
11-
Assert.False(context.HasResolvedTenant);
11+
Assert.False(context.IsResolved);
1212
}
1313

1414
[Fact]
15-
public void ReturnTrueInHasResolvedTenantIfTenantInfoIsNotNull()
15+
public void ReturnTrueIsResolvedIfTenantInfoIsNotNull()
1616
{
17-
IMultiTenantContext<TenantInfo> context = new MultiTenantContext<TenantInfo>();
18-
context.TenantInfo = new TenantInfo();
19-
Assert.True(context.HasResolvedTenant);
17+
var context = new MultiTenantContext<TenantInfo>
18+
{
19+
TenantInfo = new TenantInfo()
20+
};
21+
22+
Assert.True(context.IsResolved);
2023
}
2124

2225
[Fact]
23-
public void ReturnFalseInHasResolvedTenantIfTenantInfoIsNull_NonGeneric()
26+
public void ReturnFalseForIsResolvedIfTenantInfoIsNull_NonGeneric()
2427
{
2528
IMultiTenantContext context = new MultiTenantContext<TenantInfo>();
26-
Assert.False(context.HasResolvedTenant);
29+
Assert.False(context.IsResolved);
2730
}
2831

2932
[Fact]
30-
public void ReturnTrueInHasResolvedTenantIfTenantInfoIsNotNull_NonGeneric()
33+
public void ReturnTrueIsResolvedIfTenantInfoIsNotNull_NonGeneric()
3134
{
3235
var context = new MultiTenantContext<TenantInfo>
3336
{
3437
TenantInfo = new TenantInfo()
3538
};
3639

3740
IMultiTenantContext iContext = context;
38-
Assert.True(iContext.HasResolvedTenant);
41+
Assert.True(iContext.IsResolved);
3942
}
4043
}

‎test/Finbuckle.MultiTenant.Test/Stores/ConfigurationStoreShould.cs

+7-4
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,22 @@ public void ThrowIfInvalidSection()
5252
}
5353

5454
[Fact]
55-
public void IgnoreCaseWhenGettingTenantInfoFromStoreByIdentifier()
55+
public async Task IgnoreCaseWhenGettingTenantInfoFromStoreByIdentifier()
5656
{
5757
var store = CreateTestStore();
5858

59-
Assert.Equal("initech", store.TryGetByIdentifierAsync("INITECH").Result!.Identifier);
59+
var tenant = await store.TryGetByIdentifierAsync("INITECH");
60+
61+
Assert.NotNull(tenant);
62+
Assert.Equal("initech", tenant.Identifier);
6063
}
6164

6265
[Fact]
63-
public void ThrowWhenTryingToGetIdentifierGivenNullIdentifier()
66+
public async Task ThrowWhenTryingToGetIdentifierGivenNullIdentifier()
6467
{
6568
var store = CreateTestStore();
6669

67-
Assert.ThrowsAsync<ArgumentNullException>(async () => await store.TryGetByIdentifierAsync(null!));
70+
await Assert.ThrowsAsync<ArgumentNullException>(async () => await store.TryGetByIdentifierAsync(null!));
6871
}
6972

7073
// Basic store functionality tested in MultiTenantStoresShould.cs

‎test/Finbuckle.MultiTenant.Test/TenantResolverShould.cs

-74
Original file line numberDiff line numberDiff line change
@@ -89,28 +89,6 @@ public void ReturnMultiTenantContext()
8989
Assert.IsType<ConfigurationStore<TenantInfo>>(result?.StoreInfo!.Store);
9090
}
9191

92-
[Fact]
93-
public void ReturnNullMultiTenantContextGivenNoStrategies()
94-
{
95-
var configBuilder = new ConfigurationBuilder();
96-
configBuilder.AddJsonFile("ConfigurationStoreTestSettings.json");
97-
var configuration = configBuilder.Build();
98-
99-
var services = new ServiceCollection();
100-
services.AddSingleton<IConfiguration>(configuration);
101-
102-
services.
103-
AddMultiTenant<TenantInfo>().
104-
WithInMemoryStore().
105-
WithConfigurationStore();
106-
107-
var sp = services.BuildServiceProvider();
108-
var resolver = sp.GetRequiredService<ITenantResolver<TenantInfo>>();
109-
var result = resolver.ResolveAsync(new object()).Result;
110-
111-
Assert.Null(result);
112-
}
113-
11492
[Fact]
11593
public void ThrowGivenStaticStrategyWithNullIdentifierArgument()
11694
{
@@ -163,58 +141,6 @@ public void IgnoreSomeIdentifiersFromOptions()
163141
Assert.IsType<ConfigurationStore<TenantInfo>>(result!.StoreInfo!.Store);
164142
}
165143

166-
[Fact]
167-
public void ReturnNullIfNoStrategySuccess()
168-
{
169-
var configBuilder = new ConfigurationBuilder();
170-
configBuilder.AddJsonFile("ConfigurationStoreTestSettings.json");
171-
var configuration = configBuilder.Build();
172-
173-
var services = new ServiceCollection();
174-
services.AddSingleton<IConfiguration>(configuration);
175-
176-
services.
177-
AddMultiTenant<TenantInfo>().
178-
WithDelegateStrategy(_ => Task.FromResult<string?>(null!)).
179-
WithInMemoryStore().
180-
WithConfigurationStore();
181-
var sp = services.BuildServiceProvider();
182-
sp.GetServices<IMultiTenantStore<TenantInfo>>().
183-
Single(i => i.GetType() == typeof(InMemoryStore<TenantInfo>)).TryAddAsync(new TenantInfo { Id = "null", Identifier = "null" }).Wait();
184-
185-
var resolver = sp.GetRequiredService<ITenantResolver<TenantInfo>>();
186-
var result = resolver.ResolveAsync(new object()).Result;
187-
188-
Assert.Null(result);
189-
}
190-
191-
[Fact]
192-
public void ReturnNullIfNoStoreSuccess()
193-
{
194-
var configBuilder = new ConfigurationBuilder();
195-
configBuilder.AddJsonFile("ConfigurationStoreTestSettings.json");
196-
var configuration = configBuilder.Build();
197-
198-
var services = new ServiceCollection();
199-
services.AddSingleton<IConfiguration>(configuration);
200-
201-
services.AddLogging().
202-
AddMultiTenant<TenantInfo>().
203-
WithDelegateStrategy(_ => Task.FromResult<string?>("not-found")).
204-
WithStaticStrategy("also-not-found").
205-
WithInMemoryStore().
206-
WithConfigurationStore();
207-
var sp = services.BuildServiceProvider();
208-
sp.
209-
GetServices<IMultiTenantStore<TenantInfo>>().
210-
Single(i => i.GetType() == typeof(InMemoryStore<TenantInfo>)).TryAddAsync(new TenantInfo { Id = "null", Identifier = "null" }).Wait();
211-
212-
var resolver = sp.GetRequiredService<ITenantResolver<TenantInfo>>();
213-
var result = resolver.ResolveAsync(new object()).Result;
214-
215-
Assert.Null(result);
216-
}
217-
218144
[Fact]
219145
public void CallOnTenantResolvedEventIfSuccess()
220146
{

0 commit comments

Comments
 (0)
Please sign in to comment.