Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions BotSharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.MMPEmbeddin
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.Membase", "src\Plugins\BotSharp.Plugin.Membase\BotSharp.Plugin.Membase.csproj", "{13223C71-9EAC-9835-28ED-5A4833E6F915}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MultiTenancy", "MultiTenancy", "{7C64208C-8D11-4E17-A3E9-14D7910763EB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.MultiTenancy", "src\Plugins\BotSharp.Plugin.MultiTenancy\BotSharp.Plugin.MultiTenancy.csproj", "{9BC8DF43-88D1-4C57-A678-AC0153BDF4EB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -649,6 +653,14 @@ Global
{13223C71-9EAC-9835-28ED-5A4833E6F915}.Release|Any CPU.Build.0 = Release|Any CPU
{13223C71-9EAC-9835-28ED-5A4833E6F915}.Release|x64.ActiveCfg = Release|Any CPU
{13223C71-9EAC-9835-28ED-5A4833E6F915}.Release|x64.Build.0 = Release|Any CPU
{9BC8DF43-88D1-4C57-A678-AC0153BDF4EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9BC8DF43-88D1-4C57-A678-AC0153BDF4EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9BC8DF43-88D1-4C57-A678-AC0153BDF4EB}.Debug|x64.ActiveCfg = Debug|Any CPU
{9BC8DF43-88D1-4C57-A678-AC0153BDF4EB}.Debug|x64.Build.0 = Debug|Any CPU
{9BC8DF43-88D1-4C57-A678-AC0153BDF4EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9BC8DF43-88D1-4C57-A678-AC0153BDF4EB}.Release|Any CPU.Build.0 = Release|Any CPU
{9BC8DF43-88D1-4C57-A678-AC0153BDF4EB}.Release|x64.ActiveCfg = Release|Any CPU
{9BC8DF43-88D1-4C57-A678-AC0153BDF4EB}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -723,6 +735,8 @@ Global
{E7C243B9-E751-B3B4-8F16-95C76CA90D31} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
{394B858B-9C26-B977-A2DA-8CC7BE5914CB} = {4F346DCE-087F-4368-AF88-EE9C720D0E69}
{13223C71-9EAC-9835-28ED-5A4833E6F915} = {53E7CD86-0D19-40D9-A0FA-AB4613837E89}
{7C64208C-8D11-4E17-A3E9-14D7910763EB} = {2635EC9B-2E5F-4313-AC21-0B847F31F36C}
{9BC8DF43-88D1-4C57-A678-AC0153BDF4EB} = {7C64208C-8D11-4E17-A3E9-14D7910763EB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace BotSharp.Abstraction.MultiTenancy;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class ConnectionStringNameAttribute : Attribute
{
public string Name { get; }

public ConnectionStringNameAttribute(string name)
{
Name = name;
}

public static string GetConnStringName(Type type) => type.FullName ?? string.Empty;

public static string GetConnStringName<T>() => GetConnStringName(typeof(T));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace BotSharp.Abstraction.MultiTenancy;

[Serializable]
public class ConnectionStrings : Dictionary<string, string?>
{
public const string DefaultConnectionStringName = "Default";

public string? Default
{
get => this[DefaultConnectionStringName];
set => this[DefaultConnectionStringName] = value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace BotSharp.Abstraction.MultiTenancy;

public interface IConnectionStringResolver
{
string? GetConnectionString(string connectionStringName);

string? GetConnectionString<TContext>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace BotSharp.Abstraction.MultiTenancy;

public interface ICurrentTenant
{
Guid? Id { get; }

string? Name { get; }

string? TenantId => Id?.ToString();

IDisposable Change(Guid? id, string? name = null);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace BotSharp.Abstraction.MultiTenancy;

public interface ITenantConnectionProvider
{
string GetConnectionString(string name);
string GetDefaultConnectionString();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace BotSharp.Abstraction.MultiTenancy;

public interface ITenantFeature
{
bool Enabled { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using BotSharp.Abstraction.MultiTenancy.Models;

namespace BotSharp.Abstraction.MultiTenancy;

public interface ITenantOptionProvider
{
Task<IReadOnlyList<TenantOption>> GetOptionsAsync();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using BotSharp.Abstraction.MultiTenancy.Models;

namespace BotSharp.Abstraction.MultiTenancy;

public interface ITenantResolveContributor
{
string Name { get; }

Task<TenantResolveResult> ResolveAsync(TenantResolveContext context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using BotSharp.Abstraction.MultiTenancy.Models;

namespace BotSharp.Abstraction.MultiTenancy;

public interface ITenantResolver
{
Task<TenantResolveResult> ResolveAsync(TenantResolveContext context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace BotSharp.Abstraction.MultiTenancy.Models;

public sealed record TenantOption(Guid TenantId, string Name);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Microsoft.AspNetCore.Http;

namespace BotSharp.Abstraction.MultiTenancy.Models;

public class TenantResolveContext
{
public HttpContext HttpContext { get; set; } = default!;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace BotSharp.Abstraction.MultiTenancy.Models;

public class TenantResolveResult
{
public Guid? TenantId { get; set; }
public string? Name { get; set; }
public bool Succeeded => TenantId.HasValue;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Diagnostics.CodeAnalysis;

namespace BotSharp.Abstraction.MultiTenancy.Options;

public class TenantConfiguration
{
public Guid Id { get; set; }

public string Name { get; set; } = default!;

public string NormalizedName { get; set; } = default!;

public ConnectionStrings? ConnectionStrings { get; set; }

public bool IsActive { get; set; }

public TenantConfiguration()
{
IsActive = true;
}

public TenantConfiguration(Guid id, [NotNull] string name)
: this()
{
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Name cannot be null or whitespace.");
Id = id;
Name = name;

ConnectionStrings = new ConnectionStrings();
}

public TenantConfiguration(Guid id, [NotNull] string name, [NotNull] string normalizedName)
: this(id, name)
{
if (string.IsNullOrWhiteSpace(normalizedName)) throw new ArgumentException("NormalizedName cannot be null or whitespace.");
NormalizedName = normalizedName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace BotSharp.Abstraction.MultiTenancy.Options;

public class TenantStoreOptions
{
public bool Enabled { get; set; } = false;

public TenantConfiguration[] Tenants { get; set; } = [];
}
29 changes: 26 additions & 3 deletions src/Plugins/BotSharp.Plugin.MongoStorage/MongoStoragePlugin.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using BotSharp.Abstraction.MultiTenancy;
using BotSharp.Abstraction.Repositories.Enums;
using BotSharp.Abstraction.Repositories.Settings;
using BotSharp.Plugin.MongoStorage.Repository;
Expand Down Expand Up @@ -25,10 +26,32 @@ public void RegisterDI(IServiceCollection services, IConfiguration config)
var conventionPack = new ConventionPack { new IgnoreExtraElementsConvention(true) };
ConventionRegistry.Register("IgnoreExtraElements", conventionPack, type => true);

services.AddSingleton((IServiceProvider x) =>
var tenantEnabled = config.GetValue("TenantStore:Enabled", false);
if (tenantEnabled)
{
return new MongoDbContext(dbSettings);
});
services.AddScoped((IServiceProvider x) =>
{
var tenantEnabled = x.GetService<ITenantFeature>()?.Enabled ?? false;
if (tenantEnabled)
{
var provider = x.GetService<ITenantConnectionProvider>();
if (provider != null)
{
var cs = provider.GetConnectionString("BotSharpMongoDb");
if (!string.IsNullOrWhiteSpace(cs)) dbSettings.BotSharpMongoDb = cs;
}
}

return new MongoDbContext(dbSettings);
});
}
else
{
services.AddSingleton((IServiceProvider x) =>
{
return new MongoDbContext(dbSettings);
});
}

services.AddScoped<IBotSharpRepository, MongoRepository>();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(TargetFramework)</TargetFramework>
<LangVersion>$(LangVersion)</LangVersion>
<Nullable>enable</Nullable>
<VersionPrefix>$(BotSharpVersion)</VersionPrefix>
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
<OutputPath>$(SolutionDir)packages</OutputPath>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using BotSharp.Abstraction.MultiTenancy;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
using System.Threading.Tasks;

namespace BotSharp.Plugin.MultiTenancy.Controllers;

[ApiController]
public class TenantsController : ControllerBase
{
private readonly ITenantOptionProvider _tenantOptionProvider;

public TenantsController(ITenantOptionProvider tenantOptionProvider)
{
_tenantOptionProvider = tenantOptionProvider;
}

[AllowAnonymous]
[HttpGet]
[Route("/tenants/options")]
public async Task<IActionResult> Options()
{
var tenants = await _tenantOptionProvider.GetOptionsAsync();
var payload = tenants.Select(t => new { tenantId = t.TenantId, name = t.Name }).ToArray();
return Ok(payload);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace BotSharp.Plugin.MultiTenancy.Enums;

public static class TenantConsts
{
public const string DefaultTenantKey = "__tenant";

public const string TenantId = "tenantid";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using BotSharp.Plugin.MultiTenancy.MultiTenancy;
using Microsoft.AspNetCore.Builder;

namespace BotSharp.Plugin.MultiTenancy.Extensions;

public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder UseMultiTenancy(this IApplicationBuilder app)
{
return app.UseMiddleware<MultiTenancyMiddleware>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using BotSharp.Abstraction.MultiTenancy;
using BotSharp.Abstraction.MultiTenancy.Options;
using BotSharp.Plugin.MultiTenancy.Models;
using BotSharp.Plugin.MultiTenancy.MultiTenancy;
using BotSharp.Plugin.MultiTenancy.MultiTenancy.Resolvers;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace BotSharp.Plugin.MultiTenancy.Extensions;

public static class MultiTenancyServiceCollectionExtensions
{
public static IServiceCollection AddMultiTenancy(this IServiceCollection services, IConfiguration configuration, string sectionName = "TenantStore")
{
services.Configure<TenantResolveOptions>(options =>
{
options.TenantResolvers.Add(new ClaimsTenantResolveContributor());
options.TenantResolvers.Add(new HeaderTenantResolveContributor());
options.TenantResolvers.Add(new QueryStringTenantResolveContributor());
});

services.Configure<TenantStoreOptions>(configuration.GetSection(sectionName));
services.AddScoped<ICurrentTenant, CurrentTenant>();
services.AddSingleton<ICurrentTenantAccessor>(AsyncLocalCurrentTenantAccessor.Instance);
services.AddScoped<ITenantResolver, TenantResolver>();
services.AddScoped<IConnectionStringResolver, DefaultConnectionStringResolver>();
services.AddScoped<ITenantConnectionProvider, TenantConnectionProvider>();
services.AddSingleton<ITenantFeature, TenantFeature>();
services.AddScoped<MultiTenancyMiddleware>();

services.TryAddScoped<ITenantOptionProvider, ConfigTenantOptionProvider>();

return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using System;

namespace BotSharp.Plugin.MultiTenancy.Models;

public sealed record TenantInfoBasic(Guid? TenantId, string? Name);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using BotSharp.Abstraction.MultiTenancy;
using System.Collections.Generic;

namespace BotSharp.Plugin.MultiTenancy.Models;

public class TenantResolveOptions
{
public List<ITenantResolveContributor> TenantResolvers { get; set; } = new();
}
Loading
Loading