From 6ac16a340581134d8478ccbb3e872cd95833598d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 2 Feb 2018 13:03:19 +0300 Subject: [PATCH] Created application layer and tests for multitenancy. --- Volo.Abp.sln | 30 ++++- .../EntityFrameworkCore/AbpDeskDbContext.cs | 4 +- .../Application/Dtos/IHasLongTotalCount.cs | 13 -- .../Abp/Application/Dtos/IHasTotalCount.cs | 2 +- .../Abp/Application/Dtos/PagedResultDto.cs | 4 +- .../Identity/AbpIdentityApplicationModule.cs | 1 - ...ntityApplicationModuleAutoMapperProfile.cs | 2 +- .../Abp/Identity/IdentityUserAppService.cs | 2 +- .../Identity/EfCoreIdentityUserRepository.cs | 3 +- ....MultiTenancy.Application.Contracts.csproj | 21 ++++ ...pMultiTenancyApplicationContractsModule.cs | 15 +++ .../Volo/Abp/MultiTenancy/GetTenantsInput.cs | 9 ++ .../Abp/MultiTenancy/ITenantAppService.cs | 10 ++ .../Volo/Abp/MultiTenancy/TenantCreateDto.cs | 7 ++ .../TenantCreateOrUpdateDtoBase.cs | 7 ++ .../Volo/Abp/MultiTenancy/TenantDto.cs | 10 ++ .../Volo/Abp/MultiTenancy/TenantUpdateDto.cs | 7 ++ .../Volo.Abp.MultiTenancy.Application.csproj | 22 ++++ .../AbpMultiTenancyApplicationModule.cs | 21 ++++ ...nancyApplicationModuleAutoMapperProfile.cs | 12 ++ .../MultiTenancyAppServiceBase.cs | 9 ++ .../Volo/Abp/MultiTenancy/TenantAppService.cs | 63 ++++++++++ .../TenantConnectionStringConsts.cs | 2 +- .../Volo.Abp.MultiTenancy.Domain.csproj | 1 + ...Abp.MultiTenancy.Domain.csproj.DotSettings | 2 + .../AbpMultiTenancyDomainModule.cs | 2 + .../Volo/Abp/MultiTenancy/ITenantManager.cs | 14 +++ .../Abp/MultiTenancy/ITenantRepository.cs | 12 +- .../Volo/Abp/MultiTenancy/Tenant.cs | 11 +- .../MultiTenancy/TenantConnectionString.cs | 2 +- .../Volo/Abp/MultiTenancy/TenantManager.cs | 43 +++++++ ...ncy.EntityFrameworkCore.csproj.DotSettings | 2 + .../MultiTenancy/EfCoreTenantRepository.cs | 42 +++++-- .../MultiTenancyDbContext.cs | 6 +- ...TenancyDbContextModelCreatingExtensions.cs | 19 ++- .../PersonAppServiceClientProxy_Tests.cs | 2 +- .../Identity/IdentityRoleAppService_Tests.cs | 5 +- .../Identity/IdentityUserAppService_Tests.cs | 7 +- ....Abp.MultiTenancy.Application.Tests.csproj | 26 ++++ .../AbpIdentityApplicationTestBase.cs | 30 +++++ .../AbpMultiTenancyApplicationTestModule.cs | 55 +++++++++ .../AbpMultiTenancyTestDataBuilder.cs | 34 ++++++ .../MultiTenancy/TenantAppService_Tests.cs | 113 ++++++++++++++++++ 43 files changed, 651 insertions(+), 53 deletions(-) delete mode 100644 src/Volo.Abp.Ddd/Volo/Abp/Application/Dtos/IHasLongTotalCount.cs rename src/Volo.Abp.Identity.Application/Volo/Abp/Identity/{ObjectMappings => }/AbpIdentityApplicationModuleAutoMapperProfile.cs (87%) create mode 100644 src/Volo.Abp.MultiTenancy.Application.Contracts/Volo.Abp.MultiTenancy.Application.Contracts.csproj create mode 100644 src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/AbpMultiTenancyApplicationContractsModule.cs create mode 100644 src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/GetTenantsInput.cs create mode 100644 src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/ITenantAppService.cs create mode 100644 src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/TenantCreateDto.cs create mode 100644 src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/TenantCreateOrUpdateDtoBase.cs create mode 100644 src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/TenantDto.cs create mode 100644 src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/TenantUpdateDto.cs create mode 100644 src/Volo.Abp.MultiTenancy.Application/Volo.Abp.MultiTenancy.Application.csproj create mode 100644 src/Volo.Abp.MultiTenancy.Application/Volo/Abp/MultiTenancy/AbpMultiTenancyApplicationModule.cs create mode 100644 src/Volo.Abp.MultiTenancy.Application/Volo/Abp/MultiTenancy/AbpMultiTenancyApplicationModuleAutoMapperProfile.cs create mode 100644 src/Volo.Abp.MultiTenancy.Application/Volo/Abp/MultiTenancy/MultiTenancyAppServiceBase.cs create mode 100644 src/Volo.Abp.MultiTenancy.Application/Volo/Abp/MultiTenancy/TenantAppService.cs create mode 100644 src/Volo.Abp.MultiTenancy.Domain/Volo.Abp.MultiTenancy.Domain.csproj.DotSettings create mode 100644 src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/ITenantManager.cs create mode 100644 src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/TenantManager.cs create mode 100644 src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo.Abp.MultiTenancy.EntityFrameworkCore.csproj.DotSettings create mode 100644 test/Volo.Abp.MultiTenancy.Application.Tests/Volo.Abp.MultiTenancy.Application.Tests.csproj create mode 100644 test/Volo.Abp.MultiTenancy.Application.Tests/Volo/Abp/MultiTenancy/AbpIdentityApplicationTestBase.cs create mode 100644 test/Volo.Abp.MultiTenancy.Application.Tests/Volo/Abp/MultiTenancy/AbpMultiTenancyApplicationTestModule.cs create mode 100644 test/Volo.Abp.MultiTenancy.Application.Tests/Volo/Abp/MultiTenancy/AbpMultiTenancyTestDataBuilder.cs create mode 100644 test/Volo.Abp.MultiTenancy.Application.Tests/Volo/Abp/MultiTenancy/TenantAppService_Tests.cs diff --git a/Volo.Abp.sln b/Volo.Abp.sln index e044bc5970..3e482dc366 100644 --- a/Volo.Abp.sln +++ b/Volo.Abp.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27130.0 +VisualStudioVersion = 15.0.27130.2026 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}" EndProject @@ -228,11 +228,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{282A EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Abp.MultiTenancy", "Abp.MultiTenancy", "{CA154803-3589-47B3-B7CB-B18F94FE1EB6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.MultiTenancy.EntityFrameworkCore", "src\Volo.Abp.MultiTenancy.EntityFrameworkCore\Volo.Abp.MultiTenancy.EntityFrameworkCore.csproj", "{9D7C2C19-1EE4-40BB-A1A4-27B175F1BFDD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.MultiTenancy.EntityFrameworkCore", "src\Volo.Abp.MultiTenancy.EntityFrameworkCore\Volo.Abp.MultiTenancy.EntityFrameworkCore.csproj", "{9D7C2C19-1EE4-40BB-A1A4-27B175F1BFDD}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.MultiTenancy.Domain", "src\Volo.Abp.MultiTenancy.Domain\Volo.Abp.MultiTenancy.Domain.csproj", "{3FA1F4A4-BF5A-4CE3-B76F-2147FE31AB43}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.MultiTenancy.Domain.Shared", "src\Volo.Abp.MultiTenancy.Domain.Shared\Volo.Abp.MultiTenancy.Domain.Shared.csproj", "{4605A64C-B4E0-4A04-9BF2-501027375258}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.MultiTenancy.Domain.Shared", "src\Volo.Abp.MultiTenancy.Domain.Shared\Volo.Abp.MultiTenancy.Domain.Shared.csproj", "{4605A64C-B4E0-4A04-9BF2-501027375258}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.MultiTenancy.Application.Contracts", "src\Volo.Abp.MultiTenancy.Application.Contracts\Volo.Abp.MultiTenancy.Application.Contracts.csproj", "{6A99C679-EA3B-4324-8D6B-89DB8C7D3DA0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.MultiTenancy.Application", "src\Volo.Abp.MultiTenancy.Application\Volo.Abp.MultiTenancy.Application.csproj", "{FCEC7C9D-067F-452B-98C9-1878F7EA02EB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Abp.MultiTenancy", "Abp.MultiTenancy", "{3CE22CAC-4B27-4EEC-A35E-C01219ED6E99}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.MultiTenancy.Application.Tests", "test\Volo.Abp.MultiTenancy.Application.Tests\Volo.Abp.MultiTenancy.Application.Tests.csproj", "{B65973F1-9EE0-4914-B6F2-D88F753E1D21}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -608,6 +616,18 @@ Global {4605A64C-B4E0-4A04-9BF2-501027375258}.Debug|Any CPU.Build.0 = Debug|Any CPU {4605A64C-B4E0-4A04-9BF2-501027375258}.Release|Any CPU.ActiveCfg = Release|Any CPU {4605A64C-B4E0-4A04-9BF2-501027375258}.Release|Any CPU.Build.0 = Release|Any CPU + {6A99C679-EA3B-4324-8D6B-89DB8C7D3DA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A99C679-EA3B-4324-8D6B-89DB8C7D3DA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A99C679-EA3B-4324-8D6B-89DB8C7D3DA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6A99C679-EA3B-4324-8D6B-89DB8C7D3DA0}.Release|Any CPU.Build.0 = Release|Any CPU + {FCEC7C9D-067F-452B-98C9-1878F7EA02EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCEC7C9D-067F-452B-98C9-1878F7EA02EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FCEC7C9D-067F-452B-98C9-1878F7EA02EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCEC7C9D-067F-452B-98C9-1878F7EA02EB}.Release|Any CPU.Build.0 = Release|Any CPU + {B65973F1-9EE0-4914-B6F2-D88F753E1D21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B65973F1-9EE0-4914-B6F2-D88F753E1D21}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B65973F1-9EE0-4914-B6F2-D88F753E1D21}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B65973F1-9EE0-4914-B6F2-D88F753E1D21}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -721,6 +741,10 @@ Global {9D7C2C19-1EE4-40BB-A1A4-27B175F1BFDD} = {CA154803-3589-47B3-B7CB-B18F94FE1EB6} {3FA1F4A4-BF5A-4CE3-B76F-2147FE31AB43} = {CA154803-3589-47B3-B7CB-B18F94FE1EB6} {4605A64C-B4E0-4A04-9BF2-501027375258} = {CA154803-3589-47B3-B7CB-B18F94FE1EB6} + {6A99C679-EA3B-4324-8D6B-89DB8C7D3DA0} = {CA154803-3589-47B3-B7CB-B18F94FE1EB6} + {FCEC7C9D-067F-452B-98C9-1878F7EA02EB} = {CA154803-3589-47B3-B7CB-B18F94FE1EB6} + {3CE22CAC-4B27-4EEC-A35E-C01219ED6E99} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {B65973F1-9EE0-4914-B6F2-D88F753E1D21} = {3CE22CAC-4B27-4EEC-A35E-C01219ED6E99} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/src/AbpDesk/AbpDesk.EntityFrameworkCore/AbpDesk/EntityFrameworkCore/AbpDeskDbContext.cs b/src/AbpDesk/AbpDesk.EntityFrameworkCore/AbpDesk/EntityFrameworkCore/AbpDeskDbContext.cs index 17d1eeaf57..af9b2731fa 100644 --- a/src/AbpDesk/AbpDesk.EntityFrameworkCore/AbpDesk/EntityFrameworkCore/AbpDeskDbContext.cs +++ b/src/AbpDesk/AbpDesk.EntityFrameworkCore/AbpDesk/EntityFrameworkCore/AbpDeskDbContext.cs @@ -26,9 +26,9 @@ namespace AbpDesk.EntityFrameworkCore { base.OnModelCreating(modelBuilder); - this.ConfigureMultiTenancy(modelBuilder); + this.ConfigureMultiTenancy(modelBuilder, MultiTenancyDbContext.TablePrefix, MultiTenancyDbContext.Schema); - //Use different classes to map each entity type? + //Use different classes to map each entity type, as a better practice? modelBuilder.Entity(b => { b.ToTable("DskTickets"); diff --git a/src/Volo.Abp.Ddd/Volo/Abp/Application/Dtos/IHasLongTotalCount.cs b/src/Volo.Abp.Ddd/Volo/Abp/Application/Dtos/IHasLongTotalCount.cs deleted file mode 100644 index 46873e053e..0000000000 --- a/src/Volo.Abp.Ddd/Volo/Abp/Application/Dtos/IHasLongTotalCount.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Volo.Abp.Application.Dtos -{ - /// - /// This interface is defined to standardize to set "Total Count of Items" to a DTO for long type. - /// - public interface IHasLongTotalCount - { - /// - /// Total count of Items. - /// - long TotalCount { get; set; } - } -} \ No newline at end of file diff --git a/src/Volo.Abp.Ddd/Volo/Abp/Application/Dtos/IHasTotalCount.cs b/src/Volo.Abp.Ddd/Volo/Abp/Application/Dtos/IHasTotalCount.cs index e617421e45..908af1ad20 100644 --- a/src/Volo.Abp.Ddd/Volo/Abp/Application/Dtos/IHasTotalCount.cs +++ b/src/Volo.Abp.Ddd/Volo/Abp/Application/Dtos/IHasTotalCount.cs @@ -8,6 +8,6 @@ namespace Volo.Abp.Application.Dtos /// /// Total count of Items. /// - int TotalCount { get; set; } + long TotalCount { get; set; } } } \ No newline at end of file diff --git a/src/Volo.Abp.Ddd/Volo/Abp/Application/Dtos/PagedResultDto.cs b/src/Volo.Abp.Ddd/Volo/Abp/Application/Dtos/PagedResultDto.cs index a4c8e6ce82..b10d64211a 100644 --- a/src/Volo.Abp.Ddd/Volo/Abp/Application/Dtos/PagedResultDto.cs +++ b/src/Volo.Abp.Ddd/Volo/Abp/Application/Dtos/PagedResultDto.cs @@ -11,7 +11,7 @@ namespace Volo.Abp.Application.Dtos public class PagedResultDto : ListResultDto, IPagedResult { /// - public int TotalCount { get; set; } + public long TotalCount { get; set; } //TODO: Can be a long value..? /// /// Creates a new object. @@ -26,7 +26,7 @@ namespace Volo.Abp.Application.Dtos /// /// Total count of Items /// List of items in current page - public PagedResultDto(int totalCount, IReadOnlyList items) + public PagedResultDto(long totalCount, IReadOnlyList items) : base(items) { TotalCount = totalCount; diff --git a/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModule.cs b/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModule.cs index 21bd6c7922..9c8b092d8c 100644 --- a/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModule.cs +++ b/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModule.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AutoMapper; -using Volo.Abp.Identity.ObjectMappings; using Volo.Abp.Modularity; namespace Volo.Abp.Identity diff --git a/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/ObjectMappings/AbpIdentityApplicationModuleAutoMapperProfile.cs b/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs similarity index 87% rename from src/Volo.Abp.Identity.Application/Volo/Abp/Identity/ObjectMappings/AbpIdentityApplicationModuleAutoMapperProfile.cs rename to src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs index 57dee9f692..e796a0a7ae 100644 --- a/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/ObjectMappings/AbpIdentityApplicationModuleAutoMapperProfile.cs +++ b/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs @@ -1,6 +1,6 @@ using AutoMapper; -namespace Volo.Abp.Identity.ObjectMappings +namespace Volo.Abp.Identity { public class AbpIdentityApplicationModuleAutoMapperProfile : Profile { diff --git a/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs b/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs index 1f853b5822..2683ee1405 100644 --- a/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs +++ b/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs @@ -27,7 +27,7 @@ namespace Volo.Abp.Identity public async Task> GetListAsync(GetIdentityUsersInput input) { - var count = (int)await _userRepository.GetCountAsync(); + var count = await _userRepository.GetCountAsync(); var list = await _userRepository.GetListAsync(input.Sorting, input.MaxResultCount, input.SkipCount, input.Filter); return new PagedResultDto( diff --git a/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EfCoreIdentityUserRepository.cs b/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EfCoreIdentityUserRepository.cs index 2364fb1641..48aa9df6ab 100644 --- a/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EfCoreIdentityUserRepository.cs +++ b/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EfCoreIdentityUserRepository.cs @@ -90,7 +90,8 @@ namespace Volo.Abp.Identity u.Email.Contains(filter) ) .OrderBy(sorting ?? nameof(IdentityUser.UserName)) - .PageBy(skipCount, maxResultCount).ToListAsync(cancellationToken); + .PageBy(skipCount, maxResultCount) + .ToListAsync(cancellationToken); } public async Task> GetListAsync(string sorting, int maxResultCount, int skipCount) diff --git a/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo.Abp.MultiTenancy.Application.Contracts.csproj b/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo.Abp.MultiTenancy.Application.Contracts.csproj new file mode 100644 index 0000000000..caac0f1dfa --- /dev/null +++ b/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo.Abp.MultiTenancy.Application.Contracts.csproj @@ -0,0 +1,21 @@ + + + + + + netstandard2.0 + Volo.Abp.MultiTenancy.Application.Contracts + Volo.Abp.MultiTenancy.Application.Contracts + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + diff --git a/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/AbpMultiTenancyApplicationContractsModule.cs b/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/AbpMultiTenancyApplicationContractsModule.cs new file mode 100644 index 0000000000..ad718c7eaf --- /dev/null +++ b/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/AbpMultiTenancyApplicationContractsModule.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; + +namespace Volo.Abp.MultiTenancy +{ + [DependsOn(typeof(AbpDddModule))] + [DependsOn(typeof(AbpMultiTenancyDomainSharedModule))] + public class AbpMultiTenancyApplicationContractsModule : AbpModule + { + public override void ConfigureServices(IServiceCollection services) + { + services.AddAssemblyOf(); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/GetTenantsInput.cs b/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/GetTenantsInput.cs new file mode 100644 index 0000000000..97b8a95c9e --- /dev/null +++ b/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/GetTenantsInput.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Application.Dtos; + +namespace Volo.Abp.MultiTenancy +{ + public class GetTenantsInput : PagedAndSortedResultRequestDto + { + public string Filter { get; set; } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/ITenantAppService.cs b/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/ITenantAppService.cs new file mode 100644 index 0000000000..0c6d2d109c --- /dev/null +++ b/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/ITenantAppService.cs @@ -0,0 +1,10 @@ +using System; +using Volo.Abp.Application.Services; + +namespace Volo.Abp.MultiTenancy +{ + public interface ITenantAppService : IAsyncCrudAppService + { + //TODO: Manage connection strings + } +} diff --git a/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/TenantCreateDto.cs b/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/TenantCreateDto.cs new file mode 100644 index 0000000000..4cc17605f2 --- /dev/null +++ b/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/TenantCreateDto.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.MultiTenancy +{ + public class TenantCreateDto : TenantCreateOrUpdateDtoBase + { + + } +} \ No newline at end of file diff --git a/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/TenantCreateOrUpdateDtoBase.cs b/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/TenantCreateOrUpdateDtoBase.cs new file mode 100644 index 0000000000..60e5320244 --- /dev/null +++ b/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/TenantCreateOrUpdateDtoBase.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.MultiTenancy +{ + public abstract class TenantCreateOrUpdateDtoBase + { + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/TenantDto.cs b/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/TenantDto.cs new file mode 100644 index 0000000000..3dfe2d486f --- /dev/null +++ b/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/TenantDto.cs @@ -0,0 +1,10 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Volo.Abp.MultiTenancy +{ + public class TenantDto : EntityDto + { + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/TenantUpdateDto.cs b/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/TenantUpdateDto.cs new file mode 100644 index 0000000000..db3485dd99 --- /dev/null +++ b/src/Volo.Abp.MultiTenancy.Application.Contracts/Volo/Abp/MultiTenancy/TenantUpdateDto.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.MultiTenancy +{ + public class TenantUpdateDto : TenantCreateOrUpdateDtoBase + { + + } +} \ No newline at end of file diff --git a/src/Volo.Abp.MultiTenancy.Application/Volo.Abp.MultiTenancy.Application.csproj b/src/Volo.Abp.MultiTenancy.Application/Volo.Abp.MultiTenancy.Application.csproj new file mode 100644 index 0000000000..de2d691c9d --- /dev/null +++ b/src/Volo.Abp.MultiTenancy.Application/Volo.Abp.MultiTenancy.Application.csproj @@ -0,0 +1,22 @@ + + + + + + netstandard2.0 + Volo.Abp.MultiTenancy.Application + Volo.Abp.MultiTenancy.Application + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + diff --git a/src/Volo.Abp.MultiTenancy.Application/Volo/Abp/MultiTenancy/AbpMultiTenancyApplicationModule.cs b/src/Volo.Abp.MultiTenancy.Application/Volo/Abp/MultiTenancy/AbpMultiTenancyApplicationModule.cs new file mode 100644 index 0000000000..659eca6e47 --- /dev/null +++ b/src/Volo.Abp.MultiTenancy.Application/Volo/Abp/MultiTenancy/AbpMultiTenancyApplicationModule.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AutoMapper; +using Volo.Abp.Modularity; + +namespace Volo.Abp.MultiTenancy +{ + [DependsOn(typeof(AbpMultiTenancyDomainModule))] + [DependsOn(typeof(AbpMultiTenancyApplicationContractsModule))] + public class AbpMultiTenancyApplicationModule : AbpModule + { + public override void ConfigureServices(IServiceCollection services) + { + services.Configure(options => + { + options.AddProfile(validate: true); + }); + + services.AddAssemblyOf(); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.MultiTenancy.Application/Volo/Abp/MultiTenancy/AbpMultiTenancyApplicationModuleAutoMapperProfile.cs b/src/Volo.Abp.MultiTenancy.Application/Volo/Abp/MultiTenancy/AbpMultiTenancyApplicationModuleAutoMapperProfile.cs new file mode 100644 index 0000000000..bc7174b256 --- /dev/null +++ b/src/Volo.Abp.MultiTenancy.Application/Volo/Abp/MultiTenancy/AbpMultiTenancyApplicationModuleAutoMapperProfile.cs @@ -0,0 +1,12 @@ +using AutoMapper; + +namespace Volo.Abp.MultiTenancy +{ + public class AbpMultiTenancyApplicationModuleAutoMapperProfile : Profile + { + public AbpMultiTenancyApplicationModuleAutoMapperProfile() + { + CreateMap(); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.MultiTenancy.Application/Volo/Abp/MultiTenancy/MultiTenancyAppServiceBase.cs b/src/Volo.Abp.MultiTenancy.Application/Volo/Abp/MultiTenancy/MultiTenancyAppServiceBase.cs new file mode 100644 index 0000000000..a9b7284c29 --- /dev/null +++ b/src/Volo.Abp.MultiTenancy.Application/Volo/Abp/MultiTenancy/MultiTenancyAppServiceBase.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Application.Services; + +namespace Volo.Abp.MultiTenancy +{ + public class MultiTenancyAppServiceBase : ApplicationService + { + + } +} \ No newline at end of file diff --git a/src/Volo.Abp.MultiTenancy.Application/Volo/Abp/MultiTenancy/TenantAppService.cs b/src/Volo.Abp.MultiTenancy.Application/Volo/Abp/MultiTenancy/TenantAppService.cs new file mode 100644 index 0000000000..cf765800a4 --- /dev/null +++ b/src/Volo.Abp.MultiTenancy.Application/Volo/Abp/MultiTenancy/TenantAppService.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; + +namespace Volo.Abp.MultiTenancy +{ + public class TenantAppService : MultiTenancyAppServiceBase, ITenantAppService + { + private readonly ITenantRepository _tenantRepository; + private readonly ITenantManager _tenantManager; + + public TenantAppService(ITenantRepository tenantRepository, ITenantManager tenantManager) + { + _tenantRepository = tenantRepository; + _tenantManager = tenantManager; + } + + public async Task GetAsync(Guid id) + { + return ObjectMapper.Map( + await _tenantRepository.GetAsync(id) + ); + } + + public async Task> GetListAsync(GetTenantsInput input) + { + var count = await _tenantRepository.GetCountAsync(); + var list = await _tenantRepository.GetListAsync(input.Sorting, input.MaxResultCount, input.SkipCount, input.Filter); + + return new PagedResultDto( + count, + ObjectMapper.Map, List>(list) + ); + } + + public async Task CreateAsync(TenantCreateDto input) + { + var tenant = await _tenantManager.CreateAsync(input.Name); + await _tenantRepository.InsertAsync(tenant); + return ObjectMapper.Map(tenant); + } + + public async Task UpdateAsync(Guid id, TenantUpdateDto input) + { + var tenant = await _tenantRepository.GetAsync(id); + await _tenantManager.ChangeNameAsync(tenant, input.Name); + await _tenantRepository.UpdateAsync(tenant); + return ObjectMapper.Map(tenant); + } + + public async Task DeleteAsync(Guid id) + { + var tenant = await _tenantRepository.FindAsync(id); + if (tenant == null) + { + return; + } + + await _tenantRepository.DeleteAsync(tenant); + } + } +} diff --git a/src/Volo.Abp.MultiTenancy.Domain.Shared/Volo/Abp/MultiTenancy/TenantConnectionStringConsts.cs b/src/Volo.Abp.MultiTenancy.Domain.Shared/Volo/Abp/MultiTenancy/TenantConnectionStringConsts.cs index 067b87a2d2..a1e00a97fa 100644 --- a/src/Volo.Abp.MultiTenancy.Domain.Shared/Volo/Abp/MultiTenancy/TenantConnectionStringConsts.cs +++ b/src/Volo.Abp.MultiTenancy.Domain.Shared/Volo/Abp/MultiTenancy/TenantConnectionStringConsts.cs @@ -2,7 +2,7 @@ { public static class TenantConnectionStringConsts { - public const int MaxNameLength = 256; + public const int MaxNameLength = 128; public const int MaxValueLength = 1024; } diff --git a/src/Volo.Abp.MultiTenancy.Domain/Volo.Abp.MultiTenancy.Domain.csproj b/src/Volo.Abp.MultiTenancy.Domain/Volo.Abp.MultiTenancy.Domain.csproj index fa42b9aee9..9395d1e888 100644 --- a/src/Volo.Abp.MultiTenancy.Domain/Volo.Abp.MultiTenancy.Domain.csproj +++ b/src/Volo.Abp.MultiTenancy.Domain/Volo.Abp.MultiTenancy.Domain.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Volo.Abp.MultiTenancy.Domain/Volo.Abp.MultiTenancy.Domain.csproj.DotSettings b/src/Volo.Abp.MultiTenancy.Domain/Volo.Abp.MultiTenancy.Domain.csproj.DotSettings new file mode 100644 index 0000000000..58ad6c8854 --- /dev/null +++ b/src/Volo.Abp.MultiTenancy.Domain/Volo.Abp.MultiTenancy.Domain.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp71 \ No newline at end of file diff --git a/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/AbpMultiTenancyDomainModule.cs b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/AbpMultiTenancyDomainModule.cs index 1912ef52a4..2f5c264873 100644 --- a/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/AbpMultiTenancyDomainModule.cs +++ b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/AbpMultiTenancyDomainModule.cs @@ -2,6 +2,7 @@ using Volo.Abp.AutoMapper; using Volo.Abp.Data; using Volo.Abp.Modularity; +using Volo.Abp.Ui; namespace Volo.Abp.MultiTenancy { @@ -10,6 +11,7 @@ namespace Volo.Abp.MultiTenancy [DependsOn(typeof(AbpDataModule))] [DependsOn(typeof(AbpDddModule))] [DependsOn(typeof(AbpAutoMapperModule))] + [DependsOn(typeof(AbpUiModule))] //TODO: It's not good to depend on the UI module. However, UserFriendlyException is inside it! public class AbpMultiTenancyDomainModule : AbpModule { public override void ConfigureServices(IServiceCollection services) diff --git a/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/ITenantManager.cs b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/ITenantManager.cs new file mode 100644 index 0000000000..595c3e993e --- /dev/null +++ b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/ITenantManager.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.Domain.Services; + +namespace Volo.Abp.MultiTenancy +{ + public interface ITenantManager : IDomainService + { + [NotNull] + Task CreateAsync([NotNull] string name); + + Task ChangeNameAsync([NotNull] Tenant tenant, [NotNull] string name); + } +} diff --git a/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/ITenantRepository.cs b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/ITenantRepository.cs index b1c73774d9..e574a72be7 100644 --- a/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/ITenantRepository.cs +++ b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/ITenantRepository.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; @@ -6,8 +8,14 @@ namespace Volo.Abp.MultiTenancy { public interface ITenantRepository : IBasicRepository { - Task FindByNameIncludeDetailsAsync(string name); + Task FindByNameAsync(string name, CancellationToken cancellationToken = default); - Task FindWithIncludeDetailsAsync(Guid id); + Task FindByNameIncludeDetailsAsync(string name, CancellationToken cancellationToken = default); + + Task FindWithIncludeDetailsAsync(Guid id, CancellationToken cancellationToken = default); + + Task GetCountAsync(CancellationToken cancellationToken = default); + + Task> GetListAsync(string sorting, int maxResultCount, int skipCount, string filter, CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/Tenant.cs b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/Tenant.cs index 4c6d4feac4..0d5573adfb 100644 --- a/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/Tenant.cs +++ b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/Tenant.cs @@ -17,8 +17,10 @@ namespace Volo.Abp.MultiTenancy } - public Tenant(Guid id, [NotNull] string name) + protected internal Tenant(Guid id, [NotNull] string name) { + Check.NotNull(name, nameof(name)); + Id = id; Name = name; @@ -36,5 +38,12 @@ namespace Volo.Abp.MultiTenancy { return ConnectionStrings.FirstOrDefault(c => c.Name == name)?.Value; } + + internal void SetName([NotNull] string name) + { + Check.NotNull(name, nameof(name)); + + Name = name; + } } } \ No newline at end of file diff --git a/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/TenantConnectionString.cs b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/TenantConnectionString.cs index c65163aaac..4be2075df7 100644 --- a/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/TenantConnectionString.cs +++ b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/TenantConnectionString.cs @@ -4,7 +4,7 @@ using Volo.Abp.Domain.Entities; namespace Volo.Abp.MultiTenancy { - public class TenantConnectionString : Entity //TODO: PK should be TenantId + Name (so, inherit from Entity) + public class TenantConnectionString : Entity { public virtual Guid TenantId { get; protected set; } diff --git a/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/TenantManager.cs b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/TenantManager.cs new file mode 100644 index 0000000000..5a187c7c85 --- /dev/null +++ b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/TenantManager.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Domain.Services; +using Volo.Abp.Ui; + +namespace Volo.Abp.MultiTenancy +{ + public class TenantManager : DomainService, ITenantManager + { + private readonly ITenantRepository _tenantRepository; + + public TenantManager(ITenantRepository tenantRepository) + { + _tenantRepository = tenantRepository; + } + + public async Task CreateAsync(string name) + { + Check.NotNull(name, nameof(name)); + + await ValidateNameAsync(name); + return new Tenant(GuidGenerator.Create(), name); + } + + public async Task ChangeNameAsync(Tenant tenant, string name) + { + Check.NotNull(tenant, nameof(tenant)); + Check.NotNull(name, nameof(name)); + + await ValidateNameAsync(name, tenant.Id); + tenant.SetName(name); + } + + protected virtual async Task ValidateNameAsync(string name, Guid? expectedId = null) + { + var tenant = await _tenantRepository.FindByNameAsync(name); + if (tenant != null && tenant.Id != expectedId) + { + throw new UserFriendlyException("Duplicate tenancy name: " + name); //TODO: A domain exception would be better..? + } + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo.Abp.MultiTenancy.EntityFrameworkCore.csproj.DotSettings b/src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo.Abp.MultiTenancy.EntityFrameworkCore.csproj.DotSettings new file mode 100644 index 0000000000..58ad6c8854 --- /dev/null +++ b/src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo.Abp.MultiTenancy.EntityFrameworkCore.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp71 \ No newline at end of file diff --git a/src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo/Abp/MultiTenancy/EfCoreTenantRepository.cs b/src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo/Abp/MultiTenancy/EfCoreTenantRepository.cs index 6b52af0d1d..992f0d5281 100644 --- a/src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo/Abp/MultiTenancy/EfCoreTenantRepository.cs +++ b/src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo/Abp/MultiTenancy/EfCoreTenantRepository.cs @@ -1,4 +1,8 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; @@ -12,20 +16,44 @@ namespace Volo.Abp.MultiTenancy public EfCoreTenantRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) { + } - public Task FindByNameIncludeDetailsAsync(string name) + public async Task FindByNameAsync(string name, CancellationToken cancellationToken = default) { - return DbSet - .Include(t => t.ConnectionStrings) //TODO: Why not creating a virtual Include method in EfCoreRepository and override to add included properties to be available for every query..? - .FirstOrDefaultAsync(t => t.Name == name); + return await DbSet + .FirstOrDefaultAsync(t => t.Name == name, cancellationToken); } - public Task FindWithIncludeDetailsAsync(Guid id) + public async Task FindByNameIncludeDetailsAsync(string name, CancellationToken cancellationToken = default) { - return DbSet + return await DbSet .Include(t => t.ConnectionStrings) //TODO: Why not creating a virtual Include method in EfCoreRepository and override to add included properties to be available for every query..? - .FirstOrDefaultAsync(t => t.Id == id); + .FirstOrDefaultAsync(t => t.Name == name, cancellationToken); + } + + public async Task FindWithIncludeDetailsAsync(Guid id, CancellationToken cancellationToken = default) + { + return await DbSet + .Include(t => t.ConnectionStrings) + .FirstOrDefaultAsync(t => t.Id == id, cancellationToken); + } + + public async Task GetCountAsync(CancellationToken cancellationToken = default) + { + return await DbSet.LongCountAsync(cancellationToken); + } + + public async Task> GetListAsync(string sorting, int maxResultCount, int skipCount, string filter, CancellationToken cancellationToken = default) + { + return await this.WhereIf( + !filter.IsNullOrWhiteSpace(), + u => + u.Name.Contains(filter) + ) + .OrderBy(sorting ?? nameof(Tenant.Name)) + .PageBy(skipCount, maxResultCount) + .ToListAsync(cancellationToken); } } } diff --git a/src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo/Abp/MultiTenancy/EntityFrameworkCore/MultiTenancyDbContext.cs b/src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo/Abp/MultiTenancy/EntityFrameworkCore/MultiTenancyDbContext.cs index a880920486..e2c1f90fd3 100644 --- a/src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo/Abp/MultiTenancy/EntityFrameworkCore/MultiTenancyDbContext.cs +++ b/src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo/Abp/MultiTenancy/EntityFrameworkCore/MultiTenancyDbContext.cs @@ -5,6 +5,10 @@ namespace Volo.Abp.MultiTenancy.EntityFrameworkCore { public class MultiTenancyDbContext : AbpDbContext, IMultiTenancyDbContext { + public static string TablePrefix { get; set; } = "Mt"; + + public static string Schema { get; set; } + public DbSet Tenants { get; set; } public DbSet TenantConnectionStrings { get; set; } @@ -18,7 +22,7 @@ namespace Volo.Abp.MultiTenancy.EntityFrameworkCore { base.OnModelCreating(builder); - this.ConfigureMultiTenancy(builder); + this.ConfigureMultiTenancy(builder, TablePrefix, Schema); } } } diff --git a/src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo/Abp/MultiTenancy/EntityFrameworkCore/MultiTenancyDbContextModelCreatingExtensions.cs b/src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo/Abp/MultiTenancy/EntityFrameworkCore/MultiTenancyDbContextModelCreatingExtensions.cs index 500ca5586f..eb0ebbd182 100644 --- a/src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo/Abp/MultiTenancy/EntityFrameworkCore/MultiTenancyDbContextModelCreatingExtensions.cs +++ b/src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo/Abp/MultiTenancy/EntityFrameworkCore/MultiTenancyDbContextModelCreatingExtensions.cs @@ -1,30 +1,37 @@ +using System; +using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; namespace Volo.Abp.MultiTenancy.EntityFrameworkCore { public static class MultiTenancyDbContextModelCreatingExtensions { - public static void ConfigureMultiTenancy(this IMultiTenancyDbContext dbContext, ModelBuilder builder) + public static void ConfigureMultiTenancy(this IMultiTenancyDbContext dbContext, ModelBuilder builder, string tablePrefix = "", [CanBeNull] string schema = null) { + if (tablePrefix.IsNullOrWhiteSpace()) + { + tablePrefix = ""; + } + builder.Entity(b => { - b.ToTable("MtTenants"); //TODO: Make all table and schema names changeable + b.ToTable(tablePrefix + "Tenants", schema); b.Property(t => t.Name).IsRequired().HasMaxLength(TenantConsts.MaxNameLength); b.HasMany(u => u.ConnectionStrings).WithOne().HasForeignKey(uc => uc.TenantId).IsRequired(); - b.HasIndex(u => u.Name); + b.HasIndex(u => u.Name).IsUnique(); }); builder.Entity(b => { - b.ToTable("MtTenantConnectionStrings"); + b.ToTable(tablePrefix + "TenantConnectionStrings", schema); + + b.HasKey(x => new {x.TenantId, x.Name}); b.Property(cs => cs.Name).IsRequired().HasMaxLength(TenantConnectionStringConsts.MaxNameLength); b.Property(cs => cs.Value).IsRequired().HasMaxLength(TenantConnectionStringConsts.MaxValueLength); - - b.HasIndex(cs => cs.TenantId); }); } } diff --git a/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs b/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs index 568de29522..629d979d95 100644 --- a/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs +++ b/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs @@ -39,7 +39,7 @@ namespace Volo.Abp.Http.DynamicProxying { var people = await _peopleAppService.GetListAsync(new PagedAndSortedResultRequestDto()); people.TotalCount.ShouldBeGreaterThan(0); - people.Items.Count.ShouldBe(people.TotalCount); + people.Items.Count.ShouldBe((int) people.TotalCount); } [Fact] diff --git a/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityRoleAppService_Tests.cs b/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityRoleAppService_Tests.cs index b73bb755fa..ba6b515a74 100644 --- a/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityRoleAppService_Tests.cs +++ b/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityRoleAppService_Tests.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using Xunit; using Shouldly; @@ -14,8 +13,8 @@ namespace Volo.Abp.Identity public IdentityRoleAppService_Tests() { - _roleAppService = ServiceProvider.GetRequiredService(); - _roleRepository = ServiceProvider.GetRequiredService(); + _roleAppService = GetRequiredService(); + _roleRepository = GetRequiredService(); } [Fact] diff --git a/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityUserAppService_Tests.cs b/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityUserAppService_Tests.cs index 5c4f2f4a75..e4538c3570 100644 --- a/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityUserAppService_Tests.cs +++ b/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityUserAppService_Tests.cs @@ -1,9 +1,6 @@ using System; -using System.Linq; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using Shouldly; -using Volo.Abp.Application.Dtos; using Volo.Abp.Domain.Entities; using Xunit; @@ -16,8 +13,8 @@ namespace Volo.Abp.Identity public IdentityUserAppService_Tests() { - _userAppService = ServiceProvider.GetRequiredService(); - _userRepository = ServiceProvider.GetRequiredService(); + _userAppService = GetRequiredService(); + _userRepository = GetRequiredService(); } [Fact] diff --git a/test/Volo.Abp.MultiTenancy.Application.Tests/Volo.Abp.MultiTenancy.Application.Tests.csproj b/test/Volo.Abp.MultiTenancy.Application.Tests/Volo.Abp.MultiTenancy.Application.Tests.csproj new file mode 100644 index 0000000000..a94817cc64 --- /dev/null +++ b/test/Volo.Abp.MultiTenancy.Application.Tests/Volo.Abp.MultiTenancy.Application.Tests.csproj @@ -0,0 +1,26 @@ + + + + netcoreapp2.0 + Volo.Abp.MultiTenancy.Application.Tests + Volo.Abp.MultiTenancy.Application.Tests + true + false + false + false + + + + + + + + + + + + + + + + diff --git a/test/Volo.Abp.MultiTenancy.Application.Tests/Volo/Abp/MultiTenancy/AbpIdentityApplicationTestBase.cs b/test/Volo.Abp.MultiTenancy.Application.Tests/Volo/Abp/MultiTenancy/AbpIdentityApplicationTestBase.cs new file mode 100644 index 0000000000..d0928a99f7 --- /dev/null +++ b/test/Volo.Abp.MultiTenancy.Application.Tests/Volo/Abp/MultiTenancy/AbpIdentityApplicationTestBase.cs @@ -0,0 +1,30 @@ +using System; +using Volo.Abp.MultiTenancy.EntityFrameworkCore; +using Volo.Abp.TestBase; + +namespace Volo.Abp.MultiTenancy +{ + public class AbpMultiTenancyApplicationTestBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + + protected virtual void UsingDbContext(Action action) + { + using (var dbContext = GetRequiredService()) + { + action.Invoke(dbContext); + } + } + + protected virtual T UsingDbContext(Func action) + { + using (var dbContext = GetRequiredService()) + { + return action.Invoke(dbContext); + } + } + } +} diff --git a/test/Volo.Abp.MultiTenancy.Application.Tests/Volo/Abp/MultiTenancy/AbpMultiTenancyApplicationTestModule.cs b/test/Volo.Abp.MultiTenancy.Application.Tests/Volo/Abp/MultiTenancy/AbpMultiTenancyApplicationTestModule.cs new file mode 100644 index 0000000000..de3fcf134d --- /dev/null +++ b/test/Volo.Abp.MultiTenancy.Application.Tests/Volo/Abp/MultiTenancy/AbpMultiTenancyApplicationTestModule.cs @@ -0,0 +1,55 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Autofac; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.MultiTenancy.EntityFrameworkCore; +using Volo.Abp.Modularity; +using Volo.Abp.Uow; + +namespace Volo.Abp.MultiTenancy +{ + [DependsOn( + typeof(AbpMultiTenancyApplicationModule), + typeof(AbpMultiTenancyEntityFrameworkCoreModule), + typeof(AbpAutofacModule))] + public class AbpMultiTenancyApplicationTestModule : AbpModule + { + public override void ConfigureServices(IServiceCollection services) + { + services.AddAssemblyOf(); + + services.AddEntityFrameworkInMemoryDatabase(); + + var databaseName = Guid.NewGuid().ToString(); + + services.Configure(options => + { + options.Configure(context => + { + context.DbContextOptions.UseInMemoryDatabase(databaseName); + }); + }); + + services.Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; //EF in-memory database does not support transactions + }); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + SeedTestData(context); + } + + private static void SeedTestData(ApplicationInitializationContext context) + { + using (var scope = context.ServiceProvider.CreateScope()) + { + scope.ServiceProvider + .GetRequiredService() + .Build(); + } + } + } +} diff --git a/test/Volo.Abp.MultiTenancy.Application.Tests/Volo/Abp/MultiTenancy/AbpMultiTenancyTestDataBuilder.cs b/test/Volo.Abp.MultiTenancy.Application.Tests/Volo/Abp/MultiTenancy/AbpMultiTenancyTestDataBuilder.cs new file mode 100644 index 0000000000..f55e59c855 --- /dev/null +++ b/test/Volo.Abp.MultiTenancy.Application.Tests/Volo/Abp/MultiTenancy/AbpMultiTenancyTestDataBuilder.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Threading; + +namespace Volo.Abp.MultiTenancy +{ + public class AbpMultiTenancyTestDataBuilder : ITransientDependency + { + private readonly ITenantRepository _tenantRepository; + private readonly ITenantManager _tenantManager; + + public AbpMultiTenancyTestDataBuilder( + ITenantRepository tenantRepository, + ITenantManager tenantManager) + { + _tenantRepository = tenantRepository; + _tenantManager = tenantManager; + } + + public void Build() + { + AsyncHelper.RunSync(AddTenantsAsync); + } + + private async Task AddTenantsAsync() + { + var acme = await _tenantManager.CreateAsync("acme"); + await _tenantRepository.InsertAsync(acme); + + var volosoft = await _tenantManager.CreateAsync("volosoft"); + await _tenantRepository.InsertAsync(volosoft); + } + } +} \ No newline at end of file diff --git a/test/Volo.Abp.MultiTenancy.Application.Tests/Volo/Abp/MultiTenancy/TenantAppService_Tests.cs b/test/Volo.Abp.MultiTenancy.Application.Tests/Volo/Abp/MultiTenancy/TenantAppService_Tests.cs new file mode 100644 index 0000000000..7d4726e834 --- /dev/null +++ b/test/Volo.Abp.MultiTenancy.Application.Tests/Volo/Abp/MultiTenancy/TenantAppService_Tests.cs @@ -0,0 +1,113 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Ui; +using Xunit; + +namespace Volo.Abp.MultiTenancy +{ + public class TenantAppService_Tests : AbpMultiTenancyApplicationTestBase + { + private readonly ITenantAppService _tenantAppService; + + public TenantAppService_Tests() + { + _tenantAppService = GetRequiredService(); + } + + [Fact] + public async Task GetAsync() + { + var tenantInDb = UsingDbContext(dbContext => dbContext.Tenants.First()); + var tenant = await _tenantAppService.GetAsync(tenantInDb.Id); + tenant.Name.ShouldBe(tenantInDb.Name); + } + + [Fact] + public async Task GetListAsync() + { + var result = await _tenantAppService.GetListAsync(new GetTenantsInput()); + result.TotalCount.ShouldBeGreaterThan(0); + result.Items.ShouldContain(t => t.Name == "acme"); + result.Items.ShouldContain(t => t.Name == "volosoft"); + } + + [Fact] + public async Task GetListAsync_Filtered() + { + var result = await _tenantAppService.GetListAsync(new GetTenantsInput { Filter = "volo" }); + result.TotalCount.ShouldBeGreaterThan(0); + result.Items.ShouldNotContain(t => t.Name == "acme"); + result.Items.ShouldContain(t => t.Name == "volosoft"); + } + + [Fact] + public async Task GetListAsync_Sorted_Descending_By_Name() + { + var result = await _tenantAppService.GetListAsync(new GetTenantsInput { Sorting = "Name DESC" }); + result.TotalCount.ShouldBeGreaterThan(0); + var tenants = result.Items.ToList(); + + tenants.ShouldContain(t => t.Name == "acme"); + tenants.ShouldContain(t => t.Name == "volosoft"); + + tenants.FindIndex(t => t.Name == "acme").ShouldBeGreaterThan(tenants.FindIndex(t => t.Name == "volosoft")); + } + + [Fact] + public async Task CreateAsync() + { + var tenancyName = Guid.NewGuid().ToString("N").ToLowerInvariant(); + var tenant = await _tenantAppService.CreateAsync(new TenantCreateDto { Name = tenancyName }); + tenant.Name.ShouldBe(tenancyName); + tenant.Id.ShouldNotBe(default(Guid)); + } + + [Fact] + public async Task CreateAsync_Should_Not_Allow_Duplicate_Names() + { + await Assert.ThrowsAsync(async () => + { + await _tenantAppService.CreateAsync(new TenantCreateDto { Name = "acme" }); + }); + } + + [Fact] + public async Task UpdateAsync() + { + var acme = UsingDbContext(dbContext => dbContext.Tenants.Single(t => t.Name == "acme")); + + var result = await _tenantAppService.UpdateAsync(acme.Id, new TenantUpdateDto { Name = "acme-renamed" }); + result.Id.ShouldBe(acme.Id); + result.Name.ShouldBe("acme-renamed"); + + var acmeUpdated = UsingDbContext(dbContext => dbContext.Tenants.Single(t => t.Id == acme.Id)); + acmeUpdated.Name.ShouldBe("acme-renamed"); + } + + [Fact] + public async Task UpdateAsync_Should_Not_Allow_Duplicate_Names() + { + var acme = UsingDbContext(dbContext => dbContext.Tenants.Single(t => t.Name == "acme")); + + await Assert.ThrowsAsync(async () => + { + await _tenantAppService.UpdateAsync(acme.Id, new TenantUpdateDto { Name = "volosoft" }); + }); + } + + [Fact] + public async Task DeleteAsync() + { + var acme = UsingDbContext(dbContext => dbContext.Tenants.Single(t => t.Name == "acme")); + + await _tenantAppService.DeleteAsync(acme.Id); + + UsingDbContext(dbContext => + { + dbContext.Tenants.Any(t => t.Id == acme.Id).ShouldBeFalse(); + }); + } + } +}