Skip to content

Commit

Permalink
Simplified ShardingOnlyTenantAddDto
Browse files Browse the repository at this point in the history
  • Loading branch information
JonPSmith committed Aug 7, 2023
1 parent 47d2ef9 commit 7d3a75b
Show file tree
Hide file tree
Showing 17 changed files with 237 additions and 346 deletions.
9 changes: 6 additions & 3 deletions AuthPermissions.AspNetCore/SetupExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using RunMethodsSequentially;

Expand Down Expand Up @@ -171,12 +170,16 @@ public static AuthSetupData SetupMultiTenantSharding(this AuthSetupData setupDat

//This gets access to the ConnectionStrings
setupData.Services.Configure<ConnectionStringsOption>(setupData.Options.Configuration.GetSection("ConnectionStrings"));
setupData.Services.AddTransient<ILinkToTenantDataService, LinkToTenantDataService>();

#region AuthP version 6 changes
//This changed in version 6 of the AuthP library
//The GetSetShardingEntriesFileStoreCache handles reading back an ShardingEntry that was undated during the same HTTP request
//NOTE: IOptionsMonitor service won't get a change to the json file until an new HTTP request has happened
//This change is because IOptionsMonitor service won't get a change to the json file until an new HTTP request has happened
setupData.Services.AddTransient<IGetSetShardingEntries, GetSetShardingEntriesFileStoreCache>();
setupData.Services.AddTransient<ILinkToTenantDataService, LinkToTenantDataService>();
//New version service that makes it easier to create / delete tenants when using sharding
setupData.Services.AddTransient<IShardingOnlyTenantAddRemove, ShardingOnlyTenantAddRemove>();
#endregion

switch (setupData.Options.LinkToTenantType)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,9 @@ public IStatusGeneric RemoveShardingEntry(string shardingEntryName)
/// linked to different servers, e.g. WestServer, CenterServer and EastServer (see Example6)
/// </summary>
/// <returns></returns>
public IEnumerable<string> GetConnectionStringNames()
public List<string> GetConnectionStringNames()
{
return _connectionDict.Keys;
return _connectionDict.Keys.ToList();
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public interface IGetSetShardingEntries
/// linked to different servers, e.g. WestServer, CenterServer and EastServer (see Example6)
/// </summary>
/// <returns></returns>
IEnumerable<string> GetConnectionStringNames();
List<string> GetConnectionStringNames();

/// <summary>
/// This returns all the database info names in the ShardingEntry data, with a list of tenant name linked to each connection name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ namespace AuthPermissions.AspNetCore.ShardingServices;
/// before you can create the tenant. And for delete of a shard tenant it will remove the
/// <see cref="ShardingEntry"/> entry if its <see cref="Tenant.HasOwnDb"/> is true.
/// </summary>
public interface IShardingTenantAddRemove
public interface IShardingOnlyTenantAddRemove
{
/// <summary>
/// This creates a tenant (shared or shard), and if that tenant is a shard (i.e. has its own database)
/// it will create a sharding entry to contain the new database name
/// (unless the <see cref="ShardingTenantAddDto.ShardingEntityName"/> isn't empty, when it will lookup the
/// <see cref="ShardingEntry"/> defined by the <see cref="ShardingTenantAddDto.ShardingEntityName"/>).
/// (unless the <see cref="ShardingOnlyTenantAddDto.ShardingEntityName"/> isn't empty, when it will lookup the
/// <see cref="ShardingEntry"/> defined by the <see cref="ShardingOnlyTenantAddDto.ShardingEntityName"/>).
/// If a tenant that shares a database (tenant's HasOwnDb properly is false), then it use the <see cref="ShardingEntry"/> defined by the
/// <see cref="ShardingTenantAddDto.ShardingEntityName"/> in the <see cref="ShardingTenantAddDto"/>.
/// <see cref="ShardingOnlyTenantAddDto.ShardingEntityName"/> in the <see cref="ShardingOnlyTenantAddDto"/>.
/// </summary>
/// <param name="dto">A class called <see cref="ShardingTenantAddDto"/> holds all the data needed,
/// <param name="dto">A class called <see cref="ShardingOnlyTenantAddDto"/> holds all the data needed,
/// including a method to validate that the information is correct.</param>
/// <returns>status</returns>
Task<IStatusGeneric> CreateTenantAsync(ShardingTenantAddDto dto);
Task<IStatusGeneric> CreateTenantAsync(ShardingOnlyTenantAddDto dto);

/// <summary>
/// This will delete a tenant (shared or shard), and if that tenant <see cref="Tenant.HasOwnDb"/> is true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public void FormDefaultShardingEntry(AuthPermissionsOptions options, AuthPermiss
/// This return the correct list of default <see cref="ShardingEntry"/> list.
/// Can be an empty if <see cref="TenantsInAuthPdb"/> is false (useful in sharding only situations)
/// </summary>
/// <param name="options">Needed to fill in the <see cref="ShardingEntryOptions.DatabaseType"/></param>
/// <param name="options">Needed to fill in the <see cref="ShardingEntryOptions."/></param>
/// <param name="authPContext">Optional: Only needed if AddIfEmpty and using custom database.
/// You must provide the <see cref="AuthPermissionsDbContext"/> to get the short provider name.</param>
/// <returns></returns>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright (c) 2023 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
// Licensed under MIT license. See License.txt in the project root for license information.

using AuthPermissions.BaseCode.CommonCode;
using Microsoft.IdentityModel.Tokens;

namespace AuthPermissions.AspNetCore.ShardingServices;

/// <summary>
/// This is used if you want to use the <see cref="IShardingOnlyTenantAddRemove"/>'s
/// <see cref="ShardingOnlyTenantAddRemove.CreateTenantAsync"/>.
/// </summary>
public class ShardingOnlyTenantAddDto
{
/// <summary>
/// Required: The name of the new tenant to create
/// </summary>
public string TenantName { get; set; }

/// <summary>
/// Defines if the tenant should have its own database - always true
/// </summary>
public bool HasOwnDb => true;

/// <summary>
/// Optional: If adding a child hierarchical, then this must be set to a id of the parent hierarchical tenant
/// </summary>
public int ParentTenantId { get; set; } = 0;

/// <summary>
/// Optional: List of tenant role names
/// </summary>
public List<string> TenantRoleNames { get; set; } = new List<string>();

/// <summary>
/// Optional: If you have multiple connections strings you should This should contains the names of the connection strings to select the correct server
/// for the
/// </summary>
public List<string> ConnectionStringNames { get; set; }

/// <summary>
/// Required: The name of the connection string which defines the database server to use
/// </summary>
public string ConnectionStringName { get; set; }

/// <summary>
/// The short name (e.g. SqlServer) of the database provider for this tenant
/// </summary>
public string DbProviderShortName { get; set; }

/// <summary>
/// THis
/// </summary>
/// <param name="connectionStringNames"></param>
/// <exception cref="ArgumentException"></exception>
public void SetConnectionStringNames(List<string> connectionStringNames)
{
if (connectionStringNames == null || connectionStringNames.Count == 0)
throw new ArgumentException($"The list of connection string names cannot be null or empty collection.", nameof(connectionStringNames));


ConnectionStringNames = connectionStringNames;
ConnectionStringName = connectionStringNames.First();
}

/// <summary>
/// This ensures the data provided is valid
/// </summary>
/// <exception cref="AuthPermissionsBadDataException"></exception>
public void ValidateProperties()
{
if (TenantName.IsNullOrEmpty())
throw new AuthPermissionsBadDataException("Should not be null or empty", nameof(TenantName));

if (ConnectionStringName.IsNullOrEmpty())
throw new AuthPermissionsBadDataException("Should not be null or empty", nameof(ConnectionStringName));

if (DbProviderShortName.IsNullOrEmpty())
throw new AuthPermissionsBadDataException("Should not be null or empty", nameof(DbProviderShortName));
}

/// <summary>
/// This will build the <see cref="ShardingEntry"/> when you add a shard tenant.
/// NOTE: I have used a datetime for the database name for the reasons covered in the comments.
/// If you want to change the <see cref="ShardingEntry"/>'s Name or the DatabaseName,
/// then you can create a new class and override the <see cref="FormDatabaseInformation"/> method.
/// See https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/override
/// </summary>
/// <returns></returns>
/// <exception cref="AuthPermissionsException"></exception>
public virtual ShardingEntry FormDatabaseInformation()
{
var dateTimeNow = DateTime.UtcNow;
return new ShardingEntry
{
//NOTE: I don't include the tenant name in the database name because
//1. The tenant name can be changed, but you can't always the change the database name
//2. PostgreSQL has a 64 character limit on the name of a database
Name = $"{TenantName}-{dateTimeNow.ToString("yyyyMMddHHmmss")}",
DatabaseName = dateTimeNow.ToString("yyyyMMddHHmmss-fff"),
ConnectionName = ConnectionStringName,
DatabaseType = DbProviderShortName
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,24 @@
using AuthPermissions.BaseCode.DataLayer.Classes;
using AuthPermissions.BaseCode.SetupCode;
using LocalizeMessagesAndErrors;
using Microsoft.IdentityModel.Tokens;
using StatusGeneric;

namespace AuthPermissions.AspNetCore.ShardingServices;

/// <summary>
/// This service is designed for applications using the <see cref="TenantTypes.AddSharding"/>
/// Each tenant (shared or shard) needs a <see cref="ShardingEntry"/> entry to define the database
/// before you can create the tenant. And for delete of a shard tenant it will remove the
/// <see cref="ShardingEntry"/> entry if its <see cref="Tenant.HasOwnDb"/> is true.
/// This service is designed for applications using the <see cref="TenantTypes.AddSharding"/> feature
/// and this code creates / deletes a shard tenant (i.e. the tenant's <see cref="Tenant.HasOwnDb"/> is true) and
/// at the same time adds / removes a <see cref="ShardingEntry"/> entry linked to the tenant's database.
/// </summary>
public class ShardingTenantAddRemove : IShardingTenantAddRemove
public class ShardingOnlyTenantAddRemove : IShardingOnlyTenantAddRemove
{
private readonly IAuthTenantAdminService _tenantAdmin;
private readonly IGetSetShardingEntries _getSetShardings;
private readonly AuthPermissionsOptions _options;
private readonly IDefaultLocalizer _localizeDefault;

/// <summary>Initializes a new instance of the <see cref="T:System.Object" /> class.</summary>
public ShardingTenantAddRemove(IAuthTenantAdminService tenantAdmin,
public ShardingOnlyTenantAddRemove(IAuthTenantAdminService tenantAdmin,
IGetSetShardingEntries getSetShardings,
AuthPermissionsOptions options, IAuthPDefaultLocalizer localizeProvider)
{
Expand All @@ -41,17 +39,15 @@ public ShardingTenantAddRemove(IAuthTenantAdminService tenantAdmin,
}

/// <summary>
/// This creates a tenant (shared or shard), and if that tenant is a shard (i.e. has its own database)
/// it will create a sharding entry to contain the new database name
/// (unless the <see cref="ShardingTenantAddDto.ShardingEntityName"/> isn't empty, when it will lookup the
/// <see cref="ShardingEntry"/> defined by the <see cref="ShardingTenantAddDto.ShardingEntityName"/>).
/// If a tenant that shares a database (tenant's HasOwnDb properly is false), then it use the <see cref="ShardingEntry"/> defined by the
/// <see cref="ShardingTenantAddDto.ShardingEntityName"/> in the <see cref="ShardingTenantAddDto"/>.
/// This creates a shard tenant i.e. the tenant's <see cref="Tenant.HasOwnDb"/> is true) and
/// it will create a sharding entry to contain the new database name.
/// Note this method can handle single and hierarchical tenants, including adding a child
/// hierarchical entry which uses the parent's sharding entry.
/// </summary>
/// <param name="dto">A class called <see cref="ShardingTenantAddDto"/> holds all the data needed,
/// <param name="dto">A class called <see cref="ShardingOnlyTenantAddDto"/> holds all the data needed,
/// including a method to validate that the information is correct.</param>
/// <returns>status</returns>
public async Task<IStatusGeneric> CreateTenantAsync(ShardingTenantAddDto dto)
public async Task<IStatusGeneric> CreateTenantAsync(ShardingOnlyTenantAddDto dto)
{
dto.ValidateProperties();
if (_options.TenantType.IsSingleLevel() && dto.ParentTenantId != 0)
Expand All @@ -62,62 +58,54 @@ public async Task<IStatusGeneric> CreateTenantAsync(ShardingTenantAddDto dto)
return status.AddErrorFormattedWithParams("DuplicateTenantName".ClassLocalizeKey(this, true),
$"The tenant name '{dto.TenantName}' is already used", nameof(dto.TenantName));

//1. We obtain an information data via the ShardingTenantAddDto class
ShardingEntry databaseInfo = null;
//1. We obtain an information data via the ShardingOnlyTenantAddDto class
ShardingEntry shardingEntry = null;
if (_options.TenantType.IsHierarchical() && dto.ParentTenantId != 0)
{
//if a child hierarchical tenant we don't need to get the ShardingEntry as the parent's ShardingEntry is used
var parentStatus = await _tenantAdmin.GetTenantViaIdAsync(dto.ParentTenantId);
if (status.CombineStatuses(parentStatus).HasErrors)
return status;

databaseInfo = _getSetShardings.GetSingleShardingEntry(parentStatus.Result.DatabaseInfoName);
if (databaseInfo == null)
shardingEntry = _getSetShardings.GetSingleShardingEntry(parentStatus.Result.DatabaseInfoName);
if (shardingEntry == null)
return status.AddErrorFormatted("MissingDatabaseInformation".ClassLocalizeKey(this, true),
$"The ShardingEntry for the parent '{parentStatus.Result.TenantFullName}' wasn't found.");
}
else if (!dto.ShardingEntityName.IsNullOrEmpty())
{
//The DatabaseInfoName has been set, so get the ShardingEntry
databaseInfo = _getSetShardings.GetSingleShardingEntry(dto.ShardingEntityName);

if (databaseInfo == null)
throw new AuthPermissionsException(
$"The {nameof(ShardingTenantAddDto.ShardingEntityName)} you provided wasn't found in the sharding entries.");
}
else if (dto.HasOwnDb == true)
else
{
//Its a new sharding tenant, so we need to create a new ShardingEntry entry for this database
databaseInfo = dto.FormDatabaseInformation();
shardingEntry = dto.FormDatabaseInformation();
if (status.CombineStatuses(
_getSetShardings.AddNewShardingEntry(databaseInfo)).HasErrors)
_getSetShardings.AddNewShardingEntry(shardingEntry)).HasErrors)
return status;
}

//2. Now we can create the tenant, which in turn will setup the database via your ITenantChangeService implementation
if (_options.TenantType.IsSingleLevel())
status.CombineStatuses(await _tenantAdmin.AddSingleTenantAsync(dto.TenantName, dto.TenantRoleNames,
dto.HasOwnDb, databaseInfo?.Name));
dto.HasOwnDb, shardingEntry?.Name));
else
{
status.CombineStatuses(await _tenantAdmin.AddHierarchicalTenantAsync(dto.TenantName,
dto.ParentTenantId, dto.TenantRoleNames,
dto.HasOwnDb, databaseInfo?.Name));
dto.HasOwnDb, shardingEntry?.Name));
}

if (status.HasErrors && dto.HasOwnDb == true)
if (status.HasErrors && shardingEntry != null)
{
//we created a ShardingEntry, so we want to delete it
status.CombineStatuses(
_getSetShardings.RemoveShardingEntry(databaseInfo.Name));
_getSetShardings.RemoveShardingEntry(shardingEntry.Name));
}

return status;
}

/// <summary>
/// This will delete a tenant (shared or shard), and if that tenant <see cref="Tenant.HasOwnDb"/> is true
/// it will also delete the <see cref="ShardingEntry"/> entry for this shard tenant.
/// This will delete a shard tenant i.e. the tenant's <see cref="Tenant.HasOwnDb"/> is true)
/// and will also delete the <see cref="ShardingEntry"/> entry for this shard tenant
/// (unless the tenant is a child hierarchical, in which case it doesn't delete the <see cref="ShardingEntry"/> entry).
/// </summary>
/// <param name="tenantId">The id of the tenant.</param>
/// <returns>status</returns>
Expand Down
Loading

0 comments on commit 7d3a75b

Please sign in to comment.