diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 0bd0fef7ad..3ef62ff220 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -934,6 +934,74 @@ { "text": "Microservices", "path": "framework/architecture/microservices" + }, + { + "text": "Module Development Best Practices", + "items": [ + { + "text": "Overview", + "path": "framework/architecture/best-practices" + }, + { + "text": "Module Architecture", + "path": "framework/architecture/best-practices/module-architecture.md" + }, + { + "text": "Domain Layer", + "items": [ + { + "text": "Overview", + "path": "framework/architecture/best-practices/domain-layer-overview.md" + }, + { + "text": "Entities", + "path": "framework/architecture/best-practices/entities.md" + }, + { + "text": "Repositories", + "path": "framework/architecture/best-practices/repositories.md" + }, + { + "text": "Domain Services", + "path": "framework/architecture/best-practices/domain-services.md" + } + ] + }, + { + "text": "Application Layer", + "items": [ + { + "text": "Overview", + "path": "framework/architecture/best-practices/application-layer-overview.md" + }, + { + "text": "Application Services", + "path": "framework/architecture/best-practices/application-services.md" + }, + { + "text": "Data Transfer Objects", + "path": "framework/architecture/best-practices/data-transfer-objects.md" + } + ] + }, + { + "text": "Data Access", + "items": [ + { + "text": "Overview", + "path": "framework/architecture/best-practices/data-access-overview.md" + }, + { + "text": "Entity Framework Core Integration", + "path": "framework/architecture/best-practices/entity-framework-core-integration.md" + }, + { + "text": "MongoDB Integration", + "path": "framework/architecture/best-practices/mongodb-integration.md" + } + ] + } + ] } ] }, diff --git a/docs/en/framework/architecture/best-practices/application-services.md b/docs/en/framework/architecture/best-practices/application-services.md index d3ad44ca54..efe937e956 100644 --- a/docs/en/framework/architecture/best-practices/application-services.md +++ b/docs/en/framework/architecture/best-practices/application-services.md @@ -1,8 +1,14 @@ # Application Services Best Practices & Conventions +> This document offers best practices for implementing Application Services classes in your modules and applications based on Domain-Driven-Design principles. +> +> **Ensure you've read the [*Application Services*](../domain-driven-design/application-services.md) document first.** + +## General + * **Do** create an application service for each **aggregate root**. -### Application Service Interface +## Application Service Interface * **Do** define an `interface` for each application service in the **application contracts** package. * **Do** inherit from the `IApplicationService` interface. @@ -11,11 +17,11 @@ * **Do not** get/return entities for the service methods. * **Do** define DTOs based on the [DTO best practices](data-transfer-objects.md). -#### Outputs +### Outputs * **Avoid** to define too many output DTOs for same or related entities. Instead, define a **basic** and a **detailed** DTO for an entity. -##### Basic DTO +#### Basic DTO **Do** define a **basic** DTO for an aggregate root. @@ -44,7 +50,7 @@ public class IssueLabelDto } ``` -##### Detailed DTO +#### Detailed DTO **Do** define a **detailed** DTO for an entity if it has reference(s) to other aggregate roots. @@ -81,20 +87,20 @@ public class LabelDto : ExtensibleEntityDto } ```` -#### Inputs +### Inputs * **Do not** define any property in an input DTO that is not used in the service class. * **Do not** share input DTOs between application service methods. * **Do not** inherit an input DTO class from another one. * **May** inherit from an abstract base DTO class and share some properties between different DTOs in that way. However, should be very careful in that case because manipulating the base DTO would effect all related DTOs and service methods. Avoid from that as a good practice. -#### Methods +### Methods * **Do** define service methods as asynchronous with **Async** postfix. * **Do not** repeat the entity name in the method names. * Example: Define `GetAsync(...)` instead of `GetProductAsync(...)` in the `IProductAppService`. -##### Getting A Single Entity +#### Getting A Single Entity * **Do** use the `GetAsync` **method name**. * **Do** get Id with a **primitive** method parameter. @@ -104,7 +110,7 @@ public class LabelDto : ExtensibleEntityDto Task GetAsync(Guid id); ```` -##### Getting A List Of Entities +#### Getting A List Of Entities * **Do** use the `GetListAsync` **method name**. * **Do** get a single DTO argument for **filtering**, **sorting** and **paging** if necessary. @@ -117,7 +123,7 @@ Task GetAsync(Guid id); Task> GetListAsync(QuestionListQueryDto queryDto); ```` -##### Creating A New Entity +#### Creating A New Entity * **Do** use the `CreateAsync` **method name**. * **Do** get a **specialized input** DTO to create the entity. @@ -151,7 +157,7 @@ public class CreateQuestionDto : ExtensibleObject } ```` -##### Updating An Existing Entity +#### Updating An Existing Entity - **Do** use the `UpdateAsync` **method name**. - **Do** get a **specialized input** DTO to update the entity. @@ -167,7 +173,7 @@ Example: Task UpdateAsync(Guid id, UpdateQuestionDto updateQuestionDto); ```` -##### Deleting An Existing Entity +#### Deleting An Existing Entity - **Do** use the `DeleteAsync` **method name**. - **Do** get Id with a **primitive** method parameter. Example: @@ -176,7 +182,7 @@ Task UpdateAsync(Guid id, UpdateQuestionDto updateQuesti Task DeleteAsync(Guid id); ```` -##### Other Methods +#### Other Methods * **Can** define additional methods to perform operations on the entity. Example: @@ -186,7 +192,7 @@ Task VoteAsync(Guid id, VoteType type); This method votes a question and returns the current score of the question. -### Application Service Implementation +## Application Service Implementation * **Do** develop the application layer **completely independent from the web layer**. * **Do** implement application service interfaces in the **application layer**. @@ -195,30 +201,30 @@ This method votes a question and returns the current score of the question. * **Do** make all public methods **virtual**, so developers may inherit and override them. * **Do not** make **private** methods. Instead make them **protected virtual**, so developers may inherit and override them. -#### Using Repositories +### Using Repositories * **Do** use the specifically designed repositories (like `IProductRepository`). * **Do not** use generic repositories (like `IRepository`). -#### Querying Data +### Querying Data * **Do not** use LINQ/SQL for querying data from database inside the application service methods. It's repository's responsibility to perform LINQ/SQL queries from the data source. -#### Extra Properties +### Extra Properties * **Do** use either `MapExtraPropertiesTo` extension method ([see](../../fundamentals/object-extensions.md)) or configure the object mapper (`MapExtraProperties`) to allow application developers to be able to extend the objects and services. -#### Manipulating / Deleting Entities +### Manipulating / Deleting Entities * **Do** always get all the related entities from repositories to perform the operations on them. * **Do** call repository's Update/UpdateAsync method after updating an entity. Because, not all database APIs support change tracking & auto update. -#### Handle files +### Handle files * **Do not** use any web components like `IFormFile` or `Stream` in the application services. If you want to serve a file you can use `byte[]`. * **Do** use a `Controller` to handle file uploading then pass the `byte[]` of the file to the application service method. -#### Using Other Application Services +### Using Other Application Services * **Do not** use other application services of the same module/application. Instead; * Use domain layer to perform the required task. diff --git a/docs/en/framework/architecture/best-practices/data-transfer-objects.md b/docs/en/framework/architecture/best-practices/data-transfer-objects.md index e4caa6ed66..96194b4adb 100644 --- a/docs/en/framework/architecture/best-practices/data-transfer-objects.md +++ b/docs/en/framework/architecture/best-practices/data-transfer-objects.md @@ -1,5 +1,11 @@ # Data Transfer Objects Best Practices & Conventions +> This document offers best practices for implementing Data Transfer Object classes in your modules and applications based on Domain-Driven-Design principles. +> +> **Ensure you've read the [*Data Transfer Objects*](../domain-driven-design/data-transfer-objects.md) document first.** + +## General + * **Do** define DTOs in the **application contracts** package. * **Do** inherit from the pre-built **base DTO classes** where possible and necessary (like `EntityDto`, `CreationAuditedEntityDto`, `AuditedEntityDto`, `FullAuditedEntityDto` and so on). * **Do** inherit from the **extensible DTO** classes for the **aggregate roots** (like `ExtensibleAuditedEntityDto`), because aggregate roots are extensible objects and extra properties are mapped to DTOs in this way. diff --git a/docs/en/framework/architecture/best-practices/domain-services.md b/docs/en/framework/architecture/best-practices/domain-services.md index 565a67a715..55c731d207 100644 --- a/docs/en/framework/architecture/best-practices/domain-services.md +++ b/docs/en/framework/architecture/best-practices/domain-services.md @@ -1,6 +1,10 @@ # Domain Services Best Practices & Conventions -### Domain Service +> This document offers best practices for implementing Domain Service classes in your modules and applications based on Domain-Driven-Design principles. +> +> **Ensure you've read the [*Domain Services*](../domain-driven-design/domain-services.md) document first.** + +## Domain Services - **Do** define domain services in the **domain layer**. - **Do not** create interfaces for the domain services **unless** you have a good reason to (like mock and test different implementations). @@ -14,7 +18,7 @@ public class IssueManager : DomainService } ``` -### Domain Service Methods +## Domain Service Methods - **Do not** define `GET` methods. `GET` methods do not change the state of an entity. Hence, use the repository directly in the Application Service instead of Domain Service method. @@ -57,8 +61,6 @@ public async Task AssignToAsync(Issue issue, IdentityUser user) - **Do not** return `DTO`. Return only domain objects when you need. - **Do not** involve authenticated user logic. Instead, define extra parameter and send the related data of ` CurrentUser` from the Application Service layer. - - ## See Also * [Video tutorial](https://abp.io/video-courses/essentials/domain-services) diff --git a/docs/en/framework/architecture/best-practices/entities.md b/docs/en/framework/architecture/best-practices/entities.md index 319f1648ff..cc4b0e2a21 100644 --- a/docs/en/framework/architecture/best-practices/entities.md +++ b/docs/en/framework/architecture/best-practices/entities.md @@ -1,12 +1,16 @@ # Entity Best Practices & Conventions -### Entities +> This document offers best practices for implementing Aggregate Root and Entity classes in your modules and applications based on Domain-Driven-Design principles. +> +> **Ensure you've read the [*Entities*](../domain-driven-design/entities.md) document first.** + +## Entities Every aggregate root is also an entity. So, these rules are valid for aggregate roots too unless aggregate root rules override them. - **Do** define entities in the **domain layer**. -#### Primary Constructor +### Primary Constructor * **Do** define a **primary constructor** that ensures the validity of the entity on creation. Primary constructors are used to create a new instance of the entity by the application code. @@ -14,15 +18,15 @@ Every aggregate root is also an entity. So, these rules are valid for aggregate - **Do** always initialize sub collections in the primary constructor. - **Do not** generate `Guid` keys inside the constructor. Get it as a parameter, so the calling code will use `IGuidGenerator` to generate a new `Guid` value. -#### Parameterless Constructor +### Parameterless Constructor - **Do** always define a `protected` parameterless constructor to be compatible with ORMs. -#### References +### References - **Do** always **reference** to other aggregate roots **by Id**. Never add navigation properties to other aggregate roots. -#### Other Class Members +### Other Class Members - **Do** always define properties and methods as `virtual` (except `private` methods, obviously). Because some ORMs and dynamic proxy tools require it. - **Do** keep the entity as always **valid** and **consistent** within its own boundary. @@ -30,27 +34,27 @@ Every aggregate root is also an entity. So, these rules are valid for aggregate - **Do** define `public `, `internal` or `protected internal` (virtual) **methods** to change the properties (with non-public setters) if necessary. - **Do** return the entity object (`this`) from the setter methods. -### Aggregate Roots +## Aggregate Roots -#### Primary Keys +### Primary Keys * **Do** always use a **Id** property for the aggregate root key. * **Do not** use **composite keys** for aggregate roots. * **Do** use **Guid** as the **primary key** of all aggregate roots. -#### Base Class +### Base Class * **Do** inherit from the `AggregateRoot` or one of the audited classes (`CreationAuditedAggregateRoot`, `AuditedAggregateRoot` or `FullAuditedAggregateRoot`) based on requirements. -#### Aggregate Boundary +### Aggregate Boundary * **Do** keep aggregates **as small as possible**. Most of the aggregates will only have primitive properties and will not have sub collections. Consider these as design decisions: * **Performance** & **memory** cost of loading & saving aggregates (keep in mind that an aggregate is normally loaded & saved as a single unit). Larger aggregates will consume more CPU & memory. * **Consistency** & **validity** boundary. -### Example +## Example -#### Aggregate Root +### Aggregate Root ````C# public class Issue : FullAuditedAggregateRoot //Using Guid as the key/identifier @@ -130,7 +134,7 @@ public class Issue : FullAuditedAggregateRoot //Using Guid as the key/iden } ```` -#### The Entity +### Entity ````C# public class IssueLabel : Entity @@ -151,11 +155,12 @@ public class IssueLabel : Entity } ```` -### References +## References * Effective Aggregate Design by Vaughn Vernon http://dddcommunity.org/library/vernon_2011 - ## See Also + +## See Also * [Video tutorial](https://abp.io/video-courses/essentials/entities) \ No newline at end of file diff --git a/docs/en/framework/architecture/best-practices/entity-framework-core-integration.md b/docs/en/framework/architecture/best-practices/entity-framework-core-integration.md index 9a2081e4e5..960e0e69e4 100644 --- a/docs/en/framework/architecture/best-practices/entity-framework-core-integration.md +++ b/docs/en/framework/architecture/best-practices/entity-framework-core-integration.md @@ -1,12 +1,16 @@ # Entity Framework Core Integration Best Practices -> See [Entity Framework Core Integration document](../../data/entity-framework-core) for the basics of the EF Core integration. +> This document offers best practices for implementing Entity Framework Core integration in your modules and applications. +> +> **Ensure you've read the [*Entity Framework Core Integration*](../../data/entity-framework-core/index.md) document first.** + +## General - **Do** define a separated `DbContext` interface and class for each module. - **Do not** rely on lazy loading on the application development. - **Do not** enable lazy loading for the `DbContext`. -### DbContext Interface +## DbContext Interface - **Do** define an **interface** for the `DbContext` that inherits from `IEfCoreDbContext`. - **Do** add a `ConnectionStringName` **attribute** to the `DbContext` interface. @@ -23,7 +27,7 @@ public interface IIdentityDbContext : IEfCoreDbContext * **Do not** define `set;` for the properties in this interface. -### DbContext class +## DbContext class * **Do** inherit the `DbContext` from the `AbpDbContext` class. * **Do** add a `ConnectionStringName` attribute to the `DbContext` class. @@ -46,7 +50,7 @@ public class IdentityDbContext : AbpDbContext, IIdentityDbCon } ```` -### Table Prefix and Schema +## Table Prefix and Schema - **Do** add static `TablePrefix` and `Schema` **properties** to the `DbContext` class. Set default value from a constant. Example: @@ -58,7 +62,7 @@ public static string Schema { get; set; } = AbpIdentityConsts.DefaultDbSchema; - **Do** always use a short `TablePrefix` value for a module to create **unique table names** in a shared database. `Abp` table prefix is reserved for ABP core modules. - **Do** set `Schema` to `null` as default. -### Model Mapping +## Model Mapping - **Do** explicitly **configure all entities** by overriding the `OnModelCreating` method of the `DbContext`. Example: @@ -100,7 +104,7 @@ public static class IdentityDbContextModelBuilderExtensions * **Do** call `b.ConfigureByConvention();` for each entity mapping (as shown above). -### Repository Implementation +## Repository Implementation - **Do** **inherit** the repository from the `EfCoreRepository` class and implement the corresponding repository interface. Example: @@ -168,7 +172,7 @@ public override async Task> WithDetailsAsync() } ```` -### Module Class +## Module Class - **Do** define a module class for the Entity Framework Core integration package. - **Do** add `DbContext` to the `IServiceCollection` using the `AddAbpDbContext` method. diff --git a/docs/en/framework/architecture/best-practices/module-architecture.md b/docs/en/framework/architecture/best-practices/module-architecture.md index d050094656..c168b77f87 100644 --- a/docs/en/framework/architecture/best-practices/module-architecture.md +++ b/docs/en/framework/architecture/best-practices/module-architecture.md @@ -1,13 +1,13 @@ # Module Architecture Best Practices & Conventions -### Solution Structure +## Solution Structure * **Do** create a separated Visual Studio solution for every module. * **Do** name the solution as *CompanyName.ModuleName* (for core ABP modules, it's *Volo.Abp.ModuleName*). * **Do** develop the module as layered, so it has several packages (projects) those are related to each other. * Every package has its own module definition file and explicitly declares the dependencies for the depended packages/modules. -### Layers & Packages +## Layers & Packages The following diagram shows the packages of a well-layered module and dependencies of those packages between them: diff --git a/docs/en/framework/architecture/best-practices/mongodb-integration.md b/docs/en/framework/architecture/best-practices/mongodb-integration.md index 36b037c967..1930984e2e 100644 --- a/docs/en/framework/architecture/best-practices/mongodb-integration.md +++ b/docs/en/framework/architecture/best-practices/mongodb-integration.md @@ -1,8 +1,14 @@ # MongoDB Integration +> This document offers best practices for implementing MongoDB integration in your modules and applications. +> +> **Ensure you've read the [*MongoDB Integration*](../../data/entity-framework-core/index.md) document first.** + +## General + * Do define a separated `MongoDbContext` interface and class for each module. -### MongoDbContext Interface +## MongoDbContext Interface - **Do** define an **interface** for the `MongoDbContext` that inherits from `IAbpMongoDbContext`. - **Do** add a `ConnectionStringName` **attribute** to the `MongoDbContext` interface. @@ -17,7 +23,7 @@ public interface IAbpIdentityMongoDbContext : IAbpMongoDbContext } ```` -### MongoDbContext class +## MongoDbContext class - **Do** inherit the `MongoDbContext` from the `AbpMongoDbContext` class. - **Do** add a `ConnectionStringName` attribute to the `MongoDbContext` class. @@ -34,7 +40,7 @@ public class AbpIdentityMongoDbContext : AbpMongoDbContext, IAbpIdentityMongoDbC } ``` -### Collection Prefix +## Collection Prefix - **Do** add static `CollectionPrefix` **property** to the `DbContext` class. Set default value from a constant. Example: @@ -46,7 +52,7 @@ Used the same constant defined for the EF Core integration table prefix in this - **Do** always use a short `CollectionPrefix` value for a module to create **unique collection names** in a shared database. `Abp` collection prefix is reserved for ABP core modules. -### Collection Mapping +## Collection Mapping - **Do** explicitly **configure all aggregate roots** by overriding the `CreateModel` method of the `MongoDbContext`. Example: @@ -83,7 +89,7 @@ public static class AbpIdentityMongoDbContextExtensions } ``` -### Repository Implementation +## Repository Implementation - **Do** **inherit** the repository from the `MongoDbRepository` class and implement the corresponding repository interface. Example: @@ -124,7 +130,7 @@ public async Task FindByNormalizedUserNameAsync( * Using `IQueryable` makes the code as much as similar to the EF Core repository implementation and easy to write and read. * **Do** implement data filtering if it is not possible to use the `GetMongoQueryable()` method. -### Module Class +## Module Class - **Do** define a module class for the MongoDB integration package. - **Do** add `MongoDbContext` to the `IServiceCollection` using the `AddMongoDbContext` method. diff --git a/docs/en/framework/architecture/best-practices/repositories.md b/docs/en/framework/architecture/best-practices/repositories.md index 5e491ecb80..227c620f45 100644 --- a/docs/en/framework/architecture/best-practices/repositories.md +++ b/docs/en/framework/architecture/best-practices/repositories.md @@ -1,6 +1,10 @@ # Repository Best Practices & Conventions -### Repository Interfaces +> This document offers best practices for implementing Repository classes in your modules and applications based on Domain-Driven-Design principles. +> +> **Ensure you've read the [*Repositories*](../domain-driven-design/repositories.md) document first.** + +## Repository Interfaces * **Do** define repository interfaces in the **domain layer**. * **Do** define a repository interface (like `IIdentityUserRepository`) and create its corresponding implementations for **each aggregate root**. @@ -30,7 +34,7 @@ public interface IIdentityUserRepository : IBasicRepository * **Do** inherit the repository interface from `IBasicRepository` (as normally) or a lower-featured interface, like `IReadOnlyRepository` (if it's needed). * **Do not** define repositories for entities those are **not aggregate roots**. -### Repository Methods +## Repository Methods * **Do** define all repository methods as **asynchronous**. * **Do** add an **optional** `cancellationToken` parameter to every method of the repository. Example: @@ -68,7 +72,7 @@ Task> GetListByNormalizedRoleNameAsync( * **Avoid** to create projection classes for entities to get less property of an entity from the repository. Example: Avoid to create BasicUserView class to select a few properties needed for the use case needs. Instead, directly use the aggregate root class. However, there may be some exceptions for this rule, where: * Performance is so critical for the use case and getting the whole aggregate root highly impacts the performance. -### See Also +## See Also * [Entity Framework Core Integration](./entity-framework-core-integration.md) * [MongoDB Integration](./mongodb-integration.md) diff --git a/docs/en/framework/architecture/domain-driven-design/data-transfer-objects.md b/docs/en/framework/architecture/domain-driven-design/data-transfer-objects.md index e33e7895ca..8816411a5b 100644 --- a/docs/en/framework/architecture/domain-driven-design/data-transfer-objects.md +++ b/docs/en/framework/architecture/domain-driven-design/data-transfer-objects.md @@ -1,7 +1,5 @@ # Data Transfer Objects -## Introduction - **Data Transfer Objects** (DTO) are used to transfer data between the **Application Layer** and the **Presentation Layer** or other type of clients. Typically, an [application service](./application-services.md) is called from the presentation layer (optionally) with a **DTO** as the parameter. It uses domain objects to **perform some specific business logic** and (optionally) returns a DTO back to the presentation layer. Thus, the presentation layer is completely **isolated** from domain layer. diff --git a/docs/en/framework/architecture/domain-driven-design/domain-services.md b/docs/en/framework/architecture/domain-driven-design/domain-services.md index 358e850527..36bd732a3c 100644 --- a/docs/en/framework/architecture/domain-driven-design/domain-services.md +++ b/docs/en/framework/architecture/domain-driven-design/domain-services.md @@ -1,7 +1,5 @@ # Domain Services -## Introduction - In a [Domain Driven Design](../domain-driven-design) (DDD) solution, the core business logic is generally implemented in aggregates ([entities](./entities.md)) and the Domain Services. Creating a Domain Service is especially needed when; * You implement a core domain logic that depends on some services (like repositories or other external services). 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 857df5f25d..ee8b847a45 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-package-reference-5.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-5.png index 42d7c96cb2..cc3985bb75 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-browser-orders-menu-item.png b/docs/en/tutorials/modular-crm/images/abp-studio-browser-orders-menu-item.png index 3e4d83fee1..e552239474 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-browser-orders-menu-item.png and b/docs/en/tutorials/modular-crm/images/abp-studio-browser-orders-menu-item.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 889a4251cd..bf9c9d1ead 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-update-database.png b/docs/en/tutorials/modular-crm/images/abp-studio-entity-framework-core-update-database.png index b28b4e48a0..88a0a20bb3 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-import-module-for-ordering-dialog.png b/docs/en/tutorials/modular-crm/images/abp-studio-import-module-for-ordering-dialog.png index 762265b2c3..2b07060c2a 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-import-module-for-ordering-dialog.png and b/docs/en/tutorials/modular-crm/images/abp-studio-import-module-for-ordering-dialog.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-install-module-dialog.png b/docs/en/tutorials/modular-crm/images/abp-studio-install-module-dialog.png index 5ea22e8e3e..8c9abf1bf7 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-install-module-dialog.png and b/docs/en/tutorials/modular-crm/images/abp-studio-install-module-dialog.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-module-installation-dialog.png b/docs/en/tutorials/modular-crm/images/abp-studio-module-installation-dialog.png index 9946afaaec..eb71910650 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-module-installation-dialog.png and b/docs/en/tutorials/modular-crm/images/abp-studio-module-installation-dialog.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 f4131cfb69..62a85d6589 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-ordering-swagger-ui-in-browser.png b/docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser.png index 304027af9e..1318691ae9 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser.png 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 10d210a0be..d88c2b80c7 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 269c55f8b4..7f9b312b23 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-swagger-create-order.png b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-create-order.png index e2c15fca23..01a2c5fe29 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-create-order.png and b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-create-order.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-list-orders.png b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-list-orders.png index e58ea0c51d..0fc7e63b23 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-list-orders.png and b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-list-orders.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-in-browser.png b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-in-browser.png index 8183cde1fb..1b5fec9205 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-in-browser.png and b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-in-browser.png differ diff --git a/docs/en/tutorials/modular-crm/images/solution-explorer-modular-crm-expanded.png b/docs/en/tutorials/modular-crm/images/solution-explorer-modular-crm-expanded.png index 841b8fe610..3156b75fb1 100644 Binary files a/docs/en/tutorials/modular-crm/images/solution-explorer-modular-crm-expanded.png and b/docs/en/tutorials/modular-crm/images/solution-explorer-modular-crm-expanded.png differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-main-dbcontext.png b/docs/en/tutorials/modular-crm/images/visual-studio-main-dbcontext.png index 825b6c2422..0a2db8ed76 100644 Binary files a/docs/en/tutorials/modular-crm/images/visual-studio-main-dbcontext.png and b/docs/en/tutorials/modular-crm/images/visual-studio-main-dbcontext.png differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class-2.png b/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class-2.png index 3acc6e45c7..c5a698db58 100644 Binary files a/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class-2.png and b/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class-2.png differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class.png b/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class.png index b18d071cb1..24c7cb3299 100644 Binary files a/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class.png and b/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class.png differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-order-reporting-app-service-impl.png b/docs/en/tutorials/modular-crm/images/visual-studio-order-reporting-app-service-impl.png deleted file mode 100644 index 5007486775..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/visual-studio-order-reporting-app-service-impl.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-order-reporting-app-service.png b/docs/en/tutorials/modular-crm/images/visual-studio-order-reporting-app-service.png index 7333958f11..1dcead7f0d 100644 Binary files a/docs/en/tutorials/modular-crm/images/visual-studio-order-reporting-app-service.png and b/docs/en/tutorials/modular-crm/images/visual-studio-order-reporting-app-service.png 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 index d9206f6630..512edaa4a1 100644 Binary files a/docs/en/tutorials/modular-crm/images/visual-studio-ordering-contracts.png and b/docs/en/tutorials/modular-crm/images/visual-studio-ordering-contracts.png differ diff --git a/docs/en/tutorials/modular-crm/part-01.md b/docs/en/tutorials/modular-crm/part-01.md index 316aff6150..ddc6e8debe 100644 --- a/docs/en/tutorials/modular-crm/part-01.md +++ b/docs/en/tutorials/modular-crm/part-01.md @@ -3,6 +3,10 @@ ````json //[doc-nav] { + "Previous": { + "Name": "Overview", + "Path": "tutorials/modular-crm/index" + }, "Next": { "Name": "Creating the initial Products module", "Path": "tutorials/modular-crm/part-02" @@ -10,7 +14,7 @@ } ```` -Follow the *[Get Started](../../get-started/layered-web-application.md)* guide to create a new layered web application with the following configuration: +Follow the *[Get Started](../../get-started/single-layer-web-application.md)* guide to create a single layer web application with the following configuration: * **Solution name**: `ModularCrm` * **UI Framework**: ASP.NET Core MVC / Razor Pages @@ -18,7 +22,7 @@ Follow the *[Get Started](../../get-started/layered-web-application.md)* guide t You can select the other options based on your preference. -> **Please complete the [Get Stared](../../get-started/layered-web-application.md) guide and run the web application before going further.** +> **Please complete the [Get Started](../../get-started/single-layer-web-application.md) guide and run the web application before going further.** The initial solution structure should be like the following in ABP Studio's *[Solution Explorer](../../studio/solution-explorer.md)*: @@ -28,10 +32,10 @@ Initially, you see a `ModularCrm` solution and a `ModularCrm` module under that > An ABP Studio module is typically a .NET solution and an ABP Studio solution is an umbrella concept for multiple .NET Solutions (see the [concepts](../../studio/concepts.md) document for more). -`ModularCrm` module is your main application, which is a layered .NET solution that consists of several packages (.NET projects). You can expand the `ModularCrm` module to see its packages: +The `ModularCrm` module is the core of your application, built as a single-layer ASP.NET Core Web application. You can expand the `ModularCrm` module to see: ![solution-explorer-modular-crm-expanded](images/solution-explorer-modular-crm-expanded.png) ## Summary -We've created the initial layered monolith solution. In the next part, we will learn how to create a new application module and install it to the main application. +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. diff --git a/docs/en/tutorials/modular-crm/part-02.md b/docs/en/tutorials/modular-crm/part-02.md index 8a5b4b9e6c..034b4b7bfe 100644 --- a/docs/en/tutorials/modular-crm/part-02.md +++ b/docs/en/tutorials/modular-crm/part-02.md @@ -32,12 +32,13 @@ Create a `main` and a `modules` folder using the *New Folder* command, then move ## Creating The Module -There are two module templates provided by ABP Studio: +There are three module templates provided by ABP Studio: * **Empty Module**: You can use that module template to build your module structure from scratch. * **DDD Module**: A Domain Driven Design based layered module structure. +* **Standard Module**: A module template that is similar to the DDD module but without the domain layer. -We will use the *DDD Module* template for the Product module and the *Empty Module* template later in this tutorial. +We will use the *DDD Module* template for the Product module and the *Standard Module* template later in this tutorial. Right-click the `modules` folder on the *Solution Explorer* panel, and select the *Add* -> *New Module* -> *DDD Module* command: @@ -115,7 +116,7 @@ When you click the *OK* button, ABP Studio opens the *Install Module* dialog: ![abp-studio-module-installation-dialog](images/abp-studio-module-installation-dialog.png) -This dialog simplifies installing a multi-layer module to a multi-layer application. It automatically determines which package of the `ModularCrm.Products` module should be installed to which package of the main application. For example, the `ModularCrm.Products.Domain` package is installed to the `ModularCrm.Domain` package. In that way, you can use domain objects ([entities](../../framework/architecture/domain-driven-design/entities.md), [repositories](../../framework/architecture/domain-driven-design/repositories.md), ...) of the products module from the domain layer of your main application. +This dialog simplifies installing a multi-layer module to a single-layer application. It automatically determines which package of the `ModularCrm.Products` module should be installed to which package of the main application. The default package match is good for this tutorial, so you can click the *OK* button to proceed. @@ -131,7 +132,7 @@ Graph Build is a dotnet CLI command that recursively builds all the referenced d ### Run the Main Application -Open the *Solution Runner* panel, click the *Play* button (near to the solution root), right-click the `ModularCrm.Web` application and select the *Browse* command. It will open the web application in the built-in browser. Then you can navigate to the *Products* page on the main menu of the application to see the Products page that is coming from the `ModularCrm.Products` module: +Open the *Solution Runner* panel, click the *Play* button (near to the solution root), right-click the `ModularCrm` application and select the *Browse* command. It will open the web application in the built-in browser. Then you can navigate to the *Products* page on the main menu of the application to see the Products page that is coming from the `ModularCrm.Products` module: ![abp-studio-solution-runner-initial-product-page](images/abp-studio-solution-runner-initial-product-page.png) diff --git a/docs/en/tutorials/modular-crm/part-03.md b/docs/en/tutorials/modular-crm/part-03.md index 799745b9c8..3d8896d8a5 100644 --- a/docs/en/tutorials/modular-crm/part-03.md +++ b/docs/en/tutorials/modular-crm/part-03.md @@ -164,7 +164,7 @@ Open the `ModularCrm` module (which is the main application) in your IDE: ![abp-studio-open-with-visual-studio-main-app](images/abp-studio-open-with-visual-studio-main-app.png) -Find the `ModularCrmDbContext` class under the `ModularCrm.EntityFrameworkCore` project: +Open the `ModularCrmDbContext` class under the `ModularCrm` project's `Data` folder: ![visual-studio-main-dbcontext](images/visual-studio-main-dbcontext.png) @@ -185,10 +185,9 @@ Follow the three steps below; **(2)** Implement the `IProductsDbContext` by the `ModularCrmDbContext` class: ````csharp +[ReplaceDbContext(typeof(IProductsDbContext))] public class ModularCrmDbContext : AbpDbContext, - ITenantManagementDbContext, - IIdentityDbContext, IProductsDbContext //NEW: IMPLEMENT THE INTERFACE { public DbSet Products { get; set; } //NEW: ADD DBSET PROPERTY @@ -214,7 +213,7 @@ Now, we can add a new database migration. You can use Entity Framework Core's `A 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. -Right-click the `ModularCrm.EntityFrameworkCore` package and select the *EF Core CLI* -> *Add Migration* command: +Right-click the `ModularCrm` package and select the *EF Core CLI* -> *Add Migration* command: ![abp-studio-add-entity-framework-core-migration](images/abp-studio-add-entity-framework-core-migration.png) @@ -222,7 +221,7 @@ The *Add Migration* command opens a new dialog to get a migration name: ![abp-studio-add-entity-framework-core-migration-dialog](images/abp-studio-add-entity-framework-core-migration-dialog.png) -Once you click the *OK* button, a new database migration class is added to the `Migrations` folder of the `ModularCrm.EntityFrameworkCore` project: +Once you click the *OK* button, a new database migration class is added to the `Migrations` folder of the `ModularCrm` project: ![visual-studio-new-migration-class](images/visual-studio-new-migration-class.png) @@ -369,7 +368,7 @@ For this application, we don't need to create HTTP API endpoints for the product * You can create a regular ASP.NET Core Controller class in the `ModularCrm.Products.HttpApi` project, inject `IProductAppService` and use it to create wrapper methods. We will do this later while we create the Ordering module. * 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 `ModularCrmWebModule` class in the main application's solution (the `ModularCrm` solution), find the `PreConfigureServices` method and add the following lines inside that method: +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: ````csharp PreConfigure(mvcBuilder => @@ -385,8 +384,8 @@ Then open the `ConfigureAutoApiControllers` method of the same class and add a s ````csharp Configure(options => { - options.ConventionalControllers.Create(typeof(ModularCrmApplicationModule).Assembly); - + options.ConventionalControllers.Create(typeof(ModularCrmModule).Assembly); + //ADD THE FOLLOWING LINE: options.ConventionalControllers.Create(typeof(ProductsApplicationModule).Assembly); }); @@ -404,7 +403,7 @@ This section will create a few example products using the [Swagger UI](../../fra 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 product 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. +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. 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 `Products` API: @@ -486,7 +485,7 @@ Here, we simply use the `IProductAppService` to get a list of all products and a ```` -You can build the product module's .NET solution (`ModularCrm.Products`), then right-click the `ModularCrm.Web` application on ABP Studio's solution runner and select the *Build & Restart* command: +Right-click the `ModularCrm` application on ABP Studio's solution runner and select the *Start* command: ![abp-studio-build-and-restart-application](images/abp-studio-build-and-restart-application.png) diff --git a/docs/en/tutorials/modular-crm/part-04.md b/docs/en/tutorials/modular-crm/part-04.md index 1674ced6c6..ba406f7846 100644 --- a/docs/en/tutorials/modular-crm/part-04.md +++ b/docs/en/tutorials/modular-crm/part-04.md @@ -70,8 +70,6 @@ Select the `ModularCrm.Ordering` module and check the *Install this module* opti ![abp-studio-install-module-dialog](images/abp-studio-install-module-dialog.png) -Select the `ModuleCrm.Ordering` package from the left area and the `ModularCrm.Domain` package from the middle area. Then, select the `ModularCrm.Ordering.UI` package from the left area and the `ModularCrm.Web` package from the middle area, as shown in the preceding figure. Finally, click *OK*. - -> Since the Ordering module is not layered, we didn't install its packages to the layers of our main application. We are installing it only to `ModularCrm.Domain`. In this way, we can use the Ordering module from any layer of our application since `ModularCrm.Domain` is one of the core packages of our application. If you build your modules as non-layered and you don't have much code in the main application's .NET solution, you can also consider creating a non-layered main application that composes these modules. +Select the `ModuleCrm.Ordering` and `ModularCrm.Ordering.UI` packages from the left area and the `ModularCrm` package from the middle area as shown in the preceding figure. Finally, click *OK*. In this part of the tutorial, we've created a standard module. This allows you to create modules or applications with a different structure. In the next part, we will add functionality to the Ordering module. diff --git a/docs/en/tutorials/modular-crm/part-05.md b/docs/en/tutorials/modular-crm/part-05.md index c5279b405a..a489548b65 100644 --- a/docs/en/tutorials/modular-crm/part-05.md +++ b/docs/en/tutorials/modular-crm/part-05.md @@ -28,7 +28,7 @@ Create an `Order` class to the `ModularCrm.Ordering` project (open an `Entities` ````csharp using System; -using ModularCrm.Ordering.Contracts.Enums; +using ModularCrm.Ordering.Enums; using Volo.Abp.Domain.Entities.Auditing; namespace ModularCrm.Ordering.Entities @@ -156,7 +156,7 @@ public static class OrderingDbContextModelCreatingExtensions #### Configuring the Main Application -Open the main application's solution in your IDE, find the `ModularCrmDbContext` class under the `ModularCrm.EntityFrameworkCore` project and follow the 3 steps below: +Open the main application's solution in your IDE, find the `ModularCrmDbContext` class under the `ModularCrm` project's `Data` folder, and follow the 3 steps below: **(1)** Add the following attribute on top of the `ModularCrmDbContext` class: @@ -171,8 +171,6 @@ The `ReplaceDbContext` attribute allows the use of the `ModularCrmDbContext` cla ````csharp public class ModularCrmDbContext : AbpDbContext, - ITenantManagementDbContext, - IIdentityDbContext, IProductsDbContext, IOrderingDbContext //NEW: IMPLEMENT THE INTERFACE { @@ -200,7 +198,7 @@ Now, we can add a new database migration. You can use Entity Framework Core's `A 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. -Right-click the `ModularCrm.EntityFrameworkCore` package and select the *EF Core CLI* -> *Add Migration* command: +Right-click the `ModularCrm` package and select the *EF Core CLI* -> *Add Migration* command: ![abp-studio-add-entity-framework-core-migration](images/abp-studio-add-entity-framework-core-migration.png) @@ -208,11 +206,11 @@ The *Add Migration* command opens a new dialog to get a migration name: ![abp-studio-entity-framework-core-add-migration-order](images/abp-studio-entity-framework-core-add-migration-order.png) -Once you click the *OK* button, a new database migration class is added to the `Migrations` folder of the `ModularCrm.EntityFrameworkCore` project: +Once you click the *OK* button, a new database migration class is added to the `Migrations` folder of the `ModularCrm` project: ![visual-studio-new-migration-class-2](images/visual-studio-new-migration-class-2.png) -Now, you can return to ABP Studio, right-click the `ModularCrm.EntityFrameworkCore` project and select the *EF Core CLI* -> *Update Database* command: +Now, you can return to ABP Studio, right-click the `ModularCrm` project and select the *EF Core CLI* -> *Update Database* command: ![abp-studio-entity-framework-core-update-database](images/abp-studio-entity-framework-core-update-database.png) @@ -347,14 +345,14 @@ public class OrderAppService : OrderingAppService, IOrderAppService } ```` -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: +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() { Configure(options => { - options.ConventionalControllers.Create(typeof(ModularCrmApplicationModule).Assembly); + options.ConventionalControllers.Create(typeof(ModularCrmModule).Assembly); options.ConventionalControllers.Create(typeof(ProductsApplicationModule).Assembly); //ADD THE FOLLOWING LINE: @@ -369,7 +367,7 @@ This section will create a few example orders using the [Swagger UI](../../frame 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. +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. 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: @@ -487,11 +485,11 @@ public class OrderingMenuContributor : IMenuContributor ### 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: +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` application, and select the *Build* -> *Graph Build* command: ![abp-studio-solution-runner-graph-build](images/abp-studio-solution-runner-graph-build.png) -We've performed a graph build since we've made a change on a module, and more than building the main application is needed. *Graph Build* command also builds the depended modules if necessary. Alternatively, you could build the Ordering module first (on ABP Studio or your IDE). This approach can be faster if you have too many modules and you make a change in one of the modules. Now you can run the application by right-clicking the `ModularCrm.Web` application and selecting the *Start* command. +We've performed a graph build since we've made a change on a module, and more than building the main application is needed. *Graph Build* command also builds the depended modules if necessary. Alternatively, you could build the Ordering module first (on ABP Studio or your IDE). This approach can be faster if you have too many modules and you make a change in one of the modules. Now you can run the application by right-clicking the `ModularCrm` application and selecting the *Start* command. ![abp-studio-browser-orders-menu-item](images/abp-studio-browser-orders-menu-item.png) diff --git a/docs/en/tutorials/modular-crm/part-07.md b/docs/en/tutorials/modular-crm/part-07.md index ee6b67cb23..4bcace4239 100644 --- a/docs/en/tutorials/modular-crm/part-07.md +++ b/docs/en/tutorials/modular-crm/part-07.md @@ -19,7 +19,7 @@ Another common approach to communicating between modules is messaging. By publis ABP provides two types of event buses for loosely coupled communication: * [Local Event Bus](../../framework/infrastructure/event-bus/local/index.md) is suitable for in-process messaging. Since in a modular monolith, both of publisher and subscriber are in the same process, they can communicate in-process, without needing an external message broker. -* **[Distributed Event Bus](../../framework/infrastructure/event-bus/distributed/index.md)** is normal for inter-process messaging, like microservices, for publishing and subscribing to distributed events. However, ABP's distributed event bus works as local (in-process) by default (actually, it uses the Local Event Bus under the hood by default) unless you configure an external message broker. +* [Distributed Event Bus](../../framework/infrastructure/event-bus/distributed/index.md) is normal for inter-process messaging, like microservices, for publishing and subscribing to distributed events. However, ABP's distributed event bus works as local (in-process) by default (actually, it uses the Local Event Bus under the hood by default) unless you configure an external message broker. If you consider converting your modular monolith to a microservice system later, it is best to use the Distributed Event Bus with default local/in-process implementation. It already supports database-level transactional event execution and has no performance penalty. If you switch to an external provider ([RabbitMQ](../../framework/infrastructure/event-bus/distributed/rabbitmq.md), [Kafka](../../framework/infrastructure/event-bus/distributed/kafka.md), etc.), you don't need to change your application code. @@ -218,7 +218,7 @@ We inject the product repository and update the stock count in the event handler To keep this tutorial more focused, we will not create a UI for creating an order. You can easily create a form to create an order on your user interface. In this section, we will test it just using the Swagger UI. -Graph build the `ModularCrm.Web` application, run it on the ABP Studio's *Solution Runner* panel and browse the application UI as demonstrated earlier. +Graph build the `ModularCrm` application, run it on the ABP Studio's *Solution Runner* panel and browse the application UI as demonstrated earlier. Once the application is running and ready, manually type `/swagger` to the end of the URL and press the ENTER key. You should see the Swagger UI that is used to discover and test your HTTP APIs: @@ -228,8 +228,8 @@ Find the *Orders* API, click the *Try it out* button, enter a sample value the t ````json { - "productId": "0fbf7dd0-d7e9-0d18-9214-3a14d9fa1b74", - "customerName": "David" + "customerName": "David", + "productId": "e6ce1629-cfb1-1af6-e71c-3a16f10f9cc5" } ```` diff --git a/docs/en/tutorials/modular-crm/part-08.md b/docs/en/tutorials/modular-crm/part-08.md index 20efa3d7bd..6291875d5c 100644 --- a/docs/en/tutorials/modular-crm/part-08.md +++ b/docs/en/tutorials/modular-crm/part-08.md @@ -43,7 +43,7 @@ We will define the `IOrderReportingAppService` interface in the `ModularCrm.Appl As the first step, we should reference the `ModularCrm.Ordering.Contracts` package (of the `ModularCrm.Ordering` module) since we will reuse the `OrderState` enum defined in that package. -Open the ABP Studio's *Solution Explorer* panel, right-click the `ModularCrm.Application.Contracts` package and select the *Add Package Reference* command: +Open the ABP Studio's *Solution Explorer* panel, right-click the `ModularCrm` package and select the *Add Package Reference* command: ![abp-studio-add-package-reference-5](images/abp-studio-add-package-reference-5.png) @@ -55,7 +55,7 @@ The package reference has been added, and we can now use the types in the `Modul #### Defining the `IOrderReportingAppService` Interface -Open the main `ModularCrm` .NET solution in your IDE, find the `ModularCrm.Application.Contracts` project, create an `Orders` folder and add an `IOrderReportingAppService` interface. Here is the definition of that interface: +Open the main `ModularCrm` .NET solution in your IDE, create an `Orders` folder under the `Services` folder and add an `IOrderReportingAppService` interface. Here is the definition of that interface: ````csharp using System.Collections.Generic; @@ -71,7 +71,7 @@ namespace ModularCrm.Orders } ```` -We have a single method, `GetLatestOrders`, that will return a list of the latest orders. We should also define the `OrderReportDto` class that that method returns. Create the following class in the same `Orders` folder: +We have a single method, `GetLatestOrders`, that will return a list of the latest orders. We should also define the `OrderReportDto` class that that method returns. Create the `Orders` folder under the `Services/Dtos` folder and create a class named `OrderReportDto`. ````csharp using System; @@ -93,7 +93,7 @@ namespace ModularCrm.Orders } ```` -`OrderReportDto` contains data from both the `Order` and `Product` entities. We could use the `OrderState` since we have a reference to the package that defines that enum. +`OrderReportDto` contains data from both the `Order` and `Product` entities. We could use the `OrderState` since we have a reference to the package that defines that enum. After adding these files, the final folder structure should be like this: @@ -101,9 +101,7 @@ After adding these files, the final folder structure should be like this: ### Implementing the `OrderReportingAppService` Class -Create an `Orders` folder inside the `ModularCrm.Application` project and add a class named `OrderReportingAppService` inside it. The final folder structure should be like this: - -![visual-studio-order-reporting-app-service-impl](images/visual-studio-order-reporting-app-service-impl.png) +Create a class named `OrderReportingAppService` under the `Services/Orders` folder. Open the `OrderReportingAppService.cs` file and change its content by the following code block: @@ -176,7 +174,7 @@ Open the ABP Studio UI, stop the application if it is running, build and run it Here, find the `OrderReporting` API and execute it as shown above. You should get the order objects with product names. -Alternatively, you can visit the `/api/app/order-reporting/latest-orders` URL to directly execute the HTTP API on the browser (you should write the full URL, like `https://localhost:44358/api/app/order-reporting/latest-orders` - port can be different for your case) +Alternatively, you can visit the `/api/app/order-reporting/latest-orders` URL to directly execute the HTTP API on the browser (you should write the full URL, like `https://localhost:44303/api/app/order-reporting/latest-orders` - port can be different for your case) ## Summary @@ -190,7 +188,7 @@ Now, you know the fundamental principles and mechanics of building sophisticated ## Download the Source Code -You can download the completed sample solution [here](https://github.com/abpframework/abp-samples/tree/master/ModularCRM). +You can download the completed sample solution [here](https://github.com/abpframework/abp-samples/tree/master/ModularCrm). ## See Also diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs index 10900f6ee4..097fd899a5 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs @@ -242,13 +242,19 @@ public class CachedObjectExtensionsDtoService : ICachedObjectExtensionsDtoServic e => e.GetProperties() ) ) - .Where(p => p.Type.IsEnum) + .Where(p => p.Type.IsEnum || TypeHelper.IsNullableEnum(p.Type)) .ToList(); foreach (var enumProperty in enumProperties) { - // ReSharper disable once AssignNullToNotNullAttribute (enumProperty.Type.FullName can not be null for this case) - objectExtensionsDto.Enums[enumProperty.Type.FullName!] = CreateExtensionEnumDto(enumProperty); + if (TypeHelper.IsNullableEnum(enumProperty.Type)) + { + objectExtensionsDto.Enums[Nullable.GetUnderlyingType(enumProperty.Type)!.FullName + "?"] = CreateExtensionEnumDto(enumProperty); + } + else + { + objectExtensionsDto.Enums[enumProperty.Type.FullName!] = CreateExtensionEnumDto(enumProperty); + } } } @@ -260,12 +266,23 @@ public class CachedObjectExtensionsDtoService : ICachedObjectExtensionsDtoServic LocalizationResource = enumProperty.GetLocalizationResourceNameOrNull() }; - foreach (var enumValue in enumProperty.Type.GetEnumValues()) + var enumType = enumProperty.Type.IsEnum + ? enumProperty.Type + : TypeHelper.IsNullableEnum(enumProperty.Type) + ? Nullable.GetUnderlyingType(enumProperty.Type) + : null; + + if (enumType == null) + { + return extensionEnumDto; + } + + foreach (var enumValue in enumType.GetEnumValues()) { extensionEnumDto.Fields.Add( new ExtensionEnumFieldDto { - Name = enumProperty.Type.GetEnumName(enumValue)!, + Name = enumType.GetEnumName(enumValue)!, Value = enumValue } ); diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs index 1efa6c4f29..f9fdea3e50 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs @@ -84,6 +84,14 @@ public static class TypeHelper return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); } + public static bool IsNullableEnum(Type type) + { + return type.IsGenericType && + type.GetGenericTypeDefinition() == typeof(Nullable<>) && + type.GenericTypeArguments.Length == 1 && + type.GenericTypeArguments[0].IsEnum; + } + public static Type GetFirstGenericArgumentIfNullable(this Type t) { if (t.GetGenericArguments().Length > 0 && t.GetGenericTypeDefinition() == typeof(Nullable<>)) @@ -309,6 +317,10 @@ public static class TypeHelper { return "object"; } + else if (type.IsEnum) + { + return "enum"; + } return type.FullName ?? type.Name; }