diff --git a/modules/tenantmanagement/README.md b/modules/tenantmanagement/README.md
new file mode 100644
index 0000000000..060f0d2136
--- /dev/null
+++ b/modules/tenantmanagement/README.md
@@ -0,0 +1,2 @@
+# abp-tenantmanagement
+Tenant management module for ABP framework.
diff --git a/modules/tenantmanagement/Volo.Abp.TenantManagement.sln b/modules/tenantmanagement/Volo.Abp.TenantManagement.sln
new file mode 100644
index 0000000000..20fd21a531
--- /dev/null
+++ b/modules/tenantmanagement/Volo.Abp.TenantManagement.sln
@@ -0,0 +1,116 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27428.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{799CA525-4748-421A-9892-05C68BB2FA13}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C6941869-A9FC-4BEA-AD3F-C1E104826ECA}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.TenantManagement.Domain.Shared", "src\Volo.Abp.TenantManagement.Domain.Shared\Volo.Abp.TenantManagement.Domain.Shared.csproj", "{44BBBCA1-2E6A-419E-87E6-A04468E887D1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.TenantManagement.Domain", "src\Volo.Abp.TenantManagement.Domain\Volo.Abp.TenantManagement.Domain.csproj", "{FBFB1FD6-10B5-4416-BFD1-21A62754AD33}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.TenantManagement.Application.Contracts", "src\Volo.Abp.TenantManagement.Application.Contracts\Volo.Abp.TenantManagement.Application.Contracts.csproj", "{0C93BFF5-8B86-4CE7-86D8-893B0C44192F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.TenantManagement.Application", "src\Volo.Abp.TenantManagement.Application\Volo.Abp.TenantManagement.Application.csproj", "{57E7A9DA-4149-4CE4-B9DC-75A064A9648B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.TenantManagement.EntityFrameworkCore", "src\Volo.Abp.TenantManagement.EntityFrameworkCore\Volo.Abp.TenantManagement.EntityFrameworkCore.csproj", "{1BCA75A2-10DB-4200-A006-7C003767F0D9}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.TenantManagement.HttpApi", "src\Volo.Abp.TenantManagement.HttpApi\Volo.Abp.TenantManagement.HttpApi.csproj", "{996C1AB4-1F0A-4D6A-BB30-21245C7112D0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.TenantManagement.HttpApi.Client", "src\Volo.Abp.TenantManagement.HttpApi.Client\Volo.Abp.TenantManagement.HttpApi.Client.csproj", "{3B042AC5-29F1-4037-BF17-4025092FCB7A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.TenantManagement.Web", "src\Volo.Abp.TenantManagement.Web\Volo.Abp.TenantManagement.Web.csproj", "{2AE28C0A-1B8A-4D78-A9A1-7F2271A6F2D2}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.TenantManagement.Application.Tests", "test\Volo.Abp.TenantManagement.Application.Tests\Volo.Abp.TenantManagement.Application.Tests.csproj", "{72445B2D-07FA-4A35-A3D6-FF4ACE299BF4}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.TenantManagement.EntityFrameworkCore.Tests", "test\Volo.Abp.TenantManagement.EntityFrameworkCore.Tests\Volo.Abp.TenantManagement.EntityFrameworkCore.Tests.csproj", "{A2BB8897-EBDB-46BB-B885-F8635B21F376}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.TenantManagement.MongoDB", "src\Volo.Abp.TenantManagement.MongoDB\Volo.Abp.TenantManagement.MongoDB.csproj", "{ED95242E-3C31-4A89-9C62-93B306EFEB15}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.TenantManagement.MongoDB.Tests", "test\Volo.Abp.TenantManagement.MongoDB.Tests\Volo.Abp.TenantManagement.MongoDB.Tests.csproj", "{F75B4C54-A5F1-4101-99F5-A5B868A5146B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.TenantManagement.TestBase", "test\Volo.Abp.TenantManagement.TestBase\Volo.Abp.TenantManagement.TestBase.csproj", "{C3BAD6E8-00CD-4283-9416-64287BB5B265}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {44BBBCA1-2E6A-419E-87E6-A04468E887D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {44BBBCA1-2E6A-419E-87E6-A04468E887D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {44BBBCA1-2E6A-419E-87E6-A04468E887D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {44BBBCA1-2E6A-419E-87E6-A04468E887D1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FBFB1FD6-10B5-4416-BFD1-21A62754AD33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FBFB1FD6-10B5-4416-BFD1-21A62754AD33}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FBFB1FD6-10B5-4416-BFD1-21A62754AD33}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FBFB1FD6-10B5-4416-BFD1-21A62754AD33}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0C93BFF5-8B86-4CE7-86D8-893B0C44192F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0C93BFF5-8B86-4CE7-86D8-893B0C44192F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0C93BFF5-8B86-4CE7-86D8-893B0C44192F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0C93BFF5-8B86-4CE7-86D8-893B0C44192F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {57E7A9DA-4149-4CE4-B9DC-75A064A9648B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {57E7A9DA-4149-4CE4-B9DC-75A064A9648B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {57E7A9DA-4149-4CE4-B9DC-75A064A9648B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {57E7A9DA-4149-4CE4-B9DC-75A064A9648B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1BCA75A2-10DB-4200-A006-7C003767F0D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1BCA75A2-10DB-4200-A006-7C003767F0D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1BCA75A2-10DB-4200-A006-7C003767F0D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1BCA75A2-10DB-4200-A006-7C003767F0D9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {996C1AB4-1F0A-4D6A-BB30-21245C7112D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {996C1AB4-1F0A-4D6A-BB30-21245C7112D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {996C1AB4-1F0A-4D6A-BB30-21245C7112D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {996C1AB4-1F0A-4D6A-BB30-21245C7112D0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3B042AC5-29F1-4037-BF17-4025092FCB7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3B042AC5-29F1-4037-BF17-4025092FCB7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3B042AC5-29F1-4037-BF17-4025092FCB7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3B042AC5-29F1-4037-BF17-4025092FCB7A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2AE28C0A-1B8A-4D78-A9A1-7F2271A6F2D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2AE28C0A-1B8A-4D78-A9A1-7F2271A6F2D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2AE28C0A-1B8A-4D78-A9A1-7F2271A6F2D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2AE28C0A-1B8A-4D78-A9A1-7F2271A6F2D2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {72445B2D-07FA-4A35-A3D6-FF4ACE299BF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {72445B2D-07FA-4A35-A3D6-FF4ACE299BF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {72445B2D-07FA-4A35-A3D6-FF4ACE299BF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {72445B2D-07FA-4A35-A3D6-FF4ACE299BF4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A2BB8897-EBDB-46BB-B885-F8635B21F376}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A2BB8897-EBDB-46BB-B885-F8635B21F376}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A2BB8897-EBDB-46BB-B885-F8635B21F376}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A2BB8897-EBDB-46BB-B885-F8635B21F376}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ED95242E-3C31-4A89-9C62-93B306EFEB15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ED95242E-3C31-4A89-9C62-93B306EFEB15}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ED95242E-3C31-4A89-9C62-93B306EFEB15}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ED95242E-3C31-4A89-9C62-93B306EFEB15}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F75B4C54-A5F1-4101-99F5-A5B868A5146B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F75B4C54-A5F1-4101-99F5-A5B868A5146B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F75B4C54-A5F1-4101-99F5-A5B868A5146B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F75B4C54-A5F1-4101-99F5-A5B868A5146B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C3BAD6E8-00CD-4283-9416-64287BB5B265}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C3BAD6E8-00CD-4283-9416-64287BB5B265}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C3BAD6E8-00CD-4283-9416-64287BB5B265}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C3BAD6E8-00CD-4283-9416-64287BB5B265}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {44BBBCA1-2E6A-419E-87E6-A04468E887D1} = {799CA525-4748-421A-9892-05C68BB2FA13}
+ {FBFB1FD6-10B5-4416-BFD1-21A62754AD33} = {799CA525-4748-421A-9892-05C68BB2FA13}
+ {0C93BFF5-8B86-4CE7-86D8-893B0C44192F} = {799CA525-4748-421A-9892-05C68BB2FA13}
+ {57E7A9DA-4149-4CE4-B9DC-75A064A9648B} = {799CA525-4748-421A-9892-05C68BB2FA13}
+ {1BCA75A2-10DB-4200-A006-7C003767F0D9} = {799CA525-4748-421A-9892-05C68BB2FA13}
+ {996C1AB4-1F0A-4D6A-BB30-21245C7112D0} = {799CA525-4748-421A-9892-05C68BB2FA13}
+ {3B042AC5-29F1-4037-BF17-4025092FCB7A} = {799CA525-4748-421A-9892-05C68BB2FA13}
+ {2AE28C0A-1B8A-4D78-A9A1-7F2271A6F2D2} = {799CA525-4748-421A-9892-05C68BB2FA13}
+ {72445B2D-07FA-4A35-A3D6-FF4ACE299BF4} = {C6941869-A9FC-4BEA-AD3F-C1E104826ECA}
+ {A2BB8897-EBDB-46BB-B885-F8635B21F376} = {C6941869-A9FC-4BEA-AD3F-C1E104826ECA}
+ {ED95242E-3C31-4A89-9C62-93B306EFEB15} = {799CA525-4748-421A-9892-05C68BB2FA13}
+ {F75B4C54-A5F1-4101-99F5-A5B868A5146B} = {C6941869-A9FC-4BEA-AD3F-C1E104826ECA}
+ {C3BAD6E8-00CD-4283-9416-64287BB5B265} = {C6941869-A9FC-4BEA-AD3F-C1E104826ECA}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {7C258726-2CE0-44D3-A2D7-71812E8F505C}
+ EndGlobalSection
+EndGlobal
diff --git a/modules/tenantmanagement/common.props b/modules/tenantmanagement/common.props
new file mode 100644
index 0000000000..f00a3fc2cb
--- /dev/null
+++ b/modules/tenantmanagement/common.props
@@ -0,0 +1,16 @@
+
+
+ latest
+ 0.3.0
+ $(NoWarn);CS1591
+ http://www.aspnetboilerplate.com/images/abp_nupkg.png
+ http://abp.io
+ git
+ https://github.com/volosoft/abp/
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo.Abp.TenantManagement.Application.Contracts.csproj b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo.Abp.TenantManagement.Application.Contracts.csproj
new file mode 100644
index 0000000000..b2e6b38a56
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo.Abp.TenantManagement.Application.Contracts.csproj
@@ -0,0 +1,26 @@
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.TenantManagement.Application.Contracts
+ Volo.Abp.TenantManagement.Application.Contracts
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/AbpTenantManagementApplicationContractsModule.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/AbpTenantManagementApplicationContractsModule.cs
new file mode 100644
index 0000000000..a049d96f92
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/AbpTenantManagementApplicationContractsModule.cs
@@ -0,0 +1,37 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Application;
+using Volo.Abp.Authorization.Permissions;
+using Volo.Abp.Localization;
+using Volo.Abp.Modularity;
+using Volo.Abp.TenantManagement.Localization;
+using Volo.Abp.VirtualFileSystem;
+
+namespace Volo.Abp.TenantManagement
+{
+ [DependsOn(typeof(AbpDddApplicationModule))]
+ [DependsOn(typeof(AbpTenantManagementDomainSharedModule))]
+ public class AbpTenantManagementApplicationContractsModule : AbpModule
+ {
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ services.Configure(options =>
+ {
+ options.DefinitionProviders.Add();
+ });
+
+ services.Configure(options =>
+ {
+ options.FileSets.AddEmbedded();
+ });
+
+ services.Configure(options =>
+ {
+ options.Resources
+ .Get()
+ .AddVirtualJson("/Volo/Abp/TenantManagement/Localization/ApplicationContracts");
+ });
+
+ services.AddAssemblyOf();
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/AbpTenantManagementPermissionDefinitionProvider.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/AbpTenantManagementPermissionDefinitionProvider.cs
new file mode 100644
index 0000000000..822de19ad8
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/AbpTenantManagementPermissionDefinitionProvider.cs
@@ -0,0 +1,24 @@
+using Volo.Abp.Authorization.Permissions;
+using Volo.Abp.Localization;
+using Volo.Abp.TenantManagement.Localization;
+
+namespace Volo.Abp.TenantManagement
+{
+ public class AbpTenantManagementPermissionDefinitionProvider : PermissionDefinitionProvider
+ {
+ public override void Define(IPermissionDefinitionContext context)
+ {
+ var identityGroup = context.AddGroup(TenantManagementPermissions.GroupName, L("Permission:TenantManagement"));
+
+ var rolesPermission = identityGroup.AddPermission(TenantManagementPermissions.Tenants.Default, L("Permission:TenantManagement"));
+ rolesPermission.AddChild(TenantManagementPermissions.Tenants.Create, L("Permission:Create"));
+ rolesPermission.AddChild(TenantManagementPermissions.Tenants.Update, L("Permission:Edit"));
+ rolesPermission.AddChild(TenantManagementPermissions.Tenants.Delete, L("Permission:Delete"));
+ }
+
+ private static LocalizableString L(string name)
+ {
+ return LocalizableString.Create(name);
+ }
+ }
+}
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/GetTenantsInput.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/GetTenantsInput.cs
new file mode 100644
index 0000000000..b7bdc16e6c
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/GetTenantsInput.cs
@@ -0,0 +1,9 @@
+using Volo.Abp.Application.Dtos;
+
+namespace Volo.Abp.TenantManagement
+{
+ public class GetTenantsInput : PagedAndSortedResultRequestDto
+ {
+ public string Filter { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/ITenantAppService.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/ITenantAppService.cs
new file mode 100644
index 0000000000..3f6df9b6fe
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/ITenantAppService.cs
@@ -0,0 +1,10 @@
+using System;
+using Volo.Abp.Application.Services;
+
+namespace Volo.Abp.TenantManagement
+{
+ public interface ITenantAppService : IAsyncCrudAppService
+ {
+ //TODO: Manage connection strings
+ }
+}
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/Localization/ApplicationContracts/en.json b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/Localization/ApplicationContracts/en.json
new file mode 100644
index 0000000000..92a555e5a7
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/Localization/ApplicationContracts/en.json
@@ -0,0 +1,9 @@
+{
+ "culture": "en",
+ "texts": {
+ "Permission:TenantManagement": "Tenant management",
+ "Permission:Create": "Create",
+ "Permission:Edit": "Edit",
+ "Permission:Delete": "Delete"
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/Localization/ApplicationContracts/tr.json b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/Localization/ApplicationContracts/tr.json
new file mode 100644
index 0000000000..a192048e78
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/Localization/ApplicationContracts/tr.json
@@ -0,0 +1,9 @@
+{
+ "culture": "tr",
+ "texts": {
+ "Permission:TenantManagement": "Müşteri yönetimi",
+ "Permission:Create": "Oluşturma",
+ "Permission:Edit": "Düzenleme",
+ "Permission:Delete": "Silme"
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateDto.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateDto.cs
new file mode 100644
index 0000000000..7561835ee3
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateDto.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.TenantManagement
+{
+ public class TenantCreateDto : TenantCreateOrUpdateDtoBase
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs
new file mode 100644
index 0000000000..a5a9b4af3d
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.TenantManagement
+{
+ public abstract class TenantCreateOrUpdateDtoBase
+ {
+ public string Name { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantDto.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantDto.cs
new file mode 100644
index 0000000000..a410e90da5
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantDto.cs
@@ -0,0 +1,10 @@
+using System;
+using Volo.Abp.Application.Dtos;
+
+namespace Volo.Abp.TenantManagement
+{
+ public class TenantDto : EntityDto
+ {
+ public string Name { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantManagementPermissions.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantManagementPermissions.cs
new file mode 100644
index 0000000000..bc30e65e1f
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantManagementPermissions.cs
@@ -0,0 +1,15 @@
+namespace Volo.Abp.TenantManagement
+{
+ public static class TenantManagementPermissions
+ {
+ public const string GroupName = "AbpTenantManagement";
+
+ public static class Tenants
+ {
+ public const string Default = GroupName + ".Tenants";
+ public const string Create = Default + ".Create";
+ public const string Update = Default + ".Update";
+ public const string Delete = Default + ".Delete";
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantUpdateDto.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantUpdateDto.cs
new file mode 100644
index 0000000000..8bbe7adebb
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantUpdateDto.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.TenantManagement
+{
+ public class TenantUpdateDto : TenantCreateOrUpdateDtoBase
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application/Volo.Abp.TenantManagement.Application.csproj b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application/Volo.Abp.TenantManagement.Application.csproj
new file mode 100644
index 0000000000..8a53a6b892
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application/Volo.Abp.TenantManagement.Application.csproj
@@ -0,0 +1,23 @@
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.TenantManagement.Application
+ Volo.Abp.TenantManagement.Application
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/AbpTenantManagementApplicationAutoMapperProfile.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/AbpTenantManagementApplicationAutoMapperProfile.cs
new file mode 100644
index 0000000000..61a2994dd7
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/AbpTenantManagementApplicationAutoMapperProfile.cs
@@ -0,0 +1,12 @@
+using AutoMapper;
+
+namespace Volo.Abp.TenantManagement
+{
+ public class AbpTenantManagementApplicationAutoMapperProfile : Profile
+ {
+ public AbpTenantManagementApplicationAutoMapperProfile()
+ {
+ CreateMap();
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/AbpTenantManagementApplicationModule.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/AbpTenantManagementApplicationModule.cs
new file mode 100644
index 0000000000..2cf0213119
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/AbpTenantManagementApplicationModule.cs
@@ -0,0 +1,21 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.AutoMapper;
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.TenantManagement
+{
+ [DependsOn(typeof(AbpTenantManagementDomainModule))]
+ [DependsOn(typeof(AbpTenantManagementApplicationContractsModule))]
+ public class AbpTenantManagementApplicationModule : 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/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/TenantAppService.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/TenantAppService.cs
new file mode 100644
index 0000000000..8578d29090
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/TenantAppService.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Volo.Abp.Application.Dtos;
+
+namespace Volo.Abp.TenantManagement
+{
+ [Authorize(TenantManagementPermissions.Tenants.Default)]
+ public class TenantAppService : TenantManagementAppServiceBase, 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)
+ );
+ }
+
+ [Authorize(TenantManagementPermissions.Tenants.Create)]
+ public async Task CreateAsync(TenantCreateDto input)
+ {
+ var tenant = await _tenantManager.CreateAsync(input.Name);
+ await _tenantRepository.InsertAsync(tenant);
+ return ObjectMapper.Map(tenant);
+ }
+
+ [Authorize(TenantManagementPermissions.Tenants.Update)]
+ 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);
+ }
+
+ [Authorize(TenantManagementPermissions.Tenants.Delete)]
+ public async Task DeleteAsync(Guid id)
+ {
+ var tenant = await _tenantRepository.FindAsync(id);
+ if (tenant == null)
+ {
+ return;
+ }
+
+ await _tenantRepository.DeleteAsync(tenant);
+ }
+ }
+}
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/TenantManagementAppServiceBase.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/TenantManagementAppServiceBase.cs
new file mode 100644
index 0000000000..bd68ec9f39
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/TenantManagementAppServiceBase.cs
@@ -0,0 +1,9 @@
+using Volo.Abp.Application.Services;
+
+namespace Volo.Abp.TenantManagement
+{
+ public class TenantManagementAppServiceBase : ApplicationService
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain.Shared/Volo.Abp.TenantManagement.Domain.Shared.csproj b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain.Shared/Volo.Abp.TenantManagement.Domain.Shared.csproj
new file mode 100644
index 0000000000..2c5c210d8c
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain.Shared/Volo.Abp.TenantManagement.Domain.Shared.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.TenantManagement.Domain.Shared
+ Volo.Abp.TenantManagement.Domain.Shared
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/AbpTenantManagementDomainSharedModule.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/AbpTenantManagementDomainSharedModule.cs
new file mode 100644
index 0000000000..3ac78b5030
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/AbpTenantManagementDomainSharedModule.cs
@@ -0,0 +1,20 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Localization;
+using Volo.Abp.Modularity;
+using Volo.Abp.TenantManagement.Localization;
+
+namespace Volo.Abp.TenantManagement
+{
+ public class AbpTenantManagementDomainSharedModule : AbpModule
+ {
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ services.Configure(options =>
+ {
+ options.Resources.Add("en");
+ });
+
+ services.AddAssemblyOf();
+ }
+ }
+}
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/AbpTenantManagementResource.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/AbpTenantManagementResource.cs
new file mode 100644
index 0000000000..ef94d769b8
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/AbpTenantManagementResource.cs
@@ -0,0 +1,10 @@
+using Volo.Abp.Localization;
+
+namespace Volo.Abp.TenantManagement.Localization
+{
+ [LocalizationResourceName("AbpTenantManagement")]
+ public class AbpTenantManagementResource
+ {
+
+ }
+}
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/TenantConnectionStringConsts.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/TenantConnectionStringConsts.cs
new file mode 100644
index 0000000000..526d12b7d6
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/TenantConnectionStringConsts.cs
@@ -0,0 +1,9 @@
+namespace Volo.Abp.TenantManagement
+{
+ public static class TenantConnectionStringConsts
+ {
+ public const int MaxNameLength = 128;
+
+ public const int MaxValueLength = 1024;
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/TenantConsts.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/TenantConsts.cs
new file mode 100644
index 0000000000..328c422ab9
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/TenantConsts.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.TenantManagement
+{
+ public static class TenantConsts
+ {
+ public const int MaxNameLength = 64;
+ }
+}
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo.Abp.TenantManagement.Domain.csproj b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo.Abp.TenantManagement.Domain.csproj
new file mode 100644
index 0000000000..b3050edfa4
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo.Abp.TenantManagement.Domain.csproj
@@ -0,0 +1,26 @@
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.TenantManagement.Domain
+ Volo.Abp.TenantManagement.Domain
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo.Abp.TenantManagement.Domain.csproj.DotSettings b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo.Abp.TenantManagement.Domain.csproj.DotSettings
new file mode 100644
index 0000000000..58ad6c8854
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo.Abp.TenantManagement.Domain.csproj.DotSettings
@@ -0,0 +1,2 @@
+
+ CSharp71
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementConsts.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementConsts.cs
new file mode 100644
index 0000000000..6f87227627
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementConsts.cs
@@ -0,0 +1,9 @@
+namespace Volo.Abp.TenantManagement
+{
+ public static class AbpTenantManagementConsts
+ {
+ public const string DefaultDbTablePrefix = "Abp";
+
+ public const string DefaultDbSchema = null;
+ }
+}
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainMappingProfile.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainMappingProfile.cs
new file mode 100644
index 0000000000..ac928efda6
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainMappingProfile.cs
@@ -0,0 +1,28 @@
+using AutoMapper;
+using Volo.Abp.Data;
+using Volo.Abp.MultiTenancy;
+
+namespace Volo.Abp.TenantManagement
+{
+ public class AbpTenantManagementDomainMappingProfile : Profile
+ {
+ public AbpTenantManagementDomainMappingProfile()
+ {
+ CreateMap()
+ .ForMember(ti => ti.ConnectionStrings, opts =>
+ {
+ opts.ResolveUsing(tenant =>
+ {
+ var connStrings = new ConnectionStrings();
+
+ foreach (var connectionString in tenant.ConnectionStrings)
+ {
+ connStrings[connectionString.Name] = connectionString.Value;
+ }
+
+ return connStrings;
+ });
+ });
+ }
+ }
+}
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs
new file mode 100644
index 0000000000..209fa47c79
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs
@@ -0,0 +1,29 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.AutoMapper;
+using Volo.Abp.Data;
+using Volo.Abp.Domain;
+using Volo.Abp.Modularity;
+using Volo.Abp.MultiTenancy;
+using Volo.Abp.UI;
+
+namespace Volo.Abp.TenantManagement
+{
+ [DependsOn(typeof(AbpMultiTenancyAbstractionsModule))]
+ [DependsOn(typeof(AbpTenantManagementDomainSharedModule))]
+ [DependsOn(typeof(AbpDataModule))]
+ [DependsOn(typeof(AbpDddDomainModule))]
+ [DependsOn(typeof(AbpAutoMapperModule))]
+ [DependsOn(typeof(AbpUiModule))] //TODO: It's not good to depend on the UI module. However, UserFriendlyException is inside it!
+ public class AbpTenantManagementDomainModule : AbpModule
+ {
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ services.Configure(options =>
+ {
+ options.AddProfile(validate: true);
+ });
+
+ services.AddAssemblyOf();
+ }
+ }
+}
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantManager.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantManager.cs
new file mode 100644
index 0000000000..5ab5443425
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantManager.cs
@@ -0,0 +1,14 @@
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using Volo.Abp.Domain.Services;
+
+namespace Volo.Abp.TenantManagement
+{
+ public interface ITenantManager : IDomainService
+ {
+ [NotNull]
+ Task CreateAsync([NotNull] string name);
+
+ Task ChangeNameAsync([NotNull] Tenant tenant, [NotNull] string name);
+ }
+}
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantRepository.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantRepository.cs
new file mode 100644
index 0000000000..f7d430c214
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/ITenantRepository.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.Domain.Repositories;
+
+namespace Volo.Abp.TenantManagement
+{
+ public interface ITenantRepository : IBasicRepository
+ {
+ Task FindByNameAsync(
+ string name,
+ bool includeDetails = true,
+ CancellationToken cancellationToken = default);
+
+ Task> GetListAsync(
+ string sorting = null,
+ int maxResultCount = int.MaxValue,
+ int skipCount = 0,
+ string filter = null,
+ bool includeDetails = false,
+ CancellationToken cancellationToken = default);
+
+ Task GetCountAsync(CancellationToken cancellationToken = default);
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/Tenant.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/Tenant.cs
new file mode 100644
index 0000000000..7c70746816
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/Tenant.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using JetBrains.Annotations;
+using Volo.Abp.Data;
+using Volo.Abp.Domain.Entities;
+
+namespace Volo.Abp.TenantManagement
+{
+ public class Tenant : AggregateRoot, IHasExtraProperties
+ {
+ public virtual string Name { get; protected set; }
+
+ public virtual List ConnectionStrings { get; protected set; }
+
+ public Dictionary ExtraProperties { get; }
+
+ protected Tenant()
+ {
+ ExtraProperties = new Dictionary();
+ }
+
+ protected internal Tenant(Guid id, [NotNull] string name)
+ {
+ Check.NotNull(name, nameof(name));
+
+ Id = id;
+ Name = name;
+
+ ConnectionStrings = new List();
+ ExtraProperties = new Dictionary();
+ }
+
+ [CanBeNull]
+ public virtual string FindDefaultConnectionString()
+ {
+ return FindConnectionString(Data.ConnectionStrings.DefaultConnectionStringName);
+ }
+
+ [CanBeNull]
+ public virtual string FindConnectionString(string name)
+ {
+ 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/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantConnectionString.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantConnectionString.cs
new file mode 100644
index 0000000000..b555a0eea7
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantConnectionString.cs
@@ -0,0 +1,30 @@
+using System;
+using JetBrains.Annotations;
+using Volo.Abp.Domain.Entities;
+
+namespace Volo.Abp.TenantManagement
+{
+ public class TenantConnectionString : Entity
+ {
+ public virtual Guid TenantId { get; protected set; }
+
+ public virtual string Name { get; protected set; }
+
+ public virtual string Value { get; protected set; }
+
+ protected TenantConnectionString()
+ {
+
+ }
+
+ public TenantConnectionString(Guid tenantId, [NotNull] string name, [NotNull] string value)
+ {
+ Check.NotNull(name, nameof(name));
+ Check.NotNull(value, nameof(value));
+
+ TenantId = tenantId;
+ Name = name;
+ Value = value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs
new file mode 100644
index 0000000000..b410632502
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantManager.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.Domain.Services;
+using Volo.Abp.UI;
+
+namespace Volo.Abp.TenantManagement
+{
+ 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/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantStore.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantStore.cs
new file mode 100644
index 0000000000..cd15ebe1fe
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantStore.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.MultiTenancy;
+using Volo.Abp.ObjectMapping;
+
+namespace Volo.Abp.TenantManagement
+{
+ //TODO: This class should use caching instead of querying everytime!
+
+ public class TenantStore : ITenantStore, ITransientDependency
+ {
+ private readonly ITenantRepository _tenantRepository;
+ private readonly IObjectMapper _objectMapper;
+ private readonly ICurrentTenant _currentTenant;
+
+ public TenantStore(
+ ITenantRepository tenantRepository,
+ IObjectMapper objectMapper,
+ ICurrentTenant currentTenant)
+ {
+ _tenantRepository = tenantRepository;
+ _objectMapper = objectMapper;
+ _currentTenant = currentTenant;
+ }
+
+ public async Task FindAsync(string name)
+ {
+ using (_currentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
+ {
+ var tenant = await _tenantRepository.FindByNameAsync(name);
+ if (tenant == null)
+ {
+ return null;
+ }
+
+ return _objectMapper.Map(tenant);
+ }
+ }
+
+ public async Task FindAsync(Guid id)
+ {
+ using (_currentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
+ {
+ var tenant = await _tenantRepository.FindAsync(id);
+ if (tenant == null)
+ {
+ return null;
+ }
+
+ return _objectMapper.Map(tenant);
+ }
+ }
+ }
+}
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo.Abp.TenantManagement.EntityFrameworkCore.csproj b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo.Abp.TenantManagement.EntityFrameworkCore.csproj
new file mode 100644
index 0000000000..35a3be7607
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo.Abp.TenantManagement.EntityFrameworkCore.csproj
@@ -0,0 +1,22 @@
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.TenantManagement.EntityFrameworkCore
+ Volo.Abp.TenantManagement.EntityFrameworkCore
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo.Abp.TenantManagement.EntityFrameworkCore.csproj.DotSettings b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo.Abp.TenantManagement.EntityFrameworkCore.csproj.DotSettings
new file mode 100644
index 0000000000..58ad6c8854
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo.Abp.TenantManagement.EntityFrameworkCore.csproj.DotSettings
@@ -0,0 +1,2 @@
+
+ CSharp71
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/AbpTenantManagementDbContextModelCreatingExtensions.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/AbpTenantManagementDbContextModelCreatingExtensions.cs
new file mode 100644
index 0000000000..636692cf56
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/AbpTenantManagementDbContextModelCreatingExtensions.cs
@@ -0,0 +1,42 @@
+using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore;
+
+namespace Volo.Abp.TenantManagement.EntityFrameworkCore
+{
+ public static class AbpTenantManagementDbContextModelCreatingExtensions
+ {
+ public static void ConfigureTenantManagement(
+ this ModelBuilder builder,
+ [CanBeNull] string tablePrefix = AbpTenantManagementConsts.DefaultDbTablePrefix,
+ [CanBeNull] string schema = AbpTenantManagementConsts.DefaultDbSchema)
+ {
+ Check.NotNull(builder, nameof(builder));
+
+ if (tablePrefix == null)
+ {
+ tablePrefix = "";
+ }
+
+ builder.Entity(b =>
+ {
+ 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).IsUnique();
+ });
+
+ builder.Entity(b =>
+ {
+ 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);
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/AbpTenantManagementEntityFrameworkCoreModule.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/AbpTenantManagementEntityFrameworkCoreModule.cs
new file mode 100644
index 0000000000..809b1e805e
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/AbpTenantManagementEntityFrameworkCoreModule.cs
@@ -0,0 +1,21 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.EntityFrameworkCore;
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.TenantManagement.EntityFrameworkCore
+{
+ [DependsOn(typeof(AbpTenantManagementDomainModule))]
+ [DependsOn(typeof(AbpEntityFrameworkCoreModule))]
+ public class AbpTenantManagementEntityFrameworkCoreModule : AbpModule
+ {
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ services.AddAbpDbContext(options =>
+ {
+ options.AddDefaultRepositories();
+ });
+
+ services.AddAssemblyOf();
+ }
+ }
+}
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/EfCoreTenantRepository.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/EfCoreTenantRepository.cs
new file mode 100644
index 0000000000..0a13f77bf0
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/EfCoreTenantRepository.cs
@@ -0,0 +1,61 @@
+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;
+using Volo.Abp.EntityFrameworkCore;
+
+namespace Volo.Abp.TenantManagement.EntityFrameworkCore
+{
+ public class EfCoreTenantRepository : EfCoreRepository, ITenantRepository
+ {
+ public EfCoreTenantRepository(IDbContextProvider dbContextProvider)
+ : base(dbContextProvider)
+ {
+
+ }
+
+ public virtual async Task FindByNameAsync(
+ string name,
+ bool includeDetails = true,
+ CancellationToken cancellationToken = default)
+ {
+ return await DbSet
+ .IncludeDetails(includeDetails)
+ .FirstOrDefaultAsync(t => t.Name == name, cancellationToken);
+ }
+
+ public virtual async Task> GetListAsync(
+ string sorting = null,
+ int maxResultCount = int.MaxValue,
+ int skipCount = 0,
+ string filter = null,
+ bool includeDetails = false,
+ CancellationToken cancellationToken = default)
+ {
+ return await DbSet
+ .IncludeDetails(includeDetails)
+ .WhereIf(
+ !filter.IsNullOrWhiteSpace(),
+ u =>
+ u.Name.Contains(filter)
+ )
+ .OrderBy(sorting ?? nameof(Tenant.Name))
+ .PageBy(skipCount, maxResultCount)
+ .ToListAsync(cancellationToken);
+ }
+
+ public virtual async Task GetCountAsync(CancellationToken cancellationToken = default)
+ {
+ return await DbSet.LongCountAsync(cancellationToken);
+ }
+
+ public override IQueryable WithDetails()
+ {
+ return GetQueryable().IncludeDetails();
+ }
+ }
+}
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/ITenantManagementDbContext.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/ITenantManagementDbContext.cs
new file mode 100644
index 0000000000..fcd5174d0f
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/ITenantManagementDbContext.cs
@@ -0,0 +1,14 @@
+using Microsoft.EntityFrameworkCore;
+using Volo.Abp.Data;
+using Volo.Abp.EntityFrameworkCore;
+
+namespace Volo.Abp.TenantManagement.EntityFrameworkCore
+{
+ [ConnectionStringName("AbpTenantManagement")]
+ public interface ITenantManagementDbContext : IEfCoreDbContext
+ {
+ DbSet Tenants { get; set; }
+
+ DbSet TenantConnectionStrings { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/TenantManagementDbContext.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/TenantManagementDbContext.cs
new file mode 100644
index 0000000000..382b6c1ab2
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/TenantManagementDbContext.cs
@@ -0,0 +1,30 @@
+using Microsoft.EntityFrameworkCore;
+using Volo.Abp.Data;
+using Volo.Abp.EntityFrameworkCore;
+
+namespace Volo.Abp.TenantManagement.EntityFrameworkCore
+{
+ [ConnectionStringName("AbpTenantManagement")]
+ public class TenantManagementDbContext : AbpDbContext, ITenantManagementDbContext
+ {
+ public static string TablePrefix { get; set; } = AbpTenantManagementConsts.DefaultDbTablePrefix;
+
+ public static string Schema { get; set; } = AbpTenantManagementConsts.DefaultDbSchema;
+
+ public DbSet Tenants { get; set; }
+
+ public DbSet TenantConnectionStrings { get; set; }
+
+ public TenantManagementDbContext(DbContextOptions options)
+ : base(options)
+ {
+ }
+
+ protected override void OnModelCreating(ModelBuilder builder)
+ {
+ base.OnModelCreating(builder);
+
+ builder.ConfigureTenantManagement(TablePrefix, Schema);
+ }
+ }
+}
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/TenantManagementEfCoreQueryableExtensions.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/TenantManagementEfCoreQueryableExtensions.cs
new file mode 100644
index 0000000000..3d40e2312c
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/TenantManagementEfCoreQueryableExtensions.cs
@@ -0,0 +1,19 @@
+using System.Linq;
+using Microsoft.EntityFrameworkCore;
+
+namespace Volo.Abp.TenantManagement.EntityFrameworkCore
+{
+ public static class TenantManagementEfCoreQueryableExtensions
+ {
+ public static IQueryable IncludeDetails(this IQueryable queryable, bool include = true)
+ {
+ if (!include)
+ {
+ return queryable;
+ }
+
+ return queryable
+ .Include(x => x.ConnectionStrings);
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.HttpApi.Client/Volo.Abp.TenantManagement.HttpApi.Client.csproj b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.HttpApi.Client/Volo.Abp.TenantManagement.HttpApi.Client.csproj
new file mode 100644
index 0000000000..bd268f6740
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.HttpApi.Client/Volo.Abp.TenantManagement.HttpApi.Client.csproj
@@ -0,0 +1,22 @@
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.TenantManagement.HttpApi.Client
+ Volo.Abp.TenantManagement.HttpApi.Client
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.HttpApi.Client/Volo/Abp/TenantManagement/AbpTenantManagementHttpApiClientModule.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.HttpApi.Client/Volo/Abp/TenantManagement/AbpTenantManagementHttpApiClientModule.cs
new file mode 100644
index 0000000000..fec7399b3a
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.HttpApi.Client/Volo/Abp/TenantManagement/AbpTenantManagementHttpApiClientModule.cs
@@ -0,0 +1,24 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Http.Client;
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.TenantManagement
+{
+ [DependsOn(
+ typeof(AbpTenantManagementApplicationContractsModule),
+ typeof(AbpHttpClientModule))]
+ public class AbpTenantManagementHttpApiClientModule : AbpModule
+ {
+ public const string RemoteServiceName = "AbpTenantManagement";
+
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ services.AddHttpClientProxies(
+ typeof(AbpTenantManagementApplicationContractsModule).Assembly,
+ RemoteServiceName
+ );
+
+ services.AddAssemblyOf();
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.HttpApi/Volo.Abp.TenantManagement.HttpApi.csproj b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.HttpApi/Volo.Abp.TenantManagement.HttpApi.csproj
new file mode 100644
index 0000000000..b50780edf0
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.HttpApi/Volo.Abp.TenantManagement.HttpApi.csproj
@@ -0,0 +1,22 @@
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.TenantManagement.HttpApi
+ Volo.Abp.TenantManagement.HttpApi
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/AbpTenantManagementHttpApiModule.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/AbpTenantManagementHttpApiModule.cs
new file mode 100644
index 0000000000..5e967e93f8
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/AbpTenantManagementHttpApiModule.cs
@@ -0,0 +1,18 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.AspNetCore.Mvc;
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.TenantManagement
+{
+ [DependsOn(
+ typeof(AbpTenantManagementApplicationContractsModule),
+ typeof(AbpAspNetCoreMvcModule)
+ )]
+ public class AbpTenantManagementHttpApiModule : AbpModule
+ {
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ services.AddAssemblyOf();
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/TenantController.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/TenantController.cs
new file mode 100644
index 0000000000..5091e4132a
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/TenantController.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.Application.Services;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.TenantManagement
+{
+ [Controller]
+ [RemoteService]
+ [Area("multi-tenancy")]
+ public class TenantController : ITenantAppService, ITransientDependency //TODO: Throws exception on validation if we inherit from Controller
+ {
+ private readonly ITenantAppService _service;
+
+ public TenantController(ITenantAppService service)
+ {
+ _service = service;
+ }
+
+ public virtual Task GetAsync(Guid id)
+ {
+ return _service.GetAsync(id);
+ }
+
+ public virtual Task> GetListAsync(GetTenantsInput input)
+ {
+ return _service.GetListAsync(input);
+ }
+
+ public virtual Task CreateAsync(TenantCreateDto input)
+ {
+ return _service.CreateAsync(input);
+ }
+
+ public virtual Task UpdateAsync(Guid id, TenantUpdateDto input)
+ {
+ return _service.UpdateAsync(id, input);
+ }
+
+ public virtual Task DeleteAsync(Guid id)
+ {
+ return _service.DeleteAsync(id);
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo.Abp.TenantManagement.MongoDB.csproj b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo.Abp.TenantManagement.MongoDB.csproj
new file mode 100644
index 0000000000..f4de9566b2
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo.Abp.TenantManagement.MongoDB.csproj
@@ -0,0 +1,22 @@
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.TenantManagement.MongoDB
+ Volo.Abp.TenantManagement.MongoDB
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo.Abp.TenantManagement.MongoDB.csproj.DotSettings b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo.Abp.TenantManagement.MongoDB.csproj.DotSettings
new file mode 100644
index 0000000000..58ad6c8854
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo.Abp.TenantManagement.MongoDB.csproj.DotSettings
@@ -0,0 +1,2 @@
+
+ CSharp71
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementBsonClassMap.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementBsonClassMap.cs
new file mode 100644
index 0000000000..f5a693bb65
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementBsonClassMap.cs
@@ -0,0 +1,23 @@
+using MongoDB.Bson.Serialization;
+using Volo.Abp.MongoDB;
+using Volo.Abp.Threading;
+
+namespace Volo.Abp.TenantManagement.MongoDb
+{
+ public static class AbpTenantManagementBsonClassMap
+ {
+ private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
+
+ public static void Configure()
+ {
+ OneTimeRunner.Run(() =>
+ {
+ BsonClassMap.RegisterClassMap(map =>
+ {
+ map.AutoMap();
+ map.ConfigureExtraProperties();
+ });
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementMongoDbContextExtensions.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementMongoDbContextExtensions.cs
new file mode 100644
index 0000000000..3b2a8f2a2a
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementMongoDbContextExtensions.cs
@@ -0,0 +1,24 @@
+using System;
+using Volo.Abp.MongoDB;
+
+namespace Volo.Abp.TenantManagement.MongoDb
+{
+ public static class AbpTenantManagementMongoDbContextExtensions
+ {
+ public static void ConfigureTenantManagement(
+ this IMongoModelBuilder builder,
+ Action optionsAction = null)
+ {
+ Check.NotNull(builder, nameof(builder));
+
+ var options = new TenantManagementMongoModelBuilderConfigurationOptions();
+
+ optionsAction?.Invoke(options);
+
+ builder.Entity(b =>
+ {
+ b.CollectionName = options.CollectionPrefix + "Tenants";
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementMongoDbModule.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementMongoDbModule.cs
new file mode 100644
index 0000000000..b34d77d0f6
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementMongoDbModule.cs
@@ -0,0 +1,27 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Modularity;
+using Volo.Abp.MongoDB;
+
+namespace Volo.Abp.TenantManagement.MongoDb
+{
+ [DependsOn(
+ typeof(AbpTenantManagementDomainModule),
+ typeof(AbpMongoDbModule)
+ )]
+ public class AbpTenantManagementMongoDbModule : AbpModule
+ {
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ AbpTenantManagementBsonClassMap.Configure();
+
+ services.AddMongoDbContext(options =>
+ {
+ options.AddDefaultRepositories();
+
+ options.AddRepository();
+ });
+
+ services.AddAssemblyOf();
+ }
+ }
+}
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/ITenantManagementMongoDbContext.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/ITenantManagementMongoDbContext.cs
new file mode 100644
index 0000000000..0ee4336a47
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/ITenantManagementMongoDbContext.cs
@@ -0,0 +1,12 @@
+using MongoDB.Driver;
+using Volo.Abp.Data;
+using Volo.Abp.MongoDB;
+
+namespace Volo.Abp.TenantManagement.MongoDb
+{
+ [ConnectionStringName("AbpTenantManagement")]
+ public interface ITenantManagementMongoDbContext : IAbpMongoDbContext
+ {
+ IMongoCollection Tenants { get; }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/MongoTenantRepository.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/MongoTenantRepository.cs
new file mode 100644
index 0000000000..05aa92757a
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/MongoTenantRepository.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using MongoDB.Driver.Linq;
+using System.Linq;
+using System.Linq.Dynamic.Core;
+using MongoDB.Driver;
+using Volo.Abp.Domain.Repositories.MongoDB;
+using Volo.Abp.MongoDB;
+
+namespace Volo.Abp.TenantManagement.MongoDb
+{
+ public class MongoTenantRepository : MongoDbRepository, ITenantRepository
+ {
+ public MongoTenantRepository(IMongoDbContextProvider dbContextProvider)
+ : base(dbContextProvider)
+ {
+
+ }
+
+ public virtual async Task FindByNameAsync(
+ string name,
+ bool includeDetails = true,
+ CancellationToken cancellationToken = default)
+ {
+ return await GetMongoQueryable()
+ .FirstOrDefaultAsync(t => t.Name == name, cancellationToken);
+ }
+
+ public virtual async Task GetCountAsync(CancellationToken cancellationToken = default)
+ {
+ return await GetMongoQueryable().LongCountAsync(cancellationToken);
+ }
+
+ public virtual async Task> GetListAsync(
+ string sorting = null,
+ int maxResultCount = int.MaxValue,
+ int skipCount = 0,
+ string filter = null,
+ bool includeDetails = false,
+ CancellationToken cancellationToken = default)
+ {
+ return await GetMongoQueryable()
+ .WhereIf>(
+ !filter.IsNullOrWhiteSpace(),
+ u =>
+ u.Name.Contains(filter)
+ )
+ .OrderBy(sorting ?? nameof(Tenant.Name))
+ .As>()
+ .PageBy>(skipCount, maxResultCount)
+ .ToListAsync(cancellationToken);
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/TenantManagementMongoDbContext.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/TenantManagementMongoDbContext.cs
new file mode 100644
index 0000000000..4dc913c8d2
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/TenantManagementMongoDbContext.cs
@@ -0,0 +1,24 @@
+using MongoDB.Driver;
+using Volo.Abp.Data;
+using Volo.Abp.MongoDB;
+
+namespace Volo.Abp.TenantManagement.MongoDb
+{
+ [ConnectionStringName("AbpTenantManagement")]
+ public class TenantManagementMongoDbContext : AbpMongoDbContext, ITenantManagementMongoDbContext
+ {
+ public static string CollectionPrefix { get; set; } = AbpTenantManagementConsts.DefaultDbTablePrefix;
+
+ public IMongoCollection Tenants => Collection();
+
+ protected override void CreateModel(IMongoModelBuilder modelBuilder)
+ {
+ base.CreateModel(modelBuilder);
+
+ modelBuilder.ConfigureTenantManagement(options =>
+ {
+ options.CollectionPrefix = CollectionPrefix;
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/TenantManagementMongoModelBuilderConfigurationOptions.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/TenantManagementMongoModelBuilderConfigurationOptions.cs
new file mode 100644
index 0000000000..646a58bfb0
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/TenantManagementMongoModelBuilderConfigurationOptions.cs
@@ -0,0 +1,13 @@
+using JetBrains.Annotations;
+using Volo.Abp.MongoDB;
+
+namespace Volo.Abp.TenantManagement.MongoDb
+{
+ public class TenantManagementMongoModelBuilderConfigurationOptions : MongoModelBuilderConfigurationOptions
+ {
+ public TenantManagementMongoModelBuilderConfigurationOptions([NotNull] string tablePrefix = AbpTenantManagementConsts.DefaultDbTablePrefix)
+ : base(tablePrefix)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebAutoMapperProfile.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebAutoMapperProfile.cs
new file mode 100644
index 0000000000..a37ed86dd0
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebAutoMapperProfile.cs
@@ -0,0 +1,25 @@
+using AutoMapper;
+using Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants;
+
+namespace Volo.Abp.TenantManagement.Web
+{
+ public class AbpTenantManagementWebAutoMapperProfile : Profile
+ {
+ public AbpTenantManagementWebAutoMapperProfile()
+ {
+ CreateRoleMappings();
+ }
+
+ private void CreateRoleMappings()
+ {
+ //List
+ CreateMap();
+
+ //CreateModal
+ CreateMap();
+
+ //EditModal
+ CreateMap();
+ }
+ }
+}
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebModule.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebModule.cs
new file mode 100644
index 0000000000..d5913f4bf0
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebModule.cs
@@ -0,0 +1,67 @@
+using Localization.Resources.AbpUi;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.AspNetCore.Mvc.Localization;
+using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap;
+using Volo.Abp.AutoMapper;
+using Volo.Abp.Localization;
+using Volo.Abp.Localization.Resources.AbpValidation;
+using Volo.Abp.Modularity;
+using Volo.Abp.TenantManagement.Localization;
+using Volo.Abp.TenantManagement.Web.Navigation;
+using Volo.Abp.UI.Navigation;
+using Volo.Abp.VirtualFileSystem;
+
+namespace Volo.Abp.TenantManagement.Web
+{
+ [DependsOn(typeof(AbpTenantManagementHttpApiModule))]
+ [DependsOn(typeof(AbpAspNetCoreMvcUiBootstrapModule))]
+ [DependsOn(typeof(AbpAutoMapperModule))]
+ public class AbpTenantManagementWebModule : AbpModule
+ {
+ public override void PreConfigureServices(IServiceCollection services)
+ {
+ services.PreConfigure(options =>
+ {
+ options.AddAssemblyResource(typeof(AbpTenantManagementResource), typeof(AbpTenantManagementWebModule).Assembly);
+ });
+ }
+
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ services.Configure(options =>
+ {
+ options.MenuContributors.Add(new AbpTenantManagementWebMainMenuContributor());
+ });
+
+ services.Configure(options =>
+ {
+ options.FileSets.AddEmbedded("Volo.Abp.TenantManagement.Web");
+ });
+
+ services.Configure(options =>
+ {
+ options.Resources
+ .Get()
+ .AddBaseTypes(
+ typeof(AbpValidationResource),
+ typeof(AbpUiResource)
+ ).AddVirtualJson("/Localization/Resources/AbpTenantManagement/Web");
+ });
+
+ services.Configure(options =>
+ {
+ options.AddProfile(validate: true);
+ });
+
+ services.Configure(options =>
+ {
+ options.Conventions.AuthorizePage("/TenantManagement/Tenants/Index", TenantManagementPermissions.Tenants.Default);
+ options.Conventions.AuthorizePage("/TenantManagement/Tenants/CreateModal", TenantManagementPermissions.Tenants.Create);
+ options.Conventions.AuthorizePage("/TenantManagement/Tenants/EditModal", TenantManagementPermissions.Tenants.Update);
+ });
+
+ services.AddAssemblyOf();
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Localization/Resources/AbpTenantManagement/Web/en.json b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Localization/Resources/AbpTenantManagement/Web/en.json
new file mode 100644
index 0000000000..c6eab9bf5c
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Localization/Resources/AbpTenantManagement/Web/en.json
@@ -0,0 +1,10 @@
+{
+ "culture": "en",
+ "texts": {
+ "Menu:TenantManagement": "Tenant management",
+ "Tenants": "Tenants",
+ "NewTenant": "New tenant",
+ "TenantName": "Tenant name",
+ "TenantDeletionConfirmationMessage": "Tenant '{0}' will be deleted. Do you confirm that?"
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Localization/Resources/AbpTenantManagement/Web/tr.json b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Localization/Resources/AbpTenantManagement/Web/tr.json
new file mode 100644
index 0000000000..8aaafaa7b6
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Localization/Resources/AbpTenantManagement/Web/tr.json
@@ -0,0 +1,10 @@
+{
+ "culture": "tr",
+ "texts": {
+ "Menu:TenantManagement": "Müşteri yönetimi",
+ "Tenants": "Müşteriler",
+ "NewTenant": "Yeni müşteri",
+ "TenantName": "Müşteri adı",
+ "TenantDeletionConfirmationMessage": "'{0}' isimli müşteri silinecektir. Onaylıyor musunuz?"
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Navigation/AbpIdentityWebMainMenuContributor.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Navigation/AbpIdentityWebMainMenuContributor.cs
new file mode 100644
index 0000000000..ff8dc7f1a6
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Navigation/AbpIdentityWebMainMenuContributor.cs
@@ -0,0 +1,31 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Localization;
+using Volo.Abp.TenantManagement.Localization;
+using Volo.Abp.UI.Navigation;
+
+namespace Volo.Abp.TenantManagement.Web.Navigation
+{
+ public class AbpTenantManagementWebMainMenuContributor : IMenuContributor
+ {
+ public async Task ConfigureMenuAsync(MenuConfigurationContext context)
+ {
+ if (context.Menu.Name != StandardMenus.Main)
+ {
+ return;
+ }
+
+ var authorizationService = context.ServiceProvider.GetRequiredService();
+ var l = context.ServiceProvider.GetRequiredService>();
+
+ var tenantManagementMenuItem = new ApplicationMenuItem("TenantManagement", l["Menu:TenantManagement"]);
+ context.Menu.AddItem(tenantManagementMenuItem);
+
+ if (await authorizationService.IsGrantedAsync(TenantManagementPermissions.Tenants.Default))
+ {
+ tenantManagementMenuItem.AddItem(new ApplicationMenuItem("Tenants", l["Tenants"], url: "/TenantManagement/Tenants"));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml
new file mode 100644
index 0000000000..ecef85ac8c
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml
@@ -0,0 +1,18 @@
+@page
+@using Microsoft.Extensions.Localization
+@using Volo.Abp.TenantManagement.Localization
+@using Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants
+@model CreateModalModel
+@inject IStringLocalizer L
+@{
+ Layout = null;
+}
+
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs
new file mode 100644
index 0000000000..a4239d6739
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs
@@ -0,0 +1,38 @@
+using System.ComponentModel.DataAnnotations;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
+
+namespace Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants
+{
+ public class CreateModalModel : AbpPageModel
+ {
+ [BindProperty]
+ public TenantInfoModel Tenant { get; set; }
+
+ private readonly ITenantAppService _tenantAppService;
+
+ public CreateModalModel(ITenantAppService tenantAppService)
+ {
+ _tenantAppService = tenantAppService;
+ }
+
+ public async Task OnPostAsync()
+ {
+ ValidateModel();
+
+ var input = ObjectMapper.Map(Tenant);
+ await _tenantAppService.CreateAsync(input);
+
+ return NoContent();
+ }
+
+ public class TenantInfoModel
+ {
+ [Required]
+ [StringLength(TenantConsts.MaxNameLength)]
+ [Display(Name = "TenantName")]
+ public string Name { get; set; }
+ }
+ }
+}
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/EditModal.cshtml b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/EditModal.cshtml
new file mode 100644
index 0000000000..beee12208d
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/EditModal.cshtml
@@ -0,0 +1,19 @@
+@page
+@using Microsoft.AspNetCore.Mvc.Localization
+@using Volo.Abp.TenantManagement.Localization
+@using Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants
+@model EditModalModel
+@inject IHtmlLocalizer L
+@{
+ Layout = null;
+}
+
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/EditModal.cshtml.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/EditModal.cshtml.cs
new file mode 100644
index 0000000000..1c805b75b0
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/EditModal.cshtml.cs
@@ -0,0 +1,49 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
+
+namespace Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants
+{
+ public class EditModalModel : AbpPageModel
+ {
+ [BindProperty]
+ public TenantInfoModel Tenant { get; set; }
+
+ private readonly ITenantAppService _tenantAppService;
+
+ public EditModalModel(ITenantAppService tenantAppService)
+ {
+ _tenantAppService = tenantAppService;
+ }
+
+ public async Task OnGetAsync(Guid id)
+ {
+ Tenant = ObjectMapper.Map(
+ await _tenantAppService.GetAsync(id)
+ );
+ }
+
+ public async Task OnPostAsync()
+ {
+ ValidateModel();
+
+ var input = ObjectMapper.Map(Tenant);
+ await _tenantAppService.UpdateAsync(Tenant.Id, input);
+
+ return NoContent();
+ }
+
+ public class TenantInfoModel
+ {
+ [HiddenInput]
+ public Guid Id { get; set; }
+
+ [Required]
+ [StringLength(TenantConsts.MaxNameLength)]
+ [Display(Name = "TenantName")]
+ public string Name { get; set; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.cshtml b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.cshtml
new file mode 100644
index 0000000000..ec7b51e291
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.cshtml
@@ -0,0 +1,34 @@
+@page
+@using Microsoft.AspNetCore.Mvc.Localization
+@using Volo.Abp.TenantManagement.Localization
+@using Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants
+@model IndexModel
+@inject IHtmlLocalizer L
+@section styles {
+
+}
+@section scripts {
+
+}
+
+
+
+
+ @L["Tenants"]
+
+
+
+
+
+
+
+
+
+
+ | @L["Actions"] |
+ @L["TenantName"] |
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.cshtml.cs b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.cshtml.cs
new file mode 100644
index 0000000000..c297f3d010
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.cshtml.cs
@@ -0,0 +1,12 @@
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants
+{
+ public class IndexModel : PageModel
+ {
+ public void OnGet()
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/_ViewImports.cshtml b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/_ViewImports.cshtml
new file mode 100644
index 0000000000..d1ac64721f
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/_ViewImports.cshtml
@@ -0,0 +1,2 @@
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
+@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Properties/launchSettings.json b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Properties/launchSettings.json
new file mode 100644
index 0000000000..dab1d2b972
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Properties/launchSettings.json
@@ -0,0 +1,27 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:55673/",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "Volo.Abp.TenantManagement.Web": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:55676/"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Volo.Abp.TenantManagement.Web.csproj b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Volo.Abp.TenantManagement.Web.csproj
new file mode 100644
index 0000000000..ee74333b31
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/Volo.Abp.TenantManagement.Web.csproj
@@ -0,0 +1,27 @@
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.TenantManagement.Web
+ Volo.Abp.TenantManagement.Web
+ true
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ Library
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/compilerconfig.json b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/compilerconfig.json
new file mode 100644
index 0000000000..3104320327
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/compilerconfig.json
@@ -0,0 +1,6 @@
+[
+ {
+ "outputFile": "wwwroot/modules/multi-tenancy/views/tenants/index.css",
+ "inputFile": "wwwroot/modules/multi-tenancy/views/tenants/index.less"
+ }
+]
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/compilerconfig.json.defaults b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/compilerconfig.json.defaults
new file mode 100644
index 0000000000..c75eb7d519
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/compilerconfig.json.defaults
@@ -0,0 +1,49 @@
+{
+ "compilers": {
+ "less": {
+ "autoPrefix": "",
+ "cssComb": "none",
+ "ieCompat": true,
+ "strictMath": false,
+ "strictUnits": false,
+ "relativeUrls": true,
+ "rootPath": "",
+ "sourceMapRoot": "",
+ "sourceMapBasePath": "",
+ "sourceMap": false
+ },
+ "sass": {
+ "includePath": "",
+ "indentType": "space",
+ "indentWidth": 2,
+ "outputStyle": "nested",
+ "Precision": 5,
+ "relativeUrls": true,
+ "sourceMapRoot": "",
+ "sourceMap": false
+ },
+ "stylus": {
+ "sourceMap": false
+ },
+ "babel": {
+ "sourceMap": false
+ },
+ "coffeescript": {
+ "bare": false,
+ "runtimeMode": "node",
+ "sourceMap": false
+ }
+ },
+ "minifiers": {
+ "css": {
+ "enabled": true,
+ "termSemicolons": true,
+ "gzip": false
+ },
+ "javascript": {
+ "enabled": true,
+ "termSemicolons": true,
+ "gzip": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/wwwroot/modules/multi-tenancy/views/tenants/index.css b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/wwwroot/modules/multi-tenancy/views/tenants/index.css
new file mode 100644
index 0000000000..41fbd4eaf0
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/wwwroot/modules/multi-tenancy/views/tenants/index.css
@@ -0,0 +1,21 @@
+.dataTable {
+ width: 100% !important;
+ border-spacing: 0 !important;
+}
+.table td,
+.table th {
+ padding: 8px 10px;
+}
+.dataTable tbody tr td button {
+ cursor: pointer;
+}
+.dataTable tbody tr td div.dropdown ul.dropdown-menu li {
+ cursor: pointer;
+ padding: 5px;
+}
+.dataTable tbody tr td div.dropdown ul.dropdown-menu li a {
+ display: block;
+}
+.dataTable tbody tr td div.dropdown ul.dropdown-menu li:hover {
+ background: #f4f5f8;
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/wwwroot/modules/multi-tenancy/views/tenants/index.es5.js b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/wwwroot/modules/multi-tenancy/views/tenants/index.es5.js
new file mode 100644
index 0000000000..bc6107abbc
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/wwwroot/modules/multi-tenancy/views/tenants/index.es5.js
@@ -0,0 +1,6 @@
+'use strict';
+
+$(function () {
+ $('#IdentityUsersTable').DataTable();
+});
+
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/wwwroot/modules/multi-tenancy/views/tenants/index.es5.min.js b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/wwwroot/modules/multi-tenancy/views/tenants/index.es5.min.js
new file mode 100644
index 0000000000..9a2c19267e
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/wwwroot/modules/multi-tenancy/views/tenants/index.es5.min.js
@@ -0,0 +1 @@
+"use strict";$(function(){$("#IdentityUsersTable").DataTable()});
\ No newline at end of file
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/wwwroot/modules/multi-tenancy/views/tenants/index.js b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/wwwroot/modules/multi-tenancy/views/tenants/index.js
new file mode 100644
index 0000000000..5e0628879c
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/wwwroot/modules/multi-tenancy/views/tenants/index.js
@@ -0,0 +1,79 @@
+(function () {
+
+ var l = abp.localization.getResource('AbpTenantManagement');
+ var _tenantAppService = volo.abp.tenantManagement.tenant;
+
+ var _editModal = new abp.ModalManager(abp.appPath + 'TenantManagement/Tenants/EditModal');
+ var _createModal = new abp.ModalManager(abp.appPath + 'TenantManagement/Tenants/CreateModal');
+
+ $(function () {
+
+ var _$wrapper = $('#TenantsWrapper');
+ var _$table = _$wrapper.find('table');
+
+ var _dataTable = _$table.DataTable({
+ order: [[1, "asc"]],
+ ajax: abp.libs.datatables.createAjax(_tenantAppService.getList),
+ columnDefs: [
+ {
+ //TODO: Can we eleminate targets, data, orderable, autoWidth, defaultContent fields or make these values default
+ targets: 0,
+ data: null,
+ orderable: false,
+ autoWidth: false,
+ defaultContent: '',
+ rowAction: {
+ text: ' ' + l('Actions') + ' ', //TODO: Add icon option and set text as only l('Actions')
+ items:
+ [
+ {
+ //TODO: Allow to add icon
+ text: l('Edit'),
+ visible: function () { //TODO: Allow visible to be a boolean for simple cases
+ return true;
+ },
+ action: function (data) {
+ _editModal.open({
+ id: data.record.id
+ });
+ }
+ },
+ {
+ text: l('Delete'),
+ visible: function () {
+ return true;
+ },
+ confirmMessage: function (data) { return l('TenantDeletionConfirmationMessage', data.record.name)},
+ action: function (data) {
+ _tenantAppService
+ .delete(data.record.id)
+ .then(function () {
+ _dataTable.ajax.reload();
+ });
+ }
+ }
+ ]
+ }
+ },
+ {
+ targets: 1,
+ data: "name"
+ }
+ ]
+ });
+
+ _createModal.onResult(function () {
+ _dataTable.ajax.reload();
+ });
+
+ _editModal.onResult(function () {
+ _dataTable.ajax.reload();
+ });
+
+ _$wrapper.find('button[name=CreateTenant]').click(function (e) {
+ e.preventDefault();
+ _createModal.open();
+ });
+ });
+
+})();
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/wwwroot/modules/multi-tenancy/views/tenants/index.less b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/wwwroot/modules/multi-tenancy/views/tenants/index.less
new file mode 100644
index 0000000000..21adf56097
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/wwwroot/modules/multi-tenancy/views/tenants/index.less
@@ -0,0 +1,39 @@
+//TODO: This code is duplicated for other pages too. Unify them.
+.dataTable {
+ width: 100% !important;
+ border-spacing: 0 !important;
+}
+
+.table td, .table th {
+ padding: 8px 10px;
+}
+
+.dataTable {
+ tbody {
+ tr {
+ td {
+
+ button {
+ cursor: pointer;
+ }
+
+ div.dropdown {
+ ul.dropdown-menu {
+ li {
+ cursor: pointer;
+ padding: 5px;
+
+ a {
+ display: block;
+ }
+ }
+
+ li:hover {
+ background: #f4f5f8;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/wwwroot/modules/multi-tenancy/views/tenants/index.min.css b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/wwwroot/modules/multi-tenancy/views/tenants/index.min.css
new file mode 100644
index 0000000000..e70b9e330b
--- /dev/null
+++ b/modules/tenantmanagement/src/Volo.Abp.TenantManagement.Web/wwwroot/modules/multi-tenancy/views/tenants/index.min.css
@@ -0,0 +1 @@
+.dataTable{width:100% !important;border-spacing:0 !important;}.table td,.table th{padding:8px 10px;}.dataTable tbody tr td button{cursor:pointer;}.dataTable tbody tr td div.dropdown ul.dropdown-menu li{cursor:pointer;padding:5px;}.dataTable tbody tr td div.dropdown ul.dropdown-menu li a{display:block;}.dataTable tbody tr td div.dropdown ul.dropdown-menu li:hover{background:#f4f5f8;}
\ No newline at end of file
diff --git a/modules/tenantmanagement/test/Volo.Abp.TenantManagement.Application.Tests/Volo.Abp.TenantManagement.Application.Tests.csproj b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.Application.Tests/Volo.Abp.TenantManagement.Application.Tests.csproj
new file mode 100644
index 0000000000..bb806e268d
--- /dev/null
+++ b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.Application.Tests/Volo.Abp.TenantManagement.Application.Tests.csproj
@@ -0,0 +1,23 @@
+
+
+
+ netcoreapp2.0
+ Volo.Abp.TenantManagement.Application.Tests
+ Volo.Abp.TenantManagement.Application.Tests
+ true
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/tenantmanagement/test/Volo.Abp.TenantManagement.Application.Tests/Volo/Abp/TenantManagement/AbpTenantManagementApplicationTestBase.cs b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.Application.Tests/Volo/Abp/TenantManagement/AbpTenantManagementApplicationTestBase.cs
new file mode 100644
index 0000000000..29e88032ff
--- /dev/null
+++ b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.Application.Tests/Volo/Abp/TenantManagement/AbpTenantManagementApplicationTestBase.cs
@@ -0,0 +1,24 @@
+using System;
+using Volo.Abp.TenantManagement.EntityFrameworkCore;
+
+namespace Volo.Abp.TenantManagement
+{
+ public abstract class AbpTenantManagementApplicationTestBase : TenantManagementTestBase
+ {
+ 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/modules/tenantmanagement/test/Volo.Abp.TenantManagement.Application.Tests/Volo/Abp/TenantManagement/AbpTenantManagementApplicationTestModule.cs b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.Application.Tests/Volo/Abp/TenantManagement/AbpTenantManagementApplicationTestModule.cs
new file mode 100644
index 0000000000..c8a9d0978d
--- /dev/null
+++ b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.Application.Tests/Volo/Abp/TenantManagement/AbpTenantManagementApplicationTestModule.cs
@@ -0,0 +1,19 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Modularity;
+using Volo.Abp.TenantManagement.EntityFrameworkCore;
+
+namespace Volo.Abp.TenantManagement
+{
+ [DependsOn(
+ typeof(AbpTenantManagementApplicationModule),
+ typeof(AbpTenantManagementEntityFrameworkCoreTestModule))]
+ public class AbpTenantManagementApplicationTestModule : AbpModule
+ {
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ services.AddAlwaysAllowPermissionChecker();
+
+ services.AddAssemblyOf();
+ }
+ }
+}
diff --git a/modules/tenantmanagement/test/Volo.Abp.TenantManagement.Application.Tests/Volo/Abp/TenantManagement/TenantAppService_Tests.cs b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.Application.Tests/Volo/Abp/TenantManagement/TenantAppService_Tests.cs
new file mode 100644
index 0000000000..3e44e7c8ea
--- /dev/null
+++ b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.Application.Tests/Volo/Abp/TenantManagement/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.TenantManagement
+{
+ public class TenantAppService_Tests : AbpTenantManagementApplicationTestBase
+ {
+ 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();
+ });
+ }
+ }
+}
diff --git a/modules/tenantmanagement/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests.csproj b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests.csproj
new file mode 100644
index 0000000000..e03ea985d8
--- /dev/null
+++ b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests.csproj
@@ -0,0 +1,25 @@
+
+
+
+ netcoreapp2.0
+ Volo.Abp.TenantManagement.EntityFrameworkCore.Tests
+ Volo.Abp.TenantManagement.EntityFrameworkCore.Tests
+ true
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/tenantmanagement/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo/Abp/TenantManagement/EntityFrameworkCore/AbpTenantManagementEntityFrameworkCoreTestModule.cs b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo/Abp/TenantManagement/EntityFrameworkCore/AbpTenantManagementEntityFrameworkCoreTestModule.cs
new file mode 100644
index 0000000000..bcaad7dd9d
--- /dev/null
+++ b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo/Abp/TenantManagement/EntityFrameworkCore/AbpTenantManagementEntityFrameworkCoreTestModule.cs
@@ -0,0 +1,38 @@
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.EntityFrameworkCore;
+using Volo.Abp.Modularity;
+using Volo.Abp.Uow;
+
+namespace Volo.Abp.TenantManagement.EntityFrameworkCore
+{
+ [DependsOn(
+ typeof(AbpTenantManagementEntityFrameworkCoreModule),
+ typeof(AbpTenantManagementTestBaseModule)
+ )]
+ public class AbpTenantManagementEntityFrameworkCoreTestModule : AbpModule
+ {
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ 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
+ });
+
+ services.AddAssemblyOf();
+ }
+ }
+}
diff --git a/modules/tenantmanagement/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo/Abp/TenantManagement/EntityFrameworkCore/LazyLoad_Tests.cs b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo/Abp/TenantManagement/EntityFrameworkCore/LazyLoad_Tests.cs
new file mode 100644
index 0000000000..c1b1ac3776
--- /dev/null
+++ b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo/Abp/TenantManagement/EntityFrameworkCore/LazyLoad_Tests.cs
@@ -0,0 +1,20 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.EntityFrameworkCore;
+
+namespace Volo.Abp.TenantManagement.EntityFrameworkCore
+{
+ public class LazyLoad_Tests : LazyLoad_Tests
+ {
+ protected override void BeforeAddApplication(IServiceCollection services)
+ {
+ services.Configure(options =>
+ {
+ options.PreConfigure(context =>
+ {
+ context.DbContextOptions.UseLazyLoadingProxies();
+ });
+ });
+ }
+ }
+}
diff --git a/modules/tenantmanagement/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo/Abp/TenantManagement/EntityFrameworkCore/TenantRepository_Tests.cs b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo/Abp/TenantManagement/EntityFrameworkCore/TenantRepository_Tests.cs
new file mode 100644
index 0000000000..7023ad78f0
--- /dev/null
+++ b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo/Abp/TenantManagement/EntityFrameworkCore/TenantRepository_Tests.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.TenantManagement.EntityFrameworkCore
+{
+ public class TenantRepository_Tests : TenantRepository_Tests
+ {
+
+ }
+}
diff --git a/modules/tenantmanagement/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo.Abp.TenantManagement.MongoDB.Tests.csproj b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo.Abp.TenantManagement.MongoDB.Tests.csproj
new file mode 100644
index 0000000000..21d7382eb9
--- /dev/null
+++ b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo.Abp.TenantManagement.MongoDB.Tests.csproj
@@ -0,0 +1,24 @@
+
+
+
+ netcoreapp2.0
+ Volo.Abp.TenantManagement.MongoDB.Tests
+ Volo.Abp.TenantManagement.MongoDB.Tests
+ true
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/tenantmanagement/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementMongoDbTestModule.cs b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementMongoDbTestModule.cs
new file mode 100644
index 0000000000..f1534bb254
--- /dev/null
+++ b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementMongoDbTestModule.cs
@@ -0,0 +1,33 @@
+using Microsoft.Extensions.DependencyInjection;
+using Mongo2Go;
+using Volo.Abp.Data;
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.TenantManagement.MongoDb
+{
+ [DependsOn(
+ typeof(AbpTenantManagementMongoDbModule),
+ typeof(AbpTenantManagementTestBaseModule)
+ )]
+ public class AbpTenantManagementMongoDbTestModule : AbpModule
+ {
+ private MongoDbRunner _mongoDbRunner;
+
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ _mongoDbRunner = MongoDbRunner.Start();
+
+ services.Configure(options =>
+ {
+ options.ConnectionStrings.Default = _mongoDbRunner.ConnectionString;
+ });
+
+ services.AddAssemblyOf();
+ }
+
+ public override void OnApplicationShutdown(ApplicationShutdownContext context)
+ {
+ _mongoDbRunner.Dispose();
+ }
+ }
+}
diff --git a/modules/tenantmanagement/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/TenantRepository_Tests.cs b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/TenantRepository_Tests.cs
new file mode 100644
index 0000000000..7b42e3bd32
--- /dev/null
+++ b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/TenantRepository_Tests.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.TenantManagement.MongoDb
+{
+ public class TenantRepository_Tests : TenantRepository_Tests
+ {
+
+ }
+}
diff --git a/modules/tenantmanagement/test/Volo.Abp.TenantManagement.TestBase/Volo.Abp.TenantManagement.TestBase.csproj b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.TestBase/Volo.Abp.TenantManagement.TestBase.csproj
new file mode 100644
index 0000000000..546abc9661
--- /dev/null
+++ b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.TestBase/Volo.Abp.TenantManagement.TestBase.csproj
@@ -0,0 +1,29 @@
+
+
+
+ netcoreapp2.0
+ Volo.Abp.TenantManagement.TestBase
+ Volo.Abp.TenantManagement.TestBase
+ true
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/tenantmanagement/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/AbpTenantManagementTestBaseModule.cs b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/AbpTenantManagementTestBaseModule.cs
new file mode 100644
index 0000000000..b2275b9d10
--- /dev/null
+++ b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/AbpTenantManagementTestBaseModule.cs
@@ -0,0 +1,34 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Autofac;
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.TenantManagement
+{
+ [DependsOn(
+ typeof(AbpTenantManagementDomainModule),
+ typeof(AbpAutofacModule),
+ typeof(AbpTestBaseModule)
+ )]
+ public class AbpTenantManagementTestBaseModule : AbpModule
+ {
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ services.AddAssemblyOf();
+ }
+
+ 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/modules/tenantmanagement/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/AbpTenantManagementTestDataBuilder.cs b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/AbpTenantManagementTestDataBuilder.cs
new file mode 100644
index 0000000000..45acc070d8
--- /dev/null
+++ b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/AbpTenantManagementTestDataBuilder.cs
@@ -0,0 +1,37 @@
+using System.Threading.Tasks;
+using Volo.Abp.Data;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Threading;
+
+namespace Volo.Abp.TenantManagement
+{
+ public class AbpTenantManagementTestDataBuilder : ITransientDependency
+ {
+ private readonly ITenantRepository _tenantRepository;
+ private readonly ITenantManager _tenantManager;
+
+ public AbpTenantManagementTestDataBuilder(
+ ITenantRepository tenantRepository,
+ ITenantManager tenantManager)
+ {
+ _tenantRepository = tenantRepository;
+ _tenantManager = tenantManager;
+ }
+
+ public void Build()
+ {
+ AsyncHelper.RunSync(AddTenantsAsync);
+ }
+
+ private async Task AddTenantsAsync()
+ {
+ var acme = await _tenantManager.CreateAsync("acme");
+ acme.ConnectionStrings.Add(new TenantConnectionString(acme.Id, ConnectionStrings.DefaultConnectionStringName, "DefaultConnString-Value"));
+ acme.ConnectionStrings.Add(new TenantConnectionString(acme.Id, "MyConnString", "MyConnString-Value"));
+ await _tenantRepository.InsertAsync(acme);
+
+ var volosoft = await _tenantManager.CreateAsync("volosoft");
+ await _tenantRepository.InsertAsync(volosoft);
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/LazyLoad_Tests.cs b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/LazyLoad_Tests.cs
new file mode 100644
index 0000000000..4ece524e0f
--- /dev/null
+++ b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/LazyLoad_Tests.cs
@@ -0,0 +1,33 @@
+using System.Linq;
+using System.Threading.Tasks;
+using Shouldly;
+using Volo.Abp.Modularity;
+using Volo.Abp.Uow;
+using Xunit;
+
+namespace Volo.Abp.TenantManagement
+{
+ public abstract class LazyLoad_Tests : TenantManagementTestBase
+ where TStartupModule : IAbpModule
+ {
+ public ITenantRepository TenantRepository { get; }
+
+ protected LazyLoad_Tests()
+ {
+ TenantRepository = GetRequiredService();
+ }
+
+ [Fact]
+ public async Task Should_Lazy_Load_Tenant_Collections()
+ {
+ using (var uow = GetRequiredService().Begin())
+ {
+ var role = await TenantRepository.FindByNameAsync("acme", includeDetails: false);
+ role.ConnectionStrings.ShouldNotBeNull();
+ role.ConnectionStrings.Any().ShouldBeTrue();
+
+ await uow.CompleteAsync();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/tenantmanagement/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/TenantManagementTestBase.cs b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/TenantManagementTestBase.cs
new file mode 100644
index 0000000000..eee575ba5f
--- /dev/null
+++ b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/TenantManagementTestBase.cs
@@ -0,0 +1,13 @@
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.TenantManagement
+{
+ public abstract class TenantManagementTestBase : AbpIntegratedTest
+ where TStartupModule : IAbpModule
+ {
+ protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
+ {
+ options.UseAutofac();
+ }
+ }
+}
diff --git a/modules/tenantmanagement/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/TenantRepository_Tests.cs b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/TenantRepository_Tests.cs
new file mode 100644
index 0000000000..b48299ffd1
--- /dev/null
+++ b/modules/tenantmanagement/test/Volo.Abp.TenantManagement.TestBase/Volo/Abp/TenantManagement/TenantRepository_Tests.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Shouldly;
+using Volo.Abp.Modularity;
+using Volo.Abp.Uow;
+using Xunit;
+
+namespace Volo.Abp.TenantManagement
+{
+ public abstract class TenantRepository_Tests : TenantManagementTestBase
+ where TStartupModule : IAbpModule
+ {
+ public ITenantRepository TenantRepository { get; }
+
+ protected TenantRepository_Tests()
+ {
+ TenantRepository = GetRequiredService();
+ }
+
+ [Fact]
+ public async Task FindByNameAsync()
+ {
+ var tenant = await TenantRepository.FindByNameAsync("acme");
+ tenant.ShouldNotBeNull();
+
+ tenant = await TenantRepository.FindByNameAsync("undefined-tenant");
+ tenant.ShouldBeNull();
+
+ tenant = await TenantRepository.FindByNameAsync("acme", includeDetails: true);
+ tenant.ShouldNotBeNull();
+ tenant.ConnectionStrings.Count.ShouldBeGreaterThanOrEqualTo(2);
+ }
+
+ [Fact]
+ public async Task FindAsync()
+ {
+ var tenantId = (await TenantRepository.FindByNameAsync("acme")).Id;
+
+ var tenant = await TenantRepository.FindAsync(tenantId);
+ tenant.ShouldNotBeNull();
+
+ tenant = await TenantRepository.FindAsync(Guid.NewGuid());
+ tenant.ShouldBeNull();
+
+ tenant = await TenantRepository.FindAsync(tenantId, includeDetails: true);
+ tenant.ShouldNotBeNull();
+ tenant.ConnectionStrings.Count.ShouldBeGreaterThanOrEqualTo(2);
+ }
+
+ [Fact]
+ public async Task GetCountAsync()
+ {
+ (await TenantRepository.GetCountAsync()).ShouldBeGreaterThan(0);
+ }
+
+ [Fact]
+ public async Task GetListAsync()
+ {
+ var tenants = await TenantRepository.GetListAsync();
+ tenants.ShouldContain(t => t.Name == "acme");
+ tenants.ShouldContain(t => t.Name == "volosoft");
+ }
+
+ [Fact]
+ public async Task Should_Eager_Load_Tenant_Collections()
+ {
+ var role = await TenantRepository.FindByNameAsync("acme");
+ role.ConnectionStrings.ShouldNotBeNull();
+ role.ConnectionStrings.Any().ShouldBeTrue();
+ }
+ }
+}
diff --git a/modules/users/README.md b/modules/users/README.md
new file mode 100644
index 0000000000..c284cf7c43
--- /dev/null
+++ b/modules/users/README.md
@@ -0,0 +1,2 @@
+# abp-users
+Users module for ABP framework.
diff --git a/modules/users/Volo.Abp.Users.sln b/modules/users/Volo.Abp.Users.sln
new file mode 100644
index 0000000000..8045a66035
--- /dev/null
+++ b/modules/users/Volo.Abp.Users.sln
@@ -0,0 +1,60 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27428.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Users.Domain", "src\Volo.Abp.Users.Domain\Volo.Abp.Users.Domain.csproj", "{C4147378-B0A4-4308-840F-BD75D2EFC808}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Users.EntityFrameworkCore", "src\Volo.Abp.Users.EntityFrameworkCore\Volo.Abp.Users.EntityFrameworkCore.csproj", "{F7F3F6E3-E564-49C5-8DF7-53FB390730A3}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Users.Domain.Shared", "src\Volo.Abp.Users.Domain.Shared\Volo.Abp.Users.Domain.Shared.csproj", "{C18FFD6A-0BE2-4FB8-B953-895F7B9CB374}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{6DE713B1-3AD7-4388-8B07-CCBE7158EC6E}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{61BBB58B-4B6E-417D-A768-87B2D00BC7C3}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Users.Abstractions", "src\Volo.Abp.Users.Abstractions\Volo.Abp.Users.Abstractions.csproj", "{3905D0E9-448D-43DE-8884-C60D0612AA04}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Users.MongoDB", "src\Volo.Abp.Users.MongoDB\Volo.Abp.Users.MongoDB.csproj", "{70E89492-7D4E-418A-835F-35E70282E808}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C4147378-B0A4-4308-840F-BD75D2EFC808}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C4147378-B0A4-4308-840F-BD75D2EFC808}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C4147378-B0A4-4308-840F-BD75D2EFC808}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C4147378-B0A4-4308-840F-BD75D2EFC808}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F7F3F6E3-E564-49C5-8DF7-53FB390730A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F7F3F6E3-E564-49C5-8DF7-53FB390730A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F7F3F6E3-E564-49C5-8DF7-53FB390730A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F7F3F6E3-E564-49C5-8DF7-53FB390730A3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C18FFD6A-0BE2-4FB8-B953-895F7B9CB374}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C18FFD6A-0BE2-4FB8-B953-895F7B9CB374}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C18FFD6A-0BE2-4FB8-B953-895F7B9CB374}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C18FFD6A-0BE2-4FB8-B953-895F7B9CB374}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3905D0E9-448D-43DE-8884-C60D0612AA04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3905D0E9-448D-43DE-8884-C60D0612AA04}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3905D0E9-448D-43DE-8884-C60D0612AA04}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3905D0E9-448D-43DE-8884-C60D0612AA04}.Release|Any CPU.Build.0 = Release|Any CPU
+ {70E89492-7D4E-418A-835F-35E70282E808}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {70E89492-7D4E-418A-835F-35E70282E808}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {70E89492-7D4E-418A-835F-35E70282E808}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {70E89492-7D4E-418A-835F-35E70282E808}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {C4147378-B0A4-4308-840F-BD75D2EFC808} = {6DE713B1-3AD7-4388-8B07-CCBE7158EC6E}
+ {F7F3F6E3-E564-49C5-8DF7-53FB390730A3} = {6DE713B1-3AD7-4388-8B07-CCBE7158EC6E}
+ {C18FFD6A-0BE2-4FB8-B953-895F7B9CB374} = {6DE713B1-3AD7-4388-8B07-CCBE7158EC6E}
+ {3905D0E9-448D-43DE-8884-C60D0612AA04} = {6DE713B1-3AD7-4388-8B07-CCBE7158EC6E}
+ {70E89492-7D4E-418A-835F-35E70282E808} = {6DE713B1-3AD7-4388-8B07-CCBE7158EC6E}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {E18777C8-72AB-4C08-BA72-885650BE70A9}
+ EndGlobalSection
+EndGlobal
diff --git a/modules/users/common.props b/modules/users/common.props
new file mode 100644
index 0000000000..f00a3fc2cb
--- /dev/null
+++ b/modules/users/common.props
@@ -0,0 +1,16 @@
+
+
+ latest
+ 0.3.0
+ $(NoWarn);CS1591
+ http://www.aspnetboilerplate.com/images/abp_nupkg.png
+ http://abp.io
+ git
+ https://github.com/volosoft/abp/
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Abstractions/Volo.Abp.Users.Abstractions.csproj b/modules/users/src/Volo.Abp.Users.Abstractions/Volo.Abp.Users.Abstractions.csproj
new file mode 100644
index 0000000000..7224c555da
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Abstractions/Volo.Abp.Users.Abstractions.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.Users.Abstractions
+ Volo.Abp.Users.Abstractions
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
diff --git a/modules/users/src/Volo.Abp.Users.Abstractions/Volo.Abp.Users.Abstractions.csproj.DotSettings b/modules/users/src/Volo.Abp.Users.Abstractions/Volo.Abp.Users.Abstractions.csproj.DotSettings
new file mode 100644
index 0000000000..58ad6c8854
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Abstractions/Volo.Abp.Users.Abstractions.csproj.DotSettings
@@ -0,0 +1,2 @@
+
+ CSharp71
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Settings/UserSettingManagerExtensions.cs b/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Settings/UserSettingManagerExtensions.cs
new file mode 100644
index 0000000000..4e3a1dd574
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Settings/UserSettingManagerExtensions.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using Volo.Abp.Users;
+
+namespace Volo.Abp.Settings
+{
+ //TODO: Consider to move to another package?
+
+ public static class UserSettingManagerExtensions
+ {
+ public static Task GetOrNullForUserAsync(this ISettingManager settingManager, [NotNull] string name, Guid userId, bool fallback = true)
+ {
+ return settingManager.GetOrNullAsync(name, UserSettingValueProvider.ProviderName, userId.ToString(), fallback);
+ }
+
+ public static Task GetOrNullForCurrentUserAsync(this ISettingManager settingManager, [NotNull] string name, bool fallback = true)
+ {
+ return settingManager.GetOrNullAsync(name, UserSettingValueProvider.ProviderName, null, fallback);
+ }
+
+ public static Task> GetAllForUserAsync(this ISettingManager settingManager, Guid userId, bool fallback = true)
+ {
+ return settingManager.GetAllAsync(UserSettingValueProvider.ProviderName, userId.ToString(), fallback);
+ }
+
+ public static Task> GetAllForCurrentUserAsync(this ISettingManager settingManager, bool fallback = true)
+ {
+ return settingManager.GetAllAsync(UserSettingValueProvider.ProviderName, null, fallback);
+ }
+
+ public static Task SetForUserAsync(this ISettingManager settingManager, Guid userId, [NotNull] string name, [CanBeNull] string value, bool forceToSet = false)
+ {
+ return settingManager.SetAsync(name, value, UserSettingValueProvider.ProviderName, userId.ToString(), forceToSet);
+ }
+
+ public static Task SetForCurrentUserAsync(this ISettingManager settingManager, [NotNull] string name, [CanBeNull] string value, bool forceToSet = false)
+ {
+ return settingManager.SetAsync(name, value, UserSettingValueProvider.ProviderName, null, forceToSet);
+ }
+ }
+}
diff --git a/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/AbpUsersAbstractionModule.cs b/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/AbpUsersAbstractionModule.cs
new file mode 100644
index 0000000000..6b66444d32
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/AbpUsersAbstractionModule.cs
@@ -0,0 +1,21 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Modularity;
+using Volo.Abp.Settings;
+
+namespace Volo.Abp.Users
+{
+ //TODO: Consider to (somehow) move this to the framework to the same assemblily of ICurrentUser!
+
+ public class AbpUsersAbstractionModule : AbpModule
+ {
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ services.Configure(options =>
+ {
+ options.ValueProviders.Add();
+ });
+
+ services.AddAssemblyOf();
+ }
+ }
+}
diff --git a/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/IExternalUserLookupServiceProvider.cs b/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/IExternalUserLookupServiceProvider.cs
new file mode 100644
index 0000000000..8f498e32c3
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/IExternalUserLookupServiceProvider.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Volo.Abp.Users
+{
+ public interface IExternalUserLookupServiceProvider //TODO: Consider to inherit from IUserLookupService
+ {
+ Task FindByIdAsync(Guid id, CancellationToken cancellationToken = default);
+
+ Task FindByUserNameAsync(string userName, CancellationToken cancellationToken = default);
+ }
+}
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/IUserData.cs b/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/IUserData.cs
new file mode 100644
index 0000000000..e7c0d8dab7
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/IUserData.cs
@@ -0,0 +1,24 @@
+using System;
+using JetBrains.Annotations;
+
+namespace Volo.Abp.Users
+{
+ public interface IUserData
+ {
+ Guid Id { get; }
+
+ Guid? TenantId { get; }
+
+ string UserName { get; }
+
+ [CanBeNull]
+ string Email { get; }
+
+ bool EmailConfirmed { get; }
+
+ [CanBeNull]
+ string PhoneNumber { get; }
+
+ bool PhoneNumberConfirmed { get; }
+ }
+}
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/UserData.cs b/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/UserData.cs
new file mode 100644
index 0000000000..7ed587cb9c
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/UserData.cs
@@ -0,0 +1,45 @@
+using System;
+using JetBrains.Annotations;
+
+namespace Volo.Abp.Users
+{
+ public class UserData : IUserData
+ {
+ public Guid Id { get; set; }
+
+ public string UserName { get; set; }
+
+ public string Email { get; set; }
+
+ public bool EmailConfirmed { get; set; }
+
+ public string PhoneNumber { get; set; }
+
+ public bool PhoneNumberConfirmed { get; set; }
+
+ public Guid? TenantId { get; set; }
+
+ public UserData()
+ {
+
+ }
+
+ public UserData(
+ Guid id,
+ [NotNull] string userName,
+ [CanBeNull] string email = null,
+ bool emailConfirmed = false,
+ [CanBeNull] string phoneNumber = null,
+ bool phoneNumberConfirmed = false,
+ Guid? tenantId = null)
+ {
+ Id = id;
+ UserName = userName;
+ Email = email;
+ EmailConfirmed = emailConfirmed;
+ PhoneNumber = phoneNumber;
+ PhoneNumberConfirmed = phoneNumberConfirmed;
+ TenantId = tenantId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/UserSettingValueProvider.cs b/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/UserSettingValueProvider.cs
new file mode 100644
index 0000000000..8beba5d31e
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Abstractions/Volo/Abp/Users/UserSettingValueProvider.cs
@@ -0,0 +1,48 @@
+using System.Threading.Tasks;
+using Volo.Abp.Settings;
+
+namespace Volo.Abp.Users
+{
+ //TODO: Optimization: Get all settings and cache it!
+ //TODO: Think if it's true to have this provider in this project?
+
+ public class UserSettingValueProvider : SettingValueProvider
+ {
+ public const string ProviderName = "User";
+
+ public override string Name => ProviderName;
+
+ protected ICurrentUser CurrentUser { get; }
+
+ public UserSettingValueProvider(ISettingStore settingStore, ICurrentUser currentUser)
+ : base(settingStore)
+ {
+ CurrentUser = currentUser;
+ }
+
+ public override async Task GetOrNullAsync(SettingDefinition setting, string providerKey)
+ {
+ if (providerKey == null)
+ {
+ if (CurrentUser.Id == null)
+ {
+ return null;
+ }
+
+ providerKey = CurrentUser.Id.ToString();
+ }
+
+ return await SettingStore.GetOrNullAsync(setting.Name, Name, providerKey);
+ }
+
+ public override Task SetAsync(SettingDefinition setting, string value, string providerKey)
+ {
+ return SettingStore.SetAsync(setting.Name, value, Name, providerKey);
+ }
+
+ public override Task ClearAsync(SettingDefinition setting, string providerKey)
+ {
+ return SettingStore.DeleteAsync(setting.Name, Name, providerKey);
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Domain.Shared/Volo.Abp.Users.Domain.Shared.csproj b/modules/users/src/Volo.Abp.Users.Domain.Shared/Volo.Abp.Users.Domain.Shared.csproj
new file mode 100644
index 0000000000..ec7f62d4de
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Domain.Shared/Volo.Abp.Users.Domain.Shared.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.Users.Domain.Shared
+ Volo.Abp.Users.Domain.Shared
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
diff --git a/modules/users/src/Volo.Abp.Users.Domain.Shared/Volo/Abp/Users/AbpUserConsts.cs b/modules/users/src/Volo.Abp.Users.Domain.Shared/Volo/Abp/Users/AbpUserConsts.cs
new file mode 100644
index 0000000000..96fecdd60e
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Domain.Shared/Volo/Abp/Users/AbpUserConsts.cs
@@ -0,0 +1,11 @@
+namespace Volo.Abp.Users
+{
+ public class AbpUserConsts
+ {
+ public const int MaxUserNameLength = 256;
+
+ public const int MaxEmailLength = 256;
+
+ public const int MaxPhoneNumberLength = 16;
+ }
+}
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Domain.Shared/Volo/Abp/Users/AbpUsersDomainSharedModule.cs b/modules/users/src/Volo.Abp.Users.Domain.Shared/Volo/Abp/Users/AbpUsersDomainSharedModule.cs
new file mode 100644
index 0000000000..7a8984bbb3
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Domain.Shared/Volo/Abp/Users/AbpUsersDomainSharedModule.cs
@@ -0,0 +1,13 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.Users
+{
+ public class AbpUsersDomainSharedModule : AbpModule
+ {
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ services.AddAssemblyOf();
+ }
+ }
+}
diff --git a/modules/users/src/Volo.Abp.Users.Domain/Volo.Abp.Users.Domain.csproj b/modules/users/src/Volo.Abp.Users.Domain/Volo.Abp.Users.Domain.csproj
new file mode 100644
index 0000000000..e205f2929c
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Domain/Volo.Abp.Users.Domain.csproj
@@ -0,0 +1,22 @@
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.Users.Domain
+ Volo.Abp.Users.Domain
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/users/src/Volo.Abp.Users.Domain/Volo.Abp.Users.Domain.csproj.DotSettings b/modules/users/src/Volo.Abp.Users.Domain/Volo.Abp.Users.Domain.csproj.DotSettings
new file mode 100644
index 0000000000..58ad6c8854
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Domain/Volo.Abp.Users.Domain.csproj.DotSettings
@@ -0,0 +1,2 @@
+
+ CSharp71
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/AbpUserExtensions.cs b/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/AbpUserExtensions.cs
new file mode 100644
index 0000000000..19dea86bf5
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/AbpUserExtensions.cs
@@ -0,0 +1,18 @@
+namespace Volo.Abp.Users
+{
+ public static class AbpUserExtensions
+ {
+ public static IUserData ToAbpUserData(this IUser user)
+ {
+ return new UserData(
+ user.Id,
+ user.UserName,
+ user.Email,
+ user.EmailConfirmed,
+ user.PhoneNumber,
+ user.PhoneNumberConfirmed,
+ user.TenantId
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/AbpUsersDomainModule.cs b/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/AbpUsersDomainModule.cs
new file mode 100644
index 0000000000..d0fe88111b
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/AbpUsersDomainModule.cs
@@ -0,0 +1,21 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Modularity;
+using Volo.Abp.Security;
+using Volo.Abp.Settings;
+
+namespace Volo.Abp.Users
+{
+ [DependsOn(
+ typeof(AbpUsersDomainSharedModule),
+ typeof(AbpUsersAbstractionModule),
+ typeof(AbpSecurityModule),
+ typeof(AbpSettingsModule)
+ )]
+ public class AbpUsersDomainModule : AbpModule
+ {
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ services.AddAssemblyOf();
+ }
+ }
+}
diff --git a/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUpdateUserData.cs b/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUpdateUserData.cs
new file mode 100644
index 0000000000..33fc3aed11
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUpdateUserData.cs
@@ -0,0 +1,9 @@
+using JetBrains.Annotations;
+
+namespace Volo.Abp.Users
+{
+ public interface IUpdateUserData
+ {
+ bool Update([NotNull] IUserData user);
+ }
+}
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUser.cs b/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUser.cs
new file mode 100644
index 0000000000..374c41a0b7
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUser.cs
@@ -0,0 +1,22 @@
+using System;
+using JetBrains.Annotations;
+using Volo.Abp.Domain.Entities;
+using Volo.Abp.MultiTenancy;
+
+namespace Volo.Abp.Users
+{
+ public interface IUser : IAggregateRoot, IMultiTenant
+ {
+ string UserName { get; }
+
+ [CanBeNull]
+ string Email { get; }
+
+ bool EmailConfirmed { get; }
+
+ [CanBeNull]
+ string PhoneNumber { get; }
+
+ bool PhoneNumberConfirmed { get; }
+ }
+}
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUserLookupService.cs b/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUserLookupService.cs
new file mode 100644
index 0000000000..2c50d7795f
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUserLookupService.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Volo.Abp.Users
+{
+ public interface IUserLookupService
+ where TUser : class, IUser
+ {
+ Task FindByIdAsync(Guid id, CancellationToken cancellationToken = default);
+
+ Task FindByUserNameAsync(string userName, CancellationToken cancellationToken = default);
+
+ //TODO: More...
+ }
+}
diff --git a/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUserRepository.cs b/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUserRepository.cs
new file mode 100644
index 0000000000..14eea6a3fe
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/IUserRepository.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.Domain.Entities;
+using Volo.Abp.Domain.Repositories;
+
+namespace Volo.Abp.Users
+{
+ public interface IUserRepository : IBasicRepository
+ where TUser : class, IUser, IAggregateRoot
+ {
+ Task FindByUserNameAsync(string userName, CancellationToken cancellationToken = default);
+
+ Task> GetListAsync(IEnumerable ids, CancellationToken cancellationToken = default);
+ }
+}
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/UserLookupService.cs b/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/UserLookupService.cs
new file mode 100644
index 0000000000..24b44be473
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/UserLookupService.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Uow;
+
+namespace Volo.Abp.Users
+{
+ public abstract class UserLookupService : IUserLookupService, ITransientDependency
+ where TUser : class, IUser
+ where TUserRepository : IUserRepository
+ {
+ public IExternalUserLookupServiceProvider ExternalUserLookupServiceProvider { get; set; }
+ public ILogger> Logger { get; set; }
+
+ private readonly TUserRepository _userRepository;
+ private readonly IUnitOfWorkManager _unitOfWorkManager;
+
+ protected UserLookupService(
+ TUserRepository userRepository,
+ IUnitOfWorkManager unitOfWorkManager)
+ {
+ _userRepository = userRepository;
+ _unitOfWorkManager = unitOfWorkManager;
+
+ Logger = NullLogger>.Instance;
+ }
+
+ public async Task FindByIdAsync(Guid id, CancellationToken cancellationToken = default)
+ {
+ if (ExternalUserLookupServiceProvider == null)
+ {
+ return await _userRepository.FindAsync(id, cancellationToken: cancellationToken);
+ }
+
+ IUserData externalUser;
+
+ try
+ {
+ externalUser = await ExternalUserLookupServiceProvider.FindByIdAsync(id, cancellationToken);
+ if (externalUser == null)
+ {
+ //TODO: Instead of deleting, should be make it inactive or something like that?
+ await WithNewUowAsync(() => _userRepository.DeleteAsync(id, cancellationToken: cancellationToken));
+
+ return null;
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogException(ex);
+ return await _userRepository.FindAsync(id, cancellationToken: cancellationToken);
+ }
+
+ var localUser = await _userRepository.FindAsync(id, cancellationToken: cancellationToken);
+ if (localUser == null)
+ {
+ await WithNewUowAsync(() => _userRepository.InsertAsync(CreateUser(externalUser), cancellationToken: cancellationToken));
+
+ return await _userRepository.FindAsync(id, cancellationToken: cancellationToken);
+ }
+
+ if (localUser is IUpdateUserData && ((IUpdateUserData)localUser).Update(externalUser))
+ {
+ await WithNewUowAsync(() => _userRepository.UpdateAsync(localUser, cancellationToken: cancellationToken));
+ }
+
+ return await _userRepository.FindAsync(id, cancellationToken: cancellationToken);
+ }
+
+ public async Task FindByUserNameAsync(string userName, CancellationToken cancellationToken = default)
+ {
+ if (ExternalUserLookupServiceProvider == null)
+ {
+ return await _userRepository.FindByUserNameAsync(userName, cancellationToken);
+ }
+
+ IUserData externalUser;
+
+ try
+ {
+ externalUser = await ExternalUserLookupServiceProvider.FindByUserNameAsync(userName, cancellationToken);
+ if (externalUser == null)
+ {
+ var localExistingUser = await _userRepository.FindByUserNameAsync(userName, cancellationToken);
+ if (localExistingUser != null)
+ {
+ //TODO: Instead of deleting, should be make it passive or something like that?
+ await WithNewUowAsync(() => _userRepository.DeleteAsync(localExistingUser.Id, cancellationToken: cancellationToken));
+ }
+
+ return null;
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogException(ex);
+ return await _userRepository.FindByUserNameAsync(userName, cancellationToken);
+ }
+
+ var localUser = await _userRepository.FindByUserNameAsync(userName, cancellationToken);
+ if (localUser == null)
+ {
+ await WithNewUowAsync(() => _userRepository.InsertAsync(CreateUser(externalUser), cancellationToken: cancellationToken));
+ return await _userRepository.FindAsync(externalUser.Id, cancellationToken: cancellationToken);
+ }
+
+ if (localUser is IUpdateUserData && ((IUpdateUserData)localUser).Update(externalUser))
+ {
+ await WithNewUowAsync(() => _userRepository.UpdateAsync(localUser, cancellationToken: cancellationToken));
+ }
+
+ return await _userRepository.FindAsync(externalUser.Id, cancellationToken: cancellationToken);
+ }
+
+ protected abstract TUser CreateUser(IUserData externalUser);
+
+ private async Task WithNewUowAsync(Func func)
+ {
+ using (var uow = _unitOfWorkManager.Begin(requiresNew: true))
+ {
+ await func();
+ await uow.SaveChangesAsync();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/UserLookupServiceExtensions.cs b/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/UserLookupServiceExtensions.cs
new file mode 100644
index 0000000000..88781ea026
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Domain/Volo/Abp/Users/UserLookupServiceExtensions.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.Domain.Entities;
+
+namespace Volo.Abp.Users
+{
+ public static class UserLookupServiceExtensions
+ {
+ public static async Task GetByIdAsync(this IUserLookupService userLookupService, Guid id, CancellationToken cancellationToken = default)
+ where TUser : class, IUser
+ {
+ var user = await userLookupService.FindByIdAsync(id, cancellationToken);
+ if (user == null)
+ {
+ throw new EntityNotFoundException(typeof(TUser), id);
+ }
+
+ return user;
+ }
+
+ public static async Task GetByUserNameAsync(this IUserLookupService userLookupService, string userName, CancellationToken cancellationToken = default)
+ where TUser : class, IUser
+ {
+ var user = await userLookupService.FindByUserNameAsync(userName, cancellationToken);
+ if (user == null)
+ {
+ throw new EntityNotFoundException(typeof(TUser), userName);
+ }
+
+ return user;
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo.Abp.Users.EntityFrameworkCore.csproj b/modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo.Abp.Users.EntityFrameworkCore.csproj
new file mode 100644
index 0000000000..8fc36ea81c
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo.Abp.Users.EntityFrameworkCore.csproj
@@ -0,0 +1,21 @@
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.Users.EntityFrameworkCore
+ Volo.Abp.Users.EntityFrameworkCore
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
diff --git a/modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo.Abp.Users.EntityFrameworkCore.csproj.DotSettings b/modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo.Abp.Users.EntityFrameworkCore.csproj.DotSettings
new file mode 100644
index 0000000000..58ad6c8854
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo.Abp.Users.EntityFrameworkCore.csproj.DotSettings
@@ -0,0 +1,2 @@
+
+ CSharp71
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo/Abp/Users/EntityFrameworkCore/AbpUsersDbContextModelCreatingExtensions.cs b/modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo/Abp/Users/EntityFrameworkCore/AbpUsersDbContextModelCreatingExtensions.cs
new file mode 100644
index 0000000000..1367fc423e
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo/Abp/Users/EntityFrameworkCore/AbpUsersDbContextModelCreatingExtensions.cs
@@ -0,0 +1,20 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Volo.Abp.EntityFrameworkCore.Modeling;
+
+namespace Volo.Abp.Users.EntityFrameworkCore
+{
+ public static class AbpUsersDbContextModelCreatingExtensions
+ {
+ public static void ConfigureAbpUser(this EntityTypeBuilder b, ModelBuilderConfigurationOptions options)
+ where TUser : class, IUser
+ {
+ b.Property(u => u.TenantId).HasColumnName(nameof(IUser.TenantId));
+ b.Property(u => u.UserName).IsRequired().HasMaxLength(AbpUserConsts.MaxUserNameLength).HasColumnName(nameof(IUser.UserName));
+ b.Property(u => u.Email).HasMaxLength(AbpUserConsts.MaxEmailLength).HasColumnName(nameof(IUser.Email));
+ b.Property(u => u.EmailConfirmed).HasDefaultValue(false).HasColumnName(nameof(IUser.EmailConfirmed));
+ b.Property(u => u.PhoneNumber).HasMaxLength(AbpUserConsts.MaxPhoneNumberLength).HasColumnName(nameof(IUser.PhoneNumber));
+ b.Property(u => u.PhoneNumberConfirmed).HasDefaultValue(false).HasColumnName(nameof(IUser.PhoneNumberConfirmed));
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo/Abp/Users/EntityFrameworkCore/AbpUsersEntityFrameworkCoreModule.cs b/modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo/Abp/Users/EntityFrameworkCore/AbpUsersEntityFrameworkCoreModule.cs
new file mode 100644
index 0000000000..375a42ea66
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo/Abp/Users/EntityFrameworkCore/AbpUsersEntityFrameworkCoreModule.cs
@@ -0,0 +1,18 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.EntityFrameworkCore;
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.Users.EntityFrameworkCore
+{
+ [DependsOn(
+ typeof(AbpUsersDomainModule),
+ typeof(AbpEntityFrameworkCoreModule)
+ )]
+ public class AbpUsersEntityFrameworkCoreModule : AbpModule
+ {
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ services.AddAssemblyOf();
+ }
+ }
+}
diff --git a/modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo/Abp/Users/EntityFrameworkCore/EfCoreAbpUserRepositoryBase.cs b/modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo/Abp/Users/EntityFrameworkCore/EfCoreAbpUserRepositoryBase.cs
new file mode 100644
index 0000000000..5d0958101c
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo/Abp/Users/EntityFrameworkCore/EfCoreAbpUserRepositoryBase.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
+using Volo.Abp.EntityFrameworkCore;
+
+namespace Volo.Abp.Users.EntityFrameworkCore
+{
+ public abstract class EfCoreUserRepositoryBase : EfCoreRepository, IUserRepository
+ where TDbContext : IEfCoreDbContext
+ where TUser : class, IUser
+ {
+ protected EfCoreUserRepositoryBase(IDbContextProvider dbContextProvider)
+ : base(dbContextProvider)
+ {
+
+ }
+
+ public async Task FindByUserNameAsync(string userName, CancellationToken cancellationToken = default)
+ {
+ return await this.FirstOrDefaultAsync(u => u.UserName == userName, GetCancellationToken(cancellationToken));
+ }
+
+ public virtual async Task> GetListAsync(IEnumerable ids, CancellationToken cancellationToken = default)
+ {
+ return await DbSet.Where(u => ids.Contains(u.Id)).ToListAsync(GetCancellationToken(cancellationToken));
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.MongoDB/Volo.Abp.Users.MongoDB.csproj b/modules/users/src/Volo.Abp.Users.MongoDB/Volo.Abp.Users.MongoDB.csproj
new file mode 100644
index 0000000000..dae8ecf8db
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.MongoDB/Volo.Abp.Users.MongoDB.csproj
@@ -0,0 +1,21 @@
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.Users.MongoDB
+ Volo.Abp.Users.MongoDB
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
diff --git a/modules/users/src/Volo.Abp.Users.MongoDB/Volo.Abp.Users.MongoDB.csproj.DotSettings b/modules/users/src/Volo.Abp.Users.MongoDB/Volo.Abp.Users.MongoDB.csproj.DotSettings
new file mode 100644
index 0000000000..58ad6c8854
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.MongoDB/Volo.Abp.Users.MongoDB.csproj.DotSettings
@@ -0,0 +1,2 @@
+
+ CSharp71
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.MongoDB/Volo/Abp/Users/MongoDB/AbpUsersMongoDbModule.cs b/modules/users/src/Volo.Abp.Users.MongoDB/Volo/Abp/Users/MongoDB/AbpUsersMongoDbModule.cs
new file mode 100644
index 0000000000..ef10cc9e76
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.MongoDB/Volo/Abp/Users/MongoDB/AbpUsersMongoDbModule.cs
@@ -0,0 +1,18 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Modularity;
+using Volo.Abp.MongoDB;
+
+namespace Volo.Abp.Users.MongoDB
+{
+ [DependsOn(
+ typeof(AbpUsersDomainModule),
+ typeof(AbpMongoDbModule)
+ )]
+ public class AbpUsersMongoDbModule : AbpModule
+ {
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ services.AddAssemblyOf();
+ }
+ }
+}
diff --git a/modules/users/src/Volo.Abp.Users.MongoDB/Volo/Abp/Users/MongoDB/MongoUserRepositoryBase.cs b/modules/users/src/Volo.Abp.Users.MongoDB/Volo/Abp/Users/MongoDB/MongoUserRepositoryBase.cs
new file mode 100644
index 0000000000..3c5bb66b25
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.MongoDB/Volo/Abp/Users/MongoDB/MongoUserRepositoryBase.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MongoDB.Driver;
+using MongoDB.Driver.Linq;
+using Volo.Abp.Domain.Repositories.MongoDB;
+using Volo.Abp.MongoDB;
+
+namespace Volo.Abp.Users.MongoDB
+{
+ public abstract class MongoUserRepositoryBase : MongoDbRepository, IUserRepository
+ where TDbContext : IAbpMongoDbContext
+ where TUser : class, IUser
+ {
+ protected MongoUserRepositoryBase(IMongoDbContextProvider dbContextProvider)
+ : base(dbContextProvider)
+ {
+
+ }
+
+ public virtual async Task FindByUserNameAsync(string userName, CancellationToken cancellationToken = default)
+ {
+ return await GetMongoQueryable().FirstOrDefaultAsync(u => u.UserName == userName, GetCancellationToken(cancellationToken));
+ }
+
+ public virtual async Task> GetListAsync(IEnumerable ids, CancellationToken cancellationToken = default)
+ {
+ return await GetMongoQueryable().Where(u => ids.Contains(u.Id)) .ToListAsync(GetCancellationToken(cancellationToken));
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo.Abp.Users.Tests.Shared.csproj b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo.Abp.Users.Tests.Shared.csproj
new file mode 100644
index 0000000000..755a46fb65
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo.Abp.Users.Tests.Shared.csproj
@@ -0,0 +1,31 @@
+
+
+
+ netcoreapp2.0
+ Volo.Abp.Users.Tests.Shared
+ Volo.Abp.Users.Tests.Shared
+ latest
+ true
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo.Abp.Users.Tests.Shared.csproj.DotSettings b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo.Abp.Users.Tests.Shared.csproj.DotSettings
new file mode 100644
index 0000000000..58ad6c8854
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo.Abp.Users.Tests.Shared.csproj.DotSettings
@@ -0,0 +1,2 @@
+
+ CSharp71
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUserRepository_Tests.cs b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUserRepository_Tests.cs
new file mode 100644
index 0000000000..40d8de9572
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUserRepository_Tests.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Shouldly;
+using Volo.Abp.Modularity;
+using Xunit;
+
+namespace Volo.Abp.Users
+{
+ public abstract class AbpUserRepository_Tests : AbpUsersTestBase
+ where TStartupModule : IAbpModule
+ {
+ private readonly AbpUsersLocalTestData _localTestData;
+ private readonly IAbpUserRepository _userRepository;
+
+ protected AbpUserRepository_Tests()
+ {
+ _userRepository = GetRequiredService();
+ _localTestData = GetRequiredService();
+ }
+
+ [Fact]
+ public async Task FindAsync()
+ {
+ var john = await _userRepository.FindAsync(_localTestData.John.Id);
+ john.ShouldNotBeNull();
+ john.UserName.ShouldBe(_localTestData.John.UserName);
+
+ //Undefined user
+ (await _userRepository.FindAsync(Guid.NewGuid())).ShouldBeNull();
+ }
+
+ [Fact]
+ public async Task FindByUserNameAsync()
+ {
+ var john = await _userRepository.FindByUserNameAsync(_localTestData.John.UserName);
+ john.ShouldNotBeNull();
+ john.Id.ShouldBe(_localTestData.John.Id);
+
+ //Undefined user
+ (await _userRepository.FindByUserNameAsync("undefined-user")).ShouldBeNull();
+ }
+
+ [Fact]
+ public async Task GetListAsync()
+ {
+ (await _userRepository.GetListAsync(new Guid[0])).Any().ShouldBeFalse();
+ (await _userRepository.GetListAsync(new[] { _localTestData.John.Id })).Count.ShouldBe(1);
+ (await _userRepository.GetListAsync(new[] { _localTestData.John.Id, _localTestData.David.Id })).Count.ShouldBe(2);
+ (await _userRepository.GetListAsync(new[] { _localTestData.John.Id, _localTestData.David.Id, Guid.NewGuid() })).Count.ShouldBe(2);
+ }
+ }
+}
diff --git a/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersExternalTestData.cs b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersExternalTestData.cs
new file mode 100644
index 0000000000..dba3c573de
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersExternalTestData.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Users
+{
+ public class AbpUsersExternalTestData : ISingletonDependency
+ {
+ public IAbpUserData David { get; }
+ public IAbpUserData Neo { get; }
+
+ public AbpUsersExternalTestData(AbpUsersLocalTestData localTestData)
+ {
+ Neo = new AbpUserData(Guid.NewGuid(), "neo");
+ David = localTestData.David.ToAbpUserData();
+ }
+
+ public List GetAllUsers()
+ {
+ return new List
+ {
+ David,
+ Neo
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersLocalTestData.cs b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersLocalTestData.cs
new file mode 100644
index 0000000000..2382a3f93e
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersLocalTestData.cs
@@ -0,0 +1,17 @@
+using System;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Users
+{
+ public class AbpUsersLocalTestData : ISingletonDependency
+ {
+ public AbpUser John { get; }
+ public AbpUser David { get; }
+
+ public AbpUsersLocalTestData()
+ {
+ John = new AbpUser(Guid.NewGuid(), "john");
+ David = new AbpUser(Guid.NewGuid(), "david", "david@abp.io");
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersTestBase.cs b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersTestBase.cs
new file mode 100644
index 0000000000..2d90e59d4a
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersTestBase.cs
@@ -0,0 +1,13 @@
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.Users
+{
+ public abstract class AbpUsersTestBase : AbpIntegratedTest
+ where TStartupModule : IAbpModule
+ {
+ protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
+ {
+ options.UseAutofac();
+ }
+ }
+}
diff --git a/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersTestDataBuilder.cs b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersTestDataBuilder.cs
new file mode 100644
index 0000000000..a3602491be
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersTestDataBuilder.cs
@@ -0,0 +1,29 @@
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Users
+{
+ public class AbpUsersTestDataBuilder : ITransientDependency
+ {
+ private readonly IAbpUserRepository _userRepository;
+ private readonly AbpUsersLocalTestData _localTestData;
+
+ public AbpUsersTestDataBuilder(
+ IAbpUserRepository userRepository,
+ AbpUsersLocalTestData localTestData)
+ {
+ _userRepository = userRepository;
+ _localTestData = localTestData;
+ }
+
+ public void Build()
+ {
+ AddUsers();
+ }
+
+ private void AddUsers()
+ {
+ _userRepository.Insert(_localTestData.John);
+ _userRepository.Insert(_localTestData.David);
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersTestsSharedModule.cs b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersTestsSharedModule.cs
new file mode 100644
index 0000000000..50da79183d
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/AbpUsersTestsSharedModule.cs
@@ -0,0 +1,34 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Autofac;
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.Users
+{
+ [DependsOn(
+ typeof(AbpUsersDomainModule),
+ typeof(AbpTestBaseModule),
+ typeof(AbpAutofacModule)
+ )]
+ public class AbpUsersTestsSharedModule : AbpModule
+ {
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ services.AddAssemblyOf();
+ }
+
+ 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/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/ExternalUserLookupService_Tests.cs b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/ExternalUserLookupService_Tests.cs
new file mode 100644
index 0000000000..8916607ea7
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/ExternalUserLookupService_Tests.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Shouldly;
+using Volo.Abp.Modularity;
+using Xunit;
+
+namespace Volo.Abp.Users
+{
+ public abstract class ExternalUserLookupService_Tests : AbpUsersTestBase
+ where TStartupModule : IAbpModule
+ {
+ private readonly IAbpUserLookupService _lookupService;
+ private readonly AbpUsersLocalTestData _localTestData;
+ private readonly AbpUsersExternalTestData _externalTestData;
+
+ protected ExternalUserLookupService_Tests()
+ {
+ _lookupService = GetRequiredService();
+ _localTestData = GetRequiredService();
+ _externalTestData = GetRequiredService();
+ }
+
+ protected override void AfterAddApplication(IServiceCollection services)
+ {
+ services.AddTransient();
+ }
+
+ [Fact]
+ public async Task FindByUserNameAsync()
+ {
+ (await GetRequiredService().FindByUserNameAsync(_localTestData.John.UserName)).ShouldNotBeNull();
+
+ (await _lookupService.FindByUserNameAsync("undefined-user")).ShouldBeNull();
+ (await _lookupService.FindByUserNameAsync(_localTestData.John.UserName)).ShouldBeNull(); //Because it's not available in the external provider. And this will delete the user!
+ (await _lookupService.FindByUserNameAsync(_localTestData.David.UserName)).ShouldNotBeNull();
+ (await _lookupService.FindByUserNameAsync(_externalTestData.Neo.UserName)).ShouldNotBeNull();
+
+ (await GetRequiredService().FindByUserNameAsync(_localTestData.John.UserName)).ShouldBeNull();
+ }
+
+ [Fact]
+ public async Task FindByIdAsync()
+ {
+ (await GetRequiredService().FindAsync(_localTestData.John.Id)).ShouldNotBeNull();
+
+ (await _lookupService.FindByIdAsync(Guid.NewGuid())).ShouldBeNull();
+ (await _lookupService.FindByIdAsync(_localTestData.John.Id)).ShouldBeNull(); //Because it's not available in the external provider. And this will delete the user!
+ (await _lookupService.FindByIdAsync(_localTestData.David.Id)).ShouldNotBeNull();
+ (await _lookupService.FindByIdAsync(_externalTestData.Neo.Id)).ShouldNotBeNull();
+
+ (await GetRequiredService().FindAsync(_localTestData.John.Id)).ShouldBeNull();
+ }
+
+ public class TestExternalAbpUserLookupServiceProvider : IExternalAbpUserLookupServiceProvider
+ {
+ private readonly AbpUsersExternalTestData _externalTestData;
+
+ public TestExternalAbpUserLookupServiceProvider(AbpUsersExternalTestData externalTestData)
+ {
+ _externalTestData = externalTestData;
+ }
+
+ public Task FindByIdAsync(Guid id, CancellationToken cancellationToken = default)
+ {
+ return Task.FromResult(_externalTestData.GetAllUsers().FirstOrDefault(u => u.Id == id));
+ }
+
+ public Task FindByUserNameAsync(string userName, CancellationToken cancellationToken = default)
+ {
+ return Task.FromResult(_externalTestData.GetAllUsers().FirstOrDefault(u => u.UserName == userName));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/LocalUserLookupService_Tests.cs b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/LocalUserLookupService_Tests.cs
new file mode 100644
index 0000000000..9e128f14cc
--- /dev/null
+++ b/modules/users/src/Volo.Abp.Users.Tests.Shared/Volo/Abp/Users/LocalUserLookupService_Tests.cs
@@ -0,0 +1,28 @@
+using System.Threading.Tasks;
+using Shouldly;
+using Volo.Abp.Modularity;
+using Xunit;
+
+namespace Volo.Abp.Users
+{
+ public abstract class LocalUserLookupService_Tests : AbpUsersTestBase
+ where TStartupModule : IAbpModule
+ {
+ private readonly IAbpUserLookupService _lookupService;
+ private readonly AbpUsersLocalTestData _localTestData;
+
+ protected LocalUserLookupService_Tests()
+ {
+ _lookupService = GetRequiredService();
+ _localTestData = GetRequiredService();
+ }
+
+ [Fact]
+ public async Task FindByUserNameAsync()
+ {
+ (await _lookupService.FindByUserNameAsync(_localTestData.John.UserName)).ShouldNotBeNull();
+ (await _lookupService.FindByUserNameAsync(_localTestData.David.UserName)).ShouldNotBeNull();
+ (await _lookupService.FindByUserNameAsync("undefined-user")).ShouldBeNull();
+ }
+ }
+}
diff --git a/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo.Abp.Users.EntityFrameworkCore.Tests.csproj b/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo.Abp.Users.EntityFrameworkCore.Tests.csproj
new file mode 100644
index 0000000000..8960a35c99
--- /dev/null
+++ b/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo.Abp.Users.EntityFrameworkCore.Tests.csproj
@@ -0,0 +1,25 @@
+
+
+
+ netcoreapp2.0
+ Volo.Abp.Users.EntityFrameworkCore.Tests
+ Volo.Abp.Users.EntityFrameworkCore.Tests
+ latest
+ true
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo.Abp.Users.EntityFrameworkCore.Tests.csproj.DotSettings b/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo.Abp.Users.EntityFrameworkCore.Tests.csproj.DotSettings
new file mode 100644
index 0000000000..58ad6c8854
--- /dev/null
+++ b/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo.Abp.Users.EntityFrameworkCore.Tests.csproj.DotSettings
@@ -0,0 +1,2 @@
+
+ CSharp71
\ No newline at end of file
diff --git a/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/AbpUserRepository_Tests.cs b/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/AbpUserRepository_Tests.cs
new file mode 100644
index 0000000000..7f92ae7483
--- /dev/null
+++ b/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/AbpUserRepository_Tests.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.Users.EntityFrameworkCore
+{
+ public class AbpUserRepository_Tests : AbpUserRepository_Tests
+ {
+
+ }
+}
diff --git a/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/AbpUsersEntityFrameworkCoreTestModule.cs b/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/AbpUsersEntityFrameworkCoreTestModule.cs
new file mode 100644
index 0000000000..cba8126af0
--- /dev/null
+++ b/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/AbpUsersEntityFrameworkCoreTestModule.cs
@@ -0,0 +1,38 @@
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.EntityFrameworkCore;
+using Volo.Abp.Modularity;
+using Volo.Abp.Uow;
+
+namespace Volo.Abp.Users.EntityFrameworkCore
+{
+ [DependsOn(
+ typeof(AbpUsersTestsSharedModule),
+ typeof(AbpUsersEntityFrameworkCoreModule)
+ )]
+ public class AbpUsersEntityFrameworkCoreTestModule : AbpModule
+ {
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ 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
+ });
+
+ services.AddAssemblyOf();
+ }
+ }
+}
diff --git a/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/ExternalUserLookupService_Tests.cs b/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/ExternalUserLookupService_Tests.cs
new file mode 100644
index 0000000000..26fd633808
--- /dev/null
+++ b/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/ExternalUserLookupService_Tests.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.Users.EntityFrameworkCore
+{
+ public class ExternalUserLookupService_Tests : ExternalUserLookupService_Tests
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/LocalUserLookupService_Tests.cs b/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/LocalUserLookupService_Tests.cs
new file mode 100644
index 0000000000..089fd35dbe
--- /dev/null
+++ b/modules/users/test/Volo.Abp.Users.EntityFrameworkCore.Tests/Volo/Abp/Users/EntityFrameworkCore/LocalUserLookupService_Tests.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.Users.EntityFrameworkCore
+{
+ public class LocalUserLookupService_Tests : LocalUserLookupService_Tests
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo.Abp.Users.MongoDB.Tests.csproj b/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo.Abp.Users.MongoDB.Tests.csproj
new file mode 100644
index 0000000000..a75e23f948
--- /dev/null
+++ b/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo.Abp.Users.MongoDB.Tests.csproj
@@ -0,0 +1,25 @@
+
+
+
+ netcoreapp2.0
+ Volo.Abp.Users.MongoDB.Tests
+ Volo.Abp.Users.MongoDB.Tests
+ latest
+ true
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/AbpUserRepository_Tests.cs b/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/AbpUserRepository_Tests.cs
new file mode 100644
index 0000000000..7f0f5620db
--- /dev/null
+++ b/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/AbpUserRepository_Tests.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.Users.MongoDB
+{
+ public class AbpUserRepository_Tests : AbpUserRepository_Tests
+ {
+
+ }
+}
diff --git a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/AbpUsersMongoDbTestModule.cs b/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/AbpUsersMongoDbTestModule.cs
new file mode 100644
index 0000000000..65963cc6e6
--- /dev/null
+++ b/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/AbpUsersMongoDbTestModule.cs
@@ -0,0 +1,33 @@
+using Microsoft.Extensions.DependencyInjection;
+using Mongo2Go;
+using Volo.Abp.Data;
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.Users.MongoDB
+{
+ [DependsOn(
+ typeof(AbpUsersMongoDbModule),
+ typeof(AbpUsersTestsSharedModule)
+ )]
+ public class AbpUsersMongoDbTestModule : AbpModule
+ {
+ private MongoDbRunner _mongoDbRunner;
+
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ _mongoDbRunner = MongoDbRunner.Start();
+
+ services.Configure(options =>
+ {
+ options.ConnectionStrings.Default = _mongoDbRunner.ConnectionString;
+ });
+
+ services.AddAssemblyOf();
+ }
+
+ public override void OnApplicationShutdown(ApplicationShutdownContext context)
+ {
+ _mongoDbRunner.Dispose();
+ }
+ }
+}
diff --git a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/ExternalUserLookupService_Tests.cs b/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/ExternalUserLookupService_Tests.cs
new file mode 100644
index 0000000000..451ef8baae
--- /dev/null
+++ b/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/ExternalUserLookupService_Tests.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.Users.MongoDB
+{
+ public class ExternalUserLookupService_Tests : ExternalUserLookupService_Tests
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/LocalUserLookupService_Tests.cs b/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/LocalUserLookupService_Tests.cs
new file mode 100644
index 0000000000..26f9aa1ac5
--- /dev/null
+++ b/modules/users/test/Volo.Abp.Users.MongoDB.Tests/Volo/Abp/Users/MongoDB/LocalUserLookupService_Tests.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.Users.MongoDB
+{
+ public class LocalUserLookupService_Tests : LocalUserLookupService_Tests
+ {
+
+ }
+}
\ No newline at end of file