diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index c160bacfde..2ede960b12 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -174,11 +174,11 @@ "path": "tutorials/modular-crm/part-01.md" }, { - "text": "2: Creating the Initial Products Module", + "text": "2: Setting Up the Catalog Module", "path": "tutorials/modular-crm/part-02.md" }, { - "text": "3: Building the Products Module", + "text": "3: Building the Catalog Module", "path": "tutorials/modular-crm/part-03.md" }, { diff --git a/docs/en/get-started/single-layer-web-application.md b/docs/en/get-started/single-layer-web-application.md index 5c0a256c42..23fdb33754 100644 --- a/docs/en/get-started/single-layer-web-application.md +++ b/docs/en/get-started/single-layer-web-application.md @@ -23,7 +23,7 @@ First things first! Let's setup your development environment before creating the ## Creating a New Solution -> 🛈 This document uses [ABP Studio](../studio/index.md) to create new ABP solutions. **ABP Studio** is in the beta version now. If you have any issues, you can use the [ABP CLI](../cli/index.md) to create new solutions. You can also use the [getting started page](https://abp.io/get-started) to easily build ABP CLI commands for new project creations. +> 🛈 This document uses [ABP Studio](../studio/index.md) to create new ABP solutions. You can also use the [ABP CLI](../cli/index.md) to create new solutions and use the [getting started page](https://abp.io/get-started) to easily build ABP CLI commands. > ABP startup solution templates have many options for your specific needs. If you don't understand an option that probably means you don't need it. We selected common defaults for you, so you can leave these options as they are. @@ -31,7 +31,7 @@ Assuming that you have [installed and logged in](../studio/installation.md) to t ![abp-studio-welcome-screen](images/abp-studio-welcome-screen.png) -Select the *File* -> *New Solution* in the main menu, or click the *New solution* button on the Welcome screen to open the *Create new solution* wizard: +Select the *File* -> *New Solution* in the main menu, or click the *New solution* button on the *Welcome* screen to open the *Create new solution* wizard: ![abp-studio-new-solution-dialog](images/abp-studio-no-layers-new-solution-dialog-0.9.13.png) @@ -75,7 +75,7 @@ Here, you can select the database management systems (DBMS){{ if DB == "EF" }} a Now, we are ready to allow ABP Studio to create our solution. Just click the *Create* button and let the ABP Studio do the rest for you. -After clicking the Create button, the dialog is closed and your solution is loaded into ABP Studio: +After clicking the *Create* button, the dialog is closed and your solution is loaded into ABP Studio: ![abp-studio-created-new-solution](images/abp-studio-no-layers-created-new-solution.png) diff --git a/docs/en/samples/index.md b/docs/en/samples/index.md index 6aea3d3fa5..8676400f99 100644 --- a/docs/en/samples/index.md +++ b/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 diff --git a/docs/en/studio/installation.md b/docs/en/studio/installation.md index 34dde8a5c9..fb657fba47 100644 --- a/docs/en/studio/installation.md +++ b/docs/en/studio/installation.md @@ -1,7 +1,6 @@ # Installing ABP Studio -> **Warning: Beta Version Information**\ -> Currently, ABP Studio is in its beta phase and available for everyone. To access the beta version, kindly visit [this web page](https://abp.io/studio). +This document explains how to install the ABP Studio tool. ## Pre-requirements @@ -10,7 +9,11 @@ Before you begin the installation process for ABP Studio, ensure that your syste ### Node Make sure [Node.js](https://nodejs.org/en) is installed on your system. If you have not installed Node.js, you can download the `v22+` version from the official [Node.js website](https://nodejs.org/en/download/prebuilt-installer). +### Docker +ABP Studio needs [Docker](https://www.docker.com/) for [Kubernetes](https://kubernetes.io/) operations. Install Docker by following the guidelines on the official [Docker website](https://docs.docker.com/get-docker/). + ### WireGuard (Optional) + ABP Studio needs [WireGuard](https://www.wireguard.com/) for Kubernetes operations. You can find the installation instructions for your specific operating system below: **For Windows:** @@ -19,9 +22,6 @@ Installation instructions for your Windows operating system are on the official **For macOS:** Installation instructions for your macOS operating system are on the official [WireGuard website](https://www.wireguard.com/install/#macos-homebrew-and-macports-basic-cli-homebrew-userspace-go-homebrew-tools-macports-userspace-go-macports-tools). -### Docker -ABP Studio needs [Docker](https://www.docker.com/) for [Kubernetes](https://kubernetes.io/) operations. Install Docker by following the guidelines on the official [Docker website](https://docs.docker.com/get-docker/). - ## Installation Now you have met the pre-requirements, follow the steps below to install ABP Studio: 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 ee8b847a45..3c5fb3d513 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-folder-command-2.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-new-folder-command-2.png new file mode 100644 index 0000000000..e0b9ebe352 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-add-new-folder-command-2.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-4-v2.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-4-v2.png new file mode 100644 index 0000000000..e3b42bb47c Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-4-v2.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 deleted file mode 100644 index c9c3a36596..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-4.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-dialog-3.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-dialog-3.png index 91f0e9f948..c74c90acba 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-dialog-3.png and b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-dialog-3.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-dialog-4.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-dialog-4.png index 8480ee8151..d56dc7e82e 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-dialog-4.png and b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-dialog-4.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-browser-list-of-orders-with-product-name.png b/docs/en/tutorials/modular-crm/images/abp-studio-browser-list-of-orders-with-product-name.png index 50cfc86eb4..c14e7f52bc 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-browser-list-of-orders-with-product-name.png and b/docs/en/tutorials/modular-crm/images/abp-studio-browser-list-of-orders-with-product-name.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-browser-list-of-products.png b/docs/en/tutorials/modular-crm/images/abp-studio-browser-list-of-products.png index f204d55ed6..783948d369 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-browser-list-of-products.png and b/docs/en/tutorials/modular-crm/images/abp-studio-browser-list-of-products.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-browser-orders-menu-item-v2.png b/docs/en/tutorials/modular-crm/images/abp-studio-browser-orders-menu-item-v2.png new file mode 100644 index 0000000000..e053af426e Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-browser-orders-menu-item-v2.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 deleted file mode 100644 index e552239474..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-browser-orders-menu-item.png and /dev/null 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 bf9c9d1ead..a1174c5027 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-v2.png b/docs/en/tutorials/modular-crm/images/abp-studio-entity-framework-core-add-migration-order-v2.png new file mode 100644 index 0000000000..b812e94a92 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-entity-framework-core-add-migration-order-v2.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 deleted file mode 100644 index b739e59373..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-entity-framework-core-add-migration-order.png and /dev/null 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 88a0a20bb3..e415c98a68 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 8930bdd062..e6cd8c1b32 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-dialog-for-catalog.png b/docs/en/tutorials/modular-crm/images/abp-studio-import-module-dialog-for-catalog.png new file mode 100644 index 0000000000..56ee0a4462 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-import-module-dialog-for-catalog.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 2b07060c2a..2a806c3dc1 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-import-module-ordering.png b/docs/en/tutorials/modular-crm/images/abp-studio-import-module-ordering.png index 8956033762..c507cba032 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-imports-and-dependencies-v2.png b/docs/en/tutorials/modular-crm/images/abp-studio-imports-and-dependencies-v2.png new file mode 100644 index 0000000000..f012ba165c Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-imports-and-dependencies-v2.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-imports-and-dependencies.png b/docs/en/tutorials/modular-crm/images/abp-studio-imports-and-dependencies.png deleted file mode 100644 index 30d484ba6b..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-imports-and-dependencies.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-install-module-dialog-for-ordering-v2.png b/docs/en/tutorials/modular-crm/images/abp-studio-install-module-dialog-for-ordering-v2.png new file mode 100644 index 0000000000..ab5b47a9e9 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-install-module-dialog-for-ordering-v2.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-install-module-dialog-for-ordering.png b/docs/en/tutorials/modular-crm/images/abp-studio-install-module-dialog-for-ordering.png deleted file mode 100644 index 7ecb6b6262..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-install-module-dialog-for-ordering.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-install-module-dialog-v2.png b/docs/en/tutorials/modular-crm/images/abp-studio-install-module-dialog-v2.png new file mode 100644 index 0000000000..e46accb710 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-install-module-dialog-v2.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 deleted file mode 100644 index 8c9abf1bf7..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-install-module-dialog.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-modular-crm-with-standard-module.png b/docs/en/tutorials/modular-crm/images/abp-studio-modular-crm-with-standard-module.png index 02cec6ad68..44c22c64f8 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-modular-crm-with-standard-module.png and b/docs/en/tutorials/modular-crm/images/abp-studio-modular-crm-with-standard-module.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-module-installation-dialog-for-catalog.png b/docs/en/tutorials/modular-crm/images/abp-studio-module-installation-dialog-for-catalog.png new file mode 100644 index 0000000000..35c602c353 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-module-installation-dialog-for-catalog.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-open-with-visual-studio-catalog.png b/docs/en/tutorials/modular-crm/images/abp-studio-open-with-visual-studio-catalog.png new file mode 100644 index 0000000000..5c895646a8 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-open-with-visual-studio-catalog.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-open-with-visual-studio-code-catalog.png b/docs/en/tutorials/modular-crm/images/abp-studio-open-with-visual-studio-code-catalog.png new file mode 100644 index 0000000000..6e94565360 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-open-with-visual-studio-code-catalog.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser-v2.png b/docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser-v2.png new file mode 100644 index 0000000000..5e0c1e59ad Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser-v2.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 deleted file mode 100644 index feb6627be7..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-solution-explorer-with-folders-v2.png b/docs/en/tutorials/modular-crm/images/abp-studio-solution-explorer-with-folders-v2.png new file mode 100644 index 0000000000..ee817fd97e Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-solution-explorer-with-folders-v2.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-initial-catalog-page.png b/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-initial-catalog-page.png new file mode 100644 index 0000000000..c1bab4f9f3 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-initial-catalog-page.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-order-execute-v2.png b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-order-execute-v2.png new file mode 100644 index 0000000000..7a96cee1c0 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-order-execute-v2.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 deleted file mode 100644 index 6d4f5320ee..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-order-execute.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-execute.png b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-execute.png index c85348933a..10c8423ce0 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-execute.png and b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-execute.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-try.png b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-try.png index 8229656886..805d5689a8 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-try.png and b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-try.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 d412e0c509..fdbb5f96b7 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/catalog-module-vs-code.png b/docs/en/tutorials/modular-crm/images/catalog-module-vs-code.png new file mode 100644 index 0000000000..11e65c0447 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/catalog-module-vs-code.png differ diff --git a/docs/en/tutorials/modular-crm/images/modular-crm-wizard-modularity-step.png b/docs/en/tutorials/modular-crm/images/modular-crm-wizard-modularity-step.png new file mode 100644 index 0000000000..c70874aec9 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/modular-crm-wizard-modularity-step.png differ diff --git a/docs/en/tutorials/modular-crm/images/solution-explorer-modular-crm-initial-with-modules.png b/docs/en/tutorials/modular-crm/images/solution-explorer-modular-crm-initial-with-modules.png new file mode 100644 index 0000000000..f664910c58 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/solution-explorer-modular-crm-initial-with-modules.png differ diff --git a/docs/en/tutorials/modular-crm/images/sql-server-orders-database-table-filled-v2.png b/docs/en/tutorials/modular-crm/images/sql-server-orders-database-table-filled-v2.png new file mode 100644 index 0000000000..1d042e9b29 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/sql-server-orders-database-table-filled-v2.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 deleted file mode 100644 index 75c812593c..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/sql-server-orders-database-table-filled.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/images/sql-server-orders-database-table-v2.png b/docs/en/tutorials/modular-crm/images/sql-server-orders-database-table-v2.png new file mode 100644 index 0000000000..ccd870b06f Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/sql-server-orders-database-table-v2.png differ diff --git a/docs/en/tutorials/modular-crm/images/sql-server-orders-database-table.png b/docs/en/tutorials/modular-crm/images/sql-server-orders-database-table.png deleted file mode 100644 index 2090a35673..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/sql-server-orders-database-table.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/images/sql-server-products-database-table-filled-v2.png b/docs/en/tutorials/modular-crm/images/sql-server-products-database-table-filled-v2.png new file mode 100644 index 0000000000..6152ab2b9b Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/sql-server-products-database-table-filled-v2.png differ diff --git a/docs/en/tutorials/modular-crm/images/sql-server-products-database-table-filled.png b/docs/en/tutorials/modular-crm/images/sql-server-products-database-table-filled.png deleted file mode 100644 index 051f2044f8..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/sql-server-products-database-table-filled.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/images/sql-server-products-database-table-v2.png b/docs/en/tutorials/modular-crm/images/sql-server-products-database-table-v2.png new file mode 100644 index 0000000000..ebc25a7b10 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/sql-server-products-database-table-v2.png differ diff --git a/docs/en/tutorials/modular-crm/images/sql-server-products-database-table.png b/docs/en/tutorials/modular-crm/images/sql-server-products-database-table.png deleted file mode 100644 index 803c008524..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/sql-server-products-database-table.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class-2-v2.png b/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class-2-v2.png new file mode 100644 index 0000000000..f32a6c1544 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class-2-v2.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 deleted file mode 100644 index c5a698db58..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class-2.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-order-entity-v2.png b/docs/en/tutorials/modular-crm/images/visual-studio-order-entity-v2.png new file mode 100644 index 0000000000..70828b7d40 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/visual-studio-order-entity-v2.png differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-order-entity.png b/docs/en/tutorials/modular-crm/images/visual-studio-order-entity.png deleted file mode 100644 index 112ee3d7d3..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/visual-studio-order-entity.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-order-event-handler-v2.png b/docs/en/tutorials/modular-crm/images/visual-studio-order-event-handler-v2.png new file mode 100644 index 0000000000..6677e2b251 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/visual-studio-order-event-handler-v2.png differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-order-event-handler.png b/docs/en/tutorials/modular-crm/images/visual-studio-order-event-handler.png deleted file mode 100644 index cae742abb3..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/visual-studio-order-event-handler.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-ordering-contracts-v2.png b/docs/en/tutorials/modular-crm/images/visual-studio-ordering-contracts-v2.png new file mode 100644 index 0000000000..b4beff3304 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/visual-studio-ordering-contracts-v2.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 deleted file mode 100644 index 512edaa4a1..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/visual-studio-ordering-contracts.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/images/vs-code-catalog-contracts.png b/docs/en/tutorials/modular-crm/images/vs-code-catalog-contracts.png new file mode 100644 index 0000000000..36019f5d73 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/vs-code-catalog-contracts.png differ diff --git a/docs/en/tutorials/modular-crm/images/vscode-catalog-cshtml.png b/docs/en/tutorials/modular-crm/images/vscode-catalog-cshtml.png new file mode 100644 index 0000000000..186dd9c0d6 Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/vscode-catalog-cshtml.png differ diff --git a/docs/en/tutorials/modular-crm/images/vscode-product-integration-service.png b/docs/en/tutorials/modular-crm/images/vscode-product-integration-service.png new file mode 100644 index 0000000000..3d0839f61a Binary files /dev/null and b/docs/en/tutorials/modular-crm/images/vscode-product-integration-service.png differ diff --git a/docs/en/tutorials/modular-crm/index.md b/docs/en/tutorials/modular-crm/index.md index 723425a230..b7a94994ad 100644 --- a/docs/en/tutorials/modular-crm/index.md +++ b/docs/en/tutorials/modular-crm/index.md @@ -19,17 +19,17 @@ ABP provides a great infrastructure and tooling to build modular software soluti This tutorial is organized as the following parts: * [Part 01: Creating the initial solution](part-01.md) -* [Part 02: Creating the initial Products module](part-02.md) -* [Part 03: Building the Products module](part-03.md) -* [Part 04: Creating the initial Ordering module](part-04.md) -* [Part 05: Building the Ordering module](part-05.md) +* [Part 02: Setting Up the Catalog Module](part-02.md) +* [Part 03: Building the Catalog Module](part-03.md) +* [Part 04: Creating the initial Ordering Module](part-04.md) +* [Part 05: Building the Ordering Module](part-05.md) * [Part 06: Integrating the modules: Implementing Integration Services](part-06.md) * [Part 07: Integrating the modules: Communication via Messages (Events)](part-07.md) * [Part 08: Integrating the modules: Joining the Products and Orders Data](part-08.md) ## 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-Standard). ## See Also diff --git a/docs/en/tutorials/modular-crm/part-01.md b/docs/en/tutorials/modular-crm/part-01.md index ddc6e8debe..3272ec5124 100644 --- a/docs/en/tutorials/modular-crm/part-01.md +++ b/docs/en/tutorials/modular-crm/part-01.md @@ -8,34 +8,53 @@ "Path": "tutorials/modular-crm/index" }, "Next": { - "Name": "Creating the initial Products module", + "Name": "Setting Up the Catalog Module", "Path": "tutorials/modular-crm/part-02" } } ```` +In this first part of this tutorial, we will create a new ABP solution with modularity enabled. + +## Getting Started with a new ABP Solution + 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 * **Database Provider**: Entity Framework Core -You can select the other options based on your preference. +You can select the other options based on your preference but at the **Modularity** step, check the _Setup as a modular solution_ option and add a new **Standard Module** named `ModularCrm.Catalog`: + +![](./images/modular-crm-wizard-modularity-step.png) + +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 using the *Standard Module* template. + +> **Note:** This tutorial will guide you through creating two modules: `Catalog` and `Ordering`. We've just created the `Catalog` module in the _Modularity_ step. You could also create the `Ordering` module at this stage. However, we'll create the `Ordering` module later in this tutorial 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. > **Please complete the [Get Started](../../get-started/single-layer-web-application.md) guide and run the web application before going further.** +## The Solution Structure + The initial solution structure should be like the following in ABP Studio's *[Solution Explorer](../../studio/solution-explorer.md)*: -![solution-explorer-modular-crm-initial](images/solution-explorer-modular-crm-initial.png) +![solution-explorer-modular-crm-initial-with-modules](images/solution-explorer-modular-crm-initial-with-modules.png) -Initially, you see a `ModularCrm` solution and a `ModularCrm` module under that solution. +Initially, you see a `ModularCrm` solution, a `ModularCrm` module under that solution (our main single layer application), and a `modules` folder that contains the `ModularCrm.Catalog` module and its sub .NET projects. > 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). -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: +The `ModularCrm` module is the core of your application, built as a single-layer ASP.NET Core Web application. On the other hand, the `ModularCrm.Catalog` module consist of four packages (.NET projects) and used to implement the catalog module's functionality. + +## Catalog Module's Packages + +Here are the .NET projects (ABP Studio packages) of the Catalog module: -![solution-explorer-modular-crm-expanded](images/solution-explorer-modular-crm-expanded.png) +- `ModularCrm.Catalog`: The main module project that contains your [entities](../../framework/architecture/domain-driven-design/entities.md), [application service](../../framework/architecture/domain-driven-design/application-services.md) implementations and other business objects +- `ModularCrm.Catalog.Contracts`: Basically contains [application service](../../framework/architecture/domain-driven-design/application-services.md) interfaces and [DTOs](../../framework/architecture/domain-driven-design/data-transfer-objects.md) +- `ModularCrm.Catalog.Tests`: Unit and integration tests (if you selected the _Include Tests_ option) +- `ModularCrm.Catalog.UI`: Contains user interface pages components for the module ## 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 modular solution with a Catalog module included. In the next part, you will learn how install the Catalog module 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 034b4b7bfe..e175823350 100644 --- a/docs/en/tutorials/modular-crm/part-02.md +++ b/docs/en/tutorials/modular-crm/part-02.md @@ -1,4 +1,4 @@ -# Creating the Initial Products Module +# Setting Up the Catalog Module ````json //[doc-nav] @@ -8,97 +8,31 @@ "Path": "tutorials/modular-crm/part-01" }, "Next": { - "Name": "Building the Products module", + "Name": "Building the Catalog module", "Path": "tutorials/modular-crm/part-03" } } ```` -In this part, you will create a new product management module and install it in the main CRM application. +In this part, you will install the `ModularCrm.Catalog` module to the main application, which was created in the [previous part](part-01.md). -## Creating Solution Folders +## Creating an ABP Studio Solution Folder -You can create solution folders and sub-folders in *Solution Explorer* to organize your solution components better. Right-click to the solution root on the *Solution Explorer* panel, and select *Add* -> *New Folder* command: +Before installing the `ModularCrm.Catalog` module to the main application, let's create a "main" folder and move the `ModularCrm` module (the main application) to it to tidy up the solution structure. Right-click to the solution root on the *Solution Explorer* panel, and select *Add* -> *New Folder* command: -![abp-studio-add-new-folder-command](images/abp-studio-add-new-folder-command.png) +![abp-studio-add-new-folder-command-2](images/abp-studio-add-new-folder-command-2.png) -That command opens a dialog where you can set the folder name: +That command opens a dialog where you can set the *Folder name*: ![abp-studio-new-folder-dialog](images/abp-studio-new-folder-dialog.png) -Create a `main` and a `modules` folder using the *New Folder* command, then move the `ModularCrm` module into the `main` folder (simply by drag & drop). The *Solution Explorer* panel should look like the following figure now: +After the folder is created, now you can move the `ModularCrm` module to the `main` folder (simply by drag & drop). The _Solution Explorer_ panel should look like the following figure now: -![abp-studio-solution-explorer-with-folders](images/abp-studio-solution-explorer-with-folders.png) +![abp-studio-solution-explorer-with-folders](images/abp-studio-solution-explorer-with-folders-v2.png) -## Creating The Module +## Installing the Catalog Module to the Main Application -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 *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: - -![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`: - -![abp-studio-create-new-module-dialog](images/abp-studio-create-new-module-dialog.png) - -When you click the *Next* button, you are redirected to the UI selection step. - -### Selecting the UI Type - -Here, you can select the UI type you want to support in your module: - -![abp-studio-create-new-module-dialog-step-ui](images/abp-studio-create-new-module-dialog-step-ui.png) - -A module; - -* May not contain a UI and leaves the UI development to the final application. -* May contain a single UI implementation that is typically in the same technology as the main application. -* May contain more than one UI implementation if you want to create a reusable application module and you want to make that module usable by different applications with different UI technologies. For example, all of [pre-built ABP modules](https://abp.io/modules) support multiple UI options. - -In this tutorial, we are selecting the MVC UI since we are building that module only for our `ModularCrm` solution and we are using the MVC UI in our application. So, select the MVC UI and click the *Next* button. - -### Selecting the Database Provider - -The next step is to select the database provider (or providers) you want to support with your module: - -![abp-studio-create-new-module-dialog-step-db](images/abp-studio-create-new-module-dialog-step-db.png) - -Since our main application is using Entity Framework Core and we will use the `ModularCrm.Products` module only for that main application, we can select the *Entity Framework Core* option and click the *Next* button. - -![abp-studio-create-new-module-dialog-step-additional-options](images/abp-studio-create-new-module-dialog-step-additional-options.png) - -Lastly, you can uncheck the *Include Tests* option if you don't want to include test projects in your module. Click the *Create* button to create the new module. - -### Exploring the New Module - -After adding the new module, the *Solution Explorer* panel should look like the following figure: - -![abp-studio-solution-explorer-two-modules](images/abp-studio-solution-explorer-two-modules.png) - -The new `ModularCrm.Products` module has been created and added to the solution. The `ModularCrm.Products` module has a separate and independent .NET solution. Right-click the `ModularCrm.Products` module and select the *Open with* -> *Explorer* command: - -![abp-studio-open-in-explorer](images/abp-studio-open-in-explorer.png) - -This command opens the solution folder in your file system: - -![product-module-folder](images/product-module-folder.png) - -You can open `ModularCrm.Products.sln` in your favorite IDE (e.g. Visual Studio): - -![product-module-visual-studio](images/product-module-visual-studio.png) - -As seen in the preceding figure, the `ModularCrm.Products` solution consists of several layers, each has own responsibility. - -### Installing the Product Module to the Main Application - -A module does not contain an executable application inside. The `Modular.Products.Web` project is just a class library project, not an executable web application. A module should be installed in an executable application to run it. +A module does not contain an executable application inside. The `ModularCrm.Catalog.UI` project is just a class library project, not an executable web application. A module should be installed in an executable application to run it. > **Ensure that the web application is not running in [Solution Runner](../../studio/running-applications.md) or in your IDE. Installing a module to a running application will produce errors.** @@ -108,19 +42,17 @@ The product module has yet to be related to the main application. Right-click on The *Import Module* command opens a dialog as shown below: -![abp-studio-import-module-dialog](images/abp-studio-import-module-dialog.png) +![abp-studio-import-module-dialog-for-catalog](images/abp-studio-import-module-dialog-for-catalog.png) -Select the `ModularCrm.Products` module and check the *Install this module* option. If you don't check that option, it only imports the module but doesn't set project dependencies. Importing a module without installation can be used to set up your project dependencies manually. We want to make it automatically, so check the *Install this module* option. +Select the `ModularCrm.Catalog` module and check the *Install this module* option. If you don't check that option, it only imports the module but doesn't set project dependencies. Importing a module without installation can be used to set up your project dependencies manually. We want to make it automatically, so check the *Install this module* option. 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) +![abp-studio-module-installation-dialog-for-catalog](images/abp-studio-module-installation-dialog-for-catalog.png) -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. +Select the `ModularCrm.Catalog` and `ModularCrm.Catalog.UI` packages from the left area and ensure the `ModularCrm` package from the middle area was checked as shown in the preceding figure. Finally, click _OK_. -The default package match is good for this tutorial, so you can click the *OK* button to proceed. - -### Building the Main Application +## Building the Main Application After the installation, build the entire solution by right-clicking on the `ModularCrm` module (under the `main` folder) and selecting the *Dotnet CLI* -> *Graph Build* command: @@ -132,12 +64,10 @@ 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` 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 *Catalog* page on the main menu of the application to see the Catalog page that is coming from the `ModularCrm.Catalog` module: -![abp-studio-solution-runner-initial-product-page](images/abp-studio-solution-runner-initial-product-page.png) +![abp-studio-solution-runner-initial-catalog-page](images/abp-studio-solution-runner-initial-catalog-page.png) ## Summary -In this part, we've created a new module to manage products in our modular application. Then we installed the new module to the main application and run the solution to test if it has successfully installed. - -In the next part, you will learn how to create entities, services and a basic user interface for the products module. +In this part, you installed the `ModularCrm.Catalog` module to the main application and run the solution to test if it has successfully installed. In the [next part](part-03.md), you will learn how to create entities, services and a basic user interface for the catalog module. diff --git a/docs/en/tutorials/modular-crm/part-03.md b/docs/en/tutorials/modular-crm/part-03.md index 38c6553fa3..b9862afd4b 100644 --- a/docs/en/tutorials/modular-crm/part-03.md +++ b/docs/en/tutorials/modular-crm/part-03.md @@ -1,10 +1,10 @@ -# Building the Products Module +# Building the Catalog Module ````json //[doc-nav] { "Previous": { - "Name": "Creating the initial Products module", + "Name": "Setting Up the Catalog Module", "Path": "tutorials/modular-crm/part-02" }, "Next": { @@ -14,7 +14,7 @@ } ```` -In this part, you will learn how to create entities and services and a basic user interface for the products module. +In this part, you will learn how to create entities, services and a basic user interface for the catalog module. > **This module's functionality will be minimal to focus on modularity.** You can follow the [Book Store tutorial](../book-store/index.md) to learn building more real-world applications with ABP. @@ -22,21 +22,21 @@ If it is still running, please stop the web application before continuing with t ## Creating a `Product` Entity -Open the `ModularCrm.Products` module in your favorite IDE. You can right-click the `ModularCrm.Products` module and select the *Open With* -> *Visual Studio* command to open the `ModularCrm.Products` module's .NET solution with Visual Studio. If you can not find your IDE in the *Open with* list, open with the *Explorer*, then open the `.sln` file with your IDE: +Open the `ModularCrm.Catalog` module in your favorite IDE. You can right-click the `ModularCrm.Catalog` module and select the *Open With* -> *Visual Studio Code* command to open the `ModularCrm.Catalog` module's .NET solution with Visual Studio. If you can not find your IDE in the *Open with* list, open with the *Explorer*, then open the `.sln` file with your IDE: -![abp-studio-open-with-visual-studio](images/abp-studio-open-with-visual-studio.png) +![abp-studio-open-with-visual-studio-code-catalog](images/abp-studio-open-with-visual-studio-code-catalog.png) -The `ModularCrm.Products` .NET solution should look like the following figure: +The `ModularCrm.Catalog` .NET solution should look like the following figure: -![product-module-visual-studio](images/product-module-visual-studio.png) +![catalog-module-vs-code](images/catalog-module-vs-code.png) -Add a new `Product` class under the `ModularCrm.Products.Domain` project (Right-click the `ModularCrm.Products.Domain` project, select *Add* -> *Class*): +Add a new `Product` class under the `ModularCrm.Catalog` project: ````csharp using System; using Volo.Abp.Domain.Entities; -namespace ModularCrm.Products; +namespace ModularCrm.Catalog; public class Product : AggregateRoot { @@ -45,78 +45,80 @@ public class Product : AggregateRoot } ```` +> Note that in this tutorial, we create classes directly in the project's root folder to keep things simple. It is up to you to create subfolders (namespaces) in your project to achieve finer code organization, especially for large modules. + ## Mapping Entity to Database The next step is to configure the Entity Framework Core `DbContext` class and the database for the new entity. ### Add a `DbSet` Property -Open the `ProductsDbContext` in the `ModularCrm.Products.EntityFrameworkCore` project and add a new `DbSet` property for the `Product` entity. The final `ProductsDbContext.cs` file content should be the following: +Open the `CatalogDbContext` under the **Data** folder in the same project and add a new `DbSet` property for the `Product` entity. The final `CatalogDbContext.cs` file content should be the following: ````csharp using Microsoft.EntityFrameworkCore; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; -namespace ModularCrm.Products.EntityFrameworkCore; +namespace ModularCrm.Catalog.Data; -[ConnectionStringName(ProductsDbProperties.ConnectionStringName)] -public class ProductsDbContext : AbpDbContext, IProductsDbContext +[ConnectionStringName(CatalogDbProperties.ConnectionStringName)] +public class CatalogDbContext : AbpDbContext, ICatalogDbContext { public DbSet Products { get; set; } //NEW: DBSET FOR THE PRODUCT ENTITY - public ProductsDbContext(DbContextOptions options) + public CatalogDbContext(DbContextOptions options) : base(options) { - } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); - builder.ConfigureProducts(); + builder.ConfigureCatalog(); } } + ```` -The `ProductsDbContext` class implements the `IProductsDbContext` interface. Add the following property to the `IProductsDbContext` interface: +The `CatalogDbContext` class implements the `ICatalogDbContext` interface. Add the following property to the `ICatalogDbContext` interface: ````csharp DbSet Products { get; set; } ```` -The final `IProductsDbContext` interface should be the following: +The final `ICatalogDbContext` interface should be the following: ````csharp using Microsoft.EntityFrameworkCore; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; -namespace ModularCrm.Products.EntityFrameworkCore; +namespace ModularCrm.Catalog.Data; -[ConnectionStringName(ProductsDbProperties.ConnectionStringName)] -public interface IProductsDbContext : IEfCoreDbContext +[ConnectionStringName(CatalogDbProperties.ConnectionStringName)] +public interface ICatalogDbContext : IEfCoreDbContext { DbSet Products { get; set; } } ```` -Having such an `IProductsDbContext` interface allows us to decouple our repositories (and other classes) from the concrete `ProductsDbContext` class. This provides flexibility to the final application to merge multiple `DbContext`s into a single `DbContext` to manage database migrations easier and have a database level transaction support for multi-module database operations. +Having such an `ICatalogDbContext` interface allows us to decouple our repositories (and other classes) from the concrete `CatalogDbContext` class. This provides flexibility to the final application to merge multiple `DbContext`s into a single `DbContext` to manage database migrations easier and have a database level transaction support for multi-module database operations. We will do it later in this tutorial. ### Configure the Table Mapping -The DDD module template is designed to be flexible so that your module can have a separate physical database or store its tables inside another database, like the main database of your application. To make that possible, it configures the database mapping in an extension method (`ConfigureProducts`) called inside the `OnModelCreating` method above. Find that extension method (in the `ProductsDbContextModelCreatingExtensions` class) and change its content as the following code block: +The **Standard Module** template is designed to be flexible so that your module can have a separate physical database or store its tables inside another database (typically in the main database of your application). To make that possible, it configures the database mapping in an extension method (`ConfigureCatalog()`) called inside the `OnModelCreating` method above. Find that extension method (in the `CatalogDbContextModelCreatingExtensions` class) and change its content as the following code block: ````csharp using Microsoft.EntityFrameworkCore; using Volo.Abp; using Volo.Abp.EntityFrameworkCore.Modeling; -namespace ModularCrm.Products.EntityFrameworkCore; +namespace ModularCrm.Catalog.Data; -public static class ProductsDbContextModelCreatingExtensions +public static class CatalogDbContextModelCreatingExtensions { - public static void ConfigureProducts( + public static void ConfigureCatalog( this ModelBuilder builder) { Check.NotNull(builder, nameof(builder)); @@ -124,8 +126,8 @@ public static class ProductsDbContextModelCreatingExtensions builder.Entity(b => { //Configure table & schema name - b.ToTable(ProductsDbProperties.DbTablePrefix + "Products", - ProductsDbProperties.DbSchema); + b.ToTable(CatalogDbProperties.DbTablePrefix + "Products", + CatalogDbProperties.DbSchema); //Always call this method to setup base entity properties b.ConfigureByConvention(); @@ -137,28 +139,15 @@ public static class ProductsDbContextModelCreatingExtensions } ```` -First, we are setting the database table name with the `ToTable` method. `ProductsDbProperties.DbTablePrefix` defines a constant that is added as a prefix to all database table names of this module. If you see the `ProductsDbProperties` class (in the `ModularCrm.Products.Domain` project), `DbTablePrefix` value is `Products`. In that case, the table name for the `Product` entity will be `ProductsProducts`. That is unnecessary for such a simple module; we can remove that prefix. So, you can change the `ProductsDbProperties` class with the following content to set an empty string to the `DbTablePrefix` property: - -````csharp -namespace ModularCrm.Products; - -public static class ProductsDbProperties -{ - public static string DbTablePrefix { get; set; } = ""; - public static string? DbSchema { get; set; } = null; - public const string ConnectionStringName = "Products"; -} -```` +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`. You can change the `CatalogDbProperties` class if you are not happy with the default table prefix, or set a schema for your tables. -You can set a `DbSchema` to collect a module's tables under a separate schema (if your DBMS supports it) or use a `DbTablePrefix` as a prefix for all module table names. We won't set any of them for this tutorial. - -At that point, build the `ModularCrm.Products` .NET solution in your IDE (or ABP Studio UI). We will switch to the main application's .NET solution. +At that point, build the `ModularCrm.Catalog` .NET solution in your IDE (or on the ABP Studio UI). Then, switch to the main application's .NET solution. ### Configuring the Main Application Database -We changed the Entity Framework Core configuration. The next step should be adding a new code-first database migration and updating the database so the new Products table is created on the database. +You changed the Entity Framework Core configuration. The next step should be adding a new code-first database migration and updating the database so the new `CatalogProducts` table is created on the database. -We are not managing the database migrations in the module. Instead, the main application decides which DBMS (Database Management System) to use and how to share physical database(s) among modules. We will store all the modules' data in a single physical database to simplify this tutorial. +You are not managing the database migrations in the module. Instead, the main application decides which DBMS (Database Management System) to use and how to share physical database(s) among modules. We will store all the modules' data in a single physical database in this tutorial. Open the `ModularCrm` module (which is the main application) in your IDE: @@ -168,48 +157,40 @@ Open the `ModularCrmDbContext` class under the `ModularCrm` project's `Data` fol ![visual-studio-main-dbcontext](images/visual-studio-main-dbcontext.png) -We will merge module's database configuration into `ModularCrmDbContext`. +You will merge module's database configuration into `ModularCrmDbContext`. -#### Replace the `IProductsDbContext` Service +#### Replace the `ICatalogDbContext` Service Follow the three steps below; **(1)** Add the following attribute on top of the `ModularCrmDbContext` class: ````csharp -[ReplaceDbContext(typeof(IProductsDbContext))] +[ReplaceDbContext(typeof(ICatalogDbContext))] ```` -`ReplaceDbContext` attribute makes it possible to use the `ModularCrmDbContext` class in the services in the Products module. +`ReplaceDbContext` attribute makes it possible to use the `ModularCrmDbContext` class in the services in the Catalog module. -**(2)** Implement the `IProductsDbContext` by the `ModularCrmDbContext` class: +**(2)** Implement the `ICatalogDbContext` by the `ModularCrmDbContext` class: ````csharp -[ReplaceDbContext(typeof(IProductsDbContext))] +[ReplaceDbContext(typeof(ICatalogDbContext))] public class ModularCrmDbContext : AbpDbContext, - IProductsDbContext //NEW: IMPLEMENT THE INTERFACE + ICatalogDbContext //NEW: IMPLEMENT THE INTERFACE { public DbSet Products { get; set; } //NEW: ADD DBSET PROPERTY ... } ```` -**(3)** Finally, call the `ConfigureProducts()` extension method inside the `OnModelCreating` method after other `Configure...` module calls: - -````csharp -protected override void OnModelCreating(ModelBuilder builder) -{ - ... - builder.ConfigureProducts(); //NEW: CALL THE EXTENSION METHOD -} -```` +**(3)** Finally, ensure that the `ConfigureCatalog()` extension method is called inside the `OnModelCreating` method (this should be already done because you set the _Setup as a modular solution_ option in the _Modularity_ step while creating the initial solution). -In this way, `ModularCrmDbContext` can be used by the products module over the `IProductsDbContext` interface. This part is only needed once for a module. Next time, you can add a new database migration, as explained in the next section. +In this way, `ModularCrmDbContext` can be used by the catalog module over the `ICatalogDbContext` interface. This part is only needed once for a module. Next time, you can directly add a new database migration, as explained in the next section. #### 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. +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. 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. @@ -225,28 +206,28 @@ Once you click the *OK* button, a new database migration class is added to the ` ![visual-studio-new-migration-class](images/visual-studio-new-migration-class.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` package again 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) -After the operation completes, you can check your database to see the new `Products` table has been created: +After the operation completes, you can check your database to see the new `CatalogProducts` table has been created: -![sql-server-products-database-table](images/sql-server-products-database-table.png) +![sql-server-products-database-table](images/sql-server-products-database-table-v2.png) ## Creating the Application Service -Now, we will create an [application service](../../framework/architecture/domain-driven-design/application-services.md) to perform some use cases related to products. +Now, you can create an [application service](../../framework/architecture/domain-driven-design/application-services.md) to perform some use cases related to products. ### Defining the Application Service Contract -Return to your IDE (e.g. Visual Studio), open the `ModularCrm.Products` module's .NET solution and create an `IProductAppService` interface under the `ModularCrm.Products.Application.Contracts` project: +Return to your IDE (e.g. Visual Studio), open the `ModularCrm.Catalog` module's .NET solution and create an `IProductAppService` interface under the `ModularCrm.Catalog.Contracts` project: ````csharp using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Services; -namespace ModularCrm.Products; +namespace ModularCrm.Catalog; public interface IProductAppService : IApplicationService { @@ -255,18 +236,18 @@ public interface IProductAppService : IApplicationService } ```` -We are defining application service interfaces and [data transfer objects](../../framework/architecture/domain-driven-design/data-transfer-objects.md) in the `Application.Contracts` project. That way, we can share those contracts with clients without sharing the actual implementation class. +We are defining application service interfaces and [data transfer objects](../../framework/architecture/domain-driven-design/data-transfer-objects.md) in the `ModularCrm.Catalog.Contracts` project. That way, you can share those contracts with clients without sharing the actual implementation classes. ### Defining Data Transfer Objects -The `GetListAsync` and `CreateAsync` methods use the `ProductDto` and `ProductCreationDto` classes, which have not been defined yet. So, we need to define them. +The `GetListAsync` and `CreateAsync` methods use the `ProductDto` and `ProductCreationDto` classes, which have not been defined yet. So, you need to define them. -Create a `ProductCreationDto` class under the `ModularCrm.Products.Application.Contracts` project: +Create a `ProductCreationDto` class under the `ModularCrm.Catalog.Contracts` project: ````csharp using System.ComponentModel.DataAnnotations; -namespace ModularCrm.Products; +namespace ModularCrm.Catalog; public class ProductCreationDto { @@ -279,29 +260,28 @@ public class ProductCreationDto } ```` -And create a `ProductDto` class under the `ModularCrm.Products.Application.Contracts` project: +And create a `ProductDto` class under the `ModularCrm.Catalog.Contracts` project: ````csharp using System; -namespace ModularCrm.Products +namespace ModularCrm.Catalog; + +public class ProductDto { - public class ProductDto - { - public Guid Id { get; set; } - public string Name { get; set; } - public int StockCount { get; set; } - } + public Guid Id { get; set; } + public string Name { get; set; } + public int StockCount { get; set; } } ```` -The new files under the `ModularCrm.Products.Application.Contracts` project are shown below: +The new files under the `ModularCrm.Catalog.Contracts` project are shown below: -![visual-studio-application-contracts](images/visual-studio-application-contracts.png) +![vs-code-catalog-contracts](images/vs-code-catalog-contracts.png) ### Implementing the Application Service -Now, we can implement the `IProductAppService` interface. Create a `ProductAppService` class under the `ModularCrm.Products.Application` project: +Now, you can implement the `IProductAppService` interface. Create a `ProductAppService` class under the `ModularCrm.Catalog` project: ````csharp using System; @@ -309,9 +289,9 @@ using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; -namespace ModularCrm.Products; +namespace ModularCrm.Catalog; -public class ProductAppService : ProductsAppService, IProductAppService +public class ProductAppService : CatalogAppService, IProductAppService { private readonly IRepository _productRepository; @@ -339,20 +319,20 @@ public class ProductAppService : ProductsAppService, IProductAppService } ```` -Notice that `ProductAppService` class implements the `IProductAppService` and also inherits from the `ProductsAppService` class. Do not be confused about the naming (`ProductAppService` and `ProductsAppService`). The `ProductsAppService` is a base class. It makes a few configurations for [localization](../../framework/fundamentals/localization.md) and [object mapping](../../framework/infrastructure/object-to-object-mapping.md) (you can see in the `ModularCrm.Products.Application` project). You can inherit all of your application services from that base class. This way, you can define some common properties and methods to share among all your application services. You can rename the base class if you feel that you may be confused later. +Notice that `ProductAppService` class implements the `IProductAppService` and also inherits from the `CatalogAppService` class. The `CatalogAppService` is a base class and it makes a few configurations for [localization](../../framework/fundamentals/localization.md) and [object mapping](../../framework/infrastructure/object-to-object-mapping.md) (you can see in the same `ModularCrm.Catalog` project). You can inherit all of your application services from that base class. This way, you can define some common properties and methods to share among all your application services. You can rename the base class if you feel that you may be confused later. #### Object Mapping -`ProductAppService.GetListAsync` method uses the `ObjectMapper` service to convert `Product` entities to `ProductDto` objects. The mapping should be configured. Open the `ProductsApplicationAutoMapperProfile` class in the `ModularCrm.Products.Application` project and change it to the following code block: +`ProductAppService.GetListAsync` method uses the `ObjectMapper` service to convert `Product` entities to `ProductDto` objects. The mapping should be configured. Open the `CatalogAutoMapperProfile` class in the `ModularCrm.Catalog` project and change it to the following code block: ````csharp using AutoMapper; -namespace ModularCrm.Products; +namespace ModularCrm.Catalog; -public class ProductsApplicationAutoMapperProfile : Profile +public class CatalogAutoMapperProfile : Profile { - public ProductsApplicationAutoMapperProfile() + public CatalogAutoMapperProfile() { CreateMap(); } @@ -363,56 +343,49 @@ We've added the `CreateMap();` line to define the mapping. ### Exposing Application Services as HTTP API Controllers -For this application, we don't need to create HTTP API endpoints for the products module. But it is good to understand how to do it when you need it. You have two options; - -* 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 `ModularCrmModule` class in the main application's solution (the `ModularCrm` solution), find the `PreConfigureServices` method and add the following lines inside that method: +> This application doesn't need to expose any functionality as HTTP API, because all the module integration and communication will be done in the same process as a natural aspect of a monolith modular application. However, in this section, we will create HTTP APIs because; +> +> 1. We will use these HTTP API endpoints in development to create some example data. +> 2. To know how to do it when you need it. +> +> So, follow the instructions in this section and expose the product application service as an HTTP API endpoint. -````csharp -PreConfigure(mvcBuilder => -{ - mvcBuilder.AddApplicationPartIfNotExists(typeof(ProductsApplicationModule).Assembly); -}); -```` +To create HTTP API endpoints for the catalog module, you have two options: -This will tell the ASP.NET Core to explore the given assembly to discover controllers. +* You can create a regular ASP.NET Core Controller class in the `ModularCrm.Catalog` project, inject `IProductAppService` and create wrapper methods for each public method of the product application service. 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. -Then open the `ConfigureAutoApiControllers` method of the same class and add a second `ConventionalControllers.Create` call as shown in the following code block: +Open the `CatalogModule` class in the Catalog module's .NET solution (the `ModularCrm.Catalog` .NET solution, the `ModularCrm.Catalog` .NET project) in your IDE, find the `ConfigureServices` method and add the following code block into that method: ````csharp Configure(options => { - options.ConventionalControllers.Create(typeof(ModularCrmModule).Assembly); - - //ADD THE FOLLOWING LINE: - options.ConventionalControllers.Create(typeof(ProductsApplicationModule).Assembly, settings => + options.ConventionalControllers.Create(typeof(CatalogModule).Assembly, settings => { - settings.RootPath = "products"; + settings.RootPath = "catalog"; }); }); ```` -This will tell the ABP framework to create API controllers for the application services in the assembly. +This will tell the ABP framework to create API controllers for the application services in the `ModularCrm.Catalog` assembly. -> We made these configurations in the main application's solution since there is no project in the product module's solution that references ASP.NET Core MVC packages and uses the product module's application layer. If you add a reference of `ModularCrm.Products.Application` to `ModularCrm.Products.HttpApi`, then you can move these configurations to the `ModularCrm.Products.HttpApi` project. - -Now, ABP will automatically expose the application services defined in the `ModularCrm.Products.Application` project as API controllers. The next section will use these API controllers to create some example products. +Now, ABP will automatically expose the application services defined in the `ModularCrm.Catalog` project as API controllers. The next section will use these API controllers to create some example products. ### Creating Example Products -This section will create a few example products using the [Swagger UI](../../framework/api-development/swagger.md). Thus, we will have some sample products to show on the UI. +This section will create a few example products using the [Swagger UI](../../framework/api-development/swagger.md). Thus, you will have some sample products 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 product module and the main application are built and ready to run. +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 catalog 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 `Products` API: +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 `Catalog` API: ![abp-studio-swagger-ui-in-browser](images/abp-studio-swagger-ui-in-browser.png) -Expand the `/api/products/product` API and click the *Try it out* button as shown in the following figure: +> **Note:** If you have a swagger error on the UI, then you can open the `SampleAppService` class in the `ModularCrm.Catalog` project and add `[RemoteService(false)]` attribute to the `SampleAppService` class. With this attribute, the `SampleAppService` class will not be exposed as a remote service automatically but since there is a `SampleController` class in the `ModularCrm.Catalog` project, the `Catalog` API will be exposed as a remote service. + +Expand the `POST /api/catalog/product` API and click the *Try it out* button as shown in the following figure: ![abp-studio-swagger-ui-create-product-try](images/abp-studio-swagger-ui-create-product-try.png) @@ -422,19 +395,21 @@ Then, create a few products by filling in the *Request body* and clicking the *E If you check the database, you should see the entities created in the `Products` table: -![sql-server-products-database-table-filled](images/sql-server-products-database-table-filled.png) +![sql-server-products-database-table-filled](images/sql-server-products-database-table-filled-v2.png) -We've some entities in the database; we can show them on the user interface now. +You've some entities in the database and now you can show them on the user interface. ## Creating the User Interface -In this section, we 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. -Open the `ModularCrm.Products` .NET solution in your IDE, and find the `Pages/Products/Index.cshtml` file under the `ModularCrm.Products.Web` project: +### Creating the Products Page -![visual-studio-products-cshtml](images/visual-studio-products-cshtml.png) +Open the `ModularCrm.Catalog` .NET solution in your IDE, and find the `Pages/Catalog/Index.cshtml` file under the `ModularCrm.Catalog.UI` project: + +![vscode-catalog-cshtml](images/vscode-catalog-cshtml.png) Replace the `Index.cshtml.cs` file with the following content: @@ -442,9 +417,9 @@ Replace the `Index.cshtml.cs` file with the following content: using System.Collections.Generic; using System.Threading.Tasks; -namespace ModularCrm.Products.Web.Pages.Products; +namespace ModularCrm.Catalog.UI.Pages.Catalog; -public class IndexModel : ProductsPageModel +public class IndexModel : CatalogPageModel { public List Products { get; set; } @@ -462,15 +437,15 @@ public class IndexModel : ProductsPageModel } ```` -Here, we simply use the `IProductAppService` to get a list of all products and assign the result to the `Products` property. We can use it in the `Index.cshtml` file to show a simple list of products on the UI: +Here, you simply use the `IProductAppService` to get a list of all products and assign the result to the `Products` property. You can use it in the `Index.cshtml` file to show a simple list of products on the UI: ````xml @page @using Microsoft.Extensions.Localization -@using ModularCrm.Products.Localization -@using ModularCrm.Products.Web.Pages.Products -@model ModularCrm.Products.Web.Pages.Products.IndexModel -@inject IStringLocalizer L +@using ModularCrm.Catalog.Localization +@using ModularCrm.Catalog.UI.Pages.Catalog +@model ModularCrm.Catalog.UI.Pages.Catalog.IndexModel +@inject IStringLocalizer L

Products

@@ -492,17 +467,12 @@ Right-click the `ModularCrm` application on ABP Studio's solution runner and sel ![abp-studio-build-and-restart-application](images/abp-studio-build-and-restart-application.png) -Now, you can browse the *Products* page to see the list of the products: +Now, you can browse the *Catalog* page to see the list of the products: ![abp-studio-browser-list-of-products](images/abp-studio-browser-list-of-products.png) As you can see, developing a UI page in a modular ABP application is pretty straightforward. We kept the UI very simple to focus on modularity. To learn how to build complex application UIs, please check the [Book Store Tutorial](../book-store/index.md). -## Final Notes - -Some of the projects in the product module's .NET solution (`ModularCrm.Products`) are not necessary for most of the cases. They are available to support different scenarios. You can delete them from your module (and remove the dependencies on the main application) if you want: +## Summary -* `ModularCrm.Products.HttpApi`: This project aims to define regular HTTP API controllers. If you will always use ABP's [Auto API Controllers](../../framework/api-development/auto-controllers.md) feature (like we did in this tutorial), you can delete that project. -* `ModularCrm.Products.HttpApi.Client`: That project is generally shared with 3rd-party applications, so they can easily consume your HTTP API endpoints. In a modular monolith application, you typically don't need it. -* `ModularCrm.Products.HttpApi.Installer`: That project is used to discover and install a multi-projects module (like the product module) when you deploy it to a package management system (like NuGet). If you use the module with local project references (like we did here), you can delete that project. -* You can also delete the test projects (there are 4 of them in the solution) if you don't prefer to write unit/integration tests in the module's solution (Legal warning: it is recommended to write tests 😊) +In this part of the tutorial, you've built the functionality inside the _Catalog_ module, which was created in the [previous part](part-02.md). In the next part, you will create a new _Ordering_ module and install it into the main application. \ No newline at end of file diff --git a/docs/en/tutorials/modular-crm/part-04.md b/docs/en/tutorials/modular-crm/part-04.md index ba406f7846..ea3147d97d 100644 --- a/docs/en/tutorials/modular-crm/part-04.md +++ b/docs/en/tutorials/modular-crm/part-04.md @@ -4,7 +4,7 @@ //[doc-nav] { "Previous": { - "Name": "Building the Products Module", + "Name": "Building the Catalog Module", "Path": "tutorials/modular-crm/part-03" }, "Next": { @@ -18,9 +18,11 @@ In this part, you will build a new module for placing orders and install it in t ## Creating a Standard Module -In this part, we have used the *DDD Module* template for the Product module and will use the *Standard Module* template for the Ordering module. +In the first part of this tutorial, you created the `ModularCrm` solution with selecting the _Setup as a modular solution_ option and adding a module named `ModularCrm.Catalog` using the *Standard Module* template. -Right-click the `modules` folder on the *Solution Explorer* panel, and select the *Add* -> *New Module* -> *Standard Module* command: +Now, you will create a second module for the `ModularCrm` solution through ABP Studio's *Solution Explorer*. This new module, called `ModularCrm.Ordering`, will handle all order related functionality in the application. + +To add a new module, right-click the `modules` folder on the *Solution Explorer* panel, and select the *Add* -> *New Module* -> *Standard Module* command: ![abp-studio-add-new-standard-module](images/abp-studio-add-new-standard-module.png) @@ -32,29 +34,23 @@ Set `ModularCrm.Ordering` as the *Module name*, leave the *Output folder* as is ![abp-studio-add-new-standard-module-ui-dialog](images/abp-studio-add-new-standard-module-ui-dialog.png) -Similar to DDD module creation, you can choose the type of UI you want to support in your module or select *No UI* if you don't need a user interface. In this example, we'll select the *MVC* option and click *Next*. One difference is that, for a standard module, you can only choose one UI type. +You can choose the type of UI you want to support in your module or select *No UI* if you don't need a user interface. In this example, we'll select the *MVC* option and click *Next*. ![abp-studio-add-new-standard-module-db-dialog](images/abp-studio-add-new-standard-module-db-dialog.png) -The same limitation applies to the database selection. You can only choose one database provider for a standard module. Select the *Entity Framework Core* option and click *Next*. +In this screen, select the *Entity Framework Core* option and click *Next*. ![abp-studio-add-new-standard-module-additional-dialog](images/abp-studio-add-new-standard-module-additional-dialog.png) -You can uncheck the *Include Tests* option to keep the module simple. Click the *Create* button to create the module. - -![abp-studio-modular-crm-with-standard-module](images/abp-studio-modular-crm-with-standard-module.png) - -Since we've created a standard module, it doesn't have multiple layers like the DDD module. If you open the `modules/modularcrm.ordering` in your file system, you can see the initial files: +You can include or not include unit tests for the new module here. We are unchecking the *Include Tests* option this time to show a different structure for this example. Click the *Create* button to create the module. -![file-system-odering-module-initial-folder](images/file-system-ordering-module-initial-folder.png) +Here is the final solution structure after adding the `ModularCrm.Ordering` module: -Because only a single UI package can be chosen, the UI type doesn’t matter. This is why the package name is changed to *ModularCrm.Ordering.UI*. Additionally, there are no *Domain*, *EntityFrameworkCore*, or *Http* layers like in the DDD module. We're going to use the `ModularCrm.Ordering` package for the domain business logic. You can open `ModularCrm.Ordering.sln` in your favorite IDE (e.g. Visual Studio): - -![ordering-module-visual-studio](images/ordering-module-visual-studio.png) +![abp-studio-modular-crm-with-standard-module](images/abp-studio-modular-crm-with-standard-module.png) ## Installing into the Main Application -In this section, we will install the `ModularCrm.Ordering` module in the main application so it can be part of the system. +In this section, you will install the `ModularCrm.Ordering` module in the main application so it can be part of the monolith application. > Before the installation, please ensure the web application is not running. @@ -68,8 +64,10 @@ That command opens the *Import Module* dialog: Select the `ModularCrm.Ordering` module and check the *Install this module* option as shown in the preceding figure. When you click the OK button, a new dialog is shown to select the packages to install: -![abp-studio-install-module-dialog](images/abp-studio-install-module-dialog.png) +![abp-studio-install-module-dialog](images/abp-studio-install-module-dialog-v2.png) + +Select the `ModularCrm.Ordering` and `ModularCrm.Ordering.UI` packages from the left area and ensure the `ModularCrm` package from the middle area was checked as shown in the preceding figure. Finally, click _OK_. -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*. +## Summary -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. +In this part of the tutorial, you've created a new module and installed it into the main solution. In the [next part](part-05), you will add functionality to the new Ordering module. diff --git a/docs/en/tutorials/modular-crm/part-05.md b/docs/en/tutorials/modular-crm/part-05.md index 59aa56f2f4..b7e4b28cc7 100644 --- a/docs/en/tutorials/modular-crm/part-05.md +++ b/docs/en/tutorials/modular-crm/part-05.md @@ -14,7 +14,7 @@ } ```` -In the previous part, we created Ordering module and installed it into the main application. However, the Ordering module has no functionality now. In this part, we will create an `Order` entity and add functionality to create and list the orders. +In the [previous part](part-04), you created Ordering module and installed it into the main application. However, the Ordering module has no functionality yet. In this part, you will create an `Order` entity and add functionality to create and list the orders. ## Creating an `Order` Entity @@ -24,32 +24,30 @@ Open the `ModularCrm.Ordering` .NET solution in your IDE. ### Adding an `Order` Class -Create an `Order` class to the `ModularCrm.Ordering` project (open an `Entities` folder and place the `Order.cs` into that folder): +Create an `Order` class to the `ModularCrm.Ordering` project: ````csharp using System; -using ModularCrm.Ordering.Enums; using Volo.Abp.Domain.Entities.Auditing; -namespace ModularCrm.Ordering.Entities +namespace ModularCrm.Ordering; + +public class Order : CreationAuditedAggregateRoot { - public class Order : CreationAuditedAggregateRoot - { - public Guid ProductId { get; set; } - public string CustomerName { get; set; } - public OrderState State { get; set; } - } + public Guid ProductId { get; set; } + public string CustomerName { get; set; } = null!; + public OrderState State { get; set; } } ```` -We allow users to place only a single product within an order. The `Order` entity would be much more complex in a real-world application. However, the complexity of the `Order` entity doesn't affect modularity, so we keep it simple to focus on modularity in this tutorial. We are inheriting from the [`CreationAuditedAggregateRoot` class](../../framework/architecture/domain-driven-design/entities.md) since I want to know when an order has been created and who has created it. +We allow users to place only a single product within an order. The `Order` entity would be much more complex in a real-world application. However, the complexity of the `Order` entity doesn't affect modularity. So, we keep it simple to focus on modularity in this tutorial. We are inheriting from the [`CreationAuditedAggregateRoot` class](../../framework/architecture/domain-driven-design/entities.md) since I want to know when an order has been created and who has created it. ### 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: +We used an `OrderState` enumeration that has not yet been defined. Create a `OrderState.cs` file inside the `ModularCrm.Ordering.Contracts` project and define the following Enum: ````csharp -namespace ModularCrm.Ordering.Enums; +namespace ModularCrm.Ordering; public enum OrderState : byte { @@ -61,17 +59,17 @@ public enum OrderState : byte The final structure of the Ordering module should be similar to the following figure in your IDE: -![visual-studio-order-entity](images/visual-studio-order-entity.png) +![visual-studio-order-entity](images/visual-studio-order-entity-v2.png) ## Configuring the Database Mapping -The `Order` entity has been created. Now, we need to configure the database mapping for that entity. We will first define the database table mapping, create a database migration and update the database. +The `Order` entity has been created. Now, you need to configure the database mapping for that entity. You will first define the database table mapping, create a database migration and update the database. ### 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: @@ -90,7 +88,7 @@ public interface IOrderingDbContext : IEfCoreDbContext } ```` -Afterwards, create *Orders* `DbSet` for the `OrderingDbContext` class in the `Data` folder under the `ModularCrm.Ordering` project. +Afterwards, create *Orders* `DbSet` for the `OrderingDbContext` class in the `Data` folder under the `ModularCrm.Ordering` project: ````csharp using Microsoft.EntityFrameworkCore; @@ -119,10 +117,9 @@ public class OrderingDbContext : AbpDbContext, IOrderingDbCon } ```` +You can inject and use the `IOrderingDbContext` in the Ordering module. However, you will not usually directly use that interface. Instead, you will use ABP's [repositories](../../framework/architecture/domain-driven-design/repositories.md), which internally uses that interface. -We can inject and use the `IOrderingDbContext` in the Ordering module. However, we will not usually directly use that interface. Instead, we will use ABP's [repositories](../../framework/architecture/domain-driven-design/repositories.md), which internally uses that interface. - -It is best to configure the database table mapping for the `Order` entity in the Ordering module. We will use the `OrderingDbContextModelCreatingExtensions` in the same `Data` folder: +It is best to configure the database table mapping for the `Order` entity in the Ordering module. You will use the `OrderingDbContextModelCreatingExtensions` in the same `Data` folder: ````csharp using Microsoft.EntityFrameworkCore; @@ -180,7 +177,7 @@ public class ModularCrmDbContext : } ```` -**(3)** Finally, call the `ConfigureOrdering()` extension method inside the `OnModelCreating` method after other `Configure...` module calls: +**(3)** Finally, call the `ConfigureOrdering()` extension method inside the `OnModelCreating` method after other `Configure...` module calls (this should already be done from ABP Studio): ````csharp protected override void OnModelCreating(ModelBuilder builder) @@ -190,11 +187,11 @@ protected override void OnModelCreating(ModelBuilder builder) } ```` -In this way, the Ordering module can use 'ModularCrmDbContext' over the `IProductsDbContext` interface. This part is only needed once for a module. Next time, you can add a new database migration, as explained in the next section. +In this way, the Ordering module can use 'ModularCrmDbContext' over the `IOrderingDbContext` interface. This part is only needed once for a module. Next time, you can add a new database migration, as explained in the next section. #### 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. @@ -204,34 +201,36 @@ Right-click the `ModularCrm` package and select the *EF Core CLI* -> *Add Migrat 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) +![abp-studio-entity-framework-core-add-migration-order](images/abp-studio-entity-framework-core-add-migration-order-v2.png) 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) +![visual-studio-new-migration-class-2](images/visual-studio-new-migration-class-2-v2.png) 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) -After the operation completes, you can check your database to see the new `Orders` table has been created: +After the operation completes, you can check your database to see the new `OrderingOrders` table has been created: -![sql-server-products-database-table](images/sql-server-orders-database-table.png) +![sql-server-products-database-table](images/sql-server-orders-database-table-v2.png) + +`Ordering` prefix is added to all table names of the Ordering module. If you want to change or remove it, see the `OrderingDbProperties` class in the Ordering module's .NET solution. ## 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 in the `ModularCrm.Ordering.Contracts` project: ````csharp using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Services; -namespace ModularCrm.Ordering.Services; +namespace ModularCrm.Ordering; public interface IOrderAppService : IApplicationService { @@ -242,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: @@ -250,13 +249,13 @@ Create a `OrderCreationDto` class under the `ModularCrm.Ordering.Contracts` proj using System; using System.ComponentModel.DataAnnotations; -namespace ModularCrm.Ordering.Contracts.Services; +namespace ModularCrm.Ordering; public class OrderCreationDto { [Required] [StringLength(150)] - public string CustomerName { get; set; } + public string CustomerName { get; set; } = null!; [Required] public Guid ProductId { get; set; } @@ -267,14 +266,13 @@ Create a `OrderDto` class under the `ModularCrm.Ordering.Contracts` project: ````csharp using System; -using ModularCrm.Ordering.Enums; -namespace ModularCrm.Ordering.Services; +namespace ModularCrm.Ordering; public class OrderDto { public Guid Id { get; set; } - public string CustomerName { get; set; } + public string CustomerName { get; set; } = null!; public Guid ProductId { get; set; } public OrderState State { get; set; } } @@ -282,16 +280,14 @@ public class OrderDto 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) +![visual-studio-ordering-contracts](images/visual-studio-ordering-contracts-v2.png) ### 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: +First we configure the *AutoMapper* to map the `Order` entity to the `OrderDto` object, because we will need it later. Open the `OrderingAutoMapperProfile` under the `ModularCrm.Ordering` project: ````csharp using AutoMapper; -using ModularCrm.Ordering.Entities; -using ModularCrm.Ordering.Services; namespace ModularCrm.Ordering; @@ -304,21 +300,19 @@ 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 `ModularCrm.Ordering` project: ````csharp using System; using System.Collections.Generic; using System.Threading.Tasks; -using ModularCrm.Ordering.Enums; -using ModularCrm.Ordering.Entities; using Volo.Abp.Domain.Repositories; namespace ModularCrm.Ordering.Services; public class OrderAppService : OrderingAppService, IOrderAppService { - private readonly IRepository _orderRepository; + private readonly IRepository _orderRepository; public OrderAppService(IRepository orderRepository) { @@ -347,50 +341,48 @@ 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, we can create HTTP API endpoints for the ordering module using the ABP's [Auto API Controllers](../../framework/api-development/auto-controllers.md) feature. For that purpose, open the `OrderingModule` class in the Ordering module's .NET solution (the `ModularCrm.Ordering` solution), find the `ConfigureServices` method and add the following lines inside that method: ````csharp -private void ConfigureAutoApiControllers() +Configure(options => { - Configure(options => + options.ConventionalControllers.Create(typeof(OrderingModule).Assembly, settings => { - options.ConventionalControllers.Create(typeof(ModularCrmModule).Assembly); - options.ConventionalControllers.Create(typeof(ProductsApplicationModule).Assembly, settings => - { - settings.RootPath = "products"; - }); - - //ADD THE FOLLOWING LINE: - options.ConventionalControllers.Create(typeof(OrderingModule).Assembly, settings => - { - settings.RootPath = "orders"; - }); + settings.RootPath = "ordering"; }); -} +}); ```` +This will tell the ABP framework to create API controllers for the application services in the `ModularCrm.Ordering` 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. +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 `Order` API: -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-v2.png) -![abp-studio-ordering-swagger-ui-in-browser](images/abp-studio-ordering-swagger-ui-in-browser.png) +> **Note:** If you have a swagger error on the UI, then you can open the `SampleAppService` class in the `ModularCrm.Ordering` project and add `[RemoteService(false)]` attribute to the `SampleAppService` class. With this attribute, the `SampleAppService` class will not be exposed as a remote service automatically but since there is a `SampleController` class in the `ModularCrm.Catalog` project, the `Catalog` API will be exposed as a remote service. -Expand the `/api/orders/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: +Expand the `POST /api/ordering/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) +![abp-studio-swagger-ui-create-order-execute](images/abp-studio-swagger-ui-create-order-execute-v2.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) +![sql-server-orders-database-table-filled](images/sql-server-orders-database-table-filled-v2.png) ## Creating the User Interface +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. + ### Creating the Orders Page Replace the `Index.cshtml.cs` content in the `Pages/Ordering` folder of the `ModularCrm.Ordering.UI` project with the following code block: @@ -399,30 +391,28 @@ Replace the `Index.cshtml.cs` content in the `Pages/Ordering` folder of the `Mod using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.RazorPages; -using ModularCrm.Ordering.Services; -namespace ModularCrm.Ordering.UI.Pages.Ordering +namespace ModularCrm.Ordering.UI.Pages.Ordering; + +public class IndexModel : PageModel { - public class IndexModel : PageModel - { - public List Orders { get; set; } + public List Orders { get; set; } - private readonly IOrderAppService _orderAppService; + private readonly IOrderAppService _orderAppService; - public IndexModel(IOrderAppService orderAppService) - { - _orderAppService = orderAppService; - } + public IndexModel(IOrderAppService orderAppService) + { + _orderAppService = orderAppService; + } - public async Task OnGetAsync() - { - Orders = await _orderAppService.GetListAsync(); - } + public async Task OnGetAsync() + { + Orders = await _orderAppService.GetListAsync(); } } ```` -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 @@ -446,7 +436,7 @@ Here, we are injecting `IOrderAppService` to query `Order` entities from the dat ```` -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 @@ -487,26 +477,22 @@ 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. ### 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` application, and select the *Build* -> *Graph Build* command: +Now, you 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` application and selecting the *Start* command. - -![abp-studio-browser-orders-menu-item](images/abp-studio-browser-orders-menu-item.png) - -Great! We can see the list of orders. However, there is a problem: +You've performed a graph build since you'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. -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. +![abp-studio-browser-orders-menu-item](images/abp-studio-browser-orders-menu-item-v2.png) -We will solve this problem in the [next part](part-06.md). +Great! We can see the list of orders. However, there is a problem: 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, we've built the functionality inside the Ordering module we created in the [previous part](part-04.md). In the next part, we 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](part-06.md), you will work on establishing communication between the Orders module and the Catalog module. diff --git a/docs/en/tutorials/modular-crm/part-06.md b/docs/en/tutorials/modular-crm/part-06.md index d2820c6bdc..568fa4d800 100644 --- a/docs/en/tutorials/modular-crm/part-06.md +++ b/docs/en/tutorials/modular-crm/part-06.md @@ -14,13 +14,13 @@ } ```` -In the previous parts, we created two modules: the Products module to store and manage products and the Orders 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. +You have created two modules so far: the **Catalog** module to store and manage products and the **Ordering** module to accept orders. However, these modules were completely independent from each other. 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: +In this part and next two pars, you will learn to implement three common patterns for integrating these modules: -1. The Order module will make a request to the Products module to get product information when needed. -2. The Product module will listen to events from the Orders module, so it can decrease a product's stock count when an order is placed. -3. Finally, we will execute a database query that includes product and order data. +1. The Order module will make a request to the Catalog module to get product information when needed. +2. The Product module will listen to events from the Ordering module, so it can decrease a product's stock count when an order is placed. +3. Finally, you will execute a database query that includes product and order data. Let's begin from the first one: The Integration Services. @@ -28,27 +28,27 @@ Let's begin from the first one: The Integration Services. Remember from the [previous part](part-05.md), the Orders page shows product's identities instead of their names: -![abp-studio-browser-orders-menu-item](images/abp-studio-browser-orders-menu-item.png) +![abp-studio-browser-orders-menu-item](images/abp-studio-browser-orders-menu-item-v2.png) -That is because the Orders 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 GUID identity on the UI, which is not a good user experience. -As a solution to that problem, the Orders module may ask product names to the Product 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. +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. > When you implement integration services for inter-module communication, you can easily convert them to REST API calls if you convert your solution to a microservice system and convert your modules to services later. ## Creating a Products Integration Service -The first step is to create an integration service in the Products module, so other modules can consume it. +The first step is to create an integration service in the Catalog module, so other modules can consume it. -We will define an interface in the `ModularCrm.Products.Application.Contracts` package and implement it in the `ModularCrm.Products.Application` package. +You will define an interface in the `ModularCrm.Catalog.Contracts` package and implement it in the `ModularCrm.Catalog` package. ### Defining the `IProductIntegrationService` Interface -Open the `ModularCrm.Products` .NET solution in your IDE, find the `ModularCrm.Products.Application.Contracts` project, create an `Integration` folder inside inside of that project and finally create an interface named `IProductIntegrationService` into that folder. The final folder structure should be like that: +Open the `ModularCrm.Catalog` .NET solution in your IDE, find the `ModularCrm.Catalog.Contracts` project, create an `Integration` folder inside inside of that project and finally create an interface named `IProductIntegrationService` into that folder. The final folder structure should be like that: -![visual-studio-product-integration-service](images/visual-studio-product-integration-service.png) +![vscode-product-integration-service](images/vscode-product-integration-service.png) -(Creating an`Integration` folder is not required, but it can be a good practice) +Creating an`Integration` folder is not required, but it can be a good practice to isolate integration-related code from the business logic of your module. Open the `IProductIntegrationService.cs` file and replace it's content with the following code block: @@ -56,16 +56,16 @@ Open the `IProductIntegrationService.cs` file and replace it's content with the using System; using System.Collections.Generic; using System.Threading.Tasks; +using ModularCrm.Catalog; using Volo.Abp; using Volo.Abp.Application.Services; -namespace ModularCrm.Products.Integration +namespace ModularCrm.Products.Integration; + +[IntegrationService] +public interface IProductIntegrationService : IApplicationService { - [IntegrationService] - public interface IProductIntegrationService : IApplicationService - { - Task> GetProductsByIdsAsync(List ids); - } + Task> GetProductsByIdsAsync(List ids); } ```` @@ -81,7 +81,7 @@ namespace ModularCrm.Products.Integration ### Implementing the `ProductIntegrationService` Class -We've defined the integration service interface. Now, we can implement it in the `ModularCrm.Products.Application` 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) @@ -91,78 +91,76 @@ Open the `ProductIntegrationService.cs` file and replace its content with the fo using System; using System.Collections.Generic; using System.Threading.Tasks; +using ModularCrm.Catalog; using Volo.Abp; using Volo.Abp.Domain.Repositories; -namespace ModularCrm.Products.Integration +namespace ModularCrm.Products.Integration; + +[IntegrationService] +public class ProductIntegrationService + : CatalogAppService, IProductIntegrationService { - [IntegrationService] - public class ProductIntegrationService - : ProductsAppService, IProductIntegrationService - { - private readonly IRepository _productRepository; + private readonly IRepository _productRepository; - public ProductIntegrationService(IRepository productRepository) - { - _productRepository = productRepository; - } + public ProductIntegrationService(IRepository productRepository) + { + _productRepository = productRepository; + } - public async Task> GetProductsByIdsAsync(List ids) - { - var products = await _productRepository.GetListAsync( - product => ids.Contains(product.Id) - ); + public async Task> GetProductsByIdsAsync(List ids) + { + var products = await _productRepository.GetListAsync( + product => ids.Contains(product.Id) + ); - return ObjectMapper.Map, List>(products); - } + return ObjectMapper.Map, List>(products); } } ```` 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` 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` 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 to the `ModularCrm.Products.Application.Contracts` Package +### Adding a Reference of the `ModularCrm.Catalog.Contracts` Package Open the ABP Studio UI and stop the application if it is already running. Then open the *Solution Explorer* in ABP Studio, right-click the `ModularCrm.Ordering` package and select the *Add Package Reference* command: -![abp-studio-add-package-reference-4](images/abp-studio-add-package-reference-4.png) +![abp-studio-add-package-reference-4](images/abp-studio-add-package-reference-4-v2.png) -In the opening dialog, select the *This solution* tab, find and check the `ModularCrm.Products.Application.Contracts` package and click the OK button: +In the opening dialog, select the *This solution* tab, find and check the `ModularCrm.Catalog.Contracts` package and click the OK button: ![abp-studio-add-package-reference-dialog-3](images/abp-studio-add-package-reference-dialog-3.png) ABP Studio adds the package reference and arranges the [module](../../framework/architecture/modularity/basics.md) dependency. -> Instead of directly adding such a package reference, it can be best to import the module first (right-click the `ModularCrm.Ordering` module, select the _Import Module_ command and import the `ModularCrm.Products` module), then install the package reference. In that way, it would be easy to see and keep track of inter-module dependencies. +> Instead of directly adding such a package reference, it can be possible to import the module first (right-click the `ModularCrm.Ordering` module, select the _Import Module_ command and import the `ModularCrm.Catalog` module), then add the package references. ABP automatically import module when you add a package reference from a local module, but for other sources you may need to do it manually. ### 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: +Open the `OrderAppService` class of the `ModularCrm.Ordering` project of the `ModularCrm.Ordering` .NET solution and change its content as like the following code block: ````csharp using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using ModularCrm.Ordering.Enums; -using ModularCrm.Ordering.Entities; using ModularCrm.Products.Integration; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; -namespace ModularCrm.Ordering.Services; +namespace ModularCrm.Ordering; public class OrderAppService : ApplicationService, IOrderAppService { - private readonly IRepository _orderRepository; + private readonly IRepository _orderRepository; private readonly IProductIntegrationService _productIntegrationService; public OrderAppService( @@ -207,21 +205,20 @@ public class OrderAppService : ApplicationService, IOrderAppService } ```` -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: +And also, open the `OrderDto` class of the `ModularCrm.Ordering.Contracts` project of the `ModularCrm.Ordering` .NET solution and add a `ProductName` property to it: ````csharp using System; -using ModularCrm.Ordering.Enums; -namespace ModularCrm.Ordering.Services; +namespace ModularCrm.Ordering; public class OrderDto { public Guid Id { get; set; } - public string CustomerName { get; set; } + public string CustomerName { get; set; } = null!; public Guid ProductId { get; set; } - public string ProductName { get; set; } // New property public OrderState State { get; set; } + public string ProductName { get; set; } = null!; // New Property } ```` @@ -229,8 +226,6 @@ Lastly, open the `OrderingAutoMapperProfile` class (the `OrderingAutoMapperProfi ````csharp using AutoMapper; -using ModularCrm.Ordering.Services; -using ModularCrm.Ordering.Entities; using Volo.Abp.AutoMapper; namespace ModularCrm.Ordering; @@ -248,11 +243,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` 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` 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. @@ -286,8 +281,10 @@ That's all. Now, you can graph build the main application and run it in ABP Stud As you can see, we can see the product names instead of product IDs. -In the way explained in this section, you can easily create integration services for your modules and consume these integration services in any other module. - > **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. +> 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 Catalog 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. + +## Conclusion + +In the way explained in this part of this tutorial, you can easily create integration services for your modules and consume these integration services in any other module. In the [next part](part-07.md), we will explore event based messaging between the modules. diff --git a/docs/en/tutorials/modular-crm/part-07.md b/docs/en/tutorials/modular-crm/part-07.md index 4bcace4239..8006bcae54 100644 --- a/docs/en/tutorials/modular-crm/part-07.md +++ b/docs/en/tutorials/modular-crm/part-07.md @@ -16,6 +16,8 @@ Another common approach to communicating between modules is messaging. By publishing and handling messages, a module can perform an operation when an event happens in another module. +## Understanding the Event Bus Types + 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. @@ -29,7 +31,7 @@ We will use the distributed event bus since we will use messaging (events) betwe ## Publishing an Event -In the example scenario, we want to publish an event when a new order is placed. The Ordering module will publish the event since it knows when a new order is placed. The Products module will subscribe to that event and get notified when a new order is placed. This will decrease the stock count of the product related to the new order. The scenario is pretty simple; let's implement it. +In the example scenario, we want to publish an event when a new order is placed. The Ordering module will publish the event since it knows when a new order is placed. The Catalog module will subscribe to that event and get notified when a new order is placed. This will decrease the stock count of the product related to the new order. The scenario is pretty simple; let's implement it. ### Defining the Event Class @@ -42,30 +44,27 @@ We've placed the `OrderPlacedEto` class inside the `ModularCrm.Ordering.Contract ````csharp using System; -namespace ModularCrm.Ordering.Events +namespace ModularCrm.Ordering.Events; + +public class OrderPlacedEto { - public class OrderPlacedEto - { - public string CustomerName { get; set; } - public Guid ProductId { get; set; } - } + public string CustomerName { get; set; } = null!; + public Guid ProductId { get; set; } } ```` -`OrderPlacedEto` is very simple. It is a plain C# class used to transfer data related to the event (*ETO* is an acronym for *Event Transfer Object*, a suggested naming convention but not required). You can add more properties if needed, but for this tutorial, it is more than enough. +`OrderPlacedEto` is very simple. It is a plain C# class used to transfer data related to the event (*ETO* is an acronym for *Event Transfer Object*, a suggested naming convention by the ABP team, but not technically required). You can add more properties if needed, but for this tutorial, that is more than enough. ### 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. 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: +The `IDistributedEventBus` service publishes events to the event bus. Open the `ModularCrm.Ordering` module's .NET solution, and update the `OrderAppService` as follows: ````csharp using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using ModularCrm.Ordering.Enums; using ModularCrm.Ordering.Events; -using ModularCrm.Ordering.Entities; using ModularCrm.Products.Integration; using Volo.Abp.Domain.Repositories; using Volo.Abp.EventBus.Distributed; @@ -74,7 +73,7 @@ namespace ModularCrm.Ordering.Services; public class OrderAppService : OrderingAppService, IOrderAppService { - private readonly IRepository _orderRepository; + private readonly IRepository _orderRepository; private readonly IProductIntegrationService _productIntegrationService; private readonly IDistributedEventBus _distributedEventBus; @@ -132,17 +131,17 @@ public class OrderAppService : OrderingAppService, IOrderAppService } ```` -The `OrderAppService.CreateAsync` method creates a new `Order` entity, saves it to the database and finally publishes an `OrderPlacedEto` event. +We've changed the `CreateAsync` method. Now it creates a new `Order` entity, saves it to the database and finally publishes an `OrderPlacedEto` event. ## Subscribing to an Event -This section will subscribe to the `OrderPlacedEto` event in the Products module and decrease the related product's stock count once a new order is placed. +This section will subscribe to the `OrderPlacedEto` event in the Catalog module and decrease the related product's stock count once a new order is placed. -### Adding a Reference to the `ModularCrm.Ordering.Contracts` Package +### Adding a Reference of the `ModularCrm.Ordering.Contracts` Package -Since the `OrderPlacedEto` class is in the `ModularCrm.Ordering.Contracts` project, we must add that package's reference to the Products module. This time, we will use the *Import Module* feature of ABP Studio (as an alternative to the approach we used in the *Adding a Reference to the `ModularCrm.Products.Application.Contracts` Package* section of the [previous part](part-06.md)). +Since the `OrderPlacedEto` class is in the `ModularCrm.Ordering.Contracts` project, we must add that package's reference to the Catalog module. This time, we will use the *Import Module* feature of ABP Studio (as an alternative to the approach we used in the *Adding a Reference to the `ModularCrm.Catalog.Contracts` Package* section of the [previous part](part-06.md)). -Open the ABP Studio UI and stop the application if it is already running. Then open the *Solution Explorer* in ABP Studio, right-click the `ModularCrm.Products` module and select the *Import Module* command: +Open the ABP Studio UI and stop the application if it is already running. Then open the *Solution Explorer* in ABP Studio, right-click the `ModularCrm.Catalog` module and select the *Import Module* command: ![abp-studio-import-module-ordering](images/abp-studio-import-module-ordering.png) @@ -150,27 +149,28 @@ In the opening dialog, find and select the `ModularCrm.Ordering` module, check t ![abp-studio-import-module-dialog-for-ordering](images/abp-studio-import-module-dialog-for-ordering.png) -Once you click the OK button, the Ordering module is imported to the Products module, and an installation dialog is open: +Once you click the OK button, the Ordering module is imported to the Catalog module, and an installation dialog is open: -![abp-studio-install-module-dialog-for-ordering](images/abp-studio-install-module-dialog-for-ordering.png) +![abp-studio-install-module-dialog-for-ordering](images/abp-studio-install-module-dialog-for-ordering-v2.png) -Here, select the `ModularCrm.Ordering.Contracts` package on the left side (because we want to add that package reference) and `ModularCrm.Products.Domain` package on the middle area (because we want to add the package reference to that project). We installed it on the [domain layer](../../framework/architecture/domain-driven-design/domain-layer.md) of the Products module since we will create our event handler in that layer. Click the OK button to finish the installation operation. +Here, select the `ModularCrm.Ordering.Contracts` package on the left side (because we want to add that package reference) and `ModularCrm.Catalog` package on the middle area (because we want to add the package reference to that project). Also, select the `ModularCrm.Ordering` package on the right side, and unselect all packages on the middle area (we don't need the implementation or any other packages). Then, click the OK button to finish the installation operation. You can check the ABP Studio's *Solution Explorer* panel to see the module import and the project reference (dependency). -![abp-studio-imports-and-dependencies](images/abp-studio-imports-and-dependencies.png) +![abp-studio-imports-and-dependencies](images/abp-studio-imports-and-dependencies-v2.png) ### Handling the `OrderPlacedEto` Event -Now, it is possible to use the `OrderPlacedEto` class inside the Product module's domain layer since it has the `ModularCrm.Ordering.Contracts` package reference. +Now, it is possible to use the `OrderPlacedEto` class inside the Catalog module since it has the `ModularCrm.Ordering.Contracts` package reference. -Open the Product module's .NET solution in your IDE, locate the `ModularCrm.Products.Domain` project, and create a new `Orders` folder and an `OrderEventHandler` class inside that folder. The final folder structure should be like this: +Open the Catalog module's .NET solution in your IDE, locate the `ModularCrm.Catalog` project, and create a new `Orders` folder and an `OrderEventHandler` class inside that folder. The final folder structure should be like this: -![visual-studio-order-event-handler](images/visual-studio-order-event-handler.png) +![visual-studio-order-event-handler](images/visual-studio-order-event-handler-v2.png) Replace the `OrderEventHandler.cs` file's content with the following code block: ````csharp +using ModularCrm.Catalog; using ModularCrm.Ordering.Events; using System; using System.Threading.Tasks; @@ -178,34 +178,33 @@ using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; using Volo.Abp.EventBus.Distributed; -namespace ModularCrm.Products.Orders +namespace ModularCrm.Products.Orders; + +public class OrderEventHandler : + IDistributedEventHandler, + ITransientDependency { - public class OrderEventHandler : - IDistributedEventHandler, - ITransientDependency + private readonly IRepository _productRepository; + + public OrderEventHandler(IRepository productRepository) { - private readonly IRepository _productRepository; + _productRepository = productRepository; + } - public OrderEventHandler(IRepository productRepository) + public async Task HandleEventAsync(OrderPlacedEto eventData) + { + // Find the related product + var product = await _productRepository.FindAsync(eventData.ProductId); + if (product == null) { - _productRepository = productRepository; + return; } - public async Task HandleEventAsync(OrderPlacedEto eventData) - { - // Find the related product - var product = await _productRepository.FindAsync(eventData.ProductId); - if (product == null) - { - return; - } - - // Decrease the stock count - product.StockCount = product.StockCount - 1; + // Decrease the stock count + product.StockCount = product.StockCount - 1; - // Update the entity in the database - await _productRepository.UpdateAsync(product); - } + // Update the entity in the database + await _productRepository.UpdateAsync(product); } } ```` @@ -214,7 +213,7 @@ namespace ModularCrm.Products.Orders We inject the product repository and update the stock count in the event handler method (`HandleEventAsync`). That's it. -### Testing the Order Creation +## Testing the Order Creation 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. @@ -233,7 +232,7 @@ Find the *Orders* API, click the *Try it out* button, enter a sample value the t } ```` -> **IMPORTANT:** Here, you should type a valid Product Id from the Products table of your database! +> **IMPORTANT:** Here, you should type a valid product Id from the *CatalogProducts* table of your database! Once you press the *Execute* button, a new order is created. At that point, you can check the `/Orders` page to see if the new order is shown on the UI, and check the `/Products` page to see if the related product's stock count has decreased. @@ -242,3 +241,7 @@ Here are sample screenshots from the Products and Orders pages: ![products-orders-pages-crop](images/products-orders-pages-crop.png) We placed a new order for Product C. As a result, Product C's stock count has decreased from 55 to 54 and a new line is added to the Orders page. + +## Conclusion + +In this part, we've used ABP's distributed event bus to perform loosely coupled messaging between the modules. In the [next part](part-08.md), we will execute a database query that includes product and order data as an alternative way of integrating modules' data. diff --git a/docs/en/tutorials/modular-crm/part-08.md b/docs/en/tutorials/modular-crm/part-08.md index 460fd3d4fb..7133c76a65 100644 --- a/docs/en/tutorials/modular-crm/part-08.md +++ b/docs/en/tutorials/modular-crm/part-08.md @@ -14,60 +14,40 @@ In this part, you will learn how to perform a database-level JOIN operation on t ## The Problem -One essential purpose of modularity is to create modules that hide (encapsulate) their internal data and implementation details from the other modules. These modules communicate with each other through well-defined [integration services](../../framework/api-development/integration-services.md) and [events](framework/infrastructure/event-bus/distributed). In that way, you can independently develop and change module implementations (even modules' database structures) from each other as long as you don't break these inter-module integration points. +One essential purpose of modularity is to create modules that hide (encapsulate) their internal data and implementation details from the other modules. These modules communicate with each other through well-defined [integration services](../../framework/api-development/integration-services.md) and [events](framework/infrastructure/event-bus/distributed). In that way, you can develop and change module implementations (even modules' database structures) independently from each other as long as you don't introduce break changes on these module integration points. In a non-modular application, accessing the related data is easy. You could write a LINQ expression that joins `Orders` and `Products` database tables to get the data with a single database query. It would be easier to implement and execute with a good performance. -On the other hand, it becomes harder to perform operations or get reports requiring access to multiple modules' internal data in a modular system. Remember the *[Implementing Integration Services](part-06.md)* part; We couldn't access the product data inside the Ordering module (`IOrderingDbContext` only defines a `DbSet`), so we needed to create an integration service just to get names of products. This approach is harder to implement and less performant (yet it is acceptable if you don't show too many orders on the UI or properly implement a caching layer). Still, it gives freedom to the Products module about its internal database or application logic changes. For example, you can decide to move product data to another physical database or even to another database management system (DBMS) without affecting the other modules. +On the other hand, it becomes harder to perform operations or get reports requiring access to multiple modules' internal data in a modular system. Remember the *[Implementing Integration Services](part-06.md)* part; We couldn't access the product data inside the Ordering module (`IOrderingDbContext` only defines a `DbSet`), so we needed to create an integration service just to get names of products with a list of IDs. This approach is harder to implement and less performant (yet it is acceptable if you don't show too many orders on the UI or properly implement a caching layer). Still, it gives freedom to the Catalog module about its internal database or application logic changes. For example, you can decide to move product data to another physical database or even to another database management system (DBMS) without affecting the other modules. ## A Solution Option If you want to perform a single database query that spans database tables of multiple modules in a modular system, you still have some options. One option can be creating a reporting module with access to all entities (or database tables). However, when you do that, you accept the following limitations: * When you change a module's database structure, you should also update your reporting code. That is reasonable, but all module developers should let you know in such a case. -* You can not change the DBMS of a module easily. For example, performing such a JOIN operation would be impossible if you decide to use MongoDB for your Products module while the Ordering module still uses SQL Server. Moving the Products module to another SQL Server database in another physical server can also break your reporting logic. +* You can not change the DBMS of a module easily. For example, performing such a JOIN operation would be impossible if you decide to use MongoDB for your Catalog module while the Ordering module still uses SQL Server. Moving the Catalog module to another SQL Server database in another physical server can also break your reporting logic. If these are not problems for you, or if you can handle them when they become problems, you can create reporting modules or aggregator modules that work with multiple modules' data. -In the next section, we will use the main application's codebase to implement such a JOIN operation to keep the tutorial short. However, you already learned how to create new modules, so you can create a new module and develop your JOIN logic inside that new module if you want. +In the next section, we will use the main application's codebase to implement such a JOIN operation to keep the tutorial short. However, you already learned how to create new modules, so you can create a new reporting module and develop your JOIN logic inside that new module if you want. ## The Implementation In this section, we will create an application service in the main application's .NET solution. That application service will perform a LINQ operation on the `Product` and `Order` entities. -### Defining the Reporting Service Interface - -We will define the `IOrderReportingAppService` interface in the main application's .NET solution. - -#### Adding `ModularCrm.Ordering.Contracts` Package Reference - -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` package and select the *Add Package Reference* command: - -![abp-studio-add-package-reference-5](images/abp-studio-add-package-reference-5.png) - -Select the *Imported modules* tab, find and check the `ModularCrm.Ordering.Contracts` package and click the OK button: - -![abp-studio-add-package-reference-dialog-4](images/abp-studio-add-package-reference-dialog-4.png) - -The package reference has been added, and we can now use the types in the `ModularCrm.Ordering.Contracts` package. - -#### Defining the `IOrderReportingAppService` Interface +### Defining the `IOrderReportingAppService` 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; -using System.Threading.Tasks; +using ModularCrm.Orders; using Volo.Abp.Application.Services; -namespace ModularCrm.Orders +namespace ModularCrm.Services.Orders; + +public interface IOrderReportingAppService : IApplicationService { - public interface IOrderReportingAppService : IApplicationService - { - Task> GetLatestOrders(); - } + Task> GetLatestOrders(); } ```` @@ -75,25 +55,24 @@ We have a single method, `GetLatestOrders`, that will return a list of the lates ````csharp using System; -using ModularCrm.Ordering.Contracts.Enums; +using ModularCrm.Ordering; + +namespace ModularCrm.Orders; -namespace ModularCrm.Orders +public class OrderReportDto { - public class OrderReportDto - { - // Order data - public Guid OrderId { get; set; } - public string CustomerName { get; set; } - public OrderState State { get; set; } - - // Product data - public Guid ProductId { get; set; } - public string ProductName { get; set; } - } + // Order data + public Guid OrderId { get; set; } + public string CustomerName { get; set; } = null!; + public OrderState State { get; set; } + + // Product data + public Guid ProductId { get; set; } + public string ProductName { get; set; } = null!; } ```` -`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. After adding these files, the final folder structure should be like this: @@ -106,52 +85,49 @@ Create a class named `OrderReportingAppService` under the `Services/Orders` fold Open the `OrderReportingAppService.cs` file and change its content by the following code block: ````csharp -using ModularCrm.Ordering.Entities; -using ModularCrm.Products; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using ModularCrm.Catalog; +using ModularCrm.Ordering; +using ModularCrm.Services; +using ModularCrm.Services.Orders; using Volo.Abp.Domain.Repositories; -namespace ModularCrm.Orders +namespace ModularCrm.Orders; + +public class OrderReportingAppService : + ModularCrmAppService, + IOrderReportingAppService { - public class OrderReportingAppService : - ModularCrmAppService, - IOrderReportingAppService + private readonly IRepository _orderRepository; + private readonly IRepository _productRepository; + + public OrderReportingAppService( + IRepository orderRepository, + IRepository productRepository) + { + _orderRepository = orderRepository; + _productRepository = productRepository; + } + + public async Task> GetLatestOrders() { - private readonly IRepository _orderRepository; - private readonly IRepository _productRepository; - - public OrderReportingAppService( - IRepository orderRepository, - IRepository productRepository) - { - _orderRepository = orderRepository; - _productRepository = productRepository; - } - - public async Task> GetLatestOrders() - { - var orders = await _orderRepository.GetQueryableAsync(); - var products = await _productRepository.GetQueryableAsync(); - - var latestOrders = (from order in orders - join product in products on order.ProductId equals product.Id - orderby order.CreationTime descending - select new OrderReportDto - { - OrderId = order.Id, - CustomerName = order.CustomerName, - State = order.State, - ProductId = product.Id, - ProductName = product.Name - }) - .Take(10) - .ToList(); - - return latestOrders; - } + var orders = await _orderRepository.GetQueryableAsync(); + var products = await _productRepository.GetQueryableAsync(); + + var latestOrders = (from order in orders + join product in products on order.ProductId equals product.Id + orderby order.CreationTime descending + select new OrderReportDto + { + OrderId = order.Id, + CustomerName = order.CustomerName, + State = order.State, + ProductId = product.Id, + ProductName = product.Name + }) + .Take(10) + .ToList(); + + return latestOrders; } } ```` @@ -168,11 +144,11 @@ That's all. In that way, you can execute JOIN queries that use data from multipl We haven't created a UI to show list of the latest orders using `OrderReportingAppService`. However, we can use the Swagger UI again to test it. -Open the ABP Studio UI, stop the application if it is running, build and run it again. Once the application starts, browse it, then add `/swagger` to the end of the URL to open the Swagger UI: +Open the ABP Studio UI, stop the application if it is running, build and run it again. Once the application starts, browse it, then add `/swagger` to the end of the URL to open the Swagger UI. Here, find the `OrderReporting` API and execute it as shown below: ![abp-studio-swagger-list-orders](images/abp-studio-swagger-list-orders.png) -Here, find the `OrderReporting` API and execute it as shown above. You should get the order objects with product names. +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:44303/api/app/order-reporting/latest-orders` - port can be different for your case) @@ -188,7 +164,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-Standard). ## See Also