diff --git a/docs/en/Domain-Driven-Design-Implementation-Guide.md b/docs/en/Domain-Driven-Design-Implementation-Guide.md index 84b7f186a8..6e243c5913 100644 --- a/docs/en/Domain-Driven-Design-Implementation-Guide.md +++ b/docs/en/Domain-Driven-Design-Implementation-Guide.md @@ -92,6 +92,8 @@ The picture below shows a Visual Studio Solution created using the ABP's [applic The solution name is `IssueTracking` and it consists of multiple projects. The solution is layered by considering **DDD principles** as well as **development** and **deployment** practicals. The sub sections below explains the projects in the solution; +> Your solution structure may be slightly different if you choose a different UI or Database provider. However, the Domain and Application layers will be same and this is the essential point for the DDD perspective. See the [Application Startup Template](Startup-Templates/Application.md) document if you want to know more about the solution structure. + #### The Domain Layer The Domain Layer is splitted into two projects; @@ -169,8 +171,53 @@ This design decision potentially allows you to use Entities and EF Core objects ### Execution Flow a DDD Based Application -TODO +The figure below shows a typical request flow for a web application that has been developed based on DDD patterns. + +![](images/domain-driven-design-web-request-flow.png) + +* The request typically begins by a user interaction on the UI (a *use case*) that causes an HTTP request to the server. +* An MVC Controller or a Razor Page Handler in the Presentation Layer (or in the Distributed Services Layer) handles the request and can perform some cross cutting concerns in this stage ([Authorization](Authorization.md), [Validation](Validation.md), [Exception Handling](Exception-Handling.md), etc.). A Controller/Page injects the related Application Service interface and calls its method(s) by sending and receiving DTOs. +* The Application Service use the Domain Objects (Entities, Repository interfaces, Domain Services, etc.) to implement the *use case*. Application Layer implements some cross cutting concerns (Authorization, Validation, etc.). An Application Service method should be a [Unit Of Work](Unit-Of-Work.md). That means it should be atomic. + +Most of the cross cutting concerns are **automatically and conventionally implemented by the ABP Framework** and you typically don't need to write code for them. ### Common Principles +Before going into details, let's see some overall DDD principles; + +#### Database Provider / ORM Independence + +The domain and application layers should be ORM / Database Provider agnostic. They only depends on the Repository interfaces and the Repository interfaces doesn't use any ORM specific objects. + +Here, the main reasons of this principle; + +1. To make your domain/application **infrastructure independent** since the infrastructure may change in the future or you may need to support a second database type later. +2. To make your domain/application **focus on the business code** by hiding the infrastructure details behind the repositories. +3. To make your **automated tests** easier since you can mock the repositories in this case. + +> As a respect to this principle, none of the projects in the solution has reference to the `EntityFrameworkCore` project, except the startup application. + +##### Discussion About the Database Independence Principle + +Especially, the **reason 1** deeply effects your domain **object design** (especially, the entity relations) and **application code**. Assume that you are using [Entity Framework Core](Entity-Framework-Core.md) with a relational database. If you try to make your application so that it is possible to switch to [MongoDB](MongoDB.md) later, you can't use some very **useful EF Core features**. Examples; + +* You can't assume [Change Tracking](https://docs.microsoft.com/en-us/ef/core/querying/tracking) since MongoDB provider can't do it. So, you always need to explicitly update the changed entities. +* You can't use [Navigation Properties](https://docs.microsoft.com/en-us/ef/core/modeling/relationships) (or Collections) to other Aggregates in your entities since this is not possible for a Document Database. See the "Rule: Reference Other Aggregates Only By Id" section for more info. + +If you think such features are **important** for you and you **will never move away** from the EF Core, we believe that it is worth **relaxing this principle**. We still suggest to use the repository pattern to hide the infrastructure details. But you can assume that you are using EF Core while designing your entity relations and writing your application code. You can even reference to the EF Core NuGet Package from your application layer to be able to directly use the asynchronous LINQ extension methods, like `ToListAsync()` (see the *IQueryable & Async Operations* section in the [Repositories](Repositories.md) document for more info). + +#### Presentation Technology Agnostic + +The presentation technology (UI Framework) is one of the most changed parts of a real world application. It is very important to design the **Domain and Application Layers** to be completely **unaware** of the presentation technology/framework. This principle is relatively easy to implement and ABP's startup template makes it even easier. + +In some cases, you may need to have **duplicate logic** in the application and presentation layers. For example, you may need to duplicate the **validation** and **authorization** checks in both layers. The checks in the UI layer is mostly for **user experience** while checks in the application and domain layers are for **security and data integrity**. That's perfectly normal and necessary. + +#### Focus on the State Changes, Not Reporting + +DDD focuses on how the domain objects **changes and interactions**; How to create an entity and change its properties by preserving the data **integrity/validity** and implementing the **business rules**. + +DDD **ignores reporting** and mass querying. That doesn't mean they are not important. If your application doesn't have fancy dashboards and reports, who would use it? However, reporting is another topic. You typically want to use the full power of the SQL Server or even use a separate data source (like ElasticSearch) for reporting purpose. You will write optimized queries, create indexes and even stored procedures(!). You are free to do all as long as you don't mix all these into your business logic. + +## Implementation: The Building Blocks + TODO \ No newline at end of file diff --git a/docs/en/images/domain-driven-design-web-request-flow.png b/docs/en/images/domain-driven-design-web-request-flow.png new file mode 100644 index 0000000000..a9cb98e01f Binary files /dev/null and b/docs/en/images/domain-driven-design-web-request-flow.png differ diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/SourceCodeDownloadService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/SourceCodeDownloadService.cs index ca01314758..78a95996bd 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/SourceCodeDownloadService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/SourceCodeDownloadService.cs @@ -53,6 +53,12 @@ namespace Volo.Abp.Cli.Commands.Services var zipEntry = zipInputStream.GetNextEntry(); while (zipEntry != null) { + if (IsAngularTestFile(zipEntry.Name)) + { + zipEntry = zipInputStream.GetNextEntry(); + continue; + } + var fullZipToPath = Path.Combine(outputFolder, zipEntry.Name); var directoryName = Path.GetDirectoryName(fullZipToPath); @@ -81,5 +87,43 @@ namespace Volo.Abp.Cli.Commands.Services Logger.LogInformation($"'{moduleName}' has been successfully downloaded to '{outputFolder}'"); } + + private bool IsAngularTestFile(string zipEntryName) + { + if (string.IsNullOrEmpty(zipEntryName)) + { + return false; + } + + if (zipEntryName.Contains(Path.Combine("angular/e2e"))) + { + return true; + } + if (zipEntryName.Contains(Path.Combine("angular/src"))) + { + return true; + } + if (zipEntryName.Contains(Path.Combine("angular/node_modules"))) + { + return true; + } + if (zipEntryName.Contains(Path.Combine("angular/scripts"))) + { + return true; + } + if (zipEntryName.Contains(Path.Combine("angular/source-code-requirements"))) + { + return true; + } + + var fileName = Path.GetFileName(zipEntryName); + + if (!string.IsNullOrEmpty(fileName) && zipEntryName.Equals("angular/" + fileName)) + { + return true; + } + + return false; + } } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AngularModuleSourceCodeAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AngularModuleSourceCodeAdder.cs index c9812b7bf4..6c89656b47 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AngularModuleSourceCodeAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AngularModuleSourceCodeAdder.cs @@ -33,17 +33,13 @@ namespace Volo.Abp.Cli.ProjectModification return; } - await ReplaceProjectNamesAndCopySourCodeRequirementsToProjectsAsync(angularProjectsPath, projects); - - await RemoveRedundantFilesAsync(angularProjectsPath, projects); - await AddPathsToTsConfigAsync(angularPath, angularProjectsPath, projects); await AddProjectToAngularJsonAsync(angularPath, projects); } catch (Exception e) { - Logger.LogError( "Unable to add angular source code: " + e.Message); + Logger.LogError("Unable to add angular source code: " + e.Message); } } @@ -54,7 +50,7 @@ namespace Volo.Abp.Cli.ProjectModification var json = JObject.Parse(fileContent); - var projectsJobject = (JObject)json["projects"]; + var projectsJobject = (JObject) json["projects"]; foreach (var project in projects) { @@ -62,126 +58,88 @@ namespace Volo.Abp.Cli.ProjectModification new JProperty("projectType", "library"), new JProperty("root", $"projects/{project}"), new JProperty("sourceRoot", $"projects/{project}/src"), - new JProperty("prefix", "lib"), + new JProperty("prefix", "abp"), new JProperty("architect", new JObject( new JProperty("build", new JObject( - new JProperty("builder", "@angular-devkit/build-ng-package:build"), + new JProperty("builder", "@angular-devkit/build-ng-packagr:build"), new JProperty("options", new JObject( new JProperty("tsConfig", $"projects/{project}/tsconfig.lib.json"), new JProperty("project", $"projects/{project}/ng-package.json") - )), + )), new JProperty("configurations", new JObject( new JProperty("production", new JObject( new JProperty("tsConfig", $"projects/{project}/tsconfig.lib.prod.json"))) )))), new JProperty("test", new JObject( - new JProperty("builder", "@angular-devkit/build-angular:karma"), + new JProperty("builder", "@angular-builders/jest:run"), new JProperty("options", new JObject( - new JProperty("main", $"projects/{project}/src/test.ts"), - new JProperty("tsConfig", $"projects/{project}/tsconfig.spec.json"), - new JProperty("karmaConfig", $"projects/{project}/karma.conf.js") + new JProperty("coverage", true), + new JProperty("passWithNoTests", true) ) ))), new JProperty("lint", new JObject( new JProperty("builder", "@angular-devkit/build-angular:tslint"), new JProperty("options", new JObject( - new JProperty("tsConfig", new JArray(new JValue($"projects/{project}/tsconfig.lib.json"), new JValue($"projects/{project}/tsconfig.spec.json"))), + new JProperty("tsConfig", + new JArray(new JValue($"projects/{project}/tsconfig.lib.json"), + new JValue($"projects/{project}/tsconfig.spec.json"))), new JProperty("exclude", new JArray(new JValue("**/node_modules/**"))) - )) )) )) - )); + )) + )); } File.WriteAllText(angularJsonFilePath, json.ToString(Formatting.Indented)); } - private async Task AddPathsToTsConfigAsync(string angularPath, string angularProjectsPath, List projects) + private async Task AddPathsToTsConfigAsync(string angularPath, string angularProjectsPath, + List projects) { var tsConfigPath = Path.Combine(angularPath, "tsconfig.json"); var fileContent = File.ReadAllText(tsConfigPath); - var tsConfigAsJson = JObject.Parse(fileContent); - - var compilerOptions = (JObject)tsConfigAsJson["compilerOptions"]; + var compilerOptions = (JObject) tsConfigAsJson["compilerOptions"]; foreach (var project in projects) { var projectPackageName = await GetProjectPackageNameAsync(angularProjectsPath, project); - var property = new JProperty($"{projectPackageName}", - new JArray(new object[] { $"projects/{project}/src/public-api.ts" }) - ); - var property2 = new JProperty($"{projectPackageName}/*", - new JArray(new object[] { $"projects/{project}/src/lib/*" }) - ); + var publicApis = Directory.GetFiles(Path.Combine(angularProjectsPath, project), "*public-api.ts", SearchOption.AllDirectories) + .Where(p => !p.Contains("\\node_modules\\")) + .Select(p => p.RemovePreFix(angularPath).Replace("\\", "/").RemovePreFix("/")); if (compilerOptions["paths"] == null) { - compilerOptions.Add("paths", new JObject()); } - ((JObject)compilerOptions["paths"]).Add(property); - ((JObject)compilerOptions["paths"]).Add(property2); - } - - File.WriteAllText(tsConfigPath, tsConfigAsJson.ToString(Formatting.Indented)); - } - - private async Task GetProjectPackageNameAsync(string angularProjectsPath, string project) - { - var packageJsonPath = Path.Combine(angularProjectsPath, project, "package.json"); - - var fileContent = File.ReadAllText(packageJsonPath); - - return (string)JObject.Parse(fileContent)["name"]; - } - - private async Task RemoveRedundantFilesAsync(string angularProjectsPath, List projects) - { - foreach (var project in projects) - { - var jestConfigPath = Path.Combine(angularProjectsPath, project, "jest.config.js"); - - if (File.Exists(jestConfigPath)) + foreach (var publicApi in publicApis) { - File.Delete(jestConfigPath); - } + var subFolderName = publicApi.RemovePreFix($"projects/{project}/").Split("/")[0]; - var testPath = Path.Combine(angularProjectsPath, project, "src", "test.ts"); + if (subFolderName == "src") + { + subFolderName = ""; + } + else + { + subFolderName = $"/{subFolderName}"; + } - if (File.Exists(testPath)) - { - File.Delete(testPath); + ((JObject) compilerOptions["paths"]).Add( + new JProperty($"{projectPackageName}{subFolderName}", + new JArray(new object[] {publicApi}) + ) + ); } } - } - - private async Task ReplaceProjectNamesAndCopySourCodeRequirementsToProjectsAsync(string angularProjectsPath, List projects) - { - foreach (var project in projects) - { - var sourceCodeReqFolder = Path.Combine(angularProjectsPath, project, "source-code-requirements"); - - Directory.CreateDirectory(sourceCodeReqFolder); - - var filesUnderSourceCodeReqFolder = Directory.GetFiles(sourceCodeReqFolder); - foreach (var fileUnderSourceCodeReqFolder in filesUnderSourceCodeReqFolder) - { - var newDest = Path.Combine(angularProjectsPath, project, Path.GetFileName(fileUnderSourceCodeReqFolder)); - File.Move(fileUnderSourceCodeReqFolder, newDest); - - var fileContent = File.ReadAllText(newDest); - fileContent = fileContent.Replace("{{project-name}}", project); - fileContent = fileContent.Replace("{{library-name-kebab-case}}", project); - File.WriteAllText(newDest, fileContent); - } - } + File.WriteAllText(tsConfigPath, tsConfigAsJson.ToString(Formatting.Indented)); } - private async Task> CopyAndGetNamesOfAngularProjectsAsync(string solutionFilePath, string angularProjectsPath) + private async Task> CopyAndGetNamesOfAngularProjectsAsync(string solutionFilePath, + string angularProjectsPath) { var projects = new List(); @@ -190,25 +148,40 @@ namespace Volo.Abp.Cli.ProjectModification Directory.CreateDirectory(angularProjectsPath); } - var angularPathsInDownloadedSourceCode = Directory.GetDirectories(Path.Combine(Path.Combine(Path.GetDirectoryName(solutionFilePath), "modules"))).Select(p => Path.Combine(p, "angular")) + var angularPathsInDownloadedSourceCode = Directory + .GetDirectories(Path.Combine(Path.Combine(Path.GetDirectoryName(solutionFilePath), "modules"))) + .Select(p => Path.Combine(p, "angular")) .Where(Directory.Exists); - foreach (var folder in angularPathsInDownloadedSourceCode) { var projectsInFolder = Directory.GetDirectories(folder); + + if (projectsInFolder.Length == 1 && Path.GetFileName(projectsInFolder[0]) == "projects") + { + var foldersUnderProject = Directory.GetDirectories(Path.Combine(folder, "projects")); + foreach (var folderUnderProject in foldersUnderProject) + { + if (Directory.Exists(Path.Combine(folder, Path.GetFileName(folderUnderProject)))) + { + continue; + } + Directory.Move(folderUnderProject, Path.Combine(folder, Path.GetFileName(folderUnderProject))); + } + projectsInFolder = Directory.GetDirectories(folder); + } + foreach (var projectInFolder in projectsInFolder) { var projectName = Path.GetFileName(projectInFolder.TrimEnd('\\').TrimEnd('/')); var destDirName = Path.Combine(angularProjectsPath, projectName); - if (Directory.Exists(destDirName)) + if (projectName == "projects" || Directory.Exists(destDirName)) { Directory.Delete(projectInFolder, true); continue; } - projects.Add(projectName); Directory.Move(projectInFolder, destDirName); @@ -217,5 +190,14 @@ namespace Volo.Abp.Cli.ProjectModification return projects; } + + private async Task GetProjectPackageNameAsync(string angularProjectsPath, string project) + { + var packageJsonPath = Path.Combine(angularProjectsPath, project, "package.json"); + + var fileContent = File.ReadAllText(packageJsonPath); + + return (string) JObject.Parse(fileContent)["name"]; + } } } diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AbpAuditLoggingDomainModule.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AbpAuditLoggingDomainModule.cs index b2d2505004..c5e94f633a 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AbpAuditLoggingDomainModule.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AbpAuditLoggingDomainModule.cs @@ -3,6 +3,7 @@ using Volo.Abp.Domain; using Volo.Abp.Modularity; using Volo.Abp.ObjectExtending; using Volo.Abp.ObjectExtending.Modularity; +using Volo.Abp.Threading; namespace Volo.Abp.AuditLogging { @@ -11,25 +12,30 @@ namespace Volo.Abp.AuditLogging [DependsOn(typeof(AbpAuditLoggingDomainSharedModule))] public class AbpAuditLoggingDomainModule : AbpModule { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + public override void PostConfigureServices(ServiceConfigurationContext context) { - ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( + OneTimeRunner.Run(() => + { + ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( AuditLoggingModuleExtensionConsts.ModuleName, AuditLoggingModuleExtensionConsts.EntityNames.AuditLog, typeof(AuditLog) - ); + ); - ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( - AuditLoggingModuleExtensionConsts.ModuleName, - AuditLoggingModuleExtensionConsts.EntityNames.AuditLogAction, - typeof(AuditLogAction) - ); + ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( + AuditLoggingModuleExtensionConsts.ModuleName, + AuditLoggingModuleExtensionConsts.EntityNames.AuditLogAction, + typeof(AuditLogAction) + ); - ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( - AuditLoggingModuleExtensionConsts.ModuleName, - AuditLoggingModuleExtensionConsts.EntityNames.EntityChange, - typeof(EntityChange) - ); + ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( + AuditLoggingModuleExtensionConsts.ModuleName, + AuditLoggingModuleExtensionConsts.EntityNames.EntityChange, + typeof(EntityChange) + ); + }); } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/AbpIdentityApplicationContractsModule.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/AbpIdentityApplicationContractsModule.cs index 43481ff30a..5ce1ed6755 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/AbpIdentityApplicationContractsModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/AbpIdentityApplicationContractsModule.cs @@ -5,6 +5,7 @@ using Volo.Abp.ObjectExtending; using Volo.Abp.ObjectExtending.Modularity; using Volo.Abp.PermissionManagement; using Volo.Abp.Users; +using Volo.Abp.Threading; namespace Volo.Abp.Identity { @@ -17,6 +18,8 @@ namespace Volo.Abp.Identity )] public class AbpIdentityApplicationContractsModule : AbpModule { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + public override void ConfigureServices(ServiceConfigurationContext context) { @@ -24,23 +27,24 @@ namespace Volo.Abp.Identity public override void PostConfigureServices(ServiceConfigurationContext context) { - ModuleExtensionConfigurationHelper - .ApplyEntityConfigurationToApi( + OneTimeRunner.Run(() => + { + ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToApi( IdentityModuleExtensionConsts.ModuleName, IdentityModuleExtensionConsts.EntityNames.Role, getApiTypes: new[] { typeof(IdentityRoleDto) }, createApiTypes: new[] { typeof(IdentityRoleCreateDto) }, updateApiTypes: new[] { typeof(IdentityRoleUpdateDto) } ); - - ModuleExtensionConfigurationHelper - .ApplyEntityConfigurationToApi( + + ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToApi( IdentityModuleExtensionConsts.ModuleName, IdentityModuleExtensionConsts.EntityNames.User, getApiTypes: new[] { typeof(IdentityUserDto) }, createApiTypes: new[] { typeof(IdentityUserCreateDto) }, updateApiTypes: new[] { typeof(IdentityUserUpdateDto) } ); + }); } } -} \ No newline at end of file +} diff --git a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs index 5362969011..ce26e91e20 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs @@ -11,6 +11,7 @@ using Volo.Abp.ObjectExtending.Modularity; using Volo.Abp.PermissionManagement.Web; using Volo.Abp.UI.Navigation; using Volo.Abp.VirtualFileSystem; +using Volo.Abp.Threading; namespace Volo.Abp.Identity.Web { @@ -20,6 +21,8 @@ namespace Volo.Abp.Identity.Web [DependsOn(typeof(AbpAspNetCoreMvcUiThemeSharedModule))] public class AbpIdentityWebModule : AbpModule { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + public override void PreConfigureServices(ServiceConfigurationContext context) { context.Services.PreConfigure(options => @@ -65,21 +68,24 @@ namespace Volo.Abp.Identity.Web public override void PostConfigureServices(ServiceConfigurationContext context) { - ModuleExtensionConfigurationHelper - .ApplyEntityConfigurationToUi( - IdentityModuleExtensionConsts.ModuleName, - IdentityModuleExtensionConsts.EntityNames.Role, - createFormTypes: new[] { typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.CreateModalModel.RoleInfoModel) }, - editFormTypes: new[] { typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.EditModalModel.RoleInfoModel) } - ); - - ModuleExtensionConfigurationHelper - .ApplyEntityConfigurationToUi( - IdentityModuleExtensionConsts.ModuleName, - IdentityModuleExtensionConsts.EntityNames.User, - createFormTypes: new[] { typeof(Volo.Abp.Identity.Web.Pages.Identity.Users.CreateModalModel.UserInfoViewModel) }, - editFormTypes: new[] { typeof(Volo.Abp.Identity.Web.Pages.Identity.Users.EditModalModel.UserInfoViewModel) } - ); + OneTimeRunner.Run(() => + { + ModuleExtensionConfigurationHelper + .ApplyEntityConfigurationToUi( + IdentityModuleExtensionConsts.ModuleName, + IdentityModuleExtensionConsts.EntityNames.Role, + createFormTypes: new[] { typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.CreateModalModel.RoleInfoModel) }, + editFormTypes: new[] { typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.EditModalModel.RoleInfoModel) } + ); + + ModuleExtensionConfigurationHelper + .ApplyEntityConfigurationToUi( + IdentityModuleExtensionConsts.ModuleName, + IdentityModuleExtensionConsts.EntityNames.User, + createFormTypes: new[] { typeof(Volo.Abp.Identity.Web.Pages.Identity.Users.CreateModalModel.UserInfoViewModel) }, + editFormTypes: new[] { typeof(Volo.Abp.Identity.Web.Pages.Identity.Users.EditModalModel.UserInfoViewModel) } + ); + }); } } } diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs index 58b366d645..328c70cc8a 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs @@ -20,6 +20,7 @@ using Volo.Abp.ObjectExtending; using Volo.Abp.ObjectExtending.Modularity; using Volo.Abp.Security; using Volo.Abp.Validation; +using Volo.Abp.Threading; namespace Volo.Abp.IdentityServer { @@ -34,6 +35,8 @@ namespace Volo.Abp.IdentityServer )] public class AbpIdentityServerDomainModule : AbpModule { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddAutoMapperObjectMapper(); @@ -102,23 +105,26 @@ namespace Volo.Abp.IdentityServer public override void PostConfigureServices(ServiceConfigurationContext context) { - ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( - IdentityServerModuleExtensionConsts.ModuleName, - IdentityServerModuleExtensionConsts.EntityNames.Client, - typeof(Client) - ); - - ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( - IdentityServerModuleExtensionConsts.ModuleName, - IdentityServerModuleExtensionConsts.EntityNames.IdentityResource, - typeof(IdentityResource) - ); - - ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( - IdentityServerModuleExtensionConsts.ModuleName, - IdentityServerModuleExtensionConsts.EntityNames.ApiResource, - typeof(ApiResource) - ); + OneTimeRunner.Run(() => + { + ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( + IdentityServerModuleExtensionConsts.ModuleName, + IdentityServerModuleExtensionConsts.EntityNames.Client, + typeof(Client) + ); + + ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( + IdentityServerModuleExtensionConsts.ModuleName, + IdentityServerModuleExtensionConsts.EntityNames.IdentityResource, + typeof(IdentityResource) + ); + + ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( + IdentityServerModuleExtensionConsts.ModuleName, + IdentityServerModuleExtensionConsts.EntityNames.ApiResource, + typeof(ApiResource) + ); + }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/AbpTenantManagementApplicationContractsModule.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/AbpTenantManagementApplicationContractsModule.cs index ea9bc778dd..57ab853253 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/AbpTenantManagementApplicationContractsModule.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/AbpTenantManagementApplicationContractsModule.cs @@ -2,6 +2,7 @@ using Volo.Abp.Modularity; using Volo.Abp.ObjectExtending; using Volo.Abp.ObjectExtending.Modularity; +using Volo.Abp.Threading; namespace Volo.Abp.TenantManagement { @@ -10,16 +11,21 @@ namespace Volo.Abp.TenantManagement typeof(AbpTenantManagementDomainSharedModule))] public class AbpTenantManagementApplicationContractsModule : AbpModule { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + public override void PostConfigureServices(ServiceConfigurationContext context) { - ModuleExtensionConfigurationHelper - .ApplyEntityConfigurationToApi( - TenantManagementModuleExtensionConsts.ModuleName, - TenantManagementModuleExtensionConsts.EntityNames.Tenant, - getApiTypes: new[] { typeof(TenantDto) }, - createApiTypes: new[] { typeof(TenantCreateDto) }, - updateApiTypes: new[] { typeof(TenantUpdateDto) } - ); + OneTimeRunner.Run(() => + { + ModuleExtensionConfigurationHelper + .ApplyEntityConfigurationToApi( + TenantManagementModuleExtensionConsts.ModuleName, + TenantManagementModuleExtensionConsts.EntityNames.Tenant, + getApiTypes: new[] { typeof(TenantDto) }, + createApiTypes: new[] { typeof(TenantCreateDto) }, + updateApiTypes: new[] { typeof(TenantUpdateDto) } + ); + }); } } } \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs index 686ca76378..764e9f7209 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs @@ -7,6 +7,7 @@ using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.ObjectExtending; using Volo.Abp.ObjectExtending.Modularity; +using Volo.Abp.Threading; namespace Volo.Abp.TenantManagement { @@ -17,6 +18,8 @@ namespace Volo.Abp.TenantManagement [DependsOn(typeof(AbpAutoMapperModule))] public class AbpTenantManagementDomainModule : AbpModule { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddAutoMapperObjectMapper(); @@ -34,11 +37,14 @@ namespace Volo.Abp.TenantManagement public override void PostConfigureServices(ServiceConfigurationContext context) { - ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( - TenantManagementModuleExtensionConsts.ModuleName, - TenantManagementModuleExtensionConsts.EntityNames.Tenant, - typeof(Tenant) - ); + OneTimeRunner.Run(() => + { + ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( + TenantManagementModuleExtensionConsts.ModuleName, + TenantManagementModuleExtensionConsts.EntityNames.Tenant, + typeof(Tenant) + ); + }); } } } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebModule.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebModule.cs index c186359b1c..e993ea2c63 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebModule.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/AbpTenantManagementWebModule.cs @@ -11,6 +11,7 @@ using Volo.Abp.TenantManagement.Localization; using Volo.Abp.TenantManagement.Web.Navigation; using Volo.Abp.UI.Navigation; using Volo.Abp.VirtualFileSystem; +using Volo.Abp.Threading; namespace Volo.Abp.TenantManagement.Web { @@ -20,6 +21,8 @@ namespace Volo.Abp.TenantManagement.Web [DependsOn(typeof(AbpAutoMapperModule))] public class AbpTenantManagementWebModule : AbpModule { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + public override void PreConfigureServices(ServiceConfigurationContext context) { context.Services.PreConfigure(options => @@ -62,13 +65,16 @@ namespace Volo.Abp.TenantManagement.Web public override void PostConfigureServices(ServiceConfigurationContext context) { - ModuleExtensionConfigurationHelper - .ApplyEntityConfigurationToUi( - TenantManagementModuleExtensionConsts.ModuleName, - TenantManagementModuleExtensionConsts.EntityNames.Tenant, - createFormTypes: new[] { typeof(Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants.CreateModalModel.TenantInfoModel) }, - editFormTypes: new[] { typeof(Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants.EditModalModel.TenantInfoModel) } - ); + OneTimeRunner.Run(() => + { + ModuleExtensionConfigurationHelper + .ApplyEntityConfigurationToUi( + TenantManagementModuleExtensionConsts.ModuleName, + TenantManagementModuleExtensionConsts.EntityNames.Tenant, + createFormTypes: new[] { typeof(Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants.CreateModalModel.TenantInfoModel) }, + editFormTypes: new[] { typeof(Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants.EditModalModel.TenantInfoModel) } + ); + }); } } } \ No newline at end of file