From 4ff615f39d2ab3b7c2ee4c0e208933fe8a33d07f Mon Sep 17 00:00:00 2001 From: enisn Date: Wed, 13 May 2026 16:32:27 +0300 Subject: [PATCH 1/4] Update index.md --- docs/en/framework/ui/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/framework/ui/index.md b/docs/en/framework/ui/index.md index ca33ca9ff1..b1b4bcecd0 100644 --- a/docs/en/framework/ui/index.md +++ b/docs/en/framework/ui/index.md @@ -7,11 +7,11 @@ # ABP UI Options -ABP provides several options for building the user interface (UI) in your applications. Here are some of the officially supported UI options you can use with ABP: +ABP provides several options for building the user interface (UI) in your applications. React is part of the **modern template system**. Here are some of the officially supported UI options you can use with ABP: * [React](./react/index.md) *(modern template system only)* * [MVC / Razor Pages](./mvc-razor-pages/overall.md) * [Blazor](./blazor/overall.md) * [Angular](./angular/quick-start.md) * [React Native](./react-native/index.md) -* [MAUI](./maui/index.md) \ No newline at end of file +* [MAUI](./maui/index.md) From e5643ffa0b7421c8d0daf3f6f7765e8900ac674e Mon Sep 17 00:00:00 2001 From: enisn Date: Wed, 13 May 2026 16:33:30 +0300 Subject: [PATCH 2/4] Update index.md --- docs/en/get-started/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/en/get-started/index.md b/docs/en/get-started/index.md index dcc7aa80c3..1522f3f955 100644 --- a/docs/en/get-started/index.md +++ b/docs/en/get-started/index.md @@ -20,6 +20,8 @@ Please select one of the following documents best fits for your application: - [WPF Application](wpf.md) - [Console Application](console.md) +If you seek a React-based web UI, use the **modern template system** with ABP Studio or `abp new --modern`. See [UI options](../framework/ui/index.md) for the full list of officially supported UI frameworks. + ## Which Startup Template is Suitable for Me? You can see the *[Solution Template Selection Guide](../solution-templates/guide.md)* if you are not sure which solution template is suitable for you. From f34f42e6f45b120054803aa5667055bde4428ca8 Mon Sep 17 00:00:00 2001 From: enisn Date: Wed, 13 May 2026 16:35:02 +0300 Subject: [PATCH 3/4] Docs: clarify UI options, update image and CLI link Update docs/en/index.md to mention React availability and link to the UI options page; replace/update the UI image (images/ui-options.png) with a larger width and improved alt text; and fix the ABP CLI link to ./cli/index.md. These changes clarify UI framework support and correct internal documentation links. --- docs/en/images/ui-options.png | Bin 41628 -> 33412 bytes docs/en/index.md | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/images/ui-options.png b/docs/en/images/ui-options.png index 3c30cd16d765d456664eceb183df249f9e7a8cd1..d36efc9fb666672c26efd310603d5e6a813f12b4 100644 GIT binary patch literal 33412 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 z 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 b6nvoc%I!LUeK`z%27F0NfW*r_8~Xh}9EuJ3 literal 41628 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 z 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 Sl2sTAVDIIW-;~Li1pFUKH4+p6 diff --git a/docs/en/index.md b/docs/en/index.md index 86b7104b06..3a23e08d8f 100644 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -27,9 +27,9 @@ After getting started, you can read the following documents: ### UI Framework Options -ABP can work with any UI framework, while the following frameworks are supported and well-integrated out of the box: +ABP can work with any UI framework, while the following frameworks are supported and well-integrated out of the box. React is available with the modern template system. See the [UI options](./framework/ui/index.md) page for details. -ui options +ABP UI options including React ### Database Provider Options @@ -84,7 +84,7 @@ ABP Platform provides tooling to help you in your daily development. #### ABP CLI -[ABP CLI](cli.md) is a command-line tool to create new solutions and automate the things with your ABP based solutions. +[ABP CLI](./cli/index.md) is a command-line tool to create new solutions and automate the things with your ABP based solutions. ### Startup Templates From 0db0e5e4a1539f6f2f39c4fb89bdd294e6d7c2aa Mon Sep 17 00:00:00 2001 From: enisn Date: Thu, 14 May 2026 14:27:39 +0300 Subject: [PATCH 4/4] Docs: update modern template and microservice references --- docs/en/docs-nav.json | 4 + .../en/get-started/layered-web-application.md | 8 +- docs/en/get-started/microservice.md | 65 ++++--- .../single-layer-web-application.md | 8 +- .../application-module/index.md | 8 +- docs/en/solution-templates/guide.md | 47 +++-- docs/en/solution-templates/index.md | 13 +- .../microservice/authentication.md | 6 +- .../health-check-configuration.md | 2 +- .../solution-templates/microservice/index.md | 4 +- .../microservice/localization-system.md | 108 +++-------- .../microservice/mobile-applications.md | 176 ++++-------------- .../microservice/overview.md | 28 +-- .../microservice/solution-structure.md | 26 ++- .../microservice/web-applications.md | 60 +++--- .../modular-monolith/index.md | 62 ++++++ 16 files changed, 298 insertions(+), 327 deletions(-) create mode 100644 docs/en/solution-templates/modular-monolith/index.md diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 56079eb813..37f6b402b0 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -2260,6 +2260,10 @@ } ] }, + { + "text": "Modular Monolith", + "path": "solution-templates/modular-monolith" + }, { "text": "Microservice Solution", "isLazyExpandable": true, diff --git a/docs/en/get-started/layered-web-application.md b/docs/en/get-started/layered-web-application.md index f4a383ff0b..4cc0ce9ce9 100644 --- a/docs/en/get-started/layered-web-application.md +++ b/docs/en/get-started/layered-web-application.md @@ -18,6 +18,10 @@ In this quick start guide, you will learn how to create and run a layered (and potentially modular) web application using [ABP Studio](../studio/index.md). +> This page documents the **classic** layered flow. +> +> If you want **React UI**, use the **modern** template flow instead. Modern layered solutions create your application in the `react/` folder and host the ABP Admin Console from the backend at `/admin-console/`. See [React UI](../framework/ui/react/index.md) and the [ABP CLI modern templates](../cli/index.md#modern-templates) section for the correct path. + ## Setup your development environment First things first! Let's setup your development environment before creating the first project. The following tools should be installed on your development machine: @@ -150,7 +154,7 @@ If you uncheck the *Kubernetes Configuration* option, the solution will not incl On the next screen, you can configure the modularity options for your solution: -> If you select the *Setup as a modular solution* option, the solution is created more ready for [modular monolith development](../tutorials/modular-crm/index.md) and allows you to add sub-modules during the solution creation phase. +> If your goal is a new modular monolith, prefer the dedicated **Modular Monolith** architecture in ABP Studio. The modularity option on this screen is the classic-host path for keeping this layered solution modularity-ready and allowing sub-modules during solution creation. ![abp-studio-new-solution-modularity](images/abp-studio-new-solution-dialog-modularity_dark.png) @@ -281,4 +285,4 @@ You can start the following application(s): ## What's next? - [TODO Application Tutorial with Layered Solution](../tutorials/todo/layered/index.md) -- [Web Application Development Tutorial](../tutorials/book-store/index.md) \ No newline at end of file +- [Web Application Development Tutorial](../tutorials/book-store/index.md) diff --git a/docs/en/get-started/microservice.md b/docs/en/get-started/microservice.md index d30e3926fc..ec2d4eb513 100644 --- a/docs/en/get-started/microservice.md +++ b/docs/en/get-started/microservice.md @@ -11,6 +11,8 @@ In this quick start guide, you will learn how to create and run a microservice solution using [ABP Studio](../studio/index.md). +This guide follows the current **modern** microservice template in ABP Studio. When you select a web UI, ABP Studio creates the main application in `apps/react`, the administration UI in `apps/react-admin-console`, and optionally the public website in `apps/react-public-web`. + ## Setup your development environment First things first! Let's setup your development environment before creating the first project. The following tools should be installed on your development machine: @@ -18,7 +20,7 @@ First things first! Let's setup your development environment before creating the * [Visual Studio 2026](https://visualstudio.microsoft.com/vs/) or another IDE that supports .NET development * [.NET 10.0+](https://dotnet.microsoft.com/en-us/download/dotnet) * [Node v22.11+](https://nodejs.org/) -* [Yarn v1.22+ (not v2+)](https://classic.yarnpkg.com/en/docs/install) or npm v10+ (already installed with Node), **This is required for the Angular applications.** +* npm v10+ (already installed with Node) or [Yarn v1.22+ (not v2+)](https://classic.yarnpkg.com/en/docs/install) for the React applications * [Docker Desktop (with Kubernetes enabled)](https://www.docker.com/products/docker-desktop/) * [Helm](https://helm.sh/docs/intro/install/) * [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/deploy/) @@ -62,11 +64,11 @@ On that screen, you can enable multi-tenancy for your solution. After selecting ![abp-studio-new-solution-dialog-ui-framework](images/abp-studio-new-solution-dialog-ui-framework-microservice.png) -Here, you see all the possible UI options supported by that startup solution template. You can pick your favorite one and click the *Next* button for the *Mobile Framework* selection screen: +For the modern microservice template, the web UI choices are `React` and `No UI`. Select `React` to create `MyCompanyName.MyProjectName.React` in `apps/react` together with the separate `MyCompanyName.MyProjectName.ReactAdminConsole` application in `apps/react-admin-console`. Then click the *Next* button for the *Mobile Framework* selection screen: ![abp-studio-new-solution-dialog-mobile-framework](images/abp-studio-new-solution-dialog-mobile-framework-microservice.png) -Here, you see all the mobile applications available in that startup solution template. These mobile applications are well-integrated into your solution and can use the same backend with your web application. They are simple (do not have pre-built features as much as the web application) but a very good starting point to build your mobile application. +For the modern microservice template, the mobile choices are `React Native` and `None`. If you select `React Native`, ABP Studio creates the mobile app under `apps/mobile/react-native` and adds the `MyCompanyName.MyProjectName.MobileGateway` application. > If you select a mobile application, an additional API Gateway is created that is only used by the mobile application. @@ -74,7 +76,7 @@ Pick the one best for you, or select the *None* if you don't want a mobile appli ![abp-studio-new-solution-dialog-public-web-site](images/abp-studio-new-solution-dialog-public-web-site.png) -You can select a public website to be created in your solution. The public website is a simple landing page that can be used to introduce your product, provide documentation, and so on. +If you enable the public website, ABP Studio creates the `MyCompanyName.MyProjectName.ReactPublicWeb` application under `apps/react-public-web` and the `MyCompanyName.MyProjectName.PublicGateway` application under `gateways/public`. ![abp-studio-new-solution-dialog-dynamic-localization](images/abp-studio-new-solution-dialog-dynamic-localization.png) @@ -136,7 +138,7 @@ Now, we are ready to allow ABP Studio to create our solution. Just click the *Cr ![abp-studio-created-new-microservice-solution](images/abp-studio-created-new-microservice-solution.png) -You can explore the solution, but you need to **wait for background tasks to be completed** before running any application in the solution (it can take up to a few minutes to set up all). +You can explore the solution, but you need to **wait for background tasks to be completed** before running any application in the solution. ABP Studio uses that time to install the required client-side dependencies and create the Auth Server signing certificate. > The solution structure can be different in your case based on the options you've selected. @@ -148,9 +150,13 @@ This **solution** consists of several **modules** shown in the *Solution Explore ![abp-studio-created-microservice-solution-explorer](images/abp-studio-created-microservice-solution-explorer.png) -Each leaf item (e.g. `Acme.CloudCrm.IdentityService` or `Acme.CloudCrm.Web`) in the tree above is an ABP Studio module. They are grouped into solution folders (`apps`, `gateways`, and `services`). +Each leaf item in the tree above is an ABP Studio module. They are grouped into solution folders (`apps`, `gateways`, and `services`): + +* `apps`: contains `Acme.CloudCrm.AuthServer`, the main React UI as `Acme.CloudCrm.React`, the separate administration UI as `Acme.CloudCrm.ReactAdminConsole`, and optionally `Acme.CloudCrm.ReactPublicWeb`. +* `gateways`: contains `Acme.CloudCrm.WebGateway` for the main web UI, and optionally `Acme.CloudCrm.PublicGateway` and `Acme.CloudCrm.MobileGateway`. +* `services`: contains backend microservices such as `Acme.CloudCrm.AdministrationService`, `Acme.CloudCrm.IdentityService`, and the optional services selected in the wizard. -Each module has a separate .NET Solution. You can open a module's (or .NET solution's) folder by right-clicking a module in the *Solution Explorer* tree, select *Open with* -> *Explorer* option as shown below: +The .NET applications in `apps/auth-server`, `gateways/*`, and `services/*` each have their own .NET solutions. The React applications are standard Node projects under `apps/react` and `apps/react-admin-console`, plus `apps/react-public-web` when the public website is enabled. You can open any module's folder by right-clicking it in the *Solution Explorer* tree and selecting *Open with* -> *Explorer* as shown below: ![abp-studio-open-module-folder](images/abp-studio-open-module-folder.png) @@ -158,15 +164,15 @@ If we open the `Acme.CloudCrm.IdentityService` module's path in the explorer, we ![abp-studio-microservice-example-identity-service-files](images/abp-studio-microservice-example-identity-service-files.png) -This microservice solution is designed to have separate .NET solutions for each service to make it possible to develop independently from the other services and applications. +This microservice solution is designed so each backend service, gateway, and the Auth Server can be developed independently, while the React applications stay in their own app folders. -You can open any module's .NET solution in your favorite IDE and make your development. The following figure is a screenshot from the *Identity* microservice opened in Visual Studio: +You can open any backend module's .NET solution in your favorite IDE and make your development. The following figure is a screenshot from the *Identity* microservice opened in Visual Studio: ![abp-studio-microservice-example-identity-service-in-visual-studio](images/abp-studio-microservice-example-identity-service-in-visual-studio.png) -If you explore that .NET solution, you will typically see some configuration code, and you won't see any business code. That's because the solution uses [pre-built application modules](../modules) as NuGet packages, and doesn't contain their source code. In this way, you can easily upgrade these application modules when a new version is available. +If you explore that .NET solution, you will typically see composition and configuration code, and you won't see the source code of the built-in modules. That's because the solution uses [pre-built application modules](../modules) as NuGet packages. In this way, you can easily upgrade these application modules when a new version is available. -You will typically add new microservices to the solution and perform your business logic inside these new services (however, you can always want to download the source code of any pre-built application module and include it into your solution to freely customize it). +You will typically add new microservices to the solution and perform your business logic inside these new services. You can also customize the React applications in `apps/react`, `apps/react-admin-console`, and, if enabled, `apps/react-public-web` when you need UI-specific changes. ## Running the Solution @@ -184,7 +190,7 @@ In the *Solution Runner* section (on the left side) you can see all the runnable > A leaf item in the *Solution Runner* is called as an *Application* as it is an executable application, excluding items under `Containers`. -As shown in the figure above, the executable applications are grouped into folders like `apps`, `gateways`, and `services`. You can start/stop them all, a group (folder) of them, or one by one. The `Containers` branch contains the needed docker containers for the applications. +As shown in the figure above, the executable applications are grouped into folders like `apps`, `gateways`, and `services`. You can start/stop them all, a group (folder) of them, or one by one. The `Containers` branch contains the needed docker containers for the applications. Before running the applications, you can run the all application by right-clicking the root item in the *Solution Runner* and select *Build* -> *Build All* action. However, you don't need to do that, because ABP Studio builds the applications before running them by default. @@ -192,6 +198,19 @@ Before running the applications, you can run the all application by right-clicki You can click the *Play* button on the root item in *Solution Runner* to start all the applications. +For the common React-based web flow, the main applications are: + +* `Acme.CloudCrm.AuthServer` +* `Acme.CloudCrm.WebGateway` +* `Acme.CloudCrm.AdministrationService` +* `Acme.CloudCrm.IdentityService` +* `Acme.CloudCrm.React` +* `Acme.CloudCrm.ReactAdminConsole` + +If you enabled optional features, also start the related applications such as `Acme.CloudCrm.ReactPublicWeb`, `Acme.CloudCrm.PublicGateway`, `Acme.CloudCrm.MobileGateway`, `Acme.CloudCrm.SaasService`, `Acme.CloudCrm.AuditLoggingService`, `Acme.CloudCrm.GdprService`, `Acme.CloudCrm.ChatService`, or `Acme.CloudCrm.FileManagementService`. + +> ABP Studio runs the React applications as CLI applications by executing `npm run dev` in their app folders. + > **About the Docker Containers** > > Docker will fetch the docker images before starting the containers in your first run (if they were not fetched before) and that process may take a few minutes depending on your internet connection speed. So, please wait for it to completely start. If the process takes more time than you expect, you can right-click on `Docker-Dependencies` and select the *Logs* command to see what's happening. @@ -200,29 +219,31 @@ You can click the *Play* button on the root item in *Solution Runner* to start a > > Some applications/services may fail on the first run. That may be because of service and database dependencies were not satisfied and an error occurs on the application startup. ABP Studio automatically restarts failing services until it is successfully started. Being completely ready for such a distributed solution may take a while, but it will be eventually started. -Once all the applications are ready, you can right-click the `Web` application and select the *Browse* command: +Once all the applications are ready, you can right-click the `Acme.CloudCrm.React` application and select the *Browse* command: ![abp-studio-microservice-solution-runner-browse](images/abp-studio-microservice-solution-runner-browse.png) -The *Browse* command opens the web application's UI in the built-in browser of ABP Studio: +The *Browse* command opens the main React application's UI in the built-in browser of ABP Studio: ![abp-studio-microservice-solution-runner-browse-microservice](images/abp-studio-microservice-solution-runner-browse-microservice.png) -You can browse your application in a full-featured web browser in ABP Studio. Click the *Login* button in the application UI, enter `admin` as username and `1q2w3E*` as password to login to the application. +You can browse your application in a full-featured web browser in ABP Studio. Click the *Login* button in the application UI, enter `admin` as username and `1q2w3E*` as password to log in to the application. + +Use the same *Browse* command on `Acme.CloudCrm.ReactAdminConsole` to open the administration UI. That application runs separately and uses `/admin-console/` as its base path. > You can also browse the other applications/services (that provides a UI) inside ABP Studio. In this way, you don't need to use an external browser or manually type the application's URL. ## Developing Services Using the Solution Runner -Solution Runner not only runs a multi-applications system easier, but is also useful while developing your services and applications. In a microservice solution, you typically focus on one or a few services and applications. Assume that you want to make a development in `IdentityService`. You can use the following development flow: +Solution Runner not only makes a multi-application system easier to run, but is also useful while developing your services and applications. In a microservice solution, you typically focus on one or a few services and applications. Assume that you want to make a development in `IdentityService`. You can use the following development flow: * Start all the applications/services in the solution and test if everything works as expected. * Stop the `IdentityService` in the Solution Runner. -* Open the `IdentityService`'s .NET solution in your favorite IDE (e.g. Visual Studio). As an easy way of opening it, you can use the *Solution Explorer*, find the `Acme.CloudCrm.IdentityService` module, right-click to it and select the *Open with* -> *Visual Studio* command. +* Open the `IdentityService`'s .NET solution in your favorite IDE (e.g. Visual Studio). As an easy way of opening it, you can use the *Solution Explorer*, find the `Acme.CloudCrm.IdentityService` module, right-click it and select the *Open with* -> *Visual Studio* command. * Make your development in the `IdentityService`. * Run (with or without debugging) your service in Visual Studio (or another IDE). -Once you run the `IdentityService` in Visual Studio, it will be completely integrated into the rest of the system since they all run in your local machine. In addition, the `IdentityService` application will automatically connect to ABP Studio and send runtime data to it as it works in ABP Studio. When you run an application out of ABP Studio, it is shown as *external* in the Solution Runner and you can't stop it in ABP Studio (you should stop where you've started): +Once you run the `IdentityService` in Visual Studio, it will be completely integrated into the rest of the system since they all run on your local machine. In addition, the `IdentityService` application will automatically connect to ABP Studio and send runtime data to it as it works in ABP Studio. When you run an application out of ABP Studio, it is shown as *external* in the Solution Runner and you can't stop it in ABP Studio (you should stop it where you've started it): ![abp-studio-microservice-solution-runner-external-service](images/abp-studio-microservice-solution-runner-external-service.png) @@ -236,6 +257,8 @@ To enable watching, right-click the application/service you want to watch, selec Now, you can make your development on the `IdentityService`. Whenever you save a code file, it is automatically rebuilt and restarted by ABP Studio, so any change will be effective on the running solution in a few seconds. +If you are working on the frontend instead, you can apply the same flow to `Acme.CloudCrm.React` or `Acme.CloudCrm.ReactAdminConsole`, and to `Acme.CloudCrm.ReactPublicWeb` when the public website is enabled, then continue from the related app folder with `npm run dev`. + When you enable watch for an application an *eye* icon is added near to the application: ![abp-studio-microservice-solution-runner-watch-enabled-icon](images/abp-studio-microservice-solution-runner-watch-enabled-icon.png) @@ -271,7 +294,7 @@ After building the Docker images, it is ready to install the Helm chart to Kuber > Installing chart should be fast. However, it may take time for being fully ready in Kubernetes. For example, if an image of a service (e.g. Redis, Rabbit) was not pulled before, it will need to pull image first. -Once the solution is ready in Kubernetes, you can open a browser and visit the following URL: https://cloudcrm-local-web It will open a web page as shown below: +Once the solution is ready in Kubernetes, you can open a browser and visit the following URL: `https://cloudcrm-local-web`. It opens the main web application as shown below: ![abp-studio-microservice-web-application-home-page](images/abp-studio-microservice-web-application-home-page.png) @@ -335,7 +358,7 @@ It will start the interception process, and finally you will see the *intercepti ![abp-studio-microservice-kubernetes-interception-enabled](images/abp-studio-microservice-kubernetes-interception-enabled.png) -From now on, all the traffic coming to the Audit Logging microservice is redirected to your local computer. If you open the Audit Logging page now (`https://cloudcrm-local-web/AuditLogs`), you get an error, because the request is redirected to your local machine but the Audit Logging service is not running on your local machine yet. +From now on, all the traffic coming to the Audit Logging microservice is redirected to your local computer. If you open the Audit Logging page now (`https://cloudcrm-local-web/admin-console/audit-logs`), you get an error, because the request is redirected to your local machine but the Audit Logging service is not running on your local machine yet. Open the `Acme.CloudCrm.AuditLoggingService` .NET solution in your IDE (e.g. Visual Studio), set the `Acme.CloudCrm.AuditLoggingService` as startup project and run it (using F5 for debug mode or CTRL+F5 to run it without debugging). @@ -355,7 +378,7 @@ A typical development flow can be as the following: * *Connect* to a Kubernetes cluster where the solution is already deployed (as explained in the *Kubernetes Integration: Connecting to the Cluster* section). You can do it yourself as explained in the *Kubernetes Integration: Working with Helm Charts* section. * *Intercept* a service you want to develop in your local machine. -* Develop, run, stop, fix, debug, re-run... your service easily in your local environment. You can test your service as integrated to others and visit the application UI in the Kubernetes (you can make it for the Web application as similar). +* Develop, run, stop, fix, debug, re-run... your service easily in your local environment. You can test your service as integrated to others and visit the application UI in Kubernetes. You can follow a similar flow for the main React application or the Admin Console. * Once your development is done, you can *Disable* the interception and re-deploy the service to the Kubernetes cluster. To re-deploy a service to Kubernetes, right-click the service and select *Commands* -> *Redeploy* command: diff --git a/docs/en/get-started/single-layer-web-application.md b/docs/en/get-started/single-layer-web-application.md index ef657722b0..ebd4465122 100644 --- a/docs/en/get-started/single-layer-web-application.md +++ b/docs/en/get-started/single-layer-web-application.md @@ -17,6 +17,10 @@ In this quick start guide, you will learn how to create and run a single layer web application using [ABP Studio](../studio/index.md). +> This page documents the **classic** single-layer flow. +> +> If you want **React UI**, use the **modern** template flow instead. Modern single-layer solutions create your application in the `react/` folder and host the ABP Admin Console from the backend at `/admin-console/`. See [React UI](../framework/ui/react/index.md) and the [ABP CLI modern templates](../cli/index.md#modern-templates) section for the correct path. + ## Setup your development environment First things first! Let's setup your development environment before creating the first project. The following tools should be installed on your development machine: @@ -116,7 +120,7 @@ You can change these settings later if needed. Then click the *Next* button for Configure any additional options as needed and click the *Next* button to continue. On the next screen, you can configure the modularity options for your solution: -> If you select the *Setup as a modular solution* option, the solution is created more ready for [modular monolith development](../tutorials/modular-crm/index.md) and allows you to add sub-modules during the solution creation phase. +> If your goal is a new modular monolith, prefer the dedicated **Modular Monolith** architecture in ABP Studio. The modularity option on this screen is the classic-host path for keeping this single-layer solution modularity-ready and allowing sub-modules during solution creation. ![abp-studio-no-layers-new-solution-modularity](images/abp-studio-no-layers-new-solution-modularity_dark.png) @@ -188,4 +192,4 @@ You can use `admin` as username and `1q2w3E*` as default password to login to th ## What's next? -- [TODO Application Tutorial with Single-Layer Solution](../tutorials/todo/single-layer/index.md) \ No newline at end of file +- [TODO Application Tutorial with Single-Layer Solution](../tutorials/todo/single-layer/index.md) diff --git a/docs/en/solution-templates/application-module/index.md b/docs/en/solution-templates/application-module/index.md index 19b2294a76..dec38a1cfa 100644 --- a/docs/en/solution-templates/application-module/index.md +++ b/docs/en/solution-templates/application-module/index.md @@ -9,6 +9,8 @@ This document explains how to create a **reusable [application module](../../modules)** based on the [module development best practices & conventions](../../framework/architecture/best-practices). +This page documents creating a **standalone module solution**. If you are using the modern [Modular Monolith solution template](../modular-monolith/index.md), ABP Studio can also scaffold additional modules during solution creation or later from *Solution Explorer*. The generated module structure still follows the same reusable module concepts explained here. + > Notice that the application module that is created in this tutorial is not an executable application. To see the module in action, you should install it into an executable application. > > It is advised to see the *[Modular Monolith Application Development Tutorial](../../tutorials/modular-crm/index.md)* to learn how to create application modules, install them into an executable web application, run and test the application. That tutorial uses the *Standard* module template, while this document explains the *DDD* module template. @@ -21,6 +23,8 @@ First, install the ABP Studio if you haven't installed before. You can follow th ### Creating a New Empty Solution +This empty-solution flow is for a standalone module repository. It is different from the modern modular monolith wizard, where modules are added to a main application solution. + Open the ABP Studio and click the `New solution` button in the welcome page or the `File > New Solution` top menu item. Click the `empty solution` link to select the empty solution template. ![New Solution](images/new-solution-v2.png) @@ -143,7 +147,9 @@ You can still create unit tests for your classes which will be harder to write ( ### Host Applications -The solution doesn't have a host application to run your module. However, you can create a [single-layer](../../get-started/single-layer-web-application.md) or [layered](../../get-started/layered-web-application.md) application and [import](../../studio/solution-explorer.md#imports) the created module into the host application. +The solution doesn't have a host application to run your module. For a new modern ABP Studio solution, the most direct host choice is the [Modular Monolith solution template](../modular-monolith/index.md). ABP Studio can add the module during solution creation or later from *Solution Explorer*. + +Classic single-layer and layered host applications are still valid options. You can create a [single-layer](../../get-started/single-layer-web-application.md) or [layered](../../get-started/layered-web-application.md) application and [import](../../studio/solution-explorer.md#imports) the created module into the host application. You can also see the *[Modular Monolith Application Development Tutorial](../../tutorials/modular-crm/index.md)* to learn how to create application modules, install them into an executable web application, run and test the application diff --git a/docs/en/solution-templates/guide.md b/docs/en/solution-templates/guide.md index 944e503c53..3d51d68fad 100644 --- a/docs/en/solution-templates/guide.md +++ b/docs/en/solution-templates/guide.md @@ -1,7 +1,7 @@ ```json //[doc-seo] { - "Description": "Explore ABP's guide to selecting the right startup template for your project, covering architectures like Microservices, N-Layered, and more." + "Description": "Explore ABP's guide to selecting the right classic or modern startup template for your project, covering Single-Layer, Layered, Modular Monolith, and Microservice architectures." } ``` @@ -9,11 +9,23 @@ ABP provides several [startup templates](index.md) to you. It is important to start with the right startup template that is suitable for your **project** and **team**. This guide aims to lead you to select the most proper startup template for your requirements. +ABP currently exposes two closely related template families: + +* **Classic templates**, documented with the `Single-Layer` and `Layered` names in this section. +* **Modern solution wizard architectures**, which use the names `Simple Monolith`, `Layered Monolith`, `Modular Monolith`, and `Microservice`. + +Throughout this guide: + +* **Single-Layer** corresponds to the modern **Simple Monolith** architecture. +* **N-Layered** corresponds to the modern **Layered Monolith** architecture. +* **Modular Monolith** has a dedicated modern solution path in ABP Studio. +* **Microservice** exists in both families, but the current microservice reference pages describe the modern React-based web structure. + The following **architectures** will be discussed based on ABP startup templates: -* **Single-Layer** (non-layered) application -* **N-Layered** application -* **Modular** application +* **Single-Layer / Simple Monolith** application +* **N-Layered / Layered Monolith** application +* **Modular Monolith** application * **Microservice** solution ## What is a Startup Template? @@ -24,7 +36,7 @@ In the following section, you will understand what a startup template is and wha A startup solution template is a **pre-architected** structure. For example, the [layered startup template](layered-web-application/index.md) is a great starting point if you want to build a layered application code-base based on [Domain-Driven Design](../framework/architecture/domain-driven-design/index.md) principles and patterns. -However, starting with any startup template **doesn't limit you** on adding or removing projects, layers, integration packages, and creating other applications/services. You can even start with a [single-layer application template]() and convert it to a microservice solution. However, if you want to build a microservice solution, starting with the [microservice startup template](microservice/index.md) is the best. +However, starting with any startup template **doesn't limit you** on adding or removing projects, layers, integration packages, and creating other applications/services. You can even start with a [single-layer application template](single-layer-web-application/index.md) and convert it to a microservice solution. However, if you want to build a microservice solution, starting with the [microservice startup template](microservice/index.md) is the best. So, it is **best to start with the most suitable startup template** for your purpose and then modify the solution to fit your custom requirements. @@ -52,7 +64,7 @@ Up to this point, it is explained what a startup template is and the features it ### Single-Layer Application Solution Template -The [single-layer solution template](single-layer-web-application/index.md) is the simplest. It provides a **minimal solution architecture** while starting a new project. Your .NET solution typically contains a **single, or a few .NET projects** depending on your UI and other preferences while creating your solution. +The [single-layer solution template](single-layer-web-application/index.md) is the simplest classic option. It provides a **minimal solution architecture** while starting a new project. In the modern solution wizard, the comparable architecture is **Simple Monolith**. Your .NET solution typically contains a **single, or a few .NET projects** depending on your UI and other preferences while creating your solution. The following figure shows a single-project web application that has [MVC (Razor Pages) UI](../framework/ui/mvc-razor-pages/overall.md) and [Entity Framework Core](../framework/data/entity-framework-core/index.md) database provider with the default configuration: @@ -85,7 +97,7 @@ These options are not implemented to keep the solution structure as simple as po ### Layered Solution Template -The [layered application startup template](layered-web-application/index.md) is a .NET solution that consists of several projects. +The [layered application startup template](layered-web-application/index.md) is the classic layered option. In the modern solution wizard, the comparable architecture is **Layered Monolith**. It is a .NET solution that consists of several projects. Each project represents a layer of the application or has a specific functionality for the solution. The exact project count in your solution depends on the options you have selected. @@ -117,9 +129,11 @@ In the following conditions, you may consider to use the layered solution templa ### Modular Monolith Applications -ABP does not provide a specific modular monolith application startup template. However, it is not needed. Let us explain why. +ABP Studio's modern solution wizard includes a dedicated [Modular Monolith solution template](modular-monolith/index.md). It creates a main application, enables modularity automatically, and lets you scaffold additional modules while creating the solution. This is the most direct path for new modular ABP Studio solutions. + +Classic ABP solutions can still build a modular monolith by combining a host application and reusable application modules. -The ABP Framework and [ABP Studio](../studio/index.md) are already designed to support modular application development from their beginning. ABP framework provides all the **necessary infrastructure** for [modularity](../framework/architecture/modularity/basics.md) and all other framework features are **compatible with modular solutions**. +The ABP Framework and [ABP Studio](../studio/index.md) are designed to support modular application development. ABP framework provides all the **necessary infrastructure** for [modularity](../framework/architecture/modularity/basics.md) and all other framework features are **compatible with modular solutions**. On the other hand, the main purpose of ABP Studio's [Solution Explorer panel](../studio/solution-explorer.md) is to **architect and build modular and complex software solutions**. You can easily create new modules, arrange dependencies between the modules and import/install these modules into a monolith application. While you can do all these manually yourself, ABP Studio makes it extremely easy to do and understand it. @@ -131,19 +145,20 @@ A **modular monolith** application consists of a **single host** application and In this example, `MyCrm.Host` is an almost-empty host application that has package references to other modules. Every module consists of two packages: implementation and contract packages. -You can follow the steps below to create such a modular solution with ABP Studio: +You can follow one of the paths below to create such a modular solution with ABP Studio: -* **Create a new application** using either [single-layer](single-layer-web-application/index.md) or [layered](layered-web-application/index.md) application startup template. That application will be the **host application** of your solution. -* **Create new modules** (right-click to the solution root, select the *Add* -> *New Module* -> ... command). -* **Import & Install** these **modules** to the host application. +* **Modern path**: Choose the [Modular Monolith solution template](modular-monolith/index.md), configure the main application, then add extra modules in the *Modularity* step or later from *Solution Explorer*. +* **Classic composition path**: Create a host application using either the [single-layer](single-layer-web-application/index.md) or [layered](layered-web-application/index.md) template, create new modules, then import and install these modules into the host application. > You can follow the **[Modular Monolith Application Development Tutorial](../tutorials/modular-crm/index.md)** to learn how to build a modular application step by step. #### Which Startup Template should be used for a Modular Application? -So, both [single-layer](single-layer-web-application/index.md) and [layered](layered-web-application/index.md) application startup templates are inherently modular. Just use one of them and start your modular solution. You may wonder which one to start: +For a new modern ABP Studio solution, use the dedicated [Modular Monolith solution template](modular-monolith/index.md). + +If you are composing a modular monolith by using the classic host + module approach, both [single-layer](single-layer-web-application/index.md) and [layered](layered-web-application/index.md) application startup templates are inherently modular. In that case, you may wonder which one to start: -* Use the **[single-layer startup template](single-layer-web-application/index.md)** for the host application of your modular monolith if you will leave the host application as empty. It will contain some configuration code of course, but it won't contain any actual application code. **This is the suggested approach.** +* Use the **[single-layer startup template](single-layer-web-application/index.md)** for the host application of your modular monolith if you will leave the host application as empty. It will contain some configuration code of course, but it won't contain any actual application code. **This is the suggested classic host approach.** * Use the **[layered application startup template](layered-web-application/index.md)** if you will write some application code into the hosting application. You may want to write some code that makes multiple module operations that are not easy to implement in a particular module. In that case, a layered hosting application will be a better way to organize your codebase. However, this approach can quickly move your solution away from a modular system. So, take your own risk. #### When Should You Start a Modular Monolith Application? @@ -164,7 +179,7 @@ Even if you are considering building a microservice architecture, it is usually ### Microservice Solution Template -ABP's [microservice startup template](microservice/index.md) includes multiple services, API gateways and applications that are well integrated into each other and ready to be a great **base solution for your microservice system**. +ABP's [microservice startup template](microservice/index.md) includes multiple services, API gateways and applications that are well integrated into each other and ready to be a great **base solution for your microservice system**. The current reference pages describe the modern React-based web structure. In the following picture, you can see an overall diagram that shows the main components of the solution (they vary based on the options while you are creating your solution): ![ms-overall-architecture](microservice/images/overall-architecture.png) diff --git a/docs/en/solution-templates/index.md b/docs/en/solution-templates/index.md index c833b8d508..56969fc6ef 100644 --- a/docs/en/solution-templates/index.md +++ b/docs/en/solution-templates/index.md @@ -1,7 +1,7 @@ ```json //[doc-seo] { - "Description": "Explore ABP's production-ready startup solution templates for Single-Layer, Layered, and Microservice architectures to kickstart your project!" + "Description": "Explore ABP's startup solution templates, from classic Single-Layer and Layered solutions to modern Modular Monolith and Microservice architectures." } ``` @@ -11,11 +11,14 @@ ABP provides pre-architected and production-ready templates to jump start a new > **You can see the [Solution Template Selection Guide](guide.md) if you are not sure which solution template is suitable for you.** +The reference pages below cover both classic and modern ABP Studio template families. The Single-Layer and Layered pages document the classic templates. The Modular Monolith and Microservice pages call out the current modern structure where it differs. + The following solution templates are provided out of the box: -* **[Single-Layer Solution](single-layer-web-application/index.md)**: A single-project solution. Recommended for building an application with a **simpler and easy to understand** architecture. -* **[Layered Solution](layered-web-application/index.md)**: A fully layered (multiple projects) solution based on [Domain Driven Design](../framework/architecture/domain-driven-design) practices. Recommended for long-term projects that need a **maintainable and extensible** codebase. -* **[Microservice Solution](microservice/index.md)**: A **distributed solution** to build **microservice systems**. It includes pre-built services, API gateways, web and mobile applications, Kubernetes and Helm configuration, and everything you need to start your large-scale microservice solution. +* **[Single-Layer Solution](single-layer-web-application/index.md)**: The classic single-project solution. In the modern solution wizard, the closest architecture is **Simple Monolith**. +* **[Layered Solution](layered-web-application/index.md)**: The classic fully layered (multiple projects) solution based on [Domain Driven Design](../framework/architecture/domain-driven-design) practices. In the modern solution wizard, the closest architecture is **Layered Monolith**. +* **[Modular Monolith](modular-monolith/index.md)**: The dedicated modern ABP Studio path for a main application plus reusable modules deployed as a single unit. +* **[Microservice Solution](microservice/index.md)**: A **distributed solution** to build **microservice systems**. The current reference pages describe the modern React-based web structure. * **[Application Module](application-module/index.md)**: A template that can be used to create a **reusable [application module](../modules/index.md)** based on the [module development best practices & conventions](../framework/architecture/best-practices/index.md). It is also suitable for creating **services** (with or without UI). * **Others** - [MAUI Application](../get-started/maui.md) @@ -25,4 +28,4 @@ The following solution templates are provided out of the box: ## See Also * [Solution Template Selection Guide](guide.md) -* [Get Started with ABP Platform](../get-started/index.md) \ No newline at end of file +* [Get Started with ABP Platform](../get-started/index.md) diff --git a/docs/en/solution-templates/microservice/authentication.md b/docs/en/solution-templates/microservice/authentication.md index f079334a24..05721dee24 100644 --- a/docs/en/solution-templates/microservice/authentication.md +++ b/docs/en/solution-templates/microservice/authentication.md @@ -45,4 +45,8 @@ The solution has an authentication server (auth-server) application to provide t ## Authentication Flows -The applications use several flows to authenticate users based on the application type. The MVC UI web application uses the [hybrid flow](https://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth) (OpenID Connect Authentication) to authenticate users, while the SPA and Swagger applications use the [authorization code flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth) to authenticate users. After the user logs into the system and receives the token from the authentication server, the applications (microservices) use [JWT Bearer Authentication](https://jwt.io/introduction/) to authorize users. \ No newline at end of file +The current modern microservice template generates React-based web clients and, optionally, a React Native mobile client. The generated authentication flows are: + +* `react`, `react-admin-console`, and `react-public-web` (when enabled) use the [authorization code flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth) against the `AuthServer`. +* `react-native` (when enabled) is configured with its own client id and scopes, uses the password grant plus refresh tokens against the `AuthServer`, and sends bearer tokens to backend APIs through the `MobileGateway`. +* Backend services, gateways, and other protected APIs use [JWT Bearer Authentication](https://jwt.io/introduction/) to validate the tokens issued by the authentication server. diff --git a/docs/en/solution-templates/microservice/health-check-configuration.md b/docs/en/solution-templates/microservice/health-check-configuration.md index 37cd515209..c966b25824 100644 --- a/docs/en/solution-templates/microservice/health-check-configuration.md +++ b/docs/en/solution-templates/microservice/health-check-configuration.md @@ -12,7 +12,7 @@ Health Check is a feature that allows applications to monitor their health and diagnose potential issues. The Microservice solution template comes with pre-configured Health Check system. -In the Microservice solution template, Health Check configuration is applied in all the services, gateways and UI applications (except Blazor Wasm & Blazor WebApp applications UI applications). +In the current modern microservice template, Health Check configuration is applied to the .NET applications in the solution, such as backend services, gateways, and `auth-server`. The generated React frontend applications (`react`, `react-admin-console`, and `react-public-web` when enabled) do not expose these ASP.NET Core health check endpoints. ### Configuration in `HealthChecksBuilderExtensions.cs` diff --git a/docs/en/solution-templates/microservice/index.md b/docs/en/solution-templates/microservice/index.md index 9b4ed6b3a3..9ea6c3e0f1 100644 --- a/docs/en/solution-templates/microservice/index.md +++ b/docs/en/solution-templates/microservice/index.md @@ -21,6 +21,8 @@ ABP Studio provides pre-architected and production-ready templates to jump start a new solution. One of them is the Microservice solution template. You can use it to build distributed systems with common microservice patterns. It includes multiple services, API gateways and applications that are well integrated to each other and ready to be a great base solution for your microservice system. +Unless stated otherwise, the pages in this section describe the current modern ABP Studio microservice template, where the web layer is React-based. + > **This document explains the Microservice solution template in every details. So, it is a reference document to fully understand the solution and refer when you have trouble.** > > **If you just want to quickly create a microservice solution, please refer to *[Quick Start: Creating a Microservice Solution with ABP Studio](../../get-started/microservice.md)* document.** @@ -61,4 +63,4 @@ ABP Studio provides pre-architected and production-ready templates to jump start * [Adding new API gateways](adding-new-api-gateways.md) * [Mono-repo vs multiple repository approaches](mono-repo-vs-multiple-repository-approaches.md) * [Authoring unit and integration tests](authoring-unit-and-integration-tests.md) - * [How to use with ABP Suite](how-to-use-with-abp-suite.md) \ No newline at end of file + * [How to use with ABP Suite](how-to-use-with-abp-suite.md) diff --git a/docs/en/solution-templates/microservice/localization-system.md b/docs/en/solution-templates/microservice/localization-system.md index ebc710f840..29c887c5f3 100644 --- a/docs/en/solution-templates/microservice/localization-system.md +++ b/docs/en/solution-templates/microservice/localization-system.md @@ -1,7 +1,7 @@ ```json //[doc-seo] { - "Description": "Learn how the ABP Framework manages localization in microservice solutions, enhancing resource management across applications seamlessly." + "Description": "Learn how localization works in ABP Studio's modern microservice solution template across backend services, React apps, and the optional React Native client." } ``` @@ -25,7 +25,7 @@ Like the other fundamental feature modules ([Permission Management](permission-m ## Language Management -> If the dynamic localization option is enabled, then the *Language Management Module** will be removed from the optional modules and a new microservice named `LanguageService` will be created. The `LanguageService` uses *Language Management Module* behind the scene. +> If the dynamic localization option is enabled, then the *Language Management Module* is removed from the optional modules and a new microservice named `LanguageService` is created. `LanguageService` uses the *Language Management Module* behind the scenes. The *Administration* microservice provides a set of APIs to manage localization. The localization resources are defined in each microservice, and when a microservice starts, it registers its localization resources to the related localization tables automatically. After that, you can see the localization resources from the [language texts](../../modules/language-management.md#language-texts) and manage them. @@ -39,106 +39,42 @@ When you create a new microservice solution, you can **enable dynamic localizati ![](./images/enable-dynamic-localization.png) -When you enable this option, a new microservice named **LanguageService** will be added (with the language management module integrated) and you can use its `LanguageServiceResource` class to use the localization entries in your UI application. It's already configured in your final host application, so you don't need to make any configuration related to that. To define a new localization entry you can either use the language files in the `LanguageService` or update the already defined localization entries in the UI (on the *Language Texts* page). - -After defining localization entries or updating them, you can inject the `IStringLocalizer<>` or `IHtmlLocalizer<>` services and use the localized values in your pages for MVC/Razor Pages UI, for instance: - -```html -@page -@using Microsoft.Extensions.Localization -@inject IStringLocalizer L - -
-

@L["LongWelcomeMessage"]

-
-``` +When you enable this option, a new microservice named **LanguageService** is added with the language management module integrated. Define new shared localization entries in the `LanguageService` localization files, or update the registered texts from the *Language Texts* UI. The generated React applications can then consume those backend resources through the application configuration and `application-localization` endpoints. ## UI Localizations -In the microservice architecture, localizations also can be defined in the final host application, if they only need to be defined in the UI. When you create a new microservice solution template, independent from the UI, all configurations are made and you can directly define localization entries and use them in your final UI application. - -> **Note:** When you define localization entries in the host application, then you can't make dynamic localization with the language management module! (Because the language management module, would be unaware of the defined localization entries on the UI side) - -### MVC/Razor Pages & Blazor UIs +In the current modern microservice template, UI-only localizations live in the generated React or React Native applications. The template does not generate MVC, Blazor, or Angular hosts for the modern microservice solution structure. -For MVC & Blazor UIs, you can see the **Localization** directory in your final application, like in the following figure: +> **Note:** UI-only strings that you keep in frontend locale files are not managed by the Language Management module. Put shared or domain-level texts in backend localization resources if they should participate in dynamic localization. -![](./images/ui-localization-mvc.png) +### `react` -In this directory, you can see the language files, those are pre-defined for you to directly add localization entries. The related configurations are already made for you, in the module class (inside the `ConfigureLocalization` method) as below: +The main `react` application initializes `i18next` from `apps/react/src/locales/en.json`. It also: -```csharp - private void ConfigureLocalization(IWebHostEnvironment hostingEnvironment) - { - //code abbreviated for brevity... +* persists the selected culture in browser storage +* sets the document language +* loads available languages from the application configuration +* fetches additional cultures from `/api/abp/application-localization` and merges them into `i18next` - Configure(options => - { - options.Resources - .Add("en") - .AddVirtualJson("/Localization/CloudCrmWeb"); +Use the locale files under `apps/react/src/locales` for UI-only strings that belong only to this application. - options.DefaultResourceType = typeof(CloudCrmWebResource); - }); - } -``` +### `react-admin-console` -You can define new localization entries in the language files under the **Localization** folder and directly use them in your application by using the `IStringLocalizer<>` or `IHtmlLocalizer<>` services: +The `react-admin-console` application keeps its bundled translations under `apps/react-admin-console/src/locales/*.json` and initializes `i18next` from those files. -```html -@page -@using Microsoft.Extensions.Localization -@inject IStringLocalizer L +Its supported language list is controlled by `Volo.Abp.AdminConsole.AbpAdminConsoleOptions.LocalizationLanguages`, which is exposed to the SPA through `/admin-console/api/config`. If no languages are configured, the frontend falls back to `en`. -
-

@L["LongWelcomeMessage"]

-
-``` +Use the admin console locale files for UI-only texts that are specific to the administration surface. Keep shared module texts in backend localization resources. -### Angular UI - -Angular UI gets the localization resources from the [`application-localization`](../../framework/api-development/standard-apis/localization.md) API's response and merges these resources in the `ConfigStateService` for the localization entries/resources coming from the backend side. - -In addition, you may need to define some localization entries and only use them on the UI side. ABP already provides the related configuration for you, so you don't need to make any configurations related to that and instead you can directly define localization entries in the `app.config.ts` file of your angular application as follows: - -```ts -import { provideAbpCore, withOptions } from '@abp/ng.core'; - -export const appConfig: ApplicationConfig = { - providers: [ - // ... - provideAbpCore( - withOptions({ - environment, - registerLocaleFn: registerLocale(), - localizations: [ - { - culture: 'en', - resources: [ - { - resourceName: 'MyProjectName', - texts: { - "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information visit" - } - } - ] - } - ] - }), - ), - ], -}; -``` +### `react-native` -After defining the localization entries, it can be used as below: +When mobile is enabled, the generated React Native client stores its bundled translations under `apps/mobile/react-native/src/locales`. The `LocalizationService.ts` file: -{%{ -```html -
{{ 'MyProjectName::LongWelcomePage' | abpLocalization }}
-``` -}%} +* registers the available locale files +* exposes the language list used by the app +* maps the default resource name from `Environment.ts` -> For more information, please refer to [UI Localization section of the Angular Localization document](../../framework/ui/angular/localization.md). +Use these locale files for mobile-specific UI strings. ## Creating a New Localization Resource diff --git a/docs/en/solution-templates/microservice/mobile-applications.md b/docs/en/solution-templates/microservice/mobile-applications.md index c14895b7b9..224ff906e0 100644 --- a/docs/en/solution-templates/microservice/mobile-applications.md +++ b/docs/en/solution-templates/microservice/mobile-applications.md @@ -1,7 +1,7 @@ ```json //[doc-seo] { - "Description": "Explore how to integrate mobile applications with ABP's microservice solution, featuring options like MAUI and React Native for seamless development." + "Description": "Understand the optional React Native mobile application in ABP Studio's modern microservice solution template." } ``` @@ -19,162 +19,62 @@ > You must have an ABP Business or a higher license to be able to create a microservice solution. -The ABP Studio microservice solution template comes with an optional mobile application that is completely integrated to the solution. There are two options for the mobile application: +The current ABP Studio modern microservice solution template can optionally generate a mobile application under `apps/mobile/react-native`. -* MAUI -* React Native +The modern template does not generate a MAUI mobile application. Its mobile choices are **None** and **React Native**. -You can select the mobile application type while [creating your solution](../../get-started/microservice.md). +## The Mobile Gateway -## Fundamental Structures +If you enable the mobile application, an API gateway named `MobileGateway` is added under `gateways/mobile`. The React Native client calls backend APIs through this gateway. -The following sections explain the common structure of the mobile applications (valid for both of MAUI and React Native applications). +See *[API Gateways](api-gateways.md)* for the shared gateway structure. -### The Mobile Gateway +## Authentication -If you've selected to include the mobile application into your solution, an API Gateway, named `MobileGateway` is also added to the solution. It is located under the `gateways/mobile` in the solution folder. +The generated React Native app is configured in `Environment.ts` with: -You can refer to the *[API Gateways](api-gateways.md)* document to understand the structure of the mobile gateway. +* the `AuthServer` issuer URL +* the `MobileGateway` base URL +* the `ReactNative` client id and scopes -### Authentication +At runtime, the mobile client uses the password grant to exchange credentials for access and refresh tokens at the `AuthServer` `/connect/token` endpoint, then sends bearer tokens to backend APIs through the `MobileGateway`. Account-related operations such as registration, password reset, profile picture management, and logout use the generated API client under `src/api`. -Both of the MAUI and React Native applications are installed as native applications to the devices. So, they are using the [OpenID Connect](../../modules/openiddict.md) protocol to authenticate the users. The authentication is done by the `AuthServer` application. +## Built-in Capabilities -They don't run on a browser, so they can't use the [Cookie Authentication](../../modules/account.md#cookie-authentication) method. They are using the [JWT Bearer Authentication](../../modules/account.md#jwt-bearer-authentication) method. +The generated mobile app already includes screens and flows for: -Best way to communicate with the `AuthServer` application is using the browser. So, the mobile applications are opening a browser window to authenticate the user. Then browser redirects back to the mobile application with the authentication result. +* sign in, registration, forgot password, and reset password +* account and profile picture management +* user-facing settings such as language, theme, and logout -The following screenshot was taken from the *Login* page of the [Account](../../modules/account.md) module in the mobile application's UI: +Localization is handled by the bundled locale files under `src/locales` and `src/services/LocalizationService.ts`. Theme handling lives under `src/theme`. -![mobile-application-login-page](images/authserver-login-page-maui.png) +## Solution Structure +The React Native application is based on [React Native](https://reactnative.dev/) and [Expo](https://expo.dev/). The main files and folders in `apps/mobile/react-native` are: -### User Management +* `Environment.ts`: runtime URLs, client id, scopes, and default localization resource name. +* `src/api`: HTTP clients for token, account, and application configuration calls. +* `src/components`: reusable UI components. +* `src/contexts`: shared React contexts, including localization context. +* `src/hooks`: reusable React hooks. +* `src/interceptors`: request and response interception for the API client. +* `src/locales`: bundled UI translations. +* `src/navigators`: drawer, stack, and tab navigation definitions. +* `src/screens`: application pages such as login, home, settings, and account flows. +* `src/services`: cross-cutting services such as localization helpers. +* `src/store`: Redux actions, listeners, reducers, and selectors. +* `src/theme`, `src/types`, `src/utils`: shared theming, typings, and helper utilities. -User Management is implemented in the MAUI Application in the `Acme.CloudCrm.Maui` project with XAML and C# for MAUI and it is implemented in the React Native Application in the react-native project for React Native UI option. +## Running the Application -ABP MAUI IdentityUsersPage +The React Native app is not started by the ABP Studio solution runner. Run `AuthServer`, `MobileGateway`, and the required backend services first, then start the mobile app with the standard React Native / Expo toolchain. - -### Profile Management - -Profile management allows users to view and update their personal profile picture and their passwords. It provides a seamless experience for users to manage their profiles within the mobile application without navigating to the authserver web application. - -The following screenshot was taken from the *Profile* page in the MAUI application: - -ABP MAUI ProfilePage - -### Other Features - -#### Settings Page -The settings page allows users to change the language and theme of the application, manage their profiles, change their passwords, and also to logout from the application. - -ABP MAUI SettingsPage - -- **Language**: Applications implements ABP localization logic on the platforms. The language is automatically selected based on the device's language. Users can also change the language manually from the settings page. - -- **Dark/Light Theme**: ABP MAUI and React Native applications support both dark and light themes. The theme is automatically selected based on the device's theme. Users can also change the theme manually from the settings page. - -## Applications - -Following sections explain the structure of MAUI and React Native Applications. - -### The MAUI Application - -This is the mobile application that is built based on Microsoft's [MAUI framework](https://learn.microsoft.com/en-us/dotnet/maui). It will be in the solution only if you've selected the MAUI as your mobile application option. - -#### Project Structure -Entire MAUI application is built on the AppShell pattern of MAUI. You can find the AppShell class in the `Acme.CloudCrm.Maui` project. It is the entry point of the application. It is responsible for initializing the application and registering the services. You find all the pages and routing information in the `AppShell.xaml` file. - -- **Pages**: Pages are located in the `Pages` folder of the project. Each page has a XAML & C# file. XAML file is responsible for the UI and C# file is responsible for the initialization of the page. - -- **ViewModels**: ViewModels are located in the `ViewModels` folder of the project. Each ViewModel has a C# file. ViewModels are responsible for the business logic of the pages. - -- **Oidc**: Oidc folder contains the logic for the authentication of the application. It contains the `MauiAuthenticationBrowser` class which manages the authentication process of the application. - -- **Localization**: Localization folder contains the localization logic of the application. It contains regular ABP Localization logic and the `LocalizationResourceManager` class which is wrapper for the ABP localization logic on MAUI. - -- **Messages**: Messages folder contains the message data for the communication inside application. Messages are used to send data between pages and viewmodels. It's designed on the [MVVM Toolkit Messenger](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/messenger) feature. - -- **Storage**: Storage folder contains the storage logic of the application. It contains the `IStorage` class which is wrapper for the [SecureStorage](https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/storage/secure-storage) feature. It is used to store the authentication data of the user and preferences of the application. - -_Rest of the folders are MAUI default folders. You can check the [.NET MAUI single project documentatipon](https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/single-project?view=net-maui-8.0) for more information._ - -#### Running the application -Before running the MAUI Application, rest of the applications in the solution must be running. Such as AuthServer, MobileGateway and the microservices. - -Make sure that you prepared devices for debugging. You can check the following documentation for each platform. - -- [Android](https://learn.microsoft.com/en-us/dotnet/maui/android/emulator/) -- [iOS](https://learn.microsoft.com/en-us/dotnet/maui/ios/pair-to-mac) -- [MacCatalyst](https://learn.microsoft.com/en-us/dotnet/maui/mac-catalyst/cli) -- [Windows](https://learn.microsoft.com/en-us/dotnet/maui/windows/setup) - -##### Network - -All the platforms including iOS, MacCataylst and Windows, runs the applications in the same network of the host. So, you can use the `localhost` address to connect to the applications. - -But in the **Android Emulator**, you need to use the `adb reverse` command to connect to the applications. You can use the following command to connect to the AuthServer application: +For Android emulators or devices, map the development ports before testing: ```bash -adb reverse tcp:44300 tcp:44300 +adb reverse tcp: tcp: +adb reverse tcp: tcp: ``` -> `44300` is an example port. You need to change it based on the port of the AuthServer & MobileGateway application. - -> You need to run the command for a running emulator. If you run the emulator after running the command, you need to run the command again. - - -##### Target Framework - -Since MAUI Applications have multiple target frameworks, you need to select the target framework before running the application. You can select the target framework from the context menu of the Solution Runner. - -![ABP Studio MAUI Target Framework](images/solutionrunner-maui-targetframework.png) - - -##### Running with ABP Studio -You can start the MAUI application with the solution runner. You can click the start button of the MAUI application in the solution runner tree. It will start the application on the selected target framework. Since they're not running on a process and they're running on a device, you can't see them as running state in the solution runner. After the application is deployed, it'll be opened on the device and it'll be shown as stopped in the solution runner. - ---- - -#### Development on MAUI Application - -You can follow [Mobile Application Development Tutorial - MAUI](../../tutorials/mobile/maui) to learn how to develop on MAUI Application. - -### The React Native Application - -This is the mobile application that is built based on Facebook's [React Native framework](https://reactnative.dev/) and [Expo](https://expo.dev/). It will be in the solution only if you've selected React Native as your mobile application option. - -#### Project Structure -- **Environment.ts**: file using for providing application level variables like `apiUrl`, `oAuthConfig` and etc. - -- **api**: The `api` folder contains HTTP request files that simplify API management in the React Native starter template - - `API.ts:` exports **axiosInstance**. It provides axios instance filled api url. - -- **components**: In the `components` folder, you can reach built in react native components that you can use in your app. These components **facilitates** your list, select and etc. operations. - -- **contexts**: `contexts` folder contains [react context](https://react.dev/reference/react/createContext). You can expots your contexts in this folder. `Localization context provided in here` - -- **hocs**: this folder is added to contain higher order components. The purpose is to wrap components with additional features or properties. It initially has a `PermissionHoc.tsx` that wraps a component to check the permission grant status. - -- **hooks**: covers the react native hooks where you can get a reference from [the official documentation](https://react.dev/reference/react/hooks). - -- **interceptors**: initializes a file called `APIInterceptor.ts` that has a function to manage the http operations in a better way. - -- **navigators**: folder contains [react-native stacks](https://reactnavigation.org/docs/stack-navigator/). After creating a new *FeatureName*Navigator we need to provide in `DrawerNavigator.ts` file as `Drawer.Screen` - -- **screens**: folder has the content of navigated page. We will pass as component property to [Stack.Screen](https://reactnavigation.org/docs/native-stack-navigator/) - -- **store**: folder manages state-management operations. We will define `actions`, `listeners`, `reducers`, and `selectors` here. - -- **styles**: folder contains app styles. `system-style.ts` comes built in template we can also add new styles. - -- **utils**: folder contains helper functions that we can use in application - -#### Running the Application - -React Native applications can't be run with the solution runner. You need to run them with the React Native CLI. You can check the [React Native documentation](https://reactnative.dev/docs/environment-setup) to learn how to setup the environment for React Native development. - -Before running the React Native application, the rest of the applications in the solution must be running. Such as AuthServer, MobileGateway and the microservices. - -Then you can run the React Native application by following this documentation: [Getting Started with the React Native](../../framework/ui/react-native/index.md). \ No newline at end of file +Use the ports assigned to `MobileGateway` and `AuthServer` in your generated solution. diff --git a/docs/en/solution-templates/microservice/overview.md b/docs/en/solution-templates/microservice/overview.md index fc322565c3..5d1438f22c 100644 --- a/docs/en/solution-templates/microservice/overview.md +++ b/docs/en/solution-templates/microservice/overview.md @@ -1,7 +1,7 @@ ```json //[doc-seo] { - "Description": "Explore the Microservice solution template for ABP Framework, featuring pre-installed libraries and services for seamless development and production." + "Description": "Explore the modern ABP microservice solution template, featuring React-based web apps, API gateways, and pre-installed libraries and services for development and production." } ``` @@ -21,6 +21,8 @@ In this document, you will learn what the Microservice solution template offers to you. +This page describes the current modern microservice template. In this template family, the web layer supports `React` or `No UI`. + ## The Big Picture ![ms-overall-architecture](images/overall-architecture.png) @@ -45,9 +47,8 @@ All the following **libraries and services** are **pre-installed** and **configu The following features are built and pre-configured for you in the solution. * **Authentication** is fully configured based on best practices; - * **JWT Bearer Authentication** for microservices and applications. - * **OpenId Connect Authentication**, if you have selected the MVC UI. - * **Authorization code flow** is implemented, if you have selected a SPA UI (Angular or Blazor WASM). + * **JWT Bearer Authentication** for microservices and gateways. + * **OpenId Connect / authorization code flow** for the React web applications (`react`, `react-admin-console`, and `react-public-web` when enabled). * Other flows (resource owner password, client credentials...) are easy to use when you need them. * **[Permission](../../framework/fundamentals/authorization/index.md)** (authorization), **[setting](../../framework/infrastructure/settings.md)**, **[feature](../../framework/infrastructure/features.md)** and the **[localization](../../framework/fundamentals/localization.md)** management systems are pre-configured and ready to use. * **[Background job system](../../framework/infrastructure/background-jobs/index.md)** with [RabbitMQ integrated](../../framework/infrastructure/background-jobs/rabbitmq.md). @@ -98,21 +99,22 @@ There are two database provider options are provided on a new microservice solut ### UI Frameworks -The solution comes with a main web application with the following UI Framework options: +The current modern microservice template supports the following web options: + +* **None**: Doesn't include the React web applications. +* **React**: Creates the web application set for the solution. + +When you select **React**, ABP Studio creates: -* **None** (doesn't include a web application to the solution) -* **Angular** -* **MVC / Razor Pages UI** -* **Blazor WebAssembly** -* **Blazor Server** -* **MAUI with Blazor (Hybrid)** +* `apps/react` as the main SPA behind the `web` gateway. +* `apps/react-admin-console` as the dedicated administration SPA. +* `apps/react-public-web` as an additional public site when the *Public Website* option is enabled. ### The Mobile Application -If you prefer, the solution includes a mobile application with its dedicated API Gateway. The mobile application is fully integrated to the system, implements authentication (login) and other ABP features, and includes a few screens that you can use and take as example. The following options are available: +If you prefer, the solution includes a mobile application with its dedicated API Gateway. The mobile application is fully integrated to the system, implements authentication (login) and other ABP features, and includes a few screens that you can use and take as example. In the current modern template, the available options are: * **None** (doesn't include a mobile application to the solution) -* **MAUI** * **React Native** ### Multi-Tenancy & SaaS Module diff --git a/docs/en/solution-templates/microservice/solution-structure.md b/docs/en/solution-templates/microservice/solution-structure.md index 90fd211136..efca1a2f44 100644 --- a/docs/en/solution-templates/microservice/solution-structure.md +++ b/docs/en/solution-templates/microservice/solution-structure.md @@ -1,7 +1,7 @@ ```json //[doc-seo] { - "Description": "Explore the folder structure of ABP Studio's microservice solution template, essential for effective project organization and development." + "Description": "Explore the folder structure of ABP Studio's modern microservice solution template, including its React apps, gateways, and services." } ``` @@ -23,15 +23,17 @@ This document explains the solution and folder structure of ABP Studio's [micros > This document assumes that you've created a new microservice solution by following the *[Quick Start: Creating a Microservice Solution with ABP Studio](../../get-started/microservice.md)* guide. +The current modern template uses React-based web applications. Older MVC, Angular, Blazor, and MAUI web app layouts are not part of this structure. + ## Understanding the ABP Solution Structure When you create a new microservice solution, you will see a tree structure similar to the one below in the *Solution Explorer* panel: ![microservice-solution-in-explorer](images/microservice-solution-in-explorer.png) -Each leaf item (e.g. `Acme.CloudCrm.IdentityService`, `Acme.CloudCrm.Web`, `Acme.CloudCrm.WebGateway`...) in the tree above is an **ABP Studio module**. An ABP Studio module can be a web application, an API gateway, a microservice, a console application or whatever .NET allows you to build. They are grouped into **folders** (`apps`, `gateways` and `services`) in that solution. +Each leaf item in the tree above is an **ABP Studio module**. They are grouped into **folders** (`apps`, `gateways` and `services`) in that solution. -**Each ABP Studio module has a separate .NET solution**; this allows your team to develop them individually, in keeping with the nature of the microservices architecture. +The .NET-based modules, such as `auth-server`, gateways, and backend services, keep their own .NET solution structure. Frontend applications such as `react`, `react-admin-console`, and `react-public-web` live in their own frontend folders. > Refer to the *[Concepts](../../studio/concepts.md)* document for a full definition of ABP Studio solution, module and package terms. @@ -48,13 +50,21 @@ The root folder of the solution will be similar to the following: The folder structure basically matches to the solution in ABP Studio's *Solution Explorer*: * `.abpstudio` folder contains your personal preferences for this solution and it is not added to your source control system (Git ignored). It is created and used by ABP Studio. -* `app` folder contains the applications that has a UI and typically used by the end users of your system. +* `apps` folder contains the applications of the solution: + * `auth-server` is the authentication server based on OpenIddict. + * `react` is the main React SPA when the web UI is enabled. + * `react-admin-console` is the dedicated administration SPA when the web UI is enabled. + * `react-public-web` is the optional public-facing site. + * `mobile/react-native` is the optional mobile application. * `etc` folder contains some additional files for the solution. It has the following sub-folders: * `abp-studio` folder contains settings that are managed by ABP Studio. This folder is added to your source control system and shared between developers. * `docker` folder contains docker-compose configuration to easily run infrastructure dependencies (e.g. RabbitMQ, Redis) of the solution on your local computer. * `helm` folder contains all the Helm charts and related scripts to deploy the solution to Kubernetes. - * `k8s` folder contains some additional files to setup *Kubernetes Dashboard* on your local machine. -* `gateways` folder contains one or more API Gateways (the count depends on if you've selected mobile application or other applications if available). This solution implements the [BFF](https://learn.microsoft.com/en-us/azure/architecture/patterns/backends-for-frontends) (Backend for frontend pattern), that means it has a dedicated API Gateway for each different UI application. + * `scripts` folder contains helper scripts for initializing and running the solution. +* `gateways` folder contains one or more API Gateways. This solution implements the [BFF](https://learn.microsoft.com/en-us/azure/architecture/patterns/backends-for-frontends) pattern, so it has a dedicated API Gateway for each different client type: + * `web` is the gateway for `react`. + * `public` is the gateway for `react-public-web`, when the public website is enabled. + * `mobile` is the gateway for the React Native application, when mobile is enabled. * `services` folder contains the microservices. The microservice count varies based on the options you've selected during the solution creation. However, the following microservices are always included: - * `administration` microservice is used to manage permissions, languages and other fundamental settings of the system. - * `identity` microservice is used to manage users, roles and their permissions. It basically serves to the [Identity](../../modules/identity.md) module's UI (and [OpenIddict](../../modules/openiddict.md) module's UI, if selected). \ No newline at end of file + * `administration` microservice manages permissions, settings, features, and other operational capabilities used by the solution. + * `identity` microservice manages users, roles, and related identity/OpenIddict endpoints used by the web applications. diff --git a/docs/en/solution-templates/microservice/web-applications.md b/docs/en/solution-templates/microservice/web-applications.md index 6c50a7801a..4e9e7eea0c 100644 --- a/docs/en/solution-templates/microservice/web-applications.md +++ b/docs/en/solution-templates/microservice/web-applications.md @@ -1,7 +1,7 @@ ```json //[doc-seo] { - "Description": "Explore the ABP Framework's microservice solution template, featuring integrated web applications and API gateways for seamless development." + "Description": "Explore the web applications in ABP Studio's modern microservice solution template, including react, react-admin-console, react-public-web, and AuthServer." } ``` @@ -19,15 +19,9 @@ > You must have an ABP Business or a higher license to be able to create a microservice solution. -The ABP Studio microservice solution template contains a few web applications. These applications are fully integrated to the solution, uses the [microservices](microservices.md) through the [API gateways](api-gateways.md). +The current ABP Studio microservice solution template uses React-based web applications. These applications are fully integrated to the solution and use the [microservices](microservices.md) through the [API gateways](api-gateways.md). -The following figure shows the application in the *[Solution Explorer](../../studio/solution-explorer.md)* pane of ABP Studio: - -![applications-in-microservice-solution](images/applications-in-microservice-solution.png) - - - -Count and types of the web applications depends on the options you've selected while [creating your solution](../../get-started/microservice.md). This document introduces and explains all the pre-built web applications included in the microservice solution template. +Count and type of the web applications depend on the options you've selected while [creating your solution](../../get-started/microservice.md). This document introduces the pre-built web applications included in the current modern microservice template. ## AuthServer @@ -35,7 +29,7 @@ Count and types of the web applications depends on the options you've selected w The `AuthServer` application is also used by microservices as Authority for JWT Bearer Authentication. -> You normally do not directly browse this application. It is used by the other applications to authenticate the users and applications.. +> You normally do not directly browse this application. It is used by the other applications to authenticate users and applications. The following screenshot was taken from the *Login* page of the [Account](../../modules/account.md) module in the application's UI: @@ -43,40 +37,42 @@ The following screenshot was taken from the *Login* page of the [Account](../../ That application is mainly based on the [OpenIddict](../../modules/openiddict.md), the [Identity](../../modules/identity.md), and the [Account](../../modules/account.md) modules. So, it basically has login, register, forgot password, two factor authentication and other authentication related pages. -## The Main Web Application (optional) - -This is the main web application of the solution. It uses the `Acme.CloudCrm.AuthServer` application as the [API gateway](api-gateways.md). It also uses the Authentication Server application to make users login. +## `react` -The following screenshot was taken from the *Role Management* page of the [Identity](../../modules/identity.md) module in the web application's UI: +`apps/react` is the main authenticated React SPA of the solution. It talks to backend services through the `web` [API gateway](api-gateways.md) and uses `AuthServer` for user login. -![web-application-ui](images/web-application-ui.png) +This application is the main user-facing web UI for the solution. In the generated route configuration, it can also deep-link users to the separate `react-admin-console` application for operational and administration tasks. -The following options are provided while [creating the solution](../../get-started/microservice.md): +If you choose `No UI` while creating the solution, ABP Studio doesn't generate `apps/react`. -* MVC / Razor Pages UI -* Angular -* Blazor WebAssembly -* Blazor Server -* MAUI Blazor (Hybrid) +## `react-admin-console` -The following sections explain each of these UI types. +`apps/react-admin-console` is the dedicated administration SPA. It uses the `/admin-console/` base path and focuses on back-office and operational capabilities such as: -### MVC / Razor Pages Web Application +* account management +* users, roles, claim types, and organization units +* tenants and editions +* OpenIddict applications and scopes +* settings, audit logs, and optional module UIs -`Acme.CloudCrm.Web` module is created if you've selected the MVC / Razor Pages UI while creating the solution. It has own .NET solution that is located under the `apps/web` folder of the solution root. +The route list is filtered by backend permissions and by which backend modules are actually available. -### Angular Web Application +## `Volo.Abp.AdminConsole` Backend Role -If you've selected the Angular UI while creating your solution, a folder named `angular` is included in the `apps` folder of the solution. That folder contains the main web application of the solution that is implemented using Angular. +`Volo.Abp.AdminConsole` is the backend-side companion for the admin console when you host it from an ASP.NET Core application. It is responsible for: -### Blazor WebAssembly Web Application +* serving the admin console under `/admin-console` +* exposing runtime configuration from `/admin-console/api/config` +* exposing module discovery from `/admin-console/api/modules` +* applying branding, theme, localization, and customization settings for the admin console +* optionally redirecting `/` to `/admin-console` -If you've selected the Blazor WebAssembly UI while creating your solution, `Acme.CloudCrm.Blazor` project is included in the `apps` folder of the solution. That folder contains the main web application of the solution that is implemented using Blazor WebAssembly. +The standalone `react-admin-console` app follows the same `/admin-console` route and OIDC conventions, so the frontend and backend pieces stay aligned. -### Blazor Server Web Application +## `react-public-web` -If you've selected the Blazor Server UI while creating your solution, `Acme.CloudCrm.Blazor` project is included in the `apps` folder of the solution. That folder contains the main web application of the solution that is implemented using Blazor Server. +When you enable the *Public Website* option, ABP Studio creates `apps/react-public-web`. This is a separate public-facing React application behind the `public` gateway. -### MAUI Blazor (Hybrid) Web Application +Use this application for anonymous or customer-facing traffic. Keep authenticated operational flows in `react` and `react-admin-console`. -If you've selected the MAUI Blazor (Hybrid) UI while creating your solution, `Acme.CloudCrm.MauiBlazor` project is included in the `apps` folder of the solution. That folder contains the main desktop application of the solution that is implemented using MAUI Blazor (Hybrid) that uses existing Blazor UI Implementation. +`react-public-web` can still participate in authentication flows when needed, but its role is different from the back-office applications: it is the public entry point, not the administration surface. diff --git a/docs/en/solution-templates/modular-monolith/index.md b/docs/en/solution-templates/modular-monolith/index.md new file mode 100644 index 0000000000..a49a268bc7 --- /dev/null +++ b/docs/en/solution-templates/modular-monolith/index.md @@ -0,0 +1,62 @@ +```json +//[doc-seo] +{ + "Description": "Learn how ABP Studio's modern Modular Monolith solution template organizes a main application and reusable modules in a single deployable solution." +} +``` + +# ABP Studio: Modular Monolith Solution Template + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Layered Solution", + "Path": "solution-templates/layered-web-application/index.md" + }, + "Next": { + "Name": "Microservice Solution", + "Path": "solution-templates/microservice/index.md" + } +} +```` + +ABP Studio's modern solution wizard includes a dedicated **Modular Monolith** architecture. Under the hood, it uses the modern no-layers application template for the main application, then enables modularity automatically. + +> **This page documents the modern modular monolith path. If you are working with the classic template family, see the [Solution Template Selection Guide](../guide.md) for the host + module composition approach.** + +## What ABP Studio Creates + +When you choose `Modular Monolith` in the modern solution wizard, ABP Studio: + +* creates the main application by using the modern no-layers host template. +* locks the solution into modular mode. +* creates a `main` folder in the solution model and moves the main application into it. +* creates a `modules` folder for reusable module solutions. +* lets you add additional modules during solution creation and optionally install them into the main application immediately. + +## Typical Layout + +In ABP Studio's *Solution Explorer*, the solution is organized around these areas: + +* `main`: the main application and its host-side assets. +* `modules`: one module solution per business capability. +* `etc`: shared infrastructure, run profiles, and deployment files. + +Additional modules are generated under the solution's `modules/` directory, while the main application remains the single deployable host. + +## How Modules Fit In + +The module solutions created for a modular monolith use the same reusable module concepts documented in the [Application Module Template](../application-module/index.md) page. + +Use this template when you want: + +* clear module boundaries without a distributed deployment model. +* separate module solutions for teams or business domains. +* a monolith today, with a cleaner path toward microservices later. + +## See Also + +* [Solution Template Selection Guide](../guide.md) +* [Application Module Template](../application-module/index.md) +* [Modular Monolith Application Development Tutorial](../../tutorials/modular-crm/index.md)