Browse Source

Complete revision for ModularCrm tutorial

pull/23128/head
EngincanV 8 months ago
parent
commit
ea97364bc1
  1. 2
      docs/en/samples/index.md
  2. 2
      docs/en/tutorials/modular-crm/index.md
  3. 4
      docs/en/tutorials/modular-crm/part-01.md
  4. 8
      docs/en/tutorials/modular-crm/part-03.md
  5. 34
      docs/en/tutorials/modular-crm/part-05.md
  6. 18
      docs/en/tutorials/modular-crm/part-06.md

2
docs/en/samples/index.md

@ -61,7 +61,7 @@ A modular monolith application that demonstrates how to create, compose, and com
* **ModularCRM: Razor Pages UI & Entity Framework Core**
* [Tutorial](../tutorials/modular-crm/part-01.md?UI=MVC&DB=EF)
* [Source code](https://github.com/abpframework/abp-samples/tree/master/ModularCrm)
* [Source code](https://github.com/abpframework/abp-samples/tree/master/ModularCrm-Standard)
## CloudCrm

2
docs/en/tutorials/modular-crm/index.md

@ -29,7 +29,7 @@ This tutorial is organized as the following parts:
## Download the Source Code
You can download the completed sample solution [here](https://github.com/abpframework/abp-samples/tree/master/ModularCrm-standard).
You can download the completed sample solution [here](https://github.com/abpframework/abp-samples/tree/master/ModularCrm-Standard).
## See Also

4
docs/en/tutorials/modular-crm/part-01.md

@ -27,7 +27,7 @@ You can select the other options based on your preference but at the **Modularit
Since modularity is a key aspect of the ABP Framework, it provides an option to create a modular system from the beginning. Here, you're creating a `ModularCrm.Catalog` module and setting it as a "Standard Module" (a module template similar to the DDD module but without the domain layer). This will create four projects (-depends on the options you selected-):
- `ModularCrm.Catalog`: The main module project
- `ModularCrm.Catalog.Contracts`: Contains service interfaces and DTOs
- `ModularCrm.Catalog.Tests`: Unit and integration tests (since we selected the _Include Test_ option)
- `ModularCrm.Catalog.Tests`: Unit and integration tests (since you selected the _Include Test_ option)
- `ModularCrm.Catalog.UI`: Contains UI components for the module
> **Note:** This tutorial will guide you through creating two modules: `Catalog` and `Ordering`. While you just created the `Catalog` module in the _Modularity_ step, you could also create the `Ordering` module at this stage. However, you'll create the `Ordering` module in subsequent parts to better demonstrate ABP Studio's module management capabilities and to simulate a more realistic development workflow where modules are typically added incrementally as the application evolves.
@ -48,4 +48,4 @@ The `ModularCrm` module is the core of your application, built as a single-layer
## Summary
We've created the initial single layer monolith solution. In the next part, we will learn how to create a new application module and install it to the main application.
You've created the initial single layer monolith solution. In the next part, you will learn how to create a new application module and install it to the main application.

8
docs/en/tutorials/modular-crm/part-03.md

@ -137,7 +137,7 @@ public static class CatalogDbContextModelCreatingExtensions
}
````
First, you are setting the database table name with the `ToTable` method. `CatalogDbProperties.DbTablePrefix` defines a constant that is added as a prefix to all database table names of this module. If you see the `CatalogDbProperties` class, `DbTablePrefix` value is `Catalog`. In that case, the table name for the `Product` entity will be `CatalogProducts`. That is unnecessary for such a simple module; we can remove that prefix. So, you can change the `CatalogDbProperties` class with the following content to set an empty string to the `DbTablePrefix` property:
First, you are setting the database table name with the `ToTable` method. `CatalogDbProperties.DbTablePrefix` defines a constant that is added as a prefix to all database table names of this module. If you see the `CatalogDbProperties` class, `DbTablePrefix` value is `Catalog`. In that case, the table name for the `Product` entity will be `CatalogProducts`. That is unnecessary for such a simple module; you can remove that prefix. So, you can change the `CatalogDbProperties` class with the following content to set an empty string to the `DbTablePrefix` property:
````csharp
namespace ModularCrm.Catalog;
@ -209,7 +209,7 @@ In this way, `ModularCrmDbContext` can be used by the catalog module over the `I
#### Add a Database Migration
Now, we can add a new database migration. You can use Entity Framework Core's `Add-Migration` (or `dotnet ef migrations add`) terminal command, but we will use ABP Studio's shortcut UI in this tutorial.
Now, you can add a new database migration. You can use Entity Framework Core's `Add-Migration` (or `dotnet ef migrations add`) terminal command, but you will use ABP Studio's shortcut UI in this tutorial.
Ensure that the solution has built. You can right-click the `ModularCrm` (under the `main` folder) on ABP Studio *Solution Runner* and select the *Dotnet CLI* -> *Graph Build* command.
@ -364,7 +364,7 @@ You've added the `CreateMap<Product, ProductDto>();` line to define the mapping.
To create HTTP API endpoints for the catalog module, you have two options:
* You can create a regular ASP.NET Core Controller class in the `ModularCrm.Catalog` project, inject `IProductAppService` and use it to create wrapper methods. We will do this later while we create the Ordering module. (Also, you can check the `SampleController` class under the **Samples** folder in the `ModularCrm.Catalog` project for an example.)
* You can create a regular ASP.NET Core Controller class in the `ModularCrm.Catalog` project, inject `IProductAppService` and use it to create wrapper methods. You will do this later while you create the Ordering module. (Also, you can check the `SampleController` class under the **Samples** folder in the `ModularCrm.Catalog` project for an example.)
* Alternatively, you can use the ABP's [Auto API Controllers](../../framework/api-development/auto-controllers.md) feature to expose your application services as API controllers by conventions. We will do it here.
Open the `ModularCrmModule` class in the main application's solution (the `ModularCrm` solution), find the `PreConfigureServices` method and add the following lines inside that method:
@ -427,7 +427,7 @@ You've some entities in the database and now you can show them on the user inter
## Creating the User Interface
In this section, you will create a very simple user interface to demonstrate how to build UI in the products module and make it work in the main application.
In this section, you will create a very simple user interface to demonstrate how to build UI in the catalog module and make it work in the main application.
As a first step, you can stop the application on ABP Studio's Solution Runner if it is currently running.

34
docs/en/tutorials/modular-crm/part-05.md

@ -45,7 +45,7 @@ We allow users to place only a single product within an order. The `Order` entit
### Adding an `OrderState` Enumeration
We used an `OrderState` enumeration that has not yet been defined. Open an `Enums` folder in the `ModularCrm.Ordering.Contracts` project and create an `OrderState.cs` file inside it:
You used an `OrderState` enumeration that has not yet been defined. Open an `Enums` folder in the `ModularCrm.Ordering.Contracts` project and create an `OrderState.cs` file inside it:
````csharp
namespace ModularCrm.Ordering.Contracts.Enums;
@ -68,9 +68,9 @@ The `Order` entity has been created. Now, you need to configure the database map
### Defining the Database Mappings
Entity Framework Core requires defining a `DbContext` class as the main object for the database mapping. We want to use the main application's `DbContext` object. That way, we can control the database migrations at a single point, ensure database transactions on multi-module operations, and establish relations between database tables of different modules. However, the Ordering module can not use the main application's `DbContext` object because it doesn't depend on the main application, and we don't want to establish such a dependency.
Entity Framework Core requires defining a `DbContext` class as the main object for the database mapping. We want to use the main application's `DbContext` object. That way, you can control the database migrations at a single point, ensure database transactions on multi-module operations, and establish relations between database tables of different modules. However, the Ordering module can not use the main application's `DbContext` object because it doesn't depend on the main application, and you don't want to establish such a dependency.
As a solution, we will use `DbContext` interface in the Ordering module which is then implemented by the main module's `DbContext`.
As a solution, you will use `DbContext` interface in the Ordering module which is then implemented by the main module's `DbContext`.
Open your IDE, in `Data` folder under the `ModularCrm.Ordering` project, and edit `IOrderingDbContext` interface as shown:
@ -192,7 +192,7 @@ In this way, the Ordering module can use 'ModularCrmDbContext' over the `IOrderi
#### Add a Database Migration
Now, we can add a new database migration. You can use Entity Framework Core's `Add-Migration` (or `dotnet ef migrations add`) terminal command, but in this tutorial, we will use ABP Studio's shortcut UI.
Now, you can add a new database migration. You can use Entity Framework Core's `Add-Migration` (or `dotnet ef migrations add`) terminal command, but in this tutorial, you will use ABP Studio's shortcut UI.
Ensure that the solution has built. You can right-click the `ModularCrm` (under the `main` folder) on ABP Studio *Solution Runner* and select the *Dotnet CLI* -> *Graph Build* command.
@ -218,11 +218,11 @@ After the operation completes, you can check your database to see the new `Order
## Creating the Application Service
We will create an application service to manage the `Order` entities.
You 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. 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:
You're gonna create the `IOrderAppService` interface under the `ModularCrm.Ordering.Contracts` project. 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;
@ -241,7 +241,7 @@ public interface IOrderAppService : IApplicationService
### 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.
The `GetListAsync` and `CreateAsync` methods will use data transfer objects (DTOs) to communicate with the client. You will create two DTO classes for that purpose.
Create a `OrderCreationDto` class under the `ModularCrm.Ordering.Contracts` project:
@ -285,7 +285,7 @@ The new files under the `ModularCrm.Ordering.Contracts` project should be like t
### Implementing the Application Service
Now we should configure the *AutoMapper* object to map the `Order` entity to the `OrderDto` object. We will use the `OrderingAutoMapperProfile` under the `ModularCrm.Ordering` project:
Now you should configure the *AutoMapper* object to map the `Order` entity to the `OrderDto` object. You will use the `OrderingAutoMapperProfile` under the `ModularCrm.Ordering` project:
````csharp
using AutoMapper;
@ -303,7 +303,7 @@ public class OrderingAutoMapperProfile : Profile
}
````
Now, we can implement the `IOrderAppService` interface. Create an `OrderAppService` class under the `Services` folder of the `ModularCrm.Ordering` project:
Now, you can implement the `IOrderAppService` interface. Create an `OrderAppService` class under the `Services` folder of the `ModularCrm.Ordering` project:
````csharp
using System;
@ -346,7 +346,7 @@ public class OrderAppService : OrderingAppService, IOrderAppService
### Exposing Application Services as HTTP API Controllers
After implementing the application service, now we need to create HTTP API endpoints for the ordering module. For that purpose, open the `ModularCrmModule` class in the main application's solution (the `ModularCrm` solution), find the `ConfigureAutoApiControllers` method and add the following lines inside that method:
After implementing the application service, now you need to create HTTP API endpoints for the ordering module. For that purpose, open the `ModularCrmModule` 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()
@ -370,11 +370,11 @@ private void ConfigureAutoApiControllers()
### 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.
This section will create a few example orders using the [Swagger UI](../../framework/api-development/swagger.md). Thus, you 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` application runs, we can right-click it and select the *Browse* command to open the user interface.
After the build process completes, open the Solution Runner panel and click the *Play* button near the solution root. Once the `ModularCrm` application runs, you 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:
@ -420,7 +420,7 @@ public class IndexModel : PageModel
}
````
Here, we are injecting `IOrderAppService` to query `Order` entities from the database to show on the page. Open the `Index.cshtml` file and replace the content with the following code block:
Here, you are injecting `IOrderAppService` to query `Order` entities from the database to show on the page. Open the `Index.cshtml` file and replace the content with the following code block:
````html
@page
@ -444,7 +444,7 @@ Here, we are injecting `IOrderAppService` to query `Order` entities from the dat
</abp-card>
````
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).
This page shows a list of orders on the UI. You 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).
### Editing the Menu Item
@ -485,7 +485,7 @@ public class OrderingMenuContributor : IMenuContributor
````
`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.
`OrderingMenuContributor` implements the `IMenuContributor` interface, which forces us to implement the `ConfigureMenuAsync` method. In that method, you 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.
> You can check the [menu documentation](../../framework/ui/mvc-razor-pages/navigation-menu.md) to learn more about manipulating menu items.
@ -501,10 +501,10 @@ You've performed a graph build since you've made a change on a module, and more
Great! We can see the list of orders. However, there is a problem:
1. We see Product's GUID ID instead of its name. This is because the Ordering module has no integration with the Products module and doesn't have access to Product module's database to perform a JOIN query.
1. We see Product's GUID ID instead of its name. This is because the Ordering module has no integration with the Catalog module and doesn't have access to Product module's database to perform a JOIN query.
We will solve this problem in the [next part](part-06.md).
## Summary
In this part of the *Modular CRM* tutorial, you've built the functionality inside the Ordering module you created in the [previous part](part-04.md). In the next part, you will work on establishing communication between the Orders module and the Products module.
In this part of the *Modular CRM* tutorial, you've built the functionality inside the Ordering module you created in the [previous part](part-04.md). In the next part, you will work on establishing communication between the Orders module and the Catalog module.

18
docs/en/tutorials/modular-crm/part-06.md

@ -14,7 +14,7 @@
}
````
In the previous parts, we created two modules: the **Catalog** module to store and manage products and the **Ordering** module to accept orders. However, these modules were completely independent from each other. Only the main application brought them together to execute in the same application, but these modules don't communicate with each other.
In the previous parts, you created two modules: the **Catalog** module to store and manage products and the **Ordering** module to accept orders. However, these modules were completely independent from each other. Only the main application brought them together to execute in the same application, but these modules don't communicate with each other.
In the next three parts, you will learn to implement three patterns for integrating these modules:
@ -30,7 +30,7 @@ Remember from the [previous part](part-05.md), the Orders page shows product's i
![abp-studio-browser-orders-menu-item](images/abp-studio-browser-orders-menu-item.png)
That is because the Ordering module has no access to the product data, so it can not perform a JOIN query to get the names of products from the `Products` table. That is a natural result of the modular design. However, we also don't want to show a product's identity on the UI, which is not a good user experience.
That is because the Ordering module has no access to the product data, so it can not perform a JOIN query to get the names of products from the `Products` table. That is a natural result of the modular design. However, you also don't want to show a product's identity on the UI, which is not a good user experience.
As a solution to that problem, the Ordering module may ask product names to the Catalog module using an [integration service](../../framework/api-development/integration-services.md). Integration service concept in ABP is designed for request/response style inter-module (in modular applications) and inter-microservice (in distributed systems) communication.
@ -80,7 +80,7 @@ public interface IProductIntegrationService : IApplicationService
### Implementing the `ProductIntegrationService` Class
We've defined the integration service interface. Now, we can implement it in the `ModularCrm.Catalog` project. Create an `Integration` folder and then create a `ProductIntegrationService` class in that folder. The final folder structure should be like this:
We've defined the integration service interface. Now, you can implement it in the `ModularCrm.Catalog` project. Create an `Integration` folder and then create a `ProductIntegrationService` class in that folder. The final folder structure should be like this:
![visual-studio-product-integration-service-implementation](images/visual-studio-product-integration-service-implementation.png)
@ -120,11 +120,11 @@ public class ProductIntegrationService
The implementation is pretty simple. Just using a [repository](../../framework/architecture/domain-driven-design/repositories.md) to query `Product` [entities](../../framework/architecture/domain-driven-design/entities.md).
> Here, we directly used `List<T>` classes, but instead, you could wrap inputs and outputs into [DTOs](../../framework/architecture/domain-driven-design/data-transfer-objects.md). In that way, it can be possible to add new properties to these DTOs without changing the signature of your integration service method (and without introducing breaking changes for your client modules).
> Here, you directly used `List<T>` classes, but instead, you could wrap inputs and outputs into [DTOs](../../framework/architecture/domain-driven-design/data-transfer-objects.md). In that way, it can be possible to add new properties to these DTOs without changing the signature of your integration service method (and without introducing breaking changes for your client modules).
## Consuming the Products Integration Service
The Product Integration Service is ready for the other modules to use. In this section, we will use it in the Ordering module to convert product IDs to product names.
The Product Integration Service is ready for the other modules to use. In this section, you will use it in the Ordering module to convert product IDs to product names.
### Adding a Reference of the `ModularCrm.Catalog.Contracts` Package
@ -142,7 +142,7 @@ ABP Studio adds the package reference and arranges the [module](../../framework/
### Using the Products Integration Service
Now, we can inject and use `IProductIntegrationService` in the Ordering module codebase.
Now, you can inject and use `IProductIntegrationService` in the Ordering module codebase.
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:
@ -248,11 +248,11 @@ public class OrderingApplicationAutoMapperProfile : Profile
Let's see what we've changed:
* 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.
* Injecting the `IProductIntegrationService` interface so you can use it to request products.
* 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<ProductDto>` object.
* Next, you are preparing a unique list of product IDs since the `GetProductsByIdsAsync` method requests it.
* Then you are calling the `IProductIntegrationService.GetProductsByIdsAsync` method to get a `List<ProductDto>` 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.

Loading…
Cancel
Save