From 320a3829adab66bb4bed29ed4b97caa5b490c95b Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Mon, 28 Nov 2022 14:20:10 +0300 Subject: [PATCH 01/21] Initial state of Bulk Operations article --- .../2022-11-28-ef-bulk-operations/POST.md | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md diff --git a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md new file mode 100644 index 0000000000..3f679abf98 --- /dev/null +++ b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md @@ -0,0 +1,57 @@ +# Entity Framework Core Bulk Operations +Entity Framework tracks all the entity changes and applies those changes to database one by one when `SaveChanges()` method called. There was no way to execute bulk operations in Entity Framework Core without a dependency. + +As you know [Entity Framework Extensions](https://entityframework-extensions.net/bulk-savechanges) library were doing it but it was not free. + +There was no other solution until now. [Bulk Operations](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#executeupdate-and-executedelete-bulk-updates) are now available in Entity Framework Core with .NET 7. + +With .NET 7, there are two new methods such as `ExecuteUpdate` and `ExecuteDelete` available to execute bulk operations. It's a similar usage with Entity Framework Core Extensions library if you're familiar with it. + +You can visit the microsoft example [here](https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#executeupdate-and-executedelete-bulk-updates) about how to use it. + + +## Bulk Operations in ABP Framework + +ABP Framework supports Entity Framework Core as its ORM. So, you can use these new methods in your repository classes in your ABP applications. But there is another feature that ABP Framework provides: `IEfCoreBulkOperationProvider`. You can visit the [documentation](https://docs.abp.io/en/abp/latest/Entity-Framework-Core#customize-bulk-operations) to learn more about it. You always have full control of execution of bulk operations in your ABP applications. When you call `InsertMany`, `DeleteMany` or `UpdateMany` methods of repository, that provider will be invoked and you can customize all the queries from one point. Mostly you don't need to implement & customize that interface, but it's good to know that you can do it if you need. + +## Usage in Repositories + +Most of real world case scenarios, you need custom queries to execute bulk operations. You can't manage all the cases with overriding `IEfCoreBulkOperationProvider` even you can't identify entities in this provider because it's not aware of the properties of entities. Best way to use this new feature is inside the repositories. + +Let me show an example below: + +```csharp +public class BookEntityFrameworkCoreRepository : EfCoreRepository, IBookRepository +{ + public BookEntityFrameworkCoreRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + + public async Task UpdateListingAsync() + { + var dbSet = await GetDbSetAsync(); + + dbSet + .Where(x => x.IsListed && x.PublishedOn.Year <= 2022) + .ExecuteUpdate(s => s.SetProperty(x => x.IsListed, x => false)); + } + + public async Task DeleteOldBooksAsync() + { + var dbSet = await GetDbSetAsync(); + + dbSet.Where(x => x.PublishedOn.Year <= 2000) + .ExecuteDelete(); + } + + public async Task ImportAsync(List books) + { + var dbSet = await GetDbSetAsync(); + + // No need an action for bulk inserting. + await base.InsertManyAsync(books); + } +} +``` + +> There is no need to take an action for bulk inserting. You can use `InsertManyAsync` method of repository instead of creating a new method for it if you don't have a custom logic. It'll use new bulk inserting feature autmatically since it's available in EF Core 7.0. \ No newline at end of file From a359b6a16d83b2e384fe653aeef90f9decf6823b Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Mon, 28 Nov 2022 14:27:46 +0300 Subject: [PATCH 02/21] Spellcheck fix --- .../en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md index 3f679abf98..d439edd070 100644 --- a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md +++ b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md @@ -54,4 +54,4 @@ public class BookEntityFrameworkCoreRepository : EfCoreRepository There is no need to take an action for bulk inserting. You can use `InsertManyAsync` method of repository instead of creating a new method for it if you don't have a custom logic. It'll use new bulk inserting feature autmatically since it's available in EF Core 7.0. \ No newline at end of file +> There is no need to take an action for bulk inserting. You can use `InsertManyAsync` method of repository instead of creating a new method for it if you don't have a custom logic. It'll use new bulk inserting feature automatically since it's available in EF Core 7.0. \ No newline at end of file From 84b7b34211193a056a7fd43eaa3f7a71d26a319f Mon Sep 17 00:00:00 2001 From: Hamza Albreem <94292623+braim23@users.noreply.github.com> Date: Mon, 28 Nov 2022 14:45:51 +0300 Subject: [PATCH 03/21] quick fix --- .../2022-11-28-ef-bulk-operations/POST.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md index d439edd070..1aa356bf3e 100644 --- a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md +++ b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md @@ -1,22 +1,22 @@ # Entity Framework Core Bulk Operations -Entity Framework tracks all the entity changes and applies those changes to database one by one when `SaveChanges()` method called. There was no way to execute bulk operations in Entity Framework Core without a dependency. +Entity Framework tracks all the entity changes and applies those changes to the database one by one when the `SaveChanges()` method is called. There was no way to execute bulk operations in Entity Framework Core without a dependency. -As you know [Entity Framework Extensions](https://entityframework-extensions.net/bulk-savechanges) library were doing it but it was not free. +As you know the [Entity Framework Extensions](https://entityframework-extensions.net/bulk-savechanges) library was doing it but it was not free. There was no other solution until now. [Bulk Operations](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#executeupdate-and-executedelete-bulk-updates) are now available in Entity Framework Core with .NET 7. -With .NET 7, there are two new methods such as `ExecuteUpdate` and `ExecuteDelete` available to execute bulk operations. It's a similar usage with Entity Framework Core Extensions library if you're familiar with it. +With .NET 7, there are two new methods such as `ExecuteUpdate` and `ExecuteDelete` available to execute bulk operations. It's a similar usage with the Entity Framework Core Extensions library if you're familiar with it. You can visit the microsoft example [here](https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#executeupdate-and-executedelete-bulk-updates) about how to use it. ## Bulk Operations in ABP Framework -ABP Framework supports Entity Framework Core as its ORM. So, you can use these new methods in your repository classes in your ABP applications. But there is another feature that ABP Framework provides: `IEfCoreBulkOperationProvider`. You can visit the [documentation](https://docs.abp.io/en/abp/latest/Entity-Framework-Core#customize-bulk-operations) to learn more about it. You always have full control of execution of bulk operations in your ABP applications. When you call `InsertMany`, `DeleteMany` or `UpdateMany` methods of repository, that provider will be invoked and you can customize all the queries from one point. Mostly you don't need to implement & customize that interface, but it's good to know that you can do it if you need. +ABP Framework supports Entity Framework Core as its ORM. So, you can use these new methods in your repository classes in your ABP applications. But there is another feature that the ABP Framework provides: `IEfCoreBulkOperationProvider`. You can visit the [documentation](https://docs.abp.io/en/abp/latest/Entity-Framework-Core#customize-bulk-operations) to learn more about it. You always have full control of the execution of bulk operations in your ABP applications. When you call the `InsertMany`, `DeleteMany` or `UpdateMany` methods of the repository, that provider will be invoked and you can customize all the queries from one point. You don't mostly need to implement & customize that interface, but it's good to know that you can do it if you need. ## Usage in Repositories -Most of real world case scenarios, you need custom queries to execute bulk operations. You can't manage all the cases with overriding `IEfCoreBulkOperationProvider` even you can't identify entities in this provider because it's not aware of the properties of entities. Best way to use this new feature is inside the repositories. +In most of the real world case scenarios, you need custom queries to execute bulk operations. You can't manage all the cases by overriding `IEfCoreBulkOperationProvider` and you can't even identify the entities in this provider because it's not aware of the properties of the entities. The best way to use this new feature is inside the repositories. Let me show an example below: @@ -48,10 +48,10 @@ public class BookEntityFrameworkCoreRepository : EfCoreRepository There is no need to take an action for bulk inserting. You can use `InsertManyAsync` method of repository instead of creating a new method for it if you don't have a custom logic. It'll use new bulk inserting feature automatically since it's available in EF Core 7.0. \ No newline at end of file +> There is no need to take an action for bulk inserting. You can use the `InsertManyAsync` method of the repository instead of creating a new method for it if you don't have custom logic. It'll use a new bulk inserting feature automatically since it's available in EF Core 7.0. From dcf42700b231d5f4e627b9821794b5c9d14743d5 Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Mon, 28 Nov 2022 15:30:11 +0300 Subject: [PATCH 04/21] Update POST.md --- .../en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md index 1aa356bf3e..84666a59da 100644 --- a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md +++ b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md @@ -55,3 +55,5 @@ public class BookEntityFrameworkCoreRepository : EfCoreRepository There is no need to take an action for bulk inserting. You can use the `InsertManyAsync` method of the repository instead of creating a new method for it if you don't have custom logic. It'll use a new bulk inserting feature automatically since it's available in EF Core 7.0. + +> `ExecuteDeleteAsync` breaks the soft delete and auditing since deleted entities are removed from the change tracker. ABP can't handle it. So, you have to be careful while using it. \ No newline at end of file From 95bd723edf2b0e3f58bb2a3a1e7dcbe8cb275b17 Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Mon, 28 Nov 2022 15:54:41 +0300 Subject: [PATCH 05/21] Update title of article --- .../en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md index 84666a59da..fe4f677c4a 100644 --- a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md +++ b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md @@ -1,4 +1,4 @@ -# Entity Framework Core Bulk Operations +# Bulk Operations with Entity Framework Core 7.0 Entity Framework tracks all the entity changes and applies those changes to the database one by one when the `SaveChanges()` method is called. There was no way to execute bulk operations in Entity Framework Core without a dependency. As you know the [Entity Framework Extensions](https://entityframework-extensions.net/bulk-savechanges) library was doing it but it was not free. From b902b182c975d7002669e7d1cef4a6e1e5ed9767 Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Mon, 28 Nov 2022 17:41:11 +0300 Subject: [PATCH 06/21] Update docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Halil İbrahim Kalkan --- .../en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md index fe4f677c4a..f138b7ef91 100644 --- a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md +++ b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md @@ -56,4 +56,4 @@ public class BookEntityFrameworkCoreRepository : EfCoreRepository There is no need to take an action for bulk inserting. You can use the `InsertManyAsync` method of the repository instead of creating a new method for it if you don't have custom logic. It'll use a new bulk inserting feature automatically since it's available in EF Core 7.0. -> `ExecuteDeleteAsync` breaks the soft delete and auditing since deleted entities are removed from the change tracker. ABP can't handle it. So, you have to be careful while using it. \ No newline at end of file +> If you use `ExecuteDeleteAsync` or `ExecuteUpdateAsync`, then ABP's soft delete and auditing features can not work. Because these ABP features work with EF Core's change tracking system and these new methods doesn't work with the change tracking system. So, use them carefully. \ No newline at end of file From 1f5f615c65c33c2ff7cbad22bfc65baea407ad70 Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Mon, 28 Nov 2022 17:41:59 +0300 Subject: [PATCH 07/21] Update POST.md --- .../2022-11-28-ef-bulk-operations/POST.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md index f138b7ef91..27b8022fac 100644 --- a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md +++ b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md @@ -31,17 +31,17 @@ public class BookEntityFrameworkCoreRepository : EfCoreRepository x.IsListed && x.PublishedOn.Year <= 2022) - .ExecuteUpdate(s => s.SetProperty(x => x.IsListed, x => false)); + .ExecuteUpdateAsync(s => s.SetProperty(x => x.IsListed, x => false)); } public async Task DeleteOldBooksAsync() { var dbSet = await GetDbSetAsync(); - dbSet.Where(x => x.PublishedOn.Year <= 2000) - .ExecuteDelete(); + await dbSet.Where(x => x.PublishedOn.Year <= 2000) + .ExecuteDeleteAsync(); } public async Task ImportAsync(List books) From 2db6d5516216ee1f4bd56a5b9b546383b243e323 Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Mon, 28 Nov 2022 17:42:37 +0300 Subject: [PATCH 08/21] Update POST.md --- .../2022-11-28-ef-bulk-operations/POST.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md index 27b8022fac..5b01cbcdd8 100644 --- a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md +++ b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md @@ -43,14 +43,6 @@ public class BookEntityFrameworkCoreRepository : EfCoreRepository x.PublishedOn.Year <= 2000) .ExecuteDeleteAsync(); } - - public async Task ImportAsync(List books) - { - var dbSet = await GetDbSetAsync(); - - // No need for an action for bulk inserting. - await base.InsertManyAsync(books); - } } ``` From e54a6e47271c45d92816a4858979fa88688c67ed Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Mon, 28 Nov 2022 17:45:57 +0300 Subject: [PATCH 09/21] Remove IEfCoreBulkOperationProvider reference from documentation --- .../2022-11-28-ef-bulk-operations/POST.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md index 5b01cbcdd8..edf88bfcaa 100644 --- a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md +++ b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md @@ -9,16 +9,11 @@ With .NET 7, there are two new methods such as `ExecuteUpdate` and `ExecuteDelet You can visit the microsoft example [here](https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#executeupdate-and-executedelete-bulk-updates) about how to use it. - -## Bulk Operations in ABP Framework - -ABP Framework supports Entity Framework Core as its ORM. So, you can use these new methods in your repository classes in your ABP applications. But there is another feature that the ABP Framework provides: `IEfCoreBulkOperationProvider`. You can visit the [documentation](https://docs.abp.io/en/abp/latest/Entity-Framework-Core#customize-bulk-operations) to learn more about it. You always have full control of the execution of bulk operations in your ABP applications. When you call the `InsertMany`, `DeleteMany` or `UpdateMany` methods of the repository, that provider will be invoked and you can customize all the queries from one point. You don't mostly need to implement & customize that interface, but it's good to know that you can do it if you need. +ABP Framework supports Entity Framework Core as its ORM. So, you can use these new methods in your repository classes in your ABP applications without any limitation. ## Usage in Repositories -In most of the real world case scenarios, you need custom queries to execute bulk operations. You can't manage all the cases by overriding `IEfCoreBulkOperationProvider` and you can't even identify the entities in this provider because it's not aware of the properties of the entities. The best way to use this new feature is inside the repositories. - -Let me show an example below: +You can use these methods in your repositories as below: ```csharp public class BookEntityFrameworkCoreRepository : EfCoreRepository, IBookRepository @@ -40,7 +35,8 @@ public class BookEntityFrameworkCoreRepository : EfCoreRepository x.PublishedOn.Year <= 2000) + await dbSet + .Where(x => x.PublishedOn.Year <= 2000) .ExecuteDeleteAsync(); } } From 506779db7747934a4087e2d7a9e9dfbfbbe36cbd Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 29 Nov 2022 15:24:08 +0800 Subject: [PATCH 10/21] CLI supports MAUI Blazor UI --- .../Volo/Abp/Cli/Commands/NewCommand.cs | 2 +- .../Commands/ProjectCreationCommandBase.cs | 20 +++++--- .../ProjectBuilding/Building/UiFramework.cs | 3 +- .../Templates/App/AppTemplateBase.cs | 40 ++++++++++++++- .../MauiBlazorChangeApplicationIdGuidStep.cs | 34 +++++++++++++ ...orPortChangeForSeparatedAuthServersStep.cs | 50 +++++++++++++++++++ .../Templates/Maui/MauiChangePortStep.cs | 1 + 7 files changed, 138 insertions(+), 12 deletions(-) create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/MauiBlazorChangeApplicationIdGuidStep.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/MauiBlazorPortChangeForSeparatedAuthServersStep.cs diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs index 8421eb656b..ee0b6a90da 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs @@ -111,7 +111,7 @@ public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransien var skipBundling = commandLineArgs.Options.ContainsKey(Options.SkipBundling.Long) || commandLineArgs.Options.ContainsKey(Options.SkipBundling.Short); if (!skipBundling) { - await RunBundleForBlazorWasmTemplateAsync(projectArgs); + await RunBundleForBlazorWasmOrMauiBlazorTemplateAsync(projectArgs); } await ConfigurePwaSupportForAngular(projectArgs); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs index 2d5c9856cf..e0cbfe7d07 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs @@ -118,7 +118,7 @@ public abstract class ProjectCreationCommandBase Logger.LogInformation("DBMS: " + databaseManagementSystem); } - var uiFramework = GetUiFramework(commandLineArgs); + var uiFramework = GetUiFramework(commandLineArgs, template); if (uiFramework != UiFramework.NotSpecified) { Logger.LogInformation("UI Framework: " + uiFramework); @@ -413,22 +413,24 @@ public abstract class ProjectCreationCommandBase } } - protected async Task RunBundleForBlazorWasmTemplateAsync(ProjectBuildArgs projectArgs) + protected async Task RunBundleForBlazorWasmOrMauiBlazorTemplateAsync(ProjectBuildArgs projectArgs) { - if (AppTemplateBase.IsAppTemplate(projectArgs.TemplateName) && projectArgs.UiFramework == UiFramework.Blazor) + if (AppTemplateBase.IsAppTemplate(projectArgs.TemplateName) && projectArgs.UiFramework is UiFramework.Blazor or UiFramework.MauiBlazor) { - Logger.LogInformation("Generating bundles for Blazor Wasm..."); + var isWebassembly = projectArgs.UiFramework == UiFramework.Blazor; + var message = isWebassembly ? "Generating bundles for Blazor Wasm" : "Generating bundles for MAUI Blazor"; + Logger.LogInformation($"{message}..."); await EventBus.PublishAsync(new ProjectCreationProgressEvent { - Message = "Generating bundles for Blazor Wasm" + Message = message }, false); var directory = Path.GetDirectoryName( - Directory.GetFiles(projectArgs.OutputFolder, "*.Blazor.csproj", SearchOption.AllDirectories).First() + Directory.GetFiles(projectArgs.OutputFolder, isWebassembly? "*.Blazor.csproj" :"*.MauiBlazor.csproj", SearchOption.AllDirectories).First() ); - await _bundlingService.BundleAsync(directory, true); + await _bundlingService.BundleAsync(directory, true, projectType: isWebassembly ? BundlingConsts.WebAssembly : BundlingConsts.MauiBlazor); } } @@ -531,7 +533,7 @@ public abstract class ProjectCreationCommandBase } } - protected virtual UiFramework GetUiFramework(CommandLineArgs commandLineArgs) + protected virtual UiFramework GetUiFramework(CommandLineArgs commandLineArgs, string template = "app") { if (commandLineArgs.Options.ContainsKey("no-ui")) { @@ -554,6 +556,8 @@ public abstract class ProjectCreationCommandBase return UiFramework.Blazor; case "blazor-server": return UiFramework.BlazorServer; + case "maui-blazor" when template == AppProTemplate.TemplateName: + return UiFramework.MauiBlazor; default: throw new CliUsageException(ExceptionMessageHelper.GetInvalidOptionExceptionMessage("UI Framework")); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFramework.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFramework.cs index b1ffe8cb11..5b1ebb56f4 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFramework.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFramework.cs @@ -7,5 +7,6 @@ public enum UiFramework Mvc = 2, Angular = 3, Blazor = 4, - BlazorServer = 5 + BlazorServer = 5, + MauiBlazor = 6 } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs index 2ffa12bca7..b3bdf2d7e9 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs @@ -132,7 +132,11 @@ public abstract class AppTemplateBase : TemplateInfo case UiFramework.BlazorServer: ConfigureWithBlazorServerUi(context, steps); break; - + + case UiFramework.MauiBlazor: + ConfigureWithMauiBlazorUi(context, steps); + break; + case UiFramework.Mvc: case UiFramework.NotSpecified: ConfigureWithMvcUi(context, steps); @@ -510,6 +514,37 @@ public abstract class AppTemplateBase : TemplateInfo } } + protected void ConfigureWithMauiBlazorUi(ProjectBuildContext context, List steps) + { + context.Symbols.Add("ui:maui-blazor"); + + steps.Add(new MauiBlazorChangeApplicationIdGuidStep()); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Host")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Tests", projectFolderPath: "/aspnet-core/test/MyCompanyName.MyProjectName.Web.Tests")); + + if (context.BuildArgs.ExtraProperties.ContainsKey("separate-identity-server") || + context.BuildArgs.ExtraProperties.ContainsKey("separate-auth-server")) + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.HttpApi.HostWithIds")); + steps.Add(new MauiBlazorPortChangeForSeparatedAuthServersStep()); + steps.Add(new AppTemplateChangeDbMigratorPortSettingsStep("44300")); + + if (context.BuildArgs.MobileApp == MobileApp.ReactNative) + { + steps.Add(new ReactEnvironmentFilePortChangeForSeparatedAuthServersStep()); + } + } + else + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.HttpApi.Host")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.IdentityServer")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.AuthServer")); + steps.Add(new TemplateProjectRenameStep("MyCompanyName.MyProjectName.HttpApi.HostWithIds", "MyCompanyName.MyProjectName.HttpApi.Host")); + steps.Add(new AppTemplateChangeConsoleTestClientPortSettingsStep("44305")); + } + } + protected void RemoveUnnecessaryPorts(ProjectBuildContext context, List steps) { steps.Add(new RemoveUnnecessaryPortsStep()); @@ -599,7 +634,8 @@ public abstract class AppTemplateBase : TemplateInfo { if ((context.BuildArgs.UiFramework == UiFramework.Mvc || context.BuildArgs.UiFramework == UiFramework.Blazor - || context.BuildArgs.UiFramework == UiFramework.BlazorServer) && + || context.BuildArgs.UiFramework == UiFramework.BlazorServer + || context.BuildArgs.UiFramework == UiFramework.MauiBlazor) && context.BuildArgs.MobileApp == MobileApp.None) { steps.Add(new MoveFolderStep("/aspnet-core/", "/")); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/MauiBlazorChangeApplicationIdGuidStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/MauiBlazorChangeApplicationIdGuidStep.cs new file mode 100644 index 0000000000..953447cfa5 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/MauiBlazorChangeApplicationIdGuidStep.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using System.Xml; +using Volo.Abp.Cli.ProjectBuilding.Building; +using Volo.Abp.Cli.Utils; + +namespace Volo.Abp.Cli.ProjectBuilding.Templates.App; + +public class MauiBlazorChangeApplicationIdGuidStep: ProjectBuildPipelineStep +{ + public override void Execute(ProjectBuildContext context) + { + var projectFile = context.Files.FirstOrDefault(f => f.Name.EndsWith("MyCompanyName.MyProjectName.MauiBlazor.csproj")); + + if (projectFile == null) + { + return; + } + + using (var stream = StreamHelper.GenerateStreamFromString(projectFile.Content)) + { + var doc = new XmlDocument { PreserveWhitespace = true }; + doc.Load(stream); + + var node = doc.SelectSingleNode("/Project/PropertyGroup/ApplicationIdGuid"); + if (node != null) + { + node.InnerText = Guid.NewGuid().ToString(); + } + + projectFile.SetContent(doc.OuterXml); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/MauiBlazorPortChangeForSeparatedAuthServersStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/MauiBlazorPortChangeForSeparatedAuthServersStep.cs new file mode 100644 index 0000000000..92b59de7f3 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/MauiBlazorPortChangeForSeparatedAuthServersStep.cs @@ -0,0 +1,50 @@ +using System; +using System.Linq; +using Volo.Abp.Cli.ProjectBuilding.Building; + +namespace Volo.Abp.Cli.ProjectBuilding.Templates.App; + +public class MauiBlazorPortChangeForSeparatedAuthServersStep: ProjectBuildPipelineStep +{ + public override void Execute(ProjectBuildContext context) + { + + var appsettingsFile = context.Files.FirstOrDefault(x => + !x.IsDirectory && + x.Name.EndsWith("aspnet-core/src/MyCompanyName.MyProjectName.MauiBlazor/appsettings.json", + StringComparison.InvariantCultureIgnoreCase) + ); + + if(appsettingsFile == null) + { + return; + } + + appsettingsFile.NormalizeLineEndings(); + var lines = appsettingsFile.GetLines(); + + for (var i = 1; i < lines.Length; i++) + { + var line = lines[i]; + + var previousLine = lines[i-1]; + + if (line.Contains("Authority") && line.Contains("localhost")) + { + line = line.Replace("44305", "44301"); + } + else if (previousLine.Contains("AbpAccountPublic") && line.Contains("BaseUrl") && line.Contains("localhost")) + { + line = line.Replace("44305", "44301"); + } + else if (previousLine.Contains("Default") && line.Contains("BaseUrl") && line.Contains("localhost")) + { + line = line.Replace("44305", "44300"); + } + + lines[i] = line; + } + + appsettingsFile.SetLines(lines); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Maui/MauiChangePortStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Maui/MauiChangePortStep.cs index 2b45d64b19..41b9393573 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Maui/MauiChangePortStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Maui/MauiChangePortStep.cs @@ -53,6 +53,7 @@ public class MauiChangePortStep : ProjectBuildPipelineStep { case UiFramework.Angular: case UiFramework.Blazor: + case UiFramework.MauiBlazor: authServerPort = "44305"; apiHostPort = "44305"; break; From 07a921513e07acb087d97c7c34ac87204705c26a Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Tue, 29 Nov 2022 11:31:06 +0300 Subject: [PATCH 11/21] ADd CmsKit breaking-change into the docs --- docs/en/Migration-Guides/Abp-7_0.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/en/Migration-Guides/Abp-7_0.md b/docs/en/Migration-Guides/Abp-7_0.md index 3b6b1c5e6a..7c3a0d90b5 100644 --- a/docs/en/Migration-Guides/Abp-7_0.md +++ b/docs/en/Migration-Guides/Abp-7_0.md @@ -101,3 +101,11 @@ See https://github.com/abpframework/abp/pull/13644 for more info. We've already done this for our themes. See https://github.com/abpframework/abp/pull/13845 for more info. + +## Replaced `BlogPostPublicDto` with `BlogPostCommonDto` + +- In the CMS Kit Module, `BlogPostPublicDto` has been moved to `Volo.CmsKit.Common.Application.Contracts` from `Volo.CmsKit.Public.Application.Contracts` and renamed to `BlogPostCommonDto`. + +- See the [PR#13499](https://github.com/abpframework/abp/pull/13499) for more information. + +> You can ignore this if you don't use CMS Kit Module. \ No newline at end of file From 0d891aca7a3d240defef1bd3bfdf0465313e1e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 29 Nov 2022 16:31:27 +0300 Subject: [PATCH 12/21] Revision for the dependency injection document --- docs/en/Dependency-Injection.md | 53 +++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/docs/en/Dependency-Injection.md b/docs/en/Dependency-Injection.md index bfce8a71bf..147ef1689a 100644 --- a/docs/en/Dependency-Injection.md +++ b/docs/en/Dependency-Injection.md @@ -2,7 +2,7 @@ ABP's Dependency Injection system is developed based on Microsoft's [dependency injection extension](https://medium.com/volosoft/asp-net-core-dependency-injection-best-practices-tips-tricks-c6e9c67f9d96) library (Microsoft.Extensions.DependencyInjection nuget package). So, it's documentation is valid in ABP too. -> While ABP has no core dependency to any 3rd-party DI provider, it's required to use a provider that supports dynamic proxying and some other advanced features to make some ABP features properly work. Startup templates come with Autofac installed. See [Autofac integration](Autofac-Integration.md) document for more information. +> While ABP has no core dependency to any 3rd-party DI provider. However, it's required to use a provider that supports dynamic proxying and some other advanced features to make some ABP features properly work. Startup templates come with [Autofac](https://autofac.org/) installed. See [Autofac integration](Autofac-Integration.md) document for more information. ## Modularity @@ -20,24 +20,24 @@ public class BlogModule : AbpModule ## Conventional Registration -ABP introduces conventional service registration. You need not do anything to register a service by convention. It's automatically done. If you want to disable it, you can set `SkipAutoServiceRegistration` to `true` by overriding the `PreConfigureServices` method. +ABP introduces conventional service registration. You need not do anything to register a service by convention. It's automatically done. If you want to disable it, you can set `SkipAutoServiceRegistration` to `true` in the constructor of your module class. Example: ````C# public class BlogModule : AbpModule { - public override void PreConfigureServices(ServiceConfigurationContext context) + public BlogModule() { SkipAutoServiceRegistration = true; } } ```` -Once you skip auto registration, you should manually register your services. In that case, ``AddAssemblyOf`` extension method can help you to register all your services by convention. Example: +Once you skip the auto registration, you should manually register your services. In that case, ``AddAssemblyOf`` extension method can help you to register all your services by convention. Example: ````c# public class BlogModule : AbpModule { - public override void PreConfigureServices(ServiceConfigurationContext context) + public BlogModule() { SkipAutoServiceRegistration = true; } @@ -105,9 +105,7 @@ Example: [Dependency(ServiceLifetime.Transient, ReplaceServices = true)] public class TaxCalculator { - } - ```` ``Dependency`` attribute has a higher priority than other dependency interfaces if it defines the ``Lifetime`` property. @@ -120,7 +118,6 @@ public class TaxCalculator [ExposeServices(typeof(ITaxCalculator))] public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency { - } ```` @@ -179,7 +176,11 @@ public class MyModule : AbpModule public override void ConfigureServices(ServiceConfigurationContext context) { //Replacing the IConnectionStringResolver service - context.Services.Replace(ServiceDescriptor.Transient()); + context.Services.Replace( + ServiceDescriptor.Transient< + IConnectionStringResolver, + MyConnectionStringResolver + >()); } } ```` @@ -202,7 +203,7 @@ public class TaxAppService : ApplicationService _taxCalculator = taxCalculator; } - public void DoSomething() + public async Task DoSomethingAsync() { //...use _taxCalculator... } @@ -227,7 +228,7 @@ public class MyService : ITransientDependency Logger = NullLogger.Instance; } - public void DoSomething() + public async Task DoSomethingAsync() { //...use Logger to write logs... } @@ -244,17 +245,21 @@ One restriction of property injection is that you cannot use the dependency in y Property injection is also useful when you want to design a base class that has some common services injected by default. If you're going to use constructor injection, all derived classes should also inject depended services into their own constructors which makes development harder. However, be very careful using property injection for non-optional services as it makes it harder to clearly see the requirements of a class. -#### DisablePropertyInjectionAttribute +#### DisablePropertyInjection Attribute -You can use `[DisablePropertyInjection]` attribute on class or properties to disable property injection for the whole class or some specific properties. +You can use `[DisablePropertyInjection]` attribute on classes or their properties to disable property injection for the whole class or some specific properties. ````C# +// Disabling for all properties of the MyService class [DisablePropertyInjection] public class MyService : ITransientDependency { + public ILogger Logger { get; set; } + public ITaxCalculator TaxCalculator { get; set; } } +// Disabling only for the TaxCalculator property public class MyService : ITransientDependency { public ILogger Logger { get; set; } @@ -262,17 +267,21 @@ public class MyService : ITransientDependency [DisablePropertyInjection] public ITaxCalculator TaxCalculator { get; set; } } - ```` ### Resolve Service from IServiceProvider -You may want to resolve a service directly from ``IServiceProvider``. In that case, you can inject IServiceProvider into your class and use ``GetService`` method as shown below: +You may want to resolve a service directly from ``IServiceProvider``. In that case, you can inject `IServiceProvider` into your class and use the ``GetService`` or the `GetRequiredService` method as shown below: ````C# public class MyService : ITransientDependency { - public ILogger Logger { get; set; } + private readonly ITaxCalculator _taxCalculator; + + public MyService(IServiceProvider serviceProvider) + { + _taxCalculator = serviceProvider.GetRequiredService(); + } } ```` @@ -374,15 +383,15 @@ IEnumerable services = _serviceProvider.GetServices Date: Tue, 29 Nov 2022 10:59:24 -0500 Subject: [PATCH 13/21] removed pr section --- .../Community-Articles/2022-11-29-EF-Core-Guarded-Types/POST.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/en/Community-Articles/2022-11-29-EF-Core-Guarded-Types/POST.md b/docs/en/Community-Articles/2022-11-29-EF-Core-Guarded-Types/POST.md index 2d9c21d803..417d4a2731 100644 --- a/docs/en/Community-Articles/2022-11-29-EF-Core-Guarded-Types/POST.md +++ b/docs/en/Community-Articles/2022-11-29-EF-Core-Guarded-Types/POST.md @@ -197,7 +197,6 @@ As in many DDD concepts and patterns, guarded types provides improved safety for ## The Source Code * You can find the full source code of the example application [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreGuardedTypeDemo). -* You can see [this pull request](https://github.com/abpframework/abp-samples/pull/207/files) for the changes I've done after creating the application. ## See Also From 777aca28e3a49012667ee4f722920db57d67982c Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Tue, 29 Nov 2022 23:42:45 +0300 Subject: [PATCH 14/21] Update POST.md --- .../2022-11-28-ef-bulk-operations/POST.md | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md index edf88bfcaa..ac517506ec 100644 --- a/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md +++ b/docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md @@ -9,11 +9,17 @@ With .NET 7, there are two new methods such as `ExecuteUpdate` and `ExecuteDelet You can visit the microsoft example [here](https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#executeupdate-and-executedelete-bulk-updates) about how to use it. -ABP Framework supports Entity Framework Core as its ORM. So, you can use these new methods in your repository classes in your ABP applications without any limitation. -## Usage in Repositories +It can be easily used with the DbContext. -You can use these methods in your repositories as below: +```csharp +await context.Tags.Where(t => t.Text.Contains(".NET")).ExecuteDeleteAsync(); +``` + +## Using with ABP Framework +ABP Framework provides an abstraction over database operations and implements generic repository pattern. So, DbContext can't be accessed outside of [repositories](https://docs.abp.io/en/abp/latest/Repositories). + +You can use the `ExecuteUpdate` and `ExecuteDelete` methods inside a repository. ```csharp public class BookEntityFrameworkCoreRepository : EfCoreRepository, IBookRepository @@ -42,6 +48,24 @@ public class BookEntityFrameworkCoreRepository : EfCoreRepository There is no need to take an action for bulk inserting. You can use the `InsertManyAsync` method of the repository instead of creating a new method for it if you don't have custom logic. It'll use a new bulk inserting feature automatically since it's available in EF Core 7.0. +There is no need to take an action for bulk inserting. You can use the `InsertManyAsync` method of the repository instead of creating a new method for it if you don't have custom logic. It'll use a new bulk inserting feature automatically since it's available in EF Core 7.0. + +```csharp +public class MyDomainService : DomainService +{ + protected IRepository BookRepository { get; } + + public MyDomainService(IRepository bookRepository) + { + BookRepository = bookRepository; + } + + public async Task CreateBooksAsync(List books) + { + // It'll use bulk inserting automatically. + await BookRepository.InsertManyAsync(books); + } +} +``` -> If you use `ExecuteDeleteAsync` or `ExecuteUpdateAsync`, then ABP's soft delete and auditing features can not work. Because these ABP features work with EF Core's change tracking system and these new methods doesn't work with the change tracking system. So, use them carefully. \ No newline at end of file +> If you use `ExecuteDeleteAsync` or `ExecuteUpdateAsync`, then ABP's soft delete and auditing features can not work. Because these ABP features work with EF Core's change tracking system and these new methods doesn't work with the change tracking system. So, use them carefully. From 70b69590a1ab48810bd23a67a4fc19bf4eece142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 30 Nov 2022 09:56:30 +0300 Subject: [PATCH 15/21] Fix AbpLazyServiceProvider.LazyGetRequiredService It should not return null, but throw exception if a service was not found. --- .../Abp/DependencyInjection/AbpLazyServiceProvider.cs | 2 +- .../Abp/DependencyInjection/CachedServiceProviderBase.cs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs index 3aff8a78ad..7a18689dbe 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs @@ -17,7 +17,7 @@ public class AbpLazyServiceProvider : CachedServiceProviderBase, IAbpLazyService public virtual object LazyGetRequiredService(Type serviceType) { - return GetService(serviceType); + return GetRequiredService(serviceType); } public virtual T LazyGetService() diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs index c261d3a3bb..2116ec8fb7 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using Microsoft.Extensions.DependencyInjection; namespace Volo.Abp.DependencyInjection; @@ -22,4 +23,12 @@ public abstract class CachedServiceProviderBase _ => new Lazy(() => ServiceProvider.GetService(serviceType)) ).Value; } + + public virtual object GetRequiredService(Type serviceType) + { + return CachedServices.GetOrAdd( + serviceType, + _ => new Lazy(() => ServiceProvider.GetRequiredService(serviceType)) + ).Value; + } } From 39839ad5dfd532660dcef2b02449bb010d52c4ce Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Wed, 30 Nov 2022 01:59:44 -0500 Subject: [PATCH 16/21] Revision and grammar fix --- .../2022-11-29-EF-Core-Guarded-Types/POST.md | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/en/Community-Articles/2022-11-29-EF-Core-Guarded-Types/POST.md b/docs/en/Community-Articles/2022-11-29-EF-Core-Guarded-Types/POST.md index 417d4a2731..53903e1ccc 100644 --- a/docs/en/Community-Articles/2022-11-29-EF-Core-Guarded-Types/POST.md +++ b/docs/en/Community-Articles/2022-11-29-EF-Core-Guarded-Types/POST.md @@ -1,6 +1,6 @@ # Value generation for DDD guarded types with Entity Framework Core 7.0 -In domain-driven design (DDD), *guarded keys* can improve the type safety of key properties. This is achieved by wrapping the key type in another type which is specific to the use of the key. In this article, I will explain the cases why you may need to use use guarded types and discuss the advantages and limitations when implementing to an ABP application. +In domain-driven design (DDD), *guarded keys* can improve the type safety of key properties. This is achieved by wrapping the key type in another type which is specific to the use of the key. In this article, I will explain the cases why you may need to use guarded types and discuss the advantages and limitations when implementing to an ABP application. > You can find the source code of the example application [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreGuardedTypeDemo). @@ -30,14 +30,16 @@ public class ProductAppService : MyProductStoreAppService, IProductAppService } ```` -While the sample demonstrates a very simple mistake, whilst the business logic get more complex, it is easier to come across mistakes similar to this one. The next section offers using guarded types to prevent these kind of problems as a solution to the problem. +While the sample demonstrates a very simple mistake, it is easier to come across similar mistakes when the business logic gets more complex especially when you are using methods with **multiple foreign key arguments**. The next section offers using guarded types to prevent these kind of problems as a solution to the problem. ## The Solution -With EFCore7, key properties can be guarded with type safety. To use that, update your aggreagate root or entity unique identifier with a complex object. +Strongly-typed IDs (*guarded keys*) is a DDD approach to address this problem. One of the main problems with .NET users was handling the persisting these objects. With EFCore7, key properties can be guarded with type safety seamlessly. + +To use guarded keys, update your aggregate root or entity unique identifier with a complex type to overcome *primitive obsession*: ````csharp -public struct CategoryId +public readonly struct CategoryId { public CategoryId(Guid value) => Value = value; public Guid Value { get; } @@ -50,8 +52,6 @@ public readonly struct ProductId } ```` -> It is worth considering as with many [Domain-Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design) (DDD) concepts, this improved type safety comes with additional code complexity. - You can now use these keys for your aggregate roots or entities: ```csharp @@ -84,7 +84,9 @@ public class Category : AggregateRoot } ``` -`ProductId` and `CategoryId` guarded key types shown in the sample use `Guid` key values, which means Guid values will be used in the mapped database tables. This is achieved by defining [value converters](https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions) for the types. Override the `ConfigureConventions` method of your DbContext to use the value converters: +`ProductId` and `CategoryId` guarded key types shown in the sample use `Guid` key values, which means Guid values will be used in the mapped database tables. This is achieved by defining [value converters](https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions) for the types. + +Override the `ConfigureConventions` method of your DbContext to use the value converters: ````csharp protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) @@ -133,15 +135,15 @@ public class ProductStoreDataSeedContributor : IDataSeedContributor, ITransientD } ``` -You can also use `integer` as guarded type for your key properties and use [Sequence-based key generation for SQL Server](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#sequence-based-key-generation-for-sql-server). +You can also use `integer` as guarded type for your key properties and use [Sequence-based key generation for SQL Server](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#sequence-based-key-generation-for-sql-server) for value generation. ## Discussions In this section, I will discuss the use cases of guarded types and limitations when implementing to an ABP application. -### Do I need guarded type key properties even if I follow DDD principles? +### Do I need to use guarded types? -While using strongly-typed key properties reduce the chance of unnoticed errors, admittedly it increases the code complexity by adding new types to your solution. If you are already following the best practices of [Domain-Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design), you are aware that **updates** and **creations** of an aggregate are done **over** the aggregate root itself as a whole unit. And entity state changes of an aggregate root can be done using the [domain services](https://docs.abp.io/en/abp/latest/Domain-Services). Domain services should already validate the entity. +If you are already following the best practices of [Domain-Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design), you are aware that **updates** and **creations** of an aggregate are done **over** the aggregate root itself as a whole unit. And entity state changes of an aggregate root should be done using the [domain services](https://docs.abp.io/en/abp/latest/Domain-Services). Domain services should already validate the entity. **Example: Using domain service to update product:** @@ -166,7 +168,7 @@ public class ProductManager : DomainService } ```` -In this sample, domain service validates that both **product** and the **category** entities, passed by the application layer, are valid objects since they are not key properties. However, manual assignment is already in place and more complex the domain logic, higher to miss out mistakes. +In this sample, domain service validates that both **product** and the **category** entities, passed by the application layer, are valid objects since they are not key properties. However, manual assignment is already in place and more complex the domain logic, higher to miss out mistakes. At the end, it will depend on your tolerance level for developer errors comparing to the time you want to spend time on additional code base for guarded types. ### Limitations @@ -192,7 +194,9 @@ var newProduct = await _productRepository.InsertAsync( In this article, I tried to explain DDD guarded types for key properties and value generation for these properties using Entity Framework 7.0 and ABP. -As in many DDD concepts and patterns, guarded types provides improved safety for your code base at the expense of additional code complexity. If you have a large team working on a large scale solution containing complex business logics where key assignments are abundant, I personally suggest using guarded types. +Using strongly-typed key properties reduce the chance of unnoticed errors. Admittedly it increases the code complexity by adding new types to your solution with extra coding, especially if you are using classes as keys. Guarded types provide improved safety for your code base at the expense of additional code complexity as in many DDD concepts and patterns. + +If you have a large team working on a large scale solution containing complex business logics where key assignments are abundant or if you are using methods with multiple foreign key arguments, I personally suggest using guarded types. ## The Source Code From 740312cc6720a07c01446f7a4d07bbaedf6c9e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 30 Nov 2022 10:45:00 +0300 Subject: [PATCH 17/21] Inherit IAbpLazyServiceProvider from IServiceProvider Provide methods without Lazy prefix. --- .../AbpLazyServiceProvider.cs | 46 ++++++++++++++---- .../CachedServiceProviderBase.cs | 10 +--- .../IAbpLazyServiceProvider.cs | 48 ++++++++++++++++++- 3 files changed, 84 insertions(+), 20 deletions(-) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs index 7a18689dbe..8af18416ff 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs @@ -1,15 +1,42 @@ using System; +using Microsoft.Extensions.DependencyInjection; namespace Volo.Abp.DependencyInjection; [ExposeServices(typeof(IAbpLazyServiceProvider))] -public class AbpLazyServiceProvider : CachedServiceProviderBase, IAbpLazyServiceProvider, ITransientDependency +public class AbpLazyServiceProvider : + CachedServiceProviderBase, + IAbpLazyServiceProvider, + ITransientDependency { public AbpLazyServiceProvider(IServiceProvider serviceProvider) : base(serviceProvider) { } + public T GetService(T defaultValue) + { + return (T)GetService(typeof(T), defaultValue); + } + + public object GetService(Type serviceType, object defaultValue) + { + return GetService(serviceType) ?? defaultValue; + } + + public T GetService(Func factory) + { + return (T)GetService(typeof(T), factory); + } + + public object GetService(Type serviceType, Func factory) + { + return CachedServices.GetOrAdd( + serviceType, + _ => new Lazy(() => factory(ServiceProvider)) + ).Value; + } + public virtual T LazyGetRequiredService() { return (T)LazyGetRequiredService(typeof(T)); @@ -17,7 +44,7 @@ public class AbpLazyServiceProvider : CachedServiceProviderBase, IAbpLazyService public virtual object LazyGetRequiredService(Type serviceType) { - return GetRequiredService(serviceType); + return this.GetRequiredService(serviceType); } public virtual T LazyGetService() @@ -30,26 +57,27 @@ public class AbpLazyServiceProvider : CachedServiceProviderBase, IAbpLazyService return GetService(serviceType); } + #region Old Methods + public virtual T LazyGetService(T defaultValue) { - return (T)LazyGetService(typeof(T), defaultValue); + return GetService(defaultValue); } public virtual object LazyGetService(Type serviceType, object defaultValue) { - return LazyGetService(serviceType) ?? defaultValue; + return GetService(serviceType, defaultValue); } public virtual T LazyGetService(Func factory) { - return (T)LazyGetService(typeof(T), factory); + return GetService(factory); } public virtual object LazyGetService(Type serviceType, Func factory) { - return CachedServices.GetOrAdd( - serviceType, - _ => new Lazy(() => factory(ServiceProvider)) - ).Value; + return GetService(serviceType, factory); } + + #endregion } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs index 2116ec8fb7..49da116fea 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.DependencyInjection; namespace Volo.Abp.DependencyInjection; -public abstract class CachedServiceProviderBase +public abstract class CachedServiceProviderBase : IServiceProvider { protected IServiceProvider ServiceProvider { get; } protected ConcurrentDictionary> CachedServices { get; } @@ -23,12 +23,4 @@ public abstract class CachedServiceProviderBase _ => new Lazy(() => ServiceProvider.GetService(serviceType)) ).Value; } - - public virtual object GetRequiredService(Type serviceType) - { - return CachedServices.GetOrAdd( - serviceType, - _ => new Lazy(() => ServiceProvider.GetRequiredService(serviceType)) - ).Value; - } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IAbpLazyServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IAbpLazyServiceProvider.cs index 610bb40dfa..bfe08026ca 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IAbpLazyServiceProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IAbpLazyServiceProvider.cs @@ -2,21 +2,65 @@ namespace Volo.Abp.DependencyInjection; -public interface IAbpLazyServiceProvider +public interface IAbpLazyServiceProvider : IServiceProvider { + T GetService(T defaultValue); + + object GetService(Type serviceType, object defaultValue); + + T GetService(Func factory); + + object GetService(Type serviceType, Func factory); + + #region Old Methods + + /// + /// This method is equivalent of the GetRequiredService method. + /// It does exists for backward compatibility. + /// T LazyGetRequiredService(); + /// + /// This method is equivalent of the GetRequiredService method. + /// It does exists for backward compatibility. + /// object LazyGetRequiredService(Type serviceType); + /// + /// This method is equivalent of the GetService method. + /// It does exists for backward compatibility. + /// T LazyGetService(); + /// + /// This method is equivalent of the GetService method. + /// It does exists for backward compatibility. + /// object LazyGetService(Type serviceType); + /// + /// This method is equivalent of the method. + /// It does exists for backward compatibility. + /// T LazyGetService(T defaultValue); - + + /// + /// This method is equivalent of the method. + /// It does exists for backward compatibility. + /// object LazyGetService(Type serviceType, object defaultValue); + /// + /// This method is equivalent of the method. + /// It does exists for backward compatibility. + /// object LazyGetService(Type serviceType, Func factory); + /// + /// This method is equivalent of the method. + /// It does exists for backward compatibility. + /// T LazyGetService(Func factory); + + #endregion } From 0a061ad4e3b2d5813a44213fb3dfd499ac78e2a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 30 Nov 2022 10:53:08 +0300 Subject: [PATCH 18/21] Equalize IAbpLazyServiceProvider and ITransientCachedServiceProvider To reduce cached service provider service count. --- .../AbpLazyServiceProvider.cs | 29 ++++--------------- .../CachedServiceProviderBase.cs | 26 +++++++++++++++-- .../IAbpLazyServiceProvider.cs | 26 ++++++----------- .../ICachedServiceProvider.cs | 6 ++-- .../ICachedServiceProviderBase.cs | 14 +++++++++ .../ITransientCachedServiceProvider.cs | 4 +-- 6 files changed, 55 insertions(+), 50 deletions(-) create mode 100644 framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProviderBase.cs diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs index 8af18416ff..60d4afd40c 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs @@ -3,6 +3,10 @@ using Microsoft.Extensions.DependencyInjection; namespace Volo.Abp.DependencyInjection; +/// +/// This class is equivalent of the . +/// Use instead of this class, for new projects. +/// [ExposeServices(typeof(IAbpLazyServiceProvider))] public class AbpLazyServiceProvider : CachedServiceProviderBase, @@ -13,29 +17,8 @@ public class AbpLazyServiceProvider : : base(serviceProvider) { } - - public T GetService(T defaultValue) - { - return (T)GetService(typeof(T), defaultValue); - } - - public object GetService(Type serviceType, object defaultValue) - { - return GetService(serviceType) ?? defaultValue; - } - - public T GetService(Func factory) - { - return (T)GetService(typeof(T), factory); - } - public object GetService(Type serviceType, Func factory) - { - return CachedServices.GetOrAdd( - serviceType, - _ => new Lazy(() => factory(ServiceProvider)) - ).Value; - } + #region Old Methods public virtual T LazyGetRequiredService() { @@ -57,8 +40,6 @@ public class AbpLazyServiceProvider : return GetService(serviceType); } - #region Old Methods - public virtual T LazyGetService(T defaultValue) { return GetService(defaultValue); diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs index 49da116fea..21f2b1c516 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Concurrent; -using Microsoft.Extensions.DependencyInjection; namespace Volo.Abp.DependencyInjection; -public abstract class CachedServiceProviderBase : IServiceProvider +public abstract class CachedServiceProviderBase : ICachedServiceProviderBase { protected IServiceProvider ServiceProvider { get; } protected ConcurrentDictionary> CachedServices { get; } @@ -23,4 +22,27 @@ public abstract class CachedServiceProviderBase : IServiceProvider _ => new Lazy(() => ServiceProvider.GetService(serviceType)) ).Value; } + + public T GetService(T defaultValue) + { + return (T)GetService(typeof(T), defaultValue); + } + + public object GetService(Type serviceType, object defaultValue) + { + return GetService(serviceType) ?? defaultValue; + } + + public T GetService(Func factory) + { + return (T)GetService(typeof(T), factory); + } + + public object GetService(Type serviceType, Func factory) + { + return CachedServices.GetOrAdd( + serviceType, + _ => new Lazy(() => factory(ServiceProvider)) + ).Value; + } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IAbpLazyServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IAbpLazyServiceProvider.cs index bfe08026ca..cd5f1da67f 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IAbpLazyServiceProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IAbpLazyServiceProvider.cs @@ -2,18 +2,12 @@ namespace Volo.Abp.DependencyInjection; -public interface IAbpLazyServiceProvider : IServiceProvider +/// +/// This service is equivalent of the . +/// Use instead of this interface, for new projects. +/// +public interface IAbpLazyServiceProvider : ICachedServiceProviderBase { - T GetService(T defaultValue); - - object GetService(Type serviceType, object defaultValue); - - T GetService(Func factory); - - object GetService(Type serviceType, Func factory); - - #region Old Methods - /// /// This method is equivalent of the GetRequiredService method. /// It does exists for backward compatibility. @@ -39,28 +33,26 @@ public interface IAbpLazyServiceProvider : IServiceProvider object LazyGetService(Type serviceType); /// - /// This method is equivalent of the method. + /// This method is equivalent of the method. /// It does exists for backward compatibility. /// T LazyGetService(T defaultValue); /// - /// This method is equivalent of the method. + /// This method is equivalent of the method. /// It does exists for backward compatibility. /// object LazyGetService(Type serviceType, object defaultValue); /// - /// This method is equivalent of the method. + /// This method is equivalent of the method. /// It does exists for backward compatibility. /// object LazyGetService(Type serviceType, Func factory); /// - /// This method is equivalent of the method. + /// This method is equivalent of the method. /// It does exists for backward compatibility. /// T LazyGetService(Func factory); - - #endregion } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProvider.cs index 073daa135a..4a923901f9 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProvider.cs @@ -1,6 +1,4 @@ -using System; - -namespace Volo.Abp.DependencyInjection; +namespace Volo.Abp.DependencyInjection; /// /// Provides services by caching the resolved services. @@ -8,7 +6,7 @@ namespace Volo.Abp.DependencyInjection; /// This service's lifetime is scoped and it should be used /// for a limited scope. /// -public interface ICachedServiceProvider : IServiceProvider +public interface ICachedServiceProvider : ICachedServiceProviderBase { } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProviderBase.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProviderBase.cs new file mode 100644 index 0000000000..2e15a45a46 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProviderBase.cs @@ -0,0 +1,14 @@ +using System; + +namespace Volo.Abp.DependencyInjection; + +public interface ICachedServiceProviderBase : IServiceProvider +{ + T GetService(T defaultValue); + + object GetService(Type serviceType, object defaultValue); + + T GetService(Func factory); + + object GetService(Type serviceType, Func factory); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ITransientCachedServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ITransientCachedServiceProvider.cs index 55c43e7c94..7b57b54e07 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ITransientCachedServiceProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ITransientCachedServiceProvider.cs @@ -1,5 +1,3 @@ -using System; - namespace Volo.Abp.DependencyInjection; /// @@ -8,7 +6,7 @@ namespace Volo.Abp.DependencyInjection; /// This service's lifetime is transient. /// for the one with scoped lifetime. /// -public interface ITransientCachedServiceProvider : IServiceProvider +public interface ITransientCachedServiceProvider : ICachedServiceProviderBase { } \ No newline at end of file From 184e50e6e6f109477b8c7e51699da014aaab5f3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 30 Nov 2022 11:13:23 +0300 Subject: [PATCH 19/21] Added "Cached Service Providers" section to the document --- docs/en/Dependency-Injection.md | 33 +++++++++++++++++++ .../CachedServiceProvider.cs | 1 - 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/docs/en/Dependency-Injection.md b/docs/en/Dependency-Injection.md index 147ef1689a..c59b371018 100644 --- a/docs/en/Dependency-Injection.md +++ b/docs/en/Dependency-Injection.md @@ -403,6 +403,39 @@ using (var scope = _serviceProvider.CreateScope()) Both services are released when the created scope is disposed (at the end of the `using` block). +### Cached Service Providers + +ABP provides two special services to optimize resolving services from `IServiceProvider`. `ICachedServiceProvider` and `ITransientCachedServiceProvider` both inherits from the `IServiceProvider` interface and internally caches the resolved services, so you get the same service instance even if you resolve a service multiple times. + +The main difference is the `ICachedServiceProvider` is itself registered as scoped, while the `ITransientCachedServiceProvider` is registered as transient to the dependency injection system. + +The following example injects the `ICachedServiceProvider` service and resolves a service in the `DoSomethingAsync` method: + +````csharp +public class MyService : ITransientDependency +{ + private readonly ICachedServiceProvider _serviceProvider; + + public MyService(ICachedServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public async Task DoSomethingAsync() + { + var taxCalculator = _serviceProvider.GetRequiredService(); + // TODO: Use the taxCalculator + } +} + +```` + +With such a usage, you don't need to deal with creating service scopes and disposing the resolved services (as explained in the *Releasing/Disposing Services* section above). Because all the services resolved from the `ICachedServiceProvider` will be released once the service scope of the `MyService` instance is disposed. Also, you don't need to care about memory leaks (because of creating too many `ITaxCalculator` instances if we call `DoSomethingAsync` too many times), because only one `ITaxCalculator` instance is created, and it is reused. + +Use `ICachedServiceProvider` (instead of `ITransientCachedServiceProvider`) unless you need to create the service cache per usage. `ITransientCachedServiceProvider` guarantees that the created service instances are not shared with any other service, even they are in the same service scope. The services resolved from `ICachedServiceProvider` are shared with other services in the same service scope (in the same HTTP Request, for example), so it can be thought as more optimized. + +> ABP Framework also provides the `IAbpLazyServiceProvider` service. It does exists for backward compatibility and works exactly same with the `ITransientCachedServiceProvider` service. So, use the `ITransientCachedServiceProvider` since the `IAbpLazyServiceProvider` might be removed in future ABP versions. + ## Advanced Features ### IServiceCollection.OnRegistred Event diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProvider.cs index 870e1f8faf..ef0fed3541 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProvider.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; namespace Volo.Abp.DependencyInjection; From 84750d9d74bb85020a47f8a084c211705d359e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 30 Nov 2022 11:15:48 +0300 Subject: [PATCH 20/21] Update AbpLazyServiceProvider.cs --- .../Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs index 60d4afd40c..a180592a01 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs @@ -18,8 +18,6 @@ public class AbpLazyServiceProvider : { } - #region Old Methods - public virtual T LazyGetRequiredService() { return (T)LazyGetRequiredService(typeof(T)); @@ -59,6 +57,4 @@ public class AbpLazyServiceProvider : { return GetService(serviceType, factory); } - - #endregion } From 790c0bb763b7d621b6aebb794a978e83928f9a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 30 Nov 2022 11:22:10 +0300 Subject: [PATCH 21/21] Update Dependency-Injection.md --- docs/en/Dependency-Injection.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/en/Dependency-Injection.md b/docs/en/Dependency-Injection.md index c59b371018..47e60700bc 100644 --- a/docs/en/Dependency-Injection.md +++ b/docs/en/Dependency-Injection.md @@ -432,6 +432,8 @@ public class MyService : ITransientDependency With such a usage, you don't need to deal with creating service scopes and disposing the resolved services (as explained in the *Releasing/Disposing Services* section above). Because all the services resolved from the `ICachedServiceProvider` will be released once the service scope of the `MyService` instance is disposed. Also, you don't need to care about memory leaks (because of creating too many `ITaxCalculator` instances if we call `DoSomethingAsync` too many times), because only one `ITaxCalculator` instance is created, and it is reused. +Since `ICachedServiceProvider` and `ITransientCachedServiceProvider` extends the standard `IServiceProvider` interface, you can use all the extension method of the `IServiceProvider` interface on them. In addition, they provides some other methods to provide a default value or a factory method for the services that are not found (that means not registered to the dependency injection system). Notice that the default value (or the value returned from your factory method) is also cached and reused. + Use `ICachedServiceProvider` (instead of `ITransientCachedServiceProvider`) unless you need to create the service cache per usage. `ITransientCachedServiceProvider` guarantees that the created service instances are not shared with any other service, even they are in the same service scope. The services resolved from `ICachedServiceProvider` are shared with other services in the same service scope (in the same HTTP Request, for example), so it can be thought as more optimized. > ABP Framework also provides the `IAbpLazyServiceProvider` service. It does exists for backward compatibility and works exactly same with the `ITransientCachedServiceProvider` service. So, use the `ITransientCachedServiceProvider` since the `IAbpLazyServiceProvider` might be removed in future ABP versions.