Skip to content

Commit

Permalink
Partial
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewTriesToCode committed Jun 30, 2023
1 parent 79d9124 commit 75582bb
Show file tree
Hide file tree
Showing 21 changed files with 274 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public interface IMultiTenantContext<T>
/// Information about the tenant for this context.
/// </summary>
T? TenantInfo { get; set; }

/// <summary>
/// Returns true if a non-null tenant has been resolved.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
// Copyright Finbuckle LLC, Andrew White, and Contributors.
// Refer to the solution LICENSE file for more inforation.

// ReSharper disable once CheckNamespace

namespace Finbuckle.MultiTenant;

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

/// <summary>
/// Provides access the current MultiTenantContext.
/// </summary>
/// <typeparam name="T">The ITenantInfo implementation type.</typeparam>
public interface IMultiTenantContextAccessor<T> where T : class, ITenantInfo, new()
{
/// <summary>
/// Gets or sets the current MultiTenantContext.
/// </summary>
IMultiTenantContext<T>? MultiTenantContext { get; set; }
}
10 changes: 5 additions & 5 deletions src/Finbuckle.MultiTenant/Abstractions/IMultiTenantStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,30 +31,30 @@ public interface IMultiTenantStore<TTenantInfo> where TTenantInfo : class, ITena
/// <summary>
/// Try to remove the TTenantInfo from the store.
/// </summary>
/// <param name="identifier">Identifier for the tenant to remove. </param>
/// <returns>True if successfully removed</returns>
/// <param name="identifier">Identifier for the tenant to remove.</param>
/// <returns>True if successfully removed.</returns>
Task<bool> TryRemoveAsync(string identifier);

/// <summary>
/// Retrieve the TTenantInfo for a given identifier.
/// </summary>
/// <param name="identifier">Identifier for the tenant to retrieve.</param>
/// <returns>Returns TTenantInfo instance if found or null.</returns>
/// <returns>The found TTenantInfo instance or null if none found.</returns>
/// TODO make obsolete
Task<TTenantInfo?> TryGetByIdentifierAsync(string identifier);

/// <summary>
/// Retrieve the TTenantInfo for a given tenant Id.
/// </summary>
/// <param name="id">TenantId for the tenant to retrieve.</param>
/// <returns>Returns TTenantInfo instance if found or null.</returns>
/// <returns>The found TTenantInfo instance or null if none found.</returns>
/// TODO make obsolete
Task<TTenantInfo?> TryGetAsync(string id);


/// <summary>
/// Retrieve all the TTenantInfo's from the store.
/// </summary>
/// <returns>IEnumerable of all tenants in the store.</returns>
/// <returns>An IEnumerable of all tenants in the store.</returns>
Task<IEnumerable<TTenantInfo>> GetAllAsync();
}
11 changes: 6 additions & 5 deletions src/Finbuckle.MultiTenant/Abstractions/IMultiTenantStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@

using System.Threading.Tasks;

// ReSharper disable once CheckNamespace
namespace Finbuckle.MultiTenant;

/// <summary>
/// The interface for determining the tenant idenfitider.
/// Determines the tenant identifier.
/// </summary>
public interface IMultiTenantStrategy
{
/// <summary>
/// Method for implemenations to control how the identifier is determined.
/// Method for implementations to control how the identifier is determined.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
/// <returns>The found identifier or null.</returns>
Task<string?> GetIdentifierAsync(object context);

/// <summary>
/// Determines strategy execution order. Normally handled in the order registered.
/// Strategy execution order priority. Low values are executed first. Equal values are executed in order of registration.
/// </summary>
int Priority => 0;
}
24 changes: 24 additions & 0 deletions src/Finbuckle.MultiTenant/Abstractions/ITenantInfo.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
// Copyright Finbuckle LLC, Andrew White, and Contributors.
// Refer to the solution LICENSE file for more inforation.

// ReSharper disable once CheckNamespace
namespace Finbuckle.MultiTenant;

public interface ITenantInfo
{

/// <summary>
/// Gets or sets a unique id for the tenant.
/// </summary>
/// <remarks>
/// Unlike the Identifier, the id is never intended to be changed.
/// </remarks>
string? Id { get; set; }

/// <summary>
/// Gets or sets a unique identifier for the tenant.
/// </summary>
/// <remarks>
/// The Identifier is intended for use during tenant resolution and format is determined by convention. For example
/// a web based strategy may require URL friendly identifiers. Identifiers can be changed if needed.
/// </remarks>
string? Identifier { get; set; }

/// <summary>
/// Gets or sets a display friendly name for the tenant.
/// </summary>
string? Name { get; set; }

/// <summary>
/// Gets or sets a connection string for the tenant.
/// </summary>
string? ConnectionString { get; set; }
}
25 changes: 25 additions & 0 deletions src/Finbuckle.MultiTenant/Abstractions/ITenantResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,43 @@
using System.Collections.Generic;
using System.Threading.Tasks;

// ReSharper disable once CheckNamespace
namespace Finbuckle.MultiTenant;

/// <summary>
/// Resolves the current tenant.
/// </summary>
public interface ITenantResolver
{
/// <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?> ResolveAsync(object context);
}

/// <summary>
/// Resolves the current tenant.
/// </summary>
public interface ITenantResolver<T>
where T : class, ITenantInfo, new()
{
/// <summary>
/// Gets the multitenant strategies used for tenant resolution.
/// </summary>
IEnumerable<IMultiTenantStrategy> Strategies { get; }


/// <summary>
/// Get;s the 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);
}
84 changes: 43 additions & 41 deletions src/Finbuckle.MultiTenant/DependencyInjection/MultiTenantBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,24 @@
using Finbuckle.MultiTenant;
using Finbuckle.MultiTenant.Options;

// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection;

public partial class FinbuckleMultiTenantBuilder<TTenantInfo> where TTenantInfo : class, ITenantInfo, new()
/// <summary>
/// Builder class for Finbuckle.MultiTenant configuration.
/// </summary>
/// <typeparam name="T">A type implementing ITenantInfo.</typeparam>
public class FinbuckleMultiTenantBuilder<T> where T : class, ITenantInfo, new()
{
/// <summary>
/// Gets or sets the IServiceCollection instance used by the builder.
/// </summary>
public IServiceCollection Services { get; set; }

/// <summary>
/// Construction a new instance of FinbuckleMultiTenantBuilder.
/// </summary>
/// <param name="services">An IServiceCollection instance to be used by the builder.</param>
public FinbuckleMultiTenantBuilder(IServiceCollection services)
{
Services = services;
Expand All @@ -24,26 +36,10 @@ public FinbuckleMultiTenantBuilder(IServiceCollection services)
/// <param name="tenantConfigureOptions">The configuration action to be run for each tenant.</param>
/// <returns>The same MultiTenantBuilder passed into the method.</returns>
/// <remarks>This is similar to `ConfigureAll` in that it applies to all named and unnamed options of the type.</remarks>
public FinbuckleMultiTenantBuilder<TTenantInfo> WithPerTenantOptions<TOptions>(
Action<TOptions, TTenantInfo> tenantConfigureOptions) where TOptions : class, new()
public FinbuckleMultiTenantBuilder<T> WithPerTenantOptions<TOptions>(
Action<TOptions, T> tenantConfigureOptions) where TOptions : class, new()
{
// if (tenantConfigureOptions == null)
// {
// throw new ArgumentNullException(nameof(tenantConfigureOptions));
// }
//
// // Handles multiplexing cached options.
// Services.TryAddSingleton<IOptionsMonitorCache<TOptions>, MultiTenantOptionsCache<TOptions, TTenantInfo>>();
//
// // Necessary to apply tenant options in between configuration and postconfiguration
// Services
// .AddSingleton<ITenantConfigureOptions<TOptions, TTenantInfo>,
// TenantConfigureOptions<TOptions, TTenantInfo>>(sp =>
// new TenantConfigureOptions<TOptions, TTenantInfo>(tenantConfigureOptions));
// Services.TryAddTransient<IOptionsFactory<TOptions>, MultiTenantOptionsFactory<TOptions, TTenantInfo>>();
// Services.TryAddScoped<IOptionsSnapshot<TOptions>>(sp => BuildOptionsManager<TOptions>(sp));
// Services.TryAddSingleton<IOptions<TOptions>>(sp => BuildOptionsManager<TOptions>(sp));

// TODO maybe change this to string empty so null an be used for all options, note remarks.
return WithPerTenantNamedOptions(null, tenantConfigureOptions);
}

Expand All @@ -53,58 +49,63 @@ public FinbuckleMultiTenantBuilder(IServiceCollection services)
/// <param name="name">The option name.</param>
/// <param name="tenantConfigureNamedOptions">The configuration action to be run for each tenant.</param>
/// <returns>The same MultiTenantBuilder passed into the method.</returns>
public FinbuckleMultiTenantBuilder<TTenantInfo> WithPerTenantNamedOptions<TOptions>(string? name,
Action<TOptions, TTenantInfo> tenantConfigureNamedOptions) where TOptions : class, new()
// ReSharper disable once MemberCanBePrivate.Global
public FinbuckleMultiTenantBuilder<T> WithPerTenantNamedOptions<TOptions>(string? name,
Action<TOptions, T> tenantConfigureNamedOptions) where TOptions : class, new()
{
if (tenantConfigureNamedOptions == null)
{
throw new ArgumentNullException(nameof(tenantConfigureNamedOptions));
}

// Handles multiplexing cached options.
Services.TryAddSingleton<IOptionsMonitorCache<TOptions>, MultiTenantOptionsCache<TOptions, TTenantInfo>>();
Services.TryAddSingleton<IOptionsMonitorCache<TOptions>, MultiTenantOptionsCache<TOptions, T>>();

// Necessary to apply tenant named options in between configuration and post configuration
Services.AddSingleton<ITenantConfigureNamedOptions<TOptions, TTenantInfo>,
TenantConfigureNamedOptions<TOptions, TTenantInfo>>(sp => new TenantConfigureNamedOptions<TOptions,
TTenantInfo>(name, tenantConfigureNamedOptions));
Services.TryAddTransient<IOptionsFactory<TOptions>, MultiTenantOptionsFactory<TOptions, TTenantInfo>>();
Services.TryAddScoped<IOptionsSnapshot<TOptions>>(sp => BuildOptionsManager<TOptions>(sp));
Services.TryAddSingleton<IOptions<TOptions>>(sp => BuildOptionsManager<TOptions>(sp));
Services.AddSingleton<ITenantConfigureNamedOptions<TOptions, T>,
TenantConfigureNamedOptions<TOptions, T>>(_ => new TenantConfigureNamedOptions<TOptions,
T>(name, tenantConfigureNamedOptions));
Services.TryAddTransient<IOptionsFactory<TOptions>, MultiTenantOptionsFactory<TOptions, T>>();
Services.TryAddScoped<IOptionsSnapshot<TOptions>>(BuildOptionsManager<TOptions>);
Services.TryAddSingleton<IOptions<TOptions>>(BuildOptionsManager<TOptions>);

return this;
}

// TODO consider per tenant AllOptions variation
// TODO consider per-tenant post options
// TODO consider OptionsBuilder api

private static MultiTenantOptionsManager<TOptions> BuildOptionsManager<TOptions>(IServiceProvider sp)
where TOptions : class, new()
{
var cache = (IOptionsMonitorCache<TOptions>)ActivatorUtilities.CreateInstance(sp, typeof(MultiTenantOptionsCache<TOptions, TTenantInfo>));
var cache = (IOptionsMonitorCache<TOptions>)ActivatorUtilities.CreateInstance(sp,
typeof(MultiTenantOptionsCache<TOptions, T>));
return (MultiTenantOptionsManager<TOptions>)
ActivatorUtilities.CreateInstance(sp, typeof(MultiTenantOptionsManager<TOptions>), cache);
}

/// <summary>
/// Adds and configures a IMultiTenantStore to the application using default dependency injection.
/// Adds and configures an IMultiTenantStore to the application using default dependency injection.
/// </summary>>
/// <param name="lifetime">The service lifetime.</param>
/// <param name="parameters">a parameter list for any constructor parameters not covered by dependency injection.</param>
/// <returns>The same MultiTenantBuilder passed into the method.</returns>
public FinbuckleMultiTenantBuilder<TTenantInfo> WithStore<TStore>(ServiceLifetime lifetime,
public FinbuckleMultiTenantBuilder<T> WithStore<TStore>(ServiceLifetime lifetime,
params object[] parameters)
where TStore : IMultiTenantStore<TTenantInfo>
where TStore : IMultiTenantStore<T>
=> WithStore<TStore>(lifetime, sp => ActivatorUtilities.CreateInstance<TStore>(sp, parameters));

/// <summary>
/// Adds and configures a IMultiTenantStore to the application using a factory method.
/// Adds and configures an IMultiTenantStore to the application using a factory method.
/// </summary>
/// <param name="lifetime">The service lifetime.</param>
/// <param name="factory">A delegate that will create and configure the store.</param>
/// <returns>The same MultiTenantBuilder passed into the method.</returns>
public FinbuckleMultiTenantBuilder<TTenantInfo> WithStore<TStore>(ServiceLifetime lifetime,
// ReSharper disable once MemberCanBePrivate.Global
public FinbuckleMultiTenantBuilder<T> WithStore<TStore>(ServiceLifetime lifetime,
Func<IServiceProvider, TStore> factory)
where TStore : IMultiTenantStore<TTenantInfo>
where TStore : IMultiTenantStore<T>
{
if (factory == null)
{
Expand All @@ -113,28 +114,29 @@ private static MultiTenantOptionsManager<TOptions> BuildOptionsManager<TOptions>

// Note: can't use TryAddEnumerable here because ServiceDescriptor.Describe with a factory can't set implementation type.
Services.Add(
ServiceDescriptor.Describe(typeof(IMultiTenantStore<TTenantInfo>), sp => factory(sp), lifetime));
ServiceDescriptor.Describe(typeof(IMultiTenantStore<T>), sp => factory(sp), lifetime));

return this;
}

/// <summary>
/// Adds and configures a IMultiTenantStrategy to the application using default dependency injection.
/// Adds and configures an IMultiTenantStrategy to the application using default dependency injection.
/// </summary>
/// <param name="lifetime">The service lifetime.</param>
/// <param name="parameters">a parameter list for any constructor parameters not covered by dependency injection.</param>
/// <returns>The same MultiTenantBuilder passed into the method.</returns>
public FinbuckleMultiTenantBuilder<TTenantInfo> WithStrategy<TStrategy>(ServiceLifetime lifetime,
public FinbuckleMultiTenantBuilder<T> WithStrategy<TStrategy>(ServiceLifetime lifetime,
params object[] parameters) where TStrategy : IMultiTenantStrategy
=> WithStrategy(lifetime, sp => ActivatorUtilities.CreateInstance<TStrategy>(sp, parameters));

/// <summary>
/// Adds and configures a IMultiTenantStrategy to the application using a factory method.
/// Adds and configures an IMultiTenantStrategy to the application using a factory method.
/// </summary>
/// <param name="lifetime">The service lifetime.</param>
/// <param name="factory">A delegate that will create and configure the strategy.</param>
/// <returns>The same MultiTenantBuilder passed into the method.</returns>
public FinbuckleMultiTenantBuilder<TTenantInfo> WithStrategy<TStrategy>(ServiceLifetime lifetime,
// ReSharper disable once MemberCanBePrivate.Global
public FinbuckleMultiTenantBuilder<T> WithStrategy<TStrategy>(ServiceLifetime lifetime,
Func<IServiceProvider, TStrategy> factory)
where TStrategy : IMultiTenantStrategy
{
Expand Down
12 changes: 12 additions & 0 deletions src/Finbuckle.MultiTenant/Events/MultiTenantEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,22 @@
using System;
using System.Threading.Tasks;

// ReSharper disable once CheckNamespace
namespace Finbuckle.MultiTenant;

/// <summary>
/// Events for successful and failed tenant resolution.
/// </summary>
public class MultiTenantEvents
{
/// <summary>
/// Called when a tenant is successfully resolved.
/// </summary>
public Func<TenantResolvedContext, Task> OnTenantResolved { get; set; } = context => Task.CompletedTask;


/// <summary>
/// Called when no tenant fails is successfully resolved.
/// </summary>
public Func<TenantNotResolvedContext, Task> OnTenantNotResolved { get; set; } = context => Task.CompletedTask;
}
Loading

0 comments on commit 75582bb

Please sign in to comment.