diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-entity-framework-core-migration-dialog.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-entity-framework-core-migration-dialog.png index c96217ac47..7edec8f3d9 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-add-entity-framework-core-migration-dialog.png and b/docs/en/tutorials/modular-crm/images/abp-studio-add-entity-framework-core-migration-dialog.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-entity-framework-core-migration.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-entity-framework-core-migration.png index 1629f541d8..857df5f25d 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-add-entity-framework-core-migration.png and b/docs/en/tutorials/modular-crm/images/abp-studio-add-entity-framework-core-migration.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-new-dd-module.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-new-dd-module.png deleted file mode 100644 index f8a701770e..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-add-new-dd-module.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-new-ddd-module.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-new-ddd-module.png new file mode 100644 index 0000000000..ed11a95f57 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-add-new-ddd-module.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-new-empty-module.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-new-empty-module.png index 8815d15c44..53d453cd37 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-add-new-empty-module.png and b/docs/en/tutorials/modular-crm/images/abp-studio-add-new-empty-module.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-new-folder-command.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-new-folder-command.png index 6557599a4e..56c861d4fd 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-add-new-folder-command.png and b/docs/en/tutorials/modular-crm/images/abp-studio-add-new-folder-command.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-new-package.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-new-package.png index de9e1aa8c5..7e28d708bf 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-add-new-package.png and b/docs/en/tutorials/modular-crm/images/abp-studio-add-new-package.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-2.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-2.png index 47a5671f7f..308d32c6ad 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-2.png and b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-2.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-3.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-3.png index e79b72747f..dc51826734 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-3.png and b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-3.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-4.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-4.png index fa3b5c4e23..b29850cce6 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-4.png and b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-4.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-5.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-5.png index 4a8ef0ba58..42d7c96cb2 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-5.png and b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-5.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-6.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-6.png new file mode 100644 index 0000000000..954ea41060 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-6.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-7.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-7.png new file mode 100644 index 0000000000..3dea701062 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-7.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-dialog-5.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-dialog-5.png new file mode 100644 index 0000000000..ad78c4eee7 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-dialog-5.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-dialog-6.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-dialog-6.png new file mode 100644 index 0000000000..bd1c968b03 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-dialog-6.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference.png index 2cbd244a4b..a6870e4c0e 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference.png and b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-added-ddd-contracts-package.png b/docs/en/tutorials/modular-crm/images/abp-studio-added-ddd-contracts-package.png new file mode 100644 index 0000000000..f451a550d8 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-added-ddd-contracts-package.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-build-and-restart-application.png b/docs/en/tutorials/modular-crm/images/abp-studio-build-and-restart-application.png index 010851ee05..889a4251cd 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-build-and-restart-application.png and b/docs/en/tutorials/modular-crm/images/abp-studio-build-and-restart-application.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-entity-framework-core-add-migration-order.png b/docs/en/tutorials/modular-crm/images/abp-studio-entity-framework-core-add-migration-order.png index 1e247f069b..b739e59373 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-entity-framework-core-add-migration-order.png and b/docs/en/tutorials/modular-crm/images/abp-studio-entity-framework-core-add-migration-order.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-entity-framework-core-update-database.png b/docs/en/tutorials/modular-crm/images/abp-studio-entity-framework-core-update-database.png index bece8dfc12..b28b4e48a0 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-entity-framework-core-update-database.png and b/docs/en/tutorials/modular-crm/images/abp-studio-entity-framework-core-update-database.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-graph-build.png b/docs/en/tutorials/modular-crm/images/abp-studio-graph-build.png index 11855d792e..8930bdd062 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-graph-build.png and b/docs/en/tutorials/modular-crm/images/abp-studio-graph-build.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-import-module-for-ordering.png b/docs/en/tutorials/modular-crm/images/abp-studio-import-module-for-ordering.png index 94ab70d255..13aab5d477 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-import-module-for-ordering.png and b/docs/en/tutorials/modular-crm/images/abp-studio-import-module-for-ordering.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-import-module-ordering.png b/docs/en/tutorials/modular-crm/images/abp-studio-import-module-ordering.png index e62c0dc315..8956033762 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-import-module-ordering.png and b/docs/en/tutorials/modular-crm/images/abp-studio-import-module-ordering.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-import-module.png b/docs/en/tutorials/modular-crm/images/abp-studio-import-module.png index 673d303307..7a63894709 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-import-module.png and b/docs/en/tutorials/modular-crm/images/abp-studio-import-module.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-new-folder-dialog.png b/docs/en/tutorials/modular-crm/images/abp-studio-new-folder-dialog.png index f726332a37..e1e3065b9c 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-new-folder-dialog.png and b/docs/en/tutorials/modular-crm/images/abp-studio-new-folder-dialog.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-open-in-explorer.png b/docs/en/tutorials/modular-crm/images/abp-studio-open-in-explorer.png index ce052d2b17..99c6d26b20 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-open-in-explorer.png and b/docs/en/tutorials/modular-crm/images/abp-studio-open-in-explorer.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-open-with-visual-studio-main-app.png b/docs/en/tutorials/modular-crm/images/abp-studio-open-with-visual-studio-main-app.png index 1f879ed795..f4131cfb69 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-open-with-visual-studio-main-app.png and b/docs/en/tutorials/modular-crm/images/abp-studio-open-with-visual-studio-main-app.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-open-with-visual-studio.png b/docs/en/tutorials/modular-crm/images/abp-studio-open-with-visual-studio.png index ba96d6f488..2b1bac2db6 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-open-with-visual-studio.png and b/docs/en/tutorials/modular-crm/images/abp-studio-open-with-visual-studio.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser.png b/docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser.png new file mode 100644 index 0000000000..304027af9e Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-graph-build.png b/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-graph-build.png index ececabbf01..10d210a0be 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-graph-build.png and b/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-graph-build.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-initial-product-page.png b/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-initial-product-page.png index 480da27d7e..269c55f8b4 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-initial-product-page.png and b/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-initial-product-page.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-orders-page.png b/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-orders-page.png index f3e9896e8f..fbd419d145 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-orders-page.png and b/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-orders-page.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-order-execute.png b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-order-execute.png new file mode 100644 index 0000000000..b0f384f15d Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-order-execute.png differ diff --git a/docs/en/tutorials/modular-crm/images/sql-server-orders-database-table-filled.png b/docs/en/tutorials/modular-crm/images/sql-server-orders-database-table-filled.png new file mode 100644 index 0000000000..75c812593c Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/sql-server-orders-database-table-filled.png differ diff --git a/docs/en/tutorials/modular-crm/images/sql-server-orders-table-content.png b/docs/en/tutorials/modular-crm/images/sql-server-orders-table-content.png deleted file mode 100644 index 1bc432c650..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/sql-server-orders-table-content.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-ordering-contracts.png b/docs/en/tutorials/modular-crm/images/visual-studio-ordering-contracts.png new file mode 100644 index 0000000000..d9206f6630 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/visual-studio-ordering-contracts.png differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-ordering-controller.png b/docs/en/tutorials/modular-crm/images/visual-studio-ordering-controller.png deleted file mode 100644 index 56a1dad5b1..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/visual-studio-ordering-controller.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/part-02.md b/docs/en/tutorials/modular-crm/part-02.md index 14560b3766..de8fd69f92 100644 --- a/docs/en/tutorials/modular-crm/part-02.md +++ b/docs/en/tutorials/modular-crm/part-02.md @@ -41,7 +41,7 @@ We will use the *DDD Module* template for the Product module and the *Empty Modu Right-click the `modules` folder on the *Solution Explorer* panel, and select the *Add* -> *New Module* -> *DDD Module* command: -![abp-studio-add-new-dd-module](images/abp-studio-add-new-dd-module.png) +![abp-studio-add-new-ddd-module](images/abp-studio-add-new-ddd-module.png) This command opens a new dialog to define the properties of the new module. You can use the following values to create a new module named `ModularCrm.Products`: diff --git a/docs/en/tutorials/modular-crm/part-05.md b/docs/en/tutorials/modular-crm/part-05.md index f7d624bcbf..69a9223882 100644 --- a/docs/en/tutorials/modular-crm/part-05.md +++ b/docs/en/tutorials/modular-crm/part-05.md @@ -232,9 +232,215 @@ After the operation completes, you can check your database to see the new `Order ![sql-server-products-database-table](images/sql-server-orders-database-table.png) -## Creating the User Interface +## Creating the Application Service + +We will create an application service to manage the `Order` entities. + +### Defining the Application Service Contract + +We're gonna create the `IOrderAppService` interface under the `ModularCrm.Ordering.Contracts` project but first, we need to add `Volo.Abp.Ddd.Application.Contracts` package reference. + +Right-click the `ModularCrm.Ordering.Contracts` project in the *Solution Explorer* panel and select the *Add Package Reference* command: + +![abp-studio-add-package-reference-6](images/abp-studio-add-package-reference-6.png) + +This command opens a dialog to add a new package reference: + +![abp-studio-add-package-reference-dialog-5](images/abp-studio-add-package-reference-dialog-5.png) + +Select the *NuGet* tab, type `Volo.Abp.Ddd.Application.Contracts` as the *Package name* and write the version of the package you want to install. Please be sure that you are installing the same version as the other ABP packages you are already using. + +Click the *Ok* button. Now you can check the *Packages* under the `ModularCrm.Ordering.Contracts` project *Dependencies* to see the `Volo.Abp.Ddd.Application.Contracts` package is installed: + +![abp-studio-added-ddd-contracts-package](images/abp-studio-added-ddd-contracts-package.png) + +Return to your IDE, open the `ModularCrm.Ordering` module's .NET solution and create an `IOrderAppService` interface under the `Services` folder for `ModularCrm.Ordering.Contracts` project: + +````csharp +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace ModularCrm.Ordering.Contracts.Services; + +public interface IOrderAppService : IApplicationService +{ + Task> GetListAsync(); + Task CreateAsync(OrderCreationDto input); +} +```` + +### Defining Data Transfer Objects + +The `GetListAsync` and `CreateAsync` methods will use data transfer objects (DTOs) to communicate with the client. We will create two DTO classes for that purpose. + +Create a `OrderCreationDto` class under the `ModularCrm.Ordering.Contracts` project: + +````csharp +using System; +using System.ComponentModel.DataAnnotations; + +namespace ModularCrm.Ordering.Contracts.Services; + +public class OrderCreationDto +{ + [Required] + [StringLength(150)] + public string CustomerName { get; set; } + + [Required] + public Guid ProductId { get; set; } +} +```` + +Create a `OrderDto` class under the `ModularCrm.Ordering.Contracts` project: + +````csharp +using System; +using ModularCrm.Ordering.Contracts.Enums; + +namespace ModularCrm.Ordering.Contracts.Services; + +public class OrderDto +{ + public Guid Id { get; set; } + public string CustomerName { get; set; } + public Guid ProductId { get; set; } + public OrderState State { get; set; } +} +```` + +The new files under the `ModularCrm.Ordering.Contracts` project should be like the following figure: + +![visual-studio-ordering-contracts](images/visual-studio-ordering-contracts.png) + +### Implementing the Application Service + +Before creating the `OrderAppService` class, we need to add the `Volo.Abp.Ddd.Application` and `Volo.Abp.AutoMapper` packages to the Ordering module. + +Right-click the `ModularCrm.Ordering` package in the *Solution Explorer* panel and select the *Add Package Reference* command: + +![abp-studio-add-package-reference-7](images/abp-studio-add-package-reference-7.png) + +This command opens a dialog to add a new package reference: + +![abp-studio-add-package-reference-dialog-6](images/abp-studio-add-package-reference-dialog-6.png) + +Select the *NuGet* tab, enter `Volo.Abp.Ddd.Application` as the *Package name*, and specify the version of the package you wish to install. Afterward, you can add the `Volo.Abp.AutoMapper` package in the same dialog. Ensure that you install the same version as the other ABP packages you are already using. + +Click the *OK* button. Now we should configure the *AutoMapper* object to map the `Order` entity to the `OrderDto` object. We will create a class named `OrderingApplicationAutoMapperProfile` under the `ModularCrm.Ordering` project: + +````csharp +using AutoMapper; +using ModularCrm.Ordering.Contracts.Services; +using ModularCrm.Ordering.Entities; + +namespace ModularCrm.Ordering; + +public class OrderingApplicationAutoMapperProfile : Profile +{ + public OrderingApplicationAutoMapperProfile() + { + CreateMap(); + } +} +```` -Since this is a non-layered module, we can use entities and repositories directly on the user interface. If you think that is not a good practice, then use the layered module template as we've already done for the *Products* module. But for the Ordering module, we will keep it very simple for this tutorial to show it is also possible. +And configure the `OrderingWebModule` class to use the `OrderingApplicationAutoMapperProfile`: + +````csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + //Add these lines + context.Services.AddAutoMapperObjectMapper(); + Configure(options => + { + options.AddMaps(validate: true); + }); +} +```` + +Now, we can implement the `IOrderAppService` interface. Create an `OrderAppService` class under the `Services` folder of the `ModularCrm.Ordering` project: + +````csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using ModularCrm.Ordering.Contracts.Enums; +using ModularCrm.Ordering.Contracts.Services; +using ModularCrm.Ordering.Entities; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; + +namespace ModularCrm.Ordering.Services; + +public class OrderAppService : ApplicationService, IOrderAppService +{ + private readonly IRepository _orderRepository; + + public OrderAppService(IRepository orderRepository) + { + _orderRepository = orderRepository; + ObjectMapperContext = typeof(OrderingWebModule); + } + + public async Task> GetListAsync() + { + var orders = await _orderRepository.GetListAsync(); + return ObjectMapper.Map, List>(orders); + } + + public async Task CreateAsync(OrderCreationDto input) + { + var order = new Order + { + CustomerName = input.CustomerName, + ProductId = input.ProductId, + State = OrderState.Placed + }; + + await _orderRepository.InsertAsync(order); + } +} +```` + +Open the `ModularCrmWebModule` class in the main application's solution (the `ModularCrm` solution), find the `ConfigureAutoApiControllers` method and add the following lines inside that method: + +````csharp +private void ConfigureAutoApiControllers() +{ + Configure(options => + { + options.ConventionalControllers.Create(typeof(ModularCrmApplicationModule).Assembly); + options.ConventionalControllers.Create(typeof(ProductsApplicationModule).Assembly); + + //ADD THE FOLLOWING LINE: + options.ConventionalControllers.Create(typeof(OrderingWebModule).Assembly); + }); +} +```` + +### Creating Example Orders + +This section will create a few example orders using the [Swagger UI](../../framework/api-development/swagger.md). Thus, we will have some sample orders to show on the UI. + +Now, right-click the `ModularCrm` under the `main` folder in the Solution Explorer panel and select the *Dotnet CLI* -> *Graph Build* command. This will ensure that the order module and the main application are built and ready to run. + +After the build process completes, open the Solution Runner panel and click the *Play* button near the solution root. Once the `ModularCrm.Web` application runs, we can right-click it and select the *Browse* command to open the user interface. + +Once you see the user interface of the web application, type `/swagger` at the end of the URL to open the Swagger UI. If you scroll down, you should see the `Orders` API: + +![abp-studio-ordering-swagger-ui-in-browser](images/abp-studio-ordering-swagger-ui-in-browser.png) + +Expand the `/api/app/order` API and click the *Try it out* button. Then, create a few orders by filling in the request body and clicking the *Execute* button: + +![abp-studio-swagger-ui-create-order-execute](images/abp-studio-swagger-ui-create-order-execute.png) + +If you check the database, you should see the entities created in the *Orders* table: + +![sql-server-orders-database-table-filled](images/sql-server-orders-database-table-filled.png) + +## Creating the User Interface ### Creating a `_ViewImports.cshtml` File @@ -257,28 +463,26 @@ Create an `Orders` folder under the `Pages` folder and add an `Index.cshtml` Raz ````csharp using Microsoft.AspNetCore.Mvc.RazorPages; -using ModularCrm.Ordering.Entities; -using System; using System.Collections.Generic; using System.Threading.Tasks; -using Volo.Abp.Domain.Repositories; +using ModularCrm.Ordering.Contracts.Services; namespace ModularCrm.Ordering.Pages.Orders { public class IndexModel : PageModel { - public List Orders { get; set; } + public List Orders { get; set; } - private readonly IRepository _orderRepository; + private readonly IOrderAppService _orderAppService; - public IndexModel(IRepository orderRepository) + public IndexModel(IOrderAppService orderAppService) { - _orderRepository = orderRepository; + _orderAppService = orderAppService; } public async Task OnGetAsync() { - Orders = await _orderRepository.GetListAsync(); + Orders = await _orderAppService.GetListAsync(); } } } @@ -310,14 +514,6 @@ Here, we are injecting a repository to query `Order` entities from the database This page shows a list of orders on the UI. We haven't created a UI to create new orders, and we will not do it to keep this tutorial simple. If you want to learn how to create advanced UIs with ABP, please follow the [Book Store tutorial](../book-store/index.md). -### Creating Some Sample Data - -You can open the database and manually create a few order records to show on the UI: - -![sql-server-orders-table-content](images/sql-server-orders-table-content.png) - -You can get `ProductId` values from the `Products` table and [generate](https://www.guidgenerator.com/) some random GUIDs for other GUID fields. - ### Building the Application Now, we will run the application to see the result. Please stop the application if it is already running. Then open the *Solution Runner* panel, right-click the `ModularCrm.Web` application, and select the *Build* -> *Graph Build* command: @@ -375,11 +571,13 @@ namespace ModularCrm.Ordering `OrderingMenuContributor` implements the `IMenuContributor` interface, which forces us to implement the `ConfigureMenuAsync` method. In that method, we can manipulate the menu items (add new menu items, remove existing menu items or change the properties of existing menu items). The `ConfigureMenuAsync` method is executed whenever the menu is rendered on the UI, so you can dynamically decide how to manipulate the menu items. -After creating such a class, we should configure the `AbpNavigationOptions` to add that contributor. Open the `OrderingWebModule` class in the `ModularCrm.Ordering` project and add the following configuration code into the `ConfigureServices` method (if there is no `ConfigureServices` method, first create it as shown below): +After creating such a class, we should configure the `AbpNavigationOptions` to add that contributor. Open the `OrderingWebModule` class in the `ModularCrm.Ordering` project and add the following configuration code into the `ConfigureServices` method: ````csharp public override void ConfigureServices(ServiceConfigurationContext context) { + //... other configurations + Configure(options => { options.MenuContributors.Add(new OrderingMenuContributor()); diff --git a/docs/en/tutorials/modular-crm/part-06.md b/docs/en/tutorials/modular-crm/part-06.md index 2fd5d82f34..eca859a8f5 100644 --- a/docs/en/tutorials/modular-crm/part-06.md +++ b/docs/en/tutorials/modular-crm/part-06.md @@ -145,68 +145,121 @@ ABP Studio adds the package reference and arranges the [module](../../framework/ Now, we can inject and use `IProductIntegrationService` in the Ordering module codebase. -Open the `IndexModel` class (the `IndexModel.cshtml.cs` file under the `Pages/Orders` folder of the `ModularCrm.Ordering` project of the `ModularCrm.Ordering` .NET solution) and change its content as like the following code block: +Open the `OrderAppService` class (the `OrderAppService.cs` file under the `Services` folder of the `ModularCrm.Ordering` project of the `ModularCrm.Ordering` .NET solution) and change its content as like the following code block: ````csharp -using Microsoft.AspNetCore.Mvc.RazorPages; -using ModularCrm.Ordering.Entities; -using ModularCrm.Products.Integration; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using ModularCrm.Ordering.Contracts.Enums; +using ModularCrm.Ordering.Contracts.Services; +using ModularCrm.Ordering.Entities; +using ModularCrm.Products.Integration; +using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; -namespace ModularCrm.Ordering.Pages.Orders +namespace ModularCrm.Ordering.Services; + +public class OrderAppService : ApplicationService, IOrderAppService { - public class IndexModel : PageModel + private readonly IRepository _orderRepository; + private readonly IProductIntegrationService _productIntegrationService; + + public OrderAppService( + IRepository orderRepository, + IProductIntegrationService productIntegrationService) { - public List Orders { get; set; } - - // Define a dictionary for Id -> Name conversion - public Dictionary ProductNames { get; set; } + _orderRepository = orderRepository; + _productIntegrationService = productIntegrationService; + ObjectMapperContext = typeof(OrderingWebModule); + } - private readonly IRepository _orderRepository; - private readonly IProductIntegrationService _productIntegrationService; + public async Task> GetListAsync() + { + var orders = await _orderRepository.GetListAsync(); - public IndexModel( - IRepository orderRepository, - IProductIntegrationService productIntegrationService) + // Prepare a list of products we need + var productIds = orders.Select(o => o.ProductId).Distinct().ToList(); + var products = (await _productIntegrationService + .GetProductsByIdsAsync(productIds)) + .ToDictionary(p => p.Id, p => p.Name); + + var result = ObjectMapper.Map, List>(orders); + result = result.Select(a => { - _orderRepository = orderRepository; - _productIntegrationService = productIntegrationService; - } + a.ProductName = products[a.ProductId]; + return a; + }) + .ToList(); + + return result; + } - public async Task OnGetAsync() + public async Task CreateAsync(OrderCreationDto input) + { + var order = new Order { - // Getting the orders from this module's database - Orders = await _orderRepository.GetListAsync(); + CustomerName = input.CustomerName, + ProductId = input.ProductId, + State = OrderState.Placed + }; + + await _orderRepository.InsertAsync(order); + } +} +```` - // Prepare a list of products we need - var productIds = Orders.Select(o => o.ProductId).Distinct().ToList(); +And also, open the `OrderDto` class (the `OrderDto.cs` file under the `Services` folder of the `ModularCrm.Ordering.Contracts` project of the `ModularCrm.Ordering` .NET solution) and add a `ProductName` property to it: - // Request the related products from the product integration service - var products = await _productIntegrationService - .GetProductsByIdsAsync(productIds); +````csharp +using System; +using ModularCrm.Ordering.Contracts.Enums; - // Create a dictionary to get a product name easily by its id - ProductNames = products.ToDictionary(p => p.Id, p => p.Name); - } +namespace ModularCrm.Ordering.Contracts.Services; + +public class OrderDto +{ + public Guid Id { get; set; } + public string CustomerName { get; set; } + public Guid ProductId { get; set; } + public string ProductName { get; set; } // New property + public OrderState State { get; set; } +} +```` + +Lastly, open the `OrderingApplicationAutoMapperProfile` class (the `OrderingApplicationAutoMapperProfile.cs` file under the `Services` folder of the `ModularCrm.Ordering` project of the `ModularCrm.Ordering` .NET solution) and ignore the `ProductName` property in the mapping configuration: + +````csharp +using AutoMapper; +using ModularCrm.Ordering.Contracts.Services; +using ModularCrm.Ordering.Entities; +using Volo.Abp.AutoMapper; + +namespace ModularCrm.Ordering; + +public class OrderingApplicationAutoMapperProfile : Profile +{ + public OrderingApplicationAutoMapperProfile() + { + CreateMap() + .Ignore(x => x.ProductName); // New line } } ```` Let's see what we've changed: -* We have defined a `ProductNames` dictionary. We will use it on the UI to convert product IDs to product names. We are filling that dictionary with products from the product integration service. +* We've added a `ProductName` property to the `OrderDto` class to store the product name. * Injecting the `IProductIntegrationService` interface so we can use it to request products. -* In the `OnGetAsync` method; +* In the `GetListAsync` method; * First getting the orders from the ordering module's database just like done before. * Next, we are preparing a unique list of product IDs since the `GetProductsByIdsAsync` method requests it. * Then we are calling the `IProductIntegrationService.GetProductsByIdsAsync` method to get a `List` object. * In the last line, we are converting the product list to a dictionary, where the key is `Guid Id` and the value is `string Name`. That way, we can easily find a product's name with its ID. + * Finally, we are mapping the orders to `OrderDto` objects and setting the product name by looking up the product ID in the dictionary. -Open the `Index.cshtml` file, and change the `@order.ProductId` part by `@Model.ProductNames[order.ProductId]` to write the product name instead of the product ID. The final `Index.cshtml` content should be the following: +Open the `Index.cshtml` file, and change the `@order.ProductId` part by `@Model.ProductName` to write the product name instead of the product ID. The final `Index.cshtml` content should be the following: ````html @page @@ -219,11 +272,11 @@ Open the `Index.cshtml` file, and change the `@order.ProductId` part by `@Model. @foreach (var order in Model.Orders) { - - Customer: @order.CustomerName
- Product: @Model.ProductNames[order.ProductId]
- State: @order.State -
+ + Customer: @order.CustomerName
+ Product: @order.ProductName
+ State: @order.State +
}
@@ -241,4 +294,3 @@ In the way explained in this section, you can easily create integration services > **Design Tip** > > It is suggested that you keep that type of communication to a minimum and not couple your modules with each other. It can make your solution complicated and may also decrease your system performance. When you need to do it, think about performance and try to make some optimizations. For example, if the Ordering module frequently needs product data, you can use a kind of [cache layer](../../framework/fundamentals/caching.md), so it doesn't make frequent requests to the Products module. Especially if you consider converting your system to a microservice solution in the future, too many direct integration API calls can be a performance bottleneck. - diff --git a/docs/en/tutorials/modular-crm/part-07.md b/docs/en/tutorials/modular-crm/part-07.md index 9dc18648bd..de02c08fee 100644 --- a/docs/en/tutorials/modular-crm/part-07.md +++ b/docs/en/tutorials/modular-crm/part-07.md @@ -56,83 +56,87 @@ namespace ModularCrm.Ordering.Contracts.Events ### Using the `IDistributedEventBus` Service -The `IDistributedEventBus` service publishes events to the event bus. Until this point, the Ordering module has no functionality to create new orders. - -In Part 3, we used ABP's Auto HTTP API Controller feature to expose HTTP APIs from application services automatically. In this section, we will create an ASP.NET Core API controller class to create a new order. In that way, you will also see that it is not different from creating a regular ASP.NET Core controller. - -Open the `ModularCrm.Ordering` module's .NET solution, create a `Controllers` folder in the `ModularCrm.Ordering` project and place a controller class named `OrdersController` in that new folder. The final folder structure should be like that: - -![visual-studio-ordering-controller](images/visual-studio-ordering-controller.png) - -Here is the full `OrdersController` class: +The `IDistributedEventBus` service publishes events to the event bus. Until this point, the Ordering module has no functionality to create new orders. Let's change that and place an order, for that purpose open the `ModularCrm.Ordering` module's .NET solution, and update the `OrderAppService` as follows: ````csharp -using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using ModularCrm.Ordering.Contracts.Enums; using ModularCrm.Ordering.Contracts.Events; +using ModularCrm.Ordering.Contracts.Services; using ModularCrm.Ordering.Entities; -using System; -using System.ComponentModel.DataAnnotations; -using System.Threading.Tasks; -using Volo.Abp.AspNetCore.Mvc; +using ModularCrm.Products.Integration; +using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; using Volo.Abp.EventBus.Distributed; -namespace ModularCrm.Ordering.Controllers +namespace ModularCrm.Ordering.Services; + +public class OrderAppService : ApplicationService, IOrderAppService { - [Route("api/orders")] - [ApiController] - public class OrdersController : AbpControllerBase + private readonly IRepository _orderRepository; + private readonly IProductIntegrationService _productIntegrationService; + private readonly IDistributedEventBus _distributedEventBus; + + public OrderAppService( + IRepository orderRepository, + IProductIntegrationService productIntegrationService, + IDistributedEventBus distributedEventBus) { - private readonly IRepository _orderRepository; - private readonly IDistributedEventBus _distributedEventBus; + _orderRepository = orderRepository; + _productIntegrationService = productIntegrationService; + _distributedEventBus = distributedEventBus; + ObjectMapperContext = typeof(OrderingWebModule); + } - public OrdersController( - IRepository orderRepository, - IDistributedEventBus distributedEventBus) - { - _orderRepository = orderRepository; - _distributedEventBus = distributedEventBus; - } + public async Task> GetListAsync() + { + var orders = await _orderRepository.GetListAsync(); + + // Prepare a list of products we need + var productIds = orders.Select(o => o.ProductId).Distinct().ToList(); + var products = (await _productIntegrationService + .GetProductsByIdsAsync(productIds)) + .ToDictionary(p => p.Id, p => p.Name); - [HttpPost] - public async Task CreateAsync(OrderCreationModel input) + var result = ObjectMapper.Map, List>(orders); + result = result.Select(a => { - // Create a new Order entity - var order = new Order - { - CustomerName = input.CustomerName, - ProductId = input.ProductId, - State = OrderState.Placed - }; - - // Save it to the database - await _orderRepository.InsertAsync(order); - - // Publish an event so other modules can be informed - await _distributedEventBus.PublishAsync( - new OrderPlacedEto - { - ProductId = order.ProductId, - CustomerName = order.CustomerName - }); - - return Created(); - } + a.ProductName = products[a.ProductId]; + return a; + }) + .ToList(); - public class OrderCreationModel + return result; + } + + public async Task CreateAsync(OrderCreationDto input) + { + // Create a new Order entity + var order = new Order { - public Guid ProductId { get; set; } + CustomerName = input.CustomerName, + ProductId = input.ProductId, + State = OrderState.Placed + }; - [Required] - [StringLength(120)] - public string CustomerName { get; set; } - } + // Save it to the database + await _orderRepository.InsertAsync(order); + + // Publish an event so other modules can be informed + await _distributedEventBus.PublishAsync( + new OrderPlacedEto + { + ProductId = order.ProductId, + CustomerName = order.CustomerName + }); } } ```` -The `OrdersController.CreateAsync` method creates a new `Order` entity, saves it to the database and finally publishes an `OrderPlacedEto` event. +The `OrderAppService.CreateAsync` method creates a new `Order` entity, saves it to the database and finally publishes an `OrderPlacedEto` event. ## Subscribing to an Event