Skip to content

Commit

Permalink
feat: refactor DI and improve nullability
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
AndrewTriesToCode committed Apr 21, 2024
1 parent e9940a7 commit eca24bf
Show file tree
Hide file tree
Showing 30 changed files with 430 additions and 411 deletions.
4 changes: 2 additions & 2 deletions docs/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ using Finbuckle.MultiTenant;

var builder = WebApplication.CreateBuilder(args);

// ...add app services
// add app services...
// add Finbuckle.MultiTenant services
builder.Services.AddMultiTenant<TenantInfo>()
Expand All @@ -38,7 +38,7 @@ var app = builder.Build();
// add the Finbuckle.MultiTenant middleware
app.UseMultiTenant();

// ...add other middleware
// add other middleware...
app.Run();
```
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<PackageTags>finbuckle;multitenant;multitenancy;aspnet;aspnetcore;entityframework;entityframework-core;efcore</PackageTags>
<PackageIcon>finbuckle-128x128.png</PackageIcon>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>CS1591</NoWarn>
<!-- <NoWarn>CS1591</NoWarn>-->
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright Finbuckle LLC, Andrew White, and Contributors.
// Refer to the solution LICENSE file for more information.

using System;
using Finbuckle.MultiTenant.Abstractions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

Expand All @@ -13,38 +15,59 @@ namespace Finbuckle.MultiTenant
public static class FinbuckleHttpContextExtensions
{
/// <summary>
/// Returns the current MultiTenantContext or null if there is none.
/// Returns the current MultiTenantContext.
/// </summary>
/// <param name="httpContext">The HttpContext instance.</param>
/// <typeparam name="TTenantInfo">The ITenantInfo implementation type.</typeparam>
public static IMultiTenantContext<TTenantInfo> GetMultiTenantContext<TTenantInfo>(this HttpContext httpContext)
where TTenantInfo : class, ITenantInfo, new()
where TTenantInfo : class, ITenantInfo, new()
{
var services = httpContext.RequestServices;
var context = services.GetRequiredService<IMultiTenantContextAccessor<TTenantInfo>>();
return context.MultiTenantContext;
if (httpContext.Items.TryGetValue(typeof(IMultiTenantContext), out var mtc) && mtc is not null)
return (IMultiTenantContext<TTenantInfo>)mtc;

mtc = new MultiTenantContext<TTenantInfo>();
httpContext.Items[typeof(IMultiTenantContext)] = mtc;

return (IMultiTenantContext<TTenantInfo>)mtc;
}

/// <summary>
/// Returns the current generic TTenantInfo instance or null if there is none.
/// </summary>
/// <param name="httpContext">The HttpContext instance.</param>
/// <typeparam name="TTenantInfo">The ITenantInfo implementation type.</typeparam>
public static TTenantInfo? GetTenantInfo<TTenantInfo>(this HttpContext httpContext)
where TTenantInfo : class, ITenantInfo, new() =>
httpContext.GetMultiTenantContext<TTenantInfo>().TenantInfo;


/// <summary>
/// Sets the provided TenantInfo on the MultiTenantContext.
/// Sets StrategyInfo and StoreInfo on the MultiTenant Context to null.
/// Optionally resets the current dependency injection service provider.
/// </summary>
public static bool TrySetTenantInfo<T>(this HttpContext httpContext, T tenantInfo, bool resetServiceProviderScope)
where T : class, ITenantInfo, new()
/// <param name="httpContext">The HttpContext instance.</param>
/// <param name="tenantInfo">The tenant info instance to set as current.</param>
/// <param name="resetServiceProviderScope">Creates a new service provider scope if true.</param>
/// <typeparam name="TTenantInfo">The ITenantInfo implementation type.</typeparam>
public static void SetTenantInfo<TTenantInfo>(this HttpContext httpContext, TTenantInfo tenantInfo,
bool resetServiceProviderScope)
where TTenantInfo : class, ITenantInfo, new()
{
if (resetServiceProviderScope)
httpContext.RequestServices = httpContext.RequestServices.CreateScope().ServiceProvider;

var multitenantContext = new MultiTenantContext<T>
var multiTenantContext = new MultiTenantContext<TTenantInfo>
{
TenantInfo = tenantInfo,
StrategyInfo = null,
StoreInfo = null
};

var accessor = httpContext.RequestServices.GetRequiredService<IMultiTenantContextAccessor<T>>();
accessor.MultiTenantContext = multitenantContext;
var setter = httpContext.RequestServices.GetRequiredService<IMultiTenantContextSetter>();
setter.MultiTenantContext = multiTenantContext;

return true;
httpContext.Items[typeof(IMultiTenantContext)] = multiTenantContext;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
// Refer to the solution LICENSE file for more information.

using System.Threading.Tasks;
using Finbuckle.MultiTenant.Abstractions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace Finbuckle.MultiTenant.AspNetCore
{
/// <summary>
/// Middleware for resolving the TenantContext and storing it in HttpContext.
/// Middleware for resolving the MultiTenantContext and storing it in HttpContext.
/// </summary>
internal class MultiTenantMiddleware
{
Expand All @@ -21,14 +22,14 @@ public MultiTenantMiddleware(RequestDelegate next)

public async Task Invoke(HttpContext context)
{
var accessor = context.RequestServices.GetRequiredService<IMultiTenantContextAccessor>();

if (accessor.MultiTenantContext == null)
{
var resolver = context.RequestServices.GetRequiredService<ITenantResolver>();
var multiTenantContext = await resolver.ResolveAsync(context);
accessor.MultiTenantContext = multiTenantContext;
}
var mtcAccessor = context.RequestServices.GetRequiredService<IMultiTenantContextAccessor>();
var mtcSetter = context.RequestServices.GetRequiredService<IMultiTenantContextSetter>();

var resolver = context.RequestServices.GetRequiredService<ITenantResolver>();

var multiTenantContext = await resolver.ResolveAsync(context);
mtcSetter.MultiTenantContext = multiTenantContext;
context.Items[typeof(IMultiTenantContext)] = multiTenantContext;

await next(context);
}
Expand Down

This file was deleted.

40 changes: 15 additions & 25 deletions src/Finbuckle.MultiTenant/Abstractions/IMultiTenantContext.cs
Original file line number Diff line number Diff line change
@@ -1,55 +1,45 @@
// Copyright Finbuckle LLC, Andrew White, and Contributors.
// Refer to the solution LICENSE file for more information.

// ReSharper disable once CheckNamespace
namespace Finbuckle.MultiTenant;

/// <summary>
/// Non-generic interface for the multitenant context.
/// Non-generic interface for the MultiTenantContext.
/// </summary>
public interface IMultiTenantContext
{
/// <summary>
/// Information about the tenant for this context.
/// </summary>
ITenantInfo? TenantInfo { get; }

/// <summary>
/// True if a non-null tenant has been resolved.
/// True if a tenant has been resolved and TenantInfo is not null.
/// </summary>
bool HasResolvedTenant => TenantInfo != null;
bool IsResolved { get; }

/// <summary>
/// Information about the multitenant strategies for this context.
/// Information about the MultiTenant strategies for this context.
/// </summary>
StrategyInfo? StrategyInfo { get; }
}



/// <summary>
/// Generic interface for the multitenant context.
/// Generic interface for the multi-tenant context.
/// </summary>
/// <typeparam name="T">The ITenantInfo implementation type.</typeparam>
public interface IMultiTenantContext<T>
where T : class, ITenantInfo, new()
/// <typeparam name="TTenantInfo">The ITenantInfo implementation type.</typeparam>
public interface IMultiTenantContext<TTenantInfo> : IMultiTenantContext
where TTenantInfo : class, ITenantInfo, new()
{
/// <summary>
/// Information about the tenant for this context.
/// </summary>
T? TenantInfo { get; set; }
new TTenantInfo? TenantInfo { get; }

/// <summary>
/// Returns true if a non-null tenant has been resolved.
/// </summary>
bool HasResolvedTenant => TenantInfo != null;

/// <summary>
/// Information about the multitenant strategies for this context.
/// </summary>
StrategyInfo? StrategyInfo { get; set; }


/// <summary>
/// Information about the multitenant store(s) for this context.
/// Information about the MultiTenant stores for this context.
/// </summary>
StoreInfo<T>? StoreInfo { get; set; }
StoreInfo<TTenantInfo>? StoreInfo { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,25 @@
namespace Finbuckle.MultiTenant;

/// <summary>
/// Provides access the current MultiTenantContext.
/// Provides access the current MultiTenantContext.
/// </summary>
public interface IMultiTenantContextAccessor
{
/// <summary>
/// Gets or sets the current MultiTenantContext.
/// Gets the current MultiTenantContext.
/// </summary>
IMultiTenantContext? MultiTenantContext { get; set; }
IMultiTenantContext MultiTenantContext { get; }
}

/// <summary>
/// Provides access the current MultiTenantContext.
/// Provides access the current MultiTenantContext.
/// </summary>
/// <typeparam name="T">The ITenantInfo implementation type.</typeparam>
public interface IMultiTenantContextAccessor<T> where T : class, ITenantInfo, new()
/// <typeparam name="TTenantInfo">The ITenantInfo implementation type.</typeparam>
public interface IMultiTenantContextAccessor<TTenantInfo> : IMultiTenantContextAccessor
where TTenantInfo : class, ITenantInfo, new()
{
/// <summary>
/// Gets or sets the current MultiTenantContext.
/// Gets the current MultiTenantContext.
/// </summary>
IMultiTenantContext<T>? MultiTenantContext { get; set; }
new IMultiTenantContext<TTenantInfo> MultiTenantContext { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Finbuckle.MultiTenant.Abstractions;

/// <summary>
/// Interface used to set the MultiTenantContext. This is an implementation detail and not intended for general use.
/// </summary>
public interface IMultiTenantContextSetter
{
IMultiTenantContext MultiTenantContext { set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public interface IMultiTenantStrategy
/// <summary>
/// Method for implementations to control how the identifier is determined.
/// </summary>
/// <param name="context"></param>
/// <param name="context">The context object used to determine an identifier.</param>
/// <returns>The found identifier or null.</returns>
Task<string?> GetIdentifierAsync(object context);

Expand Down
3 changes: 3 additions & 0 deletions src/Finbuckle.MultiTenant/Abstractions/ITenantInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
// ReSharper disable once CheckNamespace
namespace Finbuckle.MultiTenant;

/// <summary>
/// Interface for basic tenant information.
/// </summary>
public interface ITenantInfo
{

Expand Down
32 changes: 16 additions & 16 deletions src/Finbuckle.MultiTenant/Abstractions/ITenantResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,31 @@ public interface ITenantResolver
/// Performs tenant resolution within the given context.
/// </summary>
/// <param name="context">The context for tenant resolution.</param>
/// <returns>The MultiTenantContext or null if none resolved.</returns>
Task<IMultiTenantContext?> ResolveAsync(object context);
/// <returns>The MultiTenantContext.</returns>
Task<IMultiTenantContext> ResolveAsync(object context);

/// <summary>
/// Contains a list of MultiTenant strategies used for tenant resolution.
/// </summary>
public IEnumerable<IMultiTenantStrategy> Strategies { get; set; }
}

/// <summary>
/// Resolves the current tenant.
/// </summary>
public interface ITenantResolver<T>
where T : class, ITenantInfo, new()
/// <typeparam name="TTenantInfo">The ITenantInfo implementation type.</typeparam>
public interface ITenantResolver<TTenantInfo> : ITenantResolver
where TTenantInfo : class, ITenantInfo, new()
{
/// <summary>
/// Gets the multitenant strategies used for tenant resolution.
/// Performs tenant resolution within the given context.
/// </summary>
IEnumerable<IMultiTenantStrategy> Strategies { get; }

/// <param name="context">The context for tenant resolution.</param>
/// <returns>The MultiTenantContext.</returns>
new Task<IMultiTenantContext<TTenantInfo>> ResolveAsync(object context);

/// <summary>
/// Get;s the multitenant stores used for tenant resolution.
/// Contains a list of MultiTenant stores used for tenant resolution.
/// </summary>
IEnumerable<IMultiTenantStore<T>> Stores { get; }

/// <summary>
/// Performs tenant resolution within the given context.
/// </summary>
/// <param name="context">The context for tenant resolution.</param>
/// <returns>The MultiTenantContext or null if none resolved.</returns>
Task<IMultiTenantContext<T>?> ResolveAsync(object context);
public IEnumerable<IMultiTenantStore<TTenantInfo>> Stores { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Refer to the solution LICENSE file for more information.

using Finbuckle.MultiTenant;
using Finbuckle.MultiTenant.Abstractions;
using Finbuckle.MultiTenant.Internal;
using Finbuckle.MultiTenant.Options;
using Microsoft.Extensions.DependencyInjection.Extensions;
Expand Down Expand Up @@ -30,20 +31,15 @@ public static class FinbuckleServiceCollectionExtensions
services.AddScoped<ITenantResolver>(
sp => (ITenantResolver)sp.GetRequiredService<ITenantResolver<TTenantInfo>>());

services.AddScoped<IMultiTenantContext<TTenantInfo>>(sp =>
sp.GetRequiredService<IMultiTenantContextAccessor<TTenantInfo>>().MultiTenantContext!);

services.AddScoped<TTenantInfo>(sp =>
sp.GetRequiredService<IMultiTenantContextAccessor<TTenantInfo>>().MultiTenantContext?.TenantInfo!);
services.AddScoped<ITenantInfo>(sp => sp.GetService<TTenantInfo>()!);

// TODO this might require instance to ensure it already exists when needed
services
.AddSingleton<IMultiTenantContextAccessor<TTenantInfo>,
AsyncLocalMultiTenantContextAccessor<TTenantInfo>>();
services.AddSingleton<IMultiTenantContextAccessor<TTenantInfo>,
AsyncLocalMultiTenantContextAccessor<TTenantInfo>>();
services.AddSingleton<IMultiTenantContextAccessor>(sp =>
(IMultiTenantContextAccessor)sp.GetRequiredService<IMultiTenantContextAccessor<TTenantInfo>>());
sp.GetRequiredService<IMultiTenantContextAccessor<TTenantInfo>>());

services.AddSingleton<IMultiTenantContextSetter>(sp =>
(IMultiTenantContextSetter)sp.GetRequiredService<IMultiTenantContextAccessor>());

services.Configure<MultiTenantOptions>(options => options.TenantInfoType = typeof(TTenantInfo));
services.Configure(config);

return new FinbuckleMultiTenantBuilder<TTenantInfo>(services);
Expand Down
Loading

0 comments on commit eca24bf

Please sign in to comment.