From d0d44277793428c44cef61b71f34b5a27940a5e5 Mon Sep 17 00:00:00 2001 From: Suhaib Mousa Date: Mon, 28 Apr 2025 11:03:52 +0300 Subject: [PATCH 1/3] Introduce ITenantNameValidator for extensible tenant name validation --- .../DefaultTenantNameValidator.cs | 28 +++++++++++++++ .../TenantManagement/ITenantNameValidator.cs | 9 +++++ .../Abp/TenantManagement/TenantManager.cs | 19 +++------- .../TenantNameValidator_Tests.cs | 35 +++++++++++++++++++ 4 files changed, 77 insertions(+), 14 deletions(-) create mode 100644 modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/DefaultTenantNameValidator.cs create mode 100644 modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantNameValidator.cs create mode 100644 modules/tenant-management/test/Volo.Abp.TenantManagement.Domain.Tests/Volo/Abp/TenantManagement/TenantNameValidator_Tests.cs diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/DefaultTenantNameValidator.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/DefaultTenantNameValidator.cs new file mode 100644 index 0000000000..0b348e17bc --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/DefaultTenantNameValidator.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Services; + +namespace Volo.Abp.TenantManagement; + +public class DefaultTenantNameValidator : ITenantNameValidator, ITransientDependency +{ + protected ITenantRepository TenantRepository { get; } + + public DefaultTenantNameValidator(ITenantRepository tenantRepository) + { + TenantRepository = tenantRepository; + } + + public virtual async Task ValidateAsync(string normalizedTenantName, Guid? expectedTenantId = null) + { + Check.NotNullOrWhiteSpace(normalizedTenantName, nameof(normalizedTenantName)); + + var existingTenant = await TenantRepository.FindByNameAsync(normalizedTenantName); + if (existingTenant != null && existingTenant.Id != expectedTenantId) + { + throw new BusinessException("Volo.Abp.TenantManagement:DuplicateTenantName") + .WithData("Name", normalizedTenantName); + } + } +} \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantNameValidator.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantNameValidator.cs new file mode 100644 index 0000000000..41371aacec --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantNameValidator.cs @@ -0,0 +1,9 @@ +using System; +using System.Threading.Tasks; + +namespace Volo.Abp.TenantManagement; + +public interface ITenantNameValidator +{ + Task ValidateAsync(string normalizedTenantName, Guid? expectedTenantId = null); +} \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs index 0f40cfdd0b..e5aa664336 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs @@ -9,16 +9,16 @@ namespace Volo.Abp.TenantManagement; public class TenantManager : DomainService, ITenantManager { - protected ITenantRepository TenantRepository { get; } + public ITenantNameValidator TenantNameValidator { get; } protected ITenantNormalizer TenantNormalizer { get; } protected ILocalEventBus LocalEventBus { get; } public TenantManager( - ITenantRepository tenantRepository, + ITenantNameValidator tenantNameValidator, ITenantNormalizer tenantNormalizer, ILocalEventBus localEventBus) { - TenantRepository = tenantRepository; + TenantNameValidator = tenantNameValidator; TenantNormalizer = tenantNormalizer; LocalEventBus = localEventBus; } @@ -28,7 +28,7 @@ public class TenantManager : DomainService, ITenantManager Check.NotNull(name, nameof(name)); var normalizedName = TenantNormalizer.NormalizeName(name); - await ValidateNameAsync(normalizedName); + await TenantNameValidator.ValidateAsync(normalizedName); return new Tenant(GuidGenerator.Create(), name, normalizedName); } @@ -39,18 +39,9 @@ public class TenantManager : DomainService, ITenantManager var normalizedName = TenantNormalizer.NormalizeName(name); - await ValidateNameAsync(normalizedName, tenant.Id); + await TenantNameValidator.ValidateAsync(normalizedName, tenant.Id); await LocalEventBus.PublishAsync(new TenantChangedEvent(tenant.Id, tenant.NormalizedName)); tenant.SetName(name); tenant.SetNormalizedName(normalizedName); } - - protected virtual async Task ValidateNameAsync(string normalizeName, Guid? expectedId = null) - { - var tenant = await TenantRepository.FindByNameAsync(normalizeName); - if (tenant != null && tenant.Id != expectedId) - { - throw new BusinessException("Volo.Abp.TenantManagement:DuplicateTenantName").WithData("Name", normalizeName); - } - } } diff --git a/modules/tenant-management/test/Volo.Abp.TenantManagement.Domain.Tests/Volo/Abp/TenantManagement/TenantNameValidator_Tests.cs b/modules/tenant-management/test/Volo.Abp.TenantManagement.Domain.Tests/Volo/Abp/TenantManagement/TenantNameValidator_Tests.cs new file mode 100644 index 0000000000..cf93821384 --- /dev/null +++ b/modules/tenant-management/test/Volo.Abp.TenantManagement.Domain.Tests/Volo/Abp/TenantManagement/TenantNameValidator_Tests.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Guids; +using Xunit; + +namespace Volo.Abp.TenantManagement; + +public class TenantNameValidator_Tests : AbpTenantManagementDomainTestBase +{ + private readonly ITenantNameValidator _tenantNameValidator; + private readonly ITenantRepository _tenantRepository; + public TenantNameValidator_Tests() + { + _tenantRepository = GetRequiredService(); + _tenantNameValidator = GetRequiredService(); + } + + [Fact] + public async Task Should_Throw_If_Name_Is_Null() + { + await Assert.ThrowsAsync(() => _tenantNameValidator.ValidateAsync(null)); + } + + [Fact] + public async Task Should_Throw_If_Duplicate_Name() + { + await Assert.ThrowsAsync(() => _tenantNameValidator.ValidateAsync("VOLOSOFT")); + } + + [Fact] + public async Task Should_Not_Throw_For_Unique_Name() + { + await _tenantNameValidator.ValidateAsync("UNIQUE-TENANT-NAME"); + } +} From 40bd4b2fc5a51c56435cf5bb77395bcd620ec974 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 30 Apr 2025 13:14:08 +0800 Subject: [PATCH 2/3] Add `ITenantValidator`. --- .../TenantManagement/AbpTenantValidator.cs | 26 +++++++++++++++++ .../DefaultTenantNameValidator.cs | 28 ------------------- .../TenantManagement/ITenantNameValidator.cs | 9 ------ .../Abp/TenantManagement/ITenantValidator.cs | 8 ++++++ .../Abp/TenantManagement/TenantManager.cs | 25 +++++++---------- .../TenantNameValidator_Tests.cs | 28 +++++++++++++------ 6 files changed, 64 insertions(+), 60 deletions(-) create mode 100644 modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantValidator.cs delete mode 100644 modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/DefaultTenantNameValidator.cs delete mode 100644 modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantNameValidator.cs create mode 100644 modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantValidator.cs diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantValidator.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantValidator.cs new file mode 100644 index 0000000000..badd9515b8 --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantValidator.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.TenantManagement; + +public class AbpTenantValidator : ITenantValidator, ITransientDependency +{ + protected ITenantRepository TenantRepository { get; } + + public AbpTenantValidator(ITenantRepository tenantRepository) + { + TenantRepository = tenantRepository; + } + + public virtual async Task ValidateAsync(Tenant tenant) + { + Check.NotNullOrWhiteSpace(tenant.Name, nameof(tenant.Name)); + Check.NotNullOrWhiteSpace(tenant.NormalizedName, nameof(tenant.NormalizedName)); + + var owner = await TenantRepository.FindByNameAsync(tenant.NormalizedName); + if (owner != null && owner.Id != tenant.Id) + { + throw new BusinessException("Volo.Abp.TenantManagement:DuplicateTenantName").WithData("Name", tenant.NormalizedName); + } + } +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/DefaultTenantNameValidator.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/DefaultTenantNameValidator.cs deleted file mode 100644 index 0b348e17bc..0000000000 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/DefaultTenantNameValidator.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Services; - -namespace Volo.Abp.TenantManagement; - -public class DefaultTenantNameValidator : ITenantNameValidator, ITransientDependency -{ - protected ITenantRepository TenantRepository { get; } - - public DefaultTenantNameValidator(ITenantRepository tenantRepository) - { - TenantRepository = tenantRepository; - } - - public virtual async Task ValidateAsync(string normalizedTenantName, Guid? expectedTenantId = null) - { - Check.NotNullOrWhiteSpace(normalizedTenantName, nameof(normalizedTenantName)); - - var existingTenant = await TenantRepository.FindByNameAsync(normalizedTenantName); - if (existingTenant != null && existingTenant.Id != expectedTenantId) - { - throw new BusinessException("Volo.Abp.TenantManagement:DuplicateTenantName") - .WithData("Name", normalizedTenantName); - } - } -} \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantNameValidator.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantNameValidator.cs deleted file mode 100644 index 41371aacec..0000000000 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantNameValidator.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Volo.Abp.TenantManagement; - -public interface ITenantNameValidator -{ - Task ValidateAsync(string normalizedTenantName, Guid? expectedTenantId = null); -} \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantValidator.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantValidator.cs new file mode 100644 index 0000000000..3a5cc9ffb0 --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantValidator.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.TenantManagement; + +public interface ITenantValidator +{ + Task ValidateAsync(Tenant tenant); +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs index e5aa664336..21caf852b4 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs @@ -1,6 +1,4 @@ -using System; -using System.Threading.Tasks; -using Volo.Abp.Caching; +using System.Threading.Tasks; using Volo.Abp.Domain.Services; using Volo.Abp.EventBus.Local; using Volo.Abp.MultiTenancy; @@ -9,27 +7,25 @@ namespace Volo.Abp.TenantManagement; public class TenantManager : DomainService, ITenantManager { - public ITenantNameValidator TenantNameValidator { get; } + protected ITenantValidator TenantValidator { get; } protected ITenantNormalizer TenantNormalizer { get; } protected ILocalEventBus LocalEventBus { get; } public TenantManager( - ITenantNameValidator tenantNameValidator, + ITenantValidator tenantValidator, ITenantNormalizer tenantNormalizer, ILocalEventBus localEventBus) { - TenantNameValidator = tenantNameValidator; + TenantValidator = tenantValidator; TenantNormalizer = tenantNormalizer; LocalEventBus = localEventBus; } public virtual async Task CreateAsync(string name) { - Check.NotNull(name, nameof(name)); - - var normalizedName = TenantNormalizer.NormalizeName(name); - await TenantNameValidator.ValidateAsync(normalizedName); - return new Tenant(GuidGenerator.Create(), name, normalizedName); + var tenant = new Tenant(GuidGenerator.Create(), name, TenantNormalizer.NormalizeName(name)); + await TenantValidator.ValidateAsync(tenant); + return tenant; } public virtual async Task ChangeNameAsync(Tenant tenant, string name) @@ -37,11 +33,10 @@ public class TenantManager : DomainService, ITenantManager Check.NotNull(tenant, nameof(tenant)); Check.NotNull(name, nameof(name)); - var normalizedName = TenantNormalizer.NormalizeName(name); - - await TenantNameValidator.ValidateAsync(normalizedName, tenant.Id); await LocalEventBus.PublishAsync(new TenantChangedEvent(tenant.Id, tenant.NormalizedName)); + tenant.SetName(name); - tenant.SetNormalizedName(normalizedName); + tenant.SetNormalizedName( TenantNormalizer.NormalizeName(name)); + await TenantValidator.ValidateAsync(tenant); } } diff --git a/modules/tenant-management/test/Volo.Abp.TenantManagement.Domain.Tests/Volo/Abp/TenantManagement/TenantNameValidator_Tests.cs b/modules/tenant-management/test/Volo.Abp.TenantManagement.Domain.Tests/Volo/Abp/TenantManagement/TenantNameValidator_Tests.cs index cf93821384..bc1477d1ff 100644 --- a/modules/tenant-management/test/Volo.Abp.TenantManagement.Domain.Tests/Volo/Abp/TenantManagement/TenantNameValidator_Tests.cs +++ b/modules/tenant-management/test/Volo.Abp.TenantManagement.Domain.Tests/Volo/Abp/TenantManagement/TenantNameValidator_Tests.cs @@ -1,35 +1,47 @@ using System; using System.Threading.Tasks; -using Volo.Abp.Guids; +using Shouldly; using Xunit; namespace Volo.Abp.TenantManagement; -public class TenantNameValidator_Tests : AbpTenantManagementDomainTestBase +public class TenantValidator_Tests : AbpTenantManagementDomainTestBase { - private readonly ITenantNameValidator _tenantNameValidator; + private readonly TenantManager _tenantManager; private readonly ITenantRepository _tenantRepository; - public TenantNameValidator_Tests() + + public TenantValidator_Tests() { + _tenantManager = GetRequiredService(); _tenantRepository = GetRequiredService(); - _tenantNameValidator = GetRequiredService(); } [Fact] public async Task Should_Throw_If_Name_Is_Null() { - await Assert.ThrowsAsync(() => _tenantNameValidator.ValidateAsync(null)); + await Assert.ThrowsAsync(() => _tenantManager.CreateAsync("")); } [Fact] public async Task Should_Throw_If_Duplicate_Name() { - await Assert.ThrowsAsync(() => _tenantNameValidator.ValidateAsync("VOLOSOFT")); + await Assert.ThrowsAsync(() => _tenantManager.CreateAsync("VOLOSOFT")); + + var tenant = await _tenantRepository.FindByNameAsync("ABP"); + await Assert.ThrowsAsync(() => _tenantManager.ChangeNameAsync(tenant, "VOLOSOFT")); } [Fact] public async Task Should_Not_Throw_For_Unique_Name() { - await _tenantNameValidator.ValidateAsync("UNIQUE-TENANT-NAME"); + var tenant = await _tenantManager.CreateAsync("VOLOSOFT2"); + await _tenantRepository.InsertAsync(tenant); + + tenant = await _tenantRepository.FindByNameAsync("ABP"); + await _tenantManager.ChangeNameAsync(tenant, "VOLOSOFT3"); + await _tenantRepository.UpdateAsync(tenant); + + tenant = await _tenantRepository.FindByNameAsync("VOLOSOFT3"); + tenant.ShouldNotBeNull(); } } From bb977c468cad2b4da8f10ef27391505626d631f9 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 30 Apr 2025 13:24:13 +0800 Subject: [PATCH 3/3] Ensure name parameter is not null in CreateAsync method --- .../Volo/Abp/TenantManagement/TenantManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs index 21caf852b4..91cdf87abb 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs @@ -23,6 +23,8 @@ public class TenantManager : DomainService, ITenantManager public virtual async Task CreateAsync(string name) { + Check.NotNull(name, nameof(name)); + var tenant = new Tenant(GuidGenerator.Create(), name, TenantNormalizer.NormalizeName(name)); await TenantValidator.ValidateAsync(tenant); return tenant;