From 7d3a75b275ebc6bcdb619e3d5d4e6c75f9bcedf5 Mon Sep 17 00:00:00 2001 From: Jon P Smith Date: Mon, 7 Aug 2023 09:50:33 +0100 Subject: [PATCH] Simplified ShardingOnlyTenantAddDto --- AuthPermissions.AspNetCore/SetupExtensions.cs | 9 +- .../GetSetShardingEntriesFileStoreCache.cs | 4 +- .../IGetSetShardingEntries.cs | 2 +- ...ove.cs => IShardingOnlyTenantAddRemove.cs} | 12 +- .../ShardingServices/ShardingEntryOptions.cs | 2 +- .../ShardingOnlyTenantAddDto.cs | 105 ++++++++++++ ...move.cs => ShardingOnlyTenantAddRemove.cs} | 62 +++----- .../ShardingServices/ShardingTenantAddDto.cs | 117 -------------- .../AppStart/ExampleInvoiceBuilder.cs | 2 +- .../Controllers/TenantController.cs | 21 +-- .../Example7CacheFileStore.Development.json | 3 +- Example7.MvcWebApp.ShardingOnly/Program.cs | 2 +- .../Views/Tenant/Create.cshtml | 12 +- .../Views/Tenant/Index.cshtml | 7 - ....cs => ShardingOnlyTenantChangeService.cs} | 70 ++++---- Test/StubClasses/StubGetSetShardingEntries.cs | 4 +- .../TestShardingTenantAddRemove.cs | 149 ++++-------------- 17 files changed, 237 insertions(+), 346 deletions(-) rename AuthPermissions.AspNetCore/ShardingServices/{IShardingTenantAddRemove.cs => IShardingOnlyTenantAddRemove.cs} (77%) create mode 100644 AuthPermissions.AspNetCore/ShardingServices/ShardingOnlyTenantAddDto.cs rename AuthPermissions.AspNetCore/ShardingServices/{ShardingTenantAddRemove.cs => ShardingOnlyTenantAddRemove.cs} (67%) delete mode 100644 AuthPermissions.AspNetCore/ShardingServices/ShardingTenantAddDto.cs rename Example7.SingleLevelShardingOnly/EfCoreCode/{ShardingTenantChangeService.cs => ShardingOnlyTenantChangeService.cs} (84%) diff --git a/AuthPermissions.AspNetCore/SetupExtensions.cs b/AuthPermissions.AspNetCore/SetupExtensions.cs index 30567c7e..722bc702 100644 --- a/AuthPermissions.AspNetCore/SetupExtensions.cs +++ b/AuthPermissions.AspNetCore/SetupExtensions.cs @@ -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; @@ -171,12 +170,16 @@ public static AuthSetupData SetupMultiTenantSharding(this AuthSetupData setupDat //This gets access to the ConnectionStrings setupData.Services.Configure(setupData.Options.Configuration.GetSection("ConnectionStrings")); + setupData.Services.AddTransient(); +#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(); - setupData.Services.AddTransient(); + //New version service that makes it easier to create / delete tenants when using sharding + setupData.Services.AddTransient(); +#endregion switch (setupData.Options.LinkToTenantType) { diff --git a/AuthPermissions.AspNetCore/ShardingServices/GetSetShardingEntriesFileStoreCache.cs b/AuthPermissions.AspNetCore/ShardingServices/GetSetShardingEntriesFileStoreCache.cs index 14b0924e..67a01d74 100644 --- a/AuthPermissions.AspNetCore/ShardingServices/GetSetShardingEntriesFileStoreCache.cs +++ b/AuthPermissions.AspNetCore/ShardingServices/GetSetShardingEntriesFileStoreCache.cs @@ -168,9 +168,9 @@ public IStatusGeneric RemoveShardingEntry(string shardingEntryName) /// linked to different servers, e.g. WestServer, CenterServer and EastServer (see Example6) /// /// - public IEnumerable GetConnectionStringNames() + public List GetConnectionStringNames() { - return _connectionDict.Keys; + return _connectionDict.Keys.ToList(); } /// diff --git a/AuthPermissions.AspNetCore/ShardingServices/IGetSetShardingEntries.cs b/AuthPermissions.AspNetCore/ShardingServices/IGetSetShardingEntries.cs index 75b0983f..170d36da 100644 --- a/AuthPermissions.AspNetCore/ShardingServices/IGetSetShardingEntries.cs +++ b/AuthPermissions.AspNetCore/ShardingServices/IGetSetShardingEntries.cs @@ -62,7 +62,7 @@ public interface IGetSetShardingEntries /// linked to different servers, e.g. WestServer, CenterServer and EastServer (see Example6) /// /// - IEnumerable GetConnectionStringNames(); + List GetConnectionStringNames(); /// /// This returns all the database info names in the ShardingEntry data, with a list of tenant name linked to each connection name diff --git a/AuthPermissions.AspNetCore/ShardingServices/IShardingTenantAddRemove.cs b/AuthPermissions.AspNetCore/ShardingServices/IShardingOnlyTenantAddRemove.cs similarity index 77% rename from AuthPermissions.AspNetCore/ShardingServices/IShardingTenantAddRemove.cs rename to AuthPermissions.AspNetCore/ShardingServices/IShardingOnlyTenantAddRemove.cs index b9a533e4..02d2fe3d 100644 --- a/AuthPermissions.AspNetCore/ShardingServices/IShardingTenantAddRemove.cs +++ b/AuthPermissions.AspNetCore/ShardingServices/IShardingOnlyTenantAddRemove.cs @@ -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 /// entry if its is true. /// -public interface IShardingTenantAddRemove +public interface IShardingOnlyTenantAddRemove { /// /// 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 isn't empty, when it will lookup the - /// defined by the ). + /// (unless the isn't empty, when it will lookup the + /// defined by the ). /// If a tenant that shares a database (tenant's HasOwnDb properly is false), then it use the defined by the - /// in the . + /// in the . /// - /// A class called holds all the data needed, + /// A class called holds all the data needed, /// including a method to validate that the information is correct. /// status - Task CreateTenantAsync(ShardingTenantAddDto dto); + Task CreateTenantAsync(ShardingOnlyTenantAddDto dto); /// /// This will delete a tenant (shared or shard), and if that tenant is true diff --git a/AuthPermissions.AspNetCore/ShardingServices/ShardingEntryOptions.cs b/AuthPermissions.AspNetCore/ShardingServices/ShardingEntryOptions.cs index fe68a4b5..a370d18f 100644 --- a/AuthPermissions.AspNetCore/ShardingServices/ShardingEntryOptions.cs +++ b/AuthPermissions.AspNetCore/ShardingServices/ShardingEntryOptions.cs @@ -65,7 +65,7 @@ public void FormDefaultShardingEntry(AuthPermissionsOptions options, AuthPermiss /// This return the correct list of default list. /// Can be an empty if is false (useful in sharding only situations) /// - /// Needed to fill in the + /// Needed to fill in the /// Optional: Only needed if AddIfEmpty and using custom database. /// You must provide the to get the short provider name. /// diff --git a/AuthPermissions.AspNetCore/ShardingServices/ShardingOnlyTenantAddDto.cs b/AuthPermissions.AspNetCore/ShardingServices/ShardingOnlyTenantAddDto.cs new file mode 100644 index 00000000..f2eb43bf --- /dev/null +++ b/AuthPermissions.AspNetCore/ShardingServices/ShardingOnlyTenantAddDto.cs @@ -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; + +/// +/// This is used if you want to use the 's +/// . +/// +public class ShardingOnlyTenantAddDto +{ + /// + /// Required: The name of the new tenant to create + /// + public string TenantName { get; set; } + + /// + /// Defines if the tenant should have its own database - always true + /// + public bool HasOwnDb => true; + + /// + /// Optional: If adding a child hierarchical, then this must be set to a id of the parent hierarchical tenant + /// + public int ParentTenantId { get; set; } = 0; + + /// + /// Optional: List of tenant role names + /// + public List TenantRoleNames { get; set; } = new List(); + + /// + /// 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 + /// + public List ConnectionStringNames { get; set; } + + /// + /// Required: The name of the connection string which defines the database server to use + /// + public string ConnectionStringName { get; set; } + + /// + /// The short name (e.g. SqlServer) of the database provider for this tenant + /// + public string DbProviderShortName { get; set; } + + /// + /// THis + /// + /// + /// + public void SetConnectionStringNames(List 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(); + } + + /// + /// This ensures the data provided is valid + /// + /// + 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)); + } + + /// + /// This will build the 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 's Name or the DatabaseName, + /// then you can create a new class and override the method. + /// See https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/override + /// + /// + /// + 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 + }; + } +} \ No newline at end of file diff --git a/AuthPermissions.AspNetCore/ShardingServices/ShardingTenantAddRemove.cs b/AuthPermissions.AspNetCore/ShardingServices/ShardingOnlyTenantAddRemove.cs similarity index 67% rename from AuthPermissions.AspNetCore/ShardingServices/ShardingTenantAddRemove.cs rename to AuthPermissions.AspNetCore/ShardingServices/ShardingOnlyTenantAddRemove.cs index 27e08a83..05fc3772 100644 --- a/AuthPermissions.AspNetCore/ShardingServices/ShardingTenantAddRemove.cs +++ b/AuthPermissions.AspNetCore/ShardingServices/ShardingOnlyTenantAddRemove.cs @@ -7,18 +7,16 @@ using AuthPermissions.BaseCode.DataLayer.Classes; using AuthPermissions.BaseCode.SetupCode; using LocalizeMessagesAndErrors; -using Microsoft.IdentityModel.Tokens; using StatusGeneric; namespace AuthPermissions.AspNetCore.ShardingServices; /// -/// This service is designed for applications using the -/// Each tenant (shared or shard) needs a entry to define the database -/// before you can create the tenant. And for delete of a shard tenant it will remove the -/// entry if its is true. +/// This service is designed for applications using the feature +/// and this code creates / deletes a shard tenant (i.e. the tenant's is true) and +/// at the same time adds / removes a entry linked to the tenant's database. /// -public class ShardingTenantAddRemove : IShardingTenantAddRemove +public class ShardingOnlyTenantAddRemove : IShardingOnlyTenantAddRemove { private readonly IAuthTenantAdminService _tenantAdmin; private readonly IGetSetShardingEntries _getSetShardings; @@ -26,7 +24,7 @@ public class ShardingTenantAddRemove : IShardingTenantAddRemove private readonly IDefaultLocalizer _localizeDefault; /// Initializes a new instance of the class. - public ShardingTenantAddRemove(IAuthTenantAdminService tenantAdmin, + public ShardingOnlyTenantAddRemove(IAuthTenantAdminService tenantAdmin, IGetSetShardingEntries getSetShardings, AuthPermissionsOptions options, IAuthPDefaultLocalizer localizeProvider) { @@ -41,17 +39,15 @@ public ShardingTenantAddRemove(IAuthTenantAdminService tenantAdmin, } /// - /// 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 isn't empty, when it will lookup the - /// defined by the ). - /// If a tenant that shares a database (tenant's HasOwnDb properly is false), then it use the defined by the - /// in the . + /// This creates a shard tenant i.e. the tenant's 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. /// - /// A class called holds all the data needed, + /// A class called holds all the data needed, /// including a method to validate that the information is correct. /// status - public async Task CreateTenantAsync(ShardingTenantAddDto dto) + public async Task CreateTenantAsync(ShardingOnlyTenantAddDto dto) { dto.ValidateProperties(); if (_options.TenantType.IsSingleLevel() && dto.ParentTenantId != 0) @@ -62,8 +58,8 @@ public async Task 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 @@ -71,53 +67,45 @@ public async Task CreateTenantAsync(ShardingTenantAddDto dto) 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; } /// - /// This will delete a tenant (shared or shard), and if that tenant is true - /// it will also delete the entry for this shard tenant. + /// This will delete a shard tenant i.e. the tenant's is true) + /// and will also delete the entry for this shard tenant + /// (unless the tenant is a child hierarchical, in which case it doesn't delete the entry). /// /// The id of the tenant. /// status diff --git a/AuthPermissions.AspNetCore/ShardingServices/ShardingTenantAddDto.cs b/AuthPermissions.AspNetCore/ShardingServices/ShardingTenantAddDto.cs deleted file mode 100644 index 04424ec6..00000000 --- a/AuthPermissions.AspNetCore/ShardingServices/ShardingTenantAddDto.cs +++ /dev/null @@ -1,117 +0,0 @@ -// 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; - -/// -/// This is used if you want to use the 's -/// . -/// It contains the properties to create a new tenant, either tenants that there own database -/// (HasOwnDb == true) or tenants that share a database (HasOwnDb == false) -/// -public class ShardingTenantAddDto -{ - /// - /// Required: The name of the new tenant to create - /// - public string TenantName { get; set; } - - /// - /// Defines if the tenant should have its own database - defaults to true - /// - public bool? HasOwnDb { get; set; } - - /// - /// If adding a child hierarchical, then this must be set to a id of the parent hierarchical tenant - /// - public int ParentTenantId { get; set; } = 0; - - /// - /// If you are adding a hybrid tenant (i.e. HasOwnDb == false), then you provide the name of the - /// entry that should already in the sharding data. - /// - public string ShardingEntityName { get; set; } - - /// - /// Optional: List of tenant role names - /// - public List TenantRoleNames { get; set; } = new List(); - - /// - /// The name of the connection string which defines the database server to use - /// if adding a new entry - /// - public string ConnectionStringName { get; set; } - - /// - /// The short name (e.g. SqlServer) of the database provider for this tenant - /// if adding a new entry - /// - public string DbProviderShortName { get; set; } - - /// - /// This ensures the data provided is valid - /// - /// - public void ValidateProperties() - { - if (TenantName.IsNullOrEmpty()) - throw new AuthPermissionsBadDataException("Should not be null or empty", nameof(TenantName)); - - if (HasOwnDb == null) - throw new AuthPermissionsBadDataException( - $"You must set the {nameof(HasOwnDb)} to true (has own db) or false (shares a database)", nameof(HasOwnDb)); - - if (HasOwnDb == false && ShardingEntityName.IsNullOrEmpty()) - throw new AuthPermissionsBadDataException( - $"The {nameof(HasOwnDb)} is false so you need to provide {nameof(ShardingEntityName)} ", nameof(ShardingEntityName)); - - if (ParentTenantId != 0 && !ShardingEntityName.IsNullOrEmpty()) - throw new AuthPermissionsBadDataException("If you are adding a child hierarchical (i.e. " + - $"{nameof(ParentTenantId)} isn't null), then you should NOT provide a {nameof(ShardingEntityName)}.", nameof(ParentTenantId)); - - if (HasOwnDb == true && ShardingEntityName.IsNullOrEmpty() && ParentTenantId == 0) - { - if (ConnectionStringName.IsNullOrEmpty()) - throw new AuthPermissionsBadDataException( - $"The {nameof(HasOwnDb)} is true so you need to provide {nameof(ConnectionStringName)} ", nameof(ConnectionStringName)); - - if (DbProviderShortName.IsNullOrEmpty()) - throw new AuthPermissionsBadDataException( - $"The {nameof(HasOwnDb)} is true so you need to provide {nameof(DbProviderShortName)} ", nameof(DbProviderShortName)); - } - } - - /// - /// This will build the 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 's Name or the DatabaseName, - /// then you can create a new class and override the method. - /// See https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/override - /// - /// - /// - public virtual ShardingEntry? FormDatabaseInformation() - { - if (HasOwnDb != true) - throw new AuthPermissionsException($"There is already a {nameof(ShardingEntry)} entry for this tenant."); - if (!ShardingEntityName.IsNullOrEmpty()) - throw new AuthPermissionsException( - $"The {nameof(ShardingEntityName)} property is set, so you use that to get the {nameof(ShardingEntry)} entry."); - - 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 = $"{dateTimeNow.ToString("yyyyMMddHHmmss")}-{TenantName}", - DatabaseName = dateTimeNow.ToString("yyyyMMddHHmmss-fff"), - ConnectionName = ConnectionStringName, - DatabaseType = DbProviderShortName - }; - } -} \ No newline at end of file diff --git a/Example6.SingleLevelSharding/AppStart/ExampleInvoiceBuilder.cs b/Example6.SingleLevelSharding/AppStart/ExampleInvoiceBuilder.cs index 38a6ef60..4b807ff9 100644 --- a/Example6.SingleLevelSharding/AppStart/ExampleInvoiceBuilder.cs +++ b/Example6.SingleLevelSharding/AppStart/ExampleInvoiceBuilder.cs @@ -25,7 +25,7 @@ public ExampleInvoiceBuilder(string dataKey) _dataKey = dataKey; } - public Invoice CreateRandomInvoice(string companyName, string invoiceName = null) + public Invoice CreateRandomInvoice(string companyName, string? invoiceName = null) { //thanks to https://stackoverflow.com/questions/29482/how-can-i-cast-int-to-enum var invoiceType = (ExampleInvoiceTypes)Enum.ToObject(typeof(ExampleInvoiceTypes), diff --git a/Example7.MvcWebApp.ShardingOnly/Controllers/TenantController.cs b/Example7.MvcWebApp.ShardingOnly/Controllers/TenantController.cs index e121bd6c..da95023e 100644 --- a/Example7.MvcWebApp.ShardingOnly/Controllers/TenantController.cs +++ b/Example7.MvcWebApp.ShardingOnly/Controllers/TenantController.cs @@ -5,6 +5,7 @@ using AuthPermissions.AspNetCore; using AuthPermissions.AspNetCore.ShardingServices; using AuthPermissions.BaseCode; +using AuthPermissions.BaseCode.SetupCode; using Example7.MvcWebApp.ShardingOnly.Models; using Example7.MvcWebApp.ShardingOnly.PermissionsCode; using Microsoft.AspNetCore.Mvc; @@ -43,21 +44,21 @@ public async Task ListDatabases([FromServices] IGetSetShardingEnt } [HasPermission(Example7Permissions.TenantCreate)] - public IActionResult Create([FromServices]AuthPermissionsOptions authOptions, - [FromServices] IGetSetShardingEntries shardingService) + public IActionResult Create([FromServices] IGetSetShardingEntries shardingService) { - return View(ShardingOnlyTenantDto.SetupForCreate(authOptions, - shardingService.GetAllShardingEntries().Select(x => x.Name).ToList() - )); + var dto = new ShardingOnlyTenantAddDto(); + dto.SetConnectionStringNames(shardingService.GetConnectionStringNames()); + dto.DbProviderShortName = AuthPDatabaseTypes.SqlServer.ToString(); + return View(dto); } [HttpPost] [ValidateAntiForgeryToken] [HasPermission(Example7Permissions.TenantCreate)] - public async Task Create(ShardingOnlyTenantDto input) + public async Task Create(ShardingOnlyTenantAddDto dto, + [FromServices] IShardingOnlyTenantAddRemove service) { - var status = await _authTenantAdmin.AddSingleTenantAsync(input.TenantName, null, - input.HasOwnDb, input.ShardingName); + var status = await service.CreateTenantAsync(dto); return status.HasErrors ? RedirectToAction(nameof(ErrorDisplay), @@ -104,9 +105,9 @@ public async Task Delete(int id) [HttpPost] [ValidateAntiForgeryToken] [HasPermission(Example7Permissions.TenantDelete)] - public async Task Delete(ShardingOnlyTenantDto input) + public async Task Delete(ShardingOnlyTenantDto input, [FromServices]IShardingOnlyTenantAddRemove service) { - var status = await _authTenantAdmin.DeleteTenantAsync(input.TenantId); + var status = await service.DeleteTenantAsync(input.TenantId); return status.HasErrors ? RedirectToAction(nameof(ErrorDisplay), diff --git a/Example7.MvcWebApp.ShardingOnly/Example7CacheFileStore.Development.json b/Example7.MvcWebApp.ShardingOnly/Example7CacheFileStore.Development.json index dc7c86a2..ff7af741 100644 --- a/Example7.MvcWebApp.ShardingOnly/Example7CacheFileStore.Development.json +++ b/Example7.MvcWebApp.ShardingOnly/Example7CacheFileStore.Development.json @@ -1,6 +1,7 @@ { "Cache": { - "ShardingEntry-TryOut": "{\r\n \"Name\": \"TryOut\",\r\n \"DatabaseName\": \"NewDatabase \",\r\n \"ConnectionName\": \"CentralServer\",\r\n \"DatabaseType\": \"SqlServer\"\r\n}" + "ShardingEntry-New Company-20230805132448": "{\r\n \"Name\": \"New Company-20230805132448\",\r\n \"DatabaseName\": \"20230805132448-713\",\r\n \"ConnectionName\": \"CentralServer\",\r\n \"DatabaseType\": \"SqlServer\"\r\n}", + "ShardingEntry-Another Company-20230805132615": "{\r\n \"Name\": \"Another Company-20230805132615\",\r\n \"DatabaseName\": \"20230805132615-245\",\r\n \"ConnectionName\": \"CentralServer\",\r\n \"DatabaseType\": \"SqlServer\"\r\n}" }, "TimeOuts": {} } \ No newline at end of file diff --git a/Example7.MvcWebApp.ShardingOnly/Program.cs b/Example7.MvcWebApp.ShardingOnly/Program.cs index c05edfb1..475b0956 100644 --- a/Example7.MvcWebApp.ShardingOnly/Program.cs +++ b/Example7.MvcWebApp.ShardingOnly/Program.cs @@ -50,7 +50,7 @@ .SetupMultiTenantSharding(new ShardingEntryOptions(false)) .IndividualAccountsAuthentication() .RegisterAddClaimToUser() - .RegisterTenantChangeService() + .RegisterTenantChangeService() .AddRolesPermissionsIfEmpty(Example7AppAuthSetupData.RolesDefinition) .AddAuthUsersIfEmpty(Example7AppAuthSetupData.UsersRolesDefinition) .RegisterFindUserInfoService() diff --git a/Example7.MvcWebApp.ShardingOnly/Views/Tenant/Create.cshtml b/Example7.MvcWebApp.ShardingOnly/Views/Tenant/Create.cshtml index 4f611d04..8f6402b0 100644 --- a/Example7.MvcWebApp.ShardingOnly/Views/Tenant/Create.cshtml +++ b/Example7.MvcWebApp.ShardingOnly/Views/Tenant/Create.cshtml @@ -1,4 +1,4 @@ -@model Example7.MvcWebApp.ShardingOnly.Models.ShardingOnlyTenantDto +@model AuthPermissions.AspNetCore.ShardingServices.ShardingOnlyTenantAddDto @{ ViewData["Title"] = "Create"; @@ -11,17 +11,17 @@
- + @Html.HiddenFor(x => x.DbProviderShortName)
- @Html.LabelFor(model => model.ShardingName, new { @class = "col-sm-3 col-form-label" }) -
- @Html.DropDownListFor(m => m.ShardingName, - new SelectList(Model.AllShardingEntries)) + +
+ @Html.DropDownListFor(m => m.ConnectionStringName, + new SelectList(Model.ConnectionStringNames))
diff --git a/Example7.MvcWebApp.ShardingOnly/Views/Tenant/Index.cshtml b/Example7.MvcWebApp.ShardingOnly/Views/Tenant/Index.cshtml index ae114f2c..e9ad7265 100644 --- a/Example7.MvcWebApp.ShardingOnly/Views/Tenant/Index.cshtml +++ b/Example7.MvcWebApp.ShardingOnly/Views/Tenant/Index.cshtml @@ -25,15 +25,9 @@ Tenant name - - DataKey - DatabaseInfoName - - HasOwnDb - @(User.HasPermission(Example7Permissions.TenantUpdate) ? "Change" : "") @@ -46,7 +40,6 @@ @item.TenantName - @item.ShardingName diff --git a/Example7.SingleLevelShardingOnly/EfCoreCode/ShardingTenantChangeService.cs b/Example7.SingleLevelShardingOnly/EfCoreCode/ShardingOnlyTenantChangeService.cs similarity index 84% rename from Example7.SingleLevelShardingOnly/EfCoreCode/ShardingTenantChangeService.cs rename to Example7.SingleLevelShardingOnly/EfCoreCode/ShardingOnlyTenantChangeService.cs index 641eda91..bddded4f 100644 --- a/Example7.SingleLevelShardingOnly/EfCoreCode/ShardingTenantChangeService.cs +++ b/Example7.SingleLevelShardingOnly/EfCoreCode/ShardingOnlyTenantChangeService.cs @@ -12,7 +12,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Logging; -using TestSupport.SeedDatabase; namespace Example7.SingleLevelShardingOnly.EfCoreCode; @@ -23,7 +22,7 @@ namespace Example7.SingleLevelShardingOnly.EfCoreCode; /// see at the end of this class. This also allows the DataKey to be added /// which removes the need for using the IgnoreQueryFilters method on any queries /// -public class ShardingTenantChangeService : ITenantChangeService +public class ShardingOnlyTenantChangeService : ITenantChangeService { private readonly Microsoft.EntityFrameworkCore.DbContextOptions _options; private readonly IGetSetShardingEntries _shardingService; @@ -35,8 +34,8 @@ public class ShardingTenantChangeService : ITenantChangeService /// public int DeletedTenantId { get; private set; } - public ShardingTenantChangeService(Microsoft.EntityFrameworkCore.DbContextOptions options, - IGetSetShardingEntries shardingService, ILogger logger) + public ShardingOnlyTenantChangeService(Microsoft.EntityFrameworkCore.DbContextOptions options, + IGetSetShardingEntries shardingService, ILogger logger) { _options = options; _shardingService = shardingService; @@ -104,21 +103,9 @@ public async Task SingleTenantDeleteAsync(Tenant tenant) return null; } - await using var transaction = await context.Database.BeginTransactionAsync(IsolationLevel.Serializable); - try - { - await DeleteTenantData(tenant.GetTenantDataKey(), context, tenant); - DeletedTenantId = tenant.TenantId; - - await transaction.CommitAsync(); - } - catch (Exception e) - { - _logger.LogError(e, $"Failure when trying to delete the '{tenant.TenantFullName}' tenant."); - return "There was a system-level problem - see logs for more detail"; - } - - return null; + //Now we delete it + DeletedTenantId = tenant.TenantId; + return await DeleteTenantData(tenant.GetTenantDataKey(), context, tenant); } public Task HierarchicalTenantUpdateNameAsync(List tenantsToUpdate) @@ -174,28 +161,51 @@ public Task MoveToDifferentDatabaseAsync(string oldDatabaseInfoName, str return null; } - private async Task DeleteTenantData(string dataKey, ShardingOnlyDbContext context, Tenant? tenant = null) + private async Task DeleteTenantData(string dataKey, ShardingOnlyDbContext context, Tenant? tenant = null) { if (tenant?.HasOwnDb == true) { //The tenant its own database, then you should drop the database, but that depends on what SQL Server provider you use. //In this case I can the database because it is on a local SqlServer server. - await context.Database.EnsureDeletedAsync(); - return; + try + { + await context.Database.EnsureDeletedAsync(); + return null; + } + catch (Exception e) + { + _logger.LogError(e, $"Failure when trying to delete the '{tenant.TenantFullName}' tenant."); + return "There was a system-level problem - see logs for more detail"; + } + return null; + } //else we remove all the data with the DataKey of the tenant - var deleteSalesSql = $"DELETE FROM invoice.{nameof(ShardingOnlyDbContext.LineItems)} WHERE DataKey = '{dataKey}'"; - await context.Database.ExecuteSqlRawAsync(deleteSalesSql); - var deleteStockSql = $"DELETE FROM invoice.{nameof(ShardingOnlyDbContext.Invoices)} WHERE DataKey = '{dataKey}'"; - await context.Database.ExecuteSqlRawAsync(deleteStockSql); + await using var transaction = await context.Database.BeginTransactionAsync(IsolationLevel.Serializable); + try + { + var deleteSalesSql = $"DELETE FROM invoice.{nameof(ShardingOnlyDbContext.LineItems)} WHERE DataKey = '{dataKey}'"; + await context.Database.ExecuteSqlRawAsync(deleteSalesSql); + var deleteStockSql = $"DELETE FROM invoice.{nameof(ShardingOnlyDbContext.Invoices)} WHERE DataKey = '{dataKey}'"; + await context.Database.ExecuteSqlRawAsync(deleteStockSql); - var companyTenant = await context.Companies.SingleOrDefaultAsync(); - if (companyTenant != null) + var companyTenant = await context.Companies.SingleOrDefaultAsync(); + if (companyTenant != null) + { + context.Remove(companyTenant); + await context.SaveChangesAsync(); + } + + await transaction.CommitAsync(); + } + catch (Exception e) { - context.Remove(companyTenant); - await context.SaveChangesAsync(); + _logger.LogError(e, $"Failure when trying to delete the '{tenant.TenantFullName}' tenant."); + return "There was a system-level problem - see logs for more detail"; } + + return null; } /// diff --git a/Test/StubClasses/StubGetSetShardingEntries.cs b/Test/StubClasses/StubGetSetShardingEntries.cs index 7d5aac87..0e4a7c14 100644 --- a/Test/StubClasses/StubGetSetShardingEntries.cs +++ b/Test/StubClasses/StubGetSetShardingEntries.cs @@ -104,9 +104,9 @@ public IStatusGeneric RemoveShardingEntry(string shardingEntryName) return new StatusGenericHandler(); } - public IEnumerable GetConnectionStringNames() + public List GetConnectionStringNames() { - return new[] { "UnitTestConnection", "PostgreSqlConnection" }; + return new List { "UnitTestConnection", "PostgreSqlConnection" }; } public IStatusGeneric TestFormingConnectionString(ShardingEntry databaseInfo) diff --git a/Test/UnitTests/TestSharding/TestShardingTenantAddRemove.cs b/Test/UnitTests/TestSharding/TestShardingTenantAddRemove.cs index dcbc3b9a..e7197201 100644 --- a/Test/UnitTests/TestSharding/TestShardingTenantAddRemove.cs +++ b/Test/UnitTests/TestSharding/TestShardingTenantAddRemove.cs @@ -13,7 +13,7 @@ namespace Test.UnitTests.TestSharding; -public class TestShardingTenantAddRemove +public class TestShardingOnlyTenantAddRemove { private readonly ITestOutputHelper _output; @@ -21,21 +21,20 @@ public class TestShardingTenantAddRemove private StubGetSetShardingEntries _getSetShardings; private StubAuthTenantAdminService _stubTenantAdmin; - public TestShardingTenantAddRemove(ITestOutputHelper output) + public TestShardingOnlyTenantAddRemove(ITestOutputHelper output) { _output = output; } /// - /// This returns an instance of the with the TenantType set. + /// This returns an instance of the with the TenantType set. /// It also creates a extra to check duplication errors and also for the Delete /// - /// /// /// /// - private ShardingTenantAddRemove SetupService(bool hasOwnDb, TenantTypes tenantType = TenantTypes.SingleLevel, - bool childTenant = false) + private ShardingOnlyTenantAddRemove SetupService(TenantTypes tenantType = TenantTypes.SingleLevel, + bool childTenant = false) { _authPOptions = new AuthPermissionsOptions { @@ -43,17 +42,17 @@ private ShardingTenantAddRemove SetupService(bool hasOwnDb, TenantTypes tenantTy }; var demoTenant = tenantType == TenantTypes.SingleLevel - ? "TenantSingle".CreateSingleShardingTenant("Other Database", hasOwnDb) - : "TenantHierarchical".CreateHierarchicalShardingTenant("Other Database", hasOwnDb); + ? "TenantSingle".CreateSingleShardingTenant("Other Database", true) + : "TenantHierarchical".CreateHierarchicalShardingTenant("Other Database", true); if (tenantType == TenantTypes.HierarchicalTenant && childTenant) { demoTenant = - "TenantHierarchicalChild".CreateHierarchicalShardingTenant("Other Database", hasOwnDb, demoTenant); + "TenantHierarchicalChild".CreateHierarchicalShardingTenant("Other Database", true, demoTenant); } _getSetShardings = new StubGetSetShardingEntries(this); _stubTenantAdmin = new StubAuthTenantAdminService(demoTenant); - return new ShardingTenantAddRemove(_stubTenantAdmin, _getSetShardings, + return new ShardingOnlyTenantAddRemove(_stubTenantAdmin, _getSetShardings, _authPOptions, "en".SetupAuthPLoggingLocalizer()); } @@ -61,17 +60,17 @@ private ShardingTenantAddRemove SetupService(bool hasOwnDb, TenantTypes tenantTy // Create Single [Fact] - public async Task Create_Single_HasOwnDbTrue_Good() + public async Task Create_Single_Good() { //SETUP - var dto = new ShardingTenantAddDto + var dto = new ShardingOnlyTenantAddDto { TenantName = "Test", - HasOwnDb = true, + ConnectionStringName = "DefaultConnection", DbProviderShortName = "SqlServer", }; - var service = SetupService(true); + var service = SetupService(); //ATTEMPT var status = await service.CreateTenantAsync(dto); @@ -79,44 +78,23 @@ public async Task Create_Single_HasOwnDbTrue_Good() //VERIFY status.HasErrors.ShouldBeFalse(status.GetAllErrors()); _output.WriteLine(_getSetShardings.SharingEntryAddUpDel.ToString()); - _getSetShardings.SharingEntryAddUpDel.Name.ShouldEndWith("-Test"); + _getSetShardings.SharingEntryAddUpDel.Name.ShouldStartWith("Test-"); _getSetShardings.SharingEntryAddUpDel.ConnectionName.ShouldEqual("DefaultConnection"); _getSetShardings.SharingEntryAddUpDel.DatabaseType.ShouldEqual("SqlServer"); _stubTenantAdmin.CalledMethodName.ShouldEqual("AddSingleTenantAsync"); } [Fact] - public async Task Create_Single_HasOwnDbTrue_Good_OverriddenByDatabaseInfoName() - { - //SETUP - var dto = new ShardingTenantAddDto - { - TenantName = "Test", - HasOwnDb = true, - ShardingEntityName = "Default Database" - }; - var service = SetupService(true); - - //ATTEMPT - var status = await service.CreateTenantAsync(dto); - - //VERIFY - status.HasErrors.ShouldBeFalse(status.GetAllErrors()); - _stubTenantAdmin.CalledMethodName.ShouldEqual("AddSingleTenantAsync"); - } - - [Fact] - public async Task Create_Single_HasOwnDbTrue_DuplicateTenant() + public async Task Create_Single_DuplicateTenant() { //SETUP - var dto = new ShardingTenantAddDto + var dto = new ShardingOnlyTenantAddDto { TenantName = "TenantSingle", - HasOwnDb = true, ConnectionStringName = "DefaultConnection", DbProviderShortName = "SqlServer", }; - var service = SetupService(true); + var service = SetupService(); //ATTEMPT var status = await service.CreateTenantAsync(dto); @@ -126,42 +104,21 @@ public async Task Create_Single_HasOwnDbTrue_DuplicateTenant() status.GetAllErrors().ShouldEqual("The tenant name 'TenantSingle' is already used"); } - [Fact] - public async Task Create_Single_HasOwnDbFalse_Good() - { - //SETUP - var dto = new ShardingTenantAddDto - { - TenantName = "Test", - HasOwnDb = false, - ShardingEntityName = "Other Database" - }; - var service = SetupService(false); - - //ATTEMPT - var status = await service.CreateTenantAsync(dto); - - //VERIFY - status.HasErrors.ShouldBeFalse(status.GetAllErrors()); - _output.WriteLine(status.Message); - _stubTenantAdmin.CalledMethodName.ShouldEqual("AddSingleTenantAsync"); - } - //--------------------------------------------------- // Create Hierarchical [Fact] - public async Task Create_Hierarchical_TopLevel_HasOwnDbTrue_Good() + public async Task Create_Hierarchical_TopLevel_Good() { //SETUP - var dto = new ShardingTenantAddDto + var dto = new ShardingOnlyTenantAddDto { TenantName = "Test", - HasOwnDb = true, + ConnectionStringName = "DefaultConnection", DbProviderShortName = "SqlServer", }; - var service = SetupService(false, TenantTypes.HierarchicalTenant); + var service = SetupService(TenantTypes.HierarchicalTenant); //ATTEMPT var status = await service.CreateTenantAsync(dto); @@ -169,39 +126,19 @@ public async Task Create_Hierarchical_TopLevel_HasOwnDbTrue_Good() //VERIFY status.HasErrors.ShouldBeFalse(status.GetAllErrors()); _output.WriteLine(_getSetShardings.SharingEntryAddUpDel.ToString()); - _getSetShardings.SharingEntryAddUpDel.Name.ShouldEndWith("-Test"); + _getSetShardings.SharingEntryAddUpDel.Name.ShouldStartWith("Test-"); _getSetShardings.SharingEntryAddUpDel.ConnectionName.ShouldEqual("DefaultConnection"); _getSetShardings.SharingEntryAddUpDel.DatabaseType.ShouldEqual("SqlServer"); _stubTenantAdmin.CalledMethodName.ShouldEqual("AddHierarchicalTenantAsync"); } - [Fact] - public async Task Create_Hierarchical_TopLevel_HasOwnDbFalse_Good() - { - //SETUP - var dto = new ShardingTenantAddDto - { - TenantName = "Test", - HasOwnDb = false, - ShardingEntityName = "Other Database" - }; - var service = SetupService(false, TenantTypes.HierarchicalTenant); - - //ATTEMPT - var status = await service.CreateTenantAsync(dto); - - //VERIFY - status.HasErrors.ShouldBeFalse(status.GetAllErrors()); - _output.WriteLine(status.Message); - _stubTenantAdmin.CalledMethodName.ShouldEqual("AddHierarchicalTenantAsync"); - } //Its very hard to test this //[Fact] //public async Task Create_Hierarchical_Child_Good() //{ // //SETUP - // var dto = new ShardingTenantAddDto + // var dto = new ShardingOnlyTenantAddDto // { // TenantName = "Test", // HasOwnDb = true, @@ -219,13 +156,13 @@ public async Task Create_Hierarchical_TopLevel_HasOwnDbFalse_Good() //} //--------------------------------------------------- - // Create Single + // Delete Single [Fact] - public async Task Delete_Single_HasOwnDbTrue_Good() + public async Task Delete_Single_Good() { //SETUP - var service = SetupService(true); + var service = SetupService(); ////ATTEMPT var status = await service.DeleteTenantAsync(0); @@ -237,29 +174,14 @@ public async Task Delete_Single_HasOwnDbTrue_Good() _getSetShardings.SharingEntryAddUpDel.Name.ShouldEqual("Other Database"); } - [Fact] - public async Task Delete_Single_HasOwnDbFalse_Good() - { - //SETUP - var service = SetupService(false); - - ////ATTEMPT - var status = await service.DeleteTenantAsync(0); - - ////VERIFY - status.IsValid.ShouldBeTrue(status.GetAllErrors()); - _stubTenantAdmin.CalledMethodName.ShouldEqual("DeleteTenantAsync"); - _getSetShardings.CalledMethodName.ShouldBeNull(); - } - //--------------------------------------------------- // Delete Hierarchical [Fact] - public async Task Delete_Hierarchical_HasOwnDbTrue_Good() + public async Task Delete_Hierarchical_Good() { //SETUP - var service = SetupService(true, TenantTypes.HierarchicalTenant); + var service = SetupService(TenantTypes.HierarchicalTenant); ////ATTEMPT var status = await service.DeleteTenantAsync(0); @@ -270,19 +192,4 @@ public async Task Delete_Hierarchical_HasOwnDbTrue_Good() _getSetShardings.CalledMethodName.ShouldEqual("RemoveShardingEntry"); _getSetShardings.SharingEntryAddUpDel.Name.ShouldEqual("Other Database"); } - - [Fact] - public async Task Delete_Hierarchical_HasOwnDbFalse_Good() - { - //SETUP - var service = SetupService(false, TenantTypes.HierarchicalTenant); - - ////ATTEMPT - var status = await service.DeleteTenantAsync(0); - - ////VERIFY - status.IsValid.ShouldBeTrue(status.GetAllErrors()); - _stubTenantAdmin.CalledMethodName.ShouldEqual("DeleteTenantAsync"); - _getSetShardings.CalledMethodName.ShouldBeNull(); - } } \ No newline at end of file