diff --git a/Directory.Packages.props b/Directory.Packages.props index 8cd134d1d8..251b2cf00d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -17,10 +17,10 @@ - - - - + + + + diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index 9c414e2f52..e48a677ee7 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -683,6 +683,8 @@ "DiscountRate": "Discount Rate", "Menu:RedisManagement": "Redis Management", "RedisManagement": "Redis Management", - "Permission:RedisManagement": "Redis Management" + "Permission:RedisManagement": "Redis Management", + "UserCleanUp": "User Clean Up", + "Permission:UserCleanUp": "User Clean Up" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json index 3fba33369c..05e4959514 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json @@ -1868,6 +1868,10 @@ "GenerateQuote" : "Generate Quote" , "GeneratePriceQuote": "Generate a Price Quote", "Qa:QuestionPageTitle": "Support", - "SelectedTrainingName" : "Trainings" + "SelectedTrainingName" : "Trainings", + "RcStableDifference": "What is the difference between the RC version and the stable version of ABP?", + "RcStableDifferenceExplanation1": "The RC (Release Candidate) version is a pre-release version that allows early access to upcoming features and updates in the ABP project. It is primarily intended for testing purposes and for developers who want to prepare for the upcoming stable release. While it undergoes internal testing, it may still contain unresolved issues and it is not recommended for use in the production environment.", + "RcStableDifferenceExplanation2": "The Stable version is tested and officially supported for production use. It ensures reliability and compatibility.", + "RcStableDifferenceExplanation3": "Use the RC version for testing and early adoption but use the Stable version for production deployment." } } diff --git a/docs/en/cli/index.md b/docs/en/cli/index.md index 645c12a715..e9b4e5c42a 100644 --- a/docs/en/cli/index.md +++ b/docs/en/cli/index.md @@ -883,18 +883,13 @@ abp translate -c zh-Hans --online --deepl-auth-key ### login -Some features of the CLI requires to be logged in to ABP Platform. To login with your username write: +Some features of the CLI requires to be logged in to ABP Platform. The login command supports the following usage options: ```bash -abp login # Allows you to enter your password hidden -abp login -p # Specify the password as a parameter (password is visible) -abp login --organization # If you have multiple organizations, you need set your active organization -abp login -p -o # You can enter both your password and organization in the same command -abp login --device # Use device login flow +abp login # Opens a default browser to log in to ABP Platform via abp.io +abp login --device # Use device login flow ``` -> When using the -p parameter, be careful as your password will be visible. It's useful for CI/CD automation pipelines. - A new login with an already active session overwrites the previous session. ### login-info diff --git a/docs/en/samples/index.md b/docs/en/samples/index.md index 8c8733408d..6aea3d3fa5 100644 --- a/docs/en/samples/index.md +++ b/docs/en/samples/index.md @@ -1,6 +1,6 @@ # ABP Samples -Here, a list of official samples built with ABP. +This document provides a list of samples built with ABP. Each sample is briefly explained below, along with its live demo (if available), source code, and tutorial links (where applicable). ## Event Hub @@ -11,13 +11,13 @@ A reference application built with ABP. It implements the Domain Driven Design w ## eShopOnAbp +> ⚠️ **Important Notice** +> This project, "eShopOnAbp," is outdated. It served as a reference project for microservice architecture using the ABP Framework, but we now recommend using the [ABP Microservice Solution Template](https://abp.io/docs/latest/solution-templates/microservice) for new projects. + Reference microservice solution built with ABP and .NET. * [Source code](https://github.com/abpframework/eShopOnAbp) -> ⚠️ **Important Notice** -> This project, "eShopOnAbp," is outdated. It served as a reference project for microservice architecture using the ABP Framework, but we now recommend using the [ABP Microservice Solution Template](https://abp.io/docs/latest/solution-templates/microservice) for new projects. - ## CMS Kit Demo A minimal example website built with the [CMS Kit module](../modules/cms-kit/index.md). @@ -34,92 +34,48 @@ A middle-size CRM application built with ABP. ## Book Store -A simple CRUD application to show basic principles of developing an application with ABP. The same sample was implemented with different technologies and different modules. - -### With Open Source Modules - -The following samples uses only the open source (free) modules. +A simple CRUD application to show basic principles of developing an application with ABP. The same sample was implemented with different technologies and different modules: * **Book Store: Razor Pages UI & Entity Framework Core** * [Tutorial](../tutorials/book-store/part-01.md?UI=MVC&DB=EF) * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) + * [Download source code (with PRO modules) *](https://abp.io/Account/Login?returnUrl=/api/download/samples/bookstore-mvc-ef) * **Book Store: Blazor UI & Entity Framework Core** * [Tutorial](../tutorials/book-store/part-01.md?UI=Blazor&DB=EF) * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) + * [Download source code (with PRO modules) *](https://abp.io/Account/Login?returnUrl=/api/download/samples/bookstore-blazor-efcore) * **Book Store: Angular UI & MongoDB** * [Tutorial](../tutorials/book-store/part-01.md?UI=NG&DB=Mongo) * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) + * [Download source code (with PRO modules) *](https://abp.io/Account/Login?returnUrl=/api/download/samples/bookstore-angular-mongodb) * **Book Store: Modular application (Razor Pages UI & EF Core)** * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Modular) -### With Pro Modules +If you want to create the BookStore application and generate CRUD pages automatically with ABP Suite, please refer to the [Book Store Application (with ABP Suite) tutorial](../tutorials/book-store-with-abp-suite/part-01.md). Also, you can follow the [Mobile Application Development Tutorials](../tutorials/mobile/index.md), if you want to implement the CRUD operations for [MAUI](../tutorials/mobile/maui/index.md) & [React Native](../tutorials/mobile/react-native/index.md) mobile applications. -The following samples uses the pro modules. +> **Note:** _Downloading source codes (with PRO modules) \*_ require an active [ABP License](https://abp.io/pricing). -- **Book Store: Razor Pages (MVC) UI & Entity Framework Core** - - [Tutorial](../tutorials/book-store/part-01.md?UI=MVC&DB=EF) - - [Download the source code](https://abp.io/Account/Login?returnUrl=/api/download/samples/bookstore-mvc-ef) -- **Book Store: Blazor UI & Entity Framework Core** - - [Tutorial](../tutorials/book-store/part-01.md?UI=Blazor&DB=EF) - - [Download the source code](https://abp.io/Account/Login?returnUrl=/api/download/samples/bookstore-blazor-efcore) -- **Book Store: Angular UI & MongoDB** - - [Tutorial](../tutorials/book-store/part-01.md?UI=NG&DB=Mongo) - - [Download the source code](https://abp.io/Account/Login?returnUrl=/api/download/samples/bookstore-angular-mongodb) +## ModularCRM + +A modular monolith application that demonstrates how to create, compose, and communicate between application modules to build a modular web application: + +* **ModularCRM: Razor Pages UI & Entity Framework Core** + * [Tutorial](../tutorials/modular-crm/part-01.md?UI=MVC&DB=EF) + * [Source code](https://github.com/abpframework/abp-samples/tree/master/ModularCrm) + +## CloudCrm + +> This tutorial & sample application is suitable for those who have an [ABP Business or a higher license](https://abp.io/pricing). + +A microservice solution that shows how to start a new microservice solution, create services and communicate between these services. It's a reference tutorial to learn to use these services from a web application through an API gateway and automatically generate CRUD pages using the ABP Suite tool: + +* **CloudCRM: Razor Pages UI & Entity Framework Core** + * [Tutorial](../tutorials/microservice/part-01.md?UI=MVC&DB=EF) + * [Download source code](https://abp.io/api/download/samples/cloud-crm-mvc-ef) ## Other Samples -* **Event Organizer**: A sample application to create events (meetups) and allow others to register the events. Developed using EF Core and Blazor UI. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/EventOrganizer) - * [Article](https://abp.io/community/articles/creating-an-event-organizer-application-with-the-blazor-ui-wbe0sf2z) -* **Entity Framework Migrations**: A solution to demonstrate how to split your application into multiple databases each database contains different modules. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo) - * [EF Core database migrations document](../framework/data/entity-framework-core/migrations.md) -* **SignalR Demo**: A simple chat application that allows to send and receive messages among authenticated users. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/SignalRDemo) - * [SignalR Integration document](../framework/real-time/signalr.md) -* **Real Time Messaging In A Distributed Architecture** (using SingalR & RabbitMQ) - * [Source code](https://github.com/abpframework/abp-samples/tree/master/SignalRTieredDemo) - * [Article](https://abp.io/community/articles/real-time-messaging-in-a-distributed-architecture-using-abp-framework-singalr-rabbitmq-daf47e17) -* **Dashboard Demo**: A simple application to show how to use the widget system for the ASP.NET Core MVC UI. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo) - * [Widget documentation](../framework/ui/mvc-razor-pages/widgets.md) -* **RabbitMQ Event Bus Demo**: A solution consists of two applications communicating to each other via distributed events with RabbitMQ integration. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/RabbitMqEventBus) - * [Distributed event bus document](../framework/infrastructure/event-bus/distributed) - * [RabbitMQ distributed event bus integration document](../framework/infrastructure/event-bus/distributed/rabbitmq.md) -* **Text Templates Demo**: Shows different use cases of the text templating system. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) - * [Text templating documentation](../framework/infrastructure/text-templating) -* **Stored Procedure Demo**: Demonstrates how to use stored procedures, database views and functions with best practices. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo) -* **Passwordless Authentication**: Shows how to add a custom token provider to authenticate a user with a link, instead of entering a password. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication) - * [Article](https://abp.io/community/articles/implementing-passwordless-authentication-with-asp.net-core-identity-c25l8koj) -* **Authentication Customization**: A solution to show how to customize the authentication for ASP.NET Core MVC / Razor Pages applications. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization) - * Related articles: - * [Azure Active Directory Authentication](https://abp.io/community/articles/how-to-use-the-azure-active-directory-authentication-for-mvc-razor-page-applications-4603b9cf) - * [Customize the Login Page](https://abp.io/community/articles/how-to-customize-the-login-page-for-mvc-razor-page-applications-9a40f3cd) - * [Customize the SignIn Manager](https://abp.io/community/articles/how-to-customize-the-signin-manager-3e858753) -* **GRPC Demo**: Shows how to add a gRPC service to an ABP based web application and consume it from a console application. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo) -* **Telerik Blazor Integration**: Shows how to install and use Telerik Blazor components with ABP. - * [Article](https://abp.io/community/articles/how-to-integrate-the-telerik-blazor-components-to-the-abp-blazor-ui-q8g31abb) -* **Angular Material Integration**: Implemented the web application tutorial using the Angular Material library. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/AcmeBookStoreAngularMaterial) - * [Article](https://abp.io/community/articles/using-angular-material-components-with-the-abp-framework-af8ft6t9) -* **DevExtreme Angular Component Integration**: How to install and use DevExtreme components in the ABP Angular UI. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/DevExtreme-Angular) - * [Article](https://abp.io/community/articles/using-devextreme-angular-components-with-the-abp-framework-x5nyvj3i) -* **DevExtreme MVC / Razor Pages Component Integration**: How to install and use DevExtreme components in the ABP MVC / Razor Pages UI. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/DevExtreme-Mvc) - * [Article](https://abp.io/community/articles/using-devextreme-components-with-the-abp-framework-zb8z7yqv) -* **Syncfusion Blazor Integration**: Shows how to install and integrate Syncfusion UI the ABP Blazor UI. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/SyncfusionSample) - * [Article](https://abp.io/community/articles/using-syncfusion-components-with-the-abp-framework-5ccvi8kc) -* **Empty ASP.NET Core Application**: The most basic ASP.NET Core application with ABP installed. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication) - * [Documentation](../get-started/empty-aspnet-core-application.md) -* **Using Elsa Workflow with ABP**: Shows how to use the Elsa Core workflow library within an ABP-based application. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/ElsaDemo) - * [Article](https://abp.io/community/articles/using-elsa-workflow-with-the-abp-framework-773siqi9) +ABP Platform provides many sample applications demonstrating various use cases and integrations. You can: + +* Browse all sample applications in the [abp-samples repository](https://github.com/abpframework/abp-samples). +* Read detailed articles and tutorials in the [ABP Community](https://abp.io/community), which are shared by ABP Community & Contributors. \ No newline at end of file diff --git a/docs/en/solution-templates/layered-web-application/deployment/deployment-docker-compose.md b/docs/en/solution-templates/layered-web-application/deployment/deployment-docker-compose.md index a9e106221b..fe2d226d53 100644 --- a/docs/en/solution-templates/layered-web-application/deployment/deployment-docker-compose.md +++ b/docs/en/solution-templates/layered-web-application/deployment/deployment-docker-compose.md @@ -15,7 +15,7 @@ This guide will guide you through how to build docker images for your applicatio ## Building Docker Images -Each application contains a dockerfile called `Dockerfile.local` for building the docker image. As the naming implies, these Dockerfiles are not multi-stage Dockerfiles and require the project to be built in `Release` mode to create the image. Currently, if you are building your images using CI & CD pipeline, you either need to include the SDK to your pipeline before building the images or add your own [multi-stage dockerfiles](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/docker/building-net-docker-images?view=aspnetcore-7.0). +Each application contains a dockerfile called `Dockerfile.local` for building the docker image. As the naming implies, these Dockerfiles are not multi-stage Dockerfiles and require the project to be built in `Release` mode to create the image. Currently, if you are building your images using CI & CD pipeline, you either need to include the SDK to your pipeline before building the images or add your own [multi-stage dockerfiles](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/docker/building-net-docker-images?view=aspnetcore-9.0). Since they are not multi-staged Dockerfiles, if you want to build the images individually, you can navigate to the related to-be-hosted application folder and run the following command: @@ -31,7 +31,7 @@ docker build -f Dockerfile.local -t mycompanyname/myappname:version . To manually build your application image. -To ease the process, application templates provide a build script to build all the images with a single script under `etc/build` folder named `build-images-locally.ps1`. +To ease the process, application templates provide a build script to build all the images with a single script under `etc/docker-compose` folder named `build-images-locally.ps1`. Based on your application name, UI and type, a build image script will be generated. {{ if UI == "MVC"}} @@ -204,8 +204,8 @@ DbMigrator is a console application that is used to migrate the database of your `Dockerfile.local` is provided under this project as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 +COPY bin/Release/net9.0/publish/ app/ WORKDIR /app ENTRYPOINT ["dotnet", "BookStore.DbMigrator.dll"] ``` @@ -226,7 +226,7 @@ docker build -f Dockerfile.local -t acme/bookstore-db-migrator:latest . #Builds In the **WebModule** under authentication configuration, there is an extra configuration for containerized environment support: ```csharp -if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) +if (Convert.ToBoolean(configuration["AuthServer:IsOnK8s"])) { context.Services.Configure("oidc", options => { @@ -268,13 +268,13 @@ if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) This is used when the **AuthServer is running on docker containers(or pods)** to configure the redirection URLs for the internal network and the web. The application must be redirected to real DNS (localhost in this case) when the `/authorize` and `/logout` requests over the browser but handle the token validation inside the isolated network without going out to the internet. `"AuthServer:MetaAddress"` appsetting should indicate the container/pod service name while the `AuthServer:Authority` should be pointing to real DNS for the browser to redirect. -The `appsettings.json` file does not contain `AuthServer:IsContainerizedOnLocalhost` and `AuthServer:MetaAddress` settings since they are used for orchestrated deployment scenarios, you can see these settings are overridden by the `docker-compose.yml` file. +The `appsettings.json` file does not contain `AuthServer:IsOnK8s` and `AuthServer:MetaAddress` settings since they are used for orchestrated deployment scenarios, you can see these settings are overridden by the `docker-compose.yml` file. `Dockerfile.local` is provided under this project as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 +COPY bin/Release/net9.0/publish/ app/ WORKDIR /app ENTRYPOINT ["dotnet", "Acme.BookStore.Web.dll"] ``` @@ -289,11 +289,11 @@ docker build -f Dockerfile.local -t acme/bookstore-web:latest . #Builds the imag ​ {{ end }} {{ if Tiered == "No" }}MVC/Razor Pages application is a server-side rendering application that contains both the OpenID-provider and the Http.Api endpoints within self; it will be a single application to deploy. `Dockerfile.local` is provided under this project as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +COPY bin/Release/net9.0/publish/ app/ WORKDIR /app -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build WORKDIR /src RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED @@ -318,36 +318,17 @@ if (!hostingEnvironment.IsDevelopment()) options.AddDevelopmentEncryptionAndSigningCertificate = false; }); - PreConfigure(builder => + PreConfigure(serverBuilder => { - builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.SetIssuer(new Uri(configuration["AuthServer:Authority"])); + serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!); + serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!)); }); } ``` This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization. -The `GetSigningCertificate` method is a private method located under the same **WebModule**: - -```csharp -private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration) -{ - var fileName = "authserver.pfx"; - var passPhrase = "2D7AA457-5D33-48D6-936F-C48E5EF468ED"; - var file = Path.Combine(hostingEnv.ContentRootPath, fileName); - - if (!File.Exists(file)) - { - throw new FileNotFoundException($"Signing Certificate couldn't found: {file}"); - } - - return new X509Certificate2(file, passPhrase); -} -``` - -> You can always create any self-signed certificate using any other tooling outside the Dockerfile. You need to remember to set them as **embedded resource** since the `GetSigningCertificate` method will be checking this file physically. +> You can always create any self-signed certificate using any other tooling outside the Dockerfile. You need to remember to set them as **embedded resource**. If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to the **Web** folder and run: @@ -369,7 +350,7 @@ docker build -f Dockerfile.local -t acme/bookstore-web:latest . #Builds the imag In the **BlazorModule** under authentication configuration, there is an extra configuration for containerized environment support: ```csharp -if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) +if (Convert.ToBoolean(configuration["AuthServer:IsOnK8s"])) { context.Services.Configure("oidc", options => { @@ -411,13 +392,13 @@ if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) This is used when the **AuthServer is running on docker containers(or pods)** to configure the redirection URLs for the internal network and the web. The application must be redirected to real DNS (localhost in this case) when the `/authorize` and `/logout` requests over the browser but handle the token validation inside the isolated network without going out to the internet. `"AuthServer:MetaAddress"` appsetting should indicate the container/pod service name while the `AuthServer:Authority` should be pointing to real DNS for the browser to redirect. -The `appsettings.json` file does not contain `AuthServer:IsContainerizedOnLocalhost` and `AuthServer:MetaAddress` settings since they are used for orchestrated deployment scenarios, you can see these settings are overridden by the `docker-compose.yml` file. +The `appsettings.json` file does not contain `AuthServer:IsOnK8s` and `AuthServer:MetaAddress` settings since they are used for orchestrated deployment scenarios, you can see these settings are overridden by the `docker-compose.yml` file. `Dockerfile.local` is provided under this project as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 +COPY bin/Release/net9.0/publish/ app/ WORKDIR /app ENTRYPOINT ["dotnet", "Acme.BookStore.Blazor.dll"] ``` @@ -432,11 +413,11 @@ docker build -f Dockerfile.local -t acme/bookstore-blazor:latest . #Builds the i ​ {{ end }} {{ if Tiered == "No" }}Blazor Server application is a server-side rendering application that contains both the OpenID-provider and the Http.Api endpoints within self; it will be a single application to deploy. `Dockerfile.local` is provided under this project as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +COPY bin/Release/net9.0/publish/ app/ WORKDIR /app -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build WORKDIR /src RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED @@ -461,36 +442,17 @@ if (!hostingEnvironment.IsDevelopment()) options.AddDevelopmentEncryptionAndSigningCertificate = false; }); - PreConfigure(builder => + PreConfigure(serverBuilder => { - builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.SetIssuer(new Uri(configuration["AuthServer:Authority"])); + serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!); + serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!)); }); } ``` This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization. -The `GetSigningCertificate` method is a private method located under the same **BlazorModule**: - -```csharp -private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration) -{ - var fileName = "authserver.pfx"; - var passPhrase = "2D7AA457-5D33-48D6-936F-C48E5EF468ED"; - var file = Path.Combine(hostingEnv.ContentRootPath, fileName); - - if (!File.Exists(file)) - { - throw new FileNotFoundException($"Signing Certificate couldn't found: {file}"); - } - - return new X509Certificate2(file, passPhrase); -} -``` - -> You can always create any self-signed certificate using any other tooling outside the dockerfile. You need to remember to set them as **embedded resource** since the `GetSigningCertificate` method will be checking this file physically. +> You can always create any self-signed certificate using any other tooling outside the Dockerfile. You need to remember to set them as **embedded resource**. If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to the **BlazorModule** folder and run: @@ -562,7 +524,7 @@ server { } ``` -This configuration allows returning the `dynamic-env.json` file as a static file, which ABP Angular application uses for environment variables in one of the first initial requests when rendering the page. **The `dynamic-env.json` file you need to override is located under `aspnet-core/etc/docker`** folder. +This configuration allows returning the `dynamic-env.json` file as a static file, which ABP Angular application uses for environment variables in one of the first initial requests when rendering the page. **The `dynamic-env.json` file you need to override is located under `aspnet-core/etc/docker-compose`** folder. ​ {{ if Tiered == "No" }} @@ -645,8 +607,8 @@ docker build -f Dockerfile.local -t acme/bookstore-angular:latest . #Builds the The Blazor application uses [nginx:alpine-slim](https://hub.docker.com/layers/library/nginx/alpine-slim/images/sha256-0f859db466fda2c52f62b48d0602fb26867d98edbd62c26ae21414b3dea8d8f4?context=explore) base image to host the blazor application. You can modify the base image based on your preference in the `Dockerfile.local` which provided under the Blazor folder of your solution as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS build -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS build +COPY bin/Release/net9.0/publish/ app/ FROM nginx:alpine-slim AS final WORKDIR /usr/share/nginx/html @@ -701,11 +663,11 @@ docker build -f Dockerfile.local -t acme/bookstore-blazor:latest . #Builds the i This is the backend application that contains the openid-provider functionality as well. The `dockerfile.local` is located under the `Http.Api.Host` project as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +COPY bin/Release/net9.0/publish/ app/ WORKDIR /app -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build WORKDIR /src RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED @@ -730,36 +692,17 @@ if (!hostingEnvironment.IsDevelopment()) options.AddDevelopmentEncryptionAndSigningCertificate = false; }); - PreConfigure(builder => + PreConfigure(serverBuilder => { - builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.SetIssuer(new Uri(configuration["AuthServer:Authority"])); + serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!); + serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!)); }); } ``` This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization. -The `GetSigningCertificate` method is a private method located under the same **HttpApiHostModule**: - -```csharp -private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration) -{ - var fileName = "authserver.pfx"; - var passPhrase = "2D7AA457-5D33-48D6-936F-C48E5EF468ED"; - var file = Path.Combine(hostingEnv.ContentRootPath, fileName); - - if (!File.Exists(file)) - { - throw new FileNotFoundException($"Signing Certificate couldn't found: {file}"); - } - - return new X509Certificate2(file, passPhrase); -} -``` - -> You can always create any self-signed certificate using any other tooling outside of the dockerfile. You need to keep in mind to set them as **embedded resource** since the `GetSigningCertificate` method will be checking this file physically. +> You can always create any self-signed certificate using any other tooling outside the Dockerfile. You need to remember to set them as **embedded resource**. If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to **Http.Api.Host** folder and run: @@ -777,11 +720,11 @@ docker build -f Dockerfile.local -t acme/bookstore-api:latest . #Builds the imag This is the backend application that contains the OpenID-provider functionality as well. The `dockerfile.local` is located under the `Http.Api.Host` project as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +COPY bin/Release/net9.0/publish/ app/ WORKDIR /app -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build WORKDIR /src RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED @@ -806,36 +749,17 @@ if (!hostingEnvironment.IsDevelopment()) options.AddDevelopmentEncryptionAndSigningCertificate = false; }); - PreConfigure(builder => + PreConfigure(serverBuilder => { - builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.SetIssuer(new Uri(configuration["AuthServer:Authority"])); + serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!); + serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!)); }); } ``` -This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different customization options. - -The `GetSigningCertificate` method is a private method located under the same **HttpApiHostModule**: - -```csharp -private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration) -{ - var fileName = "authserver.pfx"; - var passPhrase = "2D7AA457-5D33-48D6-936F-C48E5EF468ED"; - var file = Path.Combine(hostingEnv.ContentRootPath, fileName); - - if (!File.Exists(file)) - { - throw new FileNotFoundException($"Signing Certificate couldn't found: {file}"); - } - - return new X509Certificate2(file, passPhrase); -} -``` +This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization. -> You can always create any self-signed certificate using any other tooling outside the dockerfile. You need to remember to set them as **embedded resource** since the `GetSigningCertificate` method will be checking this file physically. +> You can always create any self-signed certificate using any other tooling outside the Dockerfile. You need to remember to set them as **embedded resource**. If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to **Http.Api.Host** folder and run: @@ -855,11 +779,11 @@ docker build -f Dockerfile.local -t acme/bookstore-api:latest . #Builds the imag This is the openid-provider application, the authentication server, which should be individually hosted compared to non-tiered application templates. The `dockerfile.local` is located under the `AuthServer` project as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +COPY bin/Release/net9.0/publish/ app/ WORKDIR /app -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build WORKDIR /src RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED @@ -884,36 +808,17 @@ if (!hostingEnvironment.IsDevelopment()) options.AddDevelopmentEncryptionAndSigningCertificate = false; }); - PreConfigure(builder => + PreConfigure(serverBuilder => { - builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.SetIssuer(new Uri(configuration["AuthServer:Authority"])); + serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!); + serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!)); }); } ``` This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization. -The `GetSigningCertificate` method is a private method located under the same **AuthServerModule**: - -```csharp -private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration) -{ - var fileName = "authserver.pfx"; - var passPhrase = "2D7AA457-5D33-48D6-936F-C48E5EF468ED"; - var file = Path.Combine(hostingEnv.ContentRootPath, fileName); - - if (!File.Exists(file)) - { - throw new FileNotFoundException($"Signing Certificate couldn't found: {file}"); - } - - return new X509Certificate2(file, passPhrase); -} -``` - -> You can always create any self-signed certificate using any other tooling outside the dockerfile. You need to remember to set them as **embedded resource** since the `GetSigningCertificate` method will be checking this file physically. +> You can always create any self-signed certificate using any other tooling outside the Dockerfile. You need to remember to set them as **embedded resource**. If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to the **AuthServer** folder and run: @@ -927,8 +832,8 @@ docker build -f Dockerfile.local -t acme/bookstore-authserver:latest . #Builds t This is the backend application that exposes the endpoints and swagger UI. It is not a multi-stage dockerfile; hence you need to have already built this application in **Release mode** to use this dockerfile. The `dockerfile.local` is located under the `Http.Api.Host` project as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 +COPY bin/Release/net9.0/publish/ app/ WORKDIR /app ENTRYPOINT ["dotnet", "Acme.BookStore.HttpApi.Host.dll"] ``` @@ -944,7 +849,7 @@ docker build -f Dockerfile.local -t acme/bookstore-api:latest . #Builds the imag ## Running Docker-Compose on Localhost -Under the `etc/docker` folder, you can find the `docker-compose.yml` to run your application. To ease the running process, the template provides `run-docker.ps1` (and `run-docker.sh`) scripts that handle the HTTPS certificate creation, which is used in environment variables; +Under the `etc/docker-compose` folder, you can find the `docker-compose.yml` to run your application. To ease the running process, the template provides `run-docker.ps1` (and `run-docker.sh`) scripts that handle the HTTPS certificate creation, which is used in environment variables; ```powershell $currentFolder = $PSScriptRoot @@ -1203,7 +1108,7 @@ This is the angular application we deploy on http://localhost:4200 by default us > Don't forget to rebuild the `acme/bookstore-angular:latest` image after updating the `nginx.conf` file. -The bookstore-angular service mounts the `etc/docker/dynamic-env.json` file to change the existing dynamic-env.json file, which is copied during image creation, to change the environment variables on deployment time instead of re-creating the docker image after each environmental variable change. **Do not forget to override the `dynamic-env.json` located under the `aspnet-core/etc/docker`** folder. +The bookstore-angular service mounts the `etc/docker-compose/dynamic-env.json` file to change the existing dynamic-env.json file, which is copied during image creation, to change the environment variables on deployment time instead of re-creating the docker image after each environmental variable change. **Do not forget to override the `dynamic-env.json` located under the `aspnet-core/etc/docker-compose`** folder. > If you are not using Docker with WSL, you may have problems with the volume mount permissions. You need to grant docker to be able to use the local file system. See this [SO answer](https://stackoverflow.com/a/20652410) for more information. @@ -1438,7 +1343,7 @@ bookstore-web: - Kestrel__Certificates__Default__Password=91f91912-5ab0-49df-8166-23377efaf3cc - App__SelfUrl=https://localhost:44353 - AuthServer__RequireHttpsMetadata=false {{ if Tiered == "Yes" }} - - AuthServer__IsContainerizedOnLocalhost=true + - AuthServer__IsOnK8s=true - AuthServer__Authority=https://localhost:44334/ - RemoteServices__Default__BaseUrl=http://bookstore-api - RemoteServices__AbpAccountPublic__BaseUrl=http://bookstore-authserver @@ -1464,7 +1369,7 @@ This is the MVC/Razor Page application docker service is using the `acme/booksto The MVC/Razor Page is a server-side rendering application that uses the **hybrid flow**. This flow uses **browser** to login/logout process to the OpenID-provider but issues the **access_token from the back-channel** (server-side). To achieve this functionality, the module class has extra `OpenIdConnectOptions` to override some of the events: ```csharp -if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) +if (Convert.ToBoolean(configuration["AuthServer:IsOnK8s"])) { context.Services.Configure("oidc", options => { @@ -1511,7 +1416,7 @@ if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) {{ if Tiered == "Yes" }} -- `AuthServer__IsContainerizedOnLocalhost` is the configuration to enable the **OpenIdConnectOptions** to provide a different endpoint for the MetaAddress of the OpenID-provider and intercepting the URLS for *authorization* and *logout* endpoints. +- `AuthServer__IsOnK8s` is the configuration to enable the **OpenIdConnectOptions** to provide a different endpoint for the MetaAddress of the OpenID-provider and intercepting the URLS for *authorization* and *logout* endpoints. - `AuthServer__MetaAddress` is the `.well-known/openid-configuration` endpoint for issuing access_token and internal token validation. It is the containerized `http://bookstore-authserver` by default. @@ -1691,7 +1596,7 @@ bookstore-blazor: - Kestrel__Certificates__Default__Password=91f91912-5ab0-49df-8166-23377efaf3cc - App__SelfUrl=https://localhost:44314 - AuthServer__RequireHttpsMetadata=false {{ if Tiered == "Yes" }} - - AuthServer__IsContainerizedOnLocalhost=true + - AuthServer__IsOnK8s=true - AuthServer__Authority=https://localhost:44334/ - AuthServer__MetaAddress=http://bookstore-authserver - RemoteServices__Default__BaseUrl=http://bookstore-api @@ -1718,7 +1623,7 @@ This is the Blazor Server application Docker service is using the `acme/bookstor The Blazor Server is a server-side rendering application that uses the **hybrid flow**. This flow uses **browser** to login/logout process to the OpenID-provider but issues the **access_token from the back-channel** (server-side). To achieve this functionality, the module class has extra `OpenIdConnectOptions` to override some of the events: ```csharp -if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) +if (Convert.ToBoolean(configuration["AuthServer:IsOnK8s"])) { context.Services.Configure("oidc", options => { @@ -1765,7 +1670,7 @@ if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) {{ if Tiered == "Yes" }} -- `AuthServer__IsContainerizedOnLocalhost` is the configuration to enable the **OpenIdConnectOptions** to provide a different endpoint for the MetaAddress of the OpenID-provider and intercept the URLS for *authorization* and *logout* endpoints. +- `AuthServer__IsOnK8s` is the configuration to enable the **OpenIdConnectOptions** to provide a different endpoint for the MetaAddress of the OpenID-provider and intercept the URLS for *authorization* and *logout* endpoints. - `AuthServer__MetaAddress` is the `.well-known/openid-configuration` endpoint for issuing the access_token and internal token validation. It is the containerized `http://bookstore-authserver` by default. diff --git a/docs/en/solution-templates/layered-web-application/solution-structure.md b/docs/en/solution-templates/layered-web-application/solution-structure.md index 680df73a59..3afd1b1274 100644 --- a/docs/en/solution-templates/layered-web-application/solution-structure.md +++ b/docs/en/solution-templates/layered-web-application/solution-structure.md @@ -214,3 +214,7 @@ This project is an application that hosts the API of the solution. It has its ow Just like the default structure, this project contains the User Interface (UI) of the application. It contains razor pages, JavaScript files, style files, images and so on... This project contains an `appsettings.json` file, but this time it does not have a connection string because it never connects to the database. Instead, it mainly contains the endpoint of the remote API server and the authentication server. + +### Docker Compose + +The **docker-compose** configuration files in the `etc/docker-compose` folder is configured to run the solution with Docker. See [Docker Deployment using Docker Compose](deployment/deployment-docker-compose.md) for more information. \ No newline at end of file diff --git a/docs/en/studio/monitoring-applications.md b/docs/en/studio/monitoring-applications.md index 645df15418..6d0c5523a3 100644 --- a/docs/en/studio/monitoring-applications.md +++ b/docs/en/studio/monitoring-applications.md @@ -48,7 +48,7 @@ public override void ConfigureServices(ServiceConfigurationContext context) In this tab, you can view comprehensive overall information. You have the option to search by application name and filter by application state. To reset all filters, use the *Clear Filters* button. When you apply a filter header informations gonna refresh by filtered applications. -- `Apps Running`: The number of applications running. It includes CLI and C# applications. In the example two C# microservice applications and one CLI application is running. +- `Apps Running`: The number of applications running. It includes only C# applications. In the example, nine C# microservice applications are running. - `Requests`: The number of HTTP requests received by all C# applications. - `Events`: The number of [Distributed Event](../framework/infrastructure/event-bus/distributed) sent or received by all C# applications. - `Exceptions`: The number of exceptions thrown by all C# applications. diff --git a/docs/en/studio/running-applications.md b/docs/en/studio/running-applications.md index 9e266ef7a9..5392250153 100644 --- a/docs/en/studio/running-applications.md +++ b/docs/en/studio/running-applications.md @@ -230,3 +230,11 @@ CLI applications uses the [powershell](https://learn.microsoft.com/en-us/powersh - `Remove`: This option allows you to delete the selected application. > When CLI applications start chain icon won't be visible, because only C# applications can connect the ABP Studio. + +## Docker Compose + +You can manually run applications using [Docker Compose](https://docs.docker.com/compose/). This allows for easy setup and management of multi-container Docker applications. To get started, ensure you have Docker and Docker Compose installed on your machine. + +Refer to the [Deployment with Docker Compose](../solution-templates/layered-web-application/deployment/deployment-docker-compose.md) documentation for detailed instructions on how to configure and run your applications using `docker-compose`. + +> Note: The **Docker Compose** is not available in the ABP Studio interface. \ No newline at end of file diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser.png b/docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser.png index 1318691ae9..feb6627be7 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser.png and b/docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-order-execute.png b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-order-execute.png index b0f384f15d..6d4f5320ee 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-order-execute.png and b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-order-execute.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-execute.png b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-execute.png index e07804e37d..c85348933a 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-execute.png and b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-execute.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-try.png b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-try.png index 52a99ce8fa..8229656886 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-try.png and b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-create-product-try.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-in-browser.png b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-in-browser.png index 1b5fec9205..d412e0c509 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-in-browser.png and b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-in-browser.png differ diff --git a/docs/en/tutorials/modular-crm/part-03.md b/docs/en/tutorials/modular-crm/part-03.md index 3d8896d8a5..2dabfc54eb 100644 --- a/docs/en/tutorials/modular-crm/part-03.md +++ b/docs/en/tutorials/modular-crm/part-03.md @@ -387,7 +387,10 @@ Configure(options => options.ConventionalControllers.Create(typeof(ModularCrmModule).Assembly); //ADD THE FOLLOWING LINE: - options.ConventionalControllers.Create(typeof(ProductsApplicationModule).Assembly); + options.ConventionalControllers.Create(typeof(ProductsApplicationModule).Assembly, settings => + { + settings.RootPath = "products"; + }); }); ```` @@ -409,7 +412,7 @@ Once you see the user interface of the web application, type `/swagger` at the e ![abp-studio-swagger-ui-in-browser](images/abp-studio-swagger-ui-in-browser.png) -Expand the `/api/app/product` API and click the *Try it out* button as shown in the following figure: +Expand the `/api/products/product` API and click the *Try it out* button as shown in the following figure: ![abp-studio-swagger-ui-create-product-try](images/abp-studio-swagger-ui-create-product-try.png) diff --git a/docs/en/tutorials/modular-crm/part-05.md b/docs/en/tutorials/modular-crm/part-05.md index a489548b65..59aa56f2f4 100644 --- a/docs/en/tutorials/modular-crm/part-05.md +++ b/docs/en/tutorials/modular-crm/part-05.md @@ -345,7 +345,9 @@ public class OrderAppService : OrderingAppService, IOrderAppService } ```` -Open the `ModularCrmModule` class in the main application's solution (the `ModularCrm` solution), find the `ConfigureAutoApiControllers` method and add the following lines inside that method: +### Exposing Application Services as HTTP API Controllers + +After implementing the application service, now we need to create HTTP API endpoints for the ordering module. For that purpose, open the `ModularCrmModule` class in the main application's solution (the `ModularCrm` solution), find the `ConfigureAutoApiControllers` method and add the following lines inside that method: ````csharp private void ConfigureAutoApiControllers() @@ -353,10 +355,16 @@ private void ConfigureAutoApiControllers() Configure(options => { options.ConventionalControllers.Create(typeof(ModularCrmModule).Assembly); - options.ConventionalControllers.Create(typeof(ProductsApplicationModule).Assembly); + options.ConventionalControllers.Create(typeof(ProductsApplicationModule).Assembly, settings => + { + settings.RootPath = "products"; + }); //ADD THE FOLLOWING LINE: - options.ConventionalControllers.Create(typeof(OrderingModule).Assembly); + options.ConventionalControllers.Create(typeof(OrderingModule).Assembly, settings => + { + settings.RootPath = "orders"; + }); }); } ```` @@ -373,7 +381,7 @@ Once you see the user interface of the web application, type `/swagger` at the e ![abp-studio-ordering-swagger-ui-in-browser](images/abp-studio-ordering-swagger-ui-in-browser.png) -Expand the `/api/app/order` API and click the *Try it out* button. Then, create a few orders by filling in the request body and clicking the *Execute* button: +Expand the `/api/orders/order` API and click the *Try it out* button. Then, create a few orders by filling in the request body and clicking the *Execute* button: ![abp-studio-swagger-ui-create-order-execute](images/abp-studio-swagger-ui-create-order-execute.png) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs index 92da7dce50..b55c9195f0 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs @@ -37,9 +37,9 @@ public class ChangeThemeStep : ProjectBuildPipelineStep { var defaultThemeName = context.BuildArgs.TemplateName is AppTemplate.TemplateName or AppNoLayersTemplate.TemplateName ? LeptonXLite : LeptonX; - + new RemoveFilesStep($"/Themes/{defaultThemeName}").Execute(context); - + ChangeThemeToBasicForMvcProjects(context, defaultThemeName); ChangeThemeToBasicForBlazorProjects(context, defaultThemeName); ChangeThemeToBasicForBlazorServerProjects(context, defaultThemeName); @@ -622,6 +622,11 @@ public class ChangeThemeStep : ProjectBuildPipelineStep return; } + if (!file.Content.Contains("ThemeModule")) + { + return; + } + file.NormalizeLineEndings(); var lines = file.GetLines().ToList(); @@ -630,7 +635,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep { lines.AddFirst(@namespace); } - + file.SetLines(lines); } @@ -641,7 +646,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep ".Web", ".AuthServer", ".Web.Public", ".Web.Public.Host", "" //for app-nolayers-mvc }; - + if(!context.Symbols.Contains("tiered")) { projectNames.Add(".HttpApi.Host"); @@ -716,7 +721,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep context, $"_Host.cshtml", $"{defaultThemeName}Theme.Components", - "BasicTheme.Themes.Basic" + "BasicTheme.Themes.Basic" ); ReplaceAllKeywords( diff --git a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs index 13de378855..32b07f1b82 100644 --- a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs @@ -102,6 +102,8 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen public async override Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) { + await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, outgoingEvent.GetCorrelationId(), outgoingEvent.Id); + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { await TriggerDistributedEventSentAsync(new DistributedEventSent() @@ -111,8 +113,6 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen EventData = outgoingEvent.EventData }); } - - await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, outgoingEvent.GetCorrelationId(), outgoingEvent.Id); } public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) @@ -141,7 +141,12 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen throw new AbpException( "The message is too large to fit in the batch. Set AbpEventBusBoxesOptions.OutboxWaitingEventMaxCount to reduce the number"); } + } + await publisher.SendMessagesAsync(messageBatch); + + foreach (var outgoingEvent in outgoingEventArray) + { using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { await TriggerDistributedEventSentAsync(new DistributedEventSent() @@ -152,8 +157,6 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen }); } } - - await publisher.SendMessagesAsync(messageBatch); } public async override Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig) diff --git a/framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs index ce6dee2cfc..2b0d2c7d0d 100644 --- a/framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs @@ -153,6 +153,8 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend public async override Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) { + await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, GetEventType(outgoingEvent.EventName)), outgoingEvent.Id, outgoingEvent.GetCorrelationId()); + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { await TriggerDistributedEventSentAsync(new DistributedEventSent() @@ -162,8 +164,6 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend EventData = outgoingEvent.EventData }); } - - await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, GetEventType(outgoingEvent.EventName)), outgoingEvent.Id, outgoingEvent.GetCorrelationId()); } public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) @@ -172,6 +172,8 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend foreach (var outgoingEvent in outgoingEventArray) { + await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, GetEventType(outgoingEvent.EventName)), outgoingEvent.Id, outgoingEvent.GetCorrelationId()); + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { await TriggerDistributedEventSentAsync(new DistributedEventSent() @@ -181,8 +183,6 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend EventData = outgoingEvent.EventData }); } - - await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, GetEventType(outgoingEvent.EventName)), outgoingEvent.Id, outgoingEvent.GetCorrelationId()); } } diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index a777ff7da2..4bdf6a2782 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -197,16 +197,6 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) { - using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) - { - await TriggerDistributedEventSentAsync(new DistributedEventSent() - { - Source = DistributedEventSource.Outbox, - EventName = outgoingEvent.EventName, - EventData = outgoingEvent.EventData - }); - } - var headers = new Headers { { "messageId", System.Text.Encoding.UTF8.GetBytes(outgoingEvent.Id.ToString("N")) } @@ -222,6 +212,16 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen outgoingEvent.EventData, headers ); + + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) + { + await TriggerDistributedEventSentAsync(new DistributedEventSent() + { + Source = DistributedEventSource.Outbox, + EventName = outgoingEvent.EventName, + EventData = outgoingEvent.EventData + }); + } } public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) @@ -242,6 +242,15 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen headers.Add(EventBusConsts.CorrelationIdHeaderName, System.Text.Encoding.UTF8.GetBytes(outgoingEvent.GetCorrelationId()!)); } + producer.Produce( + AbpKafkaEventBusOptions.TopicName, + new Message + { + Key = outgoingEvent.EventName, + Value = outgoingEvent.EventData, + Headers = headers + }); + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { await TriggerDistributedEventSentAsync(new DistributedEventSent() @@ -251,15 +260,6 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen EventData = outgoingEvent.EventData }); } - - producer.Produce( - AbpKafkaEventBusOptions.TopicName, - new Message - { - Key = outgoingEvent.EventName, - Value = outgoingEvent.EventData, - Headers = headers - }); } } diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index c84aa6d9e9..b0f85afb56 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -207,6 +207,8 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) { + await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, eventId: outgoingEvent.Id, correlationId: outgoingEvent.GetCorrelationId()); + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { await TriggerDistributedEventSentAsync(new DistributedEventSent() @@ -216,8 +218,6 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis EventData = outgoingEvent.EventData }); } - - await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, eventId: outgoingEvent.Id, correlationId: outgoingEvent.GetCorrelationId()); } public async override Task PublishManyFromOutboxAsync( @@ -231,6 +231,13 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis foreach (var outgoingEvent in outgoingEventArray) { + await PublishAsync( + channel, + outgoingEvent.EventName, + outgoingEvent.EventData, + eventId: outgoingEvent.Id, + correlationId: outgoingEvent.GetCorrelationId()); + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { await TriggerDistributedEventSentAsync(new DistributedEventSent() @@ -240,13 +247,6 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis EventData = outgoingEvent.EventData }); } - - await PublishAsync( - channel, - outgoingEvent.EventName, - outgoingEvent.EventData, - eventId: outgoingEvent.Id, - correlationId: outgoingEvent.GetCorrelationId()); } channel.WaitForConfirmsOrDie(); diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs index 4b4dd3eef7..9e8398a495 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs @@ -253,6 +253,14 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen var eventType = EventTypes.GetOrDefault(outgoingEvent.EventName)!; var eventData = Serializer.Deserialize(outgoingEvent.EventData, eventType); + var headers = new Dictionary(); + if (outgoingEvent.GetCorrelationId() != null) + { + headers.Add(EventBusConsts.CorrelationIdHeaderName, outgoingEvent.GetCorrelationId()!); + } + + await PublishAsync(eventType, eventData, eventId: outgoingEvent.Id, headersArguments: headers); + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { await TriggerDistributedEventSentAsync(new DistributedEventSent() { @@ -261,14 +269,6 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen EventData = outgoingEvent.EventData }); } - - var headers = new Dictionary(); - if (outgoingEvent.GetCorrelationId() != null) - { - headers.Add(EventBusConsts.CorrelationIdHeaderName, outgoingEvent.GetCorrelationId()!); - } - - await PublishAsync(eventType, eventData, eventId: outgoingEvent.Id, headersArguments: headers); } public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) @@ -279,6 +279,8 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen { foreach (var outgoingEvent in outgoingEventArray) { + await PublishFromOutboxAsync(outgoingEvent, outboxConfig); + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { await TriggerDistributedEventSentAsync(new DistributedEventSent() @@ -288,8 +290,6 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen EventData = outgoingEvent.EventData }); } - - await PublishFromOutboxAsync(outgoingEvent, outboxConfig); } await scope.CompleteAsync(); diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs index 96a11f928e..ad69ed124b 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -85,14 +85,14 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB } } + await PublishToEventBusAsync(eventType, eventData); + await TriggerDistributedEventSentAsync(new DistributedEventSent() { Source = DistributedEventSource.Direct, EventName = EventNameAttribute.GetNameOrDefault(eventType), EventData = eventData }); - - await PublishToEventBusAsync(eventType, eventData); } public abstract Task PublishFromOutboxAsync( diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus.cs index 90c118eca1..d653c63bb7 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus.cs @@ -57,89 +57,116 @@ public class LocalDistributedEventBus : IDistributedEventBus, ISingletonDependen return Subscribe(typeof(TEvent), handler); } - public virtual IDisposable Subscribe(Func action) where TEvent : class + public IDisposable Subscribe(Func action) where TEvent : class { return _localEventBus.Subscribe(action); } - public virtual IDisposable Subscribe(ILocalEventHandler handler) where TEvent : class + public IDisposable Subscribe(ILocalEventHandler handler) where TEvent : class { return _localEventBus.Subscribe(handler); } - public virtual IDisposable Subscribe() where TEvent : class where THandler : IEventHandler, new() + public IDisposable Subscribe() where TEvent : class where THandler : IEventHandler, new() { return _localEventBus.Subscribe(); } - public virtual IDisposable Subscribe(Type eventType, IEventHandler handler) + public IDisposable Subscribe(Type eventType, IEventHandler handler) { return _localEventBus.Subscribe(eventType, handler); } - public virtual IDisposable Subscribe(IEventHandlerFactory factory) where TEvent : class + public IDisposable Subscribe(IEventHandlerFactory factory) where TEvent : class { return _localEventBus.Subscribe(factory); } - public virtual IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) + public IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) { return _localEventBus.Subscribe(eventType, factory); } - public virtual void Unsubscribe(Func action) where TEvent : class + public void Unsubscribe(Func action) where TEvent : class { _localEventBus.Unsubscribe(action); } - public virtual void Unsubscribe(ILocalEventHandler handler) where TEvent : class + public void Unsubscribe(ILocalEventHandler handler) where TEvent : class { _localEventBus.Unsubscribe(handler); } - public virtual void Unsubscribe(Type eventType, IEventHandler handler) + public void Unsubscribe(Type eventType, IEventHandler handler) { _localEventBus.Unsubscribe(eventType, handler); } - public virtual void Unsubscribe(IEventHandlerFactory factory) where TEvent : class + public void Unsubscribe(IEventHandlerFactory factory) where TEvent : class { _localEventBus.Unsubscribe(factory); } - public virtual void Unsubscribe(Type eventType, IEventHandlerFactory factory) + public void Unsubscribe(Type eventType, IEventHandlerFactory factory) { _localEventBus.Unsubscribe(eventType, factory); } - public virtual void UnsubscribeAll() where TEvent : class + public void UnsubscribeAll() where TEvent : class { _localEventBus.UnsubscribeAll(); } - public virtual void UnsubscribeAll(Type eventType) + public void UnsubscribeAll(Type eventType) { _localEventBus.UnsubscribeAll(eventType); } - public virtual Task PublishAsync(TEvent eventData, bool onUnitOfWorkComplete = true) + public async Task PublishAsync(TEvent eventData, bool onUnitOfWorkComplete = true) where TEvent : class { - return _localEventBus.PublishAsync(eventData, onUnitOfWorkComplete); + await PublishDistributedEventSentReceivedAsync(typeof(TEvent), eventData, onUnitOfWorkComplete); + await _localEventBus.PublishAsync(eventData, onUnitOfWorkComplete); } - public virtual Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true) + public async Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true) { - return _localEventBus.PublishAsync(eventType, eventData, onUnitOfWorkComplete); + await PublishDistributedEventSentReceivedAsync(eventType, eventData, onUnitOfWorkComplete); + await _localEventBus.PublishAsync(eventType, eventData, onUnitOfWorkComplete); } - public virtual Task PublishAsync(TEvent eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true) where TEvent : class + public async Task PublishAsync(TEvent eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true) where TEvent : class { - return _localEventBus.PublishAsync(eventData, onUnitOfWorkComplete); + await PublishDistributedEventSentReceivedAsync(typeof(TEvent), eventData, onUnitOfWorkComplete); + await _localEventBus.PublishAsync(eventData, onUnitOfWorkComplete); } - public virtual Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true) + public async Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true) { - return _localEventBus.PublishAsync(eventType, eventData, onUnitOfWorkComplete); + await PublishDistributedEventSentReceivedAsync(eventType, eventData, onUnitOfWorkComplete); + await _localEventBus.PublishAsync(eventType, eventData, onUnitOfWorkComplete); } -} + + private async Task PublishDistributedEventSentReceivedAsync(Type eventType, object eventData, bool onUnitOfWorkComplete) + { + if (eventType != typeof(DistributedEventSent)) + { + await _localEventBus.PublishAsync(new DistributedEventSent + { + Source = DistributedEventSource.Direct, + EventName = EventNameAttribute.GetNameOrDefault(eventType), + EventData = eventData + }, onUnitOfWorkComplete); + } + + if (eventType != typeof(DistributedEventReceived)) + { + await _localEventBus.PublishAsync(new DistributedEventReceived + { + Source = DistributedEventSource.Direct, + EventName = EventNameAttribute.GetNameOrDefault(eventType), + EventData = eventData + }, onUnitOfWorkComplete); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs index eaeb02f002..d1733577e5 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Reflection; using Volo.Abp.Reflection; namespace Volo.Abp.Http.Modeling; @@ -46,7 +47,7 @@ public class TypeApiDescriptionModel else { typeModel.Properties = type - .GetProperties() + .GetProperties(BindingFlags.Instance | BindingFlags.Public) .Where(p => p.DeclaringType == type) .Select(PropertyApiDescriptionModel.Create) .ToArray(); diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/DistributedEventHandles.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/DistributedEventHandles.cs deleted file mode 100644 index eb1f1a52df..0000000000 --- a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/DistributedEventHandles.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Threading.Tasks; - -namespace Volo.Abp.EventBus.Distributed; - -public class DistributedEventHandles : ILocalEventHandler, ILocalEventHandler -{ - public static int SentCount { get; set; } - - public static int ReceivedCount { get; set; } - - public Task HandleEventAsync(DistributedEventSent eventData) - { - SentCount++; - return Task.CompletedTask; - } - - public Task HandleEventAsync(DistributedEventReceived eventData) - { - ReceivedCount++; - return Task.CompletedTask; - } -} diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs index 880e2810cd..88c515f8dc 100644 --- a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; +using Shouldly; using Volo.Abp.Domain.Entities.Events.Distributed; using Volo.Abp.EventBus.Local; using Volo.Abp.Uow; @@ -11,12 +10,6 @@ namespace Volo.Abp.EventBus.Distributed; public class LocalDistributedEventBus_Test : LocalDistributedEventBusTestBase { - protected override void AfterAddApplication(IServiceCollection services) - { - services.Replace(ServiceDescriptor.Singleton()); - base.AfterAddApplication(services); - } - [Fact] public async Task Should_Call_Handler_AndDispose() { @@ -76,60 +69,56 @@ public class LocalDistributedEventBus_Test : LocalDistributedEventBusTestBase public async Task DistributedEventSentAndReceived_Test() { var localEventBus = GetRequiredService(); - if (localEventBus is UnitTestLocalEventBus eventBus) - { - eventBus.OnEventHandleInvoking = async (eventType, eventData) => - { - await localEventBus.PublishAsync(new DistributedEventReceived() - { - Source = DistributedEventSource.Direct, - EventName = EventNameAttribute.GetNameOrDefault(eventType), - EventData = eventData - }, onUnitOfWorkComplete: false); - }; - - eventBus.OnPublishing = async (eventType, eventData) => - { - await localEventBus.PublishAsync(new DistributedEventSent() - { - Source = DistributedEventSource.Direct, - EventName = EventNameAttribute.GetNameOrDefault(eventType), - EventData = eventData - }, onUnitOfWorkComplete: false); - }; - } - GetRequiredService().Subscribe(); - GetRequiredService().Subscribe(); + localEventBus.Subscribe(); + localEventBus.Subscribe(); DistributedEventBus.Subscribe(); using (var uow = GetRequiredService().Begin()) { + MyEventDate.Order = string.Empty; await DistributedEventBus.PublishAsync(new MyEventDate(), onUnitOfWorkComplete: false); - Assert.Equal(1, DistributedEventHandles.SentCount); - Assert.Equal(1, DistributedEventHandles.ReceivedCount); + MyEventDate.Order.ShouldBe(nameof(DistributedEventSent) + nameof(DistributedEventReceived) + nameof(MyEventHandle)); + MyEventDate.Order = string.Empty; await DistributedEventBus.PublishAsync(new MyEventDate(), onUnitOfWorkComplete: true); + MyEventDate.Order.ShouldBe(string.Empty); await uow.CompleteAsync(); - Assert.Equal(2, DistributedEventHandles.SentCount); - Assert.Equal(2, DistributedEventHandles.ReceivedCount); + MyEventDate.Order.ShouldBe(nameof(DistributedEventSent) + nameof(DistributedEventReceived) + nameof(MyEventHandle)); } } class MyEventDate { - + public static string Order { get; set; } = string.Empty; } class MyEventHandle : IDistributedEventHandler { public Task HandleEventAsync(MyEventDate eventData) { + MyEventDate.Order += nameof(MyEventHandle); return Task.CompletedTask; } } + + class DistributedEventHandles : ILocalEventHandler, ILocalEventHandler + { + public Task HandleEventAsync(DistributedEventSent eventData) + { + MyEventDate.Order += nameof(DistributedEventSent); + return Task.CompletedTask; + } + + public Task HandleEventAsync(DistributedEventReceived eventData) + { + MyEventDate.Order += nameof(DistributedEventReceived); + return Task.CompletedTask; + } + } + } diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/UnitTestLocalEventBus.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/UnitTestLocalEventBus.cs deleted file mode 100644 index 1511d6a071..0000000000 --- a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/UnitTestLocalEventBus.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Threading.Tasks; -using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Volo.Abp.EventBus.Local; -using Volo.Abp.MultiTenancy; -using Volo.Abp.Uow; - -namespace Volo.Abp.EventBus.Distributed; - -/// -/// This class is used in unit tests and supports to publish DistributedEventSent and DistributedEventReceived events. -/// -public class UnitTestLocalEventBus : LocalEventBus -{ - public UnitTestLocalEventBus( - [NotNull] IOptions options, - [NotNull] IServiceScopeFactory serviceScopeFactory, - [NotNull] ICurrentTenant currentTenant, - [NotNull] IUnitOfWorkManager unitOfWorkManager, - [NotNull] IEventHandlerInvoker eventHandlerInvoker) - : base(options, serviceScopeFactory, currentTenant, unitOfWorkManager, eventHandlerInvoker) - { - } - - public Func OnEventHandleInvoking { get; set; } - - protected async override Task InvokeEventHandlerAsync(IEventHandler eventHandler, object eventData, Type eventType) - { - if (OnEventHandleInvoking != null && eventType != typeof(DistributedEventSent) && eventType != typeof(DistributedEventReceived)) - { - await OnEventHandleInvoking(eventType, eventData); - } - - await base.InvokeEventHandlerAsync(eventHandler, eventData, eventType); - } - - public Func OnPublishing { get; set; } - - public async override Task PublishAsync( - Type eventType, - object eventData, - bool onUnitOfWorkComplete = true) - { - if (onUnitOfWorkComplete && UnitOfWorkManager.Current != null) - { - AddToUnitOfWork( - UnitOfWorkManager.Current, - new UnitOfWorkEventRecord(eventType, eventData, EventOrderGenerator.GetNext()) - ); - return; - } - - if (OnPublishing != null && eventType != typeof(DistributedEventSent) && eventType != typeof(DistributedEventReceived)) - { - await OnPublishing(eventType, eventData); - } - - await PublishToEventBusAsync(eventType, eventData); - } -} diff --git a/latest-versions.json b/latest-versions.json index bfe7ef568c..7cf305971d 100644 --- a/latest-versions.json +++ b/latest-versions.json @@ -1,4 +1,13 @@ [ + { + "version": "9.0.4", + "releaseDate": "", + "type": "stable", + "message": "", + "leptonx": { + "version": "4.0.5" + } + }, { "version": "9.0.3", "releaseDate": "", diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml b/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml index 02adac253b..9a62892b5e 100644 --- a/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml @@ -57,6 +57,10 @@
+
+ +

@BrandingProvider.AppName

+
@if (MultiTenancyOptions.Value.IsEnabled && (TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(CookieTenantResolveContributor.ContributorName) == true || TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(QueryStringTenantResolveContributor.ContributorName) == true)) diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css b/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css index 6054260e2d..14aa3186c2 100644 --- a/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css @@ -1,4 +1,6 @@ - + +@import url('https://fonts.googleapis.com/css2?family=Lexend:wght@100..900&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); + #main-navbar-tools a.dropdown-toggle { text-decoration: none; color: #fff; @@ -97,4 +99,24 @@ div.dataTables_wrapper div.dataTables_length label { } .rtl table.dataTable thead th, table.dataTable thead td, table.dataTable tfoot th, table.dataTable tfoot td { text-align: right; +} + +.brand-container{ + text-align: center; + margin-top: 8rem; +} + +.brand-logo { + height: 50px; +} + +.brand-text { + color: #292D33; + font-family: Lexend; + font-size: 30px; + font-style: normal; + font-weight: 500; + line-height: 34px; + margin-top: 5px; + margin-bottom: 15px; } \ No newline at end of file diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/logo.svg b/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/logo.svg new file mode 100644 index 0000000000..b1fb4568f8 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/logo.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Menus/CmsKitAdminMenuContributor.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Menus/CmsKitAdminMenuContributor.cs index ede01b4813..b17e595442 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Menus/CmsKitAdminMenuContributor.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Menus/CmsKitAdminMenuContributor.cs @@ -114,7 +114,7 @@ public class CmsKitAdminMenuContributor : IMenuContributor l["Cms"], icon: "far fa-newspaper"); - context.Menu.GetAdministration().AddItem(cmsMenu); + context.Menu.AddItem(cmsMenu); } foreach (var menu in cmsMenus) diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Application.Contracts/Volo/CmsKit/Users/CmsUserDto.cs b/modules/cms-kit/src/Volo.CmsKit.Common.Application.Contracts/Volo/CmsKit/Users/CmsUserDto.cs index c0f049b37b..b6a698e180 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Common.Application.Contracts/Volo/CmsKit/Users/CmsUserDto.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Common.Application.Contracts/Volo/CmsKit/Users/CmsUserDto.cs @@ -1,4 +1,4 @@ -using System; +using System; using Volo.Abp.Application.Dtos; namespace Volo.CmsKit.Users; @@ -6,9 +6,9 @@ namespace Volo.CmsKit.Users; [Serializable] public class CmsUserDto : ExtensibleEntityDto { - public virtual Guid? TenantId { get; protected set; } + public virtual Guid? TenantId { get; set; } - public virtual string UserName { get; protected set; } + public virtual string UserName { get; set; } public virtual string Name { get; set; } diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj index 89756bac0d..ace9c878ea 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj @@ -8,8 +8,8 @@ - - + + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj index 2bef783ba9..2623cbc7cc 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj @@ -8,8 +8,8 @@ - - + + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.csproj index 55bf1f166d..eab75defd4 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.csproj @@ -9,8 +9,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyCompanyName.MyProjectName.Blazor.Client.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyCompanyName.MyProjectName.Blazor.Client.csproj index bb5cf8f778..83c07df42a 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyCompanyName.MyProjectName.Blazor.Client.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyCompanyName.MyProjectName.Blazor.Client.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj index d8dc546969..78036c4c1f 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj index e3a56d7f7b..d8dc1a3133 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Client.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Client.csproj index 1ec24f6acb..d8e572ca4f 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Client.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Client.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client.csproj index 5b328c3811..8a5d26d1cd 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.csproj index 404a43492e..09cc96eb3d 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyCompanyName.MyProjectName.Blazor.WebApp.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyCompanyName.MyProjectName.Blazor.WebApp.csproj index da4524f154..b66a2a7fb9 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyCompanyName.MyProjectName.Blazor.WebApp.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyCompanyName.MyProjectName.Blazor.WebApp.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyCompanyName.MyProjectName.Blazor.Host.Client.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyCompanyName.MyProjectName.Blazor.Host.Client.csproj index ad77d7fe27..6e53e58d4e 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyCompanyName.MyProjectName.Blazor.Host.Client.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyCompanyName.MyProjectName.Blazor.Host.Client.csproj @@ -10,8 +10,8 @@ - - + + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyCompanyName.MyProjectName.Blazor.Server.Host.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyCompanyName.MyProjectName.Blazor.Server.Host.csproj index 1ff26dcb6d..717900b944 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyCompanyName.MyProjectName.Blazor.Server.Host.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyCompanyName.MyProjectName.Blazor.Server.Host.csproj @@ -13,8 +13,8 @@ - - + +