diff --git a/.github/workflows/auto-pr.yml b/.github/workflows/auto-pr.yml index a572653e48..cba64abd5d 100644 --- a/.github/workflows/auto-pr.yml +++ b/.github/workflows/auto-pr.yml @@ -1,13 +1,13 @@ -name: Merge branch dev with rel-9.0 +name: Merge branch dev with rel-9.1 on: push: branches: - - rel-9.0 + - rel-9.1 permissions: contents: read jobs: - merge-dev-with-rel-9-0: + merge-dev-with-rel-9-1: permissions: contents: write # for peter-evans/create-pull-request to create branch pull-requests: write # for peter-evans/create-pull-request to create a PR @@ -18,14 +18,14 @@ jobs: ref: dev - name: Reset promotion branch run: | - git fetch origin rel-9.0:rel-9.0 - git reset --hard rel-9.0 + git fetch origin rel-9.1:rel-9.1 + git reset --hard rel-9.1 - name: Create Pull Request uses: peter-evans/create-pull-request@v3 with: - branch: auto-merge/rel-9-0/${{github.run_number}} - title: Merge branch dev with rel-9.0 - body: This PR generated automatically to merge dev with rel-9.0. Please review the changed files before merging to prevent any errors that may occur. + branch: auto-merge/rel-9-1/${{github.run_number}} + title: Merge branch dev with rel-9.1 + body: This PR generated automatically to merge dev with rel-9.1. Please review the changed files before merging to prevent any errors that may occur. reviewers: maliming draft: true token: ${{ github.token }} @@ -34,5 +34,5 @@ jobs: GH_TOKEN: ${{ secrets.BOT_SECRET }} run: | gh pr ready - gh pr review auto-merge/rel-9-0/${{github.run_number}} --approve - gh pr merge auto-merge/rel-9-0/${{github.run_number}} --merge --auto --delete-branch + gh pr review auto-merge/rel-9-1/${{github.run_number}} --approve + gh pr merge auto-merge/rel-9-1/${{github.run_number}} --merge --auto --delete-branch diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 88b94689d6..42c241dd23 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -18,6 +18,7 @@ on: - 'templates/**/*.razor' - 'Directory.Build.props' - 'Directory.Packages.props' + - '.github/workflows/build-and-test.yml' pull_request: paths: @@ -35,6 +36,7 @@ on: - 'templates/**/*.razor' - 'Directory.Build.props' - 'Directory.Packages.props' + - '.github/workflows/build-and-test.yml' types: - opened - synchronize @@ -46,6 +48,7 @@ permissions: jobs: build-test: runs-on: ubuntu-latest + timeout-minutes: 40 if: ${{ !github.event.pull_request.draft }} steps: - uses: actions/checkout@v2 diff --git a/Directory.Packages.props b/Directory.Packages.props index ddadecbc2b..0b60bf9e60 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,8 +13,8 @@ - - + + @@ -29,7 +29,7 @@ - + @@ -39,8 +39,8 @@ - - + + @@ -51,84 +51,85 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + - + - - - - - - + + + + + + - + @@ -155,19 +156,19 @@ - + - + - - - + + + - + @@ -177,4 +178,4 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index 31db04cba2..7cf41d47eb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # ABP Framework ![build and test](https://img.shields.io/github/actions/workflow/status/abpframework/abp/build-and-test.yml?branch=dev&style=flat-square) 🔹 [![codecov](https://codecov.io/gh/abpframework/abp/branch/dev/graph/badge.svg?token=jUKLCxa6HF)](https://codecov.io/gh/abpframework/abp) 🔹 [![NuGet](https://img.shields.io/nuget/v/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core) 🔹 [![NuGet (with prereleases)](https://img.shields.io/nuget/vpre/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core) 🔹 [![MyGet (nightly builds)](https://img.shields.io/myget/abp-nightly/vpre/Volo.Abp.svg?style=flat-square)](https://abp.io/docs/latest/release-info/nightly-builds) 🔹 -[![NuGet Download](https://img.shields.io/nuget/dt/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core) 🔹 [![Code of Conduct](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](https://github.com/abpframework/abp/blob/dev/CODE_OF_CONDUCT.md) 🔹 [![CLA Signed](https://cla-assistant.io/readme/badge/abpframework/abp)](https://cla-assistant.io/abpframework/abp) 🔹 [![Discord Shield](https://discord.com/api/guilds/951497912645476422/widget.png?style=shield)](https://discord.gg/abp) +[![NuGet Download](https://img.shields.io/nuget/dt/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core) 🔹 [![Code of Conduct](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](https://github.com/abpframework/abp/blob/dev/CODE_OF_CONDUCT.md) 🔹 [![CLA Signed](https://cla-assistant.io/readme/badge/abpframework/abp)](https://cla-assistant.io/abpframework/abp) 🔹 [![Discord Shield](https://discord.com/api/guilds/951497912645476422/widget.png?style=shield)](https://abp.io/join-discord) [ABP](https://abp.io/) offers an **opinionated architecture** to build enterprise software solutions with **best practices** on top of the **.NET** and the **ASP.NET Core** platforms. It provides the fundamental infrastructure, production-ready startup templates, pre-built application modules, UI themes, tooling, guides and documentation to implement that architecture properly and **automate the details** and repetitive works as much as possible. @@ -121,4 +121,4 @@ GitHub repository stars are an important indicator of popularity and the size of ## Discord Server -We have a Discord server where you can chat with other ABP users. Share your ideas, report technical issues, showcase your creations, share the tips that worked for you and catch up with the latest news and announcements about ABP Framework. Join 👉 https://discord.gg/abp. +We have a Discord server where you can chat with other ABP users. Share your ideas, report technical issues, showcase your creations, share the tips that worked for you and catch up with the latest news and announcements about ABP Framework. Join 👉 https://abp.io/join-discord. diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json index 44ab5b3e31..db3249e881 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json @@ -13,6 +13,9 @@ "ManageAccount": "My Account | ABP.IO", "ManageYourProfile": "Manage your profile", "ReturnToApplication": "Return to application", - "IdentityUserNotAvailable:Deleted": "This email address is not available. Reason: Already deleted." + "IdentityUserNotAvailable:Deleted": "This email address is not available. Reason: Already deleted.", + "SelectYourOrganization": "Select your organization", + "PleaseSelectOrganization": "Please select an organization to continue", + "Continue": "Continue" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index 0e8546ae41..ea150de47e 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -664,6 +664,9 @@ "EditWinners": "Edit winners", "EditAttendees": "Edit attendees", "ExportAttendeesAsExcel": "Export attendees as Excel", - "DuplicateRaffle": "Duplicate raffle" + "DuplicateRaffle": "Duplicate raffle", + "Menu:RedisManagement": "Redis Management", + "RedisManagement": "Redis Management", + "Permission:RedisManagement": "Redis Management" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json index ed7781b476..4890226d9a 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json @@ -242,14 +242,14 @@ "ReturnOnInvestment": "Return on Investment", "PromotionalOffers": "Promotional Offers", "PromotionalOffersDefinition": "Discounts, seasonal campaigns, etc.", - "EventsDefinition": "Community Talks, Webinars, ABP .NET Conference, etc.", + "EventsDefinition": "Community Talks, Webinars, ABP DOTNET Conference, etc.", "ReleaseNotesDefinition": "ABP.IO Platform releases, new products, etc.", "Newsletter": "Newsletter", "NewsletterDefinition": "Blog posts, community news, etc.", "OrganizationOverview": "Organization Overview", "EmailPreferences": "Email Preferences", "VideoCourses": "Essential Videos", - "DoYouAgreePrivacyPolicy": "By clicking Subscribe button you agree to the Terms & Conditions and Privacy Policy.", + "DoYouAgreePrivacyPolicy": "By clicking Subscribe button you agree to the Terms & Conditions and Privacy Policy.", "AbpConferenceDescription": "ABP Conference is a virtual event for .NET developers to learn and connect with the community.", "Mobile": "Mobile", "MetaTwitterCard": "summary_large_image" diff --git a/common.props b/common.props index fda9b5e35b..83a713fe69 100644 --- a/common.props +++ b/common.props @@ -1,8 +1,8 @@ latest - 9.1.0-preview - 4.1.0-preview + 9.1.0-rc.1 + 4.1.0-rc.1 $(NoWarn);CS1591;CS0436 https://abp.io/assets/abp_nupkg.png https://abp.io/ diff --git a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/POST.md b/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/POST.md index b23dab0cd1..dfc5a7d67e 100644 --- a/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/POST.md +++ b/docs/en/Blog-Posts/2022-05-09 v5_3_Preview/POST.md @@ -255,4 +255,4 @@ We've created an official ABP Discord server so the ABP Community can interact w Thanks to the ABP Community, **700+** people joined our Discord Server so far and it grows every day. -You can join our Discord Server from [here](https://discord.gg/abp), if you haven't yet. \ No newline at end of file +You can join our Discord Server from [here](https://abp.io/join-discord), if you haven't yet. \ No newline at end of file diff --git a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/POST.md b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/POST.md index e545681d71..8b0e45fc7b 100644 --- a/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/POST.md +++ b/docs/en/Blog-Posts/2022-07-26 v6_0_Preview/POST.md @@ -187,7 +187,6 @@ The following improvements have been made on [eShopOnAbp project](https://github * Some improvements have been made on the Admin Application for Order Management for Angular UI. See [#110](https://github.com/abpframework/eShopOnAbp/pull/110). * `SignalR` error on Kubernetes & Docker Compose has been fixed. See [#113](https://github.com/abpframework/eShopOnAbp/pull/113). -* eShopOnAbp project has been deployed to Azure Kubernetes Service. See [#114](https://github.com/abpframework/eShopOnAbp/pull/114). The live demo can be seen from [eshoponabp.com](https://eshoponabp.com/). * Configurations have been made for some services on the `docker-compose.yml` file. See [#112](https://github.com/abpframework/eShopOnAbp/pull/112). * Gateway Redirect Loop problem on Kubernetes has been fixed. See [the commit](https://github.com/abpframework/eShopOnAbp/commit/6413ef15c91cd8a5309050b63bb4dbca23587607). diff --git a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/post.md b/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/post.md index b844fb5540..574dd6b590 100644 --- a/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/post.md +++ b/docs/en/Blog-Posts/2023-04-03-What-is-ABP-Framework/post.md @@ -105,9 +105,12 @@ ASP.NET modularity, ASP.NET modular development, ASP.NET localization, ASP.NET m ## Microservice Example: eShopOnAbp -[eShopOnAbp](https://www.eshoponabp.com/) is a microservice example built on top ABP. It is a sample net application, similar to the Microsoft's [eShopOnContainer](https://github.com/dotnet-architecture/eShopOnContainers) project. It is a reference microservice solution built with the ABP Framework and .NET, runs on Kubernetes with Helm configuration, includes API Gateways, Angular and ASP.NET Core MVC applications with PostgreSQL and MongoDB databases. For more information, check out https://github.com/abpframework/eShopOnAbp. - +[eShopOnAbp](https://github.com/abpframework/eShopOnAbp) is a microservice example built on top ABP. It is a sample net application, similar to the Microsoft's [eShopOnContainer](https://github.com/dotnet-architecture/eShopOnContainers) project. It is a reference microservice solution built with the ABP Framework and .NET, runs on Kubernetes with Helm configuration, includes API Gateways, Angular and ASP.NET Core MVC applications with PostgreSQL and MongoDB databases. For more information, check out 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. +> +> The new template offers a modernized and officially supported starting point for building microservices with ABP. Please consider transitioning to the new template for the latest features and improvements. ## Conclusion diff --git a/docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/community-talks.png b/docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/community-talks.png new file mode 100644 index 0000000000..7263b68e10 Binary files /dev/null and b/docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/community-talks.png differ diff --git a/docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/cover-image.png b/docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/cover-image.png new file mode 100644 index 0000000000..f272a8d463 Binary files /dev/null and b/docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/cover-image.png differ diff --git a/docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/post.md b/docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/post.md new file mode 100644 index 0000000000..26850bc57f --- /dev/null +++ b/docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/post.md @@ -0,0 +1,93 @@ +# ABP.IO Platform 9.0 Has Been Released Based on .NET 9.0 + +![](cover-image.png) + +Today, [ABP](https://abp.io/) 9.0 stable version has been released based on [.NET 9.0](https://dotnet.microsoft.com/en-us/download/dotnet/9.0). You can create solutions with ABP 9.0 starting from ABP Studio v0.9.11 or by using the ABP CLI as explained in the following sections. + +## What's New With Version 9.0? + +All the new features were explained in detail in the [9.0 RC Announcement Post](https://abp.io/blog/announcing-abp-9-0-release-candidate), so there is no need to review them again. You can check it out for more details. + +## Getting Started with 9.0 + +### Creating New Solutions + +You can check the [Get Started page](https://abp.io/get-started) to see how to get started with ABP. You can either download [ABP Studio](https://abp.io/get-started#abp-studio-tab) (**recommended**, if you prefer a user-friendly GUI application - desktop application) or use the [ABP CLI](https://abp.io/docs/latest/cli) to create new solutions. + +By default, ABP Studio uses stable versions to create solutions. Therefore, it will be creating the solution with the latest stable version, which is v9.0 for now, so you don't need to specify the version. **You can create solutions with ABP 9.0 starting from v0.9.11.** + +### How to Upgrade an Existing Solution + +You can upgrade your existing solutions with either ABP Studio or ABP CLI. In the following sections, both approaches are explained: + +### Upgrading via ABP Studio + +If you are already using the ABP Studio, you can upgrade it to the latest version to align it with ABP v9.0. ABP Studio periodically checks for updates in the background, and when a new version of ABP Studio is available, you will be notified through a modal. Then, you can update it by confirming the opened modal. See [the documentation](https://abp.io/docs/latest/studio/installation#upgrading) for more info. + +After upgrading the ABP Studio, then you can open your solution in the application, and simply click the **Switch to stable** action button to instantly upgrade your solution: + +![](switch-to-stable.png) + +> Please note that ABP CLI & ABP Studio only upgrade the related ABP packages, so you need to upgrade the other packages for .NET 9.0 manually. + +### Upgrading via ABP CLI + +Alternatively, you can upgrade your existing solution via ABP CLI. First, you need to install the ABP CLI or upgrade it to the latest version. + +If you haven't installed it yet, you can run the following command: + +```bash +dotnet tool install -g Volo.Abp.Studio.Cli +``` + +Or to update the existing CLI, you can run the following command: + +```bash +dotnet tool update -g Volo.Abp.Studio.Cli +``` + +After installing/updating the ABP CLI, you can use the [`update` command](https://abp.io/docs/latest/CLI#update) to update all the ABP related NuGet and NPM packages in your solution as follows: + +```bash +abp update +``` + +You can run this command in the root folder of your solution to update all ABP related packages. + +> Please note that ABP CLI & ABP Studio only upgrade the related ABP packages, so you need to upgrade the other packages for .NET 9.0 manually. + +## Migration Guides + +There are a few breaking changes in this version that may affect your application. Please read the migration guide carefully, if you are upgrading from v8.x: [ABP Version 9.0 Migration Guide](https://abp.io/docs/9.0/release-info/migration-guides/abp-9-0) + +## Community News + +### Highlights from .NET 9.0 + +Our team has closely followed the ASP.NET Core and Entity Framework Core 9.0 releases, read Microsoft's guides and documentation, and adapted the changes to our ABP.IO Platform. We are proud to say that we've shipped the ABP 9.0 based on .NET 9.0 just after Microsoft's .NET 9.0 release. + +In addition to the ABP's .NET 9.0 upgrade, our team has created many great articles to highlight the important features coming with ASP.NET Core 9.0 and Entity Framework Core 9.0. + +> You can read [this post](https://volosoft.com/blog/Highlights-for-ASP-NET-Entity-Framework-Core-NET-9-0) to see the list of all articles. + +### New ABP Community Articles + +In addition to [the articles to highlight .NET 9.0 features written by our team](https://volosoft.com/blog/Highlights-for-ASP-NET-Entity-Framework-Core-NET-9-0), here are some of the recent posts added to the [ABP Community](https://abp.io/community): + +* [Video: Building Modular Monolith Applications with ASP.NET Core & ABP Studio](https://abp.io/community/videos/building-modular-monolith-applications-with-asp.net-core-abp-studio-66znukvf) by [Halil İbrahim Kalkan](https://x.com/hibrahimkalkan) +* [How to create your Own AI Bot on WhatsApp Using an ABP.io Template](https://abp.io/community/articles/how-to-create-your-own-ai-bot-on-whatsapp-using-the-abp-framework-c6jgvt9c) by [Michael Kokula](https://abp.io/community/members/Michal_Kokula) +* [ABP Now Supports .NET 9](https://abp.io/community/articles/abp-now-supports-.net-9-zpkznc4f) by [Alper Ebiçoğlu](https://x.com/alperebicoglu) + +Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://abp.io/community/posts/submit) to the ABP Community. + +### ABP Community Talks 2024.7: What’s New with .NET 9 & ABP 9? + +![](community-talks.png) + +In this episode of ABP Community Talks, 2024.7; we will dive into the features that came with .NET 9.0 with [Alper Ebicoglu](https://github.com/ebicoglu), [Engincan Veske](https://github.com/EngincanV), [Berkan Sasmaz](https://github.com/berkansasmaz) and [Ahmet Faruk Ulu](https://github.com/ahmetfarukulu). + +## Conclusion + +This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/9.0/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v9.0 and provide feedback to help us release more stable versions. + +Thanks for being a part of this community! diff --git a/docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/switch-to-stable.png b/docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/switch-to-stable.png new file mode 100644 index 0000000000..30883ebf92 Binary files /dev/null and b/docs/en/Blog-Posts/2024-11-19 v9_0_Release_Stable/switch-to-stable.png differ diff --git a/docs/en/Blog-Posts/2024-12-15-ABP-Studio-R2R/POST.md b/docs/en/Blog-Posts/2024-12-15-ABP-Studio-R2R/POST.md new file mode 100644 index 0000000000..e4c37eec07 --- /dev/null +++ b/docs/en/Blog-Posts/2024-12-15-ABP-Studio-R2R/POST.md @@ -0,0 +1,67 @@ +# ABP Studio Goes AOT: Faster Startups with Ready-to-Run (R2R) Publishing + +We're excited that [ABP Studio](https://abp.io/studio) now supports [Ready-to-Run (R2R) publishing](https://learn.microsoft.com/en-us/dotnet/core/deploying/ready-to-run) (starting from v0.9.16+), a hybrid form of ahead-of-time (AOT) compilation. This enhancement significantly improves the startup time and overall performance of ABP Studio, making it faster and more performant than ever before. + +Let's dive into what R2R publishing is, how it works, and the benefits it brings to ABP Studio. + +## What is Ready-to-Run (R2R) Publishing? + +Ready-to-Run (R2R) is a form of AOT compilation available in the .NET ecosystem. Unlike traditional just-in-time (JIT) compilation, R2R precompiles parts of your application to native code before deployment. This precompiled code helps reduce the startup time by minimizing the work needed during runtime. + +However, R2R isn't a complete AOT compilation. Instead, it's a hybrid approach because it stores both: + +* **Native code for precompiled methods** (to improve startup time and performance) + +* **Intermediate Language (IL) code** for methods that may need further JIT compilation + +This hybrid nature is why R2R binaries are typically larger. For ABP Studio, the storage size increased by ~150 MB with R2R enabled, but the trade-off is well worth it for the performance and startup-time gains. + +## How R2R (Ready-to-Run) Improves ABP Studio + +### Faster Startup Time 🚀 + +One of the biggest advantages of R2R publishing is its impact on startup times. In our local tests, enabling R2R resulted in startup times being **reduced by 2.5x** ⬇️. + +This means you can get to work faster, without waiting for the application to being startup from the beginning. Whether you're launching ABP Studio to manage projects, generate code, or deploy applications, the improved responsiveness is noticeable. + +### Performance Enhancements 📈 + +In addition to faster startups, R2R publishing contributes to overall performance improvements. By precompiling frequently used methods, R2R reduces the workload on the JIT compiler during execution, leading to smoother and more efficient operations. + +### Trade-offs: Increased Storage Size 🆙 + +With great performance comes a slight trade-off: storage size. R2R binaries include both **native** and **IL code**, which increases the file size. In the case of ABP Studio, the storage footprint increased by ~150 MB. However, the substantial improvements in speed and responsiveness make this a worthwhile investment. + +## How to Enable R2R Publishing in Your Applications? + +If you're developing applications and want to benefit from R2R, here's a quick guide on how to enable it in your .NET projects: + +1. You can add the following configuration to your final project's `.csproj` file: + +```xml + + true + +``` + +2. Then, publish your application with the `dotnet publish` command: + +```bash +dotnet publish -c Release +``` + +Alternatively, you can specify the _PublishReadyToRun_ flag directly to the `dotnet publish` command as follows: + +```bash +dotnet publish -c Release -r win-x64 -p:PublishReadyToRun=true +``` + +That's it! Your application will now include precompiled native code for faster startup and great performance benefits. + +> Please refer to the [official documentation](https://learn.microsoft.com/en-us/dotnet/core/deploying/ready-to-run) before publishing your application with R2R. + +## Conclusion + +As ABP team, we're always looking for ways to improve the developer experience. By adopting **Ready-to-Run (R2R) publishing** for ABP Studio, we're aiming to deliver a faster and more efficient tool for your development needs. + +Stay tuned for more updates and enhancements as we continue to optimize ABP Studio and please provide us with your invaluable feedback. \ No newline at end of file diff --git a/docs/en/Community-Articles/2022-09-15-Grpc-Demo/POST.md b/docs/en/Community-Articles/2022-09-15-Grpc-Demo/POST.md index b3870ff067..f639d60b0c 100644 --- a/docs/en/Community-Articles/2022-09-15-Grpc-Demo/POST.md +++ b/docs/en/Community-Articles/2022-09-15-Grpc-Demo/POST.md @@ -240,3 +240,7 @@ gRPC on .NET has different approaches, features, configurations and more details * You can find the completed source code here: https://github.com/abpframework/abp-samples/tree/master/GrpcDemo2 * You can also see all the changes I've done in this article here: https://github.com/abpframework/abp-samples/pull/200/files + +## See Also + +* [Consuming gRPC Services from Blazor WebAssembly Application Using gRPC-Web](https://abp.io/community/articles/consuming-grpc-services-from-blazor-webassembly-application-using-grpcweb-dqjry3rv) diff --git a/docs/en/Community-Articles/2024-01-18-ABP-Now-Supports-Keyed-Services/POST.md b/docs/en/Community-Articles/2024-01-18-ABP-Now-Supports-Keyed-Services/POST.md index a5f2c694ed..d100838b1a 100644 --- a/docs/en/Community-Articles/2024-01-18-ABP-Now-Supports-Keyed-Services/POST.md +++ b/docs/en/Community-Articles/2024-01-18-ABP-Now-Supports-Keyed-Services/POST.md @@ -187,11 +187,21 @@ On the other hand, resolving keyed services from `LazyServiceProvider` is not su ### Automatically Registering Keyed Services -Currently, if you want to register a keyed service, you need to do it manually as we see in the previous sections by using one of the overloads (`.AddKeyedTransient`, `.AddKeyedScoped` and `.AddKeyedSingleton`). +ABP provides the `ExposeKeyedServiceAttribute` to control which keyed services are provided by the related class. -It would be good if we could make this process automatically and not need to manually register services, and for that purpose, I have [created an issue](https://github.com/abpframework/abp/issues/18794) that aims to introduce an attribute, which allows us to automatically register multiple services as keyed services. +For example, if you want to register a keyed service as a transient dependency, you can do it as follows: -You can [follow the issue](https://github.com/abpframework/abp/issues/18794) if you are considering using keyed services in your application and don't want to register them manually. +```csharp +[ExposeKeyedService("taxCalculator")] +[ExposeKeyedService("calculator")] +public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency +{ +} +``` + +> Notice that the ExposeKeyedServiceAttribute only exposes the keyed services. So, you can not inject the ITaxCalculator or ICalculator interfaces in your application without using the FromKeyedServicesAttribute as shown in the example above. If you want to expose both keyed and non-keyed services, you can use the ExposeServicesAttribute and ExposeKeyedServiceAttribute attributes altogether. + +Please refer to the [Dependency Injection document](https://abp.io/docs/latest/framework/fundamentals/dependency-injection#exposekeyedservice-attribute) for further info. ## Summary diff --git a/docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/img2.png b/docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/img2.png index 87076b1329..a89415db1d 100644 Binary files a/docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/img2.png and b/docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/img2.png differ diff --git a/docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/img3.png b/docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/img3.png index a458547e8a..8698b153ab 100644 Binary files a/docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/img3.png and b/docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/img3.png differ diff --git a/docs/en/Community-Articles/2024-11-25-Global-Assets/POST.md b/docs/en/Community-Articles/2024-11-25-Global-Assets/POST.md new file mode 100644 index 0000000000..33dd9eb292 --- /dev/null +++ b/docs/en/Community-Articles/2024-11-25-Global-Assets/POST.md @@ -0,0 +1,309 @@ +# ABP Global Assets - New way to bundle JavaScript/CSS files in Blazor WebAssembly app + +We have introduced a new feature in the ABP framework to bundle the `JavaScript/CSS` files in the Blazor wasm app. This feature is called `Global Assets`. +With this feature, you don't need to run the `abp bundle` command to manually create/maintain the `global.js` and `global.css` files in your Blazor wasm app. + +## How Global Assets works? + +The new `Blazor wasm app` has two projects: + +1. `MyProjectName` (ASP.NET Core app) +2. `MyProjectName.Client` (Blazor wasm app) + +The `MyProjectName` reference the `MyProjectName.Client` project, and will be the entry point of the application, which means the `MyProjectName` project will be the `host` project of the `MyProjectName.Client` project. + +The static/virtual files of `MyProjectName` can be accessed by the `MyProjectName.Client` project, so we can create dynamic global assets in the `MyProjectName` project and use them in the `MyProjectName.Client` project. + +## How it works in ABP? + +We have created a new package `WebAssembly.Theme.Bundling` for the theme `WebAssembly` module and used the `Volo.Abp.AspNetCore.Mvc.UI.Bundling.BundleContributor` to add `JavaScript/CSS` files to the bundling system. + +* LeptonXLiteTheme: `AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule` +* LeptonXTheme: `AbpAspNetCoreComponentsWebAssemblyLeptonXThemeBundlingModule` +* LeptonTheme: `AbpAspNetCoreComponentsWebAssemblyLeptonThemeBundlingModule` +* BasicTheme: `AbpAspNetCoreComponentsWebAssemblyBasicThemeBundlingModule` + +The new `ThemeBundlingModule` only depends on `AbpAspNetCoreComponentsWebAssemblyThemingBundlingModule(new package)`. It's an `abstractions module`, which only depends on `AbpAspNetCoreMvcUiBundlingAbstractionsModule`. + +We will get all `JavaScript/CSS` files on `OnApplicationInitializationAsync` method of `AbpAspNetCoreMvcUiBundlingModule` from bundling system and add them to `IDynamicFileProvider` service. After that, we can access the `JavaScript/CSS` files in the Blazor wasm app. + +## Add the Global Assets in the module + +If your module has `JavaScript/CSS` files that need to the bundling system, You have to create a new project(`YourModuleName.Blazor.WebAssembly.Bundling`) to your module solution, and reference the new project in the `MyProjectName` project and module dependencies. + +The new project should **only** depend on the `AbpAspNetCoreComponentsWebAssemblyThemingBundlingModule` and define `BundleContributor` classes to contribute the `JavaScript/CSS` files. + +> Q: The new project(`YourModuleName.Blazor.WebAssembly.Bundling`) doesn't have the `libs/myscript.js` and `libs/myscript.css` files why the files can be added to the bundling system? + +> A: Because the `MyProjectName.Client` will depend on the `MyBlazorModule(YourModuleName.Blazor)` that contains the `JavaScript/CSS` files, The `MyProjectName` is referencing the `MyProjectName.Client` project, so the `MyProjectName` project can access the `JavaScript/CSS` files in the `MyProjectName.Client` project and add them to the bundling system. + +```csharp +[DependsOn( + typeof(AbpAspNetCoreComponentsWebAssemblyThemingBundlingModule) +)] +public class MyBlazorWebAssemblyBundlingModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + // Script Bundles + options.ScriptBundles.Get(BlazorWebAssemblyStandardBundles.Scripts.Global).AddContributors(typeof(MyModuleBundleScriptContributor)); + + // Style Bundles + options.ScriptBundles.Get(BlazorWebAssemblyStandardBundles.Scripts.Global).AddContributors(typeof(MyModuleBundleStyleBundleContributor)); + }); + } +} +``` + +```csharp +public class MyModuleBundleScriptContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("_content/MyModule.Blazor/libs/myscript.js"); + } +} + +public class MyModuleBundleStyleBundleContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("_content/MyModule.Blazor/libs/myscript.css"); + } +} +``` + +## Use the Global Assets in the Blazor WASM + +### MyCompanyName.MyProjectName.Blazor + +Convert your `MyCompanyName.MyProjectName.Blazor` project to integrate the `ABP module` system and depend on the `AbpAspNetCoreMvcUiBundlingModule` and `AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule/AbpAspNetCoreComponentsWebAssemblyLeptonXThemeBundlingModule`: + +* The `AbpAspNetCoreMvcUiBundlingModule` uses to create the `JavaScript/CSS` files to virtual files. +* The `AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule/AbpAspNetCoreComponentsWebAssemblyLeptonXThemeBundlingModule` uses to add theme `JavaScript/CSS` to the bundling system. + +Here is how your project files look like: + +**`Program.cs`:** + +```csharp +public class Program +{ + public async static Task Main(string[] args) + { + //... + + var builder = WebApplication.CreateBuilder(args); + builder.Host.AddAppSettingsSecretsJson() + .UseAutofac() + .UseSerilog(); + await builder.AddApplicationAsync(); + var app = builder.Build(); + await app.InitializeApplicationAsync(); + await app.RunAsync(); + return 0; + + //... + } +} +``` + +**`MyProjectNameBlazorModule.cs`:** + +```csharp +[DependsOn( + typeof(AbpAutofacModule), + typeof(AbpAspNetCoreMvcUiBundlingModule), + typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule/AbpAspNetCoreComponentsWebAssemblyLeptonXThemeBundlingModule) //Should be added! +)] +public class MyProjectNameBlazorModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + //https://github.com/dotnet/aspnetcore/issues/52530 + Configure(options => + { + options.SuppressCheckForUnhandledSecurityMetadata = true; + }); + + // Add services to the container. + context.Services.AddRazorComponents() + .AddInteractiveWebAssemblyComponents(); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var env = context.GetEnvironment(); + var app = context.GetApplicationBuilder(); + + // Configure the HTTP request pipeline. + if (env.IsDevelopment()) + { + app.UseWebAssemblyDebugging(); + } + else + { + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.MapAbpStaticAssets(); + app.UseRouting(); + app.UseAntiforgery(); + + app.UseConfiguredEndpoints(builder => + { + builder.MapRazorComponents() + .AddInteractiveWebAssemblyRenderMode() + .AddAdditionalAssemblies(WebAppAdditionalAssembliesHelper.GetAssemblies()); + }); + } +} +``` + +**`MyCompanyName.MyProjectName.Blazor.csproj`:** + +```xml + + + + + + if you're using LeptonXTheme + + +``` + +### BlazorWebAssemblyBundlingModule in the ABP commercial + +Here is the list of `Bundling Modules` in the ABP commercial. If you're using the pro template, you should add them to the `MyCompanyName.MyProjectName.Blazor` project. + +| BundlingModules | Nuget Package | +|---------------------------------------------|-----------------------------------------------------| +| AbpAuditLoggingBlazorWebAssemblyBundlingModule | Volo.Abp.AuditLogging.Blazor.WebAssembly.Bundling | +| FileManagementBlazorWebAssemblyBundlingModule | Volo.FileManagement.Blazor.WebAssembly.Bundling | +| SaasHostBlazorWebAssemblyBundlingModule | Volo.Saas.Host.Blazor.WebAssembly.Bundling | +| ChatBlazorWebAssemblyBundlingModule | Volo.Chat.Blazor.WebAssembly.Bundling | +| CmsKitProAdminBlazorWebAssemblyBundlingModule | Volo.CmsKit.Pro.Admin.Blazor.WebAssembly.Bundling | + + +### MyCompanyName.MyProjectName.Blazor.Client + +1. Remove the `global.JavaScript/CSS` files from the `MyCompanyName.MyProjectName.Blazor`'s `wwwroot` folder. +2. Remove the `AbpCli:Bundle` section from the `appsettings.json` file. +3. Remove all BundleContributor classes that inherit from IBundleContributor. Then, create `MyProjectNameStyleBundleContributor` and `MyProjectNameScriptBundleContributor` classes to add your style and JavaScript files. Finally, add them to `AbpBundlingOptions`. + + +```cs +public class MyProjectNameStyleBundleContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.Add(new BundleFile("main.css", true)); + } +} + + +public class MyProjectNameScriptBundleContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.Add(new BundleFile("main.js", true)); + } +} +``` + +```cs +Configure(options => +{ + var globalStyles = options.StyleBundles.Get(BlazorWebAssemblyStandardBundles.Styles.Global); + globalStyles.AddContributors(typeof(MyProjectNameStyleBundleContributor)); + + var globalScripts = options.ScriptBundles.Get(BlazorWebAssemblyStandardBundles.Scripts.Global); + globalScripts.AddContributors(typeof(MyProjectNameScriptBundleContributor)); +}); +``` + +## Use the Global Assets in the Blazor WebApp + +### MyCompanyName.MyProjectName.Blazor.WebApp + +Depending on the `AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule/AbpAspNetCoreComponentsWebAssemblyLeptonXThemeBundlingModule` in your `MyCompanyName.MyProjectName.Blazor.WebApp` project. + +* The `AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule/AbpAspNetCoreComponentsWebAssemblyLeptonXThemeBundlingModule` uses to add theme `JavaScript/CSS` to the bundling system. + + +### BlazorWebAssemblyBundlingModule in the ABP commercial + +Here is the list of `Bundling Modules` in the ABP commercial. If you're using the pro template, you should add them to the `MyCompanyName.MyProjectName.Blazor.WebApp` project. + +| BundlingModules | Nuget Package | +|---------------------------------------------|-----------------------------------------------------| +| AbpAuditLoggingBlazorWebAssemblyBundlingModule | Volo.Abp.AuditLogging.Blazor.WebAssembly.Bundling | +| FileManagementBlazorWebAssemblyBundlingModule | Volo.FileManagement.Blazor.WebAssembly.Bundling | +| SaasHostBlazorWebAssemblyBundlingModule | Volo.Saas.Host.Blazor.WebAssembly.Bundling | +| ChatBlazorWebAssemblyBundlingModule | Volo.Chat.Blazor.WebAssembly.Bundling | +| CmsKitProAdminBlazorWebAssemblyBundlingModule | Volo.CmsKit.Pro.Admin.Blazor.WebAssembly.Bundling | + + +### MyCompanyName.MyProjectName.Blazor.WebApp.Client + +1. Remove the `global.JavaScript/CSS` files from the `MyCompanyName.MyProjectName.Blazor.WebApp.Client`'s `wwwroot` folder. +2. Remove the `AbpCli:Bundle` section from the `appsettings.json` file. +3. Remove all BundleContributor classes that inherit from IBundleContributor. Then, create `MyProjectNameStyleBundleContributor` and `MyProjectNameScriptBundleContributor` classes to add your style and JavaScript files. Finally, add them to `AbpBundlingOptions`. + +```cs +public class MyProjectNameStyleBundleContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.Add(new BundleFile("main.css", true)); + } +} + + +public class MyProjectNameScriptBundleContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.Add(new BundleFile("main.js", true)); + } +} +``` + +```cs +Configure(options => +{ + var globalStyles = options.StyleBundles.Get(BlazorWebAssemblyStandardBundles.Styles.Global); + globalStyles.AddContributors(typeof(MyProjectNameStyleBundleContributor)); + + var globalScripts = options.ScriptBundles.Get(BlazorWebAssemblyStandardBundles.Scripts.Global); + globalScripts.AddContributors(typeof(MyProjectNameScriptBundleContributor)); +}); +``` + +### Check the Global Assets + +Run the `MyProject` project and check the `https://localhost/global.js` and `https://localhost/global.css` files. You should be able to see the `JavaScript/CSS` files content from the Bundling system: + +![global](image.png) + +## GlobalAssets(AbpBundlingGlobalAssetsOptions) + +You can configure the JavaScript and CSS file names in the `GlobalAssets` property of the `AbpBundlingOptions` class. + +The default values are `global.js` and `global.css`. + +## Conclusion + +With the new `Global Assets` feature, you can easily bundle the `JavaScript/CSS` files in the Blazor wasm app. This feature is very useful for the Blazor wasm app, and it will save you a lot of time and effort. We hope you will enjoy this feature and use it in your projects. + +## References + +* [Virtual Files](https://docs.abp.io/en/abp/latest/Virtual-Files) +* [Bundle Contributors](https://abp.io/docs/latest/framework/ui/mvc-razor-pages/bundling-minification#bundle-contributors) +* [Global Assets Pull Request](https://github.com/abpframework/abp/pull/19968) + diff --git a/docs/en/Community-Articles/2024-11-25-Global-Assets/image.png b/docs/en/Community-Articles/2024-11-25-Global-Assets/image.png new file mode 100644 index 0000000000..8c5c52a4fc Binary files /dev/null and b/docs/en/Community-Articles/2024-11-25-Global-Assets/image.png differ diff --git a/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/POST.md b/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/POST.md new file mode 100644 index 0000000000..c07509d2e3 --- /dev/null +++ b/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/POST.md @@ -0,0 +1,385 @@ +# How to Use OpenAI API with ABP Framework + +In this article, I will show you how to integrate and use the [OpenAI API](https://github.com/openai/openai-dotnet?tab=readme-ov-file#getting-started) with the [ABP Framework](https://abp.io/). We will explore step-by-step how these technologies can work together to enhance your application with powerful AI capabilities, such as natural language processing, image generation, and more. + +![cover-image](cover-image.png) + +## Creating an ABP Project + +To begin integrating OpenAI API with ABP Framework, you first need to create an ABP project. Follow these steps to create and set up your ABP project: +### Step 1: Install ABP CLI + +The ABP CLI is a command-line interface tool that helps you create and manage ABP projects easily. To install the ABP CLI, run the following command in your terminal: + +```bash +dotnet tool install -g Volo.Abp.Studio.Cli +``` + +### Step 2: Create a New ABP Project + +Once you have installed the ABP CLI, you can create a new ABP project using the following command: + +```bash +abp new Acme.OpenAIIntegration -t app --ui-framework mvc --database-provider ef -dbms PostgreSQL --csf +``` + +> This command will generate a complete ABP project with an [MVC UI](https://abp.io/docs/latest/framework/ui/mvc-razor-pages/overall). The examples provided in this article make use of UI controllers for demonstration purposes. However, the same approach can easily be applied to other UI types supported by ABP, such as Blazor or Angular. You can find other options [here](https://abp.io/docs/latest/cli). +## OpenAI Integration Setup + +To begin integrating OpenAI API with ABP Framework, follow these steps: + +### Step 1: Create an API Key + +To use the OpenAI services, you first need an API key. To obtain one, first [create a new OpenAI account](https://platform.openai.com/signup) or [log in](https://platform.openai.com/login). Next, navigate to the [API key page](https://platform.openai.com/account/api-keys) and select "Create new secret key", optionally naming the key. Make sure to save your API key somewhere safe and do not share it with anyone. + +This key will be used to authenticate your application when making requests to the OpenAI endpoints. + +### Step 2: Adding *Microsoft.Extensions.AI* Package + +To integrate OpenAI API with ABP, we use [Microsoft.Extensions.AI](https://www.nuget.org/packages/Microsoft.Extensions.AI.OpenAI/). This package offers a unified API for integrating AI services, making it easy for developers to work with different AI providers. You can find more details in [this blog post](https://devblogs.microsoft.com/dotnet/introducing-microsoft-extensions-ai-preview/). + +To begin integrating OpenAI API with ABP Framework, follow these steps: + +1. Add the **Microsoft.Extensions.AI** and **Microsoft.Extensions.AI.OpenAI** (used to interact specifically with OpenAI services. Additionally, this package has alternatives like [Azure OpenAI](https://www.nuget.org/packages/Microsoft.Extensions.AI.OpenAI/), [Azure AI Inference](https://www.nuget.org/packages/Microsoft.Extensions.AI.AzureAIInference/), and [Ollama](https://www.nuget.org/packages/Microsoft.Extensions.AI.Ollama/), offering flexibility for developers to choose the AI provider that best fits their needs) packages: + +```bash +dotnet add package Microsoft.Extensions.AI --prerelease +dotnet add package Microsoft.Extensions.AI.OpenAI --prerelease +``` + +2. Add the required configuration to the `appsettings.json` file located inside the `Acme.OpenAIIntegration.Web` project and dependencies to your `ConfigureServices` method: + +```json +"AI": { + "OpenAI": { + "Key": "YOUR-API-KEY", + "Chat": { + "ModelId": "gpt-4o-mini" + } + } +} +``` + +> Replace the value of the `Key` with your OpenAI API key. + +Next, add the following code to the `ConfigureServices` method in `OpenAIIntegrationBlazorModule`: + +```csharp +context.Services.AddSingleton(new OpenAIClient(configuration["AI:OpenAI:Key"])); + +context.Services.AddChatClient(services => + services.GetRequiredService().AsChatClient(configuration["AI:OpenAI:Chat:ModelId"] ?? "gpt-4o-mini")); +``` + +## Creating a Sample Page + +To demonstrate the use of OpenAI API, let's create a page named `Sample` in the `Acme.OpenAIIntegration.Web` project: + +Create a `Sample` folder under the `Pages` folder of the `Acme.OpenAIIntegration.Web` project. Add a new Razor Page by right-clicking the `Sample` folder then selecting `Add > Razor Page`. Name it `Index`. + +Open the `Index.cshtml` and change the whole content as shown below: + +> Note: This example demonstrates a simple implementation of a sample page that interacts with the OpenAI API, covering chat, [retrieval-augmented generation (RAG)](https://github.com/openai/openai-dotnet?tab=readme-ov-file#how-to-use-assistants-with-retrieval-augmented-generation-rag), and image generation features. Each example is explained in detail in the next section, so feel free to continue for a better understanding of the steps and logic involved. + +```html +@page +@model Acme.OpenAIIntegration.Web.Pages.Sample +@{ + ViewData["Title"] = "OpenAI API Demonstration"; +} + +

@ViewData["Title"]

+ +

+ +
+
+

Chat Example

+
+
+ + +
+ +
+ @if (!string.IsNullOrEmpty(Model.ChatResponse)) + { +

Response:

+

@Model.ChatResponse

+ } +
+ +
+

RAG Example

+
+
+ + +
+ +
+ @if (!string.IsNullOrEmpty(Model.RAGResponse)) + { +

Result:

+

@Model.RAGResponse

+ } +
+ +
+

Image Generation Example

+
+
+ + +
+ +
+ @if (Model.GeneratedImageBytes != null) + { +

Generated Image:

+ Generated image + } +
+
+``` + +`Index.cshtml.cs` content should be like that: + +```csharp +using System; +using System.ClientModel; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.AI; +using OpenAI; +using OpenAI.Assistants; +using OpenAI.Files; +using OpenAI.Images; + +namespace Acme.OpenAIIntegration.Web.Pages; + +public class Sample : PageModel +{ + [BindProperty] + public string ChatInput { get; set; } + public string ChatResponse { get; set; } + + [BindProperty] + public string RAGQuery { get; set; } + public string RAGResponse { get; set; } + + [BindProperty] + public string ImagePrompt { get; set; } + public byte[] GeneratedImageBytes { get; set; } + + private readonly IChatClient _chatClient; + private readonly OpenAIClient _openAiClient; + + public Sample( + IChatClient chatClient, + OpenAIClient openAiClient) + { + _chatClient = chatClient; + _openAiClient = openAiClient; + } + + public async Task OnPostChatAsync() + { + ChatResponse = $"Chat response: {(await _chatClient.CompleteAsync(ChatInput)).Message}"; + return Page(); + } + + public async Task OnPostRAGAsync() + { +#pragma warning disable OPENAI001 + var fileClient = _openAiClient.GetOpenAIFileClient(); + var assistantClient = _openAiClient.GetAssistantClient(); + + using var document = BinaryData.FromBytes(GetExceptionHandlingDocumentContent().ToArray()).ToStream(); + var exceptionHandlingDoc = await fileClient.UploadFileAsync( + document, + "ExceptionHandling.md", + FileUploadPurpose.Assistants); + + AssistantCreationOptions assistantOptions = new() + { + Name = "Exception Handling Assistant", + Instructions = + """ + This assistant helps you with exception handling in ABP Framework. You can ask questions about exception handling and get answers. + + - Do not make any assumptions when asked for information that is not in the document + - Give the most accurate information possible + - Give short(max 1-2 sentence) and concise answers + - Do not provide file citations + """, + + Tools = + { + new FileSearchToolDefinition(), + }, + ToolResources = new() + { + FileSearch = new() + { + NewVectorStores = + { + new VectorStoreCreationHelper([exceptionHandlingDoc.Value.Id]), + } + } + }, + }; + + var assistant = await assistantClient.CreateAssistantAsync("gpt-4o", assistantOptions); + + ThreadCreationOptions threadOptions = new() + { + InitialMessages = { RAGQuery } + }; + + ThreadRun threadRun = assistantClient.CreateThreadAndRun(assistant.Value.Id, threadOptions); + + do + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + threadRun = assistantClient.GetRun(threadRun.ThreadId, threadRun.Id); + } while (!threadRun.Status.IsTerminal); + + CollectionResult messages + = assistantClient.GetMessages(threadRun.ThreadId, + new MessageCollectionOptions() { Order = MessageCollectionOrder.Ascending }); + + var response = new StringBuilder(); + + foreach (var message in messages) + { + response.AppendLine($"[{message.Role.ToString().ToUpper()}]: "); + foreach (var contentItem in message.Content) + { + if (!string.IsNullOrEmpty(contentItem.Text)) + { + response.AppendLine(contentItem.Text); + + if (contentItem.TextAnnotations.Count > 0) + { + response.AppendLine(""); + } + } + } + + response.AppendLine(""); +#pragma warning restore OPENAI001 + } + + RAGResponse = response.ToString(); + + return Page(); + } + + public async Task OnPostImageGenerationAsync() + { + var client = _openAiClient.GetImageClient("dall-e-3"); + + var image = await client.GenerateImageAsync(ImagePrompt, new ImageGenerationOptions + { + ResponseFormat = GeneratedImageFormat.Bytes + }); + + var imageBytes = image.Value.ImageBytes; + + using var memoryStream = new MemoryStream(); + await imageBytes.ToStream().CopyToAsync(memoryStream); + GeneratedImageBytes = memoryStream.ToArray(); + + return Page(); + } + + public ReadOnlySpan GetExceptionHandlingDocumentContent() + { + return """ + # Exception Handling + + ABP provides a built-in infrastructure and offers a standard model for handling exceptions. + + * Automatically **handles all exceptions** and sends a standard **formatted error message** to the client for an API/AJAX request. + * Automatically hides **internal infrastructure errors** and returns a standard error message. + * Provides an easy and configurable way to **localize** exception messages. + * Automatically maps standard exceptions to **HTTP status codes** and provides a configurable option to map custom exceptions. + + ## Automatic Exception Handling + + `AbpExceptionFilter` handles an exception if **any of the following conditions** are met: + + * Exception is thrown by a **controller action** which returns an **object result** (not a view result). + * The request is an AJAX request (`X-Requested-With` HTTP header value is `XMLHttpRequest`). + * Client explicitly accepts the `application/json` content type (via `accept` HTTP header). + + If the exception is handled it's automatically **logged** and a formatted **JSON message** is returned to the client. + + ## Business Exceptions + + Most of your own exceptions will be business exceptions. The `IBusinessException` interface is used to mark an exception as a business exception. + + `BusinessException` implements the `IBusinessException` interface in addition to the `IHasErrorCode`, `IHasErrorDetails` and `IHasLogLevel` interfaces. The default log level is `Warning`. + + Usually you have an error code related to a particular business exception. For example: + + ````C# + throw new BusinessException(QaErrorCodes.CanNotVoteYourOwnAnswer); + ```` + + ### User Friendly Exception + + If an exception implements the `IUserFriendlyException` interface, then ABP does not change it's `Message` and `Details` properties and directly send it to the client. + + `UserFriendlyException` class is the built-in implementation of the `IUserFriendlyException` interface. Example usage: + + ````C# + throw new UserFriendlyException( + "Username should be unique!" + ); + ```` + * The `IUserFriendlyException` interface is derived from the `IBusinessException` and the `UserFriendlyException` class is derived from the `BusinessException` class. + + """u8; + } +} +``` + +## Running the Application + +After completing the setup, you can run the application using the following command: + +```bash + dotnet run --project ./src/Acme.OpenAIIntegration.Web +``` + +Once the application is running, open your browser and navigate to `/Sample`. You should see the `Sample` page we created, which contains sections for Chat, RAG (Retrieval-Augmented Generation), and Image Generation. You can find the screenshot of the page below: + +![sample page](sample-page.png) + +## Examples Overview + +To showcase the integration of the OpenAI API with the ABP Framework, we implemented three different examples: + +1. **Chat Example**: This example demonstrates how to use OpenAI's chat capabilities by allowing users to enter a message and receive an AI-generated response. The implementation involves setting up a simple form on the `Sample` page where users can input their message. The form submission triggers the `OnPostChatAsync` method, which uses the `IChatClient` to generate a response. + +![chat-example](chat-example.gif) + +2. **Retrieval-Augmented Generation (RAG) Example**: In this example, we use OpenAI to answer user queries by referencing custom documents uploaded to the OpenAI API. The implementation involves uploading a document using the `OpenAIFileClient` and creating an assistant with specific instructions to handle the uploaded content. In this case, the document is a section from ABP's Exception Handling documentation, which includes examples on how ABP handles exceptions, user-friendly error messages, and business exceptions. Users can input their query on the `Sample` page, and the `OnPostRAGAsync` method processes the query to generate precise answers based on the document content. If users ask questions that are not covered in the document, the assistant clearly indicates that the information is not available, as per the instructions provided. For example, when asked about `Object Extensions`, the response begins with: "The uploaded document does not contain information about `Object Extensions`...". This demonstrates how the assistant adheres to the provided instructions. You can also find this example illustrated in the GIF below. + +![rag-example-1](rag-example-1.gif) + +![rag-example-2](rag-example-2.gif) + +3. **Image Generation Example**: This example leverages the [DALL-E](https://openai.com/index/dall-e-3/) model to generate images based on user-provided prompts. On the `Sample` page, users can provide a description of the image they want to generate, and the `OnPostImageGenerationAsync` method uses the `OpenAIClient` to generate the image. + +![image-generation-example](image-generation-example.gif) + +## Conclusion + +In this article, we covered how to integrate the OpenAI API with the ABP Framework by creating a sample project, setting up the OpenAI services, and implementing examples for conversational AI, knowledge-based assistance, and image generation. By following these steps, you can add powerful AI-driven capabilities to your application, making it more interactive, intelligent, and capable of meeting user needs effectively. \ No newline at end of file diff --git a/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/chat-example.gif b/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/chat-example.gif new file mode 100644 index 0000000000..f67d54cecc Binary files /dev/null and b/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/chat-example.gif differ diff --git a/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/cover-image.png b/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/cover-image.png new file mode 100644 index 0000000000..b1cc16f0b5 Binary files /dev/null and b/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/cover-image.png differ diff --git a/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/image-generation-example.gif b/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/image-generation-example.gif new file mode 100644 index 0000000000..c415f95119 Binary files /dev/null and b/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/image-generation-example.gif differ diff --git a/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/rag-example-1.gif b/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/rag-example-1.gif new file mode 100644 index 0000000000..ca42bf3f61 Binary files /dev/null and b/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/rag-example-1.gif differ diff --git a/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/rag-example-2.gif b/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/rag-example-2.gif new file mode 100644 index 0000000000..5f03e453c6 Binary files /dev/null and b/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/rag-example-2.gif differ diff --git a/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/sample-page.png b/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/sample-page.png new file mode 100644 index 0000000000..211f63215d Binary files /dev/null and b/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/sample-page.png differ diff --git a/docs/en/Community-Articles/2024-12-09-Unit-Test/POST.md b/docs/en/Community-Articles/2024-12-09-Unit-Test/POST.md new file mode 100644 index 0000000000..7c1b74f3ea --- /dev/null +++ b/docs/en/Community-Articles/2024-12-09-Unit-Test/POST.md @@ -0,0 +1,234 @@ +# The new Unit Test structure in ABP application + +A typical ABP modular project usually consists of three main projects: `Application`, `Domain`, and `EntityFrameworkCore/MongoDB`. In these projects, we may provide many services that require unit testing. + +Using abstract unit test classes involves first writing tests in the `Application` and `Domain` layers that are independent of the storage technology, ensuring the correctness of core business logic. These abstract tests are then implemented in `EntityFrameworkCore` or `MongoDB`. The benefits of this approach include: + +1. **Reduced Coupling**: Core logic tests do not depend on specific storage technologies, so switching databases does not require rewriting test code. +2. **Better Isolation**: Focuses on verifying business logic correctness, avoiding interference from database operations. +3. **Increased Reusability**: The same abstract tests can be reused with different storage implementations. +4. **Easier Maintenance and Extensibility**: Different storage implementations can be extended independently without breaking existing tests. +5. **Faster and More Reliable Tests**: Reduces dependency on databases, making tests faster and more stable. + +## How to migrate old unit tests to the new unit test structure + +Assume our project name is `MyCompanyName.MyProjectName`. + +### Changes to the `MyCompanyName.MyProjectName.Application.Tests` project: + +1. Remove the `MyCompanyName.MyProjectName.Application.Tests` project's `MyProjectNameApplicationCollection` class. +2. Modify the `MyCompanyName.MyProjectName.Application.Tests` project's `MyProjectNameApplicationTestBase` class. + +```csharp +public abstract class MyProjectNameApplicationTestBase : MyProjectNameTestBase + where TStartupModule : IAbpModule +{ + //... +} +``` + +3. Modify the `MyCompanyName.MyProjectName.Application.Tests` project's unit test classes to become abstract unit test classes, such as: `SampleAppServiceTests`. + +```csharp +public abstract class SampleAppServiceTests : MyProjectNameApplicationTestBase + where TStartupModule : IAbpModule +{ + [Fact] + public async Task Initial_Data_Should_Contain_Admin_User() + { + //... + } +} +``` + +### Changes to the `MyCompanyName.MyProjectName.Domain.Tests` project: + +1. Remove the `MyCompanyName.MyProjectName.Domain.Tests` project's `MyProjectNameDomainCollection` class. +2. Modify the `MyCompanyName.MyProjectName.Domain.Tests` project's `MyProjectNameDomainTestBase` class. + +```csharp +public abstract class MyProjectNameDomainTestBase : MyProjectNameTestBase + where TStartupModule : IAbpModule +{ + //... +} +``` + +3. Modify the `MyCompanyName.MyProjectName.Domain.Tests` project's unit test classes to become abstract unit test classes, such as: `SampleDomainTests`. + +```csharp +public abstract class SampleDomainTests : MyProjectNameDomainTestBase + where TStartupModule : IAbpModule +{ + [Fact] + public async Task Should_Set_Email_Of_A_User() + { + //... + } +} +``` + +4. Modify the `MyCompanyName.MyProjectName.Domain.Tests` project's `csproj` and module class. Remove references to `EntityFrameworkCore/MongoDB`. + +`MyCompanyName.MyProjectName.Domain.Tests.csproj`: +```xml + + + //... + + + + + + + +``` + +`MyProjectNameDomainTestModule.cs`: + +```csharp +[DependsOn( + typeof(MyProjectNameDomainModule), + typeof(MyProjectNameTestBaseModule) +)] +public class MyProjectNameDomainTestModule : AbpModule +{ + //... +} +``` + +### Changes to the `MyCompanyName.MyProjectName.EntityFrameworkCore.Tests` project: + +Here, we need to create implementation classes for all abstract unit tests. + +```csharp +[Collection(MyProjectNameTestConsts.CollectionDefinitionName)] +public class EfCoreSampleAppServiceTests : SampleAppServiceTests +{ + //... +} +``` + +```csharp +[Collection(MyProjectNameTestConsts.CollectionDefinitionName)] +public class EfCoreSampleDomainTests : SampleDomainTests +{ + //... +} +``` + +We also need to modify the project's dependencies and module class, which should directly or indirectly reference the `Application` and `Domain` test projects. + +`MyCompanyName.MyProjectName.EntityFrameworkCore.Tests.csproj`: + +```xml + + + //... + + + + + + + +``` + +`MyProjectNameEntityFrameworkCoreTestModule.cs`: + +```csharp +[DependsOn( + typeof(MyProjectNameApplicationTestModule), + typeof(MyProjectNameEntityFrameworkCoreModule), + typeof(AbpEntityFrameworkCoreSqliteModule) + )] +public class MyProjectNameEntityFrameworkCoreTestModule : AbpModule +{ + //... +} +``` + +### Changes to the `MyCompanyName.MyProjectName.MongoDB.Tests` project (skip this step if not using MongoDB): + +Like the `EntityFrameworkCore` project, we need to create implementation classes for all abstract unit tests and modify the project's dependencies and module class. + +```csharp +[Collection(MyProjectNameTestConsts.CollectionDefinitionName)] +public class MongoDBSampleAppServiceTests : SampleAppServiceTests +{ + //... +} +``` + +```csharp +[Collection(MyProjectNameTestConsts.CollectionDefinitionName)] +public class MongoDBSampleDomainTests : SampleDomainTests +{ + //... +} +``` + +```xml + + + //... + + + + + + +``` + +```csharp +[DependsOn( + typeof(MyProjectNameApplicationTestModule), + typeof(MyProjectNameMongoDbModule) +)] +public class MyProjectNameMongoDbTestModule : AbpModule +{ + //... +} +``` + +### Changes to the `MyCompanyName.MyProjectName.Web.Tests` project: + +We need to reference the `EntityFrameworkCore/MongoDB` test projects in this test project. + +```xml + + + //... + + + + + + + + +``` + +```csharp +[DependsOn( + typeof(AbpAspNetCoreTestBaseModule), + typeof(MyProjectNameWebModule), + typeof(MyProjectNameApplicationTestModule), + typeof(MyProjectNameEntityFrameworkCoreTestModule) +)] +public class MyProjectNameWebTestModule : AbpModule +{ + //... +} +``` + +We no longer need the `MyProjectNameWebCollection` class in this project. Please delete it and use `[Collection(MyProjectNameTestConsts.CollectionDefinitionName)]` instead. + +## Conclusion + +This is our new unit test structure. Decoupling unit tests from storage technologies ensures the independence of business logic and allows easy switching between storage implementations. Abstract unit test classes improve test reusability, maintainability, and efficiency, reducing refactoring costs and providing flexibility for future tech updates. + +## References + +- [Unit Test](https://abp.io/docs/latest/testing/unit-tests) +- [Abstract all db-related unit tests](https://github.com/abpframework/abp/pull/17880) diff --git a/docs/en/cli/index.md b/docs/en/cli/index.md index 9297965bb4..645c12a715 100644 --- a/docs/en/cli/index.md +++ b/docs/en/cli/index.md @@ -112,7 +112,7 @@ abp cli clear-cache ### new -Generates a new solution based on the ABP [startup templates](../solution-templates). +Generates a new solution based on the ABP [startup templates](../solution-templates). See [new solution create sample commands](new-command-samples.md) Usage: @@ -211,6 +211,7 @@ For more samples, go to [ABP CLI Create Solution Samples](new-command-samples.md * `leptonx`: LeptonX Theme. * `basic`: Basic Theme. * `--public-website`: Public Website is a front-facing website for describing your project, listing your products and doing SEO for marketing purposes. Users can login and register on your website with this website. This option is only included in PRO templates. + * `--no-grafana-dashboard` or `-ngd`: Does not add example Grafana Dashboard to the solution. * `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory. * `--local-framework-ref` or `-lfr`: Uses local projects references to the ABP framework instead of using the NuGet packages. It tries to find the paths from `ide-state.json`. The file is located at `%UserProfile%\.abp\studio\ui\ide-state.json` (for Windows) and `~/.abp/studio/ui/ide-state.json` (for MAC). * `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder. @@ -225,15 +226,16 @@ For more samples, go to [ABP CLI Create Solution Samples](new-command-samples.md * `--dont-run-bundling`: Skip bundling for Blazor packages. * `--no-kubernetes-configuration` or `-nkc`: Skips the Kubernetes configuration files. * `--no-social-logins` or `-nsl`: Skipts the social login configuration. -* *Module Options*: You can skip some modules if you don't want to add them to your solution (*Available for* ***Team*** *or higher licenses*). Available commands: +* `--no-tests` or `-ntp`: Does not add test projects. +* *Module Options*: You can skip some modules if you don't want to add them to your solution, or include if you want them (*Available for* ***Team*** *or higher licenses*). Available commands: * `-no-saas`: Skips the Saas module. * `-no-gdpr`: Skips the GDPR module. * `-no-openiddict-admin-ui`: Skips the OpenIddict Admin UI module. * `-no-audit-logging`: Skips the Audit Logging module. - * `-no-file-management`: Skips the File Management module. * `-no-language-management`: Skips the Language Management module. * `-no-text-template-management`: Skips the Text Template Management module. - * `-no-chat`: Skips the Chat module. + * `-file-management`: Includes the File Management module. + * `-chat`: Includes the Chat module. * `--legacy`: Generates a legacy solution. * `trust-version`: Trusts the user's version and does not check if the version exists or not. If the template with the given version is found in the cache, it will be used, otherwise throws an exception. diff --git a/docs/en/cli/new-command-samples.md b/docs/en/cli/new-command-samples.md index a8d469b10b..5aa65f4d18 100644 --- a/docs/en/cli/new-command-samples.md +++ b/docs/en/cli/new-command-samples.md @@ -21,7 +21,7 @@ The following commands are for creating Angular UI projects: * **Entity Framework Core**, **custom connection string**, creates the project in a new folder: ```bash - abp new Acme.BookStore -u angular -csf --connection-string Server=localhost;Database=MyDatabase;Trusted_Connection=True + abp new Acme.BookStore -u angular -csf --connection-string "Server=localhost;Database=MyDatabase;Trusted_Connection=True" ``` * **MongoDB**, default app template, mobile project included, creates solution in `C:\MyProjects\Acme.BookStore` diff --git a/docs/en/contribution/index.md b/docs/en/contribution/index.md index 081f429b31..504078f09f 100644 --- a/docs/en/contribution/index.md +++ b/docs/en/contribution/index.md @@ -50,6 +50,12 @@ This is the recommended approach, since it automatically finds all missing texts If you want to make a change on a specific resource file, you can find the file yourself, make the necessary change (or create a new file for your language) and send a pull request on GitHub. +### Commercial Modules + +The commercial modules are not open source, and their localization files are not available in the public repository. The open-source module, `Account`, and the commercial module, `Account.Pro`, may have different translations. + +If you would like to translate a commercial module, please [create an issue](https://github.com/abpframework/abp/issues/new) on Github, and we will provide the necessary files (`abp-translation.json` for one or all modules). + ## Bug Report If you find any bug, please [create an issue on the Github repository](https://github.com/abpframework/abp/issues/new). diff --git a/docs/en/deployment/configuring-openIddict.md b/docs/en/deployment/configuring-openIddict.md index 30b8078098..03db14868b 100644 --- a/docs/en/deployment/configuring-openIddict.md +++ b/docs/en/deployment/configuring-openIddict.md @@ -4,6 +4,8 @@ This document introduces how to configure `OpenIddict` in the `AuthServer` proje There are different configurations in the `AuthServer` project for the `Development` and `Production` environments. +> If your solution does not include a project named `.AuthServer`, it means that you might have another project that depends on `AbpAccountPublicWebOpenIddictModule`. The project name can be `MyProject`, `MyProject.Web`, or `MyProject.HttpApi.Host`. They are both `Authentication Server` projects in that context. + ````csharp public override void PreConfigureServices(ServiceConfigurationContext context) { @@ -38,10 +40,14 @@ To avoid that, consider creating self-signed certificates and storing them in th You can use the `dotnet dev-certs https -v -ep openiddict.pfx -p 00000000-0000-0000-0000-000000000000` command to generate the `openiddict.pfx` certificate. +> `openiddict.pfx` is just an example of a filename. You can use any filename for the pfx file. + > `00000000-0000-0000-0000-000000000000` is the password of the certificate, you can change it to any password you want. > Also, please remember to copy `openiddict.pfx` to the [Content Root Folder](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.ihostingenvironment.contentrootpath?view=aspnetcore-7.0) of the `AuthServer` website. > It is recommended to use **two** RSA certificates, distinct from the certificate(s) used for HTTPS: one for encryption, one for signing. +> If you encounter a deployment error on IIS that says **File not found** even though the file exists, it is recommended to set the application pool’s advanced settings **Load User Profile** to **True** to resolve the issue. + For more information, please refer to: https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html#registering-a-certificate-recommended-for-production-ready-scenarios diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index d30b25dfb6..e7b7a4c3b7 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -5,7 +5,8 @@ "items": [ { "text": "Overview", - "path": "get-started" + "path": "get-started", + "isIndex": true }, { "text": "Single Layer Web Application", @@ -39,6 +40,10 @@ "path": "get-started/console.md" } ] + }, + { + "text": "Pre-Requirements", + "path": "get-started/pre-requirements.md" } ] }, @@ -47,14 +52,18 @@ "items": [ { "text": "Overview", - "path": "tutorials" + "path": "tutorials", + "isIndex": true }, { "text": "TODO Application", + "isLazyExpandable": true, + "path": "tutorials/todo", "items": [ { "text": "Overview", - "path": "tutorials/todo" + "path": "tutorials/todo", + "isIndex": true }, { "text": "Single-Layer Solution", @@ -68,10 +77,13 @@ }, { "text": "Book Store Application", + "isLazyExpandable": true, + "path": "tutorials/book-store", "items": [ { "text": "Overview", - "path": "tutorials/book-store" + "path": "tutorials/book-store", + "isIndex": true }, { "text": "1: Creating the Server Side", @@ -115,12 +127,47 @@ } ] }, + { + "text": "Book Store Application (with ABP Suite)", + "isLazyExpandable": true, + "path": "tutorials/book-store-with-abp-suite/index.md", + "items": [ + { + "text": "Overview", + "path": "tutorials/book-store-with-abp-suite", + "isIndex": true + }, + { + "text": "1: Creating the Solution", + "path": "tutorials/book-store-with-abp-suite/part-01.md" + }, + { + "text": "2: Creating the Books", + "path": "tutorials/book-store-with-abp-suite/part-02.md" + }, + { + "text": "3: Creating the Authors", + "path": "tutorials/book-store-with-abp-suite/part-03.md" + }, + { + "text": "4: Book to Author Relation", + "path": "tutorials/book-store-with-abp-suite/part-04.md" + }, + { + "text": "5: Customizing the Generated Code", + "path": "tutorials/book-store-with-abp-suite/part-05.md" + } + ] + }, { "text": "Modular Monolith Application", + "isLazyExpandable": true, + "path": "tutorials/modular-crm/index.md", "items": [ { "text": "Overview", - "path": "tutorials/modular-crm/index.md" + "path": "tutorials/modular-crm/index.md", + "isIndex": true }, { "text": "1: Creating the Initial Solution", @@ -156,6 +203,65 @@ } ] }, + { + "text": "Microservice Solution", + "isLazyExpandable": true, + "path": "tutorials/microservice/index.md", + "items": [ + { + "text": "Overview", + "path": "tutorials/microservice/index.md", + "isIndex": true + }, + { + "text": "1: Creating the initial solution", + "path": "tutorials/microservice/part-01.md" + }, + { + "text": "2: Creating the initial Catalog service", + "path": "tutorials/microservice/part-02.md" + }, + { + "text": "3: Building the Catalog service", + "path": "tutorials/microservice/part-03.md" + }, + { + "text": "4: Creating the initial Ordering service", + "path": "tutorials/microservice/part-04.md" + }, + { + "text": "5: Building the Ordering service", + "path": "tutorials/microservice/part-05.md" + }, + { + "text": "6: Integrating the services: HTTP API Calls", + "path": "tutorials/microservice/part-06.md" + }, + { + "text": "7: Integrating the services: Using Distributed Events", + "path": "tutorials/microservice/part-07.md" + } + ] + }, + { + "text": "Mobile Application Development", + "isLazyExpandable": true, + "path": "tutorials/mobile/index.md", + "items": [ + { + "text": "Overview", + "path": "tutorials/mobile/index.md" + }, + { + "text": "MAUI", + "path": "tutorials/mobile/maui/index.md" + }, + { + "text": "React Native", + "path": "tutorials/mobile/react-native/index.md" + } + ] + }, { "text": "Community Articles", "path": "https://abp.io/community" @@ -167,14 +273,16 @@ "items": [ { "text": "Overview", - "path": "tools.md" + "path": "tools.md", + "isIndex": true }, { "text": "ABP CLI", "items": [ { "text": "Overview", - "path": "cli" + "path": "cli", + "isIndex": true }, { "text": "New Solution Sample Commands", @@ -187,7 +295,8 @@ "items": [ { "text": "Overview", - "path": "studio" + "path": "studio", + "isIndex": true }, { "text": "Installation", @@ -198,7 +307,8 @@ "items": [ { "text": "Overview", - "path": "studio/overview.md" + "path": "studio/overview.md", + "isIndex": true }, { "text": "Solution Explorer", @@ -241,7 +351,8 @@ "items": [ { "text": "Overview", - "path": "suite" + "path": "suite", + "isIndex": true }, { "text": "How to Install", @@ -264,7 +375,8 @@ "items": [ { "text": "Overview", - "path": "suite/generating-crud-page.md" + "path": "suite/generating-crud-page.md", + "isIndex": true }, { "text": "Creating Many-To-Many Relationship", @@ -316,7 +428,8 @@ "items": [ { "text": "Overview", - "path": "framework/fundamentals" + "path": "framework/fundamentals", + "isIndex": true }, { "text": "Application Startup", @@ -327,7 +440,8 @@ "items": [ { "text": "Overview", - "path": "framework/fundamentals/authorization.md" + "path": "framework/fundamentals/authorization.md", + "isIndex": true }, { "text": "Dynamic Claims", @@ -340,7 +454,8 @@ "items": [ { "text": "Overview", - "path": "framework/fundamentals/caching.md" + "path": "framework/fundamentals/caching.md", + "isIndex": true }, { "text": "Redis Cache", @@ -361,7 +476,8 @@ "items": [ { "text": "Overview", - "path": "framework/fundamentals/dependency-injection.md" + "path": "framework/fundamentals/dependency-injection.md", + "isIndex": true }, { "text": "AutoFac Integration", @@ -394,7 +510,8 @@ "items": [ { "text": "Overview", - "path": "framework/fundamentals/validation.md" + "path": "framework/fundamentals/validation.md", + "isIndex": true }, { "text": "FluentValidation Integration", @@ -409,7 +526,8 @@ "items": [ { "text": "Overview", - "path": "framework/infrastructure" + "path": "framework/infrastructure", + "isIndex": true }, { "text": "Audit Logging", @@ -420,7 +538,8 @@ "items": [ { "text": "Overview", - "path": "framework/infrastructure/background-jobs" + "path": "framework/infrastructure/background-jobs", + "isIndex": true }, { "text": "Hangfire Integration", @@ -441,7 +560,8 @@ "items": [ { "text": "Overview", - "path": "framework/infrastructure/background-workers" + "path": "framework/infrastructure/background-workers", + "isIndex": true }, { "text": "Quartz Integration", @@ -458,7 +578,8 @@ "items": [ { "text": "Overview", - "path": "framework/infrastructure/blob-storing" + "path": "framework/infrastructure/blob-storing", + "isIndex": true }, { "text": "Storage Providers", @@ -532,7 +653,8 @@ "items": [ { "text": "Overview", - "path": "framework/infrastructure/emailing.md" + "path": "framework/infrastructure/emailing.md", + "isIndex": true }, { "text": "MailKit Integration", @@ -549,7 +671,8 @@ "items": [ { "text": "Overview", - "path": "framework/infrastructure/event-bus" + "path": "framework/infrastructure/event-bus", + "isIndex": true }, { "text": "Local Event Bus", @@ -560,7 +683,8 @@ "items": [ { "text": "Overview", - "path": "framework/infrastructure/event-bus/distributed" + "path": "framework/infrastructure/event-bus/distributed", + "isIndex": true }, { "text": "Azure Service Bus Integration", @@ -627,7 +751,8 @@ "items": [ { "text": "Overview", - "path": "framework/infrastructure/text-templating" + "path": "framework/infrastructure/text-templating", + "isIndex": true }, { "text": "Razor Integration", @@ -654,14 +779,16 @@ "items": [ { "text": "Overview", - "path": "framework/architecture" + "path": "framework/architecture", + "isIndex": true }, { "text": "Module Development Best Practices", "items": [ { "text": "Overview", - "path": "framework/architecture/best-practices" + "path": "framework/architecture/best-practices", + "isIndex": true }, { "text": "Module Architecture", @@ -672,7 +799,8 @@ "items": [ { "text": "Overview", - "path": "framework/architecture/best-practices/domain-layer-overview.md" + "path": "framework/architecture/best-practices/domain-layer-overview.md", + "isIndex": true }, { "text": "Entities", @@ -693,7 +821,8 @@ "items": [ { "text": "Overview", - "path": "framework/architecture/best-practices/application-layer-overview.md" + "path": "framework/architecture/best-practices/application-layer-overview.md", + "isIndex": true }, { "text": "Application Services", @@ -710,7 +839,8 @@ "items": [ { "text": "Overview", - "path": "framework/architecture/best-practices/data-access-overview.md" + "path": "framework/architecture/best-practices/data-access-overview.md", + "isIndex": true }, { "text": "Entity Framework Core Integration", @@ -729,7 +859,8 @@ "items": [ { "text": "Overview", - "path": "framework/architecture/modularity/basics.md" + "path": "framework/architecture/modularity/basics.md", + "isIndex": true }, { "text": "Plug-In Modules", @@ -740,7 +871,8 @@ "items": [ { "text": "Overview", - "path": "framework/architecture/modularity/extending/customizing-application-modules-guide.md" + "path": "framework/architecture/modularity/extending/customizing-application-modules-guide.md", + "isIndex": true }, { "text": "Module Entity Extension System", @@ -763,14 +895,16 @@ "items": [ { "text": "Overview", - "path": "framework/architecture/domain-driven-design" + "path": "framework/architecture/domain-driven-design", + "isIndex": true }, { "text": "Domain Layer", "items": [ { "text": "Overview", - "path": "framework/architecture/domain-driven-design/domain-layer.md" + "path": "framework/architecture/domain-driven-design/domain-layer.md", + "isIndex": true }, { "text": "Entities & Aggregate Roots", @@ -799,7 +933,8 @@ "items": [ { "text": "Overview", - "path": "framework/architecture/domain-driven-design/application-layer.md" + "path": "framework/architecture/domain-driven-design/application-layer.md", + "isIndex": true }, { "text": "Application Services", @@ -828,6 +963,74 @@ { "text": "Microservices", "path": "framework/architecture/microservices" + }, + { + "text": "Module Development Best Practices", + "items": [ + { + "text": "Overview", + "path": "framework/architecture/best-practices" + }, + { + "text": "Module Architecture", + "path": "framework/architecture/best-practices/module-architecture.md" + }, + { + "text": "Domain Layer", + "items": [ + { + "text": "Overview", + "path": "framework/architecture/best-practices/domain-layer-overview.md" + }, + { + "text": "Entities", + "path": "framework/architecture/best-practices/entities.md" + }, + { + "text": "Repositories", + "path": "framework/architecture/best-practices/repositories.md" + }, + { + "text": "Domain Services", + "path": "framework/architecture/best-practices/domain-services.md" + } + ] + }, + { + "text": "Application Layer", + "items": [ + { + "text": "Overview", + "path": "framework/architecture/best-practices/application-layer-overview.md" + }, + { + "text": "Application Services", + "path": "framework/architecture/best-practices/application-services.md" + }, + { + "text": "Data Transfer Objects", + "path": "framework/architecture/best-practices/data-transfer-objects.md" + } + ] + }, + { + "text": "Data Access", + "items": [ + { + "text": "Overview", + "path": "framework/architecture/best-practices/data-access-overview.md" + }, + { + "text": "Entity Framework Core Integration", + "path": "framework/architecture/best-practices/entity-framework-core-integration.md" + }, + { + "text": "MongoDB Integration", + "path": "framework/architecture/best-practices/mongodb-integration.md" + } + ] + } + ] } ] }, @@ -836,14 +1039,16 @@ "items": [ { "text": "Overview", - "path": "framework/api-development" + "path": "framework/api-development", + "isIndex": true }, { "text": "ABP Endpoints", "items": [ { "text": "Overview", - "path": "framework/api-development/standard-apis" + "path": "framework/api-development/standard-apis", + "isIndex": true }, { "text": "Application Configuration", @@ -886,14 +1091,16 @@ "items": [ { "text": "Overview", - "path": "framework/ui" + "path": "framework/ui", + "isIndex": true }, { "text": "MVC / Razor Pages", "items": [ { "text": "Overview", - "path": "framework/ui/mvc-razor-pages/overall.md" + "path": "framework/ui/mvc-razor-pages/overall.md", + "isIndex": true }, { "text": "Navigation / Menus", @@ -940,7 +1147,101 @@ "items": [ { "text": "Overview", - "path": "framework/ui/mvc-razor-pages/tag-helpers" + "path": "framework/ui/mvc-razor-pages/tag-helpers", + "isIndex": true + }, + { + "text": "Components", + "items": [ + { + "text": "Alerts", + "path": "framework/ui/mvc-razor-pages/tag-helpers/alerts.md" + }, + { + "text": "Badges", + "path": "framework/ui/mvc-razor-pages/tag-helpers/badges.md" + }, + { + "text": "Blockquote", + "path": "framework/ui/mvc-razor-pages/tag-helpers/blockquote.md" + }, + { + "text": "Borders", + "path": "framework/ui/mvc-razor-pages/tag-helpers/borders.md" + }, + { + "text": "Breadcrumbs", + "path": "framework/ui/mvc-razor-pages/tag-helpers/breadcrumbs.md" + }, + { + "text": "Button Groups", + "path": "framework/ui/mvc-razor-pages/tag-helpers/button-groups.md" + }, + { + "text": "Buttons", + "path": "framework/ui/mvc-razor-pages/tag-helpers/buttons.md" + }, + { + "text": "Cards", + "path": "framework/ui/mvc-razor-pages/tag-helpers/cards.md" + }, + { + "text": "Carousel", + "path": "framework/ui/mvc-razor-pages/tag-helpers/carousel.md" + }, + { + "text": "Collapse", + "path": "framework/ui/mvc-razor-pages/tag-helpers/collapse.md" + }, + { + "text": "Dropdowns", + "path": "framework/ui/mvc-razor-pages/tag-helpers/dropdowns.md" + }, + { + "text": "Figure", + "path": "framework/ui/mvc-razor-pages/tag-helpers/figure.md" + }, + { + "text": "Grids", + "path": "framework/ui/mvc-razor-pages/tag-helpers/grids.md" + }, + { + "text": "List Groups", + "path": "framework/ui/mvc-razor-pages/tag-helpers/list-groups.md" + }, + { + "text": "Modals", + "path": "framework/ui/mvc-razor-pages/tag-helpers/modals.md" + }, + { + "text": "Navs", + "path": "framework/ui/mvc-razor-pages/tag-helpers/navs.md" + }, + { + "text": "Paginator", + "path": "framework/ui/mvc-razor-pages/tag-helpers/paginator.md" + }, + { + "text": "Popovers", + "path": "framework/ui/mvc-razor-pages/tag-helpers/popovers.md" + }, + { + "text": "Progress Bars", + "path": "framework/ui/mvc-razor-pages/tag-helpers/progress-bars.md" + }, + { + "text": "Tables", + "path": "framework/ui/mvc-razor-pages/tag-helpers/tables.md" + }, + { + "text": "Tabs", + "path": "framework/ui/mvc-razor-pages/tag-helpers/tabs.md" + }, + { + "text": "Tooltips", + "path": "framework/ui/mvc-razor-pages/tag-helpers/tooltips.md" + } + ] }, { "text": "Form Elements", @@ -981,12 +1282,17 @@ "items": [ { "text": "Overview", - "path": "framework/ui/mvc-razor-pages/theming.md" + "path": "framework/ui/mvc-razor-pages/theming.md", + "isIndex": true }, { "text": "The Basic Theme", "path": "framework/ui/mvc-razor-pages/basic-theme.md" }, + { + "text": "LeptonX Lite", + "path": "ui-themes/lepton-x-lite/mvc.md" + }, { "text": "LeptonX", "path": "ui-themes/lepton-x/mvc.md" @@ -998,7 +1304,8 @@ "items": [ { "text": "Overview", - "path": "framework/ui/mvc-razor-pages/javascript-api" + "path": "framework/ui/mvc-razor-pages/javascript-api", + "isIndex": true }, { "text": "Localization", @@ -1059,7 +1366,8 @@ "items": [ { "text": "Overview", - "path": "framework/ui/mvc-razor-pages/customization-user-interface.md" + "path": "framework/ui/mvc-razor-pages/customization-user-interface.md", + "isIndex": true }, { "text": "Entity Action Extensions", @@ -1091,7 +1399,8 @@ "items": [ { "text": "Overview", - "path": "framework/ui/blazor/overall.md" + "path": "framework/ui/blazor/overall.md", + "isIndex": true }, { "text": "Navigation / Menu", @@ -1110,7 +1419,8 @@ "items": [ { "text": "Overview", - "path": "framework/ui/blazor/theming.md" + "path": "framework/ui/blazor/theming.md", + "isIndex": true }, { "text": "The Basic Theme", @@ -1232,7 +1542,8 @@ "items": [ { "text": "Overview", - "path": "framework/ui/angular/overview.md" + "path": "framework/ui/angular/overview.md", + "isIndex": true }, { "text": "Quick Start", @@ -1426,7 +1737,8 @@ "items": [ { "text": "Overview", - "path": "framework/ui/angular/theming.md" + "path": "framework/ui/angular/theming.md", + "isIndex": true }, { "text": "Configuration", @@ -1463,7 +1775,8 @@ "items": [ { "text": "Overview", - "path": "framework/ui/angular/extensions-overall.md" + "path": "framework/ui/angular/extensions-overall.md", + "isIndex": true }, { "text": "Entity Action Extensions", @@ -1525,7 +1838,8 @@ "items": [ { "text": "Overview", - "path": "framework/ui/react-native" + "path": "framework/ui/react-native", + "isIndex": true } ] }, @@ -1534,7 +1848,8 @@ "items": [ { "text": "Overview", - "path": "framework/ui/maui" + "path": "framework/ui/maui", + "isIndex": true } ] }, @@ -1563,14 +1878,16 @@ "items": [ { "text": "Overview", - "path": "framework/data" + "path": "framework/data", + "isIndex": true }, { "text": "Entity Framework Core", "items": [ { "text": "Overview", - "path": "framework/data/entity-framework-core" + "path": "framework/data/entity-framework-core", + "isIndex": true }, { "text": "Database Migrations", @@ -1640,7 +1957,8 @@ "items": [ { "text": "Overview", - "path": "solution-templates" + "path": "solution-templates", + "isIndex": true }, { "text": "Template Guide", @@ -1652,8 +1970,87 @@ }, { "text": "Layered Solution", + "isLazyExpandable": true, "path": "solution-templates/layered-web-application", "items": [ + { + "text": "Overview", + "path": "solution-templates/layered-web-application/overview.md" + }, + { + "text": "Solution Structure", + "path": "solution-templates/layered-web-application/solution-structure.md" + }, + { + "text": "Main Components", + "isLazyExpandable": true, + "path": "solution-templates/layered-web-application/main-components.md", + "items": [ + { + "text": "Web Applications", + "path": "solution-templates/layered-web-application/web-applications.md" + }, + { + "text": "Db Migrator", + "path": "solution-templates/layered-web-application/db-migrator.md" + }, + { + "text": "Mobile Applications", + "path": "solution-templates/layered-web-application/mobile-applications.md" + } + ] + }, + { + "text": "Built-In Features", + "isLazyExpandable": true, + "path": "solution-templates/layered-web-application/built-in-features.md", + "items": [ + { + "text": "Authentication", + "path": "solution-templates/layered-web-application/authentication.md" + }, + { + "text": "Database configurations", + "path": "solution-templates/layered-web-application/database-configurations.md" + }, + { + "text": "Logging (with Serilog)", + "path": "solution-templates/layered-web-application/logging.md" + }, + { + "text": "Swagger integration", + "path": "solution-templates/layered-web-application/swagger-integration.md" + }, + { + "text": "Background Jobs", + "path": "solution-templates/layered-web-application/background-jobs.md" + }, + { + "text": "Background Workers", + "path": "solution-templates/layered-web-application/background-workers.md" + }, + { + "text": "Distributed Locking", + "path": "solution-templates/layered-web-application/distributed-locking.md" + }, + { + "text": "Multi-Tenancy", + "path": "solution-templates/layered-web-application/multi-tenancy.md" + }, + { + "text": "BLOB Storing", + "path": "solution-templates/layered-web-application/blob-storing.md" + }, + { + "text": "CORS configuration", + "path": "solution-templates/layered-web-application/cors-configuration.md" + } + ] + }, + { + "text": "Helm Charts and Kubernetes", + "path": "solution-templates/layered-web-application/helm-charts-and-kubernetes.md" + }, { "text": "Deployment", "path": "solution-templates/layered-web-application/deployment", @@ -1701,7 +2098,170 @@ }, { "text": "Microservice Solution", - "path": "solution-templates/microservice" + "isLazyExpandable": true, + "path": "solution-templates/microservice", + "items":[ + { + "text": "Overview", + "path": "solution-templates/microservice" + }, + { + "text": "Solution Structure", + "path": "solution-templates/microservice/solution-structure.md" + }, + { + "text": "Main Components", + "items": [ + { + "text": "Overview", + "path": "solution-templates/microservice/main-components" + }, + { + "text": "Microservices", + "path": "solution-templates/microservice/microservices.md" + }, + { + "text": "API Gateways", + "path": "solution-templates/microservice/api-gateways.md" + }, + { + "text": "Web Applications", + "path": "solution-templates/microservice/web-applications.md" + }, + { + "text": "Mobile Applications", + "path": "solution-templates/microservice/mobile-applications.md" + } + ] + }, + { + "text": "Built-In Features", + "items": [ + { + "text": "Overview", + "path": "solution-templates/microservice/built-in-features.md" + }, + { + "text": "Authentication", + "path": "solution-templates/microservice/authentication.md" + }, + { + "text": "Database configurations", + "path": "solution-templates/microservice/database-configurations.md" + }, + { + "text": "Logging (with Serilog and Elasticsearch)", + "path": "solution-templates/microservice/logging.md" + }, + { + "text": "Monitoring (with Prometheus and Grafana)", + "path": "solution-templates/microservice/monitoring.md" + }, + { + "text": "Swagger integration", + "path": "solution-templates/microservice/swagger.md" + }, + { + "text": "Permission management", + "path": "solution-templates/microservice/permission-management.md" + }, + { + "text": "Feature management", + "path": "solution-templates/microservice/feature-management.md" + }, + { + "text": "Localization system", + "path": "solution-templates/microservice/localization-system.md" + }, + { + "text": "Background Jobs", + "path": "solution-templates/microservice/background-jobs.md" + }, + { + "text": "Background Workers", + "path": "solution-templates/microservice/background-workers.md" + }, + { + "text": "Distributed Locking", + "path": "solution-templates/microservice/distributed-locking.md" + }, + { + "text": "Distributed Cache", + "path": "solution-templates/microservice/distributed-cache.md" + }, + { + "text": "Multi-Tenancy", + "path": "solution-templates/microservice/multi-tenancy.md" + }, + { + "text": "BLOB Storing", + "path": "solution-templates/microservice/blob-storing.md" + }, + { + "text": "CORS configuration", + "path": "solution-templates/microservice/cors-configuration.md" + } + ] + }, + { + "text": "Communication", + "items":[ + { + "text": "Overview", + "path": "solution-templates/microservice/communication.md" + }, + { + "text": "HTTP API Calls", + "path": "solution-templates/microservice/http-api-calls.md" + }, + { + "text": "gRPC Calls", + "path": "solution-templates/microservice/grpc-calls.md" + }, + { + "text": "Distributed Events", + "path": "solution-templates/microservice/distributed-events.md" + } + ] + }, + { + "text": "Helm Charts and Kubernetes", + "path": "solution-templates/microservice/helm-charts-and-kubernetes.md" + }, + { + "text": "Guides", + "items": [ + { + "text": "Overview", + "path": "solution-templates/microservice/guides.md" + }, + { + "text": "Adding new microservices", + "path": "solution-templates/microservice/adding-new-microservices.md" + }, + { + "text": "Adding new applications", + "path": "solution-templates/microservice/adding-new-applications.md" + }, + { + "text": "Adding new API gateways", + "path": "solution-templates/microservice/adding-new-api-gateways.md" + }, + { + "text": "Mono-repo vs multiple repository approaches", + "path": "solution-templates/microservice/mono-repo-vs-multiple-repository-approaches.md" + }, + { + "text": "Authoring unit and integration tests", + "path": "solution-templates/microservice/authoring-unit-and-integration-tests.md" + }, + { + "text": "How to use with ABP Suite", + "path": "solution-templates/microservice/how-to-use-with-abp-suite.md" + } + ] + } + ] }, { "text": "Application Module", @@ -1714,7 +2274,8 @@ "items": [ { "text": "Overview", - "path": "modules" + "path": "modules", + "isIndex": true }, { "text": "Account", @@ -1725,7 +2286,8 @@ "items": [ { "text": "Overview", - "path": "modules/account-pro.md" + "path": "modules/account-pro.md", + "isIndex": true }, { "text": "Tenant impersonation & User impersonation", @@ -1787,10 +2349,13 @@ }, { "text": "IdentityServer", + "isLazyExpandable": true, + "path": "modules/identity-server.md", "items": [ { "text": "Overview", - "path": "modules/identity-server.md" + "path": "modules/identity-server.md", + "isIndex": true }, { "text": "IdentityServer Migration Guide", @@ -1808,10 +2373,13 @@ }, { "text": "OpenIddict", + "isLazyExpandable": true, + "path": "modules/openiddict.md", "items": [ { "text": "Overview", - "path": "modules/openiddict.md" + "path": "modules/openiddict.md", + "isIndex": true }, { "text": "OpenIddict Migration Guide", @@ -1871,11 +2439,16 @@ "items": [ { "text": "Overview", - "path": "ui-themes" + "path": "ui-themes", + "isIndex": true }, { "text": "The Basic Theme", - "path": "framework/ui/mvc-razor-pages/basic-theme.md" + "path": "ui-themes/basic-theme" + }, + { + "text": "LeptonX Lite", + "path": "ui-themes/lepton-x-lite" }, { "text": "LeptonX Theme", @@ -1888,7 +2461,8 @@ "items": [ { "text": "Overview", - "path": "testing/overall.md" + "path": "testing/overall.md", + "isIndex": true }, { "text": "Unit tests", @@ -1909,7 +2483,8 @@ "items": [ { "text": "Overview", - "path": "deployment" + "path": "deployment", + "isIndex": true }, { "text": "Configuring SSL certificate(HTTPS)", @@ -1942,7 +2517,8 @@ "items": [ { "text": "Overview", - "path": "samples" + "path": "samples", + "isIndex": true }, { "text": "EventHub", @@ -1967,7 +2543,8 @@ "items": [ { "text": "Overview", - "path": "https://abp.io/books" + "path": "https://abp.io/books", + "isIndex": true }, { "text": "Mastering ABP Framework", @@ -1984,7 +2561,8 @@ "items": [ { "text": "Overview", - "path": "release-info" + "path": "release-info", + "isIndex": true }, { "text": "Release Notes", @@ -2035,6 +2613,10 @@ "text": "Free (Open Source) License vs Commercial (Pro) Licenses", "path": "others/free-licenses-vs-pro-licenses.md" }, + { + "text": "Knowledge Base", + "path": "kb/index.md" + }, { "text": "Penetration Test Report", "path": "others/penetration-test-report.md" diff --git a/docs/en/docs-params.json b/docs/en/docs-params.json index e108f1b554..4aa720e441 100644 --- a/docs/en/docs-params.json +++ b/docs/en/docs-params.json @@ -8,6 +8,7 @@ "Blazor": "Blazor WebAssembly", "BlazorServer": "Blazor Server", "BlazorWebApp": "Blazor WebApp", + "MAUIBlazor": "MAUI Blazor (Hybrid)", "NG": "Angular" } }, diff --git a/docs/en/framework/architecture/best-practices/application-services.md b/docs/en/framework/architecture/best-practices/application-services.md index d3ad44ca54..efe937e956 100644 --- a/docs/en/framework/architecture/best-practices/application-services.md +++ b/docs/en/framework/architecture/best-practices/application-services.md @@ -1,8 +1,14 @@ # Application Services Best Practices & Conventions +> This document offers best practices for implementing Application Services classes in your modules and applications based on Domain-Driven-Design principles. +> +> **Ensure you've read the [*Application Services*](../domain-driven-design/application-services.md) document first.** + +## General + * **Do** create an application service for each **aggregate root**. -### Application Service Interface +## Application Service Interface * **Do** define an `interface` for each application service in the **application contracts** package. * **Do** inherit from the `IApplicationService` interface. @@ -11,11 +17,11 @@ * **Do not** get/return entities for the service methods. * **Do** define DTOs based on the [DTO best practices](data-transfer-objects.md). -#### Outputs +### Outputs * **Avoid** to define too many output DTOs for same or related entities. Instead, define a **basic** and a **detailed** DTO for an entity. -##### Basic DTO +#### Basic DTO **Do** define a **basic** DTO for an aggregate root. @@ -44,7 +50,7 @@ public class IssueLabelDto } ``` -##### Detailed DTO +#### Detailed DTO **Do** define a **detailed** DTO for an entity if it has reference(s) to other aggregate roots. @@ -81,20 +87,20 @@ public class LabelDto : ExtensibleEntityDto } ```` -#### Inputs +### Inputs * **Do not** define any property in an input DTO that is not used in the service class. * **Do not** share input DTOs between application service methods. * **Do not** inherit an input DTO class from another one. * **May** inherit from an abstract base DTO class and share some properties between different DTOs in that way. However, should be very careful in that case because manipulating the base DTO would effect all related DTOs and service methods. Avoid from that as a good practice. -#### Methods +### Methods * **Do** define service methods as asynchronous with **Async** postfix. * **Do not** repeat the entity name in the method names. * Example: Define `GetAsync(...)` instead of `GetProductAsync(...)` in the `IProductAppService`. -##### Getting A Single Entity +#### Getting A Single Entity * **Do** use the `GetAsync` **method name**. * **Do** get Id with a **primitive** method parameter. @@ -104,7 +110,7 @@ public class LabelDto : ExtensibleEntityDto Task GetAsync(Guid id); ```` -##### Getting A List Of Entities +#### Getting A List Of Entities * **Do** use the `GetListAsync` **method name**. * **Do** get a single DTO argument for **filtering**, **sorting** and **paging** if necessary. @@ -117,7 +123,7 @@ Task GetAsync(Guid id); Task> GetListAsync(QuestionListQueryDto queryDto); ```` -##### Creating A New Entity +#### Creating A New Entity * **Do** use the `CreateAsync` **method name**. * **Do** get a **specialized input** DTO to create the entity. @@ -151,7 +157,7 @@ public class CreateQuestionDto : ExtensibleObject } ```` -##### Updating An Existing Entity +#### Updating An Existing Entity - **Do** use the `UpdateAsync` **method name**. - **Do** get a **specialized input** DTO to update the entity. @@ -167,7 +173,7 @@ Example: Task UpdateAsync(Guid id, UpdateQuestionDto updateQuestionDto); ```` -##### Deleting An Existing Entity +#### Deleting An Existing Entity - **Do** use the `DeleteAsync` **method name**. - **Do** get Id with a **primitive** method parameter. Example: @@ -176,7 +182,7 @@ Task UpdateAsync(Guid id, UpdateQuestionDto updateQuesti Task DeleteAsync(Guid id); ```` -##### Other Methods +#### Other Methods * **Can** define additional methods to perform operations on the entity. Example: @@ -186,7 +192,7 @@ Task VoteAsync(Guid id, VoteType type); This method votes a question and returns the current score of the question. -### Application Service Implementation +## Application Service Implementation * **Do** develop the application layer **completely independent from the web layer**. * **Do** implement application service interfaces in the **application layer**. @@ -195,30 +201,30 @@ This method votes a question and returns the current score of the question. * **Do** make all public methods **virtual**, so developers may inherit and override them. * **Do not** make **private** methods. Instead make them **protected virtual**, so developers may inherit and override them. -#### Using Repositories +### Using Repositories * **Do** use the specifically designed repositories (like `IProductRepository`). * **Do not** use generic repositories (like `IRepository`). -#### Querying Data +### Querying Data * **Do not** use LINQ/SQL for querying data from database inside the application service methods. It's repository's responsibility to perform LINQ/SQL queries from the data source. -#### Extra Properties +### Extra Properties * **Do** use either `MapExtraPropertiesTo` extension method ([see](../../fundamentals/object-extensions.md)) or configure the object mapper (`MapExtraProperties`) to allow application developers to be able to extend the objects and services. -#### Manipulating / Deleting Entities +### Manipulating / Deleting Entities * **Do** always get all the related entities from repositories to perform the operations on them. * **Do** call repository's Update/UpdateAsync method after updating an entity. Because, not all database APIs support change tracking & auto update. -#### Handle files +### Handle files * **Do not** use any web components like `IFormFile` or `Stream` in the application services. If you want to serve a file you can use `byte[]`. * **Do** use a `Controller` to handle file uploading then pass the `byte[]` of the file to the application service method. -#### Using Other Application Services +### Using Other Application Services * **Do not** use other application services of the same module/application. Instead; * Use domain layer to perform the required task. diff --git a/docs/en/framework/architecture/best-practices/data-transfer-objects.md b/docs/en/framework/architecture/best-practices/data-transfer-objects.md index e4caa6ed66..96194b4adb 100644 --- a/docs/en/framework/architecture/best-practices/data-transfer-objects.md +++ b/docs/en/framework/architecture/best-practices/data-transfer-objects.md @@ -1,5 +1,11 @@ # Data Transfer Objects Best Practices & Conventions +> This document offers best practices for implementing Data Transfer Object classes in your modules and applications based on Domain-Driven-Design principles. +> +> **Ensure you've read the [*Data Transfer Objects*](../domain-driven-design/data-transfer-objects.md) document first.** + +## General + * **Do** define DTOs in the **application contracts** package. * **Do** inherit from the pre-built **base DTO classes** where possible and necessary (like `EntityDto`, `CreationAuditedEntityDto`, `AuditedEntityDto`, `FullAuditedEntityDto` and so on). * **Do** inherit from the **extensible DTO** classes for the **aggregate roots** (like `ExtensibleAuditedEntityDto`), because aggregate roots are extensible objects and extra properties are mapped to DTOs in this way. diff --git a/docs/en/framework/architecture/best-practices/domain-services.md b/docs/en/framework/architecture/best-practices/domain-services.md index 565a67a715..55c731d207 100644 --- a/docs/en/framework/architecture/best-practices/domain-services.md +++ b/docs/en/framework/architecture/best-practices/domain-services.md @@ -1,6 +1,10 @@ # Domain Services Best Practices & Conventions -### Domain Service +> This document offers best practices for implementing Domain Service classes in your modules and applications based on Domain-Driven-Design principles. +> +> **Ensure you've read the [*Domain Services*](../domain-driven-design/domain-services.md) document first.** + +## Domain Services - **Do** define domain services in the **domain layer**. - **Do not** create interfaces for the domain services **unless** you have a good reason to (like mock and test different implementations). @@ -14,7 +18,7 @@ public class IssueManager : DomainService } ``` -### Domain Service Methods +## Domain Service Methods - **Do not** define `GET` methods. `GET` methods do not change the state of an entity. Hence, use the repository directly in the Application Service instead of Domain Service method. @@ -57,8 +61,6 @@ public async Task AssignToAsync(Issue issue, IdentityUser user) - **Do not** return `DTO`. Return only domain objects when you need. - **Do not** involve authenticated user logic. Instead, define extra parameter and send the related data of ` CurrentUser` from the Application Service layer. - - ## See Also * [Video tutorial](https://abp.io/video-courses/essentials/domain-services) diff --git a/docs/en/framework/architecture/best-practices/entities.md b/docs/en/framework/architecture/best-practices/entities.md index 319f1648ff..cc4b0e2a21 100644 --- a/docs/en/framework/architecture/best-practices/entities.md +++ b/docs/en/framework/architecture/best-practices/entities.md @@ -1,12 +1,16 @@ # Entity Best Practices & Conventions -### Entities +> This document offers best practices for implementing Aggregate Root and Entity classes in your modules and applications based on Domain-Driven-Design principles. +> +> **Ensure you've read the [*Entities*](../domain-driven-design/entities.md) document first.** + +## Entities Every aggregate root is also an entity. So, these rules are valid for aggregate roots too unless aggregate root rules override them. - **Do** define entities in the **domain layer**. -#### Primary Constructor +### Primary Constructor * **Do** define a **primary constructor** that ensures the validity of the entity on creation. Primary constructors are used to create a new instance of the entity by the application code. @@ -14,15 +18,15 @@ Every aggregate root is also an entity. So, these rules are valid for aggregate - **Do** always initialize sub collections in the primary constructor. - **Do not** generate `Guid` keys inside the constructor. Get it as a parameter, so the calling code will use `IGuidGenerator` to generate a new `Guid` value. -#### Parameterless Constructor +### Parameterless Constructor - **Do** always define a `protected` parameterless constructor to be compatible with ORMs. -#### References +### References - **Do** always **reference** to other aggregate roots **by Id**. Never add navigation properties to other aggregate roots. -#### Other Class Members +### Other Class Members - **Do** always define properties and methods as `virtual` (except `private` methods, obviously). Because some ORMs and dynamic proxy tools require it. - **Do** keep the entity as always **valid** and **consistent** within its own boundary. @@ -30,27 +34,27 @@ Every aggregate root is also an entity. So, these rules are valid for aggregate - **Do** define `public `, `internal` or `protected internal` (virtual) **methods** to change the properties (with non-public setters) if necessary. - **Do** return the entity object (`this`) from the setter methods. -### Aggregate Roots +## Aggregate Roots -#### Primary Keys +### Primary Keys * **Do** always use a **Id** property for the aggregate root key. * **Do not** use **composite keys** for aggregate roots. * **Do** use **Guid** as the **primary key** of all aggregate roots. -#### Base Class +### Base Class * **Do** inherit from the `AggregateRoot` or one of the audited classes (`CreationAuditedAggregateRoot`, `AuditedAggregateRoot` or `FullAuditedAggregateRoot`) based on requirements. -#### Aggregate Boundary +### Aggregate Boundary * **Do** keep aggregates **as small as possible**. Most of the aggregates will only have primitive properties and will not have sub collections. Consider these as design decisions: * **Performance** & **memory** cost of loading & saving aggregates (keep in mind that an aggregate is normally loaded & saved as a single unit). Larger aggregates will consume more CPU & memory. * **Consistency** & **validity** boundary. -### Example +## Example -#### Aggregate Root +### Aggregate Root ````C# public class Issue : FullAuditedAggregateRoot //Using Guid as the key/identifier @@ -130,7 +134,7 @@ public class Issue : FullAuditedAggregateRoot //Using Guid as the key/iden } ```` -#### The Entity +### Entity ````C# public class IssueLabel : Entity @@ -151,11 +155,12 @@ public class IssueLabel : Entity } ```` -### References +## References * Effective Aggregate Design by Vaughn Vernon http://dddcommunity.org/library/vernon_2011 - ## See Also + +## See Also * [Video tutorial](https://abp.io/video-courses/essentials/entities) \ No newline at end of file diff --git a/docs/en/framework/architecture/best-practices/entity-framework-core-integration.md b/docs/en/framework/architecture/best-practices/entity-framework-core-integration.md index 9a2081e4e5..960e0e69e4 100644 --- a/docs/en/framework/architecture/best-practices/entity-framework-core-integration.md +++ b/docs/en/framework/architecture/best-practices/entity-framework-core-integration.md @@ -1,12 +1,16 @@ # Entity Framework Core Integration Best Practices -> See [Entity Framework Core Integration document](../../data/entity-framework-core) for the basics of the EF Core integration. +> This document offers best practices for implementing Entity Framework Core integration in your modules and applications. +> +> **Ensure you've read the [*Entity Framework Core Integration*](../../data/entity-framework-core/index.md) document first.** + +## General - **Do** define a separated `DbContext` interface and class for each module. - **Do not** rely on lazy loading on the application development. - **Do not** enable lazy loading for the `DbContext`. -### DbContext Interface +## DbContext Interface - **Do** define an **interface** for the `DbContext` that inherits from `IEfCoreDbContext`. - **Do** add a `ConnectionStringName` **attribute** to the `DbContext` interface. @@ -23,7 +27,7 @@ public interface IIdentityDbContext : IEfCoreDbContext * **Do not** define `set;` for the properties in this interface. -### DbContext class +## DbContext class * **Do** inherit the `DbContext` from the `AbpDbContext` class. * **Do** add a `ConnectionStringName` attribute to the `DbContext` class. @@ -46,7 +50,7 @@ public class IdentityDbContext : AbpDbContext, IIdentityDbCon } ```` -### Table Prefix and Schema +## Table Prefix and Schema - **Do** add static `TablePrefix` and `Schema` **properties** to the `DbContext` class. Set default value from a constant. Example: @@ -58,7 +62,7 @@ public static string Schema { get; set; } = AbpIdentityConsts.DefaultDbSchema; - **Do** always use a short `TablePrefix` value for a module to create **unique table names** in a shared database. `Abp` table prefix is reserved for ABP core modules. - **Do** set `Schema` to `null` as default. -### Model Mapping +## Model Mapping - **Do** explicitly **configure all entities** by overriding the `OnModelCreating` method of the `DbContext`. Example: @@ -100,7 +104,7 @@ public static class IdentityDbContextModelBuilderExtensions * **Do** call `b.ConfigureByConvention();` for each entity mapping (as shown above). -### Repository Implementation +## Repository Implementation - **Do** **inherit** the repository from the `EfCoreRepository` class and implement the corresponding repository interface. Example: @@ -168,7 +172,7 @@ public override async Task> WithDetailsAsync() } ```` -### Module Class +## Module Class - **Do** define a module class for the Entity Framework Core integration package. - **Do** add `DbContext` to the `IServiceCollection` using the `AddAbpDbContext` method. diff --git a/docs/en/framework/architecture/best-practices/module-architecture.md b/docs/en/framework/architecture/best-practices/module-architecture.md index d050094656..c168b77f87 100644 --- a/docs/en/framework/architecture/best-practices/module-architecture.md +++ b/docs/en/framework/architecture/best-practices/module-architecture.md @@ -1,13 +1,13 @@ # Module Architecture Best Practices & Conventions -### Solution Structure +## Solution Structure * **Do** create a separated Visual Studio solution for every module. * **Do** name the solution as *CompanyName.ModuleName* (for core ABP modules, it's *Volo.Abp.ModuleName*). * **Do** develop the module as layered, so it has several packages (projects) those are related to each other. * Every package has its own module definition file and explicitly declares the dependencies for the depended packages/modules. -### Layers & Packages +## Layers & Packages The following diagram shows the packages of a well-layered module and dependencies of those packages between them: diff --git a/docs/en/framework/architecture/best-practices/mongodb-integration.md b/docs/en/framework/architecture/best-practices/mongodb-integration.md index 36b037c967..1930984e2e 100644 --- a/docs/en/framework/architecture/best-practices/mongodb-integration.md +++ b/docs/en/framework/architecture/best-practices/mongodb-integration.md @@ -1,8 +1,14 @@ # MongoDB Integration +> This document offers best practices for implementing MongoDB integration in your modules and applications. +> +> **Ensure you've read the [*MongoDB Integration*](../../data/entity-framework-core/index.md) document first.** + +## General + * Do define a separated `MongoDbContext` interface and class for each module. -### MongoDbContext Interface +## MongoDbContext Interface - **Do** define an **interface** for the `MongoDbContext` that inherits from `IAbpMongoDbContext`. - **Do** add a `ConnectionStringName` **attribute** to the `MongoDbContext` interface. @@ -17,7 +23,7 @@ public interface IAbpIdentityMongoDbContext : IAbpMongoDbContext } ```` -### MongoDbContext class +## MongoDbContext class - **Do** inherit the `MongoDbContext` from the `AbpMongoDbContext` class. - **Do** add a `ConnectionStringName` attribute to the `MongoDbContext` class. @@ -34,7 +40,7 @@ public class AbpIdentityMongoDbContext : AbpMongoDbContext, IAbpIdentityMongoDbC } ``` -### Collection Prefix +## Collection Prefix - **Do** add static `CollectionPrefix` **property** to the `DbContext` class. Set default value from a constant. Example: @@ -46,7 +52,7 @@ Used the same constant defined for the EF Core integration table prefix in this - **Do** always use a short `CollectionPrefix` value for a module to create **unique collection names** in a shared database. `Abp` collection prefix is reserved for ABP core modules. -### Collection Mapping +## Collection Mapping - **Do** explicitly **configure all aggregate roots** by overriding the `CreateModel` method of the `MongoDbContext`. Example: @@ -83,7 +89,7 @@ public static class AbpIdentityMongoDbContextExtensions } ``` -### Repository Implementation +## Repository Implementation - **Do** **inherit** the repository from the `MongoDbRepository` class and implement the corresponding repository interface. Example: @@ -124,7 +130,7 @@ public async Task FindByNormalizedUserNameAsync( * Using `IQueryable` makes the code as much as similar to the EF Core repository implementation and easy to write and read. * **Do** implement data filtering if it is not possible to use the `GetMongoQueryable()` method. -### Module Class +## Module Class - **Do** define a module class for the MongoDB integration package. - **Do** add `MongoDbContext` to the `IServiceCollection` using the `AddMongoDbContext` method. diff --git a/docs/en/framework/architecture/best-practices/repositories.md b/docs/en/framework/architecture/best-practices/repositories.md index 5e491ecb80..227c620f45 100644 --- a/docs/en/framework/architecture/best-practices/repositories.md +++ b/docs/en/framework/architecture/best-practices/repositories.md @@ -1,6 +1,10 @@ # Repository Best Practices & Conventions -### Repository Interfaces +> This document offers best practices for implementing Repository classes in your modules and applications based on Domain-Driven-Design principles. +> +> **Ensure you've read the [*Repositories*](../domain-driven-design/repositories.md) document first.** + +## Repository Interfaces * **Do** define repository interfaces in the **domain layer**. * **Do** define a repository interface (like `IIdentityUserRepository`) and create its corresponding implementations for **each aggregate root**. @@ -30,7 +34,7 @@ public interface IIdentityUserRepository : IBasicRepository * **Do** inherit the repository interface from `IBasicRepository` (as normally) or a lower-featured interface, like `IReadOnlyRepository` (if it's needed). * **Do not** define repositories for entities those are **not aggregate roots**. -### Repository Methods +## Repository Methods * **Do** define all repository methods as **asynchronous**. * **Do** add an **optional** `cancellationToken` parameter to every method of the repository. Example: @@ -68,7 +72,7 @@ Task> GetListByNormalizedRoleNameAsync( * **Avoid** to create projection classes for entities to get less property of an entity from the repository. Example: Avoid to create BasicUserView class to select a few properties needed for the use case needs. Instead, directly use the aggregate root class. However, there may be some exceptions for this rule, where: * Performance is so critical for the use case and getting the whole aggregate root highly impacts the performance. -### See Also +## See Also * [Entity Framework Core Integration](./entity-framework-core-integration.md) * [MongoDB Integration](./mongodb-integration.md) diff --git a/docs/en/framework/architecture/domain-driven-design/data-transfer-objects.md b/docs/en/framework/architecture/domain-driven-design/data-transfer-objects.md index e33e7895ca..8816411a5b 100644 --- a/docs/en/framework/architecture/domain-driven-design/data-transfer-objects.md +++ b/docs/en/framework/architecture/domain-driven-design/data-transfer-objects.md @@ -1,7 +1,5 @@ # Data Transfer Objects -## Introduction - **Data Transfer Objects** (DTO) are used to transfer data between the **Application Layer** and the **Presentation Layer** or other type of clients. Typically, an [application service](./application-services.md) is called from the presentation layer (optionally) with a **DTO** as the parameter. It uses domain objects to **perform some specific business logic** and (optionally) returns a DTO back to the presentation layer. Thus, the presentation layer is completely **isolated** from domain layer. diff --git a/docs/en/framework/architecture/domain-driven-design/domain-services.md b/docs/en/framework/architecture/domain-driven-design/domain-services.md index 358e850527..36bd732a3c 100644 --- a/docs/en/framework/architecture/domain-driven-design/domain-services.md +++ b/docs/en/framework/architecture/domain-driven-design/domain-services.md @@ -1,7 +1,5 @@ # Domain Services -## Introduction - In a [Domain Driven Design](../domain-driven-design) (DDD) solution, the core business logic is generally implemented in aggregates ([entities](./entities.md)) and the Domain Services. Creating a Domain Service is especially needed when; * You implement a core domain logic that depends on some services (like repositories or other external services). diff --git a/docs/en/framework/fundamentals/caching.md b/docs/en/framework/fundamentals/caching.md index 9b29763737..bdef675f64 100644 --- a/docs/en/framework/fundamentals/caching.md +++ b/docs/en/framework/fundamentals/caching.md @@ -1,14 +1,14 @@ # Distributed Caching -ABP extends the [ASP.NET Core distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed). +ABP extends the [ASP.NET Core distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to provide a more comfortable and easy-to-use cache service. -> **Default implementation of the `IDistributedCache` interface is` MemoryDistributedCache` which works in-memory.** See [ASP.NET Core's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to see how to switch to Redis or another cache provider. Also, see the [Redis Cache](./redis-cache.md) document if you want to use Redis as the distributed cache server. +> **Default implementation of the `IDistributedCache` interface is` MemoryDistributedCache` which works in-memory.** Memory cache is only useful if you are building a monolith application and you run a single instance of your application. For other cases, consider using a distributed cache server. See the ***[When to Use a Distributed Cache Server](../../kb/when-to-use-a-distributed-cache-server.md)*** document for more details. ## Installation -> This package is already installed by default with the [application startup template](../../solution-templates/layered-web-application). So, most of the time, you don't need to install it manually. +> This package is already installed by default in [startup templates](../../solution-templates/index.md). So, most of the time, you don't need to install it manually. -[Volo.Abp.Caching](https://www.nuget.org/packages/Volo.Abp.Caching) is the main package of the caching system. You can install it a project using the add-package command of the [ABP CLI](../../cli): +[Volo.Abp.Caching](https://www.nuget.org/packages/Volo.Abp.Caching) is the main package of the caching system. You can install it as a project using the add-package command of the [ABP CLI](../../cli): ```bash abp add-package Volo.Abp.Caching @@ -24,10 +24,10 @@ ASP.NET Core defines the `IDistributedCache` interface to get/set the cache valu * It works with **byte arrays** rather than .NET objects. So, you need to **serialize/deserialize** the objects you need to cache. * It provides a **single key pool** for all cache items, so; - * You need to care about the keys to distinguish **different type of objects**. + * You need to care about the keys to distinguish **different types of objects**. * You need to care about the cache items of **different tenants** in a [multi-tenant](../architecture/multi-tenancy) system. -> `IDistributedCache` is defined in the `Microsoft.Extensions.Caching.Abstractions` package. That means it is not only usable for ASP.NET Core applications, but also available to **any type of applications**. +> `IDistributedCache` is defined in the `Microsoft.Extensions.Caching.Abstractions` package. That means it is not only usable for ASP.NET Core applications but also available to **any type of applications**. See [ASP.NET Core's distributed caching document](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) for more information. @@ -37,12 +37,12 @@ ABP defines the generic `IDistributedCache` interface in the [Volo.A `IDistributedCache` solves the difficulties explained above; -* It internally **serializes/deserializes** the cached objects. Uses **JSON** serialization by default, but can be overridden by replacing the `IDistributedCacheSerializer` service in the [dependency injection](./dependency-injection.md) system. -* It automatically adds a **cache name** prefix to the cache keys based on the object type stored in the cache. Default cache name is the full name of the cache item class (`CacheItem` postfix is removed if your cache item class ends with it). You can use the **`CacheName` attribute** on the cache item class to set the cache name. +* It internally **serializes/deserializes** the cached objects. It uses **JSON** serialization by default but can be overridden by replacing the `IDistributedCacheSerializer` service in the [dependency injection](./dependency-injection.md) system. +* It automatically adds a **cache name** prefix to the cache keys based on the object type stored in the cache. The default cache name is the full name of the cache item class (`CacheItem` postfix is removed if your cache item class ends with it). You can use the **`CacheName` attribute** on the cache item class to set the cache name. * It automatically adds the **current tenant id** to the cache key to distinguish cache items for different tenants (if your application is [multi-tenant](../architecture/multi-tenancy)). Define `IgnoreMultiTenancy` attribute on the cache item class to disable this if you want to share the cached objects among all tenants in a multi-tenant application. -* Allows to define a **global cache key prefix** per application, so different applications can use their isolated key pools in a shared distributed cache server. +* Allows defining a **global cache key prefix** per application so different applications can use their isolated key pools in a shared distributed cache server. * It **can tolerate errors** wherever possible and bypasses the cache. This is useful when you have temporary problems on the cache server. -* It has methods like `GetManyAsync` and `SetManyAsync` which significantly improve the performance on **batch operations**. +* It has methods like `GetManyAsync` and `SetManyAsync` which significantly improve the performance of **batch operations**. **Example: Store Book names and prices in the cache** @@ -167,7 +167,7 @@ namespace MyProject ```` * This sample service uses the `GetOrAddAsync()` method to get a book item from the cache. -* Since cache explicitly implemented as using `Guid` as cache key, `Guid` value passed to `_cache_GetOrAddAsync()` method. +* Since the cache is explicitly implemented as using `Guid` as the cache key, the `Guid` value is passed to the `_cache_GetOrAddAsync()` method. #### Complex Types as the Cache Key @@ -228,29 +228,29 @@ Configure(options => * `HideErrors` (`bool`, default: `true`): Enables/disables hiding the errors on writing/reading values from the cache server. * `KeyPrefix` (`string`, default: `null`): If your cache server is shared by multiple applications, you can set a prefix for the cache keys for your application. In this case, different applications can not overwrite each other's cache items. -* `GlobalCacheEntryOptions` (`DistributedCacheEntryOptions`): Used to set default distributed cache options (like `AbsoluteExpiration` and `SlidingExpiration`) used when you don't specify the options while saving cache items. Default value uses the `SlidingExpiration` as 20 minutes. +* `GlobalCacheEntryOptions` (`DistributedCacheEntryOptions`): Used to set default distributed cache options (like `AbsoluteExpiration` and `SlidingExpiration`) used when you don't specify the options while saving cache items. The default value uses the `SlidingExpiration` as 20 minutes. ## Error Handling -When you design a cache for your objects, you typically try to get the value from cache first. If not found in the cache, you query the object from the **original source**. It may be located in a **database** or may require to perform an HTTP call to a remote server. +When you design a cache for your objects, you typically try to get the value from the cache first. If not found in the cache, you query the object from the **original source**. It may be located in a **database** or may require an HTTP call to a remote server to be performed. -In most cases, you want to **tolerate the cache errors**; If you get error from the cache server you don't want to cancel the operation. Instead, you silently hide (and log) the error and **query from the original source**. This is what the ABP does by default. +In most cases, you want to **tolerate the cache errors**; If you get an error from the cache server, you don't want to cancel the operation. Instead, you silently hide (and log) the error and **query from the original source**. This is what the ABP does by default. ABP's Distributed Cache [handle](./exception-handling.md), log and hide errors by default. There is an option to change this globally (see the options below). -In addition, all of the `IDistributedCache` (and `IDistributedCache`) methods have an optional `hideErrors` parameter, which is `null` by default. The global value is used if this parameter left as `null`, otherwise you can decide to hide or throw the exceptions for individual method calls. +In addition, all of the `IDistributedCache` (and `IDistributedCache`) methods have an optional `hideErrors` parameter, which is `null` by default. The global value is used if this parameter is left as `null`; otherwise, you can decide to hide or throw the exceptions for individual method calls. ## Batch Operations -ABP's distributed cache interfaces provide methods to perform batch methods those improves the performance when you want to batch operation multiple cache items in a single method call. +ABP's distributed cache interfaces provide methods to perform batch operations that improve performance when you want to batch operation multiple cache items in a single method call. * `SetManyAsync` and `SetMany` methods can be used to set multiple values to the cache. * `GetManyAsync` and `GetMany` methods can be used to retrieve multiple values from the cache. * `GetOrAddManyAsync` and `GetOrAddMany` methods can be used to retrieve multiple values and set missing values from the cache -* `RefreshManyAsync` and `RefreshMany` methods can be used to resets the sliding expiration timeout of multiple values from the cache +* `RefreshManyAsync` and `RefreshMany` methods can be used to reset the sliding expiration timeout of multiple values from the cache * `RemoveManyAsync` and `RemoveMany` methods can be used to remove multiple values from the cache -> These are not standard methods of the ASP.NET Core caching. So, some providers may not support them. They are supported by the [ABP Redis Cache integration package](./redis-cache.md). If the provider doesn't support, it fallbacks to `SetAsync` and `GetAsync` ... methods (called once for each item). +> These are not standard methods of the ASP.NET Core caching. So, some providers may not support them. They are supported by the [ABP Redis Cache integration package](./redis-cache.md). If the provider doesn't support it, it falls back to `SetAsync` and `GetAsync` ... methods (called once for each item). ## Caching Entities @@ -266,17 +266,17 @@ It's designed as read-only and automatically invalidates a cached entity if the Distributed cache service provides an interesting feature. Assume that you've updated the price of a book in the database, then set the new price to the cache, so you can use the cached value later. What if you have an exception after setting the cache and you **rollback the transaction** that updates the price of the book? In this case, cache value will be incorrect. -`IDistributedCache<..>` methods gets an optional parameter, named `considerUow`, which is `false` by default. If you set it to `true`, then the changes you made for the cache are not actually applied to the real cache store, but associated with the current [unit of work](../architecture/domain-driven-design/unit-of-work.md). You get the value you set in the same unit of work, but the changes are applied **only if the current unit of work succeed**. +`IDistributedCache<..>` methods gets an optional parameter, named `considerUow`, which is `false` by default. If you set it to `true`, then the changes you made for the cache are not actually applied to the real cache store, but associated with the current [unit of work](../architecture/domain-driven-design/unit-of-work.md). You get the value you set in the same unit of work, but the changes are applied **only if the current unit of work succeeds**. ### IDistributedCacheSerializer -`IDistributedCacheSerializer` service is used to serialize and deserialize the cache items. Default implementation is the `Utf8JsonDistributedCacheSerializer` class that uses `IJsonSerializer` service to convert objects to [JSON](../../json-serialization.md) and vice verse. Then it uses UTC8 encoding to convert the JSON string to a byte array which is accepted by the distributed cache. +`IDistributedCacheSerializer` service is used to serialize and deserialize the cache items. The default implementation is the `Utf8JsonDistributedCacheSerializer` class that uses `IJsonSerializer` service to convert objects to [JSON](../../json-serialization.md) and vice verse. Then it uses UTC8 encoding to convert the JSON string to a byte array which is accepted by the distributed cache. -You can [replace](./dependency-injection.md) this service by your own implementation if you want to implement your own serialization logic. +You can [replace](./dependency-injection.md) this service with your own implementation if you want to implement your own serialization logic. ### IDistributedCacheKeyNormalizer -`IDistributedCacheKeyNormalizer` is implemented by the `DistributedCacheKeyNormalizer` class by default. It adds cache name, application cache prefix and current tenant id to the cache key. If you need a more advanced key normalization, you can [replace](./dependency-injection.md) this service by your own implementation. +`IDistributedCacheKeyNormalizer` is implemented by the `DistributedCacheKeyNormalizer` class by default. It adds the cache name, application cache prefix and current tenant ID to the cache key. If you need a more advanced key normalization, you can [replace](./dependency-injection.md) this service with your own implementation. ## See Also diff --git a/docs/en/framework/fundamentals/dependency-injection.md b/docs/en/framework/fundamentals/dependency-injection.md index dc0b847cbf..9dda3aef45 100644 --- a/docs/en/framework/fundamentals/dependency-injection.md +++ b/docs/en/framework/fundamentals/dependency-injection.md @@ -498,6 +498,8 @@ Use `ICachedServiceProvider` (instead of `ITransientCachedServiceProvider`) unle > ABP also provides the `IAbpLazyServiceProvider` service. It does exists for backward compatibility and works exactly same with the `ITransientCachedServiceProvider` service. So, use the `ITransientCachedServiceProvider` since the `IAbpLazyServiceProvider` might be removed in future ABP versions. +> Another advantage of using `ICachedServiceProvider` is that, during an HTTP request, if a service's constructor requires injecting many dependencies, it can negatively impact performance, as the injected services may not all be used by the current request. By resolving services on-demand, performance degradation can be effectively avoided. + ## Advanced Features ### IServiceCollection.OnRegistered Event diff --git a/docs/en/framework/infrastructure/audit-logging.md b/docs/en/framework/infrastructure/audit-logging.md index f04bfafbe5..3392f5ac35 100644 --- a/docs/en/framework/infrastructure/audit-logging.md +++ b/docs/en/framework/infrastructure/audit-logging.md @@ -106,6 +106,24 @@ Configure(options => `IgnoredUrls` is the only option. It is a list of ignored URLs prefixes. In the preceding example, all URLs starting with `/products` will be ignored for audit logging. +## AbpAspNetCoreAuditingUrlOptions + +`AbpAspNetCoreAuditingUrlOptions` is the [options object](../fundamentals/options.md) to configure audit logging in the ASP.NET Core layer. You can configure it in the `ConfigureServices` method of your [module](../architecture/modularity/basics.md): + +````csharp +Configure(options => +{ + options.IncludeQuery = true; +}); +```` + +Here, a list of the options you can configure: + +* `IncludeSchema` (default: `false`): If you set to true, it will include the schema in the URL. +* `IncludeHost` (default: `false`): If you set to true, it will include the host in the URL. +* `IncludeQuery` (default: `false`): If you set to true, it will include the query string in the URL. + + ## Enabling/Disabling Audit Logging for Services ### Enable/Disable for Controllers & Actions diff --git a/docs/en/framework/infrastructure/background-jobs/index.md b/docs/en/framework/infrastructure/background-jobs/index.md index c4536885c7..35f761ddf1 100644 --- a/docs/en/framework/infrastructure/background-jobs/index.md +++ b/docs/en/framework/infrastructure/background-jobs/index.md @@ -221,6 +221,13 @@ public class MyModule : AbpModule } ```` +* `JobPollPeriod` is used to determine the interval between two job polling operations. Default is 5000 ms (5 seconds). +* `MaxJobFetchCount` is used to determine the maximum job count to fetch in a single polling operation. Default is 1000. +* `DefaultFirstWaitDuration` is used to determine the duration to wait before the first retry. Default is 60 seconds. +* `DefaultTimeout` is used to determine the timeout duration for a job. Default is 172800 seconds (2 days). +* `DefaultWaitFactor` is used to determine the factor to increase the wait duration between retries. Default is 2.0. +* `DistributedLockName` is used to determine the distributed lock name to use. Default is `AbpBackgroundJobWorker`. + ### Data Store The default background job manager needs a data store to save and read jobs. It defines `IBackgroundJobStore` as an abstraction to store the jobs. diff --git a/docs/en/framework/infrastructure/features.md b/docs/en/framework/infrastructure/features.md index 06bd99a223..ef5d97bd91 100644 --- a/docs/en/framework/infrastructure/features.md +++ b/docs/en/framework/infrastructure/features.md @@ -161,8 +161,16 @@ namespace FeaturesDemo { var myGroup = context.AddGroup("MyApp"); - myGroup.AddFeature("MyApp.PdfReporting", defaultValue: "false"); - myGroup.AddFeature("MyApp.MaxProductCount", defaultValue: "10"); + myGroup.AddFeature( + "MyApp.PdfReporting", + defaultValue: "false" + ); + + myGroup.AddFeature( + "MyApp.MaxProductCount", + defaultValue: "10", + valueType: new FreeTextStringValueType(new NumericValueValidator()) + ); } } } diff --git a/docs/en/framework/ui/angular/quick-start.md b/docs/en/framework/ui/angular/quick-start.md index 32e4522937..dec3a23262 100644 --- a/docs/en/framework/ui/angular/quick-start.md +++ b/docs/en/framework/ui/angular/quick-start.md @@ -6,7 +6,7 @@ Please follow the steps below to prepare your development environment for Angular. -1. **Install Node.js:** Please visit [Node.js downloads page](https://nodejs.org/en/download/) and download proper Node.js `v18.19+` installer for your OS. An alternative is to install [NVM](https://github.com/nvm-sh/nvm) and use it to have multiple versions of Node.js in your operating system. +1. **Install Node.js:** Please visit [Node.js downloads page](https://nodejs.org/en/download/) and download proper Node.js `v20.11+` installer for your OS. An alternative is to install [NVM](https://github.com/nvm-sh/nvm) and use it to have multiple versions of Node.js in your operating system. 2. **[Optional] Install Yarn:** You may install Yarn v1.22+ (not v2) following the instructions on [the installation page](https://classic.yarnpkg.com/en/docs/install). Yarn v1 delivers an arguably better developer experience compared to npm v10 and below. You may skip this step and work with npm, which is built-in in Node.js, instead. 3. **[Optional] Install VS Code:** [VS Code](https://code.visualstudio.com/) is a free, open-source IDE which works seamlessly with TypeScript. Although you can use any IDE including Visual Studio or Rider, VS Code will most likely deliver the best developer experience when it comes to Angular projects. ABP project templates even contain plugin recommendations for VS Code users, which VS Code will ask you to install when you open the Angular project folder. Here is a list of recommended extensions: - [Angular Language Service](https://marketplace.visualstudio.com/items?itemName=angular.ng-template) diff --git a/docs/en/framework/ui/blazor/global-scripts-styles.md b/docs/en/framework/ui/blazor/global-scripts-styles.md index d53fc3d2ba..a8819df35a 100644 --- a/docs/en/framework/ui/blazor/global-scripts-styles.md +++ b/docs/en/framework/ui/blazor/global-scripts-styles.md @@ -1,83 +1,72 @@ # Blazor UI: Managing Global Scripts & Styles -Some modules may require additional styles or scripts that need to be referenced in **index.html** file. It's not easy to find and update these types of references in Blazor apps. ABP offers a simple, powerful, and modular way to manage global style and scripts in Blazor apps. +You can add your JavaScript and CSS files from your modules or applications to the Blazor global assets system. All the JavaScript and CSS files will be added to the `global.js` and `global.css` files. You can access these files via the following URL in a Blazor WASM project: -To update script & style references without worrying about dependencies, ordering, etc in a project, you can use the [bundle command](../../../cli#bundle). +- https://localhost/global.js +- https://localhost/global.css -You can also add custom styles and scripts and let ABP manage them for you. In your Blazor project, you can create a class implementing `IBundleContributor` interface. +## Add JavaScript and CSS to the global assets system in the module -`IBundleContributor` interface contains two methods. +Your module project solution will have two related Blazor projects: -* `AddScripts(...)` -* `AddStyles(...)` +* `MyModule.Blazor`:This project includes the JavaScript/CSS files required for your Blazor components. The `MyApp.Blazor.Client (Blazor WASM)` project will reference this project. +* `MyModule.Blazor.WebAssembly.Bundling`:This project is used to add your JavaScript/CSS files to the Blazor global resources. The `MyModule.Blazor (ASP.NET Core)` project will reference this project. -Both methods get `BundleContext` as a parameter. You can add scripts and styles to the `BundleContext` and run [bundle command](../../../cli#bundle). Bundle command detects custom styles and scripts with module dependencies and updates `index.html` file. +You need to define JavaScript and CSS contributor classes in the `MyModule.Blazor.WebAssembly.Bundling` project to add the files to the global assets system. -## Example Usage -```csharp -namespace MyProject.Blazor +> Please use `BlazorWebAssemblyStandardBundles.Scripts.Global` and `BlazorWebAssemblyStandardBundles.Styles.Global` for the bundle name. + +```cs +public class MyModuleBundleScriptContributor : BundleContributor { - public class MyProjectBundleContributor : IBundleContributor + public override void ConfigureBundle(BundleConfigurationContext context) { - public void AddScripts(BundleContext context) - { - context.Add("site.js"); - } - - public void AddStyles(BundleContext context) - { - context.Add("main.css"); - context.Add("custom-styles.css"); - } + context.Files.AddIfNotContains("_content/MyModule.Blazor/libs/myscript.js"); } } ``` -> There is a BundleContributor class implementing `IBundleContributor` interface coming by default with the startup templates. So, most of the time, you don't need to add it manually. - -## Bundling And Minification -`abp bundle` command offers bundling and minification support for client-side resources(JavaScript and CSS files). `abp bundle` command reads the `appsettings.json` file inside the Blazor project and bundles the resources according to the configuration. You can find the bundle configurations inside `AbpCli.Bundle` element. - -Here are the options that you can control inside the `appsettings.json` file. - -`Mode`: Bundling and minification mode. Possible values are -* `BundleAndMinify`: Bundle all the files into a single file and minify the content. -* `Bundle`: Bundle all files into a single file, but not minify. -* `None`: Add files individually, do not bundle. - -`Name`: Bundle file name. Default value is `global`. - -`Parameters`: You can define additional key/value pair parameters inside this section. `abp bundle` command automatically sends these parameters to the bundle contributors, and you can check these parameters inside the bundle contributor, take some actions according to these values. - -Let's say that you want to exclude some resources from the bundle and control this action using the bundle parameters. You can add a parameter to the bundle section like below. - -```json -"AbpCli": { - "Bundle": { - "Mode": "BundleAndMinify", /* Options: None, Bundle, BundleAndMinify */ - "Name": "global", - "Parameters": { - "ExcludeThemeFromBundle":"true" - } - } - } -``` - -You can check this parameter and take action like below. - -```csharp -public class MyProjectNameBundleContributor : IBundleContributor +```cs +public class MyModuleBundleStyleContributor : BundleContributor { - public void AddScripts(BundleContext context) + public override void ConfigureBundle(BundleConfigurationContext context) { + context.Files.AddIfNotContains("_content/MyModule.Blazor/libs/mystyle.css"); } +} +``` - public void AddStyles(BundleContext context) +```cs +[DependsOn( + typeof(AbpAspNetCoreComponentsWebAssemblyThemingBundlingModule) +)] +public class MyBlazorWebAssemblyBundlingModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) { - var excludeThemeFromBundle = bool.Parse(context.Parameters.GetValueOrDefault("ExcludeThemeFromBundle")); - context.Add("mytheme.css", excludeFromBundle: excludeThemeFromBundle); - context.Add("main.css"); + Configure(options => + { + // Add script bundle + options.ScriptBundles.Get(BlazorWebAssemblyStandardBundles.Scripts.Global) + .AddContributors(typeof(MyModuleBundleScriptContributor)); + + // Add style bundle + options.StyleBundles.Get(BlazorWebAssemblyStandardBundles.Styles.Global) + .AddContributors(typeof(MyModuleBundleStyleContributor)); + }); } } ``` +## Add JavaScript and CSS to the global assets system in the application + +This is similar to the module. You need to define JavaScript and CSS contributor classes in the `MyApp.Blazor.Client` project to add the files to the global assets system. + +## AbpBundlingGlobalAssetsOptions + +You can configure the JavaScript and CSS file names in the `GlobalAssets` property of the `AbpBundlingOptions` class. The default values are `global.js` and `global.css`. + +## Reference + +- [ASP.NET Core MVC Bundling & Minification](../mvc-razor-pages/bundling-minification#bundle-contributorsg) +- [ABP Global Assets - New way to bundle JavaScript/CSS files in Blazor WebAssembly app](https://github.com/abpframework/abp/blob/dev/docs/en/Community-Articles/2024-11-25-Global-Assets/POST.md) diff --git a/docs/en/framework/ui/maui/index.md b/docs/en/framework/ui/maui/index.md index 69f9518e92..573278a060 100644 --- a/docs/en/framework/ui/maui/index.md +++ b/docs/en/framework/ui/maui/index.md @@ -40,6 +40,9 @@ Open a command line terminal and run the `adb reverse` command to expose a port > You should replace "44305" with the real port. > You should run the command after starting the emulator. +> If you don't have a separate installation of Android Debug Bridge, you can open it from **Visual Studio** by following toolbar menu `Tools` > `Android` > `Android Adb Command Prompt`. Android emulator has to be running for this operation. + + ### iOS The iOS simulator uses the host machine network. Therefore, applications running in the simulator can connect to web services running on your local machine via the machines IP address or via the localhost hostname. For example, given a local secure web service that exposes a GET operation via the /api/todoitems/ relative URI, an application running on the iOS simulator can consume the operation by sending a GET request to https://localhost:/api/todoitems/. diff --git a/docs/en/framework/ui/mvc-razor-pages/client-side-package-management.md b/docs/en/framework/ui/mvc-razor-pages/client-side-package-management.md index bb984a001f..31e54079ba 100644 --- a/docs/en/framework/ui/mvc-razor-pages/client-side-package-management.md +++ b/docs/en/framework/ui/mvc-razor-pages/client-side-package-management.md @@ -41,7 +41,7 @@ After depending on a NPM package, all you should do is to run the **yarn** comma yarn ``` -Alternatively, you can use `npm install` but [Yarn](https://classic.yarnpkg.com/) is suggested as mentioned before. +Alternatively, you can use `npm install` but [Yarn v1.22+ (not v2)](https://classic.yarnpkg.com/en/docs/install) is suggested as mentioned before. #### Package Contribution diff --git a/docs/en/framework/ui/mvc-razor-pages/tag-helpers/dynamic-forms.md b/docs/en/framework/ui/mvc-razor-pages/tag-helpers/dynamic-forms.md index ceab5d1312..6b9a07f63a 100644 --- a/docs/en/framework/ui/mvc-razor-pages/tag-helpers/dynamic-forms.md +++ b/docs/en/framework/ui/mvc-razor-pages/tag-helpers/dynamic-forms.md @@ -23,7 +23,7 @@ public class DynamicFormsModel : PageModel new SelectListItem { Value = "CA", Text = "Canada"}, new SelectListItem { Value = "US", Text = "USA"}, new SelectListItem { Value = "UK", Text = "United Kingdom"}, - new SelectListItem { Value = "RU", Text = "Russia"} + new SelectListItem { Value = "RU", Text = "Turkey"} }; public void OnGet() @@ -217,7 +217,7 @@ public class DynamicFormsModel : PageModel new SelectListItem { Value = "CA", Text = "Canada"}, new SelectListItem { Value = "US", Text = "USA"}, new SelectListItem { Value = "UK", Text = "United Kingdom"}, - new SelectListItem { Value = "RU", Text = "Russia"} + new SelectListItem { Value = "RU", Text = "Turkey"} }; public void OnGet() @@ -278,4 +278,4 @@ public string Name { get; set; } ## See Also -* [Form Elements](form-elements.md) \ No newline at end of file +* [Form Elements](form-elements.md) diff --git a/docs/en/framework/ui/react-native/index.md b/docs/en/framework/ui/react-native/index.md index 3dd60b6348..4b6a0b5ec7 100644 --- a/docs/en/framework/ui/react-native/index.md +++ b/docs/en/framework/ui/react-native/index.md @@ -17,7 +17,7 @@ ABP platform provide basic [React Native](https://reactnative.dev/) startup temp Please follow the steps below to prepare your development environment for React Native. -1. **Install Node.js:** Please visit [Node.js downloads page](https://nodejs.org/en/download/) and download proper Node.js v16 or v18 installer for your OS. An alternative is to install [NVM](https://github.com/nvm-sh/nvm) and use it to have multiple versions of Node.js in your operating system. +1. **Install Node.js:** Please visit [Node.js downloads page](https://nodejs.org/en/download/) and download proper Node.js v20.11+ installer for your OS. An alternative is to install [NVM](https://github.com/nvm-sh/nvm) and use it to have multiple versions of Node.js in your operating system. 2. **[Optional] Install Yarn:** You may install Yarn v1 (not v2) following the instructions on [the installation page](https://classic.yarnpkg.com/en/docs/install). Yarn v1 delivers an arguably better developer experience compared to npm v6 and below. You may skip this step and work with npm, which is built-in in Node.js, instead. 3. **[Optional] Install VS Code:** [VS Code](https://code.visualstudio.com/) is a free, open-source IDE which works seamlessly with TypeScript. Although you can use any IDE including Visual Studio or Rider, VS Code will most likely deliver the best developer experience when it comes to React Native projects. 4. **Install an Emulator:** React Native applications need an Android emulator or an iOS simulator to run on your OS. See the [Android Studio Emulator](https://docs.expo.io/workflow/android-simulator/) or [iOS Simulator](https://docs.expo.io/workflow/ios-simulator/) on expo.io documentation to learn how to set up an emulator. diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner-docker-dependencies.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner-docker-dependencies.png deleted file mode 100644 index 99c09045f8..0000000000 Binary files a/docs/en/get-started/images/abp-studio-microservice-solution-runner-docker-dependencies.png and /dev/null differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-1.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-1.png new file mode 100644 index 0000000000..1d953a9552 Binary files /dev/null and b/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-1.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-2.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-2.png new file mode 100644 index 0000000000..53152f1243 Binary files /dev/null and b/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-2.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch.png deleted file mode 100644 index 2efe8e5380..0000000000 Binary files a/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch.png and /dev/null differ diff --git a/docs/en/get-started/images/abp-studio-no-layers-new-solution-additional-options-0.9.13.png b/docs/en/get-started/images/abp-studio-no-layers-new-solution-additional-options-0.9.13.png new file mode 100644 index 0000000000..208e047662 Binary files /dev/null and b/docs/en/get-started/images/abp-studio-no-layers-new-solution-additional-options-0.9.13.png differ diff --git a/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-0.9.13.png b/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-0.9.13.png new file mode 100644 index 0000000000..c0b9f85ea6 Binary files /dev/null and b/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-0.9.13.png differ diff --git a/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-configurations-efcore-0.9.13.png b/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-configurations-efcore-0.9.13.png new file mode 100644 index 0000000000..bb85423493 Binary files /dev/null and b/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-configurations-efcore-0.9.13.png differ diff --git a/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-configurations-mongo-0.9.13.png b/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-configurations-mongo-0.9.13.png new file mode 100644 index 0000000000..3d4f435955 Binary files /dev/null and b/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-configurations-mongo-0.9.13.png differ diff --git a/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-provider-efcore-0.9.13.png b/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-provider-efcore-0.9.13.png new file mode 100644 index 0000000000..7fc1b37953 Binary files /dev/null and b/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-provider-efcore-0.9.13.png differ diff --git a/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-provider-mongo-0.9.13.png b/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-provider-mongo-0.9.13.png new file mode 100644 index 0000000000..cfe31c98d9 Binary files /dev/null and b/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-database-provider-mongo-0.9.13.png differ diff --git a/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-solution-properties-0.9.13.png b/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-solution-properties-0.9.13.png new file mode 100644 index 0000000000..4a217fd933 Binary files /dev/null and b/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-solution-properties-0.9.13.png differ diff --git a/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-ui-framework-0.9.13.png b/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-ui-framework-0.9.13.png new file mode 100644 index 0000000000..eb6c95e5bb Binary files /dev/null and b/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-ui-framework-0.9.13.png differ diff --git a/docs/en/get-started/images/abp-studio-nolayers-new-solution-dialog-ui-theme-0.9.13.png b/docs/en/get-started/images/abp-studio-nolayers-new-solution-dialog-ui-theme-0.9.13.png new file mode 100644 index 0000000000..ba5d165e26 Binary files /dev/null and b/docs/en/get-started/images/abp-studio-nolayers-new-solution-dialog-ui-theme-0.9.13.png differ diff --git a/docs/en/get-started/index.md b/docs/en/get-started/index.md index 6ffbfe548f..59f9812563 100644 --- a/docs/en/get-started/index.md +++ b/docs/en/get-started/index.md @@ -5,7 +5,7 @@ Great that you've decided to create a new application with ABP. ABP provides mul Please select one of the following documents best fits for your application: - **[Single-Layer Solution](single-layer-web-application.md)**: Creates a single-project solution. Recommended for building an application with a **simpler and easy to understand** architecture. -- **[Layered Solution](layered-web-application.md)**: A fully layered (multiple projects) solution based on [Domain Driven Design](../framework/architecture/domain-driven-design) practices. Recommended for long-term projects that need a **maintainable and extensible** codebase. +- **[Application (Layered)](layered-web-application.md)**: A fully layered (multiple projects) solution based on [Domain Driven Design](../framework/architecture/domain-driven-design) practices. Recommended for long-term projects that need a **maintainable and extensible** codebase. - **[Microservice Solution](microservice.md)**: A **distributed solution** to build **microservice systems**. It includes pre-built services, API gateways, web and mobile applications, Kubernetes and Helm configuration, and everything you need to start your large-scale microservice solution. - **Others** - [Empty ASP.NET Core Application](empty-aspnet-core-application.md) diff --git a/docs/en/get-started/layered-web-application.md b/docs/en/get-started/layered-web-application.md index e8896b8666..8d4e540292 100644 --- a/docs/en/get-started/layered-web-application.md +++ b/docs/en/get-started/layered-web-application.md @@ -13,31 +13,15 @@ In this quick start guide, you will learn how to create and run a layered (and p ## Setup your development environment -First things first! Let's setup your development environment before creating the first project. +First things first! Let's setup your development environment before creating the first project. The following tools should be installed on your development machine: -### Pre-requirements +* [Visual Studio 2022](https://visualstudio.microsoft.com/) or another IDE that supports [.NET 9.0+](https://dotnet.microsoft.com/download/dotnet) development. +* [.NET 9.0+](https://dotnet.microsoft.com/en-us/download/dotnet){{ if UI != "Blazor" }} +* [Node v22.11+](https://nodejs.org/) +* [Yarn v1.22+ (not v2+)](https://classic.yarnpkg.com/en/docs/install) or npm v10+ (already installed with Node){{ end }} +* [Docker Desktop](https://www.docker.com/products/docker-desktop/) -The following tools should be installed on your development machine: - -* [Visual Studio 2022](https://visualstudio.microsoft.com/vs/) (v17.3+) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). [1](#f-editor) -* [.NET 8.0+](https://dotnet.microsoft.com/en-us/download/dotnet) -{{ if UI != "Blazor" }} -* [Node v18.19+](https://nodejs.org/) -* [Yarn v1.22+ (not v2)](https://classic.yarnpkg.com/en/docs/install) [2](#f-yarn) or npm v10+ (already installed with Node) -{{ end }} -{{ if Tiered == "Yes" }} -* [Redis](https://redis.io/) (as the [distributed cache](../framework/fundamentals/caching.md)). -{{ else }} -* [Redis](https://redis.io/) (as the [distributed cache](../framework/fundamentals/caching.md)) is required if you select the Public website option. -{{ end }} - -1 _You can use another editor instead of Visual Studio as long as it supports .NET Core and ASP.NET Core._ [↩](#a-editor) - -{{ if UI != "Blazor" }} - -2 _Yarn v2 works differently and is not supported._ [↩](#a-yarn) - -{{ end }} +> Check the [Pre-requirements document](pre-requirements.md) for more detailed information about these tools. ## Creating a New Solution @@ -147,7 +131,7 @@ In the Solution Runner section (on the left side) you can see all the runnable a You can run all the applications or start them one by one. To start an application, either click the *Play* icon near to the application or right-click and select the *Run* -> *Start* context menu item. -> For the first run, you'll need to build the application. You can achieve this by selecting *Run* -> *Build & Start* from the context menu. +> ABP Studio builds the application by default. So, you don't need to manually build the application before running it. You can start the following application(s): @@ -229,7 +213,7 @@ You can start the following application(s): {{ else }} - `Acme.BookStore.Web` {{ end }} - + Before starting the mobile application, ensure that you configure it for [react-native](../framework/ui/react-native) or [MAUI](../framework/ui/maui). ![mobile-sample](images/abp-studio-mobile-sample.gif) diff --git a/docs/en/get-started/microservice.md b/docs/en/get-started/microservice.md index 5be705e215..e29c4cbe10 100644 --- a/docs/en/get-started/microservice.md +++ b/docs/en/get-started/microservice.md @@ -4,6 +4,21 @@ In this quick start guide, you will learn how to create and run a microservice solution using [ABP Studio](../studio/index.md). +## Setup your development environment + +First things first! Let's setup your development environment before creating the first project. The following tools should be installed on your development machine: + +* [Visual Studio 2022](https://visualstudio.microsoft.com/vs/) or another IDE that supports .NET development +* [.NET 9.0+](https://dotnet.microsoft.com/en-us/download/dotnet) +* [Node v22.11+](https://nodejs.org/) +* [Yarn v1.22+ (not v2+)](https://classic.yarnpkg.com/en/docs/install) or npm v10+ (already installed with Node) +* [Docker Desktop (with Kubernetes enabled)](https://www.docker.com/products/docker-desktop/) +* [Helm](https://helm.sh/docs/intro/install/) +* [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/deploy/) +* [mkcert](https://github.com/FiloSottile/mkcert#installation) + +> Check the [Pre-requirements document](pre-requirements.md) for more detailed information about these tools. + ## Creating a New Solution > 🛈 This document uses [ABP Studio](../studio/index.md) to create new ABP solutions. **ABP Studio** is in the beta version now. If you have any issues, you can use the [ABP CLI](../cli/index.md) to create new solutions. You can also use the [getting started page](https://abp.io/get-started) to easily build ABP CLI commands for new project creations. @@ -73,11 +88,11 @@ Click the Next button to see *Additional Options* selection: If you unchecked the *Kubernetes Configuration* option, the solution will not include the Kubernetes configuration files which include the Helm charts and other Kubernetes related files. You can also specify *Social Logins*; if you uncheck this option, the solution will not be configured for social login. Lastly, you can specify the *Include Tests* option to include the test projects in the solution. -Now, we are ready to allow ABP Studio to create our solution. Just click the *Create* button and let the ABP Studio do the rest for you. After clicking the Create button, the dialog is closed and your solution is loaded into ABP Studio: +Now, we are ready to allow ABP Studio to create our solution. Just click the *Create* button and let the ABP Studio do the rest for you. After clicking the *Create* button, the dialog is closed and your solution is loaded into ABP Studio: ![abp-studio-created-new-microservice-solution](images/abp-studio-created-new-microservice-solution.png) -You can explore the solution, but you need to wait for background tasks to be completed before running any application in the solution (it can take up to a few minutes to set up all). +You can explore the solution, but you need to **wait for background tasks to be completed** before running any application in the solution (it can take up to a few minutes to set up all). > The solution structure can be different in your case based on the options you've selected. @@ -123,28 +138,22 @@ In the *Solution Runner* section (on the left side) you can see all the runnable ![abp-studio-microservice-solution-runner-applications](images/abp-studio-microservice-solution-runner-applications.png) -> All the leaf items in the *Solution Runner* is called as an *Application* as they are executable applications. - -> For a faster start process, first start the *Docker-Dependencies*, then you can start all applications. +> A leaf item in the *Solution Runner* is called as an *Application* as it is an executable application. As shown in the figure above, the executable applications are grouped into folders like `apps`, `gateways`, `infrastructure`, and `services`. You can start/stop them all, a group (folder) of them, or one by one. -Before running the applications, it is good to be sure that all applications are built. To do that, right-click the root item in the *Solution Runner* and select *Build* -> *Build All* action. - -![abp-studio-microservice-solution-runner-build-all](images/abp-studio-microservice-solution-runner-build-all.png) - -> *Solution Runner* doesn't build an application before running it. That provides a great performance gain because most of the time you will work on one or a few services and you don't need to build all of the other applications in every run. However, if you want to build before running, you can right-click an item in the *Solution Runner* tree and select *Run* -> *Build & Start* command. - -It will take some time to build all. Once all is done, you can start the system. +Before running the applications, you can run the all application by right-clicking the root item in the *Solution Runner* and select *Build* -> *Build All* action. However, you don't need to do that, because ABP Studio builds the applications before running them by default. -You can click the *Play* button on the root item in Solution Runner to start all the applications. Or you can start `Docker-Dependencies` first, so the database and other infrastructure services get ready before the other applications: +> If you want to change this behavior, and don't want ABP Studio to build before running the applications, you can click the *Manage start actions* button in the *Solution Runner*, which you can see from the root item or per folder. -![abp-studio-microservice-solution-runner-docker-dependencies](images/abp-studio-microservice-solution-runner-docker-dependencies.png) +You can click the *Play* button on the root item in *Solution Runner* to start all the applications. +> **About the Docker Containers** +> > Docker will fetch the docker images before starting the containers in your first run (if they were not fetched before) and that process may take a few minutes depending on your internet connection speed. So, please wait for it to completely start. If the process takes more time than you expect, you can right-click on `Docker-Dependencies` and select the *Logs* command to see what's happening. -Once `Docker-Dependencies` is ready, you can click the *Play* button on the root item in Solution Runner to start all the applications. - +> **About Failing Services on Startup** +> > Some applications/services may fail on the first run. That may be because of service and database dependencies were not satisfied and an error occurs on the application startup. ABP Studio automatically restarts failing services until it is successfully started. Being completely ready for such a distributed solution may take a while, but it will be eventually started. Once all the applications are ready, you can right-click the `Web` application and select the *Browse* command: @@ -175,9 +184,11 @@ Once you run the `IdentityService` in Visual Studio, it will be completely integ As an alternative approach, especially if you don't need to debug your service, you can enable the watching feature of ABP Studio to automatically re-build and re-start when there is change in your application/service. -To enable watching, right-click the application/service you want to watch, select the *Run* -> *Enable Watch* command as shown in the following figure: +To enable watching, right-click the application/service you want to watch, select the *Properties* -> *Watch changes while running* option as shown in the following figure: + +![abp-studio-microservice-solution-runner-enable-watch-1](images/abp-studio-microservice-solution-runner-enable-watch-1.png) -![abp-studio-microservice-solution-runner-enable-watch](images/abp-studio-microservice-solution-runner-enable-watch.png) +![abp-studio-microservice-solution-runner-enable-watch-2](images/abp-studio-microservice-solution-runner-enable-watch-2.png) Now, you can make your development on the `IdentityService`. Whenever you save a code file, it is automatically rebuilt and restarted by ABP Studio, so any change will be effective on the running solution in a few seconds. @@ -185,8 +196,6 @@ When you enable watch for an application an *eye* icon is added near to the appl ![abp-studio-microservice-solution-runner-watch-enabled-icon](images/abp-studio-microservice-solution-runner-watch-enabled-icon.png) -You can disable watching by right-clicking an application and selecting *Run* -> *Disable Watch* command. - ## Kubernetes Integration: Working with Helm Charts Solution Runner is a great way to locally run all the applications and services of your solution. However, there are some drawbacks: @@ -222,6 +231,8 @@ Once the solution is ready in Kubernetes, you can open a browser and visit the f ![abp-studio-microservice-web-application-home-page](images/abp-studio-microservice-web-application-home-page.png) +> We could use `cloudcrm-local-web` as the host name since ABP Studio has added an entry to the host file for us. + Click the *Login* link in the application UI, it will redirect you to the *Authentication Server* application, enter `admin` as username and `1q2w3E*` as password to login to the application. > The services run independently from each other and perform some initial data seed logic on their startups. So, they may fail in their first run. In that case, Kubernetes will re-start them. So, it may initially get some time to make the solution fully ready and working. @@ -248,11 +259,11 @@ Clicking the *Connect* button will start a process that establishes the VPN conn ![abp-studio-microservice-kubernetes-services](images/abp-studio-microservice-kubernetes-services.png) -Now, you can access all the services inside the Kubernetes cluster, including the services those are not exposes out of the cluster. You can use the service name as DNS. For example, you can directly visit `http://cloudcrm-local-identity` in your Browser. You can also right-click to a service or application and select the Browse command to open it's UI in the built-in browser of ABP Studio: +Now, you can access all the services inside the Kubernetes cluster, including the services those are not exposed out of the cluster. You can use the service name as DNS. For example, you can directly visit `http://cloudcrm-local-identity` in your Browser. You can also right-click to a service or application and select the Browse command to open it's UI in the built-in browser of ABP Studio: ![abp-studio-microservice-kubernetes-services-browse](images/abp-studio-microservice-kubernetes-services-browse.png) -You can even use the other services (e.g. SQL Server or RabbitMQ) from your local computer (even if they were not exposed out of cluster) with their service names. `sa` password for the SQL server is `myPassw@rd` by default, you can use your SQL Server management studio to connect to it and see the databases: +You can even use the other services (e.g. SQL Server or RabbitMQ) from your local computer (even if they were not exposed out of cluster) with their service names. `sa` password for the SQL server is `myPassw@rd` by default, you can use your SQL Server management studio to connect to it and see the databases (*Server name* is `cloudcrm-local-sqlserver`): ![abp-studio-microservice-sql-server-connection](images/abp-studio-microservice-sql-server-connection.png) @@ -266,6 +277,8 @@ When you connect to Kubernetes, ABP Studio automatically connects to the applica In this way, you can easily track HTTP requests, distributed events, exceptions, logs and other details of your applications. +> If you want to browse a web application in the integrated browser of ABP Studio, right-click to a service in the *Kubernetes* tab of the *Kubernetes* panel and select the *Browse* command. + ## Kubernetes Integration: Intercepting Services The next step is to intercept a service to forward the traffic (coming to that service) to your local computer, so you can run the same service in your local computer to test, debug and develop it. This is the way of connecting two environments (your local machine and the Kubernetes cluster) to develop your services integrated to Kubernetes. @@ -306,3 +319,7 @@ To re-deploy a service to Kubernetes, right-click the service and select *Comman ![abp-studio-microservice-kubernetes-redeploy](images/abp-studio-microservice-kubernetes-redeploy.png) ABP Studio will re-build the Docker image and re-install it using the related Helm chart. + +## See Also + +* [Microservice Development Tutorial](../tutorials/microservice/index.md) diff --git a/docs/en/get-started/pre-requirements.md b/docs/en/get-started/pre-requirements.md new file mode 100644 index 0000000000..c1c7b39c28 --- /dev/null +++ b/docs/en/get-started/pre-requirements.md @@ -0,0 +1,107 @@ +# Prerequisites for Developing ABP Applications + +This document will guide you through preparing your development environment for ABP based application development. + +## Notices + +The prerequisites mentioned in this document are not necessary for every project type; + +* You don't need to install the EF Core CLI if your application uses MongoDB instead of EF Core. +* You don't need to install Helm, NGINX Ingress, or mkcert if you are developing a non-microservice application. + +`README.MD` files in new solutions contain specific requirements for your solution. Please refer to the `README.MD` file of your solution. + +## IDE + +You need to use an IDE that supports .NET development. The following IDEs are the most popular ones for .NET development. + +### Visual Studio + +Visual Studio is Microsoft's IDE and is the de facto tool for developing .NET projects. You can download Visual Studio from the [Visual Studio official website](https://visualstudio.microsoft.com/). It also has a **free Community edition** which is more than enough for ABP projects. + +### Visual Studio Code + +Visual Studio Code is a **free and cross-platform** lightweight code editor that supports .NET development. You can [download from here](https://code.visualstudio.com/download). + +### JetBrains Rider + +[JetBrains Rider](https://www.jetbrains.com/rider/download) is a cross-platform IDE by [JetBrains](https://www.jetbrains.com/) that supports .NET development. It is **[free for non-commercial use](https://blog.jetbrains.com/blog/2024/10/24/webstorm-and-rider-are-now-free-for-non-commercial-use/)**. + +## .NET SDK + +ABP is based on NET, so you need to install the .NET SDK. You can download the .NET SDK from the [.NET official website](https://dotnet.microsoft.com/en-us/download/dotnet/9.0). + +> Installing Visual Studio or JetBrains Rider may automatically install the .NET SDK. + +### EF Core CLI + +If you are using [Entity Framework Core](https://learn.microsoft.com/en-us/ef/core/) as your database access provider, you need to install the [EF Core CLI](https://learn.microsoft.com/en-us/ef/core/cli/dotnet). You can install it by running the following command: + +```bash +dotnet tool install --global dotnet-ef +``` + +If you have already installed the `EF Core CLI`, you can update it by running the following command: + +```bash +dotnet tool update --global dotnet-ef +``` + +## Node.js and Yarn + +ABP projects include some frontend resource packages, so you need to install Node.js and Yarn to manage these resource packages. You can download Node.js from the [official Node.js website](https://nodejs.org/). We recommend installing version v20.11+. + +Using Yarn (classic) to manage frontend resource packages is faster and more stable than using npm. You can download `Yarn` from the [Yarn official website](https://classic.yarnpkg.com/en/docs/install). We recommend installing Yarn v1.22+ (make sure to install the Classic version, not v2+). + +To install Yarn using npm, run the following command: + +```bash +npm install --global yarn +``` + +## Docker Engine or Docker Desktop + +ABP's [Layered Solution](../solution-templates/layered-web-application/index.md) and [Microservice Solution](../solution-templates/microservice/index.md) use Docker to run infrastructure services (e.g. SQL Server, Redis, RabbitMQ) required by your application. You can install Docker Engine or Docker Desktop (recommended) on Windows, macOS and Linux. + +* [Docker Desktop](https://www.docker.com/products/docker-desktop/) (recommended) +* [Docker Engine](https://docs.docker.com/engine/install/) + +### Is Docker Engine or Docker Desktop Free? + +Docker Engine is an open-source and free containerization technology for building and containerizing your applications. [`Docker Engine` follows the Apache License 2.0](https://docs.docker.com/engine/#licensing). + +Docker Desktop is free [for small businesses (fewer than 250 employees and less than $10 million in annual revenue), personal use, education, and non-commercial open-source projects](https://docs.docker.com/subscription/desktop-license/). + +## PowerShell + +ABP startup solution templates and tools use some PowerShell scripts (`*.ps1`) to perform certain tasks. You can refer to the [PowerShell documentation](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell) for guidance on how to install PowerShell on Windows, macOS, and Linux. + +* [Install PowerShell on Windows](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows) +* [Install PowerShell on macOS](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-macos) +* [Install PowerShell on Linux](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-linux) + +## MicroService Solution + +The following tools are only required to develop ABP's [microservice solution](../solution-templates/microservice/index.md) + +### Helm + +[Helm](https://helm.sh/) is a package manager for Kubernetes. You can install Helm by following the [Helm installation guide](https://helm.sh/docs/intro/install/). + +See [Helm Deployment on Local Kubernetes Cluster](../solution-templates/microservice/helm-charts-and-kubernetes.md) for more information. + +### NGINX Ingress or NGINX Ingress using Helm + +[NGINX Ingress](https://kubernetes.github.io/ingress-nginx/deploy/) is an Ingress controller for Kubernetes. You can install NGINX Ingress by following the [NGINX Ingress installation guide](https://kubernetes.github.io/ingress-nginx/deploy/). + +If you are using Helm, you can install NGINX Ingress using the following commands: + +```cs +helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx +helm repo update +helm upgrade --install --version=4.0.19 ingress-nginx ingress-nginx/ingress-nginx +``` + +### mkcert + +Use mkcert to generate trusted certificates for local development. You can install mkcert by following the [official mkcert installation guide](https://github.com/FiloSottile/mkcert#installation). diff --git a/docs/en/get-started/single-layer-web-application.md b/docs/en/get-started/single-layer-web-application.md index 319fb19c9a..67d840d768 100644 --- a/docs/en/get-started/single-layer-web-application.md +++ b/docs/en/get-started/single-layer-web-application.md @@ -12,31 +12,14 @@ In this quick start guide, you will learn how to create and run a single layer w ## Setup your development environment -First things first! Let's setup your development environment before creating the first project. +First things first! Let's setup your development environment before creating the first project. The following tools should be installed on your development machine: -### Pre-requirements +* [Visual Studio 2022](https://visualstudio.microsoft.com/) or another IDE that supports [.NET 9.0+](https://dotnet.microsoft.com/download/dotnet) development. +* [.NET 9.0+](https://dotnet.microsoft.com/en-us/download/dotnet){{ if UI != "Blazor" }} +* [Node v22.11+](https://nodejs.org/) +* [Yarn v1.22+ (not v2+)](https://classic.yarnpkg.com/en/docs/install) or npm v10+ (already installed with Node){{ end }} -The following tools should be installed on your development machine: - -* [Visual Studio 2022](https://visualstudio.microsoft.com/vs/) (v17.3+) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). [1](#f-editor) -* [.NET 8.0+](https://dotnet.microsoft.com/en-us/download/dotnet) -{{ if UI != "Blazor" }} -* [Node v18.19+](https://nodejs.org/) -* [Yarn v1.22+ (not v2)](https://classic.yarnpkg.com/en/docs/install) [2](#f-yarn) or npm v10+ (already installed with Node) -{{ end }} -{{ if Tiered == "Yes" }} -* [Redis](https://redis.io/) (as the [distributed cache](../framework/fundamentals/caching.md)). -{{ else }} -* [Redis](https://redis.io/) (as the [distributed cache](../framework/fundamentals/caching.md)) is required if you select the Public website option. -{{ end }} - -1 _You can use another editor instead of Visual Studio as long as it supports .NET Core and ASP.NET Core._ [↩](#a-editor) - -{{ if UI != "Blazor" }} - -2 _Yarn v2 works differently and is not supported._ [↩](#a-yarn) - -{{ end }} +> Check the [Pre-requirements document](pre-requirements.md) for more detailed information about these tools. ## Creating a New Solution @@ -50,11 +33,11 @@ Assuming that you have [installed and logged in](../studio/installation.md) to t Select the *File* -> *New Solution* in the main menu, or click the *New solution* button on the Welcome screen to open the *Create new solution* wizard: -![abp-studio-new-solution-dialog](images/abp-studio-no-layers-new-solution-dialog.png) +![abp-studio-new-solution-dialog](images/abp-studio-no-layers-new-solution-dialog-0.9.13.png) We will use the *Application (Single Layer)* solution template for this tutorial, so pick it and click the *Next* button: -![abp-studio-new-solution-dialog-solution-properties](images/abp-studio-no-layers-new-solution-dialog-solution-properties.png) +![abp-studio-new-solution-dialog-solution-properties](images/abp-studio-no-layers-new-solution-dialog-solution-properties-0.9.13.png) On that screen, you choose a name for your solution. You can use different levels of namespaces; e.g. `BookStore`, `Acme.BookStore` or `Acme.Retail.BookStore`. @@ -62,31 +45,35 @@ Then select an *output folder* to create your solution. The *Create solution fol Once your configuration is done, click the *Next* button to navigate to the *UI Framework* selection: -![abp-studio-new-solution-dialog-ui-framework](images/abp-studio-no-layers-new-solution-dialog-ui-framework.png) +![abp-studio-new-solution-dialog-ui-framework](images/abp-studio-no-layers-new-solution-dialog-ui-framework-0.9.13.png) Here, you see all the possible UI options supported by that startup solution template. Pick the **{{ UI_Value }}**. Notice that; Once you select a UI type, some additional options will be available under the UI Framework list. You can further configure the options or leave them as default and click the *Next* button for the *UI Theme* selection screen: -![abp-studio-new-solution-dialog-ui-theme](images/abp-studio-nolayers-new-solution-dialog-ui-theme.png) +![abp-studio-new-solution-dialog-ui-theme](images/abp-studio-nolayers-new-solution-dialog-ui-theme-0.9.13.png) LeptonX is the suggested UI theme that is proper for production usage. Select one of the themes, configure the additional options, and click the *Next* button for the *Database Provider* selection: {{ if DB == "EF" }} -![abp-studio-new-solution-dialog-database-provider](images/abp-studio-no-layers-new-solution-dialog-database-provider-efcore.png) +![abp-studio-new-solution-dialog-database-provider](images/abp-studio-no-layers-new-solution-dialog-database-provider-efcore-0.9.13.png) {{ else }} -![abp-studio-new-solution-dialog-database-provider](images/abp-studio-no-layers-new-solution-dialog-database-provider-mongo.png) +![abp-studio-new-solution-dialog-database-provider](images/abp-studio-no-layers-new-solution-dialog-database-provider-mongo-0.9.13.png) {{ end }} On that screen, you can decide on your database provider by selecting one of the provided options. There are some additional options for each database provider. Leave them as default or change them based on your preferences, then click the *Next* button for additional *Database Configurations*: {{ if DB == "EF" }} -![abp-studio-new-solution-dialog-database-configurations](images/abp-studio-no-layers-new-solution-dialog-database-configurations-efcore.png) +![abp-studio-new-solution-dialog-database-configurations](images/abp-studio-no-layers-new-solution-dialog-database-configurations-efcore-0.9.13.png) {{ else }} -![abp-studio-new-solution-dialog-database-configurations](images/abp-studio-no-layers-new-solution-dialog-database-configurations-mongo.png) +![abp-studio-new-solution-dialog-database-configurations](images/abp-studio-no-layers-new-solution-dialog-database-configurations-mongo-0.9.13.png) {{ end }} -Here, you can select the database management systems (DBMS){{ if DB == "EF" }} and the connection string{{ end }}. Now, we are ready to allow ABP Studio to create our solution. Just click the *Create* button and let the ABP Studio do the rest for you. +Here, you can select the database management systems (DBMS){{ if DB == "EF" }} and the connection string{{ end }}. Then, you can select optional modules and enable additional options according to your preferences. + +![abp-studio-no-layers-new-solution-additional-options](images/abp-studio-no-layers-new-solution-additional-options-0.9.13.png) + +Now, we are ready to allow ABP Studio to create our solution. Just click the *Create* button and let the ABP Studio do the rest for you. After clicking the Create button, the dialog is closed and your solution is loaded into ABP Studio: @@ -112,8 +99,6 @@ In the Solution Runner section (on the left side) you can see all the runnable a To start an application, either click the *Play* icon near to the application or right-click and select the *Run* -> *Start* context menu item. -> For the first run, you'll need to build the application. You can achieve this by selecting *Run* -> *Build & Start* from the context menu. - You can start the `Acme.BookStore`{{ if UI == "NG" }} and `Acme.BookStore.Angular`{{ end }}. Once the `Acme.BookStore{{ if UI == "NG" }}.Angular{{ end }}` application started, you can right-click it and select the *Browse* command: diff --git a/docs/en/images/generic-repositories.png b/docs/en/images/generic-repositories.png index 6287afc56c..00f751a7bf 100644 Binary files a/docs/en/images/generic-repositories.png and b/docs/en/images/generic-repositories.png differ diff --git a/docs/en/images/idle-message.png b/docs/en/images/idle-message.png new file mode 100644 index 0000000000..14ea10c9f8 Binary files /dev/null and b/docs/en/images/idle-message.png differ diff --git a/docs/en/images/idle-setting.png b/docs/en/images/idle-setting.png new file mode 100644 index 0000000000..faecb9174c Binary files /dev/null and b/docs/en/images/idle-setting.png differ diff --git a/docs/en/images/pen-test-alert-list-9.0.png b/docs/en/images/pen-test-alert-list-9.0.png new file mode 100644 index 0000000000..ad07a574ca Binary files /dev/null and b/docs/en/images/pen-test-alert-list-9.0.png differ diff --git a/docs/en/images/suite-registry.png b/docs/en/images/suite-registry.png new file mode 100644 index 0000000000..f1a968bfa8 Binary files /dev/null and b/docs/en/images/suite-registry.png differ diff --git a/docs/en/kb/can-not-login-with-admin-user.md b/docs/en/kb/can-not-login-with-admin-user.md new file mode 100644 index 0000000000..d69695b503 --- /dev/null +++ b/docs/en/kb/can-not-login-with-admin-user.md @@ -0,0 +1,5 @@ +# KB#0003: Can not login with the admin user + +* Try username `admin` and Password `1q2w3E*`. +* Try to migrate database. If you have a `DbMigrator` application in your solution, use it. It will seed initial data and create the admin user for you. +* If not works, read the README.MD file in your solution, or check the [Getting Started](https://abp.io/docs/latest/get-started) document. diff --git a/docs/en/kb/index.md b/docs/en/kb/index.md new file mode 100644 index 0000000000..5bbb201765 --- /dev/null +++ b/docs/en/kb/index.md @@ -0,0 +1,7 @@ +# ABP Knowledge Base + +The following documents provide useful information about several topics you might need to know. + +* KB#0001: ["Filename too long" error on Windows](windows-path-too-long-fix.md) +* KB#0002: [When to use a distributed cache server](when-to-use-a-distributed-cache-server.md) +* KB#0003: [Can not login with the admin user](can-not-login-with-admin-user.md) \ No newline at end of file diff --git a/docs/en/kb/when-to-use-a-distributed-cache-server.md b/docs/en/kb/when-to-use-a-distributed-cache-server.md new file mode 100644 index 0000000000..27fced60a9 --- /dev/null +++ b/docs/en/kb/when-to-use-a-distributed-cache-server.md @@ -0,0 +1,49 @@ +# KB#0002: When to Use a Distributed Cache Server + +ABP provides a [distributed cache service](../framework/fundamentals/caching.md) that is based on [ASP.NET Core's distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed). This document explains when you need to have a separate cache server for your applications. + +## Understanding the Default Cache Service + +**Default implementation of the cache service works in-memory**. Memory cache is only useful if you are building a monolith application and you run a single instance of your application. For other cases, **you should use a real distributed cache server**. + +Here are a few example cases where you should use a distributed cache server: + +* You have a **monolith application**, but you run **multiple instances** of that application concurrently, for example, in a [clustered environment](../deployment/clustered-environment.md) +* You build a **microservice** or any kind of **distributed** system +* You have web **multiple applications** in your solution and they should share the same cache + +The problem is obvious: If each application instance uses its internal in-memory cache, and if two or more applications cache the same data, it is probable that they will cache different copies of the data. In that case, there is no way to **invalidate/refresh** that data in every application's memory when the data changes. + +## What is a Distributed Cache Server + +A **distributed cache server** (e.g. [Redis](../framework/fundamentals/redis-cache.md)) stores cache objects in a separate server application and allows multiple applications/processes to share the same cache objects. In that way; + +* All applications/services and all their instances use the same cache store and share the same cached objects. Once an application instance refreshes a cached object, all others use the new object. +* Even if your applications stop and restart, the cached objects are not lost, since they are managed by a separate cache server. + +## How to Use a Distributed Cache Server + +ABP [solution templates](../solution-templates/index.md) come with Redis configured when it is certainly necessary. For example; + +* The [microservice startup template](../solution-templates/microservice/index.md) always comes with [Redis configured](../solution-templates/microservice/distributed-cache.md) and also included as a docker container. + +* The application startup template comes with Redis configured when you select multiple applications, tiered architecture, or some other configuration that requires a distributed cache server. + +In other cases, to keep the dependencies minimal, they come with the default (in-memory) cache configuration. In those cases, if you need a distributed cache server, you should manually switch to a distributed cache provider for your application. + +See the *[Redis Cache](../framework/fundamentals/redis-cache.md)* document if you need to use Redis as the distributed cache server. See [ASP.NET Core's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to see how to switch to another cache provider. + +### Installing a Redis Server to Your Local Environment + +If you want to use Redis as your distributed cache provider in your development environment, you can simply use the [official Redis docker image](https://hub.docker.com/_/redis). Once you have [Docker](https://www.docker.com/products/docker-desktop/) in your local machine, you can use the following command to run a Redis container and map the default Redis port: + +````bash +docker run -p 6379:6379 --name RedisServer -d redis +```` + +You can check the [official Redis docker image](https://hub.docker.com/_/redis) document for more options. + +## See Also + +* [ABP Distributed Cache](../framework/fundamentals/caching.md) +* [ASP.NET Core Distributed Cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) diff --git a/docs/en/kb/windows-path-too-long-fix.md b/docs/en/kb/windows-path-too-long-fix.md index 6a822c1402..1486abd40f 100644 --- a/docs/en/kb/windows-path-too-long-fix.md +++ b/docs/en/kb/windows-path-too-long-fix.md @@ -1,4 +1,4 @@ -# How to Fix "Filename too long" Error on Windows +# KB#0001: How to Fix "Filename too long" Error on Windows If you encounter the "filename too long" or "unzip" error on Windows, it's probably related to the Windows maximum file path limitation. Windows has a maximum file path limitation of 255 characters. diff --git a/docs/en/modules/account-pro.md b/docs/en/modules/account-pro.md index 327a274f84..0dd960e97e 100644 --- a/docs/en/modules/account-pro.md +++ b/docs/en/modules/account-pro.md @@ -358,3 +358,4 @@ This module doesn't define any additional distributed event. See the [standard d * [Impersonation](./account/impersonation.md) * [Linked Accounts](./account/linkedaccounts.md) * [Session Management](./account/session-management.md) +* [Idle Session Timeout](./account/idle-session-timeout.md)] diff --git a/docs/en/modules/account/idle-session-timeout.md b/docs/en/modules/account/idle-session-timeout.md new file mode 100644 index 0000000000..069e6db665 --- /dev/null +++ b/docs/en/modules/account/idle-session-timeout.md @@ -0,0 +1,19 @@ +# Idle Session Timeout + +The `Idle Session Timeout` feature allows you to automatically log out users after a certain period of inactivity. + +## Configure Idle Session Timeout + +You can enable/disable the `Idle Session Timeout` feature in the `Setting > Account > Idle Session Timeout` page. + +The default idle session timeout is 1 hour. You can change it by selecting a different value from the dropdown list or entering a custom value(in minutes). + +![idle-setting](../../images/idle-setting.png) + +Once the idle session timeout is reached, the user will see a warning modal before being logged out. if user does not respond for 60 seconds, the user will be logged out automatically. + +![idle-setting](../../images/idle-message.png) + +## How it works + +There is JavaScript code running in the background to detect user activity. such as mouse movement, key press, click, etc. If there is no activity detected for setting time, The warning modal will be shown to the user. diff --git a/docs/en/modules/docs.md b/docs/en/modules/docs.md index dc04339e78..69de3362a6 100644 --- a/docs/en/modules/docs.md +++ b/docs/en/modules/docs.md @@ -490,9 +490,6 @@ Since not every single document in your projects may not have sections or may no For example [Getting-Started.md](https://github.com/abpio/abp-commercial-docs/blob/master/en/getting-started.md): -``` -..... - ​```json //[doc-params] { @@ -502,9 +499,6 @@ For example [Getting-Started.md](https://github.com/abpio/abp-commercial-docs/bl } ​``` -........ -``` - This section will be automatically deleted during render. And f course, those key values must match with the ones in **Parameter document**. ![Interface](../images/docs-section-ui.png) @@ -513,7 +507,7 @@ Now you can use **Scriban** syntax to create sections in your document. For example: -```` +````text {{ if UI == "NG" }} * `-u` argument specifies the UI framework, `angular` in this case. @@ -672,22 +666,18 @@ The **Docs Module** supports referencing previous and next documents. It's usefu To reference the previous and next documents from a document, you should specify the documentation titles and their paths as follows: -``` - - ````json - //[doc-nav] - { - "Previous": { - "Name": "Overall", - "Path": "testing/overall" - }, - "Next": { - "Name": "Integration tests", - "Path": "testing/integration-tests" - } - } - ```` - +```json +//[doc-nav] +{ + "Previous": { + "Name": "Overall", + "Path": "testing/overall" + }, + "Next": { + "Name": "Integration tests", + "Path": "testing/integration-tests" + } +} ``` After you specify the next & previous documents, they will appear at the end of the current documentation like in the following figure: diff --git a/docs/en/others/penetration-test-report.md b/docs/en/others/penetration-test-report.md index c092a856ea..c9d8280405 100644 --- a/docs/en/others/penetration-test-report.md +++ b/docs/en/others/penetration-test-report.md @@ -1,6 +1,6 @@ # ABP Penetration Test Report -The ABP Commercial MVC `v8.3.0` application template has been tested against security vulnerabilities by the [OWASP ZAP v2.14.0](https://www.zaproxy.org/) tool. The demo web application was started on the `https://localhost:44349` address. The below alerts have been reported by the pentest tool. These alerts are sorted by the risk level as high, medium, and low. The informational alerts are not mentioned in this document. +The ABP Commercial MVC `v9.0.0` application template has been tested against security vulnerabilities by the [OWASP ZAP v2.14.0](https://www.zaproxy.org/) tool. The demo web application was started on the `https://localhost:44349` address. The below alerts have been reported by the pentest tool. These alerts are sorted by the risk level as high, medium, and low. The informational alerts are not mentioned in this document. Many of these alerts are **false-positive**, meaning the vulnerability scanner detected these issues, but they are not exploitable. It's clearly explained for each false-positive alert why this alert is a false-positive. @@ -10,14 +10,14 @@ In the next sections, you will find the affected URLs, attack parameters (reques There are high _(red flag)_, medium _(orange flag)_, low _(yellow flag)_, and informational _(blue flag)_ alerts. -![penetration-test-8.3.0](../images/pen-test-alert-list-8.3.png) +![penetration-test-9.0.0](../images/pen-test-alert-list-9.0.png) > The informational alerts are not mentioned in this document. These alerts are not raising any risks on your application and they are optional. ### Path Traversal [Risk: High] - False Positive -- *[GET] - https://localhost:44349/api/audit-logging/audit-logs?startTime=&endTime=&url=&userName=&applicationName=&clientIpAddress=&correlationId=&httpMethod=audit-logs&httpStatusCode=&maxExecutionDuration=&minExecutionDuration=&hasException=true&sorting=executionTime+desc&skipCount=0&maxResultCount=10* (attack: **httpMethod=audit-logs**) - *[POST] - https://localhost:44349/Account/Login* (attack: **\Login**) +- *[POST] - https://localhost:44349/Account/LinkLogin* (attack: **\LinkLogin**) - *[POST] - https://localhost:44349/Account/Register* (attack: **\Register**) - *[POST] - https://localhost:44349/Account/SecurityLogs* (attack: **\SecurityLogs**) - *[POST] - https://localhost:44349/Identity/SecurityLogs* (attack: **\SecurityLogs**) @@ -28,19 +28,15 @@ The Path Traversal attack technique allows an attacker access to files, director **Solution**: -This is a **false-positive** alert since ABP does all related checks for this kind of attack on the backend side for these endpoints. +This is a **false-positive** alert since ABP does all related checks for this kind of attacks on the backend side for these endpoints. ### SQL Injection [Risk: High] - False Positive * *[POST] — https://localhost:44349/Account/Login* (attack: **1q2w3E* AND 1=1 --**) -* *[POST] — https://localhost:44349/AuditLogs* (attack: **GET' AND '1'='1' --**) -* *[POST] — https://localhost:44349/Identity/SecurityLogs* (attack: **admin' AND '1'='1**) -* *[POST] — https://localhost:44349/api/account/verify-authenticator-code* (attack: **AND '1'='1**) -* *[POST] — https://localhost:44349/Identity/ClaimTypes/CreateModal* (attack: **aaaa AND '1'='1**) +* *[POST] — https://localhost:44349/Account/ImpersonateUser* (attack: **CfDJ8Pyqeg0vtHtJpnK-9eLaft7-JxLJfJ6WHKPOdBZVxz14BDo061qpJ2NLplgAn2Hw16ec0IR38_wWAUkJGxP8hL6PcLfH0bh-ATNTspWyWYTGGbiH-zeKWiS5vWX-br2BA1hE7Dc45eWGUZNcVc_vm2s AND 1=1 --**) +* *[POST] — https://localhost:44349/Abp/MultiTenancy/TenantSwitchModal* (attack: **CfDJ8Pyqeg0vtHtJpnK-9eLaft7-JxLJfJ6WHKPOdBZVxz14BDo061qpJ2NLplgAn2Hw16ec0IR38_wWAUkJGxP8hL6PcLfH0bh-ATNTspWyWYTGGbiH-zeKWiS5vWX-br2BA1hE7Dc45eWGUZNcVc_vm2s AND 1=1 --**) * *[POST] — https://localhost:44349/Identity/OrganizationUnits/\** (attack: **6f4cd0ab-f4eb-7ce0-8b26-3a138af1840d" AND '1'='1**) (also, several other URLs...) -* *[POST] — https://localhost:44349/Identity/ClaimTypes/EditModal* (attack: **aaaa AND '1'='1**) -* *[POST] — https://localhost:44349/LanguageManagement/Texts* (attack: **true" AND "1"="1" --**) -* *[POST] — https://localhost:44349/Account/Manage?CurrentPassword=ZAP%27+AND+%271%27%3D%271%27+--+&NewPassword=ZAP&NewPasswordConfirm=ZAP* +* *[POST] — https://localhost:44349/Identity/ClaimTypes/CreateModal* (attack: **aaaad AND '1'='1**) **Description**: @@ -95,11 +91,17 @@ There are only one URL that is reported as exposing error messages. This is a ** ### Content Security Policy (CSP) Header Not Set [Risk: Medium] — Positive (Fixed) - *[GET] — https://localhost:44349* +- *[GET] — https://localhost:44349/AuditLogs* +- *[GET] — https://localhost:44349/CookiePolicy* +- *[GET] — https://localhost:44349/Gdpr/PersonalData* +- *[GET] — https://localhost:44349/Identity/ClaimTypes/{0}* (create & edit modal URLs - also there are other modal related URLs...) - *[GET] — https://localhost:44349/AbpPermissionManagement/PermissionManagementModal?providerName=R&providerKey=role&providerKeyDisplayName=role* - *[GET] — https://localhost:44349/Abp/MultiTenancy/TenantSwitchModal* - *[GET] — https://localhost:44349/Account/AuthorityDelegation/AuthorityDelegationModal* - *[GET] — https://localhost:44349/Account/AuthorityDelegation/DelegateNewUserModal* - *[GET] — https://localhost:44349/Account/ForgotPassword _(other several account URLS)_* +- *[GET] — https://localhost:44349/Account/ExternalLogins _(other several account URLS)_* +- *[GET] — https://localhost:44349/Account/SecurityLogs _(other several account URLS)_* - *[GET] — https://localhost:44349/Account/Login _(other several account URLS)_* - *[GET] — https://localhost:44349/Account/Register _(other several account URLS)_* - *[GET] — https://localhost:44349/Account/Manage _(other several account URLS)_* @@ -143,12 +145,13 @@ The first affected URL is a **false-positive** alert since it's already fixed an The second URL is also a **false-positive** alert because there is no bad character string in the response. -> **Note**: However, it might be possible if you had any sensitive localization key-value pair in your localization entries, because this endpoint returns all localization values to be able to be used in the application. Therefore, keep that in mind while defining new localization entries. +> **Note**: However, it might be possible if you had any sensitive localization key-value pair in your localization entries, because this endpoint returns all localization values to be able to be used in the application. Therefore, keep that in mind while defining new localization entries. Pass the critical values in your code while using the localization entry as a parameter. ### XSLT Injection [Risk: Medium] - False Positive - *[GET] — https://localhost:44349/Abp/Languages/Switch?culture=%3Cxsl%3Avalue-of+select%3D%22system-property%28%27xsl%3Avendor%27%29%22%2F%3E&returnUrl=%2F&uiCulture=ar* - *[POST] — https://localhost:44349/Account/Login _(same URL with different parameters...)_* +- *[POST] — https://localhost:44349/Account/ImpersonateUser _(same URL with different parameters...)_* - *[POST] — https://localhost:44349/Account/Register _(same URL with different parameters...)_* - *[POST] — https://localhost:44349/Account/Manage _(same URL with different parameters...)_* - *[POST] — https://localhost:44349/Account/ForgotPassword _(same URL with different parameters...)_* @@ -161,13 +164,14 @@ Injection using XSL transformations may be possible and may allow an attacker to **Explanation**: -This is a **false-positive** alert. v8.3.0 uses .NET 8 and the XSLT transformation is not possible on .NET5 or higher. +This is a **false-positive** alert. v9.0 uses .NET 9 and the XSLT transformation is not possible on .NET5 or higher. ### Application Error Disclosure [Risk: Low] — False Positive +- *[GET] — https://localhost:44349/Abp/Languages/Switch* - *[POST] — https://localhost:44349/Account/ImpersonateUser* -- *[POST] — https://localhost:44349/Saas/Host/Editions* -- *[POST] — https://localhost:44349/Saas/Host/Tenants* +- *[GET] — https://localhost:44349/Account/ExternalLogins* +- *[GET] — https://localhost:44349/Account/Logout* **Description:** @@ -304,6 +308,7 @@ This vulnerability was reported as a positive alert because the application ran ### Timestamp Disclosure - Unix [Risk: Low] - False Positive - *[GET] — https://localhost:44349/libs/zxcvbn/zxcvbn.js?=* +- *[GET] — https://localhost:44349/libs/sweetalert2/sweetalert2.all.min.js?=* **Description**: @@ -319,8 +324,9 @@ This vulnerability was reported as a positive alert, because ABP uses the [zxcvb ### X-Content-Type-Options Header Missing [Risk: Low] - Positive (Fixed) -- *[GET] — https://localhost:44349/client-proxies/account-proxy.js?_v=638550091940000000 (and other client-proxies related URLs)* +- *[GET] — https://localhost:44349/client-proxies/account-proxy.js?_v=638550091940000000 (and other client-proxies related URLs...)* - *[GET] — https://localhost:44349/favicon.svg* +- *[GET] — https://localhost:44349/images/getting-started/bg-01.png* (and other image URLs...) - *[GET] — https://localhost:44349/global-styles.css?_v=638556076064360335* - *[GET] — https://localhost:44349/libs/@fortawesome/fontawesome-free/css/all.css?_v=%5CWEB-INF%5Cweb.xml (other several URLs...)* - other URLs... @@ -340,11 +346,3 @@ If possible, ensure that the end user uses a standards-compliant and modern web The `X-Content-Type-Options` header allows you to avoid MIME type sniffing by saying that the MIME types are deliberately configured. This headeer is not strictly required, but it is highly recommended for security reasons. While modern browsers have improved security features, you can still set this header for ensuring the security of web applications. You can add the [ABP's Security Header Middleware](../framework/ui/mvc-razor-pages/security-headers.md#security-headers-middleware) into the request pipeline to set the `X-Content-Type-Options` as *no-sniff*. Also, this middleware adds other pre-defined security headers to your application, including `X-XSS-Protection`, `X-Frame-Options` and `Content-Security-Policy` (if it's enabled). Read [Security Headers](../framework/ui/mvc-razor-pages/security-headers.md) documentation for more info. - -## Other Alerts (Fixed) - -The following alerts were reported by the community or our customers in v8.2 and fixed: - -* https://github.com/abpframework/abp/issues/19576 -* https://github.com/abpframework/abp/issues/19588 -* https://github.com/abpframework/abp/issues/19589 diff --git a/docs/en/release-info/migration-guides/openiddict5-to-6.md b/docs/en/release-info/migration-guides/openiddict5-to-6.md new file mode 100644 index 0000000000..f876f86265 --- /dev/null +++ b/docs/en/release-info/migration-guides/openiddict5-to-6.md @@ -0,0 +1,28 @@ +# OpenIddict 5.x to 6.x Migration Guide + +The 6.0 release of OpenIddict is a major release that introduces breaking changes. + +Check this blog [OpenIddict 6.0 general availability](https://kevinchalet.com/2024/12/17/openiddict-6-0-general-availability/) for the new features introduced in OpenIddict 6.0. and the [Migrate to OpenIddict 6.0](https://documentation.openiddict.com/guides/migration/50-to-60) for more information about the changes. + +In this guide, we will explain the changes you need to make to your ABP application. + +## Constant changes + +The following constants have been renamed: + +| Old Constant Name | New Constant Name | +|---------------------------------------------------------------|-----------------------------------------------------------------| +| `OpenIddictConstants.Permissions.Endpoints.Logout` | `OpenIddictConstants.Permissions.Endpoints.EndSession` | +| `OpenIddictConstants.Permissions.Endpoints.Device` | `OpenIddictConstants.Permissions.Endpoints.DeviceAuthorization` | + + +## IdentityModel packages + +If you have a reference to `IdentityModel` directly, please upgrade the necessary package versions to the latest stable version, which is currently 8.3.0: + +* [System.IdentityModel.Tokens.Jwt](https://www.nuget.org/packages/System.IdentityModel.Tokens.Jwt/) +* [Microsoft.IdentityModel.Protocols.OpenIdConnect](https://www.nuget.org/packages/Microsoft.IdentityModel.Protocols.OpenIdConnect/) +* [Microsoft.IdentityModel.Tokens](https://www.nuget.org/packages/Microsoft.IdentityModel.Tokens/) +* [Microsoft.IdentityModel.JsonWebTokens](https://www.nuget.org/packages/Microsoft.IdentityModel.JsonWebTokens/) + +That's all, it's a simple migration! If you have advanced usage of OpenIddict, please check the [official migration guide](https://documentation.openiddict.com/guides/migration/50-to-60) for more information. diff --git a/docs/en/samples/easy-crm.md b/docs/en/samples/easy-crm.md index 480aa80316..1a0fff3b3f 100644 --- a/docs/en/samples/easy-crm.md +++ b/docs/en/samples/easy-crm.md @@ -35,7 +35,7 @@ When you download and open the zip file, you will see two folders: * First, follow all the steps above to run the server side and seed the sample data. * Open a command prompt in the angular folder. -* Run the `yarn` command to install NPM packages (requires the [Yarn](https://yarnpkg.com/) package manager). +* Run the `yarn` command to install NPM packages (requires the [Yarn v1.22+ (not v2)](https://classic.yarnpkg.com/en/docs/install) package manager). * Run the `yarn start` command to run the Angular application. It will automatically open the `localhost://4200` in your default browser once the application initialized. ### Blazor UI diff --git a/docs/en/samples/eshop-on-abp/index.md b/docs/en/samples/eshop-on-abp/index.md index 068d39f92b..ac45772150 100644 --- a/docs/en/samples/eshop-on-abp/index.md +++ b/docs/en/samples/eshop-on-abp/index.md @@ -1,3 +1,8 @@ # 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. +> +> The new template offers a modernized and officially supported starting point for building microservices with ABP. Please consider transitioning to the new template for the latest features and improvements. + This document is in progress. Please see the source code for now: https://github.com/abpframework/eShopOnAbp diff --git a/docs/en/samples/index.md b/docs/en/samples/index.md index 793a777ad1..8c8733408d 100644 --- a/docs/en/samples/index.md +++ b/docs/en/samples/index.md @@ -13,9 +13,11 @@ A reference application built with ABP. It implements the Domain Driven Design w Reference microservice solution built with ABP and .NET. -* [Live demo](https://www.eshoponabp.com/) * [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). diff --git a/docs/en/solution-templates/application-module/images/additional-options.png b/docs/en/solution-templates/application-module/images/additional-options.png new file mode 100644 index 0000000000..216c60e50f Binary files /dev/null and b/docs/en/solution-templates/application-module/images/additional-options.png differ diff --git a/docs/en/solution-templates/application-module/images/create-new-module.png b/docs/en/solution-templates/application-module/images/create-new-module.png new file mode 100644 index 0000000000..0b92070b66 Binary files /dev/null and b/docs/en/solution-templates/application-module/images/create-new-module.png differ diff --git a/docs/en/solution-templates/application-module/images/issuemanagement-module-solution.png b/docs/en/solution-templates/application-module/images/issuemanagement-module-solution.png new file mode 100644 index 0000000000..c0c77fb529 Binary files /dev/null and b/docs/en/solution-templates/application-module/images/issuemanagement-module-solution.png differ diff --git a/docs/en/solution-templates/application-module/images/new-module.png b/docs/en/solution-templates/application-module/images/new-module.png new file mode 100644 index 0000000000..af046d16c3 Binary files /dev/null and b/docs/en/solution-templates/application-module/images/new-module.png differ diff --git a/docs/en/solution-templates/application-module/images/new-solution.png b/docs/en/solution-templates/application-module/images/new-solution.png new file mode 100644 index 0000000000..29e6fe4b74 Binary files /dev/null and b/docs/en/solution-templates/application-module/images/new-solution.png differ diff --git a/docs/en/solution-templates/application-module/images/select-database-provider.png b/docs/en/solution-templates/application-module/images/select-database-provider.png new file mode 100644 index 0000000000..1ff834ee1a Binary files /dev/null and b/docs/en/solution-templates/application-module/images/select-database-provider.png differ diff --git a/docs/en/solution-templates/application-module/images/select-user-interface.png b/docs/en/solution-templates/application-module/images/select-user-interface.png new file mode 100644 index 0000000000..b0eecbc25d Binary files /dev/null and b/docs/en/solution-templates/application-module/images/select-user-interface.png differ diff --git a/docs/en/solution-templates/application-module/images/solution-properties.png b/docs/en/solution-templates/application-module/images/solution-properties.png new file mode 100644 index 0000000000..67549db2c5 Binary files /dev/null and b/docs/en/solution-templates/application-module/images/solution-properties.png differ diff --git a/docs/en/solution-templates/application-module/index.md b/docs/en/solution-templates/application-module/index.md index 857b4ed936..0c0f86ddbd 100644 --- a/docs/en/solution-templates/application-module/index.md +++ b/docs/en/solution-templates/application-module/index.md @@ -4,43 +4,51 @@ This template can be used to create a **reusable [application module](../../modu ## How to Start With? -You can use the [ABP CLI](../../cli) to create a new project using this startup template. Alternatively, you can generate a CLI command from the [Get Started](https://abp.io/get-started) page. CLI approach is used here. +You can use the [ABP CLI](../../cli) or [ABP Studio](../../studio/overview.md) to create a new project using this startup template. Alternatively, you can generate a CLI command from the [Get Started](https://abp.io/get-started) page. We will use the ABP Studio for this guide. -First, install the ABP CLI if you haven't installed before: +First, install the ABP Studio if you haven't installed before. You can follow the [installation guide](../../studio/installation.md) for this. -```bash -dotnet tool install -g Volo.Abp.Studio.Cli -``` +### Creating a New Empty Solution -Then use the `abp new` command in an empty folder to create a new solution: +Open the ABP Studio and click the `New solution` button in the welcome page or the `File > New Solution` top menu item. Select the `Empty Solution` template and click the `Next` button. -```bash -abp new-module Acme.BookStore -``` +![New Solution](images/new-solution.png) + +Enter the solution name, select the solution folder and click the `Create` button. + +![Solution Properties](images/solution-properties.png) - `Acme.IssueManagement` is the solution name, like *YourCompany.YourProduct*. You can use single level, two-levels or three-levels naming. -### Specifying the User Interface +> To understand the terms solution, module, and package, refer to the ABP Studio [concepts](../../studio/concepts.md) document. + +### Creating a New DDD Module + +When you create a new solution, the solution explorer on the left side of the screen will appear empty. Right-click on the root of the solution and select `Add > New Module > DDD Module` from the context menu. + +![New Module](images/new-module.png) + +The `Create New Module` dialog will open. Enter the module name and click the `Next` button. + +![Create New Module](images/create-new-module.png) + +Now, you can select the user interface options or leave it empty to create a module without a user interface. A module can support multiple user interfaces, such as MVC, Blazor, Angular, etc., or none at all. Click the `Next` button to specify the database provider. -The template comes without a user interface by default. You can use the `mvc`, `blazor`, `blazor-server`, or `angular` options to include any of these UI layers. You can also combine them. For example, you can use `mvc,angular` to include both MVC and Angular UI. To create a module without a user interface, don't specify any value. +![Select User Interface](images/select-user-interface.png) -````bash -abp new-module Acme.IssueManagement -u mvc,angular -```` +Select the database provider(s) you want to use in your module. You can choose `EntityFrameworkCore`, `MongoDB`, or both. Unlike the user interface options, you must select at least one database provider. Click the `Next` button to see the additional options. -#### Specifying the Database Provider +![Select Database Provider](images/select-database-provider.png) -The template comes with the *EntityFrameworkCore* database provider by default. You can use the `ef` or `mongodb` options to include either of these providers. You can also combine them. For example, you can use `ef,mongodb` to include both EntityFrameworkCore and MongoDB. +You can exclude the test projects from the module by unchecking the `Include Tests` option. Click the `Create` button to create the module. -````bash -abp new-module Acme.IssueManagement -d ef,mongodb -```` +![Additional Options](images/additional-options.png) ## Solution Structure Based on the options you've specified, you will get a slightly different solution structure. If you don't specify any option, you will have a solution like shown below: -![issuemanagement-module-solution](../../images/issuemanagement-module-solution.png) +![issuemanagement-module-solution](images/issuemanagement-module-solution.png) Projects are organized as `src` and`test` folders: diff --git a/docs/en/solution-templates/index.md b/docs/en/solution-templates/index.md index b5bf59688d..739dbc453e 100644 --- a/docs/en/solution-templates/index.md +++ b/docs/en/solution-templates/index.md @@ -1,4 +1,4 @@ -# Solution Templates +# Startup Solution Templates ABP provides pre-architected and production-ready templates to jump start a new solution. diff --git a/docs/en/solution-templates/layered-web-application/authentication.md b/docs/en/solution-templates/layered-web-application/authentication.md new file mode 100644 index 0000000000..0133e95246 --- /dev/null +++ b/docs/en/solution-templates/layered-web-application/authentication.md @@ -0,0 +1,56 @@ +# Layered Solution: Authentication + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Built-In Features", + "Path": "solution-templates/layered-web-application/built-in-features" + }, + "Next": { + "Name": "Database configurations in the Layered solution", + "Path": "solution-templates/layered-web-application/database-configurations" + } +} +``` + +> Some of the features mentioned in this document may not be available in the free version. We're using the **\*** symbol to indicate that a feature is available in the **[Team](https://abp.io/pricing)** and **[Higher](https://abp.io/pricing)** licenses. + +The [Layered solution template](index.md) is fully configured for authentication. All the services and applications are configured to use the [OpenIddict](https://documentation.openiddict.com) library for authentication. They are configured in a common way for authentication. This document explains that common authentication structure. + +If you choose the *Tiered* option while [creating](../../get-started/layered-web-application.md#creating-a-new-solution) the solution, the solution will have the `*.AuthServer` project. + +## OpenIddict + +[OpenIddict](https://documentation.openiddict.com) is an open-source library that provides a simple and easy way to implement an OpenID Connect server in your application. ABP has built-in modules ([OpenIddict](../../modules/openiddict.md), [OpenIddict UI **\***](../../modules/openiddict-pro.md)) to integrate OpenIddict into the solution. + +## Domain Layer + +The layered solution template *Domain* layer is the responsible for the OpenIddict definitions (Applications, Scopes, etc.). Also, it provides the *OpenIddictDataSeedContributor* class to seed the initial data. It creates the default clients (applications) and scopes for the solution. + +The [OpenIddict UI **\***](../../modules/openiddict-pro.md) module is added only if you choose the OpenIddict UI module while creating the solution. + +![new-solution-openiddict-module](images/new-solution-openiddict-module.png) + +The OpenIddict UI **\*** module provides a user interface to manage the OpenIddict entities such as applications, scopes, etc. You can manage these entities from the application UI. + +![openiddict-ui](images/openiddict-ui.png) + +## The Authentication Application + +The solution may include an external authentication server (`auth-server`) application if you select the *Tiered* option during solution creation. Otherwise, the authentication server is integrated into one of the [Web Applications](web-applications.md). + +The authentication server handles token generation, validation, and user account management (e.g., login, registration). It uses the [Account](../../modules/account.md) or [Account Pro **\***](../../modules/account-pro.md) module. The [Account Pro **\***](../../modules/account-pro.md) module additionally supports [social logins](../../modules/account-pro.md#social--external-logins) (e.g., Google, Facebook). Social logins can be enabled, disabled, and configured directly from the application's user interface. + +![account-external-provider](images/account-external-provider.png) + +## Authentication Flows + +Applications in the solution use different authentication flows depending on the application type: + +- **MVC UI Web Application**: + Uses the [Hybrid Flow](https://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth) (OpenID Connect Authentication) for user authentication. +- **SPA and Swagger Applications**: + Use the [Authorization Code Flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth) to authenticate users. + +Once a user logs into the system and obtains a token from the authentication server, the `*.HttpApi.Host` application use [JWT Bearer Authentication](https://jwt.io/introduction/) to authorize the user's actions. diff --git a/docs/en/solution-templates/layered-web-application/background-jobs.md b/docs/en/solution-templates/layered-web-application/background-jobs.md new file mode 100644 index 0000000000..beef27d5d0 --- /dev/null +++ b/docs/en/solution-templates/layered-web-application/background-jobs.md @@ -0,0 +1,19 @@ +# Layered Solution: Background Jobs + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Swagger integration", + "Path": "solution-templates/layered-web-application/swagger-integration" + }, + "Next": { + "Name": "Background Workers", + "Path": "solution-templates/layered-web-application/background-workers" + } +} +``` + +Background jobs are long-running, asynchronous tasks that operate in the background of your application. They are ideal for non-time-sensitive tasks, such as sending emails, generating reports, or processing data. These jobs are usually triggered by a user action or a scheduled task. For more information, refer to the [Background Jobs](../../framework/infrastructure/background-jobs/index.md) document. + +In the layered solution template, background jobs are implemented using the [Background Jobs](../../modules/background-jobs.md) module. This module offers a simple and efficient way to create and manage background jobs in your application. It provides features like job queues and job scheduling. Job information is stored in the database, enabling you to track job statuses and retry failed jobs. \ No newline at end of file diff --git a/docs/en/solution-templates/layered-web-application/background-workers.md b/docs/en/solution-templates/layered-web-application/background-workers.md new file mode 100644 index 0000000000..500a21756b --- /dev/null +++ b/docs/en/solution-templates/layered-web-application/background-workers.md @@ -0,0 +1,63 @@ +# Layered Solution: Background Workers + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Background Jobs", + "Path": "solution-templates/layered-web-application/background-jobs" + }, + "Next": { + "Name": "Distribution Locking", + "Path": "solution-templates/layered-web-application/distributed-locking" + } +} +``` + +Background workers are long-running processes that operate in the background of your application. They are ideal for non-time-sensitive tasks, such as processing data, sending notifications, or monitoring system health. Typically, background workers start when the application launches and run continuously until the application stops. For more information, refer to the [Background Workers](../../framework/infrastructure/background-workers/index.md) document. + +Basically, you can create scheduled workers to run at specific time intervals based on your requirements. For example, you might create a worker to check the status of inactive users and change their status to passive if they haven't logged in to the application in the last 30 days. + +```csharp +public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase +{ + public PassiveUserCheckerWorker( + AbpAsyncTimer timer, + IServiceScopeFactory serviceScopeFactory) : base( + timer, + serviceScopeFactory) + { + Timer.Period = 600000; //10 minutes + } + + protected async override Task DoWorkAsync( + PeriodicBackgroundWorkerContext workerContext) + { + Logger.LogInformation("Starting: Setting status of inactive users..."); + + // Resolve dependencies + var userRepository = workerContext + .ServiceProvider + .GetRequiredService(); + + // Do the work + await userRepository.UpdateInactiveUserStatusesAsync(); + + Logger.LogInformation("Completed: Setting status of inactive users..."); + } +} +``` + +After creating a worker, you should also register it in the application. You might add it in the *Domain* or *Application* layer. You can register your worker in the `OnApplicationInitializationAsync` method of your module class: + +```csharp +public class BookstoreApplicationModule : AbpModule +{ + public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) + { + await context.AddBackgroundWorkerAsync(); + } +} +``` + +> When scaling out your application in a distributed system, it's crucial to consider that the same background workers might run on multiple instances of the same service. This requires careful management of potential side effects. For example, if you're processing messages from a queue, you need to ensure that each message is processed only once. To prevent multiple instances from handling the same message, you can use [distributed locking](../../framework/infrastructure/distributed-locking.md). diff --git a/docs/en/solution-templates/layered-web-application/blob-storing.md b/docs/en/solution-templates/layered-web-application/blob-storing.md new file mode 100644 index 0000000000..9e1332e03a --- /dev/null +++ b/docs/en/solution-templates/layered-web-application/blob-storing.md @@ -0,0 +1,51 @@ +# Layered Solution: BLOB Storing + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Multi-Tenancy", + "Path": "solution-templates/layered-web-application/multi-tenancy" + }, + "Next": { + "Name": "CORS Configuration", + "Path": "solution-templates/layered-web-application/cors-configuration" + } +} +``` + +> Some of the features mentioned in this document may not be available in the free version. We're using the **\*** symbol to indicate that a feature is available in the **[Team](https://abp.io/pricing)** and **[Higher](https://abp.io/pricing)** licenses. + +This document explains how to store BLOBs (Binary Large Objects) in a layered solution. It is common to store files, images, videos, and other large objects in a distributed system. You can learn more about BLOB storage in the [BLOB Storing System](../../framework/infrastructure/blob-storing/index.md) documentation. + +In the layered solution template, the [Database Provider](../../framework/infrastructure/blob-storing/database.md) is used to store BLOBs in the database. The `Volo.Abp.BlobStoring.Database.EntityFrameworkCore` or `Volo.Abp.BlobStoring.Database.MongoDB` package provides the necessary implementations to store and retrieve BLOBs in the database. This setup is integrated into the layered solution template and is used in all related projects. You can change the database configuration in the `appsettings.json` file of the related project. + +You can use the `IBlobContainer` or `IBlobContainer` service to store and retrieve BLOBs. Here is an example of storing a BLOB: + +```csharp +public class MyService : ITransientDependency +{ + private readonly IBlobContainer _blobContainer; + + public MyService(IBlobContainer blobContainer) + { + _blobContainer = blobContainer; + } + + public async Task SaveBytesAsync(byte[] bytes) + { + await _blobContainer.SaveAsync("my-blob-1", bytes); + } + + public async Task GetBytesAsync() + { + return await _blobContainer.GetAllBytesOrNullAsync("my-blob-1"); + } +} +``` + +## File Management Module + +The *File Management* module is optional and can be added to the solution during the creation process. It provides a user interface to manage folders and files. You can learn more about the module in the [File Management *](../../modules/file-management.md) document. + +![file-management](images/file-management-index-page.png) diff --git a/docs/en/solution-templates/layered-web-application/built-in-features.md b/docs/en/solution-templates/layered-web-application/built-in-features.md new file mode 100644 index 0000000000..b5dba7c444 --- /dev/null +++ b/docs/en/solution-templates/layered-web-application/built-in-features.md @@ -0,0 +1,25 @@ +# Layered Solution: Built-In Features + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Mobile Applications", + "Path": "solution-templates/layered-web-application/mobile-applications" + }, + "Next": { + "Name": "Authentication", + "Path": "solution-templates/layered-web-application/authentication" + } +} +``` + +The Layered solution template includes several built-in features to help you get started with your layered web application. These features are designed to provide a solid foundation for your application and help you focus on your business logic. This document provides an overview of the built-in features included in the Layered solution template. The following documents explains these features in details: + +* [Authentication](authentication.md) +* [Database configurations](database-configurations.md) +* [Logging (with Serilog)](logging.md) +* [Swagger integration](swagger-integration.md) +* [Multi-Tenancy](multi-tenancy.md) +* [BLOB storing](blob-storing.md) +* [CORS configuration](cors-configuration.md) \ No newline at end of file diff --git a/docs/en/solution-templates/layered-web-application/cors-configuration.md b/docs/en/solution-templates/layered-web-application/cors-configuration.md new file mode 100644 index 0000000000..6df207c107 --- /dev/null +++ b/docs/en/solution-templates/layered-web-application/cors-configuration.md @@ -0,0 +1,37 @@ +# Layered Solution: CORS Configuration + +```json +//[doc-nav] +{ + "Previous": { + "Name": "BLOB Storing", + "Path": "solution-templates/layered-web-application/blob-storing" + }, + "Next": { + "Name": "Helm Charts and Kubernetes", + "Path": "solution-templates/layered-web-application/helm-charts-and-kubernetes" + } +} +``` + +Cross-Origin Resource Sharing (CORS) is a security feature that allows web applications to make requests to a different domain than the one that served the web page. + +In the layered solution template, CORS configuration is applied in the following cases: +- If you select the [Tiered solution](solution-structure.md#tiered-structure-). +- If you choose [Angular](web-applications.md#angular) as the web application type. +- If you choose [Blazor WebAssembly](web-applications.md#blazor-webassembly) as the web application type. +- If you choose [No UI](web-applications.md#no-ui) as the web application type. + +The CORS settings are configured in the `appsettings.json` file of the corresponding project. Typically, the web application serves as the entry point for front-end applications, so it must be configured to accept requests from different origins. + +The default configuration in `appsettings.json` is as follows: + +```json +{ + "App": { + "CorsOrigins": "https://*.MyProjectName.com" + } +} +``` + +You can modify the `CorsOrigins` property to include additional domains or wildcard subdomains as required by your application. \ No newline at end of file diff --git a/docs/en/solution-templates/layered-web-application/database-configurations.md b/docs/en/solution-templates/layered-web-application/database-configurations.md new file mode 100644 index 0000000000..0776062c4f --- /dev/null +++ b/docs/en/solution-templates/layered-web-application/database-configurations.md @@ -0,0 +1,266 @@ +# Layered Solution: Database configurations + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Authentication", + "Path": "solution-templates/layered-web-application/authentication" + }, + "Next": { + "Name": "Logging (with Serilog)", + "Path": "solution-templates/layered-web-application/logging" + } +} +``` + +> Some of the features mentioned in this document may not be available in the free version. We're using the **\*** symbol to indicate that a feature is available in the **[Team](https://abp.io/pricing)** and **[Higher](https://abp.io/pricing)** licenses. + +ABP Studio's Layered Solution Template includes pre-configured database settings. This document explains how to manage database configurations in your solution. + +## Connection Strings + +Connection strings are stored in the `appsettings.json` file. You can customize them for different environments by modifying the respective `appsettings.json` files. The `*.DbMigrator` project and one of the [Web Application](web-applications.md) projects use the `Default` connection string by default. + +To change the connection string for the `Default` key, update the `appsettings.json` file in your project. Connection strings are defined under the `ConnectionStrings` section, as shown below: + +```json +{ + "ConnectionStrings": { + "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=Bookstore;Trusted_Connection=True;TrustServerCertificate=true" + } +} +``` + +### The DbContext Class + +In the `*.EntityFrameworkCore` project, the `DbContext` class is defined. The `DbContext` class is derived from the `AbpDbContext` class, which is a part of the ABP Framework. + +```csharp +[ReplaceDbContext(typeof(IIdentityProDbContext))] +[ReplaceDbContext(typeof(ISaasDbContext))] +[ConnectionStringName("Default")] +public class BookstoreDbContext : + AbpDbContext, + ISaasDbContext, + IIdentityProDbContext +{ + #region Entities from the modules + + // Identity + public DbSet Users { get; set; } + public DbSet Roles { get; set; } + public DbSet ClaimTypes { get; set; } + public DbSet OrganizationUnits { get; set; } + public DbSet SecurityLogs { get; set; } + public DbSet LinkUsers { get; set; } + public DbSet UserDelegations { get; set; } + public DbSet Sessions { get; set; } + + // SaaS + public DbSet Tenants { get; set; } + public DbSet Editions { get; set; } + public DbSet TenantConnectionStrings { get; set; } + + #endregion + + public BookstoreDbContext(DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.ConfigurePermissionManagement(); + builder.ConfigureSettingManagement(); + builder.ConfigureBackgroundJobs(); + builder.ConfigureAuditLogging(); + builder.ConfigureFeatureManagement(); + builder.ConfigureIdentityPro(); + builder.ConfigureOpenIddictPro(); + builder.ConfigureLanguageManagement(); + builder.ConfigureSaas(); + builder.ConfigureTextTemplateManagement(); + builder.ConfigureGdpr(); + builder.ConfigureCmsKit(); + builder.ConfigureCmsKitPro(); + builder.ConfigureBlobStoring(); + + /* Configure your own tables/entities inside here */ + + //builder.Entity(b => + //{ + // b.ToTable(BookstoreConsts.DbTablePrefix + "YourEntities", BookstoreConsts.DbSchema); + // b.ConfigureByConvention(); //auto configure for the base class props + // //... + //}); + } +} +``` + +#### ConnectionStringName Attribute + +We're using the *Default* connection string in the `BookstoreDbContext` class. You can change the connection string name by updating the `ConnectionStringName` attribute. + +```csharp +[ConnectionStringName("Default")] +``` + +[The `ConnectionStringName` attribute](../../framework/fundamentals/connection-strings.md#set-the-connection-string-name) defines the unique name of the connection string that is being used by that `DbContext` class. It matches with the connection string defined in the `appsettings.json` file. That name is also used in database migrations to distinguish different database schemas, and used as the key while storing tenant connection strings for a multi-tenant system. + +#### ReplaceDbContext Attribute + +```csharp +[ReplaceDbContext(typeof(IIdentityProDbContext))] +[ReplaceDbContext(typeof(ISaasDbContext))] +``` + +The application DbContext utilizes the [Identity](../../modules/identity.md) and [Saas **\***](../../modules/saas.md) modules and creates a single database that contains these modules database schemas. These modules define their own `DbContext` class normally. But [the `ReplaceDbContext` attribute](../../framework/data/entity-framework-core/index.md#replace-other-dbcontextes) tells to ABP to use this (`BookstoreDbContext`) `DbContext` class instead of the `DbContext` classes defined by these modules. Technically, it replaces the given `DbContext` classes on runtime. We are doing that to ensure that we have a single (merged) database schema, single database migration path and a single database transaction operation when we work these multiple modules. When we replace a `DbContext`, we should implement its interface as done with the `BookstoreDbContext` class: + +````csharp +public class BookstoreDbContext : + AbpDbContext, + ISaasDbContext, + IIdentityProDbContext +```` + +* That class implements `ISaasDbContext` and `IIdentityProDbContext`, so these modules can use it. + +As the next part, the `BookstoreDbContext` class defines the following properties those are forced by the implemented interfaces: + +```csharp +// Identity +public DbSet Users { get; set; } +public DbSet Roles { get; set; } +public DbSet ClaimTypes { get; set; } +public DbSet OrganizationUnits { get; set; } +public DbSet SecurityLogs { get; set; } +public DbSet LinkUsers { get; set; } +public DbSet UserDelegations { get; set; } +public DbSet Sessions { get; set; } + +// SaaS +public DbSet Tenants { get; set; } +public DbSet Editions { get; set; } +public DbSet TenantConnectionStrings { get; set; } +``` + +#### OnModelCreating Method + +The `OnModelCreating` method is used to configure the database schema. It calls the `Configure*` methods of the ABP Framework to configure the database schema for the modules. You can also configure your own tables/entities inside this method. + +```csharp +protected override void OnModelCreating(ModelBuilder builder) +{ + base.OnModelCreating(builder); + + builder.ConfigurePermissionManagement(); + builder.ConfigureSettingManagement(); + builder.ConfigureBackgroundJobs(); + builder.ConfigureAuditLogging(); + builder.ConfigureFeatureManagement(); + builder.ConfigureIdentityPro(); + builder.ConfigureOpenIddictPro(); + builder.ConfigureLanguageManagement(); + builder.ConfigureSaas(); + builder.ConfigureTextTemplateManagement(); + builder.ConfigureGdpr(); + builder.ConfigureCmsKit(); + builder.ConfigureCmsKitPro(); + builder.ConfigureBlobStoring(); + + /* Configure your own tables/entities inside here */ + + //builder.Entity(b => + //{ + // b.ToTable(BookstoreConsts.DbTablePrefix + "YourEntities", BookstoreConsts.DbSchema); + // b.ConfigureByConvention(); //auto configure for the base class props + // //... + //}); +} +``` + +> The `Configure*` methods are extension methods defined in each module's `EntityFrameworkCore` project. These methods are used to configure the database schema for their respective modules. At runtime, the `DbContext` class is replaced by the `BookstoreDbContext` class only for the `DbContext` classes that use the `ReplaceDbContext` attribute. For other modules, their own dedicated `DbContext` classes are used without replacement. + +### The `IDesignTimeDbContextFactory` Implementation + +The `IDesignTimeDbContextFactory` interface is used to create a `DbContext` instance at design time. It is used by EF Core tools to create migrations and update the database. The `BookstoreDbContextFactory` class implements the `IDesignTimeDbContextFactory` interface to create a `BookstoreMigrationsDbContext` instance. + +```csharp +public class BookstoreDbContextFactory : IDesignTimeDbContextFactory +{ + public BookstoreDbContext CreateDbContext(string[] args) + { + var configuration = BuildConfiguration(); + + BookstoreEfCoreEntityExtensionMappings.Configure(); + + var builder = new DbContextOptionsBuilder() + .UseSqlServer(configuration.GetConnectionString("Default")); + + return new BookstoreDbContext(builder.Options); + } + + private static IConfigurationRoot BuildConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "../Acme.Bookstore.DbMigrator/")) + .AddJsonFile("appsettings.json", optional: false); + + return builder.Build(); + } +} +``` + +### Configuration + +In the `*.EntityFrameworkCore` project, the `BookstoreEntityFrameworkCoreModule` class is used to configure the database context. + +```csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + context.Services.AddAbpDbContext(options => + { + /* Remove "includeAllEntities: true" to create + * default repositories only for aggregate roots */ + options.AddDefaultRepositories(includeAllEntities: true); + }); + + Configure(options => + { + /* The main point to change your DBMS. + * See also BookstoreDbContextFactory for EF Core tooling. */ + options.UseSqlServer(); + }); + +} +``` + +We are basically setting the SQL Server as the default DBMS for this application. and registering the `BookstoreDbContext` class to the [dependency injection](../../framework/fundamentals/dependency-injection.md) system. + +### SaaS Module: The Tenant Management UI **\*** + +SaaS module provides the necessary UI to set and change connection string for tenants and trigger the database migrations. + +#### The Connection String Management Modal + +You can click to the *Database Connection Strings* command in the *Actions* dropdown button for a tenant in the *Tenants* page of the SaaS module: + +![Database Connection Strings](images/database-connection-strings.png) + +It opens the *Database Connection Strings* modal as shown below: + +![Database Connection Strings Modal](images/database-connection-strings-modal.png) + +Here, we can set a *Default connection string* for the tenant. + +When you make the changes and save the dialog, the database is automatically created and migrated. If you later update the connection string (for example if you change the database name), it will also trigger the database migration process again. + +#### Manually Applying the Database Migrations + +If you need to manually trigger the database migrations for a specific tenant, click the *Actions* dropdown for the related tenant and select the *Apply Database Migrations* command on the *Tenant Management* page of the SaaS module: + +![Apply Database Migrations](images/apply-database-migrations.png) diff --git a/docs/en/solution-templates/layered-web-application/db-migrator.md b/docs/en/solution-templates/layered-web-application/db-migrator.md new file mode 100644 index 0000000000..50fd38d836 --- /dev/null +++ b/docs/en/solution-templates/layered-web-application/db-migrator.md @@ -0,0 +1,41 @@ +# Layered Solution: Db Migrator + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Web Applications", + "Path": "solution-templates/layered-web-application/web-applications" + }, + "Next": { + "Name": "Mobile Applications", + "Path": "solution-templates/layered-web-application/mobile-applications" + } +} +```` + +## Db Migrator Project + +The Db Migrator project is a console application designed to handle database schema migrations and seed data population. It operates as a standalone application that can be executed on-demand or integrated into a CI/CD pipeline. + +### Usage + +You can run the Db Migrator application: +- From the command line. +- Directly from Visual Studio. + +### Configuration + +The Db Migrator project maintains its own configuration, separate from the main application. If you need to update the database connection string or any related settings, ensure that changes are applied consistently to both the main application and the Db Migrator to avoid discrepancies. + +## Folder Structure + +In the `*.DbMigrator` project, you will find the `DbMigratorHostedService` class, which is responsible for executing database migrations and seeding data. This class is registered in the `Program` class and starts running when the application is launched. + +### Layers and Responsibilities + +- **`*.Domain` Layer**: + Contains the `Data` folder, which holds the necessary classes for managing database migrations and seed data. However, since the `*.Domain` layer does not reference the `EntityFrameworkCore` package, it only defines the abstraction for data migration. + +- **`*.EntityFrameworkCore` Layer**: + This layer is responsible for implementing database schema migrations. It includes the `EntityFrameworkCore[ProjectName]DbSchemaMigrator` class, which handles the actual migration logic using the `EntityFrameworkCore` package. diff --git a/docs/en/solution-templates/layered-web-application/distributed-locking.md b/docs/en/solution-templates/layered-web-application/distributed-locking.md new file mode 100644 index 0000000000..652a9e4376 --- /dev/null +++ b/docs/en/solution-templates/layered-web-application/distributed-locking.md @@ -0,0 +1,44 @@ +# Layered Solution: Distributed Locking + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Background Workers", + "Path": "solution-templates/layered-web-application/background-workers" + }, + "Next": { + "Name": "Multi-Tenancy", + "Path": "solution-templates/layered-web-application/multi-tenancy" + } +} +``` + +Distributed locking is a mechanism that enables multiple instances of an application to coordinate and synchronize access to shared resources. It is particularly useful in scenarios where multiple instances need to ensure that only one instance can access a resource at a time. For more information, refer to the [Distributed Locking](../../framework/infrastructure/distributed-locking.md) document. + +## Distributed Locking in Layered Solutions + +The layered solution template does not include the distributed lock package by default unless it's a *Tiered* or *Public Website* application. To use distributed locking, you can add the [Volo.Abp.DistributedLock](https://www.nuget.org/packages/Volo.Abp.DistributedLocking) package to your project. This package provides a distributed lock mechanism that works with Redis. You can inject the `IAbpDistributedLock` service to acquire and release locks. Below is an example of using distributed locking in your application: + +```csharp +public class MyService : ITransientDependency +{ + private readonly IAbpDistributedLock _distributedLock; + + public MyService(IAbpDistributedLock distributedLock) + { + _distributedLock = distributedLock; + } + + public async Task MyMethodAsync() + { + await using (var handle = await _distributedLock.TryAcquireAsync("MyLockName")) + { + if (handle != null) + { + // your code that access the shared resource + } + } + } +} +``` diff --git a/docs/en/solution-templates/layered-web-application/helm-charts-and-kubernetes.md b/docs/en/solution-templates/layered-web-application/helm-charts-and-kubernetes.md new file mode 100644 index 0000000000..dcdd805e7f --- /dev/null +++ b/docs/en/solution-templates/layered-web-application/helm-charts-and-kubernetes.md @@ -0,0 +1,65 @@ +# Layered Solution: Helm Charts and Kubernetes + +````json +//[doc-nav] +{ + "Previous": { + "Name": "CORS configuration", + "Path": "solution-templates/layered-web-application/cors-configuration" + } +} +```` + +> You must have an ABP Business or a higher license to be able to use the Kubernetes features. + +This document explains how to deploy the layered solution to a Kubernetes cluster using [Helm](https://helm.sh/) charts. The layered solution template includes Helm charts for each application and infrastructure (Redis, RabbitMQ, etc). You can use these charts to deploy the solution to a Kubernetes cluster. You can see the Helm charts in the `etc/helm` folder of the solution. + +## Folder Structure + +The folder structure of the Helm charts is as follows: + +![helm-folder](images/helm-folder.png) + +> You might have different charts based on the solution template options you selected while creating the solution. + +* **bookstore**: The Helm chart for the `Bookstore` solution. The folder name should be the same as your project name. + * **charts**: The sub-charts of the solution. Each application and infrastructure has its own chart. + * **templates**: The templates of the solution. It includes the ingress host URLs. + * **Chart.yaml**: The chart metadata. + * **values.bookstore-local.yaml**: The override values file for the [Kubernetes profile](../../studio/kubernetes.md#profile). It should follow the naming convention for your project name. + * **values.yaml**: The default values file for the chart. +* **build-all-images.ps1**: A PowerShell script to build all Docker images of the solution. +* **build-image.ps1**: A PowerShell script to build a Docker image of a specified project. +* **create-tls-secrets.ps1**: A PowerShell script to create local TLS secrets for the ingress controller. It's important when you try to [intercept a service](../../studio/kubernetes.md#intercept-a-service) and run it locally. +* **install.ps1**: A PowerShell script to install the solution to a Kubernetes cluster. You can override the default argument values. +* **uninstall.ps1**: A PowerShell script to uninstall the solution from a Kubernetes cluster. You can override the default argument values. + +## Installing the Helm Charts + +You can install the solution to a Kubernetes cluster using the `install.ps1` script. The script has the following arguments: + +* **ChartName**: Default value is the project name. You can create different charts and specify the chart name. In ABP Studio [Kubernetes Main Chart](../../studio/kubernetes.md#main-chart) *Install Chart(s)* command automatically sets the chart name. +* **Namespace**: The namespace to install the Kubernetes resources. Default value is the project name with the `-local` suffix. +* **ReleaseName**: The release name of the Helm chart. Default value is the project name with the `-local` suffix. +* **DotnetEnvironment**: The environment to run the application. Default value is `Staging`. +* **User**: The user responsible for installing the Kubernetes resources. The application will automatically set the user name if you configure it under [Specify the User](../../studio/kubernetes.md#specify-the-user). + +Before running the script, you need to build the Docker images of the solution. You can use the `build-all-images.ps1` script to build all Docker images of the solution. Afterwards, make sure that you have a Kubernetes TLS secret for the ingress controller. It is automatically created when you create the solution; however, if you clone the solution from a repository, you need to create it manually. You can use the `create-tls-secrets.ps1` script to create the TLS secret. Then you can run the `install.ps1` script to install the solution to a Kubernetes cluster. + +## Uninstalling the Helm Charts + +You can uninstall the solution from a Kubernetes cluster using the `uninstall.ps1` script. The script has the following arguments: + +* **Namespace**: The namespace to uninstall the helm chart. Default value is the project name with the `-local` suffix. +* **ReleaseName**: The release name of the Helm chart. Default value is the project name with the `-local` suffix. +* **User**: The user responsible for uninstalling the Kubernetes resources. The application will automatically set the user name if you configure it under [Specify the User](../../studio/kubernetes.md#specify-the-user). + +You can run the `uninstall.ps1` script to uninstall the solution from a Kubernetes cluster. + +```bash +./uninstall.ps1 +``` + +Additionally, in ABP Studio [Kubernetes](../../studio/kubernetes.md) feature, you can do the same operations more easily. You can use the Install Chart(s) and Uninstall Chart(s) commands to install and uninstall the solution to a Kubernetes cluster. Also, use the Build Docker Image(s) command to build the Docker images of the solution. + +![kubernetes](images/kubernetes.png) \ No newline at end of file diff --git a/docs/en/solution-templates/layered-web-application/images/account-external-provider.png b/docs/en/solution-templates/layered-web-application/images/account-external-provider.png new file mode 100644 index 0000000000..acfefeee7c Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/account-external-provider.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/angular-folder-structure.png b/docs/en/solution-templates/layered-web-application/images/angular-folder-structure.png new file mode 100644 index 0000000000..95bfc8986f Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/angular-folder-structure.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/angular-template-structure-diagram.png b/docs/en/solution-templates/layered-web-application/images/angular-template-structure-diagram.png new file mode 100644 index 0000000000..dd7a4e5cc7 Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/angular-template-structure-diagram.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/apply-database-migrations.png b/docs/en/solution-templates/layered-web-application/images/apply-database-migrations.png new file mode 100644 index 0000000000..28d5f73786 Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/apply-database-migrations.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/bookstore-solution-tiered.png b/docs/en/solution-templates/layered-web-application/images/bookstore-solution-tiered.png new file mode 100644 index 0000000000..06d21a2028 Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/bookstore-solution-tiered.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/database-connection-strings-modal.png b/docs/en/solution-templates/layered-web-application/images/database-connection-strings-modal.png new file mode 100644 index 0000000000..7dc2fd7243 Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/database-connection-strings-modal.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/database-connection-strings.png b/docs/en/solution-templates/layered-web-application/images/database-connection-strings.png new file mode 100644 index 0000000000..4d8b8f4e43 Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/database-connection-strings.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/file-management-index-page.png b/docs/en/solution-templates/layered-web-application/images/file-management-index-page.png new file mode 100644 index 0000000000..c19d41f006 Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/file-management-index-page.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/helm-folder.png b/docs/en/solution-templates/layered-web-application/images/helm-folder.png new file mode 100644 index 0000000000..ee5c055a36 Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/helm-folder.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/kubernetes.png b/docs/en/solution-templates/layered-web-application/images/kubernetes.png new file mode 100644 index 0000000000..a32b81f5d1 Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/kubernetes.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/layered-project-dependencies.png b/docs/en/solution-templates/layered-web-application/images/layered-project-dependencies.png new file mode 100644 index 0000000000..1d5c4f1195 Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/layered-project-dependencies.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/layered-solution-in-explorer.png b/docs/en/solution-templates/layered-web-application/images/layered-solution-in-explorer.png new file mode 100644 index 0000000000..1a83a497bb Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/layered-solution-in-explorer.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/mobile-applications.png b/docs/en/solution-templates/layered-web-application/images/mobile-applications.png new file mode 100644 index 0000000000..873d2d9705 Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/mobile-applications.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/new-solution-openiddict-module.png b/docs/en/solution-templates/layered-web-application/images/new-solution-openiddict-module.png new file mode 100644 index 0000000000..a3cce0c7bc Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/new-solution-openiddict-module.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/open-solution-with-explorer.png b/docs/en/solution-templates/layered-web-application/images/open-solution-with-explorer.png new file mode 100644 index 0000000000..368b607ad2 Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/open-solution-with-explorer.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/openiddict-ui.png b/docs/en/solution-templates/layered-web-application/images/openiddict-ui.png new file mode 100644 index 0000000000..449a3efcfd Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/openiddict-ui.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/run-solution.png b/docs/en/solution-templates/layered-web-application/images/run-solution.png new file mode 100644 index 0000000000..deab922ba7 Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/run-solution.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/saas-module-selection.png b/docs/en/solution-templates/layered-web-application/images/saas-module-selection.png new file mode 100644 index 0000000000..74b9857bd7 Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/saas-module-selection.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/solution-folders.png b/docs/en/solution-templates/layered-web-application/images/solution-folders.png new file mode 100644 index 0000000000..b09c115145 Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/solution-folders.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/solutionrunner-maui-targetframework.png b/docs/en/solution-templates/layered-web-application/images/solutionrunner-maui-targetframework.png new file mode 100644 index 0000000000..cd9b92c1f2 Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/solutionrunner-maui-targetframework.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/tiered-solution-applications-authserver.png b/docs/en/solution-templates/layered-web-application/images/tiered-solution-applications-authserver.png new file mode 100644 index 0000000000..1b79af73d0 Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/tiered-solution-applications-authserver.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/tiered-solution-servers.png b/docs/en/solution-templates/layered-web-application/images/tiered-solution-servers.png new file mode 100644 index 0000000000..77233195d3 Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/tiered-solution-servers.png differ diff --git a/docs/en/solution-templates/layered-web-application/images/web-applications.png b/docs/en/solution-templates/layered-web-application/images/web-applications.png new file mode 100644 index 0000000000..d232b37446 Binary files /dev/null and b/docs/en/solution-templates/layered-web-application/images/web-applications.png differ diff --git a/docs/en/solution-templates/layered-web-application/index.md b/docs/en/solution-templates/layered-web-application/index.md index 5e0d64b2d8..e1e5997154 100644 --- a/docs/en/solution-templates/layered-web-application/index.md +++ b/docs/en/solution-templates/layered-web-application/index.md @@ -1,465 +1,38 @@ -# Layered Application Solution Template +# ABP Studio: Layered Solution Template -This template provides a layered application structure based on the [Domain Driven Design](../../framework/architecture/domain-driven-design) (DDD) practices. - -## Getting Started - -This document explains **the solution structure** and projects in details. If you want to start quickly, follow the guides below: - -* [The getting started document](../../get-started/layered-web-application.md) explains how to create a new application in a few minutes. -* [The application development tutorial](../../tutorials/book-store/part-01.md) explains step by step application development. - -## How to Start With? - -You can use the [ABP CLI](../../cli) to create a new project using this startup template. Alternatively, you can generate a CLI command from the [Get Started](https://abp.io/get-started) page. CLI approach is used here. - -First, install the ABP CLI if you haven't installed it before: - -````bash -dotnet tool install -g Volo.Abp.Studio.Cli -```` - -Then use the `abp new` command in an empty folder to create a new solution: - -````bash -abp new Acme.BookStore -t app -```` - -* `Acme.BookStore` is the solution name, like *YourCompany.YourProduct*. You can use single-level, two-level or three-level naming. -* This example specified the template name (`-t` or `--template` option). However, `app` is already the default template if you didn't specify it. - -### Specify the UI Framework - -This template provides multiple UI frameworks: - -* `mvc`: ASP.NET Core MVC UI with Razor Pages (default) -* `blazor`: Blazor UI -* `blazor-server`: Blazor Server UI -* `angular`: Angular UI - -Use the `-u` or `--ui` option to specify the UI framework: - -````bash -abp new Acme.BookStore -u angular -```` - -### Specify the Database Provider - -This template supports the following database providers: - -- `ef`: Entity Framework Core (default) -- `mongodb`: MongoDB - -Use `-d` (or `--database-provider`) option to specify the database provider: - -````bash -abp new Acme.BookStore -d mongodb -```` - -### Specify the Mobile Application Framework - -This template supports the following mobile application frameworks: - -- `react-native`: React Native (*Available for* ***Team*** *or higher licenses*) - -Use the `-m` (or `--mobile`) option to specify the mobile application framework: - -````bash -abp new Acme.BookStore -m react-native -```` - -* [The getting started document](../../get-started/layered-web-application.md) explains how to create a new application with this startup template. -* [The application development tutorial](../../tutorials/book-store/part-01.md) explains step by step application development with this startup template. - -## Solution Structure - -Based on the options you've specified, you will get a slightly different solution structure. - -### Default Structure - -If you don't specify any additional options, you will have a solution as shown below: - -![bookstore-rider-solution-v6](../../images/solution-structure-solution-explorer-rider.png) - -Projects are organized in `src` and `test` folders. `src` folder contains the actual application which is layered based on [DDD](../../framework/architecture/domain-driven-design) principles as mentioned before. - -The diagram below shows the layers & project dependencies of the application: - -![layered-project-dependencies](../../images/layered-project-dependencies.png) - -Each section below will explain the related project & its dependencies. - -#### .Domain.Shared Project - -This project contains constants, enums and other objects these are actually a part of the domain layer, but needed to be used by all layers/projects in the solution. - -A `BookType` enum and a `BookConsts` class (which may have some constant fields for the `Book` entity, like `MaxNameLength`) are good candidates for this project. - -* This project has no dependency on other projects in the solution. All other projects depend on this one directly or indirectly. - -#### .Domain Project - -This is the domain layer of the solution. It mainly contains [entities, aggregate roots](../../framework/architecture/domain-driven-design/entities.md), [domain services](../../framework/architecture/domain-driven-design/domain-services.md), [value objects](../../framework/architecture/domain-driven-design/value-objects.md), [repository interfaces](../../framework/architecture/domain-driven-design/repositories.md) and other domain objects. - -A `Book` entity, a `BookManager` domain service and an `IBookRepository` interface are good candidates for this project. - -* Depends on the `.Domain.Shared` because it uses constants, enums and other objects defined in that project. - -#### .Application.Contracts Project - -This project mainly contains [application service](../../framework/architecture/domain-driven-design/application-services.md) **interfaces** and [Data Transfer Objects](../../framework/architecture/domain-driven-design/data-transfer-objects.md) (DTO) of the application layer. It exists to separate the interface & implementation of the application layer. In this way, the interface project can be shared to the clients as a contract package. - -An `IBookAppService` interface and a `BookCreationDto` class are good candidates for this project. - -* Depends on the `.Domain.Shared` because it may use constants, enums and other shared objects of this project in the application service interfaces and DTOs. - -#### .Application Project - -This project contains the [application service](../../framework/architecture/domain-driven-design/application-services.md) **implementations** of the interfaces defined in the `.Application.Contracts` project. - -A `BookAppService` class is a good candidate for this project. - -* Depends on the `.Application.Contracts` project to be able to implement the interfaces and use the DTOs. -* Depends on the `.Domain` project to be able to use domain objects (entities, repository interfaces... etc.) to perform the application logic. - -#### .EntityFrameworkCore Project - -This is the integration project for the EF Core. It defines the `DbContext` and implements repository interfaces defined in the `.Domain` project. - -* Depends on the `.Domain` project to be able to reference to entities and repository interfaces. - -> This project is available only if you are using EF Core as the database provider. If you select another database provider, its name will be different. - -#### .DbMigrator Project - -This is a console application that simplifies the execution of database migrations on development and production environments. When you run this application, it: - -* Creates the database if necessary. -* Applies the pending database migrations. -* Seeds initial data if needed. - -> This project has its own `appsettings.json` file. So, if you want to change the database connection string, remember to change this file too. - -Especially, seeding initial data is important at this point. ABP has a modular data seed infrastructure. See [its documentation](../../framework/infrastructure/data-seeding.md) for more about the data seeding. - -While creating database & applying migrations seem only necessary for relational databases, this project comes even if you choose a NoSQL database provider (like MongoDB). In that case, it still seeds the initial data which is necessary for the application. - -* Depends on the `.EntityFrameworkCore` project (for EF Core) since it needs to access to the migrations. -* Depends on the `.Application.Contracts` project to be able to access permission definitions, because the initial data seeder grants all permissions to the admin role by default. - -#### .HttpApi Project - -This project is used to define your API Controllers. - -Most of the time you don't need to manually define API Controllers since ABP's [Auto API Controllers](../../framework/api-development/auto-controllers.md) feature creates them automagically based on your application layer. However, in case of you need to write API controllers, this is the best place to do it. - -* Depends on the `.Application.Contracts` project to be able to inject the application service interfaces. - -#### .HttpApi.Client Project - -This is a project that defines C# client proxies to use the HTTP APIs of the solution. You can share this library to 3rd-party clients, so they can easily consume your HTTP APIs in their Dotnet applications (For other types of applications, they can still use your APIs, either manually or using a tool in their own platform) - -Most of the time you don't need to manually create C# client proxies, thanks to ABP's [Dynamic C# API Clients](../../framework/api-development/dynamic-csharp-clients.md) feature. - -`.HttpApi.Client.ConsoleTestApp` project is a console application created to demonstrate the usage of the client proxies. - -* Depends on the `.Application.Contracts` project to be able to share the same application service interfaces and DTOs with the remote service. - -> You can delete this project & dependencies if you don't need to create C# client proxies for your APIs. - -#### .Web Project - -This project contains the User Interface (UI) of the application if you are using ASP.NET Core MVC UI. It contains Razor pages, JavaScript files, CSS files, images and so on... - -This project contains the main `appsettings.json` file that contains the connection string and other configurations of the application. - -* Depends on the `.HttpApi` project since the UI layer needs to use APIs and the application service interfaces of the solution. - -> If you check the source code of the `.Web.csproj` file, you will see the references to the `.Application` and the `.EntityFrameworkCore` projects. -> -> These references are actually not needed while coding your UI layer, because the UI layer normally doesn't depend on the EF Core or the Application layer's implementation. These startup templates are ready for tiered deployment, where the API layer is hosted on a separate server than the UI layer. -> -> However, if you don't choose the `--tiered` option, these references will be in the .Web project to be able to host the Web, API and application layers in a single application endpoint. -> -> This gives you the ability to use domain entities & repositories in your presentation layer. However, this is considered as a bad practice according to DDD. - -#### Test Projects - -The solution has multiple test projects, one for each layer: - -* `.Domain.Tests` is used to test the domain layer. -* `.Application.Tests` is used to test the application layer. -* `.EntityFrameworkCore.Tests` is used to test EF Core configuration and custom repositories. -* `.Web.Tests` is used to test the UI (if you are using ASP.NET Core MVC UI). -* `.TestBase` is a base (shared) project for all tests. - -In addition, `.HttpApi.Client.ConsoleTestApp` is a console application (not an automated test project) which demonstrate the usage of HTTP APIs from a .NET application. - -Test projects are prepared for integration testing; - -* It is fully integrated into the ABP and all services in your application. -* It uses SQLite in-memory database for EF Core. For MongoDB, it uses the [EphemeralMongo](https://github.com/asimmon/ephemeral-mongo) library. -* Authorization is disabled, so any application service can be easily used in tests. - -You can still create unit tests for your classes which will be harder to write (because you will need to prepare mock/fake objects), but faster to run (because it only tests a single class and skips all the initialization processes). - -#### How to Run? - -Set `.Web` as the startup project and run the application. The default username is `admin` and the password is `1q2w3E*`. - -See [Getting Started With the ASP.NET Core MVC Template](../../get-started/layered-web-application.md) for more information. - -### Tiered Structure - -If you have selected the ASP.NET Core UI and specified the `--tiered` option, the solution created will be a tiered solution. The purpose of the tiered structure is to be able to **deploy Web applications and HTTP API to different servers**: - -![bookstore-visual-studio-solution-v3](../../images/tiered-solution-servers.png) - -* Browser runs your UI by executing HTML, CSS & JavaScript. -* Web servers host static UI files (CSS, JavaScript, image... etc.) & dynamic components (e.g. Razor pages). It performs HTTP requests to the API server to execute the business logic of the application. -* The API Server hosts the HTTP APIs which then use the application & domain layers of the application to perform the business logic. -* Finally, database server hosts your database. - -So, the resulting solution allows a 4-tiered deployment, by comparing to 3-tiered deployment of the default structure explained before. - -> Unless you actually need such a 4-tiered deployment, it's suggested to go with the default structure which is simpler to develop, deploy and maintain. - -The solution structure is shown below: - -![bookstore-rider-solution-v6](../../images/bookstore-rider-solution-tiered.png) - -As different from the default structure, two new projects come into play: `.AuthServer` & `.HttpApi.Host`. - -#### .AuthServer Project - -This project is used as an authentication server for other projects. `.Web` project uses OpenId Connect Authentication to get identity and access tokens for the current user from the AuthServer. Then uses the access token to call the HTTP API server. HTTP API server uses bearer token authentication to obtain claims from the access token to authorize the current user. - -![tiered-solution-applications](../../images/tiered-solution-applications-authserver.png) - -ABP uses the [OpenIddict Module](../../modules/openiddict.md) that uses the open-source [OpenIddict-core](https://github.com/openiddict/openiddict-core) library for the authentication between applications. See [OpenIddict documentation](https://documentation.openiddict.com/) for details about the OpenIddict and OpenID Connect protocol. - -It has its own `appsettings.json` that contains database connection and other configurations. - -#### .HttpApi.Host Project - -This project is an application that hosts the API of the solution. It has its own `appsettings.json` that contains database connection and other configurations. - -#### .Web Project - -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. - -#### Pre-requirements - -* [Redis](https://redis.io/): The applications use Redis as a distributed cache. So, you need to have Redis installed & running. - -#### How to Run? - -You should run the application with the given order: - -* First, run the `.AuthServer` since other applications depend on it. -* Then run the `.HttpApi.Host` since it is used by the `.Web` application. -* Finally, you can run the `.Web` project and login to the application (using `admin` as the username and `1q2w3E*` as the password). - -### Blazor UI -If you choose `Blazor` as the UI Framework (using the `-u blazor` or `-u blazor-server` option), the solution will have a project named `.Blazor`. This project contains the Blazor UI application. According to your choice, it will be a Blazor WebAssembly or Blazor Server application. If Blazor WebAssembly is selected, the solution will also have a `.HttpApi.Host`. This project is an ASP.NET Core application that hosts the backend application for the Blazor single page application. - -#### .Blazor Project (Server) -The Blazor Server project is similar to the ASP.NET Core MVC project. It replaces `.Web` project with `.Blazor` in the solution structure above. It has the same folder structure and the same application flow. Since it's an ASP.NET Core application, it can contain **.cshtml** files and **.razor** components at the same time. If routing matches a razor component, the Blazor UI will be used. Otherwise, the request will be handled by the MVC framework. - -![abp solution structure blazor server](../../images/layered-project-dependencies-blazor-server.png) - -#### .Blazor Project (WebAssembly) -The Blazor WebAssembly project is a single page application that runs on the browser. You'll see it as `.Blazor` project in the solution. It uses the `.HttpApi.Host` project to communicate with the backend. It can't be used without the backend application. It contains only **.razor** components. It's a pure client-side application. It doesn't have any server-side code. Everything in this layer will be for the client side. - -![abp solution structure blazor wasm](../../images/layered-project-dependencies-blazor-wasm.png) - -### Angular UI - -If you choose `Angular` as the UI framework (using the `-u angular` option), the solution is being separated into two folders: - -* `angular` folder contains the Angular UI application, the client-side code. -* `aspnet-core` folder contains the ASP.NET Core solution, the server-side code. - -The server-side is similar to the solution described above. `*.HttpApi.Host` project serves the API, so the `Angular` application consumes it. - -Angular application folder structure looks like below: - -![angular-folder-structure](../../images/angular-folder-structure.png) - - -Each of ABP modules is an NPM package. Some ABP modules are added as a dependency in `package.json`. These modules install with their dependencies. To see all ABP packages, you can run the following command in the `angular` folder: - -```bash -yarn list --pattern abp -``` - -Angular application module structure: - -![Angular template structure diagram](../../images/angular-template-structure-diagram.png) - -#### AppModule - -`AppModule` is the root module of the application. Some of the ABP modules and some essential modules are imported to `AppModule`. - -ABP Config modules have also been imported to `AppModule` for initial requirements of the lazy-loadable ABP modules. - -#### AppRoutingModule - -There are lazy-loadable ABP modules in the `AppRoutingModule` as routes. - -> Paths of ABP Modules should not be changed. - -You should add `routes` property in the `data` object to add a link on the menu to redirect to your custom pages. - -```js +````json +//[doc-nav] { - path: 'dashboard', - loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule), - canActivate: [authGuard, permissionGuard], - data: { - routes: { - name: 'ProjectName::Menu:Dashboard', - order: 2, - iconClass: 'fa fa-dashboard', - requiredPolicy: 'ProjectName.Dashboard.Host' - } as ABP.Route - } + "Next": { + "Name": "Overview", + "Path": "solution-templates/layered-web-application/overview" + } } -``` -In the above example; -* If the user is not logged in, authGuard blocks access and redirects to the login page. -* permissionGuard checks the user's permission with the `requiredPolicy` property of the `routes` object. If the user is not authorized to access the page, the 403 page appears. -* The `name` property of `routes` is the menu link label. A localization key can be defined. -* The `iconClass` property of the `routes` object is the menu link icon class. -* The `requiredPolicy` property of the `routes` object is the required policy key to access the page. - -After the above `routes` definition, if the user is authorized, the dashboard link will appear on the menu. - -#### Shared Module - -The modules that may be required for all modules have been imported to the `SharedModule`. You should import `SharedModule` to all modules. - -See the [Sharing Modules](https://angular.io/guide/sharing-ngmodules) document. - -#### Environments - -The files under the `src/environments` folder have the essential configuration of the application. - -#### Home Module - -Home module is an example lazy-loadable module that loads on the root address of the application. - -#### Styles - -The required style files are added to the `styles` array in `angular.json`. `AppComponent` loads some style files lazily via `LazyLoadService` after the main bundle is loaded to shorten the first rendering time. - -#### Testing - -You should create your tests in the same folder as the file you want to test. - -See the [testing document](https://angular.io/guide/testing). - -#### Depended Packages - -* [NG Bootstrap](https://ng-bootstrap.github.io/) is used as UI component library. -* [NGXS](https://www.ngxs.io/) is used as state management library. -* [angular-oauth2-oidc](https://github.com/manfredsteyer/angular-oauth2-oidc) is used to support for OAuth 2 and OpenId Connect (OIDC). -* [Chart.js](https://www.chartjs.org/) is used to create widgets. -* [ngx-validate](https://github.com/ng-turkey/ngx-validate) is used for dynamic validation of reactive forms. - -### React Native - -If the `-m react-native` option is specified in the new project command, the solution includes the [React Native](https://reactnative.dev/) application in the `react-native` folder. - -The server-side is similar to the solution described above. `*.HttpApi.Host` project serves the API, so the React Native application consumes it. - -The React Native application was generated with [Expo](https://expo.io/). Expo is a set of tools built around React Native to help you quickly start an app and, while it has many features. - -React Native application folder structure as like below: - -![react-native-folder-structure](../../images/react-native-folder-structure.png) - -* `App.js` is the bootstrap component of the application. -* `Environment.js` file has the essential configuration of the application. `prod` and `dev` configurations are defined in this file. -* [Contexts](https://reactjs.org/docs/context.html) are created in the `src/contexts` folder. -* [Higher order components](https://reactjs.org/docs/higher-order-components.html) are created in the `src/hocs` folder. -* [Custom hooks](https://reactjs.org/docs/hooks-custom.html#extracting-a-custom-hook) are created in `src/hooks`. -* [Axios interceptors](https://github.com/axios/axios#interceptors) are created in the `src/interceptors` folder. -* Utility functions are exported from `src/utils` folder. - -#### Components - -Components that can be used on all screens are created in the `src/components` folder. All components have been created as a function that is able to use [hooks](https://reactjs.org/docs/hooks-intro.html). - -#### Screens - -![react-native-navigation-structure](../../images/react-native-navigation-structure.png) - -Screens are created by creating folders that separate their names in the `src/screens` folder. Certain parts of some screens can be split into components. - -Each screen is used in a navigator in the `src/navigators` folder. - -#### Navigation - -[React Navigation](https://reactnavigation.org/) is used as a navigation library. Navigators are created in the `src/navigators`. A [drawer](https://reactnavigation.org/docs/drawer-based-navigation/) navigator and several [stack](https://reactnavigation.org/docs/hello-react-navigation/#installing-the-stack-navigator-library) navigators have been created in this folder. See the [above diagram](#screens) for the navigation structure. - -#### State Management - -[Redux](https://redux.js.org/) is used as a state management library. [Redux Toolkit](https://redux-toolkit.js.org/) library is used as a toolset for efficient Redux development. - -Actions, reducers, sagas and selectors are created in the `src/store` folder. Store folder is as below: - -![react-native-store-folder](../../images/react-native-store-folder.png) - -* [**Store**](https://redux.js.org/basics/store) is defined in the `src/store/index.js` file. -* [**Actions**](https://redux.js.org/basics/actions/) are payloads of information that send data from your application to your store. -* [**Reducers**](https://redux.js.org/basics/reducers) specify how the application's state changes in response to actions sent to the store. -* [**Redux-Saga**](https://redux-saga.js.org/) is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage. Sagas are created in the `src/store/sagas` folder. -* [**Reselect**](https://github.com/reduxjs/reselect) library is used to create memoized selectors. Selectors are created in the `src/store/selectors` folder. - -#### APIs - -[Axios](https://github.com/axios/axios) is used as an HTTP client library. An Axios instance has exported from `src/api/API.js` file to make HTTP calls with the same config. `src/api` folder also has the API files that have been created for API calls. - -#### Theming - -[Native Base](https://nativebase.io/) is used as UI components library. Native Base components can customize easily. See the [Native Base customize](https://docs.nativebase.io/customizing-components) documentation. We followed the same way. - -* Native Base theme variables are in the `src/theme/variables` folder. -* Native Base component styles are in the `src/theme/components` folder. These files have been generated with Native Base's `ejectTheme` script. -* Styles of components override with the files under the `src/theme/overrides` folder. - -#### Testing - -Unit tests will be created. - -See the [Testing Overview](https://reactjs.org/docs/testing.html) document. - -#### Depended Libraries - -* [Native Base](https://nativebase.io/) is used as UI components library. -* [React Navigation](https://reactnavigation.org/) is used as navigation library. -* [Axios](https://github.com/axios/axios) is used as an HTTP client library. -* [Redux](https://redux.js.org/) is used as state management library. -* [Redux Toolkit](https://redux-toolkit.js.org/) library is used as a toolset for efficient Redux development. -* [Redux-Saga](https://redux-saga.js.org/) is used to manage asynchronous processes. -* [Redux Persist](https://github.com/rt2zz/redux-persist) is used as state persistence. -* [Reselect](https://github.com/reduxjs/reselect) is used to create memoized selectors. -* [i18n-js](https://github.com/fnando/i18n-js) is used as i18n library. -* [expo-font](https://docs.expo.io/versions/latest/sdk/font/) library allows loading fonts easily. -* [Formik](https://github.com/jaredpalmer/formik) is used to build forms. -* [Yup](https://github.com/jquense/yup) is used for form validations. - -## Social / External Logins - -If you want to configure social/external logins for your application, please follow the [Social/External Logins](../../social-external-logins.md) document. - -## What's Next? +```` -- [The getting started document](../../get-started) explains how to create a new application in a few minutes. -- [The application development tutorial](../../tutorials/book-store/part-01.md) explains step by step application development. +ABP Studio provides pre-architected, production-ready templates to jump-start a new solution. One of these templates is the Layered solution template. It is designed for building monolithic layered systems that follow common application patterns based on [Domain-Driven Design](../../framework/architecture/domain-driven-design) (DDD) principles. The template includes multiple layers, integrates existing modules, and provides host applications based on your selections, making it an excellent foundation for your layered system. -## See Also -* [Video tutorial](https://abp.io/video-courses/essentials/app-template) +> **This document explains the Layered solution template in detail. It is a reference document to fully understand the solution and refer to when you have trouble.** +> +> **If you want to quickly create a layered solution, please refer to *[Quick Start: Creating a Layered Web Application with ABP Studio](../../get-started/layered-web-application.md)* document.** + +## Contents + +* [Overview](overview.md) +* [Solution structure](solution-structure.md) +* [Main Components](main-components.md) + * [Web Applications](web-applications.md) + * [Db Migrator](db-migrator.md) + * [Mobile Applications](mobile-applications.md) +* [Built-In Features](built-in-features.md) + * [Authentication](authentication.md) + * [Database configurations](database-configurations.md) + * [Logging (with Serilog)](logging.md) + * [Swagger integration](swagger-integration.md) + * [Background Jobs](background-jobs.md) + * [Background Workers](background-workers.md) + * [Distributed Locking](distributed-locking.md) + * [Multi-Tenancy](multi-tenancy.md) + * [BLOB storing](blob-storing.md) + * [CORS configuration](cors-configuration.md) +* [Helm Charts and Kubernetes](helm-charts-and-kubernetes.md) diff --git a/docs/en/solution-templates/layered-web-application/logging.md b/docs/en/solution-templates/layered-web-application/logging.md new file mode 100644 index 0000000000..1ee3278bfb --- /dev/null +++ b/docs/en/solution-templates/layered-web-application/logging.md @@ -0,0 +1,35 @@ +# Layered Solution: Logging + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Database configurations", + "Path": "solution-templates/layered-web-application/database-configurations" + }, + "Next": { + "Name": "Swagger integration", + "Path": "solution-templates/layered-web-application/swagger-integration" + } +} +``` + +The ABP Studio [layered solution template](index.md) is fully configured for [logging](../../framework/fundamentals/logging.md). All the applications are configured to use the [Serilog](https://serilog.net/) library for structured logging. They are configured in a common way for logging. This document explains that common logging structure. + +## The Serilog Sinks + +The Serilog library is configured so it writes the logs to the following targets (a.k.a. [sinks](https://github.com/serilog/serilog/wiki/Provided-Sinks)) in parallel: + +* **[Console](https://github.com/serilog/serilog-sinks-console)**: Logs are written to the standard output of the executing application. Logging to console is useful when you want to see logs easily while it is running in a container. +* **[File](https://github.com/serilog/serilog-sinks-file)**: Logs are written to a file named `logs.txt` located under the `Logs` folder of the executing application. File logging is useful when you run the application on your local computer. You can check logs easily when you have a trouble. This sinks is only configured for DEBUG mode. It won't be available in your production environment (you can change the behavior in your `Program.cs` file). +* **ABP Studio**: This is a Sink provided by ABP Studio. It sends all logs to ABP Studio, so you can easily monitor your logs in real-time on your ABP Studio Application Monitoring panel. + +The solution can work with [any sink](https://github.com/serilog/serilog/wiki/Provided-Sinks) supported by Serilog. You can add more sinks, remove pre-installed sinks or fine tune their configuration for your solution. + +## Program.cs + +The `Program.cs` file is the main point that configures the logging system. It is done here, because we want to initialize and start the logging in the very beginning of the application. + +## Additional Information + +We are using ABP Serilog Enrichers in the module class of the application. It is done by the `app.UseAbpSerilogEnrichers();` line in the `OnApplicationInitialization` method of your module class. That ASP.NET Core middleware adds current [tenant](../../framework/architecture/multi-tenancy/index.md), [user](../../framework/infrastructure/current-user.md), client and correlation id information to the log records. \ No newline at end of file diff --git a/docs/en/solution-templates/layered-web-application/main-components.md b/docs/en/solution-templates/layered-web-application/main-components.md new file mode 100644 index 0000000000..b7b324069e --- /dev/null +++ b/docs/en/solution-templates/layered-web-application/main-components.md @@ -0,0 +1,21 @@ +# Layered Solution: Main Components + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Solution structure", + "Path": "solution-templates/layered-web-application/solution-structure" + }, + "Next": { + "Name": "Web Applications", + "Path": "solution-templates/layered-web-application/web-applications" + } +} +```` + +The solution consists of various applications; web applications, mobile applications, and a database migrator application. These applications are the main components of the solution and are designed to work together to provide a complete solution. The following documents explains these components in details: + +* [Web Applications](web-applications.md) +* [Db Migrator](db-migrator.md) +* [Mobile Applications](mobile-applications.md) \ No newline at end of file diff --git a/docs/en/solution-templates/layered-web-application/mobile-applications.md b/docs/en/solution-templates/layered-web-application/mobile-applications.md new file mode 100644 index 0000000000..09da244f27 --- /dev/null +++ b/docs/en/solution-templates/layered-web-application/mobile-applications.md @@ -0,0 +1,122 @@ +# Layered Solution: Mobile Applications + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Db Migrator", + "Path": "solution-templates/layered-web-application/db-migrator" + }, + "Next": { + "Name": "Built-In Features", + "Path": "solution-templates/layered-web-application/built-in-features" + } +} +``` + +> You must have an ABP Team or a higher license to be able to create a mobile application project with ABP Studio. + +Mobile applications are an essential part of modern software solutions. They provide a user-friendly interface to the end-users and allow them to access the system from anywhere. ABP Studio allows you to create mobile applications for your layered solution. You can create a new mobile application project, configure it, and run it on your device. + +## Mobile Application Types + +ABP Studio supports the following mobile application types: + +- **None**: No mobile application project is created. It is the default option. +- **MAUI**: Cross-platform mobile applications with .NET MAUI (Multi-platform App UI). You can create MAUI projects with ABP Studio. +- **React Native**: Cross-platform mobile applications that share code between iOS and Android platforms. You can create React Native projects with ABP Studio. + +You can select the mobile application type when creating a new layered application project during the *Mobile Framework* step. + +![mobile-applications](images/mobile-applications.png) + +### The MAUI Application + +This is the mobile application that is built based on Microsoft's [MAUI framework](https://learn.microsoft.com/en-us/dotnet/maui). It will be in the solution only if you've selected the MAUI as your mobile application option. + +#### Project Structure +Entire MAUI application is built on the AppShell pattern of MAUI. You can find the AppShell class in the `Acme.Bookstore.Maui` project. It is the entry point of the application. It is responsible for initializing the application and registering the services. You find all the pages and routing information in the `AppShell.xaml` file. + +- **Pages**: Pages are located in the `Pages` folder of the project. Each page has a XAML & C# file. XAML file is responsible for the UI and C# file is responsible for the initialization of the page. + +- **ViewModels**: ViewModels are located in the `ViewModels` folder of the project. Each ViewModel has a C# file. ViewModels are responsible for the business logic of the pages. + +- **Oidc**: Oidc folder contains the logic for the authentication of the application. It contains the `MauiAuthenticationBrowser` class which manages the authentication process of the application. + +- **Localization**: Localization folder contains the localization logic of the application. It contains regular ABP Localization logic and the `LocalizationResourceManager` class which is wrapper for the ABP localization logic on MAUI. + +- **Messages**: Messages folder contains the message data for the communication inside application. Messages are used to send data between pages and viewmodels. It's designed on the [MVVM Toolkit Messenger](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/messenger) feature. + +- **Storage**: Storage folder contains the storage logic of the application. It contains the `IStorage` class which is wrapper for the [SecureStorage](https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/storage/secure-storage) feature. It is used to store the authentication data of the user and preferences of the application. + +_Rest of the folders are MAUI default folders. You can check the [.NET MAUI single project documentatipon](https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/single-project?view=net-maui-8.0) for more information._ + +#### Running the application +Before running the MAUI Application, rest of the applications in the solution must be running. Such as AuthServer, MobileGateway and the microservices. + +Make sure that you prepared devices for debugging. You can check the following documentation for each platform. + +- [Android](https://learn.microsoft.com/en-us/dotnet/maui/android/emulator/) +- [iOS](https://learn.microsoft.com/en-us/dotnet/maui/ios/pair-to-mac) +- [MacCatalyst](https://learn.microsoft.com/en-us/dotnet/maui/mac-catalyst/cli) +- [Windows](https://learn.microsoft.com/en-us/dotnet/maui/windows/setup) + +##### Network + +All the platforms including iOS, MacCataylst and Windows, runs the applications in the same network of the host. So, you can use the `localhost` address to connect to the applications. + +But in the **Android Emulator**, you need to use the `adb reverse` command to connect to the applications. You can use the following command to connect to the AuthServer application: + +```bash +adb reverse tcp:44300 tcp:44300 +``` + +> `44300` is an example port. You need to change it based on the port of the AuthServer & MobileGateway application. + +> You need to run the command for a running emulator. If you run the emulator after running the command, you need to run the command again. + + +##### Target Framework + +Since MAUI Applications have multiple target frameworks, you need to select the target framework before running the application. You can select the target framework from the context menu of the Solution Runner. + +![ABP Studio MAUI Target Framework](images/solutionrunner-maui-targetframework.png) + +##### Running with ABP Studio +You can start the MAUI application with the solution runner. You can click the start button of the MAUI application in the solution runner tree. It will start the application on the selected target framework. Since they're not running on a process and they're running on a device, you can't see them as running state in the solution runner. After the application is deployed, it'll be opened on the device and it'll be shown as stopped in the solution runner. + +#### Development on MAUI Application + +You can follow [Mobile Application Development Tutorial - MAUI](../../tutorials/mobile/maui/index.md) to learn how to develop on MAUI Application. + +### The React Native Application + +This is the mobile application that is built based on Facebook's [React Native framework](https://reactnative.dev/) and [Expo](https://expo.dev/). It will be in the solution only if you've selected React Native as your mobile application option. + +#### Project Structure +- **Environment.js**: file using for provide application level variables like `apiUrl`, `oAuthConfig` and etc. + +- **api**: The `api` folder contains HTTP request files that simplify API management in the React Native starter template + - `API.js:` exports **axiosInstance**. It provides axios instance filled api url + +- **components**: In the `components` folder you can reach built in react native components that you can use in your app. These components **facilitates** your list, select and etc. operations + +- **contexts**: `contexts` folder contains [react context](https://react.dev/reference/react/createContext). You can expots your contexts in this folder. `Localization context provided in here` + +- **navigators**: folder contains [react-native stacks](https://reactnavigation.org/docs/stack-navigator/). After create new *FeatureName*Navigator we need to provide in `DrawerNavigator.js` file as `Drawer.Screen` + +- **screens**: is the content of navigated page. We'll pass as component property to [Stack.Screen](https://reactnavigation.org/docs/native-stack-navigator/) + +- **store**: folder manages state-management operations. We will define `actions`, `reducers`, `sagas` and `selectors` here. + +- **styles**: folder contains app styles. `system-style.js` comes built in template we can also add new styles. + +- **utils**: folder contains helper functions that we can use in application + +#### Running the Application + +React Native applications can't be run with the solution runner. You need to run them with the React Native CLI. You can check the [React Native documentation](https://reactnative.dev/docs/environment-setup) to learn how to setup the environment for React Native development. + +Before running the React Native application, rest of the applications in the solution must be running. Such as AuthServer, MobileGateway and the microservices. + +Then you can run the React Native application by following this documentation: [Getting Started with the React Native](../../framework/ui/react-native/index.md). \ No newline at end of file diff --git a/docs/en/solution-templates/layered-web-application/multi-tenancy.md b/docs/en/solution-templates/layered-web-application/multi-tenancy.md new file mode 100644 index 0000000000..632b392ebf --- /dev/null +++ b/docs/en/solution-templates/layered-web-application/multi-tenancy.md @@ -0,0 +1,74 @@ +# Layered Solution: Multi-Tenancy + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Distribution Locking", + "Path": "solution-templates/layered-web-application/distributed-locking" + }, + "Next": { + "Name": "BLOB storing", + "Path": "solution-templates/layered-web-application/blob-storing" + } +} +``` + +> Some of the features mentioned in this document may not be available in the free version. We're using the **\*** symbol to indicate that a feature is available in the **[Team](https://abp.io/pricing)** and **[Higher](https://abp.io/pricing)** licenses. + +Multi-tenancy is a software architecture where a single instance(codebase) of software runs on a server and serves multiple tenants. Tenants are isolated from each other and can have their own data, configurations, and users. This document explains how the multi-tenancy mechanism works in the layered solution template. You can learn more about multi-tenancy in the [Multi-Tenancy](../../framework/architecture/multi-tenancy/index.md), [Tenant Management](../../modules/tenant-management.md) and [SaaS **\***](../../modules/saas.md) documents. + +## Multi-Tenancy in Layered Solutions + +The layered solution templates use the *Multi-Tenancy* architecture only if you *Enable Multi-Tenancy **\**** option while creating the solution. + +![saas-module-selection](images/saas-module-selection.png) + +You can use different databases for each tenant or a shared database for some tenants. In the *SaaS **\*** module, you can specify the database connection strings in the [Connection Strings Management Modal](../../modules/saas.md#connection-string). All cached data is isolated by tenant. Each event, background job, and other data is stored with the tenant id. + +You can use the `ICurrentTenant` service to get the current tenant information in your application. + +```csharp +public class MyService : ITransientDependency +{ + private readonly ICurrentTenant _currentTenant; + + public MyService(ICurrentTenant currentTenant) + { + _currentTenant = currentTenant; + } + + public void MyMethod() + { + var tenantId = _currentTenant.Id; + var tenantName = _currentTenant.Name; + } +} +``` + +Additionally, you can use the [DataFilter](../../framework/infrastructure/data-filtering.md#idatafilter-service-enabledisable-data-filters) system to disable the tenant filter and list all data in the same database. + +```csharp +public class MyBookService : ITransientDependency +{ + private readonly IDataFilter _multiTenantFilter; + private readonly IRepository _bookRepository; + + public MyBookService( + IDataFilter multiTenantFilter, + IRepository bookRepository) + { + _multiTenantFilter = multiTenantFilter; + _bookRepository = bookRepository; + } + + public async Task> GetAllBooksIncludingDeletedAsync() + { + //Temporary disable the IMultiTenant filter + using (_multiTenantFilter.Disable()) + { + return await _bookRepository.GetListAsync(); + } + } +} +``` \ No newline at end of file diff --git a/docs/en/solution-templates/layered-web-application/overview.md b/docs/en/solution-templates/layered-web-application/overview.md new file mode 100644 index 0000000000..bfc3f4a465 --- /dev/null +++ b/docs/en/solution-templates/layered-web-application/overview.md @@ -0,0 +1,108 @@ +# Layered Solution: Overview + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Index", + "Path": "solution-templates/layered-web-application/index" + }, + "Next": { + "Name": "Solution Structure", + "Path": "solution-templates/layered-web-application/solution-structure" + } +} +```` + +> Some of the features mentioned in this document may not be available in the free version. We're using the **\*** symbol to indicate that a feature is available in the **[Team](https://abp.io/pricing)** and **[Higher](https://abp.io/pricing)** licenses. + + +In this document, you will learn what the Layered solution template offers to you. + +## Pre-Installed Libraries & Services + +The following **libraries and services** come **pre-installed** and **configured** for both **development** and **production** environments. After creating your solution, you can **modify** or **remove** most of them as needed. + +* **[Autofac](https://autofac.org/)** for [Dependency Injection](../../framework/fundamentals/dependency-injection.md). +* **[Serilog](https://serilog.net/)** with File and Console [logging](../../framework/fundamentals/logging.md) providers. +* **[Redis](https://redis.io/)** for [distributed caching](../../framework/fundamentals/caching.md). Redis is used for distributed caching if you select the *Public Website* **\*** or *Tiered* **\*** option. +* **[Swagger](https://swagger.io/)** for exploring and testing HTTP APIs. +* **[OpenIddict](https://github.com/openiddict/openiddict-core)** as the built-in authentication server. + +## Pre-Configured Features + +The following features are built and pre-configured for you in the solution. + +* **Authentication** is fully configured based on best practices. +* **[Permission](../../framework/fundamentals/authorization.md)** (authorization), **[setting](../../framework/infrastructure/settings.md)**, **[feature](../../framework/infrastructure/features.md)** and the **[localization](../../framework/fundamentals/localization.md)** management systems are pre-configured and ready to use. +* **[Background job system](../../framework/infrastructure/background-jobs/index.md)**. +* **[BLOB storge](../../framework/infrastructure/blob-storing/index.md)** system is installed with the [database provider](../../framework/infrastructure/blob-storing/database.md). +* **On-the-fly database migration** system (services automatically migrated their database schema when you deploy a new version). **\*** +* **[Helm](https://helm.sh/)** charts are included to deploy the solution to **[Kubernetes](https://kubernetes.io/)**. **\*** +* **[Swagger](https://swagger.io/)** authentication is configured to test the authorized HTTP APIs. + +## Fundamental Modules + +The following modules are pre-installed and configured for the solution: + +* **[Account](../../modules/account.md)** to authenticate users (login, register, two factor auth **\***, etc) +* **[Identity](../../modules/identity.md)** to manage roles and users +* **[OpenIddict](../../modules/openiddict.md)** (the core part) to implement the OAuth authentication flows + +In addition these, [Feature Management](../../modules/feature-management.md), [Permission Management](../../modules/permission-management.md) and [Setting Management](../../modules/setting-management.md) modules are pre-installed as they are the fundamental feature modules of the ABP. + +## Optional Modules + +The following modules are optionally included in the solution, so you can select the ones you need: + +* **[Audit Logging](../../modules/audit-logging.md)** +* **[Chat](../../modules/chat.md)** **\*** +* **[File Management](../../modules/file-management.md)** **\*** +* **[GDPR](../../modules/gdpr.md)** **\*** +* **[Language Management](../../modules/language-management.md)** **\*** +* **[OpenIddict (Management UI)](../../modules/openiddict.md)** **\*** +* **[Tenant Management](../../modules/tenant-management.md) (Multi-Tenancy) or [SaaS](../../modules/saas.md)** **\*** +* **[Text Template Management](../../modules/text-template-management.md)** **\*** + +## UI Theme + +The **[LeptonX Lite](../../ui-themes/lepton-x-lite/index.md) or [LeptonX theme](https://leptontheme.com/)** **\*** is pre-configured for the solution. You can select one of the color palettes (System, Light or Dark) as default, while the end-user dynamically change it on the fly. + +## Other Options + +Layered startup template asks for some preferences while creating your solution. + +### Database Providers + +There are two database provider options are provided on a new solution creation: + +* **[Entity Framework Core](../../framework/data/entity-framework-core/index.md)** with SQL Server, MySQL and PostgreSQL DBMS options. You can [switch to anther DBMS](../../framework/data/entity-framework-core/other-dbms.md) manually after creating your solution. +* **[MongoDB](../../framework/data/mongodb/index.md)** + +### UI Frameworks + +The solution comes with a main web application with the following UI Framework options: + +* **None** (doesn't include a web application to the solution) +* **Angular** +* **MVC / Razor Pages UI** +* **Blazor WebAssembly** +* **Blazor Server** +* **Blazor WebApp** +* **MAUI with Blazor (Hybrid)** **\*** + +### The Mobile Application + +If you prefer, the solution includes a mobile application. The mobile application is fully integrated to the system, implements authentication (login) and other ABP features, and includes a few screens that you can use and take as example. The following options are available: + +* **None** (doesn't include a mobile application to the solution) +* **MAUI** **\*** +* **React Native** **\*** + +### Multi-Tenancy & SaaS Module **\*** + +The **[SaaS module](../../modules/saas.md)** is included as an option. When you select it, the **[multi-tenancy](../../framework/architecture/multi-tenancy/index.md)** system is automatically configured. Otherwise, the system will not include any multi-tenancy overhead. + +## See Also + +* [Quick Start: Creating a Layered Web Application with ABP Studio](../../get-started/layered-web-application.md) \ No newline at end of file diff --git a/docs/en/solution-templates/layered-web-application/solution-structure.md b/docs/en/solution-templates/layered-web-application/solution-structure.md new file mode 100644 index 0000000000..680df73a59 --- /dev/null +++ b/docs/en/solution-templates/layered-web-application/solution-structure.md @@ -0,0 +1,216 @@ +# Layered Solution: The Structure + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Overview", + "Path": "solution-templates/layered-web-application/overview" + }, + "Next": { + "Name": "Main Components", + "Path": "solution-templates/layered-web-application/main-components" + } +} +```` + +> Some of the features mentioned in this document may not be available in the free version. We're using the **\*** symbol to indicate that a feature is available in the **[Team](https://abp.io/pricing)** and **[Higher](https://abp.io/pricing)** licenses. + +This document explains the solution and folder structure of ABP Studio's [layered solution template](index.md). + +> This document assumes that you've created a new layered solution by following the *[Quick Start: Creating a Layered Web Application with ABP Studio](../../get-started/layered-web-application.md)* guide. (Choose the *Entity Framework Core* as the database provider.) + +## Understanding the ABP Solution Structure + +When you create a new layered solution, you will see a tree structure similar to the one below in the *Solution Explorer* panel: + +![layered-solution-in-explorer](images/layered-solution-in-explorer.png) + +`Acme.Bookstore` is the main **ABP Studio module** of the solution. It includes two folders: `src` and `test`, as shown in the image above. The `src` folder contains the source code of the solution, which is structured according to [DDD](../../framework/architecture/domain-driven-design/index.md) principles, while the `test` folder holds the unit and integration tests. + +> Refer to the *[Concepts](../../studio/concepts.md)* document for a comprehensive definition of ABP Studio solution, module, and package terms. + +## Exploring the Folder Structure + +You can right-click the root item in the solution explorer (`Acme.Bookstore` for this example) and select the *Open with* -> *Explorer* command to open the folder containing the solution in your file system: + +![open-solution-with-explorer](images/open-solution-with-explorer.png) + +The root folder of the solution will be similar to the following: + +![solution-folders](images/solution-folders.png) + +* `.abpstudio` folder stores your personal preferences for this solution and is excluded from source control (Git ignored). It is created and used by ABP Studio. +* `etc` folder contains additional files for the solution, organized into the following sub-folders: + * `abp-studio` folder holds settings managed by ABP Studio. This folder is included in source control and shared among developers. + * `docker` folder provides docker-compose configurations to easily run infrastructure dependencies (e.g., RabbitMQ, Redis) for the solution on your local machine. + * `helm` folder contains Helm charts and related scripts for deploying the solution to Kubernetes. **\*** +* `src` folder contains the solution's source code, structured according to [DDD](../../framework/architecture/domain-driven-design/index.md) principles. It also includes database migrations and, depending on your project creation options, may include mobile and web application projects. +* `test` folder contains unit and integration tests for the solution. + +## Understanding the Layered Solution Structure + +The diagram below illustrates the application's layers and project dependencies: + +![layered-solution-layers](images/layered-project-dependencies.png) + +### .Domain.Shared Project + +This project contains constants, enums and other objects these are actually a part of the domain layer, but needed to be used by all layers/projects in the solution. + +A `BookType` enum and a `BookConsts` class (which may have some constant fields for the `Book` entity, like `MaxNameLength`) are good candidates for this project. + +* This project has no dependency on other projects in the solution. All other projects depend on this one directly or indirectly. + +### .Domain Project + +This is the domain layer of the solution. It mainly contains [entities, aggregate roots](../../framework/architecture/domain-driven-design/entities.md), [domain services](../../framework/architecture/domain-driven-design/domain-services.md), [value objects](../../framework/architecture/domain-driven-design/value-objects.md), [repository interfaces](../../framework/architecture/domain-driven-design/repositories.md) and other domain objects. + +A `Book` entity, a `BookManager` domain service and an `IBookRepository` interface are good candidates for this project. + +* Depends on the `.Domain.Shared` because it uses constants, enums and other objects defined in that project. + +### .Application.Contracts Project + +This project mainly contains [application service](../../framework/architecture/domain-driven-design/application-services.md) **interfaces** and [Data Transfer Objects](../../framework/architecture/domain-driven-design/data-transfer-objects.md) (DTO) of the application layer. It exists to separate the interface & implementation of the application layer. In this way, the interface project can be shared to the clients as a contract package. + +An `IBookAppService` interface and a `BookCreationDto` class are good candidates for this project. + +* Depends on the `.Domain.Shared` because it may use constants, enums and other shared objects of this project in the application service interfaces and DTOs. + +### .Application Project + +This project contains the [application service](../../framework/architecture/domain-driven-design/application-services.md) **implementations** of the interfaces defined in the `.Application.Contracts` project. + +A `BookAppService` class is a good candidate for this project. + +* Depends on the `.Application.Contracts` project to be able to implement the interfaces and use the DTOs. +* Depends on the `.Domain` project to be able to use domain objects (entities, repository interfaces... etc.) to perform the application logic. + +### .EntityFrameworkCore Project + +This is the integration project for the EF Core. It defines the `DbContext` and implements repository interfaces defined in the `.Domain` project. + +* Depends on the `.Domain` project to be able to reference to entities and repository interfaces. + +> This project is available only if you are using EF Core as the database provider. If you select another database provider, its name will be different. + +### .DbMigrator Project + +This is a console application that simplifies the execution of database migrations on development and production environments. When you run this application, it: + +* Creates the database if necessary. +* Applies the pending database migrations. +* Seeds initial data if needed. + +> This project has its own `appsettings.json` file. So, if you want to change the database connection string, remember to change this file too. + +Especially, seeding initial data is important at this point. ABP has a modular data seed infrastructure. See [its documentation](../../framework/infrastructure/data-seeding.md) for more about the data seeding. + +While creating database & applying migrations seem only necessary for relational databases, this project comes even if you choose a NoSQL database provider (like MongoDB). In that case, it still seeds the initial data which is necessary for the application. + +* Depends on the `.EntityFrameworkCore` project (for EF Core) since it needs to access to the migrations. +* Depends on the `.Application.Contracts` project to be able to access permission definitions, because the initial data seeder grants all permissions to the admin role by default. + +### .HttpApi Project + +This project is used to define your API Controllers. + +Most of the time you don't need to manually define API Controllers since ABP's [Auto API Controllers](../../framework/api-development/auto-controllers.md) feature creates them automagically based on your application layer. However, in case of you need to write API controllers, this is the best place to do it. + +* Depends on the `.Application.Contracts` project to be able to inject the application service interfaces. + +### .HttpApi.Client Project + +This is a project that defines C# client proxies to use the HTTP APIs of the solution. You can share this library to 3rd-party clients, so they can easily consume your HTTP APIs in their Dotnet applications (For other types of applications, they can still use your APIs, either manually or using a tool in their own platform) + +Most of the time you don't need to manually create C# client proxies, thanks to ABP's [Dynamic C# API Clients](../../framework/api-development/dynamic-csharp-clients.md) feature. + +`.HttpApi.Client.ConsoleTestApp` project is a console application created to demonstrate the usage of the client proxies. + +* Depends on the `.Application.Contracts` project to be able to share the same application service interfaces and DTOs with the remote service. + +> You can delete this project & dependencies if you don't need to create C# client proxies for your APIs. + +### .Web Project + +This project contains the User Interface (UI) of the application if you are using ASP.NET Core MVC UI. It contains Razor pages, JavaScript files, CSS files, images and so on... + +This project contains the main `appsettings.json` file that contains the connection string and other configurations of the application. + +* Depends on the `.HttpApi` project since the UI layer needs to use APIs and the application service interfaces of the solution. + +> If you check the source code of the `.Web.csproj` file, you will see the references to the `.Application` and the `.EntityFrameworkCore` projects. +> +> These references are actually not needed while coding your UI layer, because the UI layer normally doesn't depend on the EF Core or the Application layer's implementation. These startup templates are ready for tiered deployment, where the API layer is hosted on a separate server than the UI layer. +> +> However, if you don't choose the `--tiered` option, these references will be in the .Web project to be able to host the Web, API and application layers in a single application endpoint. +> +> This gives you the ability to use domain entities & repositories in your presentation layer. However, this is considered as a bad practice according to DDD. + +### Test Projects + +The solution has multiple test projects, one for each layer: + +* `.Domain.Tests` is used to test the domain layer. +* `.Application.Tests` is used to test the application layer. +* `.EntityFrameworkCore.Tests` is used to test EF Core configuration and custom repositories. +* `.Web.Tests` is used to test the UI (if you are using ASP.NET Core MVC UI). +* `.TestBase` is a base (shared) project for all tests. + +In addition, `.HttpApi.Client.ConsoleTestApp` is a console application (not an automated test project) which demonstrate the usage of HTTP APIs from a .NET application. + +Test projects are prepared for integration testing; + +* It is fully integrated into the ABP and all services in your application. +* It uses SQLite in-memory database for EF Core. For MongoDB, it uses the [EphemeralMongo](https://github.com/asimmon/ephemeral-mongo) library. +* Authorization is disabled, so any application service can be easily used in tests. + +You can still create unit tests for your classes which will be harder to write (because you will need to prepare mock/fake objects), but faster to run (because it only tests a single class and skips all the initialization processes). + +### How to Run? + +You can open the [Solution Runner](../../studio/running-applications.md) panel and start the all applications. The default username is `admin` and the password is `1q2w3E*`. + +![run-solution](images/run-solution.png) + +See [Getting Started With the ASP.NET Core MVC Template](../../get-started/layered-web-application.md) for more information. + +## Tiered Structure **\*** + +If you have selected the ASP.NET Core UI and specified the `--tiered` option, the solution created will be a tiered solution. The purpose of the tiered structure is to be able to **deploy Web applications and HTTP API to different servers**: + +![tiered-solution](images/tiered-solution-servers.png) + +* Browser runs your UI by executing HTML, CSS & JavaScript. +* Web servers host static UI files (CSS, JavaScript, image... etc.) & dynamic components (e.g. Razor pages). It performs HTTP requests to the API server to execute the business logic of the application. +* The API Server hosts the HTTP APIs which then use the application & domain layers of the application to perform the business logic. +* Finally, database server hosts your database. + +> Unless you actually need such a 4-tiered deployment, it's suggested to go with the default structure which is simpler to develop, deploy and maintain. + +The solution structure is shown below: + +![bookstore-solution-tiered](images/bookstore-solution-tiered.png) + +As different from the default structure, two new projects come into play: `.AuthServer` & `.HttpApi.Host`. + +### .AuthServer Project + +This project is used as an authentication server for other projects. `.Web` project uses OpenId Connect Authentication to get identity and access tokens for the current user from the AuthServer. Then uses the access token to call the HTTP API server. HTTP API server uses bearer token authentication to obtain claims from the access token to authorize the current user. + +![tiered-solution-applications](images/tiered-solution-applications-authserver.png) + +ABP uses the [OpenIddict Module](../../modules/openiddict.md) that uses the open-source [OpenIddict-core](https://github.com/openiddict/openiddict-core) library for the authentication between applications. See [OpenIddict documentation](https://documentation.openiddict.com/) for details about the OpenIddict and OpenID Connect protocol. + +It has its own `appsettings.json` that contains database connection and other configurations. + +### .HttpApi.Host Project + +This project is an application that hosts the API of the solution. It has its own `appsettings.json` that contains database connection and other configurations. + +### .Web Project + +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. diff --git a/docs/en/solution-templates/layered-web-application/swagger-integration.md b/docs/en/solution-templates/layered-web-application/swagger-integration.md new file mode 100644 index 0000000000..202ab9f892 --- /dev/null +++ b/docs/en/solution-templates/layered-web-application/swagger-integration.md @@ -0,0 +1,19 @@ +# Layered Solution: Swagger Integration + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Logging (with Serilog)", + "Path": "solution-templates/layered-web-application/logging" + }, + "Next": { + "Name": "Background Jobs", + "Path": "solution-templates/layered-web-application/background-jobs" + } +} +``` + +[Swagger](https://swagger.io/) is a tool that helps to create, document, and consume RESTful web services. It provides a user interface to interact with the APIs and also a way to generate client SDKs for the APIs. + +In the [Swagger Integration](../../framework/api-development/swagger.md) document, you can find general information about Swagger integration with ABP Framework. diff --git a/docs/en/solution-templates/layered-web-application/web-applications.md b/docs/en/solution-templates/layered-web-application/web-applications.md new file mode 100644 index 0000000000..8a1b71fda7 --- /dev/null +++ b/docs/en/solution-templates/layered-web-application/web-applications.md @@ -0,0 +1,178 @@ +# Layered Solution: Web Applications + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Main Components", + "Path": "solution-templates/layered-web-application/main-components" + }, + "Next": { + "Name": "Db Migrator", + "Path": "solution-templates/layered-web-application/db-migrator" + } +} +```` + +> Some of the features mentioned in this document may not be available in the free version. We're using the **\*** symbol to indicate that a feature is available in the **[Team](https://abp.io/pricing)** and **[Higher](https://abp.io/pricing)** licenses. + +The web applications are the main user interfaces of the solution. They are the entry points for users to interact with the system. The Layered Solution Template supports the following web applications: + +- **MVC / Razor Pages**: This is an ASP.NET Core MVC application. It is a traditional web application that serves HTML pages to users and is suitable for building web applications with server-side rendering. +- **Angular**: This is an Angular application, a single-page application (SPA) that runs on the client side. It communicates with the server using HTTP requests and is ideal for building modern web applications with rich user interfaces. +- **Blazor UI**: A flexible framework for building web applications with .NET. It supports various hosting models: + - **Blazor WebAssembly**: This is a client-side SPA that runs entirely in the user's browser. It communicates with the server using HTTP requests and is suitable for modern web applications with rich interactivity and offline capabilities. + - **Blazor Server**: This is a server-side SPA that runs on the server and communicates with the client in real time using SignalR. It is ideal for applications requiring constant connectivity and rapid server updates. + - **Blazor WebApp**: This is a combination of Blazor technologies optimized for building hybrid web applications that can leverage both client-side and server-side capabilities. + - **Maui Blazor (Hybrid)** **\***: This enables building cross-platform applications that combine Blazor for the UI with .NET MAUI for native device integration. It is suitable for building apps that work across desktop and mobile platforms. +- **No UI**: This option creates a backend-only solution without a web interface, suitable for scenarios like API-only applications or headless services. + +You can select the web application type that fits your requirements during the solution creation process in the *UI Framework* step. The Layered Solution Template generates the selected web applications with the necessary configurations and integrations. + +![Web Applications](images/web-applications.png) + +## MVC / Razor Pages + +MVC (Model-View-Controller) is a design pattern commonly used for building web applications. Razor Pages, on the other hand, is a page-based programming model designed to make building web applications simpler and more productive. + +When you select the MVC / Razor Pages option in the Layered Solution Template, it generates an ASP.NET Core MVC application named something like `Acme.BookStore.Web`. This application serves as the web interface for your solution, using server-side rendering to deliver dynamic HTML pages to users. + +## Angular + +Angular is a popular front-end framework for building single-page applications (SPAs). It offers a rich set of features for creating modern web applications with dynamic and interactive user interfaces. + +When you select the Angular option in the Layered Solution Template, it generates: +- An Angular application located under the solution's root folder, typically named `angular`. +- An ASP.NET Core application, usually named something like `Acme.Bookstore.HttpApi.Host`. + +The Angular application runs as a client-side SPA in the user's browser and communicates with the server by sending HTTP requests to the `*.HttpApi.Host` application. + +![angular-folder-structure](images/angular-folder-structure.png) + +Each of ABP modules is an NPM package. Some ABP modules are added as a dependency in `package.json`. These modules install with their dependencies. To see all ABP packages, you can run the following command in the `angular` folder: + +```bash +yarn list --pattern abp +``` + +Angular application module structure: + +![Angular template structure diagram](images/angular-template-structure-diagram.png) + +### AppModule + +`AppModule` is the root module of the application. Some of the ABP modules and some essential modules are imported to `AppModule`. + +ABP Config modules have also been imported to `AppModule` for initial requirements of the lazy-loadable ABP modules. + +### AppRoutingModule + +There are lazy-loadable ABP modules in the `AppRoutingModule` as routes. + +> Paths of ABP Modules should not be changed. + +You should add `routes` property in the `data` object to add a link on the menu to redirect to your custom pages. + +```js +{ + path: 'dashboard', + loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule), + canActivate: [authGuard, permissionGuard], + data: { + routes: { + name: 'ProjectName::Menu:Dashboard', + order: 2, + iconClass: 'fa fa-dashboard', + requiredPolicy: 'ProjectName.Dashboard.Host' + } as ABP.Route + } +} +``` +In the above example; +* If the user is not logged in, authGuard blocks access and redirects to the login page. +* permissionGuard checks the user's permission with the `requiredPolicy` property of the `routes` object. If the user is not authorized to access the page, the 403 page appears. +* The `name` property of `routes` is the menu link label. A localization key can be defined. +* The `iconClass` property of the `routes` object is the menu link icon class. +* The `requiredPolicy` property of the `routes` object is the required policy key to access the page. + +After the above `routes` definition, if the user is authorized, the dashboard link will appear on the menu. + +### Shared Module + +The modules that may be required for all modules have been imported to the `SharedModule`. You should import `SharedModule` to all modules. + +See the [Sharing Modules](https://angular.io/guide/sharing-ngmodules) document. + +### Environments + +The files under the `src/environments` folder have the essential configuration of the application. + +### Home Module + +Home module is an example lazy-loadable module that loads on the root address of the application. + +### Styles + +The required style files are added to the `styles` array in `angular.json`. `AppComponent` loads some style files lazily via `LazyLoadService` after the main bundle is loaded to shorten the first rendering time. + +### Testing + +You should create your tests in the same folder as the file you want to test. + +See the [testing document](https://angular.io/guide/testing). + +### Depended Packages + +* [NG Bootstrap](https://ng-bootstrap.github.io/) is used as UI component library. +* [NGXS](https://www.ngxs.io/) is used as state management library. +* [angular-oauth2-oidc](https://github.com/manfredsteyer/angular-oauth2-oidc) is used to support for OAuth 2 and OpenId Connect (OIDC). +* [Chart.js](https://www.chartjs.org/) is used to create widgets. +* [ngx-validate](https://github.com/ng-turkey/ngx-validate) is used for dynamic validation of reactive forms. + +## Blazor UI + +Blazor is a flexible framework for building web applications with .NET. It supports various hosting models, including Blazor WebAssembly, Blazor Server, Blazor WebApp, and Maui Blazor (Hybrid). + +### Blazor WebAssembly + +Blazor WebAssembly is a client-side SPA that runs entirely in the user's browser. It communicates with the server using HTTP requests and is suitable for modern web applications with rich interactivity and offline capabilities. + +When you select the Blazor WebAssembly option in the Layered Solution Template, it generates: +- A Blazor application located under the solution's root folder, typically named `*.Blazor`, which serves as the main Blazor host project. +- A Blazor client application, named `*.Blazor.Client`, where you can write the client-side (UI logic) code. +- An ASP.NET Core application, named `*.HttpApi.Host`, where the server-side (business logic) code runs. + +The Blazor client application communicates with the server by sending HTTP requests to the `*.HttpApi.Host` application. + +### Blazor Server + +Blazor Server is a server-side SPA that runs on the server and communicates with the client in real time using SignalR. It is ideal for applications requiring constant connectivity and rapid server updates. + +When you select the Blazor Server option in the Layered Solution Template, it generates: +- A Blazor application located under the solution's root folder, typically named `*.Blazor`, which serves as the main Blazor host project. + +### Blazor WebApp + +Blazor WebApp is a combination of Blazor technologies optimized for building hybrid web applications that can leverage both client-side and server-side capabilities. + +When you select the Blazor WebApp option in the Layered Solution Template, it generates: +- A Blazor application located under the solution's root folder, typically named `*.Blazor`, which serves as the main Blazor host project. +- A Blazor client application, named `*.Blazor.Client`, where you can write the client-side (UI logic) code. + +The Blazor client application communicates with the server by sending HTTP requests to the `*.Blazor` application. + +### Maui Blazor (Hybrid) **\*** + +Maui Blazor (Hybrid) enables building cross-platform applications that combine Blazor for the UI with .NET MAUI for native device integration. It is suitable for building apps that work across desktop and mobile platforms. + +When you select the Maui Blazor (Hybrid) option in the Layered Solution Template, it generates: +- A Maui Blazor (Hybrid) application located under the solution's root folder, typically named `*.MauiBlazor`, which serves as the main UI host project. +- An ASP.NET Core application, named `*.HttpApi.Host`, where the server-side (business logic) code runs. + +The Maui Blazor (Hybrid) application communicates with the server by sending HTTP requests to the `*.HttpApi.Host` application. + +## No UI + +This option creates a backend-only solution without a web interface, suitable for scenarios like API-only applications or headless services. + +When you select the No UI option in the Layered Solution Template, it generates an ASP.NET Core application named `*.HttpApi.Host` that serves as the backend API for your solution. diff --git a/docs/en/solution-templates/microservice/grpc-calls.md b/docs/en/solution-templates/microservice/grpc-calls.md index 7e052178c7..e8f9328a46 100644 --- a/docs/en/solution-templates/microservice/grpc-calls.md +++ b/docs/en/solution-templates/microservice/grpc-calls.md @@ -12,6 +12,14 @@ > You must have an ABP Business or a higher license to be able to create a microservice solution. +## What is gRPC? + You can use [gRPC](https://grpc.io) to enable high-performance, low-latency communication between microservices. gRPC, or Google Remote Procedure Call, is an open-source remote procedure call system initially developed by Google. It uses HTTP/2 for transport, Protocol Buffers as the interface description language, and provides features such as authentication, load balancing, and more. -For inter-service communication, gRPC is a great choice because it is faster and more efficient than REST. It is also language-agnostic, meaning you can use it with any programming language that supports gRPC. ABP does not restrict you to use gRPC; you can use it just like normal .NET applications. \ No newline at end of file +## Learning Resources + +The microservice startup template hasn't any configuration related to gRPC communication. You can see the following resources to learn how to implement gRPC calls between your services: + +* [Using gRPC with the ABP Framework](https://abp.io/community/articles/using-grpc-with-the-abp-framework-2dgaxzw3) (ABP Community article) +* [Overview for gRPC on .NET](https://learn.microsoft.com/en-us/aspnet/core/grpc/) (Microsoft's documentation) +* [Code-first gRPC services and clients with .NET](https://learn.microsoft.com/en-us/aspnet/core/grpc/code-first) (Microsoft's documentation) \ No newline at end of file diff --git a/docs/en/solution-templates/microservice/guides/add-new-microservice.md b/docs/en/solution-templates/microservice/guides/add-new-microservice.md index 12c8b527e8..f68f315dd7 100644 --- a/docs/en/solution-templates/microservice/guides/add-new-microservice.md +++ b/docs/en/solution-templates/microservice/guides/add-new-microservice.md @@ -1,3 +1 @@ -# ABP Studio Microservice Solution: Adding New Microservices - -This document is planned to be written later. \ No newline at end of file +> This document is [moved to here](../adding-new-microservices.md). \ No newline at end of file diff --git a/docs/en/solution-templates/microservice/microservices.md b/docs/en/solution-templates/microservice/microservices.md index 1afc56547e..4d2848dc93 100644 --- a/docs/en/solution-templates/microservice/microservices.md +++ b/docs/en/solution-templates/microservice/microservices.md @@ -12,7 +12,7 @@ > You must have an ABP Business or a higher license to be able to create a microservice solution. -The ABP Studio Microservice solution consists of a few microservices at the beginning. It is expected that you [add more microservices](guides/add-new-microservice.md) as your solution grows. This document briefly explains the structure of pre-built microservices in the solution. +The ABP Studio Microservice solution consists of a few microservices at the beginning. It is expected that you [add more microservices](adding-new-microservices.md) as your solution grows. This document briefly explains the structure of pre-built microservices in the solution. The microservice count varies based on the options you've selected during the solution creation. However, the following microservices are always included: @@ -41,7 +41,7 @@ Let's explain the projects: * `Acme.CloudCrm.AdministrationService.Contracts` contains service interfaces and data transfer objects of your service. It is useful to separate contracts. In this way, you can share the *Contracts* package with the clients, so they can easily consume your services. See the [communication](communication.md) document to learn how to do it. * `Acme.CloudCrm.AdministrationService.Tests` contains the unit and integration tests for that microservice. While it is highly suggested to build tests for your services, you can delete that project if you don't want to write tests. -> We haven't applied **layering** for the pre-built microservices, because they don't include much code and no need for such complexity. Microservices should be small (micro!) services, so you typically can manage your codebase in a single project/layer. However, if you want to implement layering for your microservices, you can create a layered microservice by following the *[Adding New Microservice](guides/add-new-microservice.md)* guide. +> We haven't applied **layering** for the pre-built microservices, because they don't include much code and no need for such complexity. Microservices should be small (micro!) services, so you typically can manage your codebase in a single project/layer. However, if you want to implement layering for your microservices, you can create a layered microservice by following the *[Adding New Microservice](adding-new-microservices.md)* guide. Next sections introduces and explains the pre-build services of the solution. diff --git a/docs/en/solution-templates/single-layer-web-application/_index.md b/docs/en/solution-templates/single-layer-web-application/_index.md new file mode 100644 index 0000000000..d5720f5602 --- /dev/null +++ b/docs/en/solution-templates/single-layer-web-application/_index.md @@ -0,0 +1,59 @@ +# Single Layer Application Solution Template + +This template provides a simple solution structure with a single project. This document explains that solution structure in details. + +## Getting Started + +* Follow the [Getting Started guide](../../get-started/single-layer-web-application.md) to create a new solution using this startup solution template. +* Follow the [TODO application tutorial](../../tutorials/todo/single-layer/index.md) to learn how to create a simple application with this startup solution template. + +## The Solution Structure + +If you created your solution with the default options, you will have a .NET solution as shown below: + +![](../../images/bookstore-single-layer-solution-structure.png) + +In the next sections, we will explain the structure based on this example. Your startup solution can be slightly different based on your preferences. + +### Folder Structure + +Since this template provides a single-project solution, we've separated concerns into folders instead of projects. You can see the pre-defined folders as shown below: + +![](../../images/single-layer-folder-structure.png) + +* Define your database mappings (for [EF Core](../../framework/data/entity-framework-core) or [MongoDB](../../framework/data/mongodb) and [repositories](../../framework/architecture/domain-driven-design/repositories.md) in the `Data` folder. +* Define your [entities](../../framework/architecture/domain-driven-design/entities.md) in the `Entities` folder. +* Define your UI localization keys/values in the `Localization` folder. +* Define your UI menu items in the `Menus` folder. +* Define your [object-to-object mapping](../../framework/infrastructure/object-to-object-mapping.md) classes in the `ObjectMapping` folder. +* Define your UI pages (Razor Pages) in the `Pages` folder (create `Controllers` and `Views` folder yourself if you prefer the MVC pattern). +* Define your [application services](../../framework/architecture/domain-driven-design/application-services.md) in the `Services` folder. + +### How to Run? + +Before running the application, you need to create the database and seed the initial data. To do that, you can run the following command in the directory of your project (in the same folder of the `.csproj` file): + +```bash +dotnet run --migrate-database +``` + +This command will create the database and seed the initial data for you. Then you can run the application with any IDE that supports .NET or by running the `dotnet run` command in the directory of your project. The default username is `admin` and the password is `1q2w3E*`. + +> While creating a database & applying migrations seem only necessary for relational databases, you should run this command even if you choose a NoSQL database provider (like MongoDB). In that case, it still seeds the initial data which is necessary for the application. + +### The Angular UI + +If you choose `Angular` as the UI framework, the solution will be separated into two folders: + +* An `angular` folder that contains the Angular UI application, the client-side code. +* An `aspnet-core` folder that contains the ASP.NET Core solution (a single project), the server-side code. + +The server-side is similar to the solution described in the *Solution Structure* section above. This project serves the API, so the Angular application can consume it. + +The client-side application consumes the HTTP APIs as mentioned. You can see the folder structure of the Angular project shown below: + +![](../../images/single-layer-angular-folder-structure.png) + + ## See Also + +* [Video tutorial](https://abp.io/video-courses/essentials/app-template) \ No newline at end of file diff --git a/docs/en/solution-templates/single-layer-web-application/authentication.md b/docs/en/solution-templates/single-layer-web-application/authentication.md new file mode 100644 index 0000000000..17a6f0f2c5 --- /dev/null +++ b/docs/en/solution-templates/single-layer-web-application/authentication.md @@ -0,0 +1,52 @@ +# Single Layer Solution: Authentication + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Built-In Features", + "Path": "solution-templates/layered-web-application/built-in-features" + }, + "Next": { + "Name": "Database configurations in the Single-Layer solution", + "Path": "solution-templates/layered-web-application/database-configurations" + } +} +``` + +> Some of the features mentioned in this document may not be available in the free version. We're using the **\*** symbol to indicate that a feature is available in the **[Team](https://abp.io/pricing)** and **[Higher](https://abp.io/pricing)** licenses. + +The [Single Layer solution template](index.md) is fully configured for authentication. All the services and applications are configured to use the [OpenIddict](https://documentation.openiddict.com) library for authentication. They are configured in a common way for authentication. This document explains that common authentication structure. + +## OpenIddict + +[OpenIddict](https://documentation.openiddict.com) is an open-source library that provides a simple and easy way to implement an OpenID Connect server in your application. ABP has built-in modules ([OpenIddict](../../modules/openiddict.md), [OpenIddict UI **\***](../../modules/openiddict-pro.md)) to integrate OpenIddict into the solution. + +## Initial Data Seeding + +The Single Layer solution template includes an initial data seeding mechanism to create default clients (applications) and scopes for the solution, if necessary (e.g., when using an Angular UI). The `OpenIddictDataSeedContributor` class can be found in the `Data` folder of the host project. If authentication is handled by the UI application(e.g., MVC / Razor Pages), this class is not included. + +The [OpenIddict UI **\***](../../modules/openiddict-pro.md) module is added only if you select it while creating the solution. + +![new-solution-openiddict-module](images/new-solution-openiddict-module.png) + +The OpenIddict UI **\*** module provides a user interface to manage the OpenIddict entities such as applications, scopes, etc. You can manage these entities from the application UI. + +![openiddict-ui](images/openiddict-ui.png) + +### External Providers + +The authentication server handles token generation, validation, and user account management (e.g., login, registration). It uses the [Account](../../modules/account.md) or [Account Pro **\***](../../modules/account-pro.md) module. The [Account Pro **\***](../../modules/account-pro.md) module additionally supports [social logins](../../modules/account-pro.md#social--external-logins) (e.g., Google, Facebook). Social logins can be enabled, disabled, and configured directly from the application's user interface. + +![account-external-provider](images/account-external-provider.png) + +## Authentication Flows + +Applications in the solution use different authentication flows depending on the application type: + +- **MVC UI Web Application**: + Uses the [Hybrid Flow](https://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth) (OpenID Connect Authentication) for user authentication. +- **SPA and Swagger Applications**: + Use the [Authorization Code Flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth) to authenticate users. + +If the UI is a SPA application (such as an Angular app), the API host uses [JWT Bearer Authentication](https://jwt.io/introduction/) to authorize user actions. \ No newline at end of file diff --git a/docs/en/solution-templates/single-layer-web-application/background-jobs.md b/docs/en/solution-templates/single-layer-web-application/background-jobs.md new file mode 100644 index 0000000000..1a3d592a5e --- /dev/null +++ b/docs/en/solution-templates/single-layer-web-application/background-jobs.md @@ -0,0 +1,19 @@ +# Single Layer Solution: Background Jobs + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Swagger integration", + "Path": "solution-templates/single-layer-web-application/swagger-integration" + }, + "Next": { + "Name": "Background Workers", + "Path": "solution-templates/single-layer-web-application/background-workers" + } +} +``` + +Background jobs are long-running, asynchronous tasks that run in the background of your application. They are useful for tasks that are not time-sensitive, such as sending emails, generating reports, or processing data. Background jobs are typically triggered by a user action or a scheduled task. You can learn more about background jobs in the [Background Jobs](../../framework/infrastructure/background-jobs/index.md) document. + +In the Single Layer solution template, background jobs are implemented using the [Background Jobs](../../modules/background-jobs.md) module. This module provides a simple and efficient way to create and manage background jobs in your application. It includes features such as job queues, job scheduling. It stores job information in the database, allowing you to track the status of jobs and retry failed jobs. \ No newline at end of file diff --git a/docs/en/solution-templates/single-layer-web-application/background-workers.md b/docs/en/solution-templates/single-layer-web-application/background-workers.md new file mode 100644 index 0000000000..15cad61797 --- /dev/null +++ b/docs/en/solution-templates/single-layer-web-application/background-workers.md @@ -0,0 +1,63 @@ +# Single Layer Solution: Background Workers + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Background Jobs", + "Path": "solution-templates/single-layer-web-application/background-jobs" + }, + "Next": { + "Name": "Distributed Locking", + "Path": "solution-templates/single-layer-web-application/distributed-locking" + } +} +``` + +Background workers are long-running processes that run in the background of your application. They are useful for tasks that are not time-sensitive, such as processing data, sending notifications, or monitoring system health. Background workers are typically started when the application starts and run continuously until the application stops. You can learn more about background workers in the [Background Workers](../../framework/infrastructure/background-workers/index.md) document. + +Basically, you can create scheduled workers for a specific time interval based on your requirements, such as checking the status of inactive users and changing their status to passive if they have not logged in to the application in the last 30 days. + +```csharp +public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase +{ + public PassiveUserCheckerWorker( + AbpAsyncTimer timer, + IServiceScopeFactory serviceScopeFactory) : base( + timer, + serviceScopeFactory) + { + Timer.Period = 600000; //10 minutes + } + + protected async override Task DoWorkAsync( + PeriodicBackgroundWorkerContext workerContext) + { + Logger.LogInformation("Starting: Setting status of inactive users..."); + + // Resolve dependencies + var userRepository = workerContext + .ServiceProvider + .GetRequiredService(); + + // Do the work + await userRepository.UpdateInactiveUserStatusesAsync(); + + Logger.LogInformation("Completed: Setting status of inactive users..."); + } +} +``` + +After creating a worker, you should also register it in the application. You can register your worker in the `OnApplicationInitializationAsync` method of your module class: + +```csharp +public class BookstoreModule : AbpModule +{ + public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) + { + await context.AddBackgroundWorkerAsync(); + } +} +``` + +> When scaling out your application in a distributed system, it's crucial to consider that the same background workers might run on multiple instances of the same service. This requires careful management of potential side effects. For example, if you're processing messages from a queue, you need to ensure that each message is processed only once. To prevent multiple instances from handling the same message, you can use [distributed locking](../../framework/infrastructure/distributed-locking.md). diff --git a/docs/en/solution-templates/single-layer-web-application/blob-storing.md b/docs/en/solution-templates/single-layer-web-application/blob-storing.md new file mode 100644 index 0000000000..d316c009c6 --- /dev/null +++ b/docs/en/solution-templates/single-layer-web-application/blob-storing.md @@ -0,0 +1,51 @@ +# Single Layer Solution: BLOB Storing + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Multi-Tenancy", + "Path": "solution-templates/single-layer-web-application/multi-tenancy" + }, + "Next": { + "Name": "CORS Configuration", + "Path": "solution-templates/single-layer-web-application/cors-configuration" + } +} +``` + +> Some of the features mentioned in this document may not be available in the free version. We're using the **\*** symbol to indicate that a feature is available in the **[Team](https://abp.io/pricing)** and **[Higher](https://abp.io/pricing)** licenses. + +This document explains how to store BLOBs (Binary Large Objects) in a single-layer solution. Storing files, images, videos, and other large objects is common in distributed systems. For more details, refer to the [BLOB Storing System](../../framework/infrastructure/blob-storing/index.md) documentation. + +In the single-layer solution template, the [Database Provider](../../framework/infrastructure/blob-storing/database.md) is used to store BLOBs in the database. The `Volo.Abp.BlobStoring.Database.EntityFrameworkCore` or `Volo.Abp.BlobStoring.Database.MongoDB` package provides the required implementations for storing and retrieving BLOBs in the database. This setup is integrated into the single-layer solution template and is used across all related projects. You can modify the database configuration in the `appsettings.json` file of the API project. + +You can use the `IBlobContainer` or `IBlobContainer` service to store and retrieve BLOBs. Here is an example of storing a BLOB: + +```csharp +public class MyService : ITransientDependency +{ + private readonly IBlobContainer _blobContainer; + + public MyService(IBlobContainer blobContainer) + { + _blobContainer = blobContainer; + } + + public async Task SaveBytesAsync(byte[] bytes) + { + await _blobContainer.SaveAsync("my-blob-1", bytes); + } + + public async Task GetBytesAsync() + { + return await _blobContainer.GetAllBytesOrNullAsync("my-blob-1"); + } +} +``` + +## File Management Module + +The *File Management* module is optional and can be added to the solution during the creation process. It provides a user interface for managing folders and files. For more information, see the [File Management *](../../modules/file-management.md) document. + +![file-management](images/file-management-index-page.png) \ No newline at end of file diff --git a/docs/en/solution-templates/single-layer-web-application/built-in-features.md b/docs/en/solution-templates/single-layer-web-application/built-in-features.md new file mode 100644 index 0000000000..2700c1d8dd --- /dev/null +++ b/docs/en/solution-templates/single-layer-web-application/built-in-features.md @@ -0,0 +1,25 @@ +# Single Layer Solution: Built-In Features + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Db Migrator", + "Path": "solution-templates/single-layer-web-application/db-migrator" + }, + "Next": { + "Name": "Authentication", + "Path": "solution-templates/single-layer-web-application/authentication" + } +} +``` + +The Single Layer solution template includes several built-in features to help you get started with your single-layer web application. These features are designed to provide a solid foundation for your application and help you focus on your business logic. This document provides an overview of the built-in features included in the Single Layer solution template. The following documents explain these features in detail: + +* [Authentication](authentication.md) +* [Database configurations](database-configurations.md) +* [Logging (with Serilog)](logging.md) +* [Swagger integration](swagger-integration.md) +* [Multi-Tenancy](multi-tenancy.md) +* [BLOB storing](blob-storing.md) +* [CORS configuration](cors-configuration.md) \ No newline at end of file diff --git a/docs/en/solution-templates/single-layer-web-application/cors-configuration.md b/docs/en/solution-templates/single-layer-web-application/cors-configuration.md new file mode 100644 index 0000000000..f54c73ad79 --- /dev/null +++ b/docs/en/solution-templates/single-layer-web-application/cors-configuration.md @@ -0,0 +1,32 @@ +# Single Layer Solution: CORS Configuration + +```json +//[doc-nav] +{ + "Previous": { + "Name": "BLOB Storing", + "Path": "solution-templates/single-layer-web-application/blob-storing" + } +} +``` + +Cross-Origin Resource Sharing (CORS) is a security feature that allows web applications to make requests to a different domain than the one that served the web page. + +In the single-layer solution template, CORS configuration is applied in the following cases: +- When [Angular](web-applications.md#angular) is selected as the web application type. +- When [Blazor WebAssembly](web-applications.md#blazor-webassembly) is selected as the web application type. +- When [No UI](web-applications.md#no-ui) is selected as the web application type. + +CORS settings are configured in the `appsettings.json` file of the corresponding project. The web application usually serves as the entry point for front-end applications, so it must be set up to accept requests from different origins. + +The default configuration in `appsettings.json` is as follows: + +```json +{ + "App": { + "CorsOrigins": "https://*.MyProjectName.com" + } +} +``` + +You can modify the `CorsOrigins` property to include additional domains or wildcard subdomains as needed for your application. \ No newline at end of file diff --git a/docs/en/solution-templates/single-layer-web-application/database-configurations.md b/docs/en/solution-templates/single-layer-web-application/database-configurations.md new file mode 100644 index 0000000000..ec48fcf6f0 --- /dev/null +++ b/docs/en/solution-templates/single-layer-web-application/database-configurations.md @@ -0,0 +1,190 @@ +# Single Layer Solution: Database configurations + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Authentication", + "Path": "solution-templates/single-layer-web-application/authentication" + }, + "Next": { + "Name": "Logging (with Serilog)", + "Path": "solution-templates/single-layer-web-application/logging" + } +} +``` + +> Some of the features mentioned in this document may not be available in the free version. We're using the **\*** symbol to indicate that a feature is available in the **[Team](https://abp.io/pricing)** and **[Higher](https://abp.io/pricing)** licenses. + +ABP Studio's Single-Layer Solution Template includes pre-configured database settings. This document explains how to manage database configurations in your solution. + +## Connection String + +Connection strings are stored in the `appsettings.json` file and can be customized for different environments by editing this file. [Web Application](web-applications.md) projects use the `Default` connection string by default. + +To update the connection string for the `Default` key, modify the `appsettings.json` file in your project. Connection strings are defined under the `ConnectionStrings` section, as shown below: + +```json +{ + "ConnectionStrings": { + "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=Bookstore;Trusted_Connection=True;TrustServerCertificate=true" + } +} +``` + +## The DbContext Class + +In the Single-Layer Solution Template, the `DbContext` class is defined in the main project. This class manages the database schema and is derived from the `AbpDbContext` class, which offers additional features and configurations. You can customize the `DbContext` class to add new entities, relationships, and configurations. It is located in the `Data` folder of the main project. + +```csharp +public class BookstoreDbContext : AbpDbContext +{ + + public const string DbTablePrefix = "App"; + public const string DbSchema = null; + + public BookstoreDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + /* Include modules to your migration db context */ + + builder.ConfigureSettingManagement(); + builder.ConfigureBackgroundJobs(); + builder.ConfigureAuditLogging(); + builder.ConfigureFeatureManagement(); + builder.ConfigurePermissionManagement(); + builder.ConfigureBlobStoring(); + builder.ConfigureIdentityPro(); + builder.ConfigureOpenIddictPro(); + builder.ConfigureGdpr(); + builder.ConfigureLanguageManagement(); + builder.ConfigureSaas(); + builder.ConfigureTextTemplateManagement(); + + /* Configure your own entities here */ + } +} +``` + +### OnModelCreating Method + +The `OnModelCreating` method is used to configure the database schema. It calls the `Configure*` methods of the ABP Framework to configure the database schema for the modules. You can also configure your own tables/entities inside this method. + +```csharp +protected override void OnModelCreating(ModelBuilder builder) +{ + base.OnModelCreating(builder); + + builder.ConfigurePermissionManagement(); + builder.ConfigureSettingManagement(); + builder.ConfigureBackgroundJobs(); + builder.ConfigureAuditLogging(); + builder.ConfigureFeatureManagement(); + builder.ConfigureIdentityPro(); + builder.ConfigureOpenIddictPro(); + builder.ConfigureLanguageManagement(); + builder.ConfigureSaas(); + builder.ConfigureTextTemplateManagement(); + builder.ConfigureGdpr(); + builder.ConfigureCmsKit(); + builder.ConfigureCmsKitPro(); + builder.ConfigureBlobStoring(); + + /* Configure your own tables/entities inside here */ + + //builder.Entity(b => + //{ + // b.ToTable(DbTablePrefix + "YourEntities", DbSchema); + // b.ConfigureByConvention(); //auto configure for the base class props + // //... + //}); +} +``` + +> The `Configure*` methods are extension methods defined in each module's `EntityFrameworkCore` project. These methods are used to configure the database schema for their respective modules. + +### Configuration + +In the `BookstoreModule` class, the `ConfigureEfCore` method is used to configure the database context. It registers the `BookstoreDbContext` class to the [dependency injection](../../framework/fundamentals/dependency-injection.md) system and sets the SQL Server as the default DBMS for the application. + +```csharp +private void ConfigureEfCore(ServiceConfigurationContext context) +{ + context.Services.AddAbpDbContext(options => + { + /* You can remove "includeAllEntities: true" to create + * default repositories only for aggregate roots + * Documentatidon: https://docs.abp.io/en/abp/latest/Entity-Framework-Core#add-default-repositories + */ + options.AddDefaultRepositories(includeAllEntities: true); + }); + + Configure(options => + { + options.Configure(configurationContext => + { + configurationContext.UseSqlServer(); + }); + }); + +} +``` + +## The `IDesignTimeDbContextFactory` Implementation + +The `IDesignTimeDbContextFactory` interface is used to create a `DbContext` instance at design time. It is used by EF Core tools to create migrations and update the database. The `BookstoreDbContextFactory` class implements the `IDesignTimeDbContextFactory` interface to create a `BookstoreMigrationsDbContext` instance. + +```csharp +public class BookstoreDbContextFactory : IDesignTimeDbContextFactory +{ + public BookstoreDbContext CreateDbContext(string[] args) + { + BookstoreEfCoreEntityExtensionMappings.Configure(); + var configuration = BuildConfiguration(); + + var builder = new DbContextOptionsBuilder() + .UseSqlServer(configuration.GetConnectionString("Default")); + + return new BookstoreDbContext(builder.Options); + } + + private static IConfigurationRoot BuildConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false); + + return builder.Build(); + } +} +``` + +## SaaS Module: The Tenant Management UI **\*** + +SaaS module provides the necessary UI to set and change connection string for tenants and trigger the database migrations. + +### The Connection String Management Modal + +You can click to the *Database Connection Strings* command in the *Actions* dropdown button for a tenant in the *Tenants* page of the SaaS module: + +![Database Connection Strings](images/database-connection-strings.png) + +It opens the *Database Connection Strings* modal as shown below: + +![Database Connection Strings Modal](images/database-connection-strings-modal.png) + +Here, we can set a *Default connection string* for the tenant. + +When you make the changes and save the dialog, the database is automatically created and migrated. If you later update the connection string (for example if you change the database name), it will also trigger the database migration process again. + +### Manually Applying the Database Migrations + +If you need to manually trigger the database migrations for a specific tenant, click the *Actions* dropdown for the related tenant and select the *Apply Database Migrations* command on the *Tenant Management* page of the SaaS module: + +![Apply Database Migrations](images/apply-database-migrations.png) diff --git a/docs/en/solution-templates/single-layer-web-application/db-migrator.md b/docs/en/solution-templates/single-layer-web-application/db-migrator.md new file mode 100644 index 0000000000..988b16cef6 --- /dev/null +++ b/docs/en/solution-templates/single-layer-web-application/db-migrator.md @@ -0,0 +1,107 @@ +# Single Layer Solution: Db Migrator + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Web Applications", + "Path": "solution-templates/single-layer-web-application/web-applications" + }, + "Next": { + "Name": "Built-In Features", + "Path": "solution-templates/single-layer-web-application/built-in-features" + } +} +```` + +Unlike the Layered solution template, the Single Layer solution template does not include a separate database migrator project. Instead, the main application project handles database migration and seed data operations. The `*.DbMigrator` project is excluded from this template. To manage database migrations and seed data, you can use the `migrate-database.ps1` script in the root directory or run the `dotnet run --migrate-database` command from the main application project directory. + +![Single Layer Solution: Db Migrator](images/single-layer-db-migrator.png) + +After the migration completes, a message will appear in the console. You can verify the success of the migration by checking the database. + +## Database Migration Service + +Under the `Data` folder of the project, the `BookstoreDbMigrationService` class is responsible for database migration and seed data operations. The `MigrateAsync` method is called in the `Program` class to migrate the database when the application starts with the `--migrate-database` argument. + +First, it checks if the database is created and applies the pending migrations. Then, it seeds the initial data using the `SeedAsync` method. + +```csharp +public async Task MigrateAsync() +{ + var initialMigrationAdded = AddInitialMigrationIfNotExist(); + + if (initialMigrationAdded) + { + return; + } + + Logger.LogInformation("Started database migrations..."); + + await MigrateDatabaseSchemaAsync(); + await SeedDataAsync(); + + Logger.LogInformation($"Successfully completed host database migrations."); + + var tenants = await _tenantRepository.GetListAsync(includeDetails: true); + + var migratedDatabaseSchemas = new HashSet(); + foreach (var tenant in tenants) + { + using (_currentTenant.Change(tenant.Id)) + { + if (tenant.ConnectionStrings.Any()) + { + var tenantConnectionStrings = tenant.ConnectionStrings + .Select(x => x.Value) + .ToList(); + + if (!migratedDatabaseSchemas.IsSupersetOf(tenantConnectionStrings)) + { + await MigrateDatabaseSchemaAsync(tenant); + + migratedDatabaseSchemas.AddIfNotContains(tenantConnectionStrings); + } + } + + await SeedDataAsync(tenant); + } + + Logger.LogInformation($"Successfully completed {tenant.Name} tenant database migrations."); + } + + Logger.LogInformation("Successfully completed all database migrations."); + Logger.LogInformation("You can safely end this process..."); +} +``` + +The `BookstoreDbSchemaMigrator` class is used in the `MigrateDatabaseSchemaAsync` method for the database migration process. It is responsible for applying migrations to the database. + +```csharp +public class BookstoreDbSchemaMigrator : ITransientDependency +{ + private readonly IServiceProvider _serviceProvider; + + public BookstoreDbSchemaMigrator( + IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public async Task MigrateAsync() + { + + /* We intentionally resolving the BookstoreDbContext + * from IServiceProvider (instead of directly injecting it) + * to properly get the connection string of the current tenant in the + * current scope. + */ + + await _serviceProvider + .GetRequiredService() + .Database + .MigrateAsync(); + + } +} +``` \ No newline at end of file diff --git a/docs/en/solution-templates/single-layer-web-application/distributed-locking.md b/docs/en/solution-templates/single-layer-web-application/distributed-locking.md new file mode 100644 index 0000000000..f99adba265 --- /dev/null +++ b/docs/en/solution-templates/single-layer-web-application/distributed-locking.md @@ -0,0 +1,44 @@ +# Single Layer Solution: Distributed Locking + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Background Workers", + "Path": "solution-templates/single-layer-web-application/background-workers" + }, + "Next": { + "Name": "Multi-Tenancy", + "Path": "solution-templates/single-layer-web-application/multi-tenancy" + } +} +``` + +Distributed locking is a mechanism that allows multiple instances of an application to coordinate and synchronize access to shared resources. It is useful for scenarios where multiple instances of an application need to ensure that only one instance can access a resource at a time. You can learn more in the [Distributed Locking](../../framework/infrastructure/distributed-locking.md) document. + +## Distributed Locking in Single Layer Solutions + +The single-layer solution template does not include distributed lock package by default. You can add the [Volo.Abp.DistributedLock](https://www.nuget.org/packages/Volo.Abp.DistributedLocking) package to your project to use distributed locking. This package provides a distributed lock mechanism that works with Redis. You can inject the `IAbpDistributedLock` service to acquire and release. Here is an example of using distributed locking in your application: + +```csharp +public class MyService : ITransientDependency +{ + private readonly IAbpDistributedLock _distributedLock; + + public MyService(IAbpDistributedLock distributedLock) + { + _distributedLock = distributedLock; + } + + public async Task MyMethodAsync() + { + await using (var handle = await _distributedLock.TryAcquireAsync("MyLockName")) + { + if (handle != null) + { + // your code that access the shared resource + } + } + } +} +``` diff --git a/docs/en/solution-templates/single-layer-web-application/images/account-external-provider.png b/docs/en/solution-templates/single-layer-web-application/images/account-external-provider.png new file mode 100644 index 0000000000..acfefeee7c Binary files /dev/null and b/docs/en/solution-templates/single-layer-web-application/images/account-external-provider.png differ diff --git a/docs/en/solution-templates/single-layer-web-application/images/apply-database-migrations.png b/docs/en/solution-templates/single-layer-web-application/images/apply-database-migrations.png new file mode 100644 index 0000000000..28d5f73786 Binary files /dev/null and b/docs/en/solution-templates/single-layer-web-application/images/apply-database-migrations.png differ diff --git a/docs/en/solution-templates/single-layer-web-application/images/database-connection-strings-modal.png b/docs/en/solution-templates/single-layer-web-application/images/database-connection-strings-modal.png new file mode 100644 index 0000000000..7dc2fd7243 Binary files /dev/null and b/docs/en/solution-templates/single-layer-web-application/images/database-connection-strings-modal.png differ diff --git a/docs/en/solution-templates/single-layer-web-application/images/database-connection-strings.png b/docs/en/solution-templates/single-layer-web-application/images/database-connection-strings.png new file mode 100644 index 0000000000..bc13bbf932 Binary files /dev/null and b/docs/en/solution-templates/single-layer-web-application/images/database-connection-strings.png differ diff --git a/docs/en/solution-templates/single-layer-web-application/images/file-management-index-page.png b/docs/en/solution-templates/single-layer-web-application/images/file-management-index-page.png new file mode 100644 index 0000000000..c19d41f006 Binary files /dev/null and b/docs/en/solution-templates/single-layer-web-application/images/file-management-index-page.png differ diff --git a/docs/en/solution-templates/single-layer-web-application/images/new-solution-openiddict-module.png b/docs/en/solution-templates/single-layer-web-application/images/new-solution-openiddict-module.png new file mode 100644 index 0000000000..5c849d9f8c Binary files /dev/null and b/docs/en/solution-templates/single-layer-web-application/images/new-solution-openiddict-module.png differ diff --git a/docs/en/solution-templates/single-layer-web-application/images/openiddict-ui.png b/docs/en/solution-templates/single-layer-web-application/images/openiddict-ui.png new file mode 100644 index 0000000000..fdbbc4530b Binary files /dev/null and b/docs/en/solution-templates/single-layer-web-application/images/openiddict-ui.png differ diff --git a/docs/en/solution-templates/single-layer-web-application/images/saas-module-selection.png b/docs/en/solution-templates/single-layer-web-application/images/saas-module-selection.png new file mode 100644 index 0000000000..8904c4eaec Binary files /dev/null and b/docs/en/solution-templates/single-layer-web-application/images/saas-module-selection.png differ diff --git a/docs/en/solution-templates/single-layer-web-application/images/single-layer-db-migrator.png b/docs/en/solution-templates/single-layer-web-application/images/single-layer-db-migrator.png new file mode 100644 index 0000000000..08d8319268 Binary files /dev/null and b/docs/en/solution-templates/single-layer-web-application/images/single-layer-db-migrator.png differ diff --git a/docs/en/solution-templates/single-layer-web-application/images/single-layer-solution-folders.png b/docs/en/solution-templates/single-layer-web-application/images/single-layer-solution-folders.png new file mode 100644 index 0000000000..d2cf400a66 Binary files /dev/null and b/docs/en/solution-templates/single-layer-web-application/images/single-layer-solution-folders.png differ diff --git a/docs/en/solution-templates/single-layer-web-application/images/single-layer-solution-in-explorer.png b/docs/en/solution-templates/single-layer-web-application/images/single-layer-solution-in-explorer.png new file mode 100644 index 0000000000..66a47e24e8 Binary files /dev/null and b/docs/en/solution-templates/single-layer-web-application/images/single-layer-solution-in-explorer.png differ diff --git a/docs/en/solution-templates/single-layer-web-application/images/single-layer-solution-in-visual-studio.png b/docs/en/solution-templates/single-layer-web-application/images/single-layer-solution-in-visual-studio.png new file mode 100644 index 0000000000..adcb33247e Binary files /dev/null and b/docs/en/solution-templates/single-layer-web-application/images/single-layer-solution-in-visual-studio.png differ diff --git a/docs/en/solution-templates/single-layer-web-application/images/solution-runner.png b/docs/en/solution-templates/single-layer-web-application/images/solution-runner.png new file mode 100644 index 0000000000..fdc9e28a52 Binary files /dev/null and b/docs/en/solution-templates/single-layer-web-application/images/solution-runner.png differ diff --git a/docs/en/solution-templates/single-layer-web-application/images/web-applications.png b/docs/en/solution-templates/single-layer-web-application/images/web-applications.png new file mode 100644 index 0000000000..5c12767c2f Binary files /dev/null and b/docs/en/solution-templates/single-layer-web-application/images/web-applications.png differ diff --git a/docs/en/solution-templates/single-layer-web-application/index.md b/docs/en/solution-templates/single-layer-web-application/index.md index d5720f5602..ce295f20a9 100644 --- a/docs/en/solution-templates/single-layer-web-application/index.md +++ b/docs/en/solution-templates/single-layer-web-application/index.md @@ -1,59 +1,36 @@ -# Single Layer Application Solution Template - -This template provides a simple solution structure with a single project. This document explains that solution structure in details. - -## Getting Started - -* Follow the [Getting Started guide](../../get-started/single-layer-web-application.md) to create a new solution using this startup solution template. -* Follow the [TODO application tutorial](../../tutorials/todo/single-layer/index.md) to learn how to create a simple application with this startup solution template. - -## The Solution Structure - -If you created your solution with the default options, you will have a .NET solution as shown below: - -![](../../images/bookstore-single-layer-solution-structure.png) - -In the next sections, we will explain the structure based on this example. Your startup solution can be slightly different based on your preferences. - -### Folder Structure - -Since this template provides a single-project solution, we've separated concerns into folders instead of projects. You can see the pre-defined folders as shown below: - -![](../../images/single-layer-folder-structure.png) - -* Define your database mappings (for [EF Core](../../framework/data/entity-framework-core) or [MongoDB](../../framework/data/mongodb) and [repositories](../../framework/architecture/domain-driven-design/repositories.md) in the `Data` folder. -* Define your [entities](../../framework/architecture/domain-driven-design/entities.md) in the `Entities` folder. -* Define your UI localization keys/values in the `Localization` folder. -* Define your UI menu items in the `Menus` folder. -* Define your [object-to-object mapping](../../framework/infrastructure/object-to-object-mapping.md) classes in the `ObjectMapping` folder. -* Define your UI pages (Razor Pages) in the `Pages` folder (create `Controllers` and `Views` folder yourself if you prefer the MVC pattern). -* Define your [application services](../../framework/architecture/domain-driven-design/application-services.md) in the `Services` folder. - -### How to Run? - -Before running the application, you need to create the database and seed the initial data. To do that, you can run the following command in the directory of your project (in the same folder of the `.csproj` file): - -```bash -dotnet run --migrate-database -``` - -This command will create the database and seed the initial data for you. Then you can run the application with any IDE that supports .NET or by running the `dotnet run` command in the directory of your project. The default username is `admin` and the password is `1q2w3E*`. - -> While creating a database & applying migrations seem only necessary for relational databases, you should run this command even if you choose a NoSQL database provider (like MongoDB). In that case, it still seeds the initial data which is necessary for the application. - -### The Angular UI - -If you choose `Angular` as the UI framework, the solution will be separated into two folders: - -* An `angular` folder that contains the Angular UI application, the client-side code. -* An `aspnet-core` folder that contains the ASP.NET Core solution (a single project), the server-side code. - -The server-side is similar to the solution described in the *Solution Structure* section above. This project serves the API, so the Angular application can consume it. - -The client-side application consumes the HTTP APIs as mentioned. You can see the folder structure of the Angular project shown below: - -![](../../images/single-layer-angular-folder-structure.png) - - ## See Also - -* [Video tutorial](https://abp.io/video-courses/essentials/app-template) \ No newline at end of file +# ABP Studio: Single Layer Solution Template + +````json +//[doc-nav] +{ + "Next": { + "Name": "Overview", + "Path": "solution-templates/single-layer-web-application/overview" + } +} +```` + +ABP Studio offers pre-architected, production-ready templates to quickly start a new solution. One of these is the Single Layer solution template, designed for building monolithic systems with minimal layers. It follows [Domain-Driven Design](../../framework/architecture/domain-driven-design) (DDD) principles and common application patterns. The template includes a single project, integrates existing modules, and provides host applications based on your selections, making it a solid foundation for your system. + +> **This document explains the Single Layer solution template in detail. It is a reference document to fully understand the solution and refer to when you have trouble.** +> +> **If you want to quickly create a single-layer solution, please refer to *[Quick Start: Creating a Single Layer Web Application with ABP Studio](../../get-started/single-layer-web-application.md)* document.** + +## Contents + +* [Overview](overview.md) +* [Solution structure](solution-structure.md) +* [Main Components](main-components.md) + * [Web Applications](web-applications.md) + * [Db Migrator](db-migrator.md) +* [Built-In Features](built-in-features.md) + * [Authentication](authentication.md) + * [Database configurations](database-configurations.md) + * [Logging (with Serilog)](logging.md) + * [Swagger integration](swagger-integration.md) + * [Background Jobs](background-jobs.md) + * [Background Workers](background-workers.md) + * [Distributed Locking](distributed-locking.md) + * [Multi-Tenancy](multi-tenancy.md) + * [BLOB storing](blob-storing.md) + * [CORS configuration](cors-configuration.md) \ No newline at end of file diff --git a/docs/en/solution-templates/single-layer-web-application/logging.md b/docs/en/solution-templates/single-layer-web-application/logging.md new file mode 100644 index 0000000000..5b3483822b --- /dev/null +++ b/docs/en/solution-templates/single-layer-web-application/logging.md @@ -0,0 +1,35 @@ +# Single Layer Solution: Logging + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Database configurations", + "Path": "solution-templates/single-layer-web-application/database-configurations" + }, + "Next": { + "Name": "Swagger integration", + "Path": "solution-templates/single-layer-web-application/swagger-integration" + } +} +``` + +The ABP Studio [single-layer solution template](index.md) is fully configured for [logging](../../framework/fundamentals/logging.md). All the applications are configured to use the [Serilog](https://serilog.net/) library for structured logging. They are configured in a common way for logging. This document explains that common logging structure. + +## The Serilog Sinks + +The Serilog library is configured so it writes the logs to the following targets (a.k.a. [sinks](https://github.com/serilog/serilog/wiki/Provided-Sinks)) in parallel: + +* **[Console](https://github.com/serilog/serilog-sinks-console)**: Logs are written to the standard output of the executing application. Logging to console is useful when you want to see logs easily while it is running in a container. +* **[File](https://github.com/serilog/serilog-sinks-file)**: Logs are written to a file named `logs.txt` located under the `Logs` folder of the executing application. File logging is useful when you run the application on your local computer. You can check logs easily when you have a trouble. This sinks is only configured for DEBUG mode. It won't be available in your production environment (you can change the behavior in your `Program.cs` file). +* **ABP Studio**: This is a Sink provided by ABP Studio. It sends all logs to ABP Studio, so you can easily monitor your logs in real-time on your ABP Studio Application Monitoring panel. + +The solution can work with [any sink](https://github.com/serilog/serilog/wiki/Provided-Sinks) supported by Serilog. You can add more sinks, remove pre-installed sinks or fine tune their configuration for your solution. + +## Program.cs + +The `Program.cs` file is the main point that configures the logging system. It is done here, because we want to initialize and start the logging in the very beginning of the application. + +## Additional Information + +We are using ABP Serilog Enrichers in the module class of the application. It is done by the `app.UseAbpSerilogEnrichers();` line in the `OnApplicationInitialization` method of your module class. That ASP.NET Core middleware adds current [tenant](../../framework/architecture/multi-tenancy/index.md), [user](../../framework/infrastructure/current-user.md), client and correlation id information to the log records. \ No newline at end of file diff --git a/docs/en/solution-templates/single-layer-web-application/main-components.md b/docs/en/solution-templates/single-layer-web-application/main-components.md new file mode 100644 index 0000000000..d219c69155 --- /dev/null +++ b/docs/en/solution-templates/single-layer-web-application/main-components.md @@ -0,0 +1,20 @@ +# Single Layer Solution: Main Components + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Solution structure", + "Path": "solution-templates/single-layer-web-application/solution-structure" + }, + "Next": { + "Name": "Web Applications", + "Path": "solution-templates/single-layer-web-application/web-applications" + } +} +```` + +The single-layer solution template is a single project containing all the essential components for building a monolithic application. It supports the `--migrate-database` command-line argument to create the database and seed initial data. The main components of the solution are: + +* [Web Applications](web-applications.md) +* [Db Migrator](db-migrator.md) \ No newline at end of file diff --git a/docs/en/solution-templates/single-layer-web-application/multi-tenancy.md b/docs/en/solution-templates/single-layer-web-application/multi-tenancy.md new file mode 100644 index 0000000000..b2cd042d0e --- /dev/null +++ b/docs/en/solution-templates/single-layer-web-application/multi-tenancy.md @@ -0,0 +1,74 @@ +# Single Layer Solution: Multi-Tenancy + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Distributed Locking", + "Path": "solution-templates/single-layer-web-application/distributed-locking" + }, + "Next": { + "Name": "BLOB storing", + "Path": "solution-templates/single-layer-web-application/blob-storing" + } +} +``` + +> Some of the features mentioned in this document may not be available in the free version. We're using the **\*** symbol to indicate that a feature is available in the **[Team](https://abp.io/pricing)** and **[Higher](https://abp.io/pricing)** licenses. + +Multi-tenancy is a software architecture where a single instance(codebase) of software runs on a server and serves multiple tenants. Tenants are isolated from each other and can have their own data, configurations, and users. This document explains how the multi-tenancy mechanism works in the single-layer solution template. You can learn more about multi-tenancy in the [Multi-Tenancy](../../framework/architecture/multi-tenancy/index.md), [Tenant Management](../../modules/tenant-management.md) and [SaaS **\***](../../modules/saas.md) documents. + +## Multi-Tenancy in Single Layer Solutions + +The single-layer solution templates use the *Multi-Tenancy* architecture only if you *Enable Multi-Tenancy **\**** option while creating the solution. + +![saas-module-selection](images/saas-module-selection.png) + +You can use different databases for each tenant or a shared database for some tenants. In the *SaaS **\*** module, you can specify the database connection strings in the [Connection Strings Management Modal](../../modules/saas.md#connection-string). All cached data is isolated by tenant. Each event, background job, and other data is stored with the tenant id. + +You can use the `ICurrentTenant` service to get the current tenant information in your application. + +```csharp +public class MyService : ITransientDependency +{ + private readonly ICurrentTenant _currentTenant; + + public MyService(ICurrentTenant currentTenant) + { + _currentTenant = currentTenant; + } + + public void MyMethod() + { + var tenantId = _currentTenant.Id; + var tenantName = _currentTenant.Name; + } +} +``` + +Additionally, you can use the [DataFilter](../../framework/infrastructure/data-filtering.md#idatafilter-service-enabledisable-data-filters) system to disable the tenant filter and list all data in the same database. + +```csharp +public class MyBookService : ITransientDependency +{ + private readonly IDataFilter _multiTenantFilter; + private readonly IRepository _bookRepository; + + public MyBookService( + IDataFilter multiTenantFilter, + IRepository bookRepository) + { + _multiTenantFilter = multiTenantFilter; + _bookRepository = bookRepository; + } + + public async Task> GetAllBooksIncludingDeletedAsync() + { + //Temporary disable the IMultiTenant filter + using (_multiTenantFilter.Disable()) + { + return await _bookRepository.GetListAsync(); + } + } +} +``` \ No newline at end of file diff --git a/docs/en/solution-templates/single-layer-web-application/overview.md b/docs/en/solution-templates/single-layer-web-application/overview.md new file mode 100644 index 0000000000..8592dc1e8a --- /dev/null +++ b/docs/en/solution-templates/single-layer-web-application/overview.md @@ -0,0 +1,95 @@ +# Single Layer Solution: Overview + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Index", + "Path": "solution-templates/single-layer-web-application/index" + }, + "Next": { + "Name": "Solution Structure", + "Path": "solution-templates/single-layer-web-application/solution-structure" + } +} +```` + +> Some of the features mentioned in this document may not be available in the free version. We're using the **\*** symbol to indicate that a feature is available in the **[Team](https://abp.io/pricing)** and **[Higher](https://abp.io/pricing)** licenses. + +This document explains what the Single-Layer solution template offers. + +## Pre-Installed Libraries & Services + +The following **libraries and services** come **pre-installed** and **configured** for both **development** and **production** environments. After creating your solution, you can **modify** or **remove** most of them as needed. + +* **[Autofac](https://autofac.org/)** for [Dependency Injection](../../framework/fundamentals/dependency-injection.md). +* **[Serilog](https://serilog.net/)** with File and Console [logging](../../framework/fundamentals/logging.md) providers. +* **[Swagger](https://swagger.io/)** for exploring and testing HTTP APIs. +* **[OpenIddict](https://github.com/openiddict/openiddict-core)** as the built-in authentication server. + +## Pre-Configured Features + +The solution comes with the following built-in and pre-configured features: + +* **Authentication** is fully configured based on best practices. +* **[Permission](../../framework/fundamentals/authorization.md)** (authorization), **[setting](../../framework/infrastructure/settings.md)**, **[feature](../../framework/infrastructure/features.md)** and the **[localization](../../framework/fundamentals/localization.md)** management systems are pre-configured and ready to use. +* **[Background job system](../../framework/infrastructure/background-jobs/index.md)**. +* **[BLOB storge](../../framework/infrastructure/blob-storing/index.md)** system is installed with the [database provider](../../framework/infrastructure/blob-storing/database.md). +* **On-the-fly database migration** system (services automatically migrated their database schema when you deploy a new version). **\*** +* **[Swagger](https://swagger.io/)** authentication is configured to test the authorized HTTP APIs. + +## Fundamental Modules + +The following modules are pre-installed and configured for the solution: + +* **[Account](../../modules/account.md)** to authenticate users (login, register, two factor auth **\***, etc) +* **[Identity](../../modules/identity.md)** to manage roles and users +* **[OpenIddict](../../modules/openiddict.md)** (the core part) to implement the OAuth authentication flows + +In addition, [Feature Management](../../modules/feature-management.md), [Permission Management](../../modules/permission-management.md) and [Setting Management](../../modules/setting-management.md) modules are pre-installed as they are the fundamental feature modules of the ABP. + +## Optional Modules + +The following modules are optionally included in the solution, so you can select the ones you need: + +* **[Audit Logging](../../modules/audit-logging.md)** +* **[Chat](../../modules/chat.md)** **\*** +* **[File Management](../../modules/file-management.md)** **\*** +* **[GDPR](../../modules/gdpr.md)** **\*** +* **[Language Management](../../modules/language-management.md)** **\*** +* **[OpenIddict (Management UI)](../../modules/openiddict.md)** **\*** +* **[Tenant Management](../../modules/tenant-management.md) (Multi-Tenancy) or [SaaS](../../modules/saas.md)** **\*** +* **[Text Template Management](../../modules/text-template-management.md)** **\*** + +## UI Theme + +The **[LeptonX Lite](../../ui-themes/lepton-x-lite/index.md) or [LeptonX theme](https://leptontheme.com/)** **\*** is pre-configured for the solution. You can select one of the color palettes (System, Light, or Dark) as default, while the end-user dynamically change it on the fly. + +## Other Options + +Single-layer startup template asks for some preferences while creating your solution. + +### Database Providers + +There are two database provider options are provided on a new solution creation: + +* **[Entity Framework Core](../../framework/data/entity-framework-core/index.md)** with SQL Server, MySQL and PostgreSQL DBMS options. You can [switch to another DBMS](../../framework/data/entity-framework-core/other-dbms.md) manually after creating your solution. +* **[MongoDB](../../framework/data/mongodb/index.md)** + +### UI Frameworks + +The solution comes with a main web application with the following UI Framework options: + +* **None** (doesn't include a UI application to the solution) +* **Angular** +* **MVC / Razor Pages UI** +* **Blazor WebAssembly** +* **Blazor Server** + +### Multi-Tenancy & SaaS Module **\*** + +The **[SaaS module](../../modules/saas.md)** is included as an option. When you select it, the **[multi-tenancy](../../framework/architecture/multi-tenancy/index.md)** system is automatically configured. Otherwise, the system will not include any multi-tenancy overhead. + +## See Also + +* [Quick Start: Creating a Single Layer Web Application with ABP Studio](../../get-started/single-layer-web-application.md) diff --git a/docs/en/solution-templates/single-layer-web-application/solution-structure.md b/docs/en/solution-templates/single-layer-web-application/solution-structure.md new file mode 100644 index 0000000000..45448b46b4 --- /dev/null +++ b/docs/en/solution-templates/single-layer-web-application/solution-structure.md @@ -0,0 +1,71 @@ +# Single Layer Solution: The Structure + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Overview", + "Path": "solution-templates/single-layer-web-application/overview" + }, + "Next": { + "Name": "Main Components", + "Path": "solution-templates/single-layer-web-application/main-components" + } +} +```` + +> Some of the features mentioned in this document may not be available in the free version. We're using the **\*** symbol to indicate that a feature is available in the **[Team](https://abp.io/pricing)** and **[Higher](https://abp.io/pricing)** licenses. + +This document explains the solution and folder structure of ABP Studio's [single layer solution template](index.md). + +> This document assumes that you've created a new single-layer solution by following the *[Quick Start: Creating a Single Layer Web Application with ABP Studio](../../get-started/single-layer-web-application.md)* guide. (Choose the *Entity Framework Core* as the database provider.) + +## Understanding the ABP Solution Structure + +The single-layer solution template is designed to be simple and easy to understand. It includes a single project that contains all the necessary components to build a monolithic application. The solution structure is as follows: + +![single-layer-solution-in-explorer](images/single-layer-solution-in-explorer.png) + +`Acme.Bookstore` is the main **ABP Studio module** in the solution. It also includes the **ABP Studio package** `Acme.Bookstore` as the host application. + +> Refer to the *[Concepts](../../studio/concepts.md)* document for a comprehensive definition of ABP Studio solution, module, and package terms. + +## The Solution Structure + +If you create the solution based on *[Quick Start: Creating a Single Layer Web Application with ABP Studio](../../get-started/single-layer-web-application.md)* guide, the solution structure will be as follows: + +![single-layer-solution-in-visual-studio](images/single-layer-solution-in-visual-studio.png) + +### Folder Structure + +This template uses a single-project structure, with concerns separated into folders instead of projects. The pre-defined folders are shown below: + +![single-layer-solution-folders](images/single-layer-solution-folders.png) + +* **Data**: Define your database mappings (for [EF Core](../../framework/data/entity-framework-core) or [MongoDB](../../framework/data/mongodb) and [repositories](../../framework/architecture/domain-driven-design/repositories.md)) in this folder. +* **Entities**: Define your [entities](../../framework/architecture/domain-driven-design/entities.md) in this folder. +* **Localization**: Define your UI localization keys/values in this folder. +* **Menus**: Define your UI menu items in this folder. +* **Migrations**: Contains the database migration files. It is created automatically by EF Core. +* **ObjectMapping**: Define your [object-to-object mapping](../../framework/infrastructure/object-to-object-mapping.md) classes in this folder. +* **Pages**: Define your UI pages (Razor Pages) in this folder (create `Controllers` and `Views` folders yourself if you prefer the MVC pattern). +* **Permissions**: Define your [permissions](../../framework/fundamentals/authorization.md) in this folder. +* **Services**: Define your [application services](../../framework/architecture/domain-driven-design/application-services.md) in this folder. + +### How to Run? + +When you create a new solution it automatically creates initial migration and run database migrator for you (unless you uncheck these options). However, you can run the following command in the directory of your project (in the same folder of the `.csproj` file) to create the database and seed the initial data: + +```bash +dotnet run --migrate-database +``` + +This command will create the database and seed the initial data for you. Then you can run the application with the ABP Studio [Solution Runner](../../studio/running-applications.md). The default username is `admin` and the password is `1q2w3E*`. + +![solution-runner](images/solution-runner.png) + +> While creating a database & applying migrations seem only necessary for relational databases, you should run this command even if you choose a NoSQL database provider (like MongoDB). In that case, it still seeds the initial data which is necessary for the application. + +## See Also + +* [Video tutorial](https://abp.io/video-courses/essentials/app-template) \ No newline at end of file diff --git a/docs/en/solution-templates/single-layer-web-application/swagger-integration.md b/docs/en/solution-templates/single-layer-web-application/swagger-integration.md new file mode 100644 index 0000000000..6de24705ee --- /dev/null +++ b/docs/en/solution-templates/single-layer-web-application/swagger-integration.md @@ -0,0 +1,19 @@ +# Single Layer Solution: Swagger Integration + +```json +//[doc-nav] +{ + "Previous": { + "Name": "Logging (with Serilog)", + "Path": "solution-templates/single-layer-web-application/logging" + }, + "Next": { + "Name": "Background Jobs", + "Path": "solution-templates/single-layer-web-application/background-jobs" + } +} +``` + +[Swagger](https://swagger.io/) is a tool that helps to create, document, and consume RESTful web services. It provides a user interface to interact with the APIs and also a way to generate client SDKs for the APIs. + +In the [Swagger Integration](../../framework/api-development/swagger.md) document, you can find general information about Swagger integration with ABP Framework. diff --git a/docs/en/solution-templates/single-layer-web-application/web-applications.md b/docs/en/solution-templates/single-layer-web-application/web-applications.md new file mode 100644 index 0000000000..94945ba757 --- /dev/null +++ b/docs/en/solution-templates/single-layer-web-application/web-applications.md @@ -0,0 +1,72 @@ +# Single Layer Solution: Web Applications + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Main Components", + "Path": "solution-templates/single-layer-web-application/main-components" + }, + "Next": { + "Name": "Db Migrator", + "Path": "solution-templates/single-layer-web-application/db-migrator" + } +} +```` + +The single-layer solution template includes a web application project that acts as the main application. This ASP.NET Core application hosts the API endpoints and may also serve the user interface, depending on the selected UI framework. + +- **MVC / Razor Pages**: This is an ASP.NET Core MVC application. It is a traditional web application that serves HTML pages to users and is suitable for building web applications with server-side rendering. +- **Angular**: This is an Angular application, a single-page application (SPA) that runs on the client side. It communicates with the server using HTTP requests and is ideal for building modern web applications with rich user interfaces. +- **Blazor UI**: A flexible framework for building web applications with .NET. It supports various hosting models: + - **Blazor WebAssembly**: This is a client-side SPA that runs entirely in the user's browser. It communicates with the server using HTTP requests and is suitable for modern web applications with rich interactivity and offline capabilities. + - **Blazor Server**: This is a server-side SPA that runs on the server and communicates with the client in real time using SignalR. It is ideal for applications requiring constant connectivity and rapid server updates. +- **No UI**: This option creates a backend-only solution without a web interface, suitable for scenarios like API-only applications or headless services. + +You can select the web application type that fits your requirements during the solution creation process in the *UI Framework* step. The single-layer solution template generates the selected web applications with the necessary configurations and integrations. + +![Web Applications](images/web-applications.png) + +## MVC / Razor Pages + +MVC (Model-View-Controller) is a design pattern commonly used for building web applications. Razor Pages, on the other hand, is a page-based programming model designed to make building web applications simpler and more productive. + +When you select the MVC / Razor Pages option in the single-layer solution template, it generates an ASP.NET Core MVC application named something like `Acme.BookStore`. This application serves as the web interface for your solution, using server-side rendering to deliver dynamic HTML pages to users. + +## Angular + +Angular is a popular front-end framework for building single-page applications (SPAs). It offers a rich set of features for creating modern web applications with dynamic and interactive user interfaces. + +When you select the Angular option in the single-layer solution template, it generates: +- An Angular application located under the solution's root folder, typically named `angular`. +- An ASP.NET Core application, usually named something like `Acme.Bookstore`. + +The Angular application runs as a client-side SPA in the user's browser and communicates with the server by sending HTTP requests to the ASP.NET Core host application. + +## Blazor UI + +Blazor is a flexible framework for building web applications with .NET. It supports various hosting models, including Blazor WebAssembly, Blazor Server, Blazor WebApp, and Maui Blazor (Hybrid). + +### Blazor WebAssembly + +Blazor WebAssembly is a client-side SPA that runs entirely in the user's browser. It communicates with the server using HTTP requests and is suitable for modern web applications with rich interactivity and offline capabilities. + +When you select the Blazor WebAssembly option in the Layered Solution Template, it generates: +- A Blazor application located under the solution's root folder, typically named `*.Blazor`, which serves as the main Blazor host project. +- A Blazor client application, named `*.Blazor.Client`, where you can write the client-side (UI logic) code. +- An ASP.NET Core application, named `*.HttpApi.Host`, where the server-side (business logic) code runs. + +The Blazor client application communicates with the server by sending HTTP requests to the `*.HttpApi.Host` application. + +### Blazor Server + +Blazor Server is a server-side SPA that runs on the server and communicates with the client in real time using SignalR. It is ideal for applications requiring constant connectivity and rapid server updates. + +When you select the Blazor Server option in the Layered Solution Template, it generates: +- A Blazor application located under the solution's root folder, typically named `*.Blazor`, which serves as the main Blazor host project. + +## No UI + +This option creates a backend-only solution without a web interface, suitable for scenarios like API-only applications or headless services. + +When you select the No UI option in the Layered Solution Template, it generates an ASP.NET Core application named `*.HttpApi.Host` that serves as the backend API for your solution. diff --git a/docs/en/studio/images/solution-runner/cli-application-context-menu.png b/docs/en/studio/images/solution-runner/cli-application-context-menu.png index 0e35ecec0b..0a66138e61 100644 Binary files a/docs/en/studio/images/solution-runner/cli-application-context-menu.png and b/docs/en/studio/images/solution-runner/cli-application-context-menu.png differ diff --git a/docs/en/studio/images/solution-runner/csharp-application-context-menu-build.png b/docs/en/studio/images/solution-runner/csharp-application-context-menu-build.png index dea4556ad7..aec7d5d0d7 100644 Binary files a/docs/en/studio/images/solution-runner/csharp-application-context-menu-build.png and b/docs/en/studio/images/solution-runner/csharp-application-context-menu-build.png differ diff --git a/docs/en/studio/images/solution-runner/csharp-application-context-menu-monitor.png b/docs/en/studio/images/solution-runner/csharp-application-context-menu-monitor.png index 0743fb5ae9..42eca8cf00 100644 Binary files a/docs/en/studio/images/solution-runner/csharp-application-context-menu-monitor.png and b/docs/en/studio/images/solution-runner/csharp-application-context-menu-monitor.png differ diff --git a/docs/en/studio/images/solution-runner/csharp-application-context-menu.png b/docs/en/studio/images/solution-runner/csharp-application-context-menu.png index cd007f59a3..76c90a2e95 100644 Binary files a/docs/en/studio/images/solution-runner/csharp-application-context-menu.png and b/docs/en/studio/images/solution-runner/csharp-application-context-menu.png differ diff --git a/docs/en/studio/images/solution-runner/folder-context-menu-add.png b/docs/en/studio/images/solution-runner/folder-context-menu-add.png index 4dda7cfe03..c83a54f5e0 100644 Binary files a/docs/en/studio/images/solution-runner/folder-context-menu-add.png and b/docs/en/studio/images/solution-runner/folder-context-menu-add.png differ diff --git a/docs/en/studio/images/solution-runner/folder-context-menu-build.png b/docs/en/studio/images/solution-runner/folder-context-menu-build.png index ce72b6d8f5..3e5ad9b56d 100644 Binary files a/docs/en/studio/images/solution-runner/folder-context-menu-build.png and b/docs/en/studio/images/solution-runner/folder-context-menu-build.png differ diff --git a/docs/en/studio/images/solution-runner/folder-context-menu.png b/docs/en/studio/images/solution-runner/folder-context-menu.png index 1bc4478352..c17cb8659d 100644 Binary files a/docs/en/studio/images/solution-runner/folder-context-menu.png and b/docs/en/studio/images/solution-runner/folder-context-menu.png differ diff --git a/docs/en/studio/images/solution-runner/manage-start-actions.png b/docs/en/studio/images/solution-runner/manage-start-actions.png new file mode 100644 index 0000000000..4b96c08d3e Binary files /dev/null and b/docs/en/studio/images/solution-runner/manage-start-actions.png differ diff --git a/docs/en/studio/images/solution-runner/profile-root-context-menu-add.png b/docs/en/studio/images/solution-runner/profile-root-context-menu-add.png index 11b8da1362..ec26ebcaf0 100644 Binary files a/docs/en/studio/images/solution-runner/profile-root-context-menu-add.png and b/docs/en/studio/images/solution-runner/profile-root-context-menu-add.png differ diff --git a/docs/en/studio/images/solution-runner/profile-root-context-menu-build.png b/docs/en/studio/images/solution-runner/profile-root-context-menu-build.png index 972f850149..db2ae4de3a 100644 Binary files a/docs/en/studio/images/solution-runner/profile-root-context-menu-build.png and b/docs/en/studio/images/solution-runner/profile-root-context-menu-build.png differ diff --git a/docs/en/studio/images/solution-runner/profile-root-context-menu.png b/docs/en/studio/images/solution-runner/profile-root-context-menu.png index a94f5b3704..d81bf68514 100644 Binary files a/docs/en/studio/images/solution-runner/profile-root-context-menu.png and b/docs/en/studio/images/solution-runner/profile-root-context-menu.png differ diff --git a/docs/en/studio/images/solution-runner/solutioın-runner-properties.png b/docs/en/studio/images/solution-runner/solutioın-runner-properties.png index 0dcbd08b1e..8611f8be3d 100644 Binary files a/docs/en/studio/images/solution-runner/solutioın-runner-properties.png and b/docs/en/studio/images/solution-runner/solutioın-runner-properties.png differ diff --git a/docs/en/studio/installation.md b/docs/en/studio/installation.md index 4dc158e464..34dde8a5c9 100644 --- a/docs/en/studio/installation.md +++ b/docs/en/studio/installation.md @@ -8,7 +8,7 @@ Before you begin the installation process for ABP Studio, ensure that your system meets the following pre-requirements: ### Node -Make sure [Node.js](https://nodejs.org/en) is installed on your system. If you have not installed Node.js, you can download the `v18.19+` version from the official [Node.js website](https://nodejs.org/en/download/prebuilt-installer). +Make sure [Node.js](https://nodejs.org/en) is installed on your system. If you have not installed Node.js, you can download the `v22+` version from the official [Node.js website](https://nodejs.org/en/download/prebuilt-installer). ### WireGuard (Optional) ABP Studio needs [WireGuard](https://www.wireguard.com/) for Kubernetes operations. You can find the installation instructions for your specific operating system below: @@ -19,7 +19,7 @@ Installation instructions for your Windows operating system are on the official **For macOS:** Installation instructions for your macOS operating system are on the official [WireGuard website](https://www.wireguard.com/install/#macos-homebrew-and-macports-basic-cli-homebrew-userspace-go-homebrew-tools-macports-userspace-go-macports-tools). -### Docker (Optional) +### Docker ABP Studio needs [Docker](https://www.docker.com/) for [Kubernetes](https://kubernetes.io/) operations. Install Docker by following the guidelines on the official [Docker website](https://docs.docker.com/get-docker/). ## Installation diff --git a/docs/en/studio/release-notes.md b/docs/en/studio/release-notes.md index 31e5bbdfcf..8555e19d18 100644 --- a/docs/en/studio/release-notes.md +++ b/docs/en/studio/release-notes.md @@ -2,6 +2,58 @@ This document contains **brief release notes** for each ABP Studio release. Release notes only include **major features** and **visible enhancements**. Therefore, they don't include all the development done in the related version. +## 0.9.20 (2025-01-08) + +* Upgraded templates to version `9.0.3`. +* Fixed Invariant Culture problem in source code downloading. +* Added missing linux support to OldCliInstaller +* Increased database test connection timeout up to 10seconds. + +## 0.9.19 (2025-01-02) + +* Disabled auto-scroll when scrolled up in the logging section. +* Added localhost development certificate check during solution load. +* Added testing connection string in project creation. +* Made enhancements for exception handling. + +## 0.9.18 (2024-12-24) + +* Fixed Blazor WebApp Kubernetes problems. +* Added Visual Studio & Rider options to solution root. +* Fixed problems in blazor-server nolayers template. + +## 0.9.17 (2024-12-17) + +* Added social login option to the "No Layers" Blazor WebAssembly template. +* Fixed AutoMapper missing configuration exception problem during module import. +* Fixed Blazor WebAssembly build issue for the MAUI template. +* Fixed a problem that prevented ABP Studio from opening on macOS. + +## 0.9.16 (2024-12-11) + +> This version does not work for macOS, we are currently working on that manner. + +* Added a new command for refreshing the previously runned C#/Js Proxies. +* Added kubernetes configurations to Blazor WebApp for Microservice Template. +* Handled multiple dbcontexts for ef core migration operations. +* Made enhancements for dynamic localization feature in Microservice Template. +* Upgraded LeptonX Theme package versions to `4.0.2`. + +## 0.9.15 (2024-12-05) + +* Upgraded templates to version `9.0.1`. +* Fixed problems in the microservice service_nolayers template. +* Fixed microservice angular template for wrong file-management module reference. +* Fixed added extra lines in the hosts.txt file. + +## 0.9.14 (2024-12-03) + +* Refactored `dotnet watch` command in solution runner. +* Added multi-tenancy option for open source startup templates. +* Implemented adding angular library when a new microservice is created. +* Adjusted *Optional Modules* section in solution configurations +* Fixed bugs in the nolayers host project. + ## 0.9.13 (2024-11-25) * Angular - Theme-based Fixes for the Home Page. diff --git a/docs/en/studio/running-applications.md b/docs/en/studio/running-applications.md index a4dc4f8388..9e266ef7a9 100644 --- a/docs/en/studio/running-applications.md +++ b/docs/en/studio/running-applications.md @@ -41,27 +41,22 @@ When you click *Add New Profile*, it opens the *Create New Profile* window. You ## Using the Profile -After selecting the current profile, which is the *Default* profile that comes pre-configured, we can utilize the tree items. This allows us to execute collective commands and create various tree structures based on our specific needs. You can navigate through the root of the tree and right-click to view the context menu, which includes 3 options: `Run`, `Build` and `Add`. +After selecting the current profile, which is the *Default* profile that comes pre-configured, we can utilize the tree items. This allows us to execute collective commands and create various tree structures based on our specific needs. You can navigate through the root of the tree and right-click to view the context menu, which includes the following options: `Start All`, `Stop All`, `Build`, `Add`, and `Manage Start Actions`. ![profile-root-context-menu](images/solution-runner/profile-root-context-menu.png) -### Run +### Start/Stop All -We can start/stop the applications with this option. Go to root of the tree and right-click to view the context menu, in this example *Acme.Bookstore(Default)* -> *Run*. - -![profile-root-context-menu-run](images/solution-runner/profile-root-context-menu-run.png) +We can start/stop the applications with these options. Go to the root of the tree and right-click to view the context menu: - `Start All`: Start all(CLI, C#) applications. - `Stop All`: Stop all(CLI, C#) applications. -- `Build & Start All`: Builds each C# application in the [Background Tasks](./overview#background-tasks) and starts all (CLI, C#) applications after the build tasks are completed. - -> `Start All` doesn't build the C# applications before running. If you're running it for the first time or if you've made changes, you should build the applications. You can simply use the `Build & Start All`. -> You can change the current profile while applications are running in the previous profile. The applications continue to run under the previous profile. For example if we start the `Acme.BookStore.AdministrationService`, `Acme.BookStore.IdentityService` applications when current profile is *team-1* and after change the current profile to *team-2* the applications continue to run under *team-1*. +> You can change the current profile while applications are running in the previous profile. The applications continue to run under the previous profile. For example, if we start the `Acme.BookStore.AdministrationService`, `Acme.BookStore.IdentityService` applications when the current profile is *team-1* and after changing the current profile to *team-2* the applications continue to run under *team-1*. ### Build -We can use common [dotnet](https://learn.microsoft.com/en-us/dotnet/core/tools) commands in this option. Go to root of the tree and right-click to view the context menu, in this example *Acme.Bookstore(Default)* -> *Build*, there are 4 options available: +We can use common [dotnet](https://learn.microsoft.com/en-us/dotnet/core/tools) commands in this option. Go to the root of the tree and right-click to view the context menu, in this example *Acme.Bookstore(Default)* -> *Build*, there are 4 options available: ![profile-root-context-menu-build](images/solution-runner/profile-root-context-menu-build.png) @@ -80,7 +75,7 @@ We can add 3 different item type to *Profile* for defining the tree structure. T #### C# Application -When we go to root of the tree and right-click, in this example *Acme.BookStore(Default)* -> *Add* -> *C# Application* it opens the *Add Application* window. There are two methods to add applications: *This solution* and *External*. To add via the *This solution* tab, follow these steps: +When we go to the root of the tree and right-click, in this example *Acme.BookStore(Default)* -> *Add* -> *C# Application* it opens the *Add Application* window. There are two methods to add applications: *This solution* and *External*. To add via the *This solution* tab, follow these steps: ![profile-root-add-csharp-application](images/solution-runner/profile-root-add-csharp-application.png) @@ -98,7 +93,7 @@ The C# project doesn't have to be within the current [Solution Explorer](./solut - `Path`: Provide the path to the .csproj file you wish to add. The path will be [normalized](https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#path-normalization), allowing the project location to be flexible, as long as it's accessible from the current [ABP Solution](./concepts.md#solution). - `Name`: Give an arbitrary name to see in solution runner. This name should be unique for each profile. -- `Launch url`: Is the url when we want to browse. But if added project doesn't have launch url we can leave it empty. +- `Launch url`: This is the url when we want to browse. But if the added project doesn't have launch url we can leave it empty. - `Kubernetes service`: If you're not using the *Kubernetes* panel leave it empty. But if there is a helm chart for added application we should give the correct regex pattern. It's necessary for browse, when we connect the kubernetes cluster we should browse the services instead *Launch url*. Give the matching regex pattern for your helm chart kubernetes service name. You can click the `OK` button to add the C# application to the profile. @@ -137,17 +132,27 @@ You can click the `OK` button to add the folder to the profile. - To remove a folder from the tree, open the context menu by right-clicking the folder and selecting *Delete*. - When starting applications, they continue to restart until the application starts gracefully. To stop the restarting process when attempting to restart the application, click the icon on the left. Additionally, you can review the *Logs* to understand why the application isn't starting gracefully. +### Manage Start Actions + +This command will open a dialog where you can set start actions and start orders of sub-applications and sub-folders. + +![manage-start-actions](images/solution-runner/manage-start-actions.png) + +You can order the applications by dragging the icon in the first column. In the screenshot above, applications & folders are ordered like this: *Applications under infrastructure* > *Applications under services* > *Applications under gateways* > *AuthServer* > *Angular*. You can also set starting order and other actions for each folder by performing `right click > Manage Start Actions` on them. + +- **Action**: There are two options: `Start` and `Don't start`. This is usefull if you want to exclude applications from batch start. +- **Build**: This option allows to disable/enable build before starting the application. If you are working on a single application, you can exclude the other applications from build to save time. This option can also be set by performing `right click > properties` on applications. +- **Watch**: When enabled, changes in your code are watched and dotnet hot-reloads the application or restarts it if needed. This option also can be set by performing `right click > properties` on applications. + ## Folder -We already now why we need folder in the [previous](./running-applications.md#folder) section, we can use collective commands within this folder items. To do that go to folder and open the context menu by right-clicking, which includes 5 options `Start`, `Build`, `Add`, `Rename` and `Delete`. +We already now why we need folder in the [previous](./running-applications.md#folder) section, we can use collective commands within this folder items. To do that go to folder and open the context menu by right-clicking, which includes 5 options `Start`, `Stop`, `Build`, `Add`, `Manage Start Actions`, `Rename` and `Delete`. ![folder-context-menu](images/solution-runner/folder-context-menu.png) -### Start - -You can see the context menu by right-clicking *Folder* -> *Start*, it's [similar](#run) like *Acme.BookStore(Default)* -> *Run* options, there are 3 options available. The only difference with root of the tree and folder is gonna be execute in selected folder. +### Start/Stop -![folder-context-menu-start](images/solution-runner/folder-context-menu-start.png) +You can see the context menu by right-clicking *Folder*. It will start/stop all the applications under the folder. ### Build @@ -157,7 +162,7 @@ You can see the context menu by right-clicking *Folder* -> *Start*, it's [simila ### Add -*Folder* -> *Add* context menu, it's the [same](#add) options like *Acme.BookStore(Default)* -> *Add* there are 3 options avaiable. The only difference, it's gonna add item to selected folder. +*Folder* -> *Add* context menu, it's the [same](#add) options like *Acme.BookStore(Default)* -> *Add* there are 3 options avaiable. The only difference, it's gonna add item to the selected folder. ![folder-context-menu-add](images/solution-runner/folder-context-menu-add.png) @@ -168,29 +173,16 @@ You can see the context menu by right-clicking *Folder* -> *Start*, it's [simila ## C# Application -The .NET icon indicates that the application is a C# project. After we [add](#c-application) the C# applications to root of the tree or folder, we can go to any C# application and right-click to view the context menu; `Run`, `Build`, `Browse`, `Requests`, `Exceptions`, `Logs`, `Copy URL`, `Properties`, `Remove`. +The .NET icon indicates that the application is a C# project. After we [add](#c-application) the C# applications to the root of the tree or folder, we can go to any C# application and right-click to view the context menu; `Start`, `Build`, `Browse`, `Requests`, `Exceptions`, `Logs`, `Copy URL`, `Properties`, `Remove`. ![csharp-application-context-menu](images/solution-runner/csharp-application-context-menu.png) -### Run - -We have several options in C# applications. Those options are `Start`(If the application started this option shown as `Stop`), `Build & Start`(If the application started this option shown as `Build & Restart`), `Enable Watch`(If the watch is enabled this option shown as `Disable Watch`), `Restart`(It's only shown when the application started) - -![csharp-application-context-menu-run](images/solution-runner/csharp-application-context-menu-run.png) +### Start -- `Start`: Starts the selected application. This option doesn't build before run. If you're running it for the first time or if you've made changes, you should build the application. If the application is started this option changed as `Stop`. -- `Build & Start`: We can simply use if we wanna build first and start. If the application is started this option changes to `Build & Restart`. -- `Enable Watch`: When this option is enabled, there's no need to perform `Build & Start` after any change. ABP Studio watches for changes, re-builds, and re-runs your application automatically upon saving. If this option is enabled it changes to `Disable Watch`. -- `Restart`: Restarts the application. This option visible only if the application started. +Starts the selected application. Once it is started, *Stop* and *Restart* options will be available. > When you start the C# application, you should see a *chain* icon next to the application name, that means the started application connected to ABP Studio. C# applications can connect to ABP Studio even when running from outside the ABP Studio environment, for example debugging with Visual Studio. If the application is run from outside the ABP Studio environment, it will display *(external)* information next to the chain icon. -> When *Watch* is enable you should see an *eye* icon next to the application name. - -> Hint: Performing CTRL+Click on the start icon left to a stopped C# application equals to `Build & Start` command. Same applies for folders. - -![csharp-application-context-menu-run-connection](images/solution-runner/csharp-application-context-menu-run-connection.png) - ### Build It's the [similar](#build) options like root of the tree options. The only difference between them it's gonna be execute the selected application. @@ -214,7 +206,10 @@ We can open the *Application Properties* window to change *Launch url*, *Kuberne ![solutioın-runner-properties](images/solution-runner/solutioın-runner-properties.png) -You can click the `OK` button to save the changes. +- **Skip build before starting**: When enabled, application is started without build and it makes starting faster. This is useful when you are working on a single application out of multiple, so you don't need to build others everytime they start. +- **Watch changes while running**: When enabled, you should see an *eye* icon next to the application name. + +![csharp-application-context-menu-run-connection](images/solution-runner/csharp-application-context-menu-run-connection.png) ### Miscellaneous @@ -228,7 +223,7 @@ CLI applications uses the [powershell](https://learn.microsoft.com/en-us/powersh ![cli-application-context-menu](images/solution-runner/cli-application-context-menu.png) -- `Run`: This option includes 3 actions: *Start*, *Stop*, and *Restart* for the CLI application. +- `Start`: Starts the application. Once it is started, *Start* and *Restart* options will be available. - `Browse`: This option is available when a *Launch URL* is specified upon adding the CLI application. It opens the *Browse* tab, can be clicked while the application is running. - `Logs`: It opens the *Logs* tab, we can see the logs for *Start* and *Stop* commands. - `Copy URL`: This option copies the *Launch URL* of the selected application. It is visible if there is a specified *Launch URL* diff --git a/docs/en/studio/version-mapping.md b/docs/en/studio/version-mapping.md index 64ba3c1fb2..731e38a128 100644 --- a/docs/en/studio/version-mapping.md +++ b/docs/en/studio/version-mapping.md @@ -4,7 +4,10 @@ This document provides a general overview of the relationship between various ve | **ABP Studio Version** | **ABP Version of Startup Template** | |------------------------|---------------------------| -| 0.9.9 to 0.9.13 | 9.0.0 | +| 0.9.20 | 9.0.3 | +| 0.9.17 to 0.9.19 | 9.0.2 | +| 0.9.15 - 0.9.16 | 9.0.1 | +| 0.9.9 to 0.9.14 | 9.0.0 | | 0.9.8 | 8.3.4 | | 0.9.5 to 0.9.7 | 8.3.3 | | 0.9.2 to 0.9.4 | 8.3.2 | diff --git a/docs/en/suite/how-to-start.md b/docs/en/suite/how-to-start.md index 4552d23dbf..b8469ce26b 100644 --- a/docs/en/suite/how-to-start.md +++ b/docs/en/suite/how-to-start.md @@ -26,4 +26,20 @@ abp suite If you run the ABP Suite with ABP CLI, then it will open in your default browser. Do not close the command line window until you finish your work, otherwise the Suite will not function. When you finish your work, you can return to the command line and press `CTRL+C` to close the Suite. -Remember that, first access to the Suite requires to have an active internet connection, so make sure you are connected to the internet. \ No newline at end of file +Remember that, first access to the Suite requires to have an active internet connection, so make sure you are connected to the internet. + +## Starting ABP Suite in Different Port + +ABP Suite runs at port 3000 by default. If you want to run the ABP Suite in a different port, you can choose one of the following options: + +1. You can pass the `--AbpSuite:ApplicationUrl` commandline parameter to run in a different port: + +```bash +abp-suite --AbpSuite:ApplicationUrl="http://localhost:4000" +``` + +2. Update the `ApplicationUrl` in the _appsettings.json_ file of the dotnet tools directory (_%USERPROFILE%\.dotnet\tools\.store\volo.abp.suite\9.0.0\volo.abp.suite\9.0.0\tools\net9.0\any\appsettings.json_): + +![suite-registry](../images/suite-registry.png) + +> **Note:** Also, you can use this file to configure ABP Suite options. \ No newline at end of file diff --git a/docs/en/suite/index.md b/docs/en/suite/index.md index ce1c9b3454..1c99ed7198 100644 --- a/docs/en/suite/index.md +++ b/docs/en/suite/index.md @@ -16,4 +16,4 @@ ABP Suite is a complementary tool to the ABP Platform. ABP Suite allows you to b It's a .NET Core Global tool that can be installed from the command line. If you are using [ABP Studio](../studio/index.md), you don't even need to install it because it should already be installed, when you first installed the [ABP Studio](../studio/index.md). -By using the ABP Suite, you can create a new ABP solution, generate CRUD pages from the database to the front-end and directly get a kickstart for your application. ABP Suite is actively developed and new features are being added version by version according to roadmap and your feedbacks. \ No newline at end of file +By using the ABP Suite, you can generate CRUD pages from the database to the front-end and directly get a kickstart for your application. ABP Suite is actively developed and new features are being added version by version according to the roadmap and your feedback. diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-generated-tests-ef-core.png b/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-generated-tests-ef-core.png new file mode 100644 index 0000000000..f678598b4d Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-generated-tests-ef-core.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-generated-tests-mongo.png b/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-generated-tests-mongo.png new file mode 100644 index 0000000000..0a6ce96476 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-generated-tests-mongo.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-navigation-property.png b/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-navigation-property.png new file mode 100644 index 0000000000..03808be6ba Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-navigation-property.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-opening.png b/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-opening.png new file mode 100644 index 0000000000..adebe653b5 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-opening.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-solution-test-projects-ef-core.png b/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-solution-test-projects-ef-core.png new file mode 100644 index 0000000000..8a5689290d Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-solution-test-projects-ef-core.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-solution-test-projects-mongo.png b/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-solution-test-projects-mongo.png new file mode 100644 index 0000000000..a0ecbd469d Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-solution-test-projects-mongo.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-angular.png b/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-angular.png new file mode 100644 index 0000000000..687dca3593 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-angular.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-blazor.png b/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-blazor.png new file mode 100644 index 0000000000..5535659b45 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-blazor.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-mauiblazor.png b/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-mauiblazor.png new file mode 100644 index 0000000000..1bf3253fe7 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-mauiblazor.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-mvc.png b/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-mvc.png new file mode 100644 index 0000000000..54860a5e9f Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-mvc.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/book-store-suite-solution-explorer.png b/docs/en/tutorials/book-store-with-abp-suite/images/book-store-suite-solution-explorer.png new file mode 100644 index 0000000000..7b53aca64d Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/book-store-suite-solution-explorer.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/bookstore-test-succeed-ef-core.png b/docs/en/tutorials/book-store-with-abp-suite/images/bookstore-test-succeed-ef-core.png new file mode 100644 index 0000000000..9ca139a02a Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/bookstore-test-succeed-ef-core.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/bookstore-test-succeed-mongo.png b/docs/en/tutorials/book-store-with-abp-suite/images/bookstore-test-succeed-mongo.png new file mode 100644 index 0000000000..8459a737e5 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/bookstore-test-succeed-mongo.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/studio-browser-suite.png b/docs/en/tutorials/book-store-with-abp-suite/images/studio-browser-suite.png new file mode 100644 index 0000000000..47a37bbd9b Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/studio-browser-suite.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-author-entity-1.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-author-entity-1.png new file mode 100644 index 0000000000..1e9e9bf3c6 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-author-entity-1.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-author-entity-2.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-author-entity-2.png new file mode 100644 index 0000000000..bf47159ec6 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-author-entity-2.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-author-new-entity.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-author-new-entity.png new file mode 100644 index 0000000000..a680fc0bc3 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-author-new-entity.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-author-pages-1.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-author-pages-1.png new file mode 100644 index 0000000000..1a50cb0cd8 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-author-pages-1.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-1.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-1.png new file mode 100644 index 0000000000..dbb1890c14 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-1.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-2.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-2.png new file mode 100644 index 0000000000..a77794ba1b Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-2.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-3.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-3.png new file mode 100644 index 0000000000..e4bd459172 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-3.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-4.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-4.png new file mode 100644 index 0000000000..df96e6a4dd Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-4.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-6.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-6.png new file mode 100644 index 0000000000..b7814a7d7c Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-6.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-selection.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-selection.png new file mode 100644 index 0000000000..9da2a63705 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-selection.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-pages-browser.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-pages-browser.png new file mode 100644 index 0000000000..45f9ee1db9 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-pages-browser.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-with-author-create-modal.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-with-author-create-modal.png new file mode 100644 index 0000000000..7fa41795e1 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-with-author-create-modal.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-bookstore-advanced-filter-section.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-bookstore-advanced-filter-section.png new file mode 100644 index 0000000000..af9501d9eb Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-bookstore-advanced-filter-section.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-custom-code-result.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-custom-code-result.png new file mode 100644 index 0000000000..733c11535b Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-custom-code-result.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-enabling-custom-code.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-enabling-custom-code.png new file mode 100644 index 0000000000..c597bbcc41 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-enabling-custom-code.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-end-of-generation-modal.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-end-of-generation-modal.png new file mode 100644 index 0000000000..6940a9b7a6 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-end-of-generation-modal.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-repository-custom-code-ef-core.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-repository-custom-code-ef-core.png new file mode 100644 index 0000000000..8c8f696a11 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-repository-custom-code-ef-core.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-repository-custom-code-mongo.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-repository-custom-code-mongo.png new file mode 100644 index 0000000000..04c88b3084 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-repository-custom-code-mongo.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/test-data-seed-contributors.png b/docs/en/tutorials/book-store-with-abp-suite/images/test-data-seed-contributors.png new file mode 100644 index 0000000000..0c2ca8db13 Binary files /dev/null and b/docs/en/tutorials/book-store-with-abp-suite/images/test-data-seed-contributors.png differ diff --git a/docs/en/tutorials/book-store-with-abp-suite/index.md b/docs/en/tutorials/book-store-with-abp-suite/index.md new file mode 100644 index 0000000000..bc52fb75da --- /dev/null +++ b/docs/en/tutorials/book-store-with-abp-suite/index.md @@ -0,0 +1,42 @@ +# Web Application Development Tutorial (with ABP Suite) + +````json +//[doc-params] +{ + "UI": ["MVC","Blazor","BlazorServer", "BlazorWebApp","NG","MAUIBlazor"], + "DB": ["EF", "Mongo"] +} +```` + +````json +//[doc-nav] +{ + "Next": { + "Name": "Creating the Solution", + "Path": "tutorials/book-store-with-abp-suite/part-01" + } +} +```` + +> This tutorial is suitable for those who have an ABP Team or a higher [license](https://abp.io/pricing). + +## About This Tutorial + +> This tutorial explains how to build code using the [ABP Suite](../../suite/index.md) tool. You will develop an application similar to the one developed in [that tutorial](../book-store/index.md), but this time, the code will be automatically generated instead of manually written. + +In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: + +* **{{DB_Value}}** as the database provider. +* **{{UI_Value}}** as the UI Framework. + +This tutorial is organized as the following parts: + +- [Part 1: Creating the Solution](part-01.md) +- [Part 2: Creating the Books](part-02.md) +- [Part 3: Creating the Authors](part-03.md) +- [Part 4: Book to Author Relation](part-04.md) +- [Part 5: Customizing the Generated Code](part-05.md) + +### Download the Source Code + +After logging in to the ABP website, you can download the source code from [here](https://abp.io/api/download/samples/suite-bookstore-mvc-ef). \ No newline at end of file diff --git a/docs/en/tutorials/book-store-with-abp-suite/part-01.md b/docs/en/tutorials/book-store-with-abp-suite/part-01.md new file mode 100644 index 0000000000..637866e577 --- /dev/null +++ b/docs/en/tutorials/book-store-with-abp-suite/part-01.md @@ -0,0 +1,44 @@ +# Web Application Development Tutorial (with ABP Suite) - Part 1: Creating the Solution + +````json +//[doc-params] +{ + "UI": ["MVC","Blazor","BlazorServer", "BlazorWebApp","NG","MAUIBlazor"], + "DB": ["EF", "Mongo"] +} +```` + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Overview", + "Path": "tutorials/book-store-with-abp-suite/index" + }, + "Next": { + "Name": "Creating the Books", + "Path": "tutorials/book-store-with-abp-suite/part-02" + } +} +```` + +Before starting the development, create a new solution named `Acme.BookStore` and run it by following the [getting started tutorial](../../get-started/layered-web-application.md). + +You can use the following configurations: + +* **Solution Template:** Application (Layered) +* **Solution Name:** `Acme.BookStore` +* **UI Framework:** {{UI_Value}} +* **UI Theme:** LeptonX +* **Mobile Framework:** None +* **Database Provider:** {{DB_Value}} +* **Public Website:** No +* **Tiered:** No + +You can select the other options based on your preference. + +> **Please complete the [Get Started](../../get-started/layered-web-application.md) guide and run the web application before going further.** + +## Summary + +We've created the initial layered monolith solution. In the next part, we will learn how to create entities, and generate CRUD pages based on the specified options (including tests, UI, customizable code support etc.) with [ABP Suite](../../suite/index.md). diff --git a/docs/en/tutorials/book-store-with-abp-suite/part-02.md b/docs/en/tutorials/book-store-with-abp-suite/part-02.md new file mode 100644 index 0000000000..fe19272b4e --- /dev/null +++ b/docs/en/tutorials/book-store-with-abp-suite/part-02.md @@ -0,0 +1,139 @@ +# Web Application Development Tutorial (with ABP Suite) - Part 2: Creating the Books + +````json +//[doc-params] +{ + "UI": ["MVC","Blazor","BlazorServer", "BlazorWebApp","NG","MAUIBlazor"], + "DB": ["EF", "Mongo"] +} +```` + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Creating the Solution", + "Path": "tutorials/book-store-with-abp-suite/part-01" + }, + "Next": { + "Name": "Creating the Authors", + "Path": "tutorials/book-store-with-abp-suite/part-03" + } +} +```` + +In this part, you will create a new entity named `Book` and generate CRUD pages for the related entities with everything that you would normally implement manually (including application services, tests, CRUD pages, database relations and more...) via [ABP Suite](../../suite/index.md) with few clicks. + +## Opening the ABP Suite + +> Please **stop the application** in ABP Studio's *Solution Runner* panel, if it's currently running, because ABP Suite will make changes in the solution and it might need to build the solution in some steps and running the solution prevents to build it. + +After creating the solution in the previous part, now you can open the ABP Suite and start generating CRUD pages. You can select the *ABP Suite -> Open* command on the main menu to open ABP Suite: + +![opening the ABP Suite](./images/abp-suite-opening.png) + +After clicking the related command, pre-integrated browser of ABP Studio should open, then you can start generating entities and all related codes with a few configurations: + +![](./images/studio-browser-suite.png) + +## Creating the Book Entity + +Before creating the `Book` entity, first we can create a `BookType` enum in the `Acme.BookStore.Domain.Shared` project under the **Books** folder as follows: + +```csharp +namespace Acme.BookStore.Books; + +public enum BookType +{ + Undefined, + Adventure, + Biography, + Dystopia, + Fantastic, + Horror, + Science, + ScienceFiction, + Poetry +} +``` + +After creating an _enum_ file in your project, you can define it as a property while creating the entity. ABP Suite asks for an enum path to read the enum file, and set the namespace, and enum name in the generated code accordingly. Then, you can create the `Book` entity with some properties. + +Type `Book` for the *Name* field and leave the other options as is. ABP Suite automatically calculates proper values for the rest of the inputs for you: + +![](./images/suite-book-entity-1.png) + +ABP Suite sets: + +* Entity type as **master** (ABP Suite allows you to establish [master-child relationship](../../suite/creating-master-detail-relationship.md)), +* Base class as **FullAuditedAggregateRoot** ([see other possible values](../../framework/architecture/domain-driven-design/entities.md)), +* Primary key type as **Guid**, +* Plural name, database name, namespace, page title, menu item and more... + +You can change the menu-item value as **book** to show a proper icon in the generated UI, and also enable **code customization**, **creating unit & integration tests**, and other options as you wish: + +![](./images/suite-book-entity-2.png) + +After, specifying the entity metadata, open the *Properties* tab and create the properties shown in the following figure: + +![](./images/suite-book-entity-3.png) + +While defining the properties, you define a *Type* property with the type of *enum*. ABP Suite asks for an enum path and fill the all other inputs by reading the specified enum file. Therefore, you can specify the enum path for the `Type` property like in the following figure: + +![](./images/suite-book-entity-4.png) + +> After you select the enum file, ABP Suite automatically sets the namespace and enum name, and lists your enum values in the next section. You can change these values, but for now, you can leave as is. + +Here is the all details for the `Book` entity: + +* `Name` is **required**, it's a **string** property and maximum length is **128**. +* `Type` is an **enum** and the enum file path is *\Acme.BookStore.Domain.Shared\Books\BookType.cs*. +* `PublishDate` is a **DateTime** property and **not nullable**. +* `Price` is a **float** property and **required**. + +You can leave the other configurations as default. + +> ABP Suite allows you to define properties with a great range of options, for example, you can specify the property type as *string*, *int*, *float*, *Guid*, *DateTime*, and even *File* (for file upload) and also you can set any options while defining your properties, such as specifying it as *required*, or *nullable*, setting *max-min length*, *default value* and more... + +After that, you can click the **Save and Generate** button to start the code generation process. + +ABP Suite will generate the necessary code for you. It generates: + +* `Book` entity (also `BookBase` class, which is allow you customizing the generated entity), +* Repository implementation (`EfCoreBookRepository` class), +* `BookManager` domain service, +* Input & Output **DTOs** and **application service** implementations (`IBookAppService` & `BookAppService`), +* **Unit & integration tests**, +* A new **migration** (and also applies to the database), +* All related **permission**, **object mapping** and **navigation menu item** configurations, +* and all required **UI components and pages**... + +It will take some time to complete the process. After the process is completed, you will see a success message, you can click the *Ok* button, and then run the application by clicking the *Start* button (or alternatively, directly clicking the *run* icon) in the *Solution Runner* panel: + +{{ if UI == "MVC" }} + +![](./images/book-store-studio-run-app-mvc.png) + +{{ else if UI == "Angular" }} + +![](./images/book-store-studio-run-app-angular.png) + +{{ else if UI == "MAUIBlazor" }} + +![](./images/book-store-studio-run-app-mauiblazor.png) + +{{ else }} + +![](./images/book-store-studio-run-app-blazor.png) + +{{ end }} + +After the application is started, you can right-click and *Browse* on the application to open it in the ABP Studio's pre-integrated browser. You can see the Books page in the following figure with a single record: + +![](./images/suite-book-pages-browser.png) + +On this page, you can create a new book, update an existing book, delete a book, export all records (or the filtered records) to excel, filter the records by using the advanced filter section, bulk delete multiple records and so on. + +## Summary + +In this part, you've created a new entity named `Book` and generated the necessary code for it with [ABP Suite](../../suite/index.md) with a few clicks. ABP Suite generated the all code for you, including the **entity**, **application service**, **database relations**, **unit & integration tests**, **UI** and **defined the custom hooks for code customization**. \ No newline at end of file diff --git a/docs/en/tutorials/book-store-with-abp-suite/part-03.md b/docs/en/tutorials/book-store-with-abp-suite/part-03.md new file mode 100644 index 0000000000..4399910fb5 --- /dev/null +++ b/docs/en/tutorials/book-store-with-abp-suite/part-03.md @@ -0,0 +1,95 @@ +# Web Application Development Tutorial (with ABP Suite) - Part 3: Creating the Authors + +````json +//[doc-params] +{ + "UI": ["MVC","Blazor","BlazorServer", "BlazorWebApp","NG","MAUIBlazor"], + "DB": ["EF", "Mongo"] +} +```` + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Creating the Books", + "Path": "tutorials/book-store-with-abp-suite/part-02" + }, + "Next": { + "Name": "Book to Author Relation", + "Path": "tutorials/book-store-with-abp-suite/part-04" + } +} +```` + +In the previous part, you have created the `Book` entity and in this part, you will create a new entity named `Author` and generate all necesssary code via [ABP Suite](../../suite/index.md) with few clicks. After creating the `Author` entity, you will be establishing [one-to-many relationship](../../suite/generating-crud-page.md#step-by-step-creating-a-navigation-property-with-1-to-many-relationship) with *Book* and *Author* entities, in the next part. + +## Creating the Author Entity + +After generating the all necessary code for the `Book` entity, and testing the *Books* page, by building & starting the application, now you can continue with creating the `Author` entity. + +> Before, creating the `Author` entity, please **stop the application** in ABP Studio's *Solution Runner* panel, because ABP Suite will make changes in the solution and it might need to build the solution in some steps and running the solution prevents to build it. + +Click the entity selection box in the top right of the *CRUD page generation* page, and select the *-New entity-*: + +![](./images/suite-author-new-entity.png) + +Then, you can type `Author` for the *Name* field and leave the other options as is (you can change the menu icon as **pen** for a proper menu icon, and/or other options, if you wish). ABP Suite automatically calculates proper values for the rest of the inputs for you: + +![](./images/suite-author-entity-1.png) + +ABP Suite sets: + +* Entity type as **master** (ABP Suite allows you to establish [master-child relationship](../../suite/creating-master-detail-relationship.md)), +* Base class as **FullAuditedAggregateRoot** ([see other possible values](../../framework/architecture/domain-driven-design/entities.md)), +* Primary key type as **Guid**, +* Plural name, database name, namespace, page title, menu item and more... +* Also, it enables **code customization**, **UI code generation**, **unit & integration test generation** and **bulk delete** by default. + +After, specifying the entity metadata, open the *Properties* tab and create the properties shown in the following figure: + +![](./images/suite-author-entity-2.png) + +Here the details: + +* `Name` is **required**, it's a **string** property. Minimum length is **2** and maximum length is **128**. +* `BirthDate` is a **DateTime** property and **not nullable**. +* `ShortBio` is **required**, it's a **string** property, it's a **textarea**, maximum length is **256**. + +You can leave the other configurations as default. + +> **Note:** All properties are marked as **filterable** by default, and they appear in the advanced filter section because of that. You can set any properties you want as **not filterable** and then the related property will be removed from the advanced filter section and code will be generated accordingly. + +You can click the **Save and Generate** button to start the code generation process. + +ABP Suite will generate the necessary code for you. It will take some time to complete the process. After the process is completed, you will see a success message, you can click the *Ok* button, and then run the application by clicking the *Start* button (or alternatively, directly clicking the *run* icon) in the *Solution Runner* panel: + +{{ if UI == "MVC" }} + +![](./images/book-store-studio-run-app-mvc.png) + +{{ else if UI == "Angular" }} + +![](./images/book-store-studio-run-app-angular.png) + +{{ else if UI == "MAUIBlazor" }} + +![](./images/book-store-studio-run-app-mauiblazor.png) + +{{ else }} + +![](./images/book-store-studio-run-app-blazor.png) + +{{ end }} + +After the application is started, you can right-click and *Browse* on the application to open it in the ABP Studio's pre-integrated browser and try to add a new author: + +![](./images/suite-author-pages-1.png) + +As you can see, the *Name* field is **required**, the *Birth Date* field shows a datepicker, and the *Short Bio* field is also **required** and it's a **textarea**. You just configured how you want your properties with some options (for example, setting the short bio as **textarea** and **required**), and ABP Suite generated code according that. + +## Summary + +In this part, you've created a new entity named `Author` and generated the necessary code for it with [ABP Suite](../../suite/index.md) with a few clicks. ABP Suite generated the all code for you, including the **entity**, **application service**, **database relations**, **unit & integration tests**, **UI** and **defined the custom hooks for code customization**. + +In the next part, you will establish [one-to-many relation](../../suite/generating-crud-page.md) between the `Book` and `Author` entities. \ No newline at end of file diff --git a/docs/en/tutorials/book-store-with-abp-suite/part-04.md b/docs/en/tutorials/book-store-with-abp-suite/part-04.md new file mode 100644 index 0000000000..128e8c15c9 --- /dev/null +++ b/docs/en/tutorials/book-store-with-abp-suite/part-04.md @@ -0,0 +1,210 @@ +# Web Application Development Tutorial (with ABP Suite) - Part 4: Book to Author Relation + +````json +//[doc-params] +{ + "UI": ["MVC","Blazor","BlazorServer", "BlazorWebApp","NG","MAUIBlazor"], + "DB": ["EF", "Mongo"] +} +```` + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Creating the Author", + "Path": "tutorials/book-store-with-abp-suite/part-03" + }, + "Next": { + "Name": "Customizing the Generated Code", + "Path": "tutorials/book-store-with-abp-suite/part-05" + } +} +```` + +In the previous parts, you have created the `Book` and `Author` entities (& generated code for all functionalities) for the book store application. However, currently there is no relation between these entities. + +In this part, you will establish to **one-to-many relation** between the `Book` and `Author` entities. + +## Establishing Relations with ABP Suite + +ABP Suite allows establishing both **one-to-many** and [many-to-many](../../suite/creating-many-to-many-relationship.md) relationships. + +In this tutorial, you will only establish **one-to-many relation** between `Book` and `Author` entities. It's pretty straightforward to establish a relationship with ABP Suite. You should just need to navigate to the *Navigations* tab, and provide the metadata for navigation property (1-n) or navigation collection (n-n) relations. + +## Creating Book to Author Relationship + +> Please **stop the application** in ABP Studio's *Solution Runner* panel, because ABP Suite will make changes in the solution and it might need to build the solution in some steps and running the solution prevents to build it. + +To establish **one-to-many relations** between *Book* and *Author* entities, select the `Book` entity from the entity selection box on the top-right of the *CRUD page generation* page: + +![](./images/suite-book-entity-selection.png) + +Then, you can open the *Navigations* tab, and click the **Add navigation property (1-n)** button. After that, a navigation property model will open, and you can fill the fields like in the following figure: + +![](./images/abp-suite-navigation-property.png) + +Here is the details: + +* Selected the entity as `Author`. (ABP Suite will establish one-to-many relation between *Book* and *Author* entities with this configuration) +* Set the property name as *AuthorId*, it will be set as foreign-key restriction in the database and all related database configurations will be made by ABP Suite. +* Selected the display property as *Name*, this will be used in the dropdown component to set an author with a book & also it will be shown in the datatable of the *Books* page. +* Also, made the relation **required** and set it **filterable** so books can be filterable by authors. + +> **Note**: You should delete all existing books in the database (if any), before the code generation. Because, a new foreign-key will be added to the _books_ table and if there is any record in the table, then a new migration can't apply to the database and you may need to update the database manually. + +After, specifying the metadata, you can click the *Ok* button to close the modal. Then, click the **Save and generate** button to start code generation process. ABP Suite will establish one-to-many relationship between the entities, and will generate all necessary code automatically: + +![](./images/suite-end-of-generation-modal.png) + +It will take some time to complete the process. After the process is completed, you will see a success message, you can click the *Ok* button, and then run the application by clicking the *Start* button (or alternatively, directly clicking the *run* icon) in the *Solution Runner* panel: + +{{ if UI == "MVC" }} + +![](./images/book-store-studio-run-app-mvc.png) + +{{ else if UI == "Angular" }} + +![](./images/book-store-studio-run-app-angular.png) + +{{ else if UI == "MAUIBlazor" }} + +![](./images/book-store-studio-run-app-mauiblazor.png) + +{{ else }} + +![](./images/book-store-studio-run-app-blazor.png) + +{{ end }} + +After the application is started, you can right-click and *Browse* on the application to open it in the ABP Studio's pre-integrated browser. You can first create an author and then create a book with the author for testing: + +![](./images/suite-book-with-author-create-modal.png) + +Also, notice that, in the advanced filter section, there is an **Author** dropdown, which you can use to filter books by authors (remember you set **filterable** while defining navigation property and thanks to that, ABP Suite generated the code accordingly): + +![](./images/suite-bookstore-advanced-filter-section.png) + +## Unit & Integration Tests + +Since you completed the bookstore application, now we can check the generated tests, and run them to see if all of them pass or not. + +There are several test projects in the solution (slightly differs based on your _UI_ and _Database_ selection): + +{{ if DB == "EF" }} + +![](./images/abp-suite-solution-test-projects-ef-core.png) + +{{ else if DB == "Mongo" }} + +![](./images/abp-suite-solution-test-projects-mongo.png) + +{{ end }} + +ABP Suite generated unit & integration tests, for the `Book` & `Author` entities. If you open the **Test explorer** in your IDE, you will see the following tests are generated: + +{{ if DB == "EF" }} + +![](./images/abp-suite-generated-tests-ef-core.png) + +{{ else if DB == "Mongo" }} + +![](./images/abp-suite-generated-tests-mongo.png) + +{{ end }} + +ABP Suite generated tests for repository implementations & application service implementations for the generated code, if you enable *Create unit & integration tests* option, while creating the entity. Since, you already did that in the previous parts, it generated the all required tests for the entities. + +Let's examine one of the generated test classes. Open the *BooksAppServiceTests* (under the *test/Acme.BookStore.Application.Tests/Books/BookApplicationTests.cs*) and check the `CreateAsync` method: + +```csharp + [Fact] + public async Task CreateAsync() + { + // Arrange + var input = new BookCreateDto + { + Name = "6c3d1eda8bf04852b7bd5dfdbbd93224b252478c2e474d4c8faf24fa6b182168ca830d4f80e64e4a8e363f33e151d1d34a04be4709274c7fbf2214f9bb3a16c3", + Type = default, + PublishDate = new DateTime(2006, 8, 21), + Price = 754882891, + AuthorId = Guid.Parse("602460f6-df6e-456a-89d9-8c5870dfc583") + }; + + // Act + var serviceResult = await _booksAppService.CreateAsync(input); + + // Assert + var result = await _bookRepository.FindAsync(c => c.Id == serviceResult.Id); + + result.ShouldNotBe(null); + result.Name.ShouldBe("6c3d1eda8bf04852b7bd5dfdbbd93224b252478c2e474d4c8faf24fa6b182168ca830d4f80e64e4a8e363f33e151d1d34a04be4709274c7fbf2214f9bb3a16c3"); + result.Type.ShouldBe(default); + result.PublishDate.ShouldBe(new DateTime(2006, 8, 21)); + result.Price.ShouldBe(754882891); + } +``` + +ABP Suite; + +* Create the `BookCreateDto` input DTO object, and fill its values with dummy data to simulate creating a book, +* Then, it calls the `IBooksAppService.CreateAsync` method to create a book, +* And finally, asserts the returned result to see if it's as expected or not. + +Notice, also the *AuthorId* is set in the `BookCreateDto` object. At that point, you might ask yourself that I haven't created the author with that ID before, should not it throw exception? + +No, it will not throw an exception, because ABP Suite also generates simple dummy data for the entities just for the tests! You can see the test data seed contributors under the *Acme.BookStore.Domain.Tests* project: + +![](./images/test-data-seed-contributors.png) + +Here is the content of the `AuthorsDataSeedContributor.SeedAsync` method: + +```csharp + public async Task SeedAsync(DataSeedContext context) + { + if (IsSeeded) + { + return; + } + + await _authorRepository.InsertAsync(new Author + ( + id: Guid.Parse("602460f6-df6e-456a-89d9-8c5870dfc583"), + name: "d7bbb3bff0d54ad799477298c4572e9c05fd1175ab21416da17d0001e2b697cd7fef99fdb4414f26a05789667a97442bd65865510ba34c3599e874ccf08b45e4", + birthDate: new DateTime(2010, 2, 11), + shortBio: "3c2ff43c18e34d7b9ad3f1b9c444cbb000f90808d3774cb6b7702b957f472d74048597f93df744f6a6fdf507be428e016edec982f1174e09b124982cbc40156290ce6bc9fd7b49b4972741956cc847891cb55ad0942f4534b90aa0561d3e0c200340b613c7ad40c38b4b2f2c39298169a853473faed34341a130b31e1eb57e92" + )); + + await _authorRepository.InsertAsync(new Author + ( + id: Guid.Parse("6ea5a6b2-919e-4334-9728-13f4872e5e0e"), + name: "fd332fb58f184716962b08fbaa92f1c3e0963d843ba34c82bb5409517f60da3727c43b05e8d4490f996c5d19265962e53a69ed5e3e144509aad1441e37ce5081", + birthDate: new DateTime(2010, 6, 10), + shortBio: "b7808946c46c42e3935c4d8203d82973cfb98c5d81644f1da4ce1e643767849e23e0eb12a92f48be8f7eec0c07aefa043721fdd3fea542cfa644d2b7d428dc8842647180ef8a47139e097f6674c4f0d86c46765c406042a2a858865cb112ecd78d9ef6f5843e444994641f924a38a2d24ee4e212d41444888d3c0861af0cf9dd" + )); + + await _unitOfWorkManager!.Current!.SaveChangesAsync(); + + IsSeeded = true; + } +``` + +Since ABP Suite generated the test data seed contributors for each entity, you have initial data while testing your services. Also, as you would notice, the id in this example (*602460f6-df6e-456a-89d9-8c5870dfc583*) is same as the *authorId* field in the `BooksAppServiceTests.CreateAsync` method. + +Let's execute all tests, and see the results: + +{{ if DB == "EF" }} + +![](./images/bookstore-test-succeed-ef-core.png) + +{{ else if DB == "Mongo" }} + +![](./images/bookstore-test-succeed-mongo.png) + +{{ end }} + +## Summary + +So far, you have created the all functionality for the bookstore application without needing to write any single line of code. ABP Suite generated the entities, application services, UI components, unit & integration tests and more... + +In the next part, you will write some code and modify the ABP Suite's generated code by writing the code in the specified hookpoints. Thanks to [ABP Suite's Customized Code Support](../../suite/customizing-the-generated-code.md), in the next generation, our custom code will not be overridden and will be preserved. \ No newline at end of file diff --git a/docs/en/tutorials/book-store-with-abp-suite/part-05.md b/docs/en/tutorials/book-store-with-abp-suite/part-05.md new file mode 100644 index 0000000000..0283ce2425 --- /dev/null +++ b/docs/en/tutorials/book-store-with-abp-suite/part-05.md @@ -0,0 +1,175 @@ +# Web Application Development Tutorial (with ABP Suite) - Part 5: Customizing the Generated Code + +````json +//[doc-params] +{ + "UI": ["MVC","Blazor","BlazorServer", "BlazorWebApp","NG","MAUIBlazor"], + "DB": ["EF", "Mongo"] +} +```` + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Book to Author Relation", + "Path": "tutorials/book-store-with-abp-suite/part-04" + } +} +```` + +So far, you have created the all functionality for the bookstore application without needing to write any single line of code. In this part, let's write some code and check one of the great features of the ABP Suite, which is [Customizable Code Support](../../suite/customizing-the-generated-code.md). + +## Customizable Code Support + +ABP Suite allows you to customize the generated code blocks and preserve your custom code changes in the next CRUD Page Generation. It specifies hook points to allow adding custom code blocks. Then, the code written by you to these hook points will be respected and will not be overridden in the next CRUD Page Generation. + +To enable custom code support, you should check the *Customizable code* option in the **CRUD Page Generation** page (it's selected by default), and you enabled it for both entities: + +![](./images/suite-enabling-custom-code.png) + +## Custom Code Hookpoints + +When you enable the *custom code support*, ABP Suite adds some hookpoints that you can write your own custom code without worrying about, are my codes being overridden with the next CRUD page generation. + +On the C# side, ABP Suite adds abstract base classes for entities, application services, interfaces, domain services and so on... (and partial classes for interfaces) + +You can write your custom code in those classes (with the `*.Extended.cs` extension) and next time when you need to re-generate the entity, your custom code will not be overridden (only the base abstract classes will be re-generated and your changes on Suite will be respected): + +{{ if DB == "EF" }} + +![](./images/suite-repository-custom-code-ef-core.png) + +{{ else if DB == "Mongo" }} + +![](./images/suite-repository-custom-code-mongo.png) + +{{ end }} + +> For example, you can create a new repository method like in the example above, and in the next CRUD page generation, ABP Suite won't override your custom code. + +On the UI side, ABP Suite provides convenient comment placeholders within pages for MVC, Blazor, and Angular UIs. These comment sections serve as hook points where you can add your custom code. + +{{ if UI == "MVC"}} + +For example, if you open the *Books/Index.cshtml* file in your IDE, you will see those placeholders like following: + +```xml + + +@section styles +{ +@*//*@ +@*//*@ +} + + +``` + +You can write your custom codes between the _****_ placeholders and you can also extend these placeholders by customizing the [ABP Suite templates](../../suite/editing-templates.md). + +{{ else if UI == "Angular" }} + +Similar to services, there are two types of components: + +- `abstract.component.ts` +- `component.ts` + +The `.abstract.component.ts` file is recreated with each execution of schematics, while the code for `.component.ts` files is generated only once, so your custom changes are preserved. + +When the _Customizable code_ is enabled, ABP Suite introduces custom comment placeholders in the HTML file as follows: + +```html + + +``` + +{{ else }} + +For example, if you open the *Books.razor* file in your IDE, you will see those placeholders like following: + +```xml +@* ************************* PAGE HEADER ************************* *@ + + + + +@* ************************* SEARCH ************************* *@ + + +@*//*@ +@*//*@ + + + @*- Code omitted for brevity *@ +``` + +You can write your custom codes between the _****_ placeholders and you can also extend these placeholders by customizing the [ABP Suite templates](../../suite/editing-templates.md). + +{{ end }} + +> For more information, please refer to [Customizing the Generated Code documentation](../../suite/customizing-the-generated-code.md) + +## Implementing Custom Code + +Let's see the custom code support in action. We can demonstrate this feature with an easy example. + +Assume that we want to show the author's name with his abbreviated name. For example, for the author *John Ronald Reuel Tolkien*, we want to show the name *John Ronald Reuel Tolkien (a.k.a J.R.R.T)*. Achieving that is pretty straightforward. + +We just need to open the *src/Acme.BookStore.Application/Books/BooksAppService.Extended.cs* file and override the base `GetListAsync` method, which is called on the books page: + +```csharp +using System; +using System.Linq; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Caching; + +namespace Acme.BookStore.Books +{ + public class BooksAppService : BooksAppServiceBase, IBooksAppService + { + // + public BooksAppService(IBookRepository bookRepository, BookManager bookManager, IDistributedCache downloadTokenCache, IRepository authorRepository) + : base(bookRepository, bookManager, downloadTokenCache, authorRepository) + { + } + // + + //Write your custom code... + public override async Task> GetListAsync(GetBooksInput input) + { + var result = await base.GetListAsync(input); + + foreach (var book in result.Items) + { + var akaName = book.Author.Name.Split(" ").Select(q => q[0]).JoinAsString("."); + book.Author.Name += $" (a.k.a {akaName})"; + } + + return result; + } + } +} +``` + +* Here, we have overridden the `GetListAsync` method and changed its result according to our need. +* Notice, this class is derieved from the `BooksAppServiceBase` class and implements the `IBooksAppService`. +* Thus, ABP Suite only modifies the `BooksAppServiceBase` class and implements the necessary services in each generation, but does not generate the files with the `*.Extended` postfixes and let you implement your own custom code. +* You can create new methods, override an existing method and change its behaviour, add custom hookpoints to extend customization capabilities and more... + +Now, we can open ABP Suite and try to regenerate the book entity and see if our custom code is gone or not: + +![](./images/suite-end-of-generation-modal.png) + +After the regeneration has been completed, we can check the **BooksAppService.Extended.cs** file and we should see our custom code is there without any modification. ABP Suite didn't override our custom code, and finally, we can run the application to see the final result: + +![](./images/suite-custom-code-result.png) + +ABP Suite's custom code support is not limited to the backend side. You can also override the UI. You can refer to the [Customizing the Generated Code document](../../suite/customizing-the-generated-code.md) for further info. + +## Summary + +In this tutorial, you created the bookstore application without needing to write a single line of code with ABP Suite. Then, in this last part, you have written custom code in the specified hookpoints and it did not override by ABP Suite. \ No newline at end of file diff --git a/docs/en/tutorials/book-store/images/maui-blazor-add-books-component.png b/docs/en/tutorials/book-store/images/maui-blazor-add-books-component.png new file mode 100644 index 0000000000..72cf46e620 Binary files /dev/null and b/docs/en/tutorials/book-store/images/maui-blazor-add-books-component.png differ diff --git a/docs/en/tutorials/book-store/index.md b/docs/en/tutorials/book-store/index.md index c3161d2331..178aa75214 100644 --- a/docs/en/tutorials/book-store/index.md +++ b/docs/en/tutorials/book-store/index.md @@ -2,7 +2,7 @@ ````json //[doc-params] { - "UI": ["MVC","Blazor","BlazorServer","NG"], + "UI": ["MVC","Blazor","BlazorServer","NG", "MAUIBlazor"], "DB": ["EF","Mongo"] } ```` diff --git a/docs/en/tutorials/book-store/part-01.md b/docs/en/tutorials/book-store/part-01.md index a808c36034..51bfcd1b09 100644 --- a/docs/en/tutorials/book-store/part-01.md +++ b/docs/en/tutorials/book-store/part-01.md @@ -2,7 +2,7 @@ ````json //[doc-params] { - "UI": ["MVC","Blazor","BlazorServer", "BlazorWebApp","NG"], + "UI": ["MVC","Blazor","BlazorServer", "BlazorWebApp","NG","MAUIBlazor"], "DB": ["EF","Mongo"] } ```` @@ -34,7 +34,7 @@ For such cases, run the `abp install-libs` command on the root directory of your abp install-libs ``` -> We suggest you install [Yarn](https://classic.yarnpkg.com/) to prevent possible package inconsistencies, if you haven't installed it yet. +> We suggest you install [Yarn v1.22+ (not v2)](https://classic.yarnpkg.com/en/docs/install) to prevent possible package inconsistencies, if you haven't installed it yet. {{if UI=="Blazor" || UI=="BlazorWebApp"}} diff --git a/docs/en/tutorials/book-store/part-02.md b/docs/en/tutorials/book-store/part-02.md index bb812e7d81..43fcb55d47 100644 --- a/docs/en/tutorials/book-store/part-02.md +++ b/docs/en/tutorials/book-store/part-02.md @@ -2,7 +2,7 @@ ````json //[doc-params] { - "UI": ["MVC","Blazor","BlazorServer", "BlazorWebApp", "NG"], + "UI": ["MVC","Blazor","BlazorServer", "BlazorWebApp", "NG", "MAUIBlazor"], "DB": ["EF","Mongo"] } ```` @@ -518,14 +518,17 @@ Now you can see the final result on your browser: ## Create a Books Page -It's time to create something visible and usable! Right click on the `Pages` folder under the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor`{{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project and add a new **razor component**, named `Books.razor`: +It's time to create something visible and usable! Right click on the `Pages` folder under the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor`{{ else if UI == "Blazor" || UI == "BlazorWebApp" }}`Acme.BookStore.Blazor.Client`{{else}} `Acme.BookStore.MauiBlazor` {{ end }} project and add a new **razor component**, named `Books.razor`: {{ if UI == "Blazor" || UI == "BlazorWebApp" }} ![blazor-add-books-component](images/blazor-add-books-component-client.png) -{{ else }} +{{ else if UI == "BlazorServer" }} ![blazor-add-books-component](images/blazor-add-books-component.png) +{{ else if UI == "MAUIBlazor" }} +![maui-blazor-add-books-component](images/maui-blazor-add-books-component.png) {{ end }} + Replace the contents of this component as shown below: ````html @@ -540,7 +543,7 @@ Replace the contents of this component as shown below: ### Add the Books Page to the Main Menu -Open the `BookStoreMenuContributor` class in the {{ if UI == "BlazorServer"}}`Acme.BookStore.Blazor`{{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project add the following code to the end of the `ConfigureMainMenuAsync` method: +Open the `BookStoreMenuContributor` class in the {{ if UI == "BlazorServer"}}`Acme.BookStore.Blazor`{{ else if UI == "MAUIBlazor" }}`Acme.BookStore.MauiBlazor`{{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project add the following code to the end of the `ConfigureMainMenuAsync` method: ````csharp context.Menu.AddItem( @@ -577,8 +580,6 @@ Open the `Books.razor` and replace the content as the following: @using Volo.Abp.Application.Dtos @using Acme.BookStore.Books @using Acme.BookStore.Localization -@using Microsoft.Extensions.Localization -@inject IStringLocalizer L @inherits AbpCrudPageBase @@ -625,13 +626,21 @@ Open the `Books.razor` and replace the content as the following: + +@code +{ + public Books() // Constructor + { + LocalizationResource = typeof(BookStoreResource); + } +} ```` > If you see some syntax errors, you can ignore them if your application is properly built and running. Visual Studio still has some bugs with Blazor. * Inherited from `AbpCrudPageBase` which implements all the CRUD details for us. * `Entities`, `TotalCount`, `PageSize`, `OnDataGridReadAsync` are defined in the base class. -* Injected `IStringLocalizer` (as `L` object) and used for localization. +* `LocalizationResource` is set to the `BookStoreResource` to localize the texts. While the code above is pretty easy to understand, you can check the Blazorise [Card](https://blazorise.com/docs/components/card/) and [DataGrid](https://blazorise.com/docs/extensions/datagrid/) documents to understand them better. diff --git a/docs/en/tutorials/book-store/part-03.md b/docs/en/tutorials/book-store/part-03.md index 65f6d482dd..45444ee76f 100644 --- a/docs/en/tutorials/book-store/part-03.md +++ b/docs/en/tutorials/book-store/part-03.md @@ -2,7 +2,7 @@ ````json //[doc-params] { - "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG"], + "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG", "MAUIBlazor"], "DB": ["EF","Mongo"] } ```` @@ -1101,7 +1101,7 @@ Clicking the "Delete" action calls the `delete` method which then shows a confir {{end}} -{{if UI == "Blazor" || UI == "BlazorServer" || UI == "BlazorWebApp"}} +{{if UI == "Blazor" || UI == "BlazorServer" || UI == "BlazorWebApp" || UI == "MAUIBlazor"}} ## Creating a New Book @@ -1292,13 +1292,13 @@ We can now define a modal to edit the book. Add the following code to the end of The base `AbpCrudPageBase` uses the [object to object mapping](../../framework/infrastructure/object-to-object-mapping.md) system to convert an incoming `BookDto` object to a `CreateUpdateBookDto` object. So, we need to define the mapping. -Open the `BookStoreBlazorAutoMapperProfile` inside the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor`{{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project and change the content as the following: +Open the `BookStoreBlazorAutoMapperProfile` inside the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor` {{ else if UI == "MAUIBlazor" }}`Acme.BookStore.MauiBlazor` {{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project and change the content as the following: ````csharp using Acme.BookStore.Books; using AutoMapper; -{{ if UI == "BlazorServer" }}namespace Acme.BookStore.Blazor;{{ else }}namespace Acme.BookStore.Blazor.Client;{{ end }} +{{ if UI == "BlazorServer" }}namespace Acme.BookStore.Blazor; {{ else if UI == "MAUIBlazor" }}namespace Acme.BookStore.MauiBlazor; {{ else }}namespace Acme.BookStore.Blazor.Client;{{ end }} public class BookStoreBlazorAutoMapperProfile : Profile { @@ -1351,7 +1351,6 @@ Here's the complete code to create the book management CRUD page, that has been @using Acme.BookStore.Localization @using Microsoft.Extensions.Localization @using Volo.Abp.AspNetCore.Components.Web -@inject IStringLocalizer L @inject AbpBlazorMessageLocalizerHelper LH @inherits AbpCrudPageBase @@ -1396,7 +1395,7 @@ Here's the complete code to create the book management CRUD page, that has been Field="@nameof(BookDto.Type)" Caption="@L["Type"]"> - @L[$"Enum:BookType.{context.Type}"] + @L[$"Enum:BookType.{context.Type:D}"] + +@code +{ + public Books() // Constructor + { + LocalizationResource = typeof(BookStoreResource); + } +} ```` {{end}} diff --git a/docs/en/tutorials/book-store/part-04.md b/docs/en/tutorials/book-store/part-04.md index 4ab950b0e1..fba15db253 100644 --- a/docs/en/tutorials/book-store/part-04.md +++ b/docs/en/tutorials/book-store/part-04.md @@ -2,7 +2,7 @@ ````json //[doc-params] { - "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG"], + "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG", "MAUIBlazor"], "DB": ["EF","Mongo"] } ```` diff --git a/docs/en/tutorials/book-store/part-05.md b/docs/en/tutorials/book-store/part-05.md index 95ee1f53a6..0acc5cac9f 100644 --- a/docs/en/tutorials/book-store/part-05.md +++ b/docs/en/tutorials/book-store/part-05.md @@ -2,7 +2,7 @@ ````json //[doc-params] { - "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG"], + "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG", "MAUIBlazor"], "DB": ["EF","Mongo"] } ```` @@ -389,11 +389,11 @@ Open the `/src/app/book/book.component.html` file and replace the edit and delet * Added `*abpPermission="'BookStore.Books.Edit'"` that hides the edit action if the current user has no editing permission. * Added `*abpPermission="'BookStore.Books.Delete'"` that hides the delete action if the current user has no delete permission. -{{else if UI == "Blazor" || UI == "BlazorServer" || UI == "BlazorWebApp"}} +{{else if UI == "Blazor" || UI == "BlazorServer" || UI == "BlazorWebApp" || UI == "MAUIBlazor"}} ### Authorize the Razor Component -Open the `/Pages/Books.razor` file in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor`{{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project and add an `Authorize` attribute just after the `@page` directive and the following namespace imports (`@using` lines), as shown below: +Open the `/Pages/Books.razor` file in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor` {{ else if UI == "MAUIBlazor" }}`Acme.BookStore.MauiBlazor` {{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project and add an `Authorize` attribute just after the `@page` directive and the following namespace imports (`@using` lines), as shown below: ````html @page "/books" @@ -420,6 +420,8 @@ Add the following code block to the end of the `Books.razor` file: { public Books() // Constructor { + LocalizationResource = typeof(BookStoreResource); + CreatePolicyName = BookStorePermissions.Books.Create; UpdatePolicyName = BookStorePermissions.Books.Edit; DeletePolicyName = BookStorePermissions.Books.Delete; @@ -479,7 +481,7 @@ You can run and test the permissions. Remove a book related permission from the Even we have secured all the layers of the book management page, it is still visible on the main menu of the application. We should hide the menu item if the current user has no permission. -Open the `BookStoreMenuContributor` class in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor`{{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project, find the code block below: +Open the `BookStoreMenuContributor` class in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor`{{ else if UI == "MAUIBlazor" }}`Acme.BookStore.MauiBlazor`{{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project, find the code block below: ````csharp context.Menu.AddItem( @@ -509,14 +511,11 @@ var bookStoreMenu = new ApplicationMenuItem( context.Menu.AddItem(bookStoreMenu); //CHECK the PERMISSION -if (await context.IsGrantedAsync(BookStorePermissions.Books.Default)) -{ - bookStoreMenu.AddItem(new ApplicationMenuItem( - "BooksStore.Books", - l["Menu:Books"], - url: "/books" - )); -} +bookStoreMenu.AddItem(new ApplicationMenuItem( + "BooksStore.Books", + l["Menu:Books"], + url: "/books" +).RequirePermissions(BookStorePermissions.Books.Default)); ```` You also need to add `async` keyword to the `ConfigureMenuAsync` method and re-arrange the return value. The final `ConfigureMainMenuAsync` method should be the following: @@ -545,14 +544,11 @@ private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) context.Menu.AddItem(bookStoreMenu); //CHECK the PERMISSION - if (await context.IsGrantedAsync(BookStorePermissions.Books.Default)) - { - bookStoreMenu.AddItem(new ApplicationMenuItem( - "BooksStore.Books", - l["Menu:Books"], - url: "/books" - )); - } + bookStoreMenu.AddItem(new ApplicationMenuItem( + "BooksStore.Books", + l["Menu:Books"], + url: "/books" + ).RequirePermissions(BookStorePermissions.Books.Default)); } ```` diff --git a/docs/en/tutorials/book-store/part-06.md b/docs/en/tutorials/book-store/part-06.md index 58a83cfc16..d24b68f569 100644 --- a/docs/en/tutorials/book-store/part-06.md +++ b/docs/en/tutorials/book-store/part-06.md @@ -2,7 +2,7 @@ ````json //[doc-params] { - "UI": ["MVC","Blazor","BlazorServer", "BlazorWebApp", "NG"], + "UI": ["MVC","Blazor","BlazorServer", "BlazorWebApp", "NG", "MAUIBlazor"], "DB": ["EF","Mongo"] } ```` @@ -41,7 +41,6 @@ Create an `Authors` folder (namespace) in the `Acme.BookStore.Domain` project an ````csharp using System; -using JetBrains.Annotations; using Volo.Abp; using Volo.Abp.Domain.Entities.Auditing; @@ -115,7 +114,6 @@ Created this class inside the `Acme.BookStore.Domain.Shared` project since we wi ````csharp using System; using System.Threading.Tasks; -using JetBrains.Annotations; using Volo.Abp; using Volo.Abp.Domain.Services; diff --git a/docs/en/tutorials/book-store/part-07.md b/docs/en/tutorials/book-store/part-07.md index b8aa17ace8..0708ecfcb9 100644 --- a/docs/en/tutorials/book-store/part-07.md +++ b/docs/en/tutorials/book-store/part-07.md @@ -2,7 +2,7 @@ ````json //[doc-params] { - "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG"], + "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG", "MAUIBlazor"], "DB": ["EF","Mongo"] } ```` diff --git a/docs/en/tutorials/book-store/part-08.md b/docs/en/tutorials/book-store/part-08.md index 679b6d84ca..a7d73f0209 100644 --- a/docs/en/tutorials/book-store/part-08.md +++ b/docs/en/tutorials/book-store/part-08.md @@ -2,7 +2,7 @@ ````json //[doc-params] { - "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG"], + "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG", "MAUIBlazor"], "DB": ["EF","Mongo"] } ```` diff --git a/docs/en/tutorials/book-store/part-09.md b/docs/en/tutorials/book-store/part-09.md index 3c1b0cb7d5..5bf9f90af1 100644 --- a/docs/en/tutorials/book-store/part-09.md +++ b/docs/en/tutorials/book-store/part-09.md @@ -2,7 +2,7 @@ ````json //[doc-params] { - "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG"], + "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG", "MAUIBlazor"], "DB": ["EF","Mongo"] } ```` @@ -848,13 +848,13 @@ That's all! This is a fully working CRUD page, you can create, edit and delete a {{end}} -{{if UI == "Blazor" || UI == "BlazorServer" || UI == "BlazorWebApp"}} +{{if UI == "Blazor" || UI == "BlazorServer" || UI == "BlazorWebApp" || UI == "MAUIBlazor"}} ## The Author Management Page ### Authors Razor Component -Create a new Razor Component Page, `/Pages/Authors.razor`, in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor`{{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project with the following content: +Create a new Razor Component Page, `/Pages/Authors.razor`, in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor`{{ else if UI == "MAUIBlazor" }}`Acme.BookStore.MauiBlazor`{{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project with the following content: ````xml @page "/authors" @@ -1055,7 +1055,7 @@ using Blazorise.DataGrid; using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Dtos; -{{ if UI == "BlazorServer" }}namespace Acme.BookStore.Blazor.Pages;{{ else }}namespace Acme.BookStore.Blazor.Client.Pages;{{ end }} +{{ if UI == "BlazorServer" }}namespace Acme.BookStore.Blazor.Pages;{{ else if UI == "MAUIBlazor" }}namespace Acme.BookStore.MauiBlazor.Pages;{{ else }}namespace Acme.BookStore.Blazor.Client.Pages;{{ end }} public partial class Authors { @@ -1201,7 +1201,7 @@ This class typically defines the properties and methods used by the `Authors.raz `Authors` class uses the `IObjectMapper` in the `OpenEditAuthorModal` method. So, we need to define this mapping. -Open the `BookStoreBlazorAutoMapperProfile.cs` in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor`{{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project and add the following mapping code in the constructor: +Open the `BookStoreBlazorAutoMapperProfile.cs` in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor`{{ else if UI == "MAUIBlazor" }}`Acme.BookStore.MauiBlazor`{{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project and add the following mapping code in the constructor: ````csharp CreateMap(); @@ -1211,17 +1211,14 @@ You will need to declare a `using Acme.BookStore.Authors;` statement to the begi ### Add to the Main Menu -Open the `BookStoreMenuContributor.cs` in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor`{{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project and add the following code to the end of the `ConfigureMainMenuAsync` method: +Open the `BookStoreMenuContributor.cs` in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor`{{ else if UI == "MAUIBlazor" }}`Acme.BookStore.MauiBlazor`{{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project and add the following code to the end of the `ConfigureMainMenuAsync` method: ````csharp -if (await context.IsGrantedAsync(BookStorePermissions.Authors.Default)) -{ - context.Menu.AddItem(new ApplicationMenuItem( +context.Menu.AddItem(new ApplicationMenuItem( "BooksStore.Authors", l["Menu:Authors"], url: "/authors" - )); -} + ).RequirePermissions(BookStorePermissions.Books.Default)); ```` ### Localizations diff --git a/docs/en/tutorials/book-store/part-10.md b/docs/en/tutorials/book-store/part-10.md index 928881ffec..d4137e3681 100644 --- a/docs/en/tutorials/book-store/part-10.md +++ b/docs/en/tutorials/book-store/part-10.md @@ -2,7 +2,7 @@ ````json //[doc-params] { - "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG"], + "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG", "MAUIBlazor"], "DB": ["EF","Mongo"] } ```` @@ -105,7 +105,7 @@ migrationBuilder.AddForeignKey( * Creates an index on the `AuthorId` field. * Declares the foreign key to the `AppAuthors` table. -> If you are using Visual Studio, you may want to use `Add-Migration Added_AuthorId_To_Book -c BookStoreDbContext` and `Update-Database -Context BookStoreDbContext` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`Acme.BookStore.Web`{{else if UI=="BlazorServer"}}`Acme.BookStore.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`Acme.BookStore.HttpApi.Host`{{end}} is the startup project and `Acme.BookStore.EntityFrameworkCore` is the *Default Project* in PMC. +> If you are using Visual Studio, you may want to use `Add-Migration Added_AuthorId_To_Book -c BookStoreDbContext` and `Update-Database -Context BookStoreDbContext` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`Acme.BookStore.Web`{{else if UI=="BlazorServer" || UI=="BlazorWebApp"}}`Acme.BookStore.Blazor`{{else if UI=="Blazor" || UI=="NG" || UI=="MAUIBlazor"}}`Acme.BookStore.HttpApi.Host`{{end}} is the startup project and `Acme.BookStore.EntityFrameworkCore` is the *Default Project* in PMC. {{end}} @@ -1071,11 +1071,11 @@ That's all. Just run the application and try to create or edit an author. {{end}} -{{if UI == "Blazor" || UI == "BlazorServer" || UI == "BlazorWebApp" }} +{{if UI == "Blazor" || UI == "BlazorServer" || UI == "BlazorWebApp" || UI == "MAUIBlazor" }} ### The Book List -It is very easy to show the *Author Name* in the book list. Open the `/Pages/Books.razor` file in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor`{{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project and add the following `DataGridColumn` definition just after the `Name` (book name) column: +It is very easy to show the *Author Name* in the book list. Open the `/Pages/Books.razor` file in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor` {{ else if UI == "MAUIBlazor" }}`Acme.BookStore.MauiBlazor` {{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project and add the following `DataGridColumn` definition just after the `Name` (book name) column: ````xml This tutorial is suitable for those who have an ABP Business or a higher [license](https://abp.io/pricing). + +ABP is designed to be a powerful platform to build microservice solutions. It provides a [microservice solution template](../../solution-templates/microservice/index.md) to easily start a sophisticated microservice solution. All of the [pre-built application modules](../../modules/index.md) are microservice compatible and the core framework fully [supports and simplifies](../../framework/architecture/microservices/index.md) distributed application development. + +In this tutorial, you will learn how to start a new microservice solution, create services and communicate between them. You will also learn to use these services from a web application through an API gateway and automatically generate CRUD pages using the [ABP Suite](../../suite/index.md) tool. + +## Tutorial Outline + +This tutorial is organized as the following parts: + +* [Part 01: Creating the initial solution](part-01.md) +* [Part 02: Creating the initial Catalog microservice](part-02.md) +* [Part 03: Building the Catalog microservice](part-03.md) +* [Part 04: Creating the initial Ordering service](part-04.md) +* [Part 05: Building the Ordering service](part-05.md) +* [Part 06: Integrating the services: HTTP API Calls](part-06.md) +* [Part 07: Integrating the services: Using Distributed Events](part-07.md) + +## Download the Source Code + +After logging in to the ABP website, you can download the source code from [here](https://abp.io/api/download/samples/cloud-crm-mvc-ef). + +## See Also + +* [Microservice solution template](../../solution-templates/microservice/index.md) \ No newline at end of file diff --git a/docs/en/tutorials/microservice/part-01.md b/docs/en/tutorials/microservice/part-01.md new file mode 100644 index 0000000000..d843e744ca --- /dev/null +++ b/docs/en/tutorials/microservice/part-01.md @@ -0,0 +1,40 @@ +# Microservice Tutorial Part 01: Creating the Initial Solution + +````json +//[doc-nav] +{ + "Next": { + "Name": "Creating the initial Catalog service", + "Path": "tutorials/microservice/part-02" + } +} +```` + +Follow the *[Get Started](../../get-started/microservice.md)* guide to create a new layered web application with the following configurations: + +* **Solution name**: `CloudCrm` +* **Database Provider**: Entity Framework Core +* **Database Management System**: SQL Server +* **UI Framework**: MVC / Razor Pages +* **Mobile framework**: None +* **Public website**: Selected + +You can select the other options based on your preference. + +> **Please complete the *[Get Started](../../get-started/layered-web-application.md)* guide and run the web application before going further.** You can skip the sections after the *Running the Solution* section, if you don't prefer to complete all. + +The initial solution structure should be like the following in ABP Studio's *[Solution Explorer](../../studio/solution-explorer.md)*: + +![abp-studio-solution-explorer-initial-cloud-crm-microservice-solution](images/abp-studio-solution-explorer-initial-cloud-crm-microservice-solution.png) + +> ABP Studio will perform a few additional steps after creating your solution. **Please wait until all the background tasks are completed** before going further. + +Initially you see three folders (`apps`, `gateways` and `services`) and ~10 ABP Studio modules (depends on your preferences while creating the solution) under the `CloudCrm` ABP Studio solution. Some of these modules represent microservices, some of them represent web applications and some others represent API gateways in our system. + +> An **ABP Studio module** is typically a .NET solution and an **ABP Studio solution** is an umbrella concept for multiple .NET Solutions (see the *[Concepts](../../studio/concepts.md)* document for more). + +You can see the *[Microservice Solution Template](../../solution-templates/microservice/index.md)* document later if you want to understand the initial solution structure with all its details. However, it is not needed to follow this tutorial. + +## Summary + +In this part, you've created the initial microservice solution, which already contains a few infrastructure services. We will create our first business service in the [next part](part-02.md). \ No newline at end of file diff --git a/docs/en/tutorials/microservice/part-02.md b/docs/en/tutorials/microservice/part-02.md new file mode 100644 index 0000000000..014daf9b11 --- /dev/null +++ b/docs/en/tutorials/microservice/part-02.md @@ -0,0 +1,123 @@ +# Microservice Tutorial Part 02: Creating the initial Catalog service + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Creating the initial solution", + "Path": "tutorials/microservice/part-01" + }, + "Next": { + "Name": "Building the Catalog service", + "Path": "tutorials/microservice/part-03" + } +} +```` + +In this tutorial, you will create a new Catalog service and integrate it to the solution. + +## Creating the Catalog Service + +Right-click the `services` folder in the *Solution Explorer* panel, select the *Add* -> *New Module* -> *Microservice* command: + +![abp-studio-add-new-microservice-command](images/abp-studio-add-new-microservice-command.png) + +This command opens a new dialog to define the properties of the new microservice. You can use the following values to create a new microservice named `CatalogService`: + +![abp-studio-add-new-microservice-dialog](images/abp-studio-add-new-microservice-dialog.png) + +When you click the *Next* button, you are redirected to the database provider selection step. + +### Selecting the Database Type + +Here, you can select the database provider to be used by the new microservice: + +![abp-studio-add-new-microservice-dialog-database-step](images/abp-studio-add-new-microservice-dialog-database-step.png) + +Select *Entity Framework Core* option and proceed the *Next* step. + +### Integrating to the Solution + +In this step, we can select the options for integrating the new microservice to the rest of the solution components: + +![abp-studio-add-new-microservice-dialog-integration-step](images/abp-studio-add-new-microservice-dialog-integration-step.png) + +ABP Studio intelligently selects the right values for you, but you should still check them carefully since they directly affect what we will do in the next parts of this tutorial. + +**Ensure the options are configured the same as in the preceding figure**, and click the *Next* button. + +### Additional Options + +![abp-studio-add-new-microservice-dialog-additional-options-step](images/abp-studio-add-new-microservice-dialog-additional-options-step.png) + +In this step, you can select additional options for the new microservice. You can leave them as default and click the *Create* button. + +That's all, ABP Studio creates the new microservice and arranges all the integration and configuration for you. + +## Exploring the New Catalog Microservice + +In this section, we will investigate the new microservice in overall. + +### Understanding the Packages of The Service + +The new microservice is added under the `services` folder in the `CloudCrm` ABP Studio solution: + +![abp-studio-new-catalog-service-in-solution-explorer](images/abp-studio-new-catalog-service-in-solution-explorer.png) + +The new microservice has its own separate .NET solution that includes three packages (.NET projects): + +* `CloudCrm.CatalogService` is the main project that you will implement your service. It typically contains your [entities](../../framework/architecture/domain-driven-design/entities.md), [repositories](../../framework/architecture/domain-driven-design/repositories.md), [application services](../../framework/architecture/domain-driven-design/application-services.md), API controllers, etc. +* `CloudCrm.CatalogService.Contracts` project can be shared with the other services and applications. It typically contains interfaces of your [application services](../../framework/architecture/domain-driven-design/application-services.md), [data transfer objects](../../framework/architecture/domain-driven-design/data-transfer-objects.md), and some other types you may want to share with the clients of this microservice. +* `CloudCrm.CatalogService.Tests` is for building your unit and integration tests for this microservice. + +### Opening the Service in an IDE + +You can open the new microservice in your favorite IDE for development. As a shortcut, you can right-click it in ABP Studio, select the *Open with* -> *Visual Studio* command for example: + +![abp-studio-open-with-visual-studio](images/abp-studio-open-with-visual-studio.png) + +Here is the `CloudCrm.CatalogService` .NET solution in Visual Studio: + +![visual-studio-solution-explorer-catalog-service](images/visual-studio-solution-explorer-catalog-service.png) + +### Running the New Service + +You can run the solution using ABP Studio's *Solution Runner*. It will also run the new Catalog service as a part of the solution. + +> Before running the solution, **ensure that all the applications are built**. If you are not sure, right-click the root item (`CloudCrm`) in the *Solution Explorer* panel and select the *Build* -> *Graph Build* command. + +Click the *Play* button near to the solution root: + +![abp-studio-solution-runner-play-all](images/abp-studio-solution-runner-play-all.png) + +### Browsing the Catalog Service + +Once all of the applications have started, right-click the Catalog service and select the *Browse* command: + +![abp-studio-browse-catalog-service](images/abp-studio-browse-catalog-service.png) + +It will open the built-in browser and you will see the Swagger UI for the Catalog service: + +![abp-studio-browser-catalog-service-swagger-ui](images/abp-studio-browser-catalog-service-swagger-ui.png) + +You can test the APIs on the Swagger UI to see if the new microservice is properly working. + +### Opening the Catalog Database + +The new Catalog microservice has its own database. That database is created automatically by the microservice application, when you run the microservice. Also, [Entity Framework's database migrations](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/) are automatically applied by the microservice when it runs. So, you don't care about the database schema changes every time you deploy the microservice. + +Assuming you've selected SQL Server as your DBMS, you can open the SQL Server Management Studio to see its databases: + +![sql-server-management-studio-login-screen](images/sql-server-management-studio-login-screen.png) + +Use `localhost,1434` as the *Server name*, select the *SQL Server Authentication* as the *Authentication* type, use `sa` as the *Login* name and `myPassw@rd` as the *Password* value. You can find these values in the `appsettings.json` file in the `CloudCrm.CatalogService` project of the .NET solution of the Catalog microservice. + +Once you click the *Connect* button, you can see all the databases and explore their data: + +![sql-server-management-studio-databases](images/sql-server-management-studio-databases.png) + +The Catalog service's database has only three initial table. The first one is for Entity Framework Core's migration system, and the others are for ABP's [distributed event bus](../../solution-templates/microservice/distributed-events.md) to properly apply transactional events using the outbox and inbox patterns. You don't need to care about these tables since they are created and managed by Entity Framework Core and ABP. + +## Summary + +In this part of the Microservice Development Tutorial, we added a new Catalog microservice to the solution, explored its code structure and database, and browse its APIs using the Swagger UI. In the next part, we will create functionality in that new microservice. \ No newline at end of file diff --git a/docs/en/tutorials/microservice/part-03.md b/docs/en/tutorials/microservice/part-03.md new file mode 100644 index 0000000000..adae9877dd --- /dev/null +++ b/docs/en/tutorials/microservice/part-03.md @@ -0,0 +1,128 @@ +# Microservice Tutorial Part 03: Building the Catalog service + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Creating the initial Catalog service", + "Path": "tutorials/microservice/part-02" + }, + "Next": { + "Name": "Creating the initial Ordering service", + "Path": "tutorials/microservice/part-04" + } +} +```` + +In the previous part, we've created a new microservice named Catalog. In this part, we will build functionality to create and manage products in our system. + +In this part, we will use [ABP Suite](../../suite/index.md) to automatically create all the necessary code for us. So, you will see how to use ABP Suite in a microservice solution. We will do everything manually while we will create the Ordering microservice in next parts, so you will learn the details better. We suggest to use ABP Suite wherever it is possible, because it saves a lot of time. You can then investigate the changes done by ABP Suite to understand what it produced. + +## Opening the ABP Suite + +First of all, **stop all the applications** in ABP Studio's *Solution Runner* panel, because ABP Suite will make changes in the solution and it will also needs to build the solution in some steps. Running the solution prevents to build it. + +Now, select the *ABP Suite* -> *Open* command on the main menu to open ABP Suite: + +![abp-studio-open-abp-suite](images/abp-studio-open-abp-suite.png) + +It will ask to you which module you want to use: + +![abp-studio-open-abp-suite-select-module](images/abp-studio-open-abp-suite-select-module.png) + +The `CloudCrm` microservice solution contains more than one .NET solution. Typically, each ABP Studio module represents a separate .NET solution (see the [concepts](../../studio/concepts.md) document). ABP Suite works on a single .NET solution to generate code, so we should select a module here. + +Select the `CloudCrm.CatalogService` module and click the *OK* button. It will open ABP Suite as shown below: + +![abp-studio-abp-suite-inside](images/abp-studio-abp-suite-inside.png) + +## Generating a Products Page + +In the next section, we will use ABP Suite to create a fully functional CRUD page with ABP Suite. The UI part will be in the main web application (`CloudCrm.Web`) and the application service and other parts will be generated in the Catalog microservice. + +### Configuring the Product Entity Information + +Type `Product` for the *Name* field and leave the other options as is. ABP Suite will automatically calculate proper values for you: + +![abp-suite-product-info](images/abp-suite-product-info.png) + +### Configuring Properties of the Product Entity + +Open the *Properties* tab and create the properties shown in the following figure: + +![abp-suite-product-properties](images/abp-suite-product-properties.png) + +Here the details: + +* `Name` is required, minimum length is `2` and maximum length is `120`. +* `Description` is not required, it is a *Text area*, not *Filterable*, not *Shown on the list page*. +* `StockCount` has a *Default value* `0`, minimum value `0` and maximum value `999999`. +* `ReleaseDate` is *Nullable*. + +You can leave the other configurations as default. + +### Generating the Code + +![abp-suite-product-generating.png](images/abp-suite-product-generating.png) + +That's all. You can click the *Save and generate* button to start the code generation process. + +![abp-suite-product-generated](images/abp-suite-product-generated.png) + +ABP Suite will generate the necessary code for you. It will take some time to complete the process. After the process is completed, you will see a success message, click the *OK* button. + +![abp-studio-catalog-service-build-and-start](images/abp-studio-catalog-service-start.png) + +We can now start the `CloudCrm.CatalogService` application by clicking the *Start* button (or alternatively, directly clicking the *run* icon) in the *Solution Runner* panel. + +![abp-studio-browse-catalog-service-2](images/abp-studio-browse-catalog-service-2.png) + +After the application is started, you can right-click and [Browse](../../studio/running-applications.md#monitoring) on the `CloudCrm.CatalogService` application to open it in the ABP Studio's pre-integrated browser. You can see the *Products* controller in the Swagger UI. + +### Generating the UI Proxy + +Now, we need to generate the [Static API Proxy](../../framework/api-development/static-csharp-clients.md) for the *Web* project. Right-click the *CloudCrm.Web* [package](../../studio/concepts.md#package) and select the *ABP CLI* -> *Generate Proxy* -> *C#* command: + +![abp-studio-generate-proxy](images/abp-studio-generate-proxy.png) + +It will open the *Generate C# Proxies* window. Select the `CloudCrm.CatalogService` application, and it will automatically populate the *URL* field. Select the *catalog* module, set the service type to *application*, and check the *Without contracts* checkbox, as the `CloudCrm.Web` project already depends on the `CloudCrm.CatalogService.Contracts` package: + +![abp-studio-generate-proxy-window](images/abp-studio-generate-proxy-window.png) + +> To be able to select the *Application*, you must *Start* the related application beforehand. You can start the application using [Solution Runner](../../studio/running-applications.md) as explained in the previous parts. + +Lastly, we need to configure the use of a static HTTP client for the `CatalogService` in the `CloudCrm.Web` project. Open the `CloudCrmWebModule.cs` file in the `Web` project and add the following line to the `ConfigureServices` method: + +```csharp +//... +using CloudCrm.CatalogService; + +public override void ConfigureServices(ServiceConfigurationContext context) +{ + // Code omitted for brevity + context.Services.AddStaticHttpClientProxies( + typeof(CloudCrmCatalogServiceContractsModule).Assembly); +} +``` + +### Running the Application + +Now, stop any application running in the *Solution Runner* panel, and then run the applications by clicking the *Start All* button on the root item in the *Solution Runner* panel: + +![abp-studio-run-build-and-start-all](images/abp-studio-run-start-all.png) + +After the application is started, you can right-click and [Browse](../../studio/running-applications.md#monitoring) on the `CloudCrm.Web` application to open it in the ABP Studio's pre-integrated browser: + +![abp-studio-browse-cloud-crm-products](images/abp-studio-browse-cloud-crm-products.png) + +> If you can't see the *Products* menu item, you need to grant the `CatalogService` *Product* permission to the *admin* role. You can do this by navigating to *Identity Management* -> *Roles* and editing the *admin* role. Alternatively, you can restart the *CloudCrm.AdministrationService* application to automatically seed all permissions for the *admin* role. + +> When we create `Catalog` microservice, the `CatalogService` API scope is also created automatically if the "Enable integration" option is selected. You can verify the new scope in the `CloudCrm.IdentityService` module (.NET solution), in the `CloudCrm.IdentityService` project within the `OpenIddictDataSeeder` class's `CreateApiScopesAsync` method. If you are already logged in to the application, you may need to log out and log back in to reauthorize with the newly created API scope. + +You can open the Sql Server Management Studio to see the created tables and data: + +![sql-server-management-studio-products](images/sql-server-management-studio-products.png) + +## Summary + +In this part, we've created a new entity named *Product* and generated the necessary code for it. We've also generated the UI proxy for the `CatalogService` application and configured the static HTTP client for it in the `Web` project. We've run the application and tested the *Products* page. \ No newline at end of file diff --git a/docs/en/tutorials/microservice/part-04.md b/docs/en/tutorials/microservice/part-04.md new file mode 100644 index 0000000000..50386ab8e1 --- /dev/null +++ b/docs/en/tutorials/microservice/part-04.md @@ -0,0 +1,97 @@ +# Microservice Tutorial Part 04: Creating the initial Ordering service + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Building the Catalog service", + "Path": "tutorials/microservice/part-03" + }, + "Next": { + "Name": "Building the Ordering service", + "Path": "tutorials/microservice/part-05" + } +} +```` + +In the previous part, we implemented the Catalog microservice functionality using ABP Suite. In this part, we will create the Ordering microservice, and the following part will cover implementing its functionality manually. + +## Creating the Ordering Microservice + +Right-click the `services` folder in the *Solution Explorer* panel, select the *Add* -> *New Module* -> *Microservice* command: + +![abp-studio-add-new-microservice-command](images/abp-studio-add-new-microservice-command-2.png) + +This command opens a new dialog to define the properties of the new microservice. You can use the following values to create a new microservice named `OrderingService`: + +![abp-studio-add-new-microservice-dialog](images/abp-studio-add-new-microservice-dialog-2.png) + +When you click the *Next* button, you are redirected to the database provider selection step. + +### Selecting the Database Type + +Here, you can select the database provider to be used by the new microservice: + +![abp-studio-add-new-microservice-dialog-database-step](images/abp-studio-add-new-microservice-dialog-database-step.png) + +Select *Entity Framework Core* option and proceed the *Next* step. + +### Integrating to the Solution + +In this step, we can select the options for integrating the new microservice to the rest of the solution components: + +![abp-studio-add-new-microservice-dialog-integration-step](images/abp-studio-add-new-microservice-dialog-integration-step.png) + +ABP Studio intelligently selects the right values for you, but you should still check them carefully since they directly affect what we will do in the next parts of this tutorial. + +**Ensure the options are configured the same as in the preceding figure**, and click the *Next* button. + +### Additional Options + +![abp-studio-add-new-microservice-dialog-additional-options-step](images/abp-studio-add-new-microservice-dialog-additional-options-step.png) + +In this step, you can select additional options for the new microservice. You can leave them as default and click the *Create* button. + +That's all, ABP Studio creates the new microservice and arranges all the integration and configuration for you. + +## Exploring the New Ordering Microservice + +In this section, we will investigate the new microservice in overall. + +### Understanding the Solution Structure + +Just like the Catalog microservice, the Ordering microservice is a .NET solution that contains multiple projects. You can see the solution structure in the *Solution Explorer* panel: + +![abp-studio-solution-explorer-ordering-microservice](images/abp-studio-solution-explorer-ordering-microservice.png) + +* `CloudCrm.OrderingService` is the main project that you will implement your service. It typically contains your [entities](../../framework/architecture/domain-driven-design/entities.md), [repositories](../../framework/architecture/domain-driven-design/repositories.md), [application services](../../framework/architecture/domain-driven-design/application-services.md), API controllers, etc. +* `CloudCrm.OrderingService.Contracts` project can be shared with the other services and applications. It typically contains interfaces of your [application services](../../framework/architecture/domain-driven-design/application-services.md), [data transfer objects](../../framework/architecture/domain-driven-design/data-transfer-objects.md), and some other types you may want to share with the clients of this microservice. +* `CloudCrm.OrderingService.Tests` is for building your unit and integration tests for this microservice. + +### Running the New Service + +You can run the solution using ABP Studio's *Solution Runner*. It will also run the new Ordering service as a part of the solution. + +> Before running the solution, **ensure that all the applications are built**. If you are not sure, right-click the root item (`CloudCrm`) in the *Solution Explorer* panel and select the *Build* -> *Graph Build* command. + +Click the *Play* button near to the solution root: + +![abp-studio-run-solution](images/abp-studio-run-solution.png) + +### Browsing the Ordering Service + +After the application is started, you can right-click and [Browse](../../studio/running-applications.md#monitoring) on the `CloudCrm.OrderingService` application to open it in the ABP Studio's pre-integrated browser. You can see the *Orders* controller in the Swagger UI: + +![abp-studio-browse-ordering-service](images/abp-studio-browse-ordering-service.png) + +### Opening the Ordering Database + +You can use the SQL Server Management Studio or any other tool to connect to the Ordering service's database. Use `localhost,1434` as the *Server name*, select the *SQL Server Authentication* as the *Authentication* type, use `sa` as the *Login* name and `myPassw@rd` as the *Password* value. You can find these values in the `appsettings.json` file in the `CloudCrm.OrderingService` project of the .NET solution of the Ordering microservice: + +![sql-server-management-studio-databases-2](images/sql-server-management-studio-databases-2.png) + +Similarly the Ordering service's database has only three initial table. The first one is for Entity Framework Core's migration system, and the others are for ABP's [distributed event bus](../../solution-templates/microservice/distributed-events.md) to properly apply transactional events using the outbox and inbox patterns. You don't need to care about these tables since they are created and managed by Entity Framework Core and ABP. + +## Summary + +In this part, we've created the initial Ordering microservice. We will implement its functionality in the next part. \ No newline at end of file diff --git a/docs/en/tutorials/microservice/part-05.md b/docs/en/tutorials/microservice/part-05.md new file mode 100644 index 0000000000..f2cefce256 --- /dev/null +++ b/docs/en/tutorials/microservice/part-05.md @@ -0,0 +1,430 @@ +# Microservice Tutorial Part 05: Building the Ordering service + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Creating the initial Ordering service", + "Path": "tutorials/microservice/part-04" + }, + "Next": { + "Name": "Integrating the services: HTTP API Calls", + "Path": "tutorials/microservice/part-06" + } +} +```` + +In the previous part, we created the Ordering microservice. In this part, we will implement the functionality of the Ordering microservice manually. We will not use ABP Suite to generate the code. Instead, we will create the necessary code step by step to understand the details better. + +## Creating the Order Entity + +We will start by creating the `Order` entity, which will represent an order in our system. We'll add this entity to the `CloudCrm.OrderingService` project. Create a new folder named `Entities` and add a class named `Order` inside it: + +```csharp +using CloudCrm.OrderingService.Enums; +using Volo.Abp.Domain.Entities.Auditing; + +namespace CloudCrm.OrderingService.Entities; + +public class Order : CreationAuditedAggregateRoot +{ + public Guid ProductId { get; set; } + public string CustomerName { get; set; } + public OrderState State { get; set; } +} +``` + +To keep this example simple, we allow users to include only a single product within an order. The `Order` entity inherits from the [CreationAuditedAggregateRoot<>](../../framework/architecture/domain-driven-design/entities.md) class. This class, provided by the ABP Framework, includes common properties like `Id`, `CreationTime`, `CreatorId`, etc. + +### Adding the OrderState Enum + +We also need to define the `OrderState` enum. In the `CloudCrm.OrderingService.Contracts` project, create a folder named `Enums` and add an `OrderState` enum inside it: + +```csharp +namespace CloudCrm.OrderingService.Enums; + +public enum OrderState : byte +{ + Placed = 0, + Delivered = 1, + Canceled = 2 +} +``` + +The final solution structure should look like this: + +![vs-ordering-entity](images/vs-ordering-entity.png) + +## Configuring the Database Mapping + +First, we need to add the `Order` entity to the `OrderingServiceDbContext` class. Open the `OrderingServiceDbContext` class in the `CloudCrm.OrderingService` project, located in the `Data` folder, and add the following code to the `DbSet` properties: + +```csharp +using CloudCrm.OrderingService.Entities; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.DistributedEvents; +using Volo.Abp.EntityFrameworkCore.Modeling; + +namespace CloudCrm.OrderingService.Data; + +[ConnectionStringName(DatabaseName)] +public class OrderingServiceDbContext : + AbpDbContext, + IHasEventInbox, + IHasEventOutbox +{ + public const string DbTablePrefix = ""; + public const string DbSchema = null; + + public const string DatabaseName = "OrderingService"; + + public DbSet IncomingEvents { get; set; } + public DbSet OutgoingEvents { get; set; } + public DbSet Orders { get; set; } // NEW: ADD DBSET PROPERTY + + // Code omitted for brevity +} +``` + +Next, we need to configure the database mapping for the `Order` entity. We'll use the [EF Core Fluent API](https://docs.microsoft.com/en-us/ef/core/modeling/relational/tables) for this configuration. In the `OrderingServiceDbContext` class add the following code to the `OnModelCreating` method: + +```csharp +protected override void OnModelCreating(ModelBuilder builder) +{ + base.OnModelCreating(builder); + + builder.ConfigureEventInbox(); + builder.ConfigureEventOutbox(); + + builder.Entity(b => + { + // Configure table name + b.ToTable("Orders"); + + // Always call this method to set base entity properties + b.ConfigureByConvention(); + + // Properties of the entity + b.Property(q => q.CustomerName).IsRequired().HasMaxLength(120); + }); +} +``` + +In this code snippet, we configure the `Order` entity to use the `Orders` table in the database. We also specify that the `CustomerName` property is required and has a maximum length of 120 characters. + +### Add a Database Migration + +Now, we can add a new database migration. You can use Entity Framework Core's `Add-Migration` (or `dotnet ef migrations add`) terminal command, but in this tutorial, we will use ABP Studio's shortcut UI. + +Please stop the applications if they are running and ensure that the solution has built. You can right-click the `CloudCrm.OrderingService` (under the `services` folder) on ABP Studio *Solution Explorer* and select the *Dotnet CLI* -> *Graph Build* command. + +Right-click the `CloudCrm.OrderingService` package and select the *EF Core CLI* -> *Add Migration* command: + +![abp-studio-add-entity-framework-core-migration](images/abp-studio-add-entity-framework-core-migration.png) + +The *Add Migration* command opens a new dialog to get a migration name: + +![abp-studio-entity-framework-core-add-migration-order](images/abp-studio-entity-framework-core-add-migration-order.png) + +Once you click the *OK* button, a new database migration class is added to the `Migrations` folder of the `CloudCrm.OrderingService` project: + +![visual-studio-new-migration-class](images/visual-studio-new-migration-class.png) + +The changes will be applied to the database during the next application startup. For more details, refer to the [database migrations on service startup](../../solution-templates/microservice/database-configurations.md#database-migrations-on-service-startup) section. + +## Creating the Application Service + +Now, we will create the application service to manage the `Order` entity. + +### Defining the Application Service Contract + +First, we need to define the application service contract under the `CloudCrm.OrderingService.Contracts` project. Return to your IDE, open the `CloudCrm.OrderingService` module's .NET solution and create an `IOrderAppService` interface under the `Services` folder for the `CloudCrm.OrderingService.Contracts` project: + +```csharp +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace CloudCrm.OrderingService.Services; + +public interface IOrderAppService : IApplicationService +{ + Task> GetListAsync(); + Task CreateAsync(OrderCreationDto input); +} +``` + +### Defining the Data Transfer Objects + +Next, we need to define the data transfer objects (DTOs) for the `Order` entity. The `GetListAsync` and `CreateAsync` methods will use these DTOs to communicate with the client applications. + +Create a `OrderCreationDto` class under the `Services` folder in the `CloudCrm.OrderingService.Contracts` project: + +```csharp +using System; +using System.ComponentModel.DataAnnotations; + +namespace CloudCrm.OrderingService.Services; + +public class OrderCreationDto +{ + [Required] + [StringLength(150)] + public string CustomerName { get; set; } + + [Required] + public Guid ProductId { get; set; } +} +``` + +Create a `OrderDto` class under the `Services` folder in the `CloudCrm.OrderingService.Contracts` project: + +```csharp +using System; +using CloudCrm.OrderingService.Enums; + +namespace CloudCrm.OrderingService.Services; + +public class OrderDto +{ + public Guid Id { get; set; } + public string CustomerName { get; set; } + public Guid ProductId { get; set; } + public OrderState State { get; set; } +} +``` + +The final solution structure should look like this: + +![vs-ordering-contracts](images/vs-ordering-contracts.png) + +## Implementing the Application Service + +Now, we will implement the `IOrderAppService` interface in the `OrderAppService` class. Create an `OrderAppService` class under the `Services` folder in the `CloudCrm.OrderingService` project: + +```csharp +using System; +using System.Collections.Generic; +using CloudCrm.OrderingService.Entities; +using CloudCrm.OrderingService.Enums; +using CloudCrm.OrderingService.Localization; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; + +namespace CloudCrm.OrderingService.Services; + +public class OrderAppService : ApplicationService, IOrderAppService +{ + private readonly IRepository _orderRepository; + + public OrderAppService(IRepository orderRepository) + { + LocalizationResource = typeof(OrderingServiceResource); + _orderRepository = orderRepository; + } + + public async Task> GetListAsync() + { + var orders = await _orderRepository.GetListAsync(); + return ObjectMapper.Map, List>(orders); + } + + public async Task CreateAsync(OrderCreationDto input) + { + var order = new Order + { + CustomerName = input.CustomerName, + ProductId = input.ProductId, + State = OrderState.Placed + }; + + await _orderRepository.InsertAsync(order); + } +} +``` + +In this code snippet, we inject the `IRepository` into the `OrderAppService` class. We use this repository to interact with the `Order` entity. The `GetListAsync` method retrieves a list of orders from the database and maps them to the `OrderDto` class. The `CreateAsync` method creates a new order entity and inserts it into the database. + +Afterward, we need to configure the *AutoMapper* object to map the `Order` entity to the `OrderDto` class. Open the `OrderingServiceApplicationAutoMapperProfile` class in the `CloudCrm.OrderingService` project, located in the `ObjectMapping` folder, and add the following code: + +```csharp +using AutoMapper; +using CloudCrm.OrderingService.Entities; +using CloudCrm.OrderingService.Services; + +namespace CloudCrm.OrderingService.ObjectMapping; + +public class OrderingServiceApplicationAutoMapperProfile : Profile +{ + public OrderingServiceApplicationAutoMapperProfile() + { + CreateMap(); + } +} +``` + +## Testing the Application Service + +Now, we can test the `OrderAppService` class using the Swagger UI. Open the Solution Runner and right-click to `CloudCrm.OrderingService` project and select the *Start* command. After the application starts, you can open the Swagger UI by clicking to the [Browse](../../studio/running-applications.md#monitoring) command: + +![ordering-service-swagger-ui](images/ordering-service-swagger-ui.png) + +Expand the `api/ordering/order` API and click the *Try it out* button. Then, create a few orders by filling in the request body and clicking the *Execute* button: + +![ordering-service-order-swagger-ui](images/ordering-service-order-swagger-ui.png) + +If you check the database, you should see the entities created in the `Orders` table: + +![sql-server-orders-database-table-records](images/sql-server-orders-database-table-records.png) + +> Since we're using the [Auto API Controller](../../framework/api-development/auto-controllers.md) we don't need to create a controller for the `OrderAppService`. The ABP Framework automatically creates an API controller for the `OrderAppService` class. You can find the configuration in the `CloudCrmOrderingServiceModule` class, in the `ConfigureAutoControllers` method of the `CloudCrm.OrderingService` project. + +## Creating the User Interface + +Now, we will create the user interface for the Ordering module. We will use the `CloudCrm.Web` project to create the user interface. Open the `CloudCrm.Web` .NET solution in your favorite IDE. + +### Creating the Orders Page + +Create a new `Orders` folder under the `Pages` folder in the `CloudCrm.Web` project. Then, create an `Index.cshtml` Razor Page inside that new folder and edit the `Index.cshtml.cs` file as follows: + +```csharp +using CloudCrm.OrderingService.Services; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace CloudCrm.Web.Pages.Orders; + +public class Index : AbpPageModel +{ + public List Orders { get; set; } + + private readonly IOrderAppService _orderAppService; + + public Index(IOrderAppService orderAppService) + { + _orderAppService = orderAppService; + } + + public async Task OnGetAsync() + { + Orders = await _orderAppService.GetListAsync(); + } +} +``` + +Here, we inject the `IOrderAppService` into the `Index` Razor Page. We use this service to retrieve the list of orders from the database and assign them to the `Orders` property. Open the `Index.cshtml` file and add the following code: + +```html +@page +@model CloudCrm.Web.Pages.Orders.Index + +

Orders

+ + + + + @foreach (var order in Model.Orders) + { + + Customer: @order.CustomerName
+ Product: @order.ProductId
+ State: @order.State +
+ } +
+
+
+``` + +This page shows a list of orders on the UI. We haven't created a UI to create new orders, and we will not do it to keep this tutorial simple. If you want to learn how to create advanced UIs with ABP, please follow the [Book Store tutorial](../../tutorials/book-store/index.md). + +### Generating the UI Proxy + +To select the *Application* during proxy generation, ensure that the `CloudCrm.OrderingService` is *Started* beforehand. You can start the application using [Solution Runner](../../studio/running-applications.md). + +Now, we need to generate the [Static API Proxy](../../framework/api-development/static-csharp-clients.md) for the *Web* project. Right-click the *CloudCrm.Web* [package](../../studio/concepts.md#package) and select the *ABP CLI* -> *Generate Proxy* -> *C#* command: + +![abp-studio-generate-proxy-2](images/abp-studio-generate-proxy-2.png) + +It will open the *Generate C# Proxies* window. Select the `CloudCrm.OrderingService` application, and it will automatically populate the *URL* field. Choose the *ordering* module and service type is *application* lastly check the *Without contracts* checkbox, since we already have a dependency on the `CloudCrm.OrderingService.Contracts` package in the `CloudCrm.Web` project: + +![abp-studio-generate-proxy-window-ordering-module](images/abp-studio-generate-proxy-window-ordering-module.png) + +Lastly, we need to configure the use of a static HTTP client for the `OrderingService` in the `CloudCrm.Web` project. Open the `CloudCrmWebModule.cs` file in the `Web` project and add the following line to the `ConfigureServices` method: + +```csharp +//... +using CloudCrm.OrderingService; + +public override void ConfigureServices(ServiceConfigurationContext context) +{ + // Code omitted for brevity + context.Services.AddStaticHttpClientProxies( + typeof(CloudCrmOrderingServiceContractsModule).Assembly); +} +``` + +### Adding the Menu Item + +> ABP provides a modular navigation [menu system](../../framework/ui/mvc-razor-pages/navigation-menu.md) that allows you to define the menu items in a modular way. + +Finally, we need to add a menu item to the sidebar to navigate to the `Orders` page. Open the `CloudCrmMenus` file in the `Navigation` folder of the `CloudCrm.Web` project and edit with the following code: + +```csharp +namespace CloudCrm.Web.Navigation; + +public class CloudCrmMenus +{ + private const string Prefix = "CloudCrm"; + + public const string Home = Prefix + ".Home"; + + public const string HostDashboard = Prefix + ".HostDashboard"; + + public const string TenantDashboard = Prefix + ".TenantDashboard"; + + public const string Products = Prefix + ".Products"; + + public const string Orders = Prefix + ".Orders"; // NEW: ADD MENU ITEM +} +``` + +Then, open the `CloudCrmMenuContributor` class in the `CloudCrm.Web` project, located in the `Navigation` folder, and add the following code to `ConfigureMainMenuAsync` method: + +```csharp +private static async Task ConfigureMainMenuAsync(MenuConfigurationContext context) +{ + // Code omitted for brevity + + context.Menu.AddItem( + new ApplicationMenuItem( + CloudCrmMenus.Orders, // Unique menu id + "Orders", // Menu display text + "~/Orders", // URL + "fa-solid fa-basket-shopping" // Icon CSS class + ) + ); +} +``` + +## Building and Running the Application + +Now, we can run the application to see the changes. Please stop the applications if they are running. Then open the *Solution Runner* panel, right-click the `CloudCrm` root item, and select the *Start* command: + +![abp-studio-run-build-start](images/abp-studio-run-start-all.png) + +After the applications are started, you can *Browse* and navigate to the `Orders` page to see the list of orders: + +![web-orders-page](images/web-orders-page.png) + +Great! We have successfully implemented the Ordering module. However, there is a problem: + +- We see Product's GUID ID instead of its name. This is because the *Ordering* microservice has no integration with the *Catalog* microservice and doesn't have access to Product microservice's database to perform a JOIN query. + +We will solve this problem in the next part by implementing an integration service between the *Ordering* and *Catalog* microservices. + +## Summary + +In this part, we implemented the Ordering module manually. We created the `Order` entity, the `OrderState` enum, the `OrderAppService` application service, and the user interface for the `Orders` page. We also added a menu item to the sidebar to navigate to the `Orders` page. \ No newline at end of file diff --git a/docs/en/tutorials/microservice/part-06.md b/docs/en/tutorials/microservice/part-06.md new file mode 100644 index 0000000000..af05bbd792 --- /dev/null +++ b/docs/en/tutorials/microservice/part-06.md @@ -0,0 +1,306 @@ +# Microservice Tutorial Part 06: Integrating the services: HTTP API Calls + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Building the Ordering service", + "Path": "tutorials/microservice/part-05" + }, + "Next": { + "Name": "Integrating the services: Using Distributed Events", + "Path": "tutorials/microservice/part-07" + } +} +```` + +In the previous part, we implemented the functionality of the Ordering microservice. However, when listing orders, we need to display the product name instead of the product ID. To achieve this, we must call the Catalog service to retrieve the product name for each order item. + +In this section, we will integrate the Ordering service with the Catalog service using HTTP API calls. + +## The Need for the Integration Services + +In a microservices architecture, each service is responsible for its own data and business logic. However, services often need to communicate with each other to fulfill their responsibilities. This communication can be synchronous or asynchronous, depending on the requirements. + +![web-orders-page](images/web-orders-page.png) + +In our case, the Ordering service needs to display the product name instead of the product ID. To achieve this, we need to call the Catalog service to retrieve the product details based on the product ID. This is a typical example of a synchronous communication pattern between microservices. As a solution to that problem, we will use an [integration service](../../framework/api-development/integration-services.md) that will handle the communication with the Catalog service. Integration service concept in ABP is designed for request/response style inter-module (in modular applications) and inter-microservice (in distributed systems) communication. + +## Creating a Products Integration Service + +First, we need to create a service that will handle the communication with the Catalog service. This service will be responsible for fetching the product details based on the product ID. + +### Defining the `IProductIntegrationService` Interface + +Open the `CloudCrm.CatalogService` .NET solution in your IDE. Locate the `CloudCrm.CatalogService.Contracts` project, and create a new folder named `IntegrationServices`. Inside this folder, add a new interface named `IProductIntegrationService` with the following code: + +```csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using CloudCrm.CatalogService.Products; +using Volo.Abp; +using Volo.Abp.Application.Services; + +namespace CloudCrm.CatalogService.IntegrationServices; + +[IntegrationService] +public interface IProductIntegrationService : IApplicationService +{ + Task> GetProductsByIdsAsync(List ids); +} +``` + +`IProductIntegrationService` is very similar to a typical [application service](../../framework/architecture/domain-driven-design/application-services.md). The only difference is that it is marked with the `[IntegrationService]` attribute. This attribute is used to identify the service as an integration service, which allows ABP to handle the communication between services. ABP behave differently for them (for example, ABP doesn't expose [integration services](../../framework/api-development/integration-services.md) as HTTP APIs by default if you've configured the [Auto API Controllers](../../framework/api-development/auto-controllers.md) feature) + +`IProductIntegrationService` contains a single method named `GetProductsByIdsAsync`. This method takes a list of product IDs and returns a list of `ProductDto` objects. This is exactly what we need in the Ordering service. + +> **Design Tip** +> +> You may think if we can use the existing application services (like `IProductAppService`) from other services instead of creating specific integration services. Technically you can use, ABP has no restriction. However, from good design and best practice points, we don't suggest it. Because, application services are designed to be consumed specifically by the presentation layer. They will have different authorization and validation logic, they will need different DTO input and output properties, they will have different performance, optimization and caching requirements, and so on. And most importantly, all these will change by the time based on UI requirements and these changes may break your integrations later. It is best to implement specific integration APIs that is designed and optimized for that purpose. +> +> We've reused the `ProductDto` object created for `IProductAppService`, which can be reasonable from a maintenance point of view. But if you think your integration service results will be different from the application service results in the future, it can be good to separate them from the first day so you don't need to introduce breaking changes later. + +### Implementing the `ProductIntegrationService` Class + +Now, let's implement the `IProductIntegrationService` interface. Create a new folder named `IntegrationServices` in the `CloudCrm.CatalogService` project. Inside this folder, add a new class named `ProductIntegrationService` with the following code: + +```csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using CloudCrm.CatalogService.Localization; +using CloudCrm.CatalogService.Products; +using Volo.Abp; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; + +namespace CloudCrm.CatalogService.IntegrationServices; + +[IntegrationService] +public class ProductIntegrationService : ApplicationService, IProductIntegrationService +{ + private readonly IRepository _productRepository; + + public ProductIntegrationService(IRepository productRepository) + { + LocalizationResource = typeof(CatalogServiceResource); + _productRepository = productRepository; + } + + public async Task> GetProductsByIdsAsync(List ids) + { + var products = await _productRepository.GetListAsync( + product => ids.Contains(product.Id) + ); + + return ObjectMapper.Map, List>(products); + } +} +``` + +`ProductIntegrationService` is a typical application service class that implements the `IProductIntegrationService` interface. It has a constructor that takes an `IRepository` object. This repository is used to fetch the product details from the database. + +> Here, we directly used `List` classes, but instead, you could wrap inputs and outputs into [DTOs](../../framework/architecture/domain-driven-design/data-transfer-objects.md). In that way, it can be possible to add new properties to these DTOs without changing the signature of your integration service method (and without introducing breaking changes for your client applications). + +## Consuming the Products Integration Service + +Now that we have created the `IProductIntegrationService` interface and the `ProductIntegrationService` class, we can consume this service from the Ordering service. + +### Adding a Reference to the `CloudCrm.CatalogService.Contracts` Package + +First, we need to add a reference to the `CloudCrm.CatalogService.Contracts` package in the Ordering service. Open the ABP Studio, and stop the application(s) if it is running. Then, open the *Solution Explorer* and right-click on the `CloudCrm.OrderingService` package. Select *Add* -> *Package Reference* command: + +![add-package-reference-ordering-service](images/add-package-reference-ordering-service.png) + +In the *Add Package Reference* window, select the `CloudCrm.CatalogService.Contracts` package from the *This solution* tab. Click the *OK* button to add the reference: + +![add-catalog-service-contracts-reference](images/add-catalog-service-contracts-reference.png) + +ABP Studio adds the package reference and arranges the [module](../../framework/architecture/modularity/basics.md) dependency. + +> Instead of directly adding such a package reference, it can be best to import the module first (right-click the `CloudCrm.OrderingService`, select the _Import Module_ command and import the `CloudCrm.CatalogService` module), then install the package reference. In that way, it would be easy to see and keep track of inter-module dependencies. + +### Using the Products Integration Service + +Now, we can use the `IProductIntegrationService` interface to fetch the product details in the `OrderAppService` class. + +Open the `OrderAppService` class (the `OrderAppService.cs` file under the `Services` folder of the `CloudCrm.OrderingService` project of the `CloudCrm.OrderingService` .NET solution) and change its content as like the following code block: + +```csharp +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using CloudCrm.CatalogService.IntegrationServices; +using CloudCrm.OrderingService.Entities; +using CloudCrm.OrderingService.Enums; +using CloudCrm.OrderingService.Localization; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; + +namespace CloudCrm.OrderingService.Services; + +public class OrderAppService : ApplicationService, IOrderAppService +{ + private readonly IRepository _orderRepository; + private readonly IProductIntegrationService _productIntegrationService; + + public OrderAppService( + IRepository orderRepository, + IProductIntegrationService productIntegrationService) + { + LocalizationResource = typeof(OrderingServiceResource); + + _orderRepository = orderRepository; + _productIntegrationService = productIntegrationService; + } + + public async Task> GetListAsync() + { + var orders = await _orderRepository.GetListAsync(); + + // Prepare a list of products we need + var productIds = orders.Select(o => o.ProductId).Distinct().ToList(); + var products = (await _productIntegrationService + .GetProductsByIdsAsync(productIds)) + .ToDictionary(p => p.Id, p => p.Name); + + var orderDtos = ObjectMapper.Map, List>(orders); + + orderDtos.ForEach(orderDto => + { + orderDto.ProductName = products[orderDto.ProductId]; + }); + + return orderDtos; + } + + public async Task CreateAsync(OrderCreationDto input) + { + var order = new Order + { + CustomerName = input.CustomerName, + ProductId = input.ProductId, + State = OrderState.Placed + }; + + await _orderRepository.InsertAsync(order); + } +} +``` + +Now open the `OrderDto` class (the `OrderDto.cs` file under the `Services` folder of the `CloudCrm.OrderingService.Contracts` project of the `CloudCrm.OrderingService` .NET solution) and add a new property named `ProductName`: + +```csharp +using System; +using CloudCrm.OrderingService.Enums; + +namespace CloudCrm.OrderingService.Services; + +public class OrderDto +{ + public Guid Id { get; set; } + public string CustomerName { get; set; } + public Guid ProductId { get; set; } + public string ProductName { get; set; } // New property + public OrderState State { get; set; } +} +``` + +Lastly, open the `OrderingServiceApplicationAutoMapperProfile` class (the `OrderingServiceApplicationAutoMapperProfile.cs` file under the `ObjectMapping` folder of the `CloudCrm.OrderingService` project of the `CloudCrm.OrderingService` .NET solution) and ignore the `ProductName` property in the mapping configuration: + +```csharp +using AutoMapper; +using CloudCrm.OrderingService.Entities; +using CloudCrm.OrderingService.Services; +using Volo.Abp.AutoMapper; + +namespace CloudCrm.OrderingService.ObjectMapping; + +public class OrderingServiceApplicationAutoMapperProfile : Profile +{ + public OrderingServiceApplicationAutoMapperProfile() + { + CreateMap() + .Ignore(x => x.ProductName); // New line + } +} +``` +Let's explain the changes we made: + +- We added a new property named `ProductName` to the `OrderDto` class. This property will hold the product name. +- We modified the `GetListAsync` method of the `OrderAppService` class to fetch the product details using the `IProductIntegrationService` interface. We first fetch the product IDs from the orders, then call the `GetProductsByIdsAsync` method of the `IProductIntegrationService` interface to fetch the product details. Finally, we map the product names to the `OrderDto` objects. + +### Generating Proxy Classes for the Integration Service + +We have created the `IProductIntegrationService` interface and the `ProductIntegrationService` class in the `CloudCrm.CatalogService` solution. Now, we need to generate the proxy classes for the integration service in the `CloudCrm.OrderingService` package. First, *Start* the `CloudCrm.CatalogService` application in ABP Studio *Solution Runner*. Then, open the *Solution Explorer* and right-click on the `CloudCrm.OrderingService` package. Select the *ABP CLI* -> *Generate Proxy* -> *C#* command: + +![generate-proxy-catalog-service](images/generate-proxy-catalog-service.png) + +It opens the *Generate C# proxies* window. Select the `CloudCrm.CatalogService` application from the *Application* dropdown list. Then, choose the *catalog* module from the *Module* dropdown list and choose the *integration* service from the *Service type* dropdown list. Check the *Without contracts* checkbox and click the *Generate* button: + +![generate-catalog-service-proxy](images/generate-catalog-service-proxy.png) + +We have generated the proxy classes for the `IProductIntegrationService` interface. Now, we must add the *Remote Service* url to the `appsettings.json` file of the `CloudCrm.OrderingService` project. Open the `appsettings.json` file (the `appsettings.json` file of the `CloudCrm.OrderingService` project of the `CloudCrm.OrderingService` .NET solution) and add the *CatalogService* section following configuration: + +```json +{ + "RemoteServices": { + "CatalogService": { + "BaseUrl": "http://localhost:44334" + } + } +} +``` + +> **BaseUrl** refers to the base URL of the Catalog service. You can use the *Copy Url* option from the Catalog service's context menu in the ABP Studio **Solution Runner** to paste it here. + +Lastly, open the `CloudCrmOrderingServiceModule` class (the `CloudCrmOrderingServiceModule.cs` file under the `CloudCrm.OrderingService` project of the `CloudCrm.OrderingService` .NET solution) and add the following code to the `ConfigureServices` method: + +```csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + // Other configurations... + context.Services.AddStaticHttpClientProxies( + typeof(CloudCrmCatalogServiceContractsModule).Assembly, + "CatalogService"); +} + +``` + +### Updating the UI to Display the Product Name + +Open the `Index.cshtml` file (the `Index.cshtml` file under the `Pages/Orders` folder of the `CloudCrm.Web` project of the `CloudCrm.Web` .NET solution) and update the table content to display the product name instead of the product ID: + +```html +@page +@model CloudCrm.Web.Pages.Orders.Index + +

Orders

+ + + + + @foreach (var order in Model.Orders) + { + + Customer: @order.CustomerName
+ Product: @order.ProductName
+ State: @order.State +
+ } +
+
+
+``` + +That's it! Now, you can *Start* the all applications and browse it in ABP Studio to see the result: + +![web-orders-page-with-product-name](images/web-orders-page-with-product-name.png) + +Now, the Ordering service displays the product name instead of the product ID. We have successfully integrated the Ordering service with the Catalog service using HTTP API calls. + +> **Design Tip** +> +> It is suggested that you keep that type of communication to a minimum and not couple your services with each other. It can make your solution complicated and may also decrease your system performance. When you need to do it, think about performance and try to make some optimizations. For example, if the Ordering service frequently needs product data, you can use a kind of [cache layer](../../framework/fundamentals/caching.md), so it doesn't make frequent requests to the Catalog service. diff --git a/docs/en/tutorials/microservice/part-07.md b/docs/en/tutorials/microservice/part-07.md new file mode 100644 index 0000000000..633789fc6c --- /dev/null +++ b/docs/en/tutorials/microservice/part-07.md @@ -0,0 +1,231 @@ +# Microservice Tutorial Part 07: Integrating the services: Using Distributed Events + +````json +//[doc-nav] +{ + "Previous": { + "Name": "Integrating the services: HTTP API Calls", + "Path": "tutorials/microservice/part-06" + } +} +```` + +Another common approach to communicating between microservices is messaging. By publishing and handling messages, a microservice can perform an operation when an event happens in another microservice. + +ABP provides two types of event buses for loosely coupled communication: + +* [Local Event Bus](../../framework/infrastructure/event-bus/local/index.md) is suitable for in-process messaging. However, it’s not suitable for microservices as it cannot communicate across different processes. For distributed systems, consider using a distributed event bus. + +* [Distributed Event Bus](../../framework/infrastructure/event-bus/distributed/index.md) is normal for inter-process messaging, like microservices, for publishing and subscribing to distributed events. However, ABP's distributed event bus works as local (in-process) by default (actually, it uses the Local Event Bus under the hood by default) unless you configure an external message broker. + +In this tutorial, we will use the distributed event bus to communicate between the `Order` and `Catalog` microservices. + +## Publishing an Event + +In the example scenario, we want to publish an event when a new order is placed. The Ordering service will publish the event since it knows when a new order is placed. The Catalog service will subscribe to that event and get notified when a new order is placed. This will decrease the stock count of the product related to the new order. The scenario is pretty simple; let's implement it. + +### Defining the Event Class + +Open the `CloudCrm.OrderingService` .NET solution in your IDE, create an `Events` folder and create a new class named `OrderPlacedEto` under the `CloudCrm.OrderingService.Contracts` project: + +```csharp +using System; + +namespace CloudCrm.OrderingService.Events; + +public class OrderPlacedEto +{ + public string CustomerName { get; set; } + public Guid ProductId { get; set; } +} +``` + +`OrderPlacedEto` is very simple. It is a plain C# class used to transfer data related to the event (*ETO* is an acronym for *Event Transfer Object*, a suggested naming convention but not required). You can add more properties if needed, but for this tutorial, it is more than enough. + +### Using the `IDistributedEventBus` Service + +The `IDistributedEventBus` service publishes events to the event bus. Until this point, the Ordering service only creates an Order and insert to database. Let's change that and publish `OrderPlacedEto` event, for that purpose open the `CloudCrm.OrderingService` project and update to the `OrderAppService` class as shown below: + +```csharp +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using CloudCrm.CatalogService.IntegrationServices; +using CloudCrm.OrderingService.Entities; +using CloudCrm.OrderingService.Enums; +using CloudCrm.OrderingService.Events; +using CloudCrm.OrderingService.Localization; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.EventBus.Distributed; + +namespace CloudCrm.OrderingService.Services; + +public class OrderAppService : ApplicationService, IOrderAppService +{ + private readonly IRepository _orderRepository; + private readonly IProductIntegrationService _productIntegrationService; + private readonly IDistributedEventBus _distributedEventBus; + + public OrderAppService( + IRepository orderRepository, + IProductIntegrationService productIntegrationService, + IDistributedEventBus distributedEventBus) + { + LocalizationResource = typeof(OrderingServiceResource); + + _orderRepository = orderRepository; + _productIntegrationService = productIntegrationService; + _distributedEventBus = distributedEventBus; + } + + public async Task> GetListAsync() + { + var orders = await _orderRepository.GetListAsync(); + + // Prepare a list of products we need + var productIds = orders.Select(o => o.ProductId).Distinct().ToList(); + var products = (await _productIntegrationService + .GetProductsByIdsAsync(productIds)) + .ToDictionary(p => p.Id, p => p.Name); + + var orderDtos = ObjectMapper.Map, List>(orders); + + orderDtos.ForEach(orderDto => + { + orderDto.ProductName = products[orderDto.ProductId]; + }); + + return orderDtos; + } + + public async Task CreateAsync(OrderCreationDto input) + { + // Create a new Order entity + var order = new Order + { + CustomerName = input.CustomerName, + ProductId = input.ProductId, + State = OrderState.Placed + }; + + // Save it to the database + await _orderRepository.InsertAsync(order); + + // Publish an event so other microservices can be informed + await _distributedEventBus.PublishAsync( + new OrderPlacedEto + { + ProductId = order.ProductId, + CustomerName = order.CustomerName + }); + } +} +``` +The `OrderAppService.CreateAsync` method creates a new `Order` entity, saves it to the database and finally publishes an `OrderPlacedEto` event. + +## Subscribing to an Event + +The Catalog service will subscribe to the `OrderPlacedEto` event and decrease the stock count of the product related to the new order. Let's implement it. + +### Adding a Reference to the `CloudCrm.OrderingService.Contracts` Package + +Since the `OrderPlacedEto` class is in the `CloudCrm.OrderingService.Contracts` project, we must add that package's reference to the Catalog service. This time, we will use the Import Module feature of ABP Studio (as an alternative to the approach we used in the Adding a Reference to the `CloudCrm.CatalogService.Contracts` Package section of the [previous part](./part-06.md#adding-a-reference-to-the-cloudcrmcatalogservicecontracts-package)). + +Open the ABP Studio UI and stop the applications if they are running. Then, open the *Solution Explorer* panel and right-click on the `CloudCrm.CatalogService`. Select *Import Module* from the context menu: + +![Import Module](images/import-module.png) + +In the opening dialog, find and select the `CloudCrm.OrderingService` module, check the *Install this module* option, click the *OK* button: + +![Import Module Dialog](images/import-module-dialog.png) + +Once you click the OK button, the Ordering service is imported to the Catalog service. It opens the *Install Module* dialog: + +![Install Module Dialog](images/install-module-dialog.png) + +Here, select the `CloudCrm.OrderingService.Contracts` package on the left side (because we want to add that package reference) and `CloudCrm.CatalogService` package on the middle area (because we want to add the package reference to that project). + +You can check the ABP Studio's *Solution Explorer* panel to see the module and the project reference (dependency): + +![catalog-service-dependency](images/catalog-service-dependency.png) + +### Handling the `OrderPlacedEto` Event + +Now, it's time to handle the `OrderPlacedEto` event in the Catalog service. Open the `CloudCrm.CatalogService` .NET solution in your IDE. Create a new `Orders` folder, and add a new class named `OrderEventHandler` inside that folder within the `CloudCrm.CatalogService` project: + +```csharp +using System; +using System.Threading.Tasks; +using CloudCrm.CatalogService.Products; +using CloudCrm.OrderingService.Events; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.EventBus.Distributed; + +namespace CloudCrm.CatalogService.Orders; + +public class OrderEventHandler : + IDistributedEventHandler, + ITransientDependency +{ + private readonly IProductRepository _productRepository; + + public OrderEventHandler(IProductRepository productRepository) + { + _productRepository = productRepository; + } + + public async Task HandleEventAsync(OrderPlacedEto eventData) + { + // Find the related product + var product = await _productRepository.FindAsync(eventData.ProductId); + if (product == null) + { + return; + } + + // Decrease the stock count + product.StockCount = product.StockCount - 1; + + // Update the entity in the database + await _productRepository.UpdateAsync(product); + } +} +``` + +The `OrderEventHandler` class implements the `IDistributedEventHandler` interface to handle the `OrderPlacedEto` event. When the event is published, the `HandleEventAsync` method is called. In this method, we find the related product, decrease the stock count by one, and update the entity in the database. + +Implementing `ITransientDependency` registers the `OrderEventHandler` class to the dependency injection system as a transient object. + +### Testing the Order Creation + +To keep this tutorial simple, we will not implement a user interface for creating orders. Instead, we will use the Swagger UI to create an order. Open the *Solution Runner* panel in ABP Studio and use the *Start* action to launch the `CloudCrm.OrderingService` and `CloudCrm.CatalogService` applications. Then, use the *Start All* action to start the remaining applications listed in the [Solution Runner root item](../../studio/running-applications.md#run). + +Once the application is running and ready, [Browse](../../studio/running-applications.md#c-application) the `CloudCrm.OrderingService` application. Use the `POST /api/ordering/order` endpoint to create a new order: + +![Create Order](images/create-order.png) + +Find the *Order* API, click the *Try it out* button, enter a sample value the *Request body* section, and click the *Execute* button: + +```json +{ + "customerName": "David", + "productId": "5995897b-1de9-7272-b31c-3a165bbe7b18" +} +``` + +> **IMPORTANT:** Here, you should type a valid Product Id from the Products table of your database! + +Once you press the *Execute* button, a new order is created. At that point, you can check the `/Orders` page to see if the new order is listed. You can also check the `/Products` page to see if the stock count of the related product is decreased by one in the `CloudCrm.Web` application. + +Here are sample screenshots from the Orders and Products pages of the `CloudCrm.Web` application: + +![Orders](images/orders.png) + +We placed a new order for *Product A*. As a result, the stock count of *Product A* is decreased from 53 to 52 and a new line is added to the Orders page. + +## Conclusion + +In this tutorial, we used the distributed event bus to communicate between the `Order` and `Catalog` microservices. We published an event when a new order is placed and handled that event in the Catalog service to decrease the stock count of the related product. This is a simple example, but it shows how you can use distributed events to communicate between microservices. \ No newline at end of file diff --git a/docs/en/tutorials/mobile/index.md b/docs/en/tutorials/mobile/index.md new file mode 100644 index 0000000000..51db427363 --- /dev/null +++ b/docs/en/tutorials/mobile/index.md @@ -0,0 +1,26 @@ +# Mobile Application Development Tutorial: Book Store Application + +> You must have an ABP Team or a higher license to be able to create a mobile application. + +Mobile application development tutorials are designed for developers who have completed [the web development part of the tutorial](../book-store/index.md) and wish to continue building the mobile version of the application. + +## Tutorials + +You can choose between two mobile applications: [**.NET MAUI**](../../framework/ui/maui/index.md) or [**React Native**](../../framework/ui/react-native/index.md). Choose your framework and continue building your mobile application! + +- Both guides assume you have completed the web development section of the tutorial and have the necessary backend APIs in place. +- Each guide is self-contained and provides step-by-step instructions to build the mobile app. + +### .NET MAUI + +The .NET MAUI tutorial walks you through creating a mobile version of your bookstore app using .NET technologies. + +* [.NET MAUI - Mobile Application Tutorial](./maui/index.md) +* [Source Code](https://abp.io/Account/Login?returnUrl=/api/download/samples/bookstore-maui-efcore-mobile) + +### React Native + +The React Native tutorial provides instructions for building the bookstore app using JavaScript. + +* [React Native - Mobile Application Tutorial](./react-native/index.md) +* [Source Code](https://abp.io/Account/Login?returnUrl=/api/download/samples/bookstore-react-native-mongodb) \ No newline at end of file diff --git a/docs/en/tutorials/mobile/maui/index.md b/docs/en/tutorials/mobile/maui/index.md index a9f1f92d75..c9bb520e57 100644 --- a/docs/en/tutorials/mobile/maui/index.md +++ b/docs/en/tutorials/mobile/maui/index.md @@ -2,6 +2,8 @@ ## About This Tutorial +> You must have an ABP Team or a higher license to be able to create a mobile application. + This tutorial assumes that you have completed the [Web Application Development tutorial](../../book-store/part-01.md) and built an ABP based application named `Acme.BookStore` with [MAUI](../../../get-started/maui.md) as the mobile option. Therefore, if you haven't completed the [Web Application Development tutorial](../../book-store/part-01.md), you either need to complete it or download the source code from down below and follow this tutorial. In this tutorial, we will only focus on the UI side of the `Acme.BookStore` application and we will implement the CRUD operations for a MAUI mobile application. This tutorial follows the [MVVM (Model-View-ViewModel) Pattern ](https://learn.microsoft.com/en-us/dotnet/architecture/maui/mvvm), which separates the UI from the business logic of an application. diff --git a/docs/en/tutorials/mobile/react-native/index.md b/docs/en/tutorials/mobile/react-native/index.md index 74abfa3418..2dfa3df957 100644 --- a/docs/en/tutorials/mobile/react-native/index.md +++ b/docs/en/tutorials/mobile/react-native/index.md @@ -4,6 +4,8 @@ React Native mobile option is *available for* ***Team*** *or higher licenses*. T ## About This Tutorial +> You must have an ABP Team or a higher license to be able to create a mobile application. + - This tutorial assumes that you have completed the [Web Application Development tutorial](../../book-store/part-01.md) and built an ABP based application named `Acme.BookStore` with [React Native](../../../framework/ui/react-native) as the mobile option.. Therefore, if you haven't completed the [Web Application Development tutorial](../../book-store/part-01.md), you either need to complete it or download the source code from down below and follow this tutorial. - In this tutorial, we will only focus on the UI side of the `Acme.BookStore` application and will implement the CRUD operations. - Before starting, please make sure that the [React Native Development Environment](../../../framework/ui/react-native/index.md) is ready on your machine. diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-entity-framework-core-migration.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-entity-framework-core-migration.png index 857df5f25d..ee8b847a45 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-add-entity-framework-core-migration.png and b/docs/en/tutorials/modular-crm/images/abp-studio-add-entity-framework-core-migration.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-5.png b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-5.png index 42d7c96cb2..cc3985bb75 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-5.png and b/docs/en/tutorials/modular-crm/images/abp-studio-add-package-reference-5.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-browser-orders-menu-item.png b/docs/en/tutorials/modular-crm/images/abp-studio-browser-orders-menu-item.png index 3e4d83fee1..e552239474 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-browser-orders-menu-item.png and b/docs/en/tutorials/modular-crm/images/abp-studio-browser-orders-menu-item.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-build-and-restart-application.png b/docs/en/tutorials/modular-crm/images/abp-studio-build-and-restart-application.png index 889a4251cd..bf9c9d1ead 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-build-and-restart-application.png and b/docs/en/tutorials/modular-crm/images/abp-studio-build-and-restart-application.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-entity-framework-core-update-database.png b/docs/en/tutorials/modular-crm/images/abp-studio-entity-framework-core-update-database.png index b28b4e48a0..88a0a20bb3 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-entity-framework-core-update-database.png and b/docs/en/tutorials/modular-crm/images/abp-studio-entity-framework-core-update-database.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-import-module-for-ordering-dialog.png b/docs/en/tutorials/modular-crm/images/abp-studio-import-module-for-ordering-dialog.png index 762265b2c3..2b07060c2a 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-import-module-for-ordering-dialog.png and b/docs/en/tutorials/modular-crm/images/abp-studio-import-module-for-ordering-dialog.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-install-module-dialog.png b/docs/en/tutorials/modular-crm/images/abp-studio-install-module-dialog.png index 5ea22e8e3e..8c9abf1bf7 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-install-module-dialog.png and b/docs/en/tutorials/modular-crm/images/abp-studio-install-module-dialog.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-module-installation-dialog.png b/docs/en/tutorials/modular-crm/images/abp-studio-module-installation-dialog.png index 9946afaaec..eb71910650 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-module-installation-dialog.png and b/docs/en/tutorials/modular-crm/images/abp-studio-module-installation-dialog.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-open-with-visual-studio-main-app.png b/docs/en/tutorials/modular-crm/images/abp-studio-open-with-visual-studio-main-app.png index f4131cfb69..62a85d6589 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-open-with-visual-studio-main-app.png and b/docs/en/tutorials/modular-crm/images/abp-studio-open-with-visual-studio-main-app.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser.png b/docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser.png index 304027af9e..1318691ae9 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser.png and b/docs/en/tutorials/modular-crm/images/abp-studio-ordering-swagger-ui-in-browser.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-graph-build.png b/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-graph-build.png index 10d210a0be..d88c2b80c7 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-graph-build.png and b/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-graph-build.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-initial-product-page.png b/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-initial-product-page.png index 269c55f8b4..7f9b312b23 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-initial-product-page.png and b/docs/en/tutorials/modular-crm/images/abp-studio-solution-runner-initial-product-page.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-create-order.png b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-create-order.png index e2c15fca23..01a2c5fe29 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-create-order.png and b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-create-order.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-list-orders.png b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-list-orders.png index e58ea0c51d..0fc7e63b23 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-list-orders.png and b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-list-orders.png differ diff --git a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-in-browser.png b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-in-browser.png index 8183cde1fb..1b5fec9205 100644 Binary files a/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-in-browser.png and b/docs/en/tutorials/modular-crm/images/abp-studio-swagger-ui-in-browser.png differ diff --git a/docs/en/tutorials/modular-crm/images/solution-explorer-modular-crm-expanded.png b/docs/en/tutorials/modular-crm/images/solution-explorer-modular-crm-expanded.png index 841b8fe610..3156b75fb1 100644 Binary files a/docs/en/tutorials/modular-crm/images/solution-explorer-modular-crm-expanded.png and b/docs/en/tutorials/modular-crm/images/solution-explorer-modular-crm-expanded.png differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-main-dbcontext.png b/docs/en/tutorials/modular-crm/images/visual-studio-main-dbcontext.png index 825b6c2422..0a2db8ed76 100644 Binary files a/docs/en/tutorials/modular-crm/images/visual-studio-main-dbcontext.png and b/docs/en/tutorials/modular-crm/images/visual-studio-main-dbcontext.png differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class-2.png b/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class-2.png index 3acc6e45c7..c5a698db58 100644 Binary files a/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class-2.png and b/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class-2.png differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class.png b/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class.png index b18d071cb1..24c7cb3299 100644 Binary files a/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class.png and b/docs/en/tutorials/modular-crm/images/visual-studio-new-migration-class.png differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-order-reporting-app-service-impl.png b/docs/en/tutorials/modular-crm/images/visual-studio-order-reporting-app-service-impl.png deleted file mode 100644 index 5007486775..0000000000 Binary files a/docs/en/tutorials/modular-crm/images/visual-studio-order-reporting-app-service-impl.png and /dev/null differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-order-reporting-app-service.png b/docs/en/tutorials/modular-crm/images/visual-studio-order-reporting-app-service.png index 7333958f11..1dcead7f0d 100644 Binary files a/docs/en/tutorials/modular-crm/images/visual-studio-order-reporting-app-service.png and b/docs/en/tutorials/modular-crm/images/visual-studio-order-reporting-app-service.png differ diff --git a/docs/en/tutorials/modular-crm/images/visual-studio-ordering-contracts.png b/docs/en/tutorials/modular-crm/images/visual-studio-ordering-contracts.png index d9206f6630..512edaa4a1 100644 Binary files a/docs/en/tutorials/modular-crm/images/visual-studio-ordering-contracts.png and b/docs/en/tutorials/modular-crm/images/visual-studio-ordering-contracts.png differ diff --git a/docs/en/tutorials/modular-crm/part-01.md b/docs/en/tutorials/modular-crm/part-01.md index 316aff6150..ddc6e8debe 100644 --- a/docs/en/tutorials/modular-crm/part-01.md +++ b/docs/en/tutorials/modular-crm/part-01.md @@ -3,6 +3,10 @@ ````json //[doc-nav] { + "Previous": { + "Name": "Overview", + "Path": "tutorials/modular-crm/index" + }, "Next": { "Name": "Creating the initial Products module", "Path": "tutorials/modular-crm/part-02" @@ -10,7 +14,7 @@ } ```` -Follow the *[Get Started](../../get-started/layered-web-application.md)* guide to create a new layered web application with the following configuration: +Follow the *[Get Started](../../get-started/single-layer-web-application.md)* guide to create a single layer web application with the following configuration: * **Solution name**: `ModularCrm` * **UI Framework**: ASP.NET Core MVC / Razor Pages @@ -18,7 +22,7 @@ Follow the *[Get Started](../../get-started/layered-web-application.md)* guide t You can select the other options based on your preference. -> **Please complete the [Get Stared](../../get-started/layered-web-application.md) guide and run the web application before going further.** +> **Please complete the [Get Started](../../get-started/single-layer-web-application.md) guide and run the web application before going further.** The initial solution structure should be like the following in ABP Studio's *[Solution Explorer](../../studio/solution-explorer.md)*: @@ -28,10 +32,10 @@ Initially, you see a `ModularCrm` solution and a `ModularCrm` module under that > An ABP Studio module is typically a .NET solution and an ABP Studio solution is an umbrella concept for multiple .NET Solutions (see the [concepts](../../studio/concepts.md) document for more). -`ModularCrm` module is your main application, which is a layered .NET solution that consists of several packages (.NET projects). You can expand the `ModularCrm` module to see its packages: +The `ModularCrm` module is the core of your application, built as a single-layer ASP.NET Core Web application. You can expand the `ModularCrm` module to see: ![solution-explorer-modular-crm-expanded](images/solution-explorer-modular-crm-expanded.png) ## Summary -We've created the initial layered monolith solution. In the next part, we will learn how to create a new application module and install it to the main application. +We've created the initial single layer monolith solution. In the next part, we will learn how to create a new application module and install it to the main application. diff --git a/docs/en/tutorials/modular-crm/part-02.md b/docs/en/tutorials/modular-crm/part-02.md index 6ebc22f20c..034b4b7bfe 100644 --- a/docs/en/tutorials/modular-crm/part-02.md +++ b/docs/en/tutorials/modular-crm/part-02.md @@ -14,7 +14,7 @@ } ```` -In this part, you will build a new product management module and install it in the main CRM application. +In this part, you will create a new product management module and install it in the main CRM application. ## Creating Solution Folders @@ -32,12 +32,13 @@ Create a `main` and a `modules` folder using the *New Folder* command, then move ## Creating The Module -There are two module templates provided by ABP Studio: +There are three module templates provided by ABP Studio: * **Empty Module**: You can use that module template to build your module structure from scratch. * **DDD Module**: A Domain Driven Design based layered module structure. +* **Standard Module**: A module template that is similar to the DDD module but without the domain layer. -We will use the *DDD Module* template for the Product module and the *Empty Module* template later in this tutorial. +We will use the *DDD Module* template for the Product module and the *Standard Module* template later in this tutorial. Right-click the `modules` folder on the *Solution Explorer* panel, and select the *Add* -> *New Module* -> *DDD Module* command: @@ -115,7 +116,7 @@ When you click the *OK* button, ABP Studio opens the *Install Module* dialog: ![abp-studio-module-installation-dialog](images/abp-studio-module-installation-dialog.png) -This dialog simplifies installing a multi-layer module to a multi-layer application. It automatically determines which package of the `ModularCrm.Products` module should be installed to which package of the main application. For example, the `ModularCrm.Products.Domain` package is installed to the `ModularCrm.Domain` package. In that way, you can use domain objects ([entities](../../framework/architecture/domain-driven-design/entities.md), [repositories](../../framework/architecture/domain-driven-design/repositories.md), ...) of the products module from the domain layer of your main application. +This dialog simplifies installing a multi-layer module to a single-layer application. It automatically determines which package of the `ModularCrm.Products` module should be installed to which package of the main application. The default package match is good for this tutorial, so you can click the *OK* button to proceed. @@ -131,7 +132,7 @@ Graph Build is a dotnet CLI command that recursively builds all the referenced d ### Run the Main Application -Open the *Solution Runner* panel, click the *Play* button (near to the solution root), right-click the `ModularCrm.Web` application and select the *Browse* command. It will open the web application in the built-in browser. Then you can navigate to the *Products* page on the main menu of the application to see the Products page that is coming from the `ModularCrm.Products` module: +Open the *Solution Runner* panel, click the *Play* button (near to the solution root), right-click the `ModularCrm` application and select the *Browse* command. It will open the web application in the built-in browser. Then you can navigate to the *Products* page on the main menu of the application to see the Products page that is coming from the `ModularCrm.Products` module: ![abp-studio-solution-runner-initial-product-page](images/abp-studio-solution-runner-initial-product-page.png) diff --git a/docs/en/tutorials/modular-crm/part-03.md b/docs/en/tutorials/modular-crm/part-03.md index 799745b9c8..3d8896d8a5 100644 --- a/docs/en/tutorials/modular-crm/part-03.md +++ b/docs/en/tutorials/modular-crm/part-03.md @@ -164,7 +164,7 @@ Open the `ModularCrm` module (which is the main application) in your IDE: ![abp-studio-open-with-visual-studio-main-app](images/abp-studio-open-with-visual-studio-main-app.png) -Find the `ModularCrmDbContext` class under the `ModularCrm.EntityFrameworkCore` project: +Open the `ModularCrmDbContext` class under the `ModularCrm` project's `Data` folder: ![visual-studio-main-dbcontext](images/visual-studio-main-dbcontext.png) @@ -185,10 +185,9 @@ Follow the three steps below; **(2)** Implement the `IProductsDbContext` by the `ModularCrmDbContext` class: ````csharp +[ReplaceDbContext(typeof(IProductsDbContext))] public class ModularCrmDbContext : AbpDbContext, - ITenantManagementDbContext, - IIdentityDbContext, IProductsDbContext //NEW: IMPLEMENT THE INTERFACE { public DbSet Products { get; set; } //NEW: ADD DBSET PROPERTY @@ -214,7 +213,7 @@ Now, we can add a new database migration. You can use Entity Framework Core's `A Ensure that the solution has built. You can right-click the `ModularCrm` (under the `main` folder) on ABP Studio *Solution Runner* and select the *Dotnet CLI* -> *Graph Build* command. -Right-click the `ModularCrm.EntityFrameworkCore` package and select the *EF Core CLI* -> *Add Migration* command: +Right-click the `ModularCrm` package and select the *EF Core CLI* -> *Add Migration* command: ![abp-studio-add-entity-framework-core-migration](images/abp-studio-add-entity-framework-core-migration.png) @@ -222,7 +221,7 @@ The *Add Migration* command opens a new dialog to get a migration name: ![abp-studio-add-entity-framework-core-migration-dialog](images/abp-studio-add-entity-framework-core-migration-dialog.png) -Once you click the *OK* button, a new database migration class is added to the `Migrations` folder of the `ModularCrm.EntityFrameworkCore` project: +Once you click the *OK* button, a new database migration class is added to the `Migrations` folder of the `ModularCrm` project: ![visual-studio-new-migration-class](images/visual-studio-new-migration-class.png) @@ -369,7 +368,7 @@ For this application, we don't need to create HTTP API endpoints for the product * You can create a regular ASP.NET Core Controller class in the `ModularCrm.Products.HttpApi` project, inject `IProductAppService` and use it to create wrapper methods. We will do this later while we create the Ordering module. * Alternatively, you can use the ABP's [Auto API Controllers](../../framework/api-development/auto-controllers.md) feature to expose your application services as API controllers by conventions. We will do it here. -Open the `ModularCrmWebModule` class in the main application's solution (the `ModularCrm` solution), find the `PreConfigureServices` method and add the following lines inside that method: +Open the `ModularCrmModule` class in the main application's solution (the `ModularCrm` solution), find the `PreConfigureServices` method and add the following lines inside that method: ````csharp PreConfigure(mvcBuilder => @@ -385,8 +384,8 @@ Then open the `ConfigureAutoApiControllers` method of the same class and add a s ````csharp Configure(options => { - options.ConventionalControllers.Create(typeof(ModularCrmApplicationModule).Assembly); - + options.ConventionalControllers.Create(typeof(ModularCrmModule).Assembly); + //ADD THE FOLLOWING LINE: options.ConventionalControllers.Create(typeof(ProductsApplicationModule).Assembly); }); @@ -404,7 +403,7 @@ This section will create a few example products using the [Swagger UI](../../fra Now, right-click the `ModularCrm` under the `main` folder in the Solution Explorer panel and select the *Dotnet CLI* -> *Graph Build* command. This will ensure that the product module and the main application are built and ready to run. -After the build process completes, open the Solution Runner panel and click the *Play* button near the solution root. Once the `ModularCrm.Web` application runs, we can right-click it and select the *Browse* command to open the user interface. +After the build process completes, open the Solution Runner panel and click the *Play* button near the solution root. Once the `ModularCrm` application runs, we can right-click it and select the *Browse* command to open the user interface. Once you see the user interface of the web application, type `/swagger` at the end of the URL to open the Swagger UI. If you scroll down, you should see the `Products` API: @@ -486,7 +485,7 @@ Here, we simply use the `IProductAppService` to get a list of all products and a ```` -You can build the product module's .NET solution (`ModularCrm.Products`), then right-click the `ModularCrm.Web` application on ABP Studio's solution runner and select the *Build & Restart* command: +Right-click the `ModularCrm` application on ABP Studio's solution runner and select the *Start* command: ![abp-studio-build-and-restart-application](images/abp-studio-build-and-restart-application.png) diff --git a/docs/en/tutorials/modular-crm/part-04.md b/docs/en/tutorials/modular-crm/part-04.md index 1674ced6c6..ba406f7846 100644 --- a/docs/en/tutorials/modular-crm/part-04.md +++ b/docs/en/tutorials/modular-crm/part-04.md @@ -70,8 +70,6 @@ Select the `ModularCrm.Ordering` module and check the *Install this module* opti ![abp-studio-install-module-dialog](images/abp-studio-install-module-dialog.png) -Select the `ModuleCrm.Ordering` package from the left area and the `ModularCrm.Domain` package from the middle area. Then, select the `ModularCrm.Ordering.UI` package from the left area and the `ModularCrm.Web` package from the middle area, as shown in the preceding figure. Finally, click *OK*. - -> Since the Ordering module is not layered, we didn't install its packages to the layers of our main application. We are installing it only to `ModularCrm.Domain`. In this way, we can use the Ordering module from any layer of our application since `ModularCrm.Domain` is one of the core packages of our application. If you build your modules as non-layered and you don't have much code in the main application's .NET solution, you can also consider creating a non-layered main application that composes these modules. +Select the `ModuleCrm.Ordering` and `ModularCrm.Ordering.UI` packages from the left area and the `ModularCrm` package from the middle area as shown in the preceding figure. Finally, click *OK*. In this part of the tutorial, we've created a standard module. This allows you to create modules or applications with a different structure. In the next part, we will add functionality to the Ordering module. diff --git a/docs/en/tutorials/modular-crm/part-05.md b/docs/en/tutorials/modular-crm/part-05.md index 91e8b2cb37..a489548b65 100644 --- a/docs/en/tutorials/modular-crm/part-05.md +++ b/docs/en/tutorials/modular-crm/part-05.md @@ -28,7 +28,7 @@ Create an `Order` class to the `ModularCrm.Ordering` project (open an `Entities` ````csharp using System; -using ModularCrm.Ordering.Contracts.Enums; +using ModularCrm.Ordering.Enums; using Volo.Abp.Domain.Entities.Auditing; namespace ModularCrm.Ordering.Entities @@ -156,7 +156,7 @@ public static class OrderingDbContextModelCreatingExtensions #### Configuring the Main Application -Open the main application's solution in your IDE, find the `ModularCrmDbContext` class under the `ModularCrm.EntityFrameworkCore` project and follow the 3 steps below: +Open the main application's solution in your IDE, find the `ModularCrmDbContext` class under the `ModularCrm` project's `Data` folder, and follow the 3 steps below: **(1)** Add the following attribute on top of the `ModularCrmDbContext` class: @@ -171,8 +171,6 @@ The `ReplaceDbContext` attribute allows the use of the `ModularCrmDbContext` cla ````csharp public class ModularCrmDbContext : AbpDbContext, - ITenantManagementDbContext, - IIdentityDbContext, IProductsDbContext, IOrderingDbContext //NEW: IMPLEMENT THE INTERFACE { @@ -200,7 +198,7 @@ Now, we can add a new database migration. You can use Entity Framework Core's `A Ensure that the solution has built. You can right-click the `ModularCrm` (under the `main` folder) on ABP Studio *Solution Runner* and select the *Dotnet CLI* -> *Graph Build* command. -Right-click the `ModularCrm.EntityFrameworkCore` package and select the *EF Core CLI* -> *Add Migration* command: +Right-click the `ModularCrm` package and select the *EF Core CLI* -> *Add Migration* command: ![abp-studio-add-entity-framework-core-migration](images/abp-studio-add-entity-framework-core-migration.png) @@ -208,11 +206,11 @@ The *Add Migration* command opens a new dialog to get a migration name: ![abp-studio-entity-framework-core-add-migration-order](images/abp-studio-entity-framework-core-add-migration-order.png) -Once you click the *OK* button, a new database migration class is added to the `Migrations` folder of the `ModularCrm.EntityFrameworkCore` project: +Once you click the *OK* button, a new database migration class is added to the `Migrations` folder of the `ModularCrm` project: ![visual-studio-new-migration-class-2](images/visual-studio-new-migration-class-2.png) -Now, you can return to ABP Studio, right-click the `ModularCrm.EntityFrameworkCore` project and select the *EF Core CLI* -> *Update Database* command: +Now, you can return to ABP Studio, right-click the `ModularCrm` project and select the *EF Core CLI* -> *Update Database* command: ![abp-studio-entity-framework-core-update-database](images/abp-studio-entity-framework-core-update-database.png) @@ -320,7 +318,7 @@ namespace ModularCrm.Ordering.Services; public class OrderAppService : OrderingAppService, IOrderAppService { - private readonly IRepository _orderRepository; + private readonly IRepository _orderRepository; public OrderAppService(IRepository orderRepository) { @@ -347,14 +345,14 @@ public class OrderAppService : OrderingAppService, IOrderAppService } ```` -Open the `ModularCrmWebModule` class in the main application's solution (the `ModularCrm` solution), find the `ConfigureAutoApiControllers` method and add the following lines inside that method: +Open the `ModularCrmModule` class in the main application's solution (the `ModularCrm` solution), find the `ConfigureAutoApiControllers` method and add the following lines inside that method: ````csharp private void ConfigureAutoApiControllers() { Configure(options => { - options.ConventionalControllers.Create(typeof(ModularCrmApplicationModule).Assembly); + options.ConventionalControllers.Create(typeof(ModularCrmModule).Assembly); options.ConventionalControllers.Create(typeof(ProductsApplicationModule).Assembly); //ADD THE FOLLOWING LINE: @@ -369,7 +367,7 @@ This section will create a few example orders using the [Swagger UI](../../frame Now, right-click the `ModularCrm` under the `main` folder in the Solution Explorer panel and select the *Dotnet CLI* -> *Graph Build* command. This will ensure that the order module and the main application are built and ready to run. -After the build process completes, open the Solution Runner panel and click the *Play* button near the solution root. Once the `ModularCrm.Web` application runs, we can right-click it and select the *Browse* command to open the user interface. +After the build process completes, open the Solution Runner panel and click the *Play* button near the solution root. Once the `ModularCrm` application runs, we can right-click it and select the *Browse* command to open the user interface. Once you see the user interface of the web application, type `/swagger` at the end of the URL to open the Swagger UI. If you scroll down, you should see the `Orders` API: @@ -487,11 +485,11 @@ public class OrderingMenuContributor : IMenuContributor ### Building the Application -Now, we will run the application to see the result. Please stop the application if it is already running. Then open the *Solution Runner* panel, right-click the `ModularCrm.Web` application, and select the *Build* -> *Graph Build* command: +Now, we will run the application to see the result. Please stop the application if it is already running. Then open the *Solution Runner* panel, right-click the `ModularCrm` application, and select the *Build* -> *Graph Build* command: ![abp-studio-solution-runner-graph-build](images/abp-studio-solution-runner-graph-build.png) -We've performed a graph build since we've made a change on a module, and more than building the main application is needed. *Graph Build* command also builds the depended modules if necessary. Alternatively, you could build the Ordering module first (on ABP Studio or your IDE), then right-click the `ModularCrm.Web` application and select the *Run* -> *Build & Start*. This approach can be faster if you have too many modules and you make a change in one of the modules. Now you can run the application by right-clicking the `ModularCrm.Web` application and selecting the *Run* -> *Start* command. +We've performed a graph build since we've made a change on a module, and more than building the main application is needed. *Graph Build* command also builds the depended modules if necessary. Alternatively, you could build the Ordering module first (on ABP Studio or your IDE). This approach can be faster if you have too many modules and you make a change in one of the modules. Now you can run the application by right-clicking the `ModularCrm` application and selecting the *Start* command. ![abp-studio-browser-orders-menu-item](images/abp-studio-browser-orders-menu-item.png) diff --git a/docs/en/tutorials/modular-crm/part-06.md b/docs/en/tutorials/modular-crm/part-06.md index 36e4f22a64..f06baa93f0 100644 --- a/docs/en/tutorials/modular-crm/part-06.md +++ b/docs/en/tutorials/modular-crm/part-06.md @@ -162,7 +162,7 @@ namespace ModularCrm.Ordering.Services; public class OrderAppService : ApplicationService, IOrderAppService { - private readonly IRepository _orderRepository; + private readonly IRepository _orderRepository; private readonly IProductIntegrationService _productIntegrationService; public OrderAppService( diff --git a/docs/en/tutorials/modular-crm/part-07.md b/docs/en/tutorials/modular-crm/part-07.md index 510e5675b3..4bcace4239 100644 --- a/docs/en/tutorials/modular-crm/part-07.md +++ b/docs/en/tutorials/modular-crm/part-07.md @@ -19,7 +19,7 @@ Another common approach to communicating between modules is messaging. By publis ABP provides two types of event buses for loosely coupled communication: * [Local Event Bus](../../framework/infrastructure/event-bus/local/index.md) is suitable for in-process messaging. Since in a modular monolith, both of publisher and subscriber are in the same process, they can communicate in-process, without needing an external message broker. -* **[Distributed Event Bus](../../framework/infrastructure/event-bus/distributed/index.md)** is normal for inter-process messaging, like microservices, for publishing and subscribing to distributed events. However, ABP's distributed event bus works as local (in-process) by default (actually, it uses the Local Event Bus under the hood by default) unless you configure an external message broker. +* [Distributed Event Bus](../../framework/infrastructure/event-bus/distributed/index.md) is normal for inter-process messaging, like microservices, for publishing and subscribing to distributed events. However, ABP's distributed event bus works as local (in-process) by default (actually, it uses the Local Event Bus under the hood by default) unless you configure an external message broker. If you consider converting your modular monolith to a microservice system later, it is best to use the Distributed Event Bus with default local/in-process implementation. It already supports database-level transactional event execution and has no performance penalty. If you switch to an external provider ([RabbitMQ](../../framework/infrastructure/event-bus/distributed/rabbitmq.md), [Kafka](../../framework/infrastructure/event-bus/distributed/kafka.md), etc.), you don't need to change your application code. @@ -74,7 +74,7 @@ namespace ModularCrm.Ordering.Services; public class OrderAppService : OrderingAppService, IOrderAppService { - private readonly IRepository _orderRepository; + private readonly IRepository _orderRepository; private readonly IProductIntegrationService _productIntegrationService; private readonly IDistributedEventBus _distributedEventBus; @@ -218,7 +218,7 @@ We inject the product repository and update the stock count in the event handler To keep this tutorial more focused, we will not create a UI for creating an order. You can easily create a form to create an order on your user interface. In this section, we will test it just using the Swagger UI. -Graph build the `ModularCrm.Web` application, run it on the ABP Studio's *Solution Runner* panel and browse the application UI as demonstrated earlier. +Graph build the `ModularCrm` application, run it on the ABP Studio's *Solution Runner* panel and browse the application UI as demonstrated earlier. Once the application is running and ready, manually type `/swagger` to the end of the URL and press the ENTER key. You should see the Swagger UI that is used to discover and test your HTTP APIs: @@ -228,8 +228,8 @@ Find the *Orders* API, click the *Try it out* button, enter a sample value the t ````json { - "productId": "0fbf7dd0-d7e9-0d18-9214-3a14d9fa1b74", - "customerName": "David" + "customerName": "David", + "productId": "e6ce1629-cfb1-1af6-e71c-3a16f10f9cc5" } ```` diff --git a/docs/en/tutorials/modular-crm/part-08.md b/docs/en/tutorials/modular-crm/part-08.md index 20efa3d7bd..6291875d5c 100644 --- a/docs/en/tutorials/modular-crm/part-08.md +++ b/docs/en/tutorials/modular-crm/part-08.md @@ -43,7 +43,7 @@ We will define the `IOrderReportingAppService` interface in the `ModularCrm.Appl As the first step, we should reference the `ModularCrm.Ordering.Contracts` package (of the `ModularCrm.Ordering` module) since we will reuse the `OrderState` enum defined in that package. -Open the ABP Studio's *Solution Explorer* panel, right-click the `ModularCrm.Application.Contracts` package and select the *Add Package Reference* command: +Open the ABP Studio's *Solution Explorer* panel, right-click the `ModularCrm` package and select the *Add Package Reference* command: ![abp-studio-add-package-reference-5](images/abp-studio-add-package-reference-5.png) @@ -55,7 +55,7 @@ The package reference has been added, and we can now use the types in the `Modul #### Defining the `IOrderReportingAppService` Interface -Open the main `ModularCrm` .NET solution in your IDE, find the `ModularCrm.Application.Contracts` project, create an `Orders` folder and add an `IOrderReportingAppService` interface. Here is the definition of that interface: +Open the main `ModularCrm` .NET solution in your IDE, create an `Orders` folder under the `Services` folder and add an `IOrderReportingAppService` interface. Here is the definition of that interface: ````csharp using System.Collections.Generic; @@ -71,7 +71,7 @@ namespace ModularCrm.Orders } ```` -We have a single method, `GetLatestOrders`, that will return a list of the latest orders. We should also define the `OrderReportDto` class that that method returns. Create the following class in the same `Orders` folder: +We have a single method, `GetLatestOrders`, that will return a list of the latest orders. We should also define the `OrderReportDto` class that that method returns. Create the `Orders` folder under the `Services/Dtos` folder and create a class named `OrderReportDto`. ````csharp using System; @@ -93,7 +93,7 @@ namespace ModularCrm.Orders } ```` -`OrderReportDto` contains data from both the `Order` and `Product` entities. We could use the `OrderState` since we have a reference to the package that defines that enum. +`OrderReportDto` contains data from both the `Order` and `Product` entities. We could use the `OrderState` since we have a reference to the package that defines that enum. After adding these files, the final folder structure should be like this: @@ -101,9 +101,7 @@ After adding these files, the final folder structure should be like this: ### Implementing the `OrderReportingAppService` Class -Create an `Orders` folder inside the `ModularCrm.Application` project and add a class named `OrderReportingAppService` inside it. The final folder structure should be like this: - -![visual-studio-order-reporting-app-service-impl](images/visual-studio-order-reporting-app-service-impl.png) +Create a class named `OrderReportingAppService` under the `Services/Orders` folder. Open the `OrderReportingAppService.cs` file and change its content by the following code block: @@ -176,7 +174,7 @@ Open the ABP Studio UI, stop the application if it is running, build and run it Here, find the `OrderReporting` API and execute it as shown above. You should get the order objects with product names. -Alternatively, you can visit the `/api/app/order-reporting/latest-orders` URL to directly execute the HTTP API on the browser (you should write the full URL, like `https://localhost:44358/api/app/order-reporting/latest-orders` - port can be different for your case) +Alternatively, you can visit the `/api/app/order-reporting/latest-orders` URL to directly execute the HTTP API on the browser (you should write the full URL, like `https://localhost:44303/api/app/order-reporting/latest-orders` - port can be different for your case) ## Summary @@ -190,7 +188,7 @@ Now, you know the fundamental principles and mechanics of building sophisticated ## Download the Source Code -You can download the completed sample solution [here](https://github.com/abpframework/abp-samples/tree/master/ModularCRM). +You can download the completed sample solution [here](https://github.com/abpframework/abp-samples/tree/master/ModularCrm). ## See Also diff --git a/docs/en/tutorials/todo/layered/index.md b/docs/en/tutorials/todo/layered/index.md index a67076dad1..5ba38380f3 100644 --- a/docs/en/tutorials/todo/layered/index.md +++ b/docs/en/tutorials/todo/layered/index.md @@ -3,7 +3,7 @@ ````json //[doc-params] { - "UI": ["MVC", "Blazor", "BlazorServer", "BlazorWebApp" ,"NG"], + "UI": ["MVC", "Blazor", "BlazorServer", "BlazorWebApp" ,"NG", "MAUIBlazor"], "DB": ["EF", "Mongo"] } ```` @@ -52,8 +52,8 @@ This documentation has a video tutorial on **YouTube**!! You can watch it here: ## Pre-Requirements -* An IDE (e.g. [Visual Studio](https://visualstudio.microsoft.com/vs/)) that supports [.NET 8.0+](https://dotnet.microsoft.com/download/dotnet) development. -* [Node v18.19+](https://nodejs.org/) +* An IDE (e.g. [Visual Studio](https://visualstudio.microsoft.com/vs/)) that supports [.NET 9.0+](https://dotnet.microsoft.com/download/dotnet) development. +* [Node v20.11+](https://nodejs.org/) {{if DB=="Mongo"}} @@ -74,7 +74,7 @@ dotnet tool install -g Volo.Abp.Studio.Cli Create an empty folder, open a command-line terminal and execute the following command in the terminal: ````bash -abp new TodoApp{{if UI=="Blazor"}} -u blazor{{else if UI=="BlazorServer"}} -u blazor-server{{else if UI=="BlazorWebApp"}} -u blazor-webapp{{else if UI=="NG"}} -u angular{{end}}{{if DB=="Mongo"}} -d mongodb{{end}} +abp new TodoApp{{if UI=="Blazor"}} -u blazor{{else if UI=="BlazorServer"}} -u blazor-server{{else if UI=="BlazorWebApp"}} -u blazor-webapp{{else if UI=="NG"}} -u angular{{else if UI=="MAUIBlazor"}} -u maui-blazor{{end}}{{if DB=="Mongo"}} -d mongodb{{end}} ```` {{if UI=="NG"}} @@ -111,9 +111,9 @@ For such cases, run the `abp install-libs` command on the root directory of your abp install-libs ```` -> We suggest you install [Yarn](https://classic.yarnpkg.com/) to prevent possible package inconsistencies, if you haven't installed it yet. +> We suggest you install [Yarn v1.22+ (not v2)](https://classic.yarnpkg.com/en/docs/install) to prevent possible package inconsistencies, if you haven't installed it yet. -{{if UI=="Blazor" || UI=="BlazorWebApp"}} +{{if UI=="Blazor" || UI=="BlazorWebApp" || UI=="MAUIBlazor"}} #### Bundling and Minification @@ -136,12 +136,16 @@ abp bundle It is good to run the application before starting the development. Ensure the {{if UI=="BlazorServer"}}`TodoApp.Blazor`{{else}}`TodoApp.Web`{{end}} project is the startup project, then run the application (Ctrl+F5 in Visual Studio) to see the initial UI: -{{else if UI=="Blazor"}} +{{else if UI=="Blazor" || UI=="MAUIBlazor"}} It is good to run the application before starting the development. The solution has two main applications; * `TodoApp.HttpApi.Host` hosts the server-side HTTP API. +{{if UI=="Blazor"}} * `TodoApp.Blazor` is the client-side Blazor WebAssembly application. +{{else if UI=="MAUIBlazor"}} +* `TodoApp.MauiBlazor` is the MAUI Blazor application. +{{end}} Ensure the `TodoApp.HttpApi.Host` project is the startup project, then run the application (Ctrl+F5 in Visual Studio) to see the server-side HTTP API on the [Swagger UI](https://swagger.io/tools/swagger-ui/): @@ -253,7 +257,7 @@ You can apply changes to the database using the following command, in the same c dotnet ef database update ```` -> If you are using Visual Studio, you may want to use the `Add-Migration Added_TodoItem` and `Update-Database` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`TodoApp.Web`{{else if UI=="BlazorServer" || UI=="Blazor" || UI=="BlazorWebApp"}}`TodoApp.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`TodoApp.HttpApi.Host`{{end}} is the startup project and `TodoApp.EntityFrameworkCore` is the *Default Project* in PMC. +> If you are using Visual Studio, you may want to use the `Add-Migration Added_TodoItem` and `Update-Database` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`TodoApp.Web`{{else if UI=="BlazorServer" || UI=="Blazor" || UI=="BlazorWebApp"}}`TodoApp.Blazor`{{else if UI=="Blazor" || UI=="NG" || UI=="MAUIBlazor"}}`TodoApp.HttpApi.Host`{{end}} is the startup project and `TodoApp.EntityFrameworkCore` is the *Default Project* in PMC. {{else if DB=="Mongo"}} @@ -582,11 +586,11 @@ If you open the [Swagger UI](https://swagger.io/tools/swagger-ui/) by entering t ![todo-api](../images/todo-api.png) -{{else if UI=="Blazor" || UI=="BlazorServer" || UI=="BlazorWebApp"}} +{{else if UI=="Blazor" || UI=="BlazorServer" || UI=="BlazorWebApp" || UI=="MAUIBlazor"}} ### Index.razor.cs -Open the `Index.razor.cs` file in the `Pages` folder of the {{if UI=="Blazor" || UI=="BlazorWebApp"}} *TodoApp.Blazor.Client* {{else}}*TodoApp.Blazor*{{end}} project and replace the content with the following code block: +Open the `Index.razor.cs` file in the `Pages` folder of the {{if UI=="Blazor" || UI=="BlazorWebApp"}} *TodoApp.Blazor.Client* {{else if UI=="BlazorServer"}} *TodoApp.Blazor* {{else if UI=="MAUIBlazor"}} *TodoApp.MauiBlazor* {{end}} project and replace the content with the following code block: ```csharp using Microsoft.AspNetCore.Components; @@ -627,7 +631,7 @@ namespace TodoApp.Blazor.Pages This class uses `ITodoAppService` to perform operations for the todo items. It manipulates the `TodoItems` list after create and delete operations. This way, we don't need to refresh the whole todo list from the server. -{{if UI=="Blazor"}} +{{if UI=="Blazor" || UI=="MAUIBlazor"}} See the *Dynamic C# Proxies & Auto API Controllers* section below to learn how we could inject and use the application service interface from the Blazor application which is running on the browser! But now, let's continue and complete the application. @@ -635,7 +639,7 @@ See the *Dynamic C# Proxies & Auto API Controllers* section below to learn how w ### Index.razor -Open the `Index.razor` file in the `Pages` folder of the {{if UI=="Blazor" || UI=="BlazorWebApp"}} *TodoApp.Blazor.Client* {{else}} *TodoApp.Blazor* {{end}} project and replace the content with the following code block: +Open the `Index.razor` file in the `Pages` folder of the {{if UI=="Blazor" || UI=="BlazorWebApp"}} *TodoApp.Blazor.Client* {{else if UI=="BlazorServer"}} *TodoApp.Blazor* {{else if UI=="MAUIBlazor"}} *TodoApp.MauiBlazor* {{end}} project and replace the content with the following code block: ```xml @page "/" @@ -677,7 +681,7 @@ Open the `Index.razor` file in the `Pages` folder of the {{if UI=="Blazor" || UI ### Index.razor.css -As the final touch, open the `Index.razor.css` file in the `Pages` folder of the {{if UI=="Blazor" || UI=="BlazorWebApp"}}*TodoApp.Blazor.Client*{{else}}*TodoApp.Blazor*{{end}} project and replace it with the following content: +As the final touch, open the `Index.razor.css` file in the `Pages` folder of the {{if UI=="Blazor" || UI=="BlazorWebApp"}}*TodoApp.Blazor.Client*{{else if UI=="BlazorServer"}} *TodoApp.Blazor* {{else if UI=="MAUIBlazor"}} *TodoApp.MauiBlazor* {{end}} project and replace it with the following content: ```css #TodoList{ @@ -710,7 +714,7 @@ This is a simple styling for the todo page. We believe that you can do much bett Now, you can run the application again to see the result. -{{if UI=="Blazor"}} +{{if UI=="Blazor" || UI=="MAUIBlazor"}} ### Dynamic C# Proxies & Auto API Controllers diff --git a/docs/en/tutorials/todo/single-layer/index.md b/docs/en/tutorials/todo/single-layer/index.md index 3b4f755c70..c96c3715ef 100644 --- a/docs/en/tutorials/todo/single-layer/index.md +++ b/docs/en/tutorials/todo/single-layer/index.md @@ -48,8 +48,8 @@ This documentation has a video tutorial on **YouTube**!! You can watch it here: ## Pre-Requirements -* An IDE (e.g. [Visual Studio](https://visualstudio.microsoft.com/vs/)) that supports [.NET 8.0+](https://dotnet.microsoft.com/download/dotnet) development. -* [Node v18.19+](https://nodejs.org/) +* An IDE (e.g. [Visual Studio](https://visualstudio.microsoft.com/vs/)) that supports [.NET 9.0+](https://dotnet.microsoft.com/download/dotnet) development. +* [Node v20.11+](https://nodejs.org/) {{if DB=="Mongo"}} @@ -113,7 +113,7 @@ Run the `abp install-libs` command on the root directory of your solution to ins abp install-libs ``` -> We suggest you install [Yarn](https://classic.yarnpkg.com/) to prevent possible package inconsistencies, if you haven't installed it yet. +> We suggest you install [Yarn v1.22+ (not v2)](https://classic.yarnpkg.com/en/docs/install) to prevent possible package inconsistencies, if you haven't installed it yet. {{if UI=="Blazor" || UI=="BlazorServer"}} diff --git a/docs/en/ui-themes/basic-theme/index.md b/docs/en/ui-themes/basic-theme/index.md new file mode 100644 index 0000000000..f1d1a3d39c --- /dev/null +++ b/docs/en/ui-themes/basic-theme/index.md @@ -0,0 +1,17 @@ +# Basic Theme + +The Basic Theme is a minimalist theme that doesn't add any styling on top of the plain [Bootstrap](https://getbootstrap.com/) styles. You can take the Basic Theme as the base theme and build your own theme or styling on top of it. Here, a screenshot from the theme: + +![basic-theme-application-layout](../../images/basic-theme-application-layout.png) + + +See the [Theming document](../../framework/ui/mvc-razor-pages/theming.md) to learn about themes. + +## User Interfaces + +The Basic Theme has implementation for the following UI types: + +- [MVC UI](../../framework/ui/mvc-razor-pages/basic-theme.md) +- [Blazor UI](../../framework/ui/blazor/basic-theme.md) +- [Angular UI](../../framework/ui/angular/basic-theme.md) + diff --git a/docs/en/ui-themes/lepton-x/blazor.md b/docs/en/ui-themes/lepton-x/blazor.md index ef7ad683fb..78e0dc433d 100644 --- a/docs/en/ui-themes/lepton-x/blazor.md +++ b/docs/en/ui-themes/lepton-x/blazor.md @@ -262,7 +262,7 @@ If you need to replace the component, you can follow the steps below. * Create a razor page, like `MyBreadcrumbs.razor`, in your blazor application as shown below: ```html -@Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.Common; +@using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.Common; @using Volo.Abp.DependencyInjection @inherits Breadcrumbs @@ -295,7 +295,7 @@ namespace LeptonXLite.DemoApp.Blazor.MyComponents * Create a razor page, like `MyContentToolbar.razor`, in your blazor application as shown below: ```html -@Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.Common; +@using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.Common; @using Volo.Abp.DependencyInjection @inherits ContentToolbar @@ -328,7 +328,7 @@ namespace LeptonXLite.DemoApp.Blazor.MyComponents * Create a razor page, like `MyGeneralSettings.razor`, in your blazor application as shown below: ```html -@Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.Common; +@using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.Common; @using Volo.Abp.DependencyInjection @inherits GeneralSettings @@ -361,7 +361,7 @@ namespace LeptonXLite.DemoApp.Blazor.MyComponents * Create a razor page, like `MyMobileGeneralSettings.razor`, in your blazor application as shown below: ```html -@Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.Common; +@using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.Common; @using Volo.Abp.DependencyInjection @inherits MobileGeneralSettings @@ -400,7 +400,7 @@ Components used in the side menu layout. * Create a razor page, like `MyMainMenu.razor`, in your blazor application as shown below: ```html -@Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.TopMenu.Navigation; +@using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.TopMenu.Navigation; @using Volo.Abp.DependencyInjection @inherits MainMenu @@ -431,7 +431,7 @@ namespace LeptonXLite.DemoApp.Blazor.MyComponents * Create a razor page, like `MyMainMenuItem.razor`, in your blazor application as shown below: ```html -@Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.SideMenu.Navigation; +@using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.SideMenu.Navigation; @using Volo.Abp.DependencyInjection @inherits MainMenuItem @@ -464,7 +464,7 @@ namespace LeptonXLite.DemoApp.Blazor.MyComponents * Create a razor page, like `MyMobileNavbar.razor`, in your blazor application as shown below: ```html -@Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.SideMenu.Navigation; +@using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.SideMenu.Navigation; @using Volo.Abp.DependencyInjection @inherits MobileNavbar @@ -497,7 +497,7 @@ namespace LeptonXLite.DemoApp.Blazor.MyComponents * Create a razor page, like `MyMainHeader.razor`, in your blazor application as shown below: ```html -@Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.SideMenu.MainHeader +@using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.SideMenu.MainHeader @using Volo.Abp.DependencyInjection @inherits MainHeader @@ -534,7 +534,7 @@ If you need to replace the component, you can follow the steps below. * Create a razor page, like `MyMainHeaderBranding.razor`, in your blazor application as shown below: ```html -@Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.SideMenu.MainHeader +@using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.SideMenu.MainHeader @using Volo.Abp.DependencyInjection @inherits MainHeaderBranding @@ -571,7 +571,7 @@ If you need to replace the component, you can follow the steps below. * Create a razor page, like `MyMainHeaderToolbar.razor`, in your blazor application as shown below: ```html -@Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.SideMenu.MainHeader +@using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.SideMenu.MainHeader @using Volo.Abp.DependencyInjection @inherits MainHeaderToolbar @@ -610,7 +610,7 @@ Components used in the top menu layout. * Create a razor page, like `MyMainMenu.razor`, in your blazor application as shown below: ```html -@Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.TopMenu.Navigation; +@using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.TopMenu.Navigation; @using Volo.Abp.DependencyInjection @inherits MainMenu @@ -641,7 +641,7 @@ namespace LeptonXLite.DemoApp.Blazor.MyComponents * Create a razor page, like `MyMainMenuItem.razor`, in your blazor application as shown below: ```html -@Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.TopMenu.Navigation; +@using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.TopMenu.Navigation; @using Volo.Abp.DependencyInjection @inherits MainMenuItem @@ -674,7 +674,7 @@ namespace LeptonXLite.DemoApp.Blazor.MyComponents * Create a razor page, like `MyMobileNavbar.razor`, in your blazor application as shown below: ```html -@Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.TopMenu.Navigation; +@using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.TopMenu.Navigation; @using Volo.Abp.DependencyInjection @inherits MobileNavbar @@ -707,7 +707,7 @@ namespace LeptonXLite.DemoApp.Blazor.MyComponents * Create a razor page, like `MyMainHeader.razor`, in your blazor application as shown below: ```html -@Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.TopMenu.MainHeader +@using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.TopMenu.MainHeader @using Volo.Abp.DependencyInjection @inherits MainHeader @@ -742,7 +742,7 @@ Application branding can be customized with the `IBrandingProvider`. See the [Br * Create a razor page, like `MyMainHeaderBranding.razor`, in your blazor application as shown below: ```html -@Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.TopMenu.MainHeader +@using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.TopMenu.MainHeader @using Volo.Abp.DependencyInjection @inherits MainHeaderBranding @@ -779,7 +779,7 @@ If you need to replace the component, you can follow the steps below. * Create a razor page, like `MyMainHeaderToolbar.razor`, in your blazor application as shown below: ```html -@Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.TopMenu.MainHeader +@using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.TopMenu.MainHeader @using Volo.Abp.DependencyInjection @inherits MainHeaderToolbar diff --git a/docs/en/ui-themes/lepton-x/index.md b/docs/en/ui-themes/lepton-x/index.md index 241d4dfb89..e2b09358a4 100644 --- a/docs/en/ui-themes/lepton-x/index.md +++ b/docs/en/ui-themes/lepton-x/index.md @@ -88,6 +88,8 @@ You can use the following CLI command to download the source-code: abp get-source Volo.Abp.LeptonXTheme ``` +> If you are using the old ABP CLI, you can use the command: `abp get-source Volo.Abp.LeptonXTheme.Pro` + If you want to download the source code of the preview version, you can use the following command: ```bash diff --git a/docs/en/ui-themes/lepton-x/mvc.md b/docs/en/ui-themes/lepton-x/mvc.md index 4627ccb727..a317e8b9ed 100644 --- a/docs/en/ui-themes/lepton-x/mvc.md +++ b/docs/en/ui-themes/lepton-x/mvc.md @@ -81,6 +81,16 @@ Configure(options => > > If your layout is **TopMenu**, then you have to add them under the **wwwroot/Themes/LeptonX/Global/top-menu/css/** folder. + +#### Handling Style Changes + +You can add extra logic by using javascript API when style is changed with the following event. +```js +leptonx.CSSLoadEvent.on(event =>{ + console.log("Style is changed from " + event.detail.previousTheme + " to "+ event.detail.theme); +}); +``` + --- ### LeptonXThemeMvcOptions diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index ca1a85fd8d..c3ca5b63b2 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -467,12 +467,19 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.RemoteServices.Tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Abstractions", "src\Volo.Abp.AspNetCore.Abstractions\Volo.Abp.AspNetCore.Abstractions.csproj", "{E1051CD0-9262-4869-832D-B951723F4DDE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling", "src\Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling\Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling.csproj", "{2F9BA650-395C-4BE0-8CCB-9978E753562A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling", "src\Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling\Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling.csproj", "{7ADB6D92-82CC-4A2A-8BCF-FC6C6308796D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Google", "src\Volo.Abp.BlobStoring.Google\Volo.Abp.BlobStoring.Google.csproj", "{DEEB5200-BBF9-464D-9B7E-8FC035A27E94}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Google.Tests", "test\Volo.Abp.BlobStoring.Google.Tests\Volo.Abp.BlobStoring.Google.Tests.csproj", "{40FB8907-9CF7-44D0-8B5F-538AC6DAF8B9}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.ExceptionHandling.Tests", "test\Volo.Abp.ExceptionHandling.Tests\Volo.Abp.ExceptionHandling.Tests.csproj", "{E50739A7-5E2F-4EB5-AEA9-554115CB9613}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Sms.TencentCloud", "src\Volo.Abp.Sms.TencentCloud\Volo.Abp.Sms.TencentCloud.csproj", "{BE7109C5-7368-4688-8557-4A15D3F4776A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Sms.TencentCloud.Tests", "test\Volo.Abp.Sms.TencenCloud.Tests\Volo.Abp.Sms.TencentCloud.Tests.csproj", "{C753DDD6-5699-45F8-8669-08CE0BB816DE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1399,6 +1406,14 @@ Global {E1051CD0-9262-4869-832D-B951723F4DDE}.Debug|Any CPU.Build.0 = Debug|Any CPU {E1051CD0-9262-4869-832D-B951723F4DDE}.Release|Any CPU.ActiveCfg = Release|Any CPU {E1051CD0-9262-4869-832D-B951723F4DDE}.Release|Any CPU.Build.0 = Release|Any CPU + {2F9BA650-395C-4BE0-8CCB-9978E753562A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F9BA650-395C-4BE0-8CCB-9978E753562A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F9BA650-395C-4BE0-8CCB-9978E753562A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F9BA650-395C-4BE0-8CCB-9978E753562A}.Release|Any CPU.Build.0 = Release|Any CPU + {7ADB6D92-82CC-4A2A-8BCF-FC6C6308796D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7ADB6D92-82CC-4A2A-8BCF-FC6C6308796D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7ADB6D92-82CC-4A2A-8BCF-FC6C6308796D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7ADB6D92-82CC-4A2A-8BCF-FC6C6308796D}.Release|Any CPU.Build.0 = Release|Any CPU {DEEB5200-BBF9-464D-9B7E-8FC035A27E94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DEEB5200-BBF9-464D-9B7E-8FC035A27E94}.Debug|Any CPU.Build.0 = Debug|Any CPU {DEEB5200-BBF9-464D-9B7E-8FC035A27E94}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1411,6 +1426,14 @@ Global {E50739A7-5E2F-4EB5-AEA9-554115CB9613}.Debug|Any CPU.Build.0 = Debug|Any CPU {E50739A7-5E2F-4EB5-AEA9-554115CB9613}.Release|Any CPU.ActiveCfg = Release|Any CPU {E50739A7-5E2F-4EB5-AEA9-554115CB9613}.Release|Any CPU.Build.0 = Release|Any CPU + {BE7109C5-7368-4688-8557-4A15D3F4776A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE7109C5-7368-4688-8557-4A15D3F4776A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE7109C5-7368-4688-8557-4A15D3F4776A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE7109C5-7368-4688-8557-4A15D3F4776A}.Release|Any CPU.Build.0 = Release|Any CPU + {C753DDD6-5699-45F8-8669-08CE0BB816DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C753DDD6-5699-45F8-8669-08CE0BB816DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C753DDD6-5699-45F8-8669-08CE0BB816DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C753DDD6-5699-45F8-8669-08CE0BB816DE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1646,9 +1669,13 @@ Global {DFAF8763-D1D6-4EB4-B459-20E31007FE2F} = {447C8A77-E5F0-4538-8687-7383196D04EA} {DACD4485-61BE-4DE5-ACAE-4FFABC122500} = {447C8A77-E5F0-4538-8687-7383196D04EA} {E1051CD0-9262-4869-832D-B951723F4DDE} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {2F9BA650-395C-4BE0-8CCB-9978E753562A} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {7ADB6D92-82CC-4A2A-8BCF-FC6C6308796D} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {DEEB5200-BBF9-464D-9B7E-8FC035A27E94} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {40FB8907-9CF7-44D0-8B5F-538AC6DAF8B9} = {447C8A77-E5F0-4538-8687-7383196D04EA} {E50739A7-5E2F-4EB5-AEA9-554115CB9613} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {BE7109C5-7368-4688-8557-4A15D3F4776A} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {C753DDD6-5699-45F8-8669-08CE0BB816DE} = {447C8A77-E5F0-4538-8687-7383196D04EA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/AbpAspNetCoreComponentsMauiBlazorThemingModule.cs b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/AbpAspNetCoreComponentsMauiBlazorThemingModule.cs new file mode 100644 index 0000000000..b0f6bc93f1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/AbpAspNetCoreComponentsMauiBlazorThemingModule.cs @@ -0,0 +1,34 @@ +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling; + +[DependsOn( + typeof(AbpAspNetCoreMvcUiBundlingAbstractionsModule) +)] +public class AbpAspNetCoreComponentsMauiBlazorThemingBundlingModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.GlobalAssets.Enabled = true; + options.GlobalAssets.GlobalStyleBundleName = MauiBlazorStandardBundles.Styles.Global; + options.GlobalAssets.GlobalScriptBundleName = MauiBlazorStandardBundles.Scripts.Global; + + options + .StyleBundles + .Add(MauiBlazorStandardBundles.Styles.Global, bundle => + { + bundle.AddContributors(typeof(MauiStyleContributor)); + }); + + options + .ScriptBundles + .Add(MauiBlazorStandardBundles.Scripts.Global, bundle => + { + bundle.AddContributors(typeof(MauiScriptContributor)); + }); + }); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/FodyWeavers.xml b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/FodyWeavers.xsd b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/MauiBlazorStandardBundles.cs b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/MauiBlazorStandardBundles.cs new file mode 100644 index 0000000000..645b2118c9 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/MauiBlazorStandardBundles.cs @@ -0,0 +1,14 @@ +namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling; + +public class MauiBlazorStandardBundles +{ + public static class Styles + { + public static string Global = "MauiBlazor.Global"; + } + + public static class Scripts + { + public static string Global = "MauiBlazor.Global"; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/MauiScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/MauiScriptContributor.cs new file mode 100644 index 0000000000..34cda82bc8 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/MauiScriptContributor.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling; + +public class MauiScriptContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/abp.js"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/lang-utils.js"); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/MauiStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/MauiStyleContributor.cs new file mode 100644 index 0000000000..e5ae6947de --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/MauiStyleContributor.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling; + +public class MauiStyleContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming/libs/bootstrap/css/bootstrap.min.css"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming/libs/fontawesome/css/all.css"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/css/abp.css"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming/libs/flag-icon/css/flag-icon.css"); + context.Files.AddIfNotContains("_content/Blazorise/blazorise.css"); + context.Files.AddIfNotContains("_content/Blazorise.Bootstrap5/blazorise.bootstrap5.css"); + context.Files.AddIfNotContains("_content/Blazorise.Snackbar/blazorise.snackbar.css"); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling.csproj b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling.csproj new file mode 100644 index 0000000000..e12285964f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling.csproj @@ -0,0 +1,16 @@ + + + + + + + net9.0 + enable + Nullable + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming/AbpAspNetCoreComponentsMauiBlazorThemingModule.cs b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming/AbpAspNetCoreComponentsMauiBlazorThemingModule.cs index 82a05dadb5..9ba85ebe7e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming/AbpAspNetCoreComponentsMauiBlazorThemingModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming/AbpAspNetCoreComponentsMauiBlazorThemingModule.cs @@ -1,9 +1,11 @@ -using Volo.Abp.AspNetCore.Components.Web.Theming; +using Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling; +using Volo.Abp.AspNetCore.Components.Web.Theming; using Volo.Abp.Modularity; namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Theming; [DependsOn( + typeof(AbpAspNetCoreComponentsMauiBlazorThemingBundlingModule), typeof(AbpAspNetCoreComponentsWebThemingModule), typeof(AbpAspNetCoreComponentsMauiBlazorModule) )] diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming/ComponentsComponentsBundleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming/ComponentsComponentsBundleContributor.cs index 5e03821176..6a218b08f0 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming/ComponentsComponentsBundleContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming/ComponentsComponentsBundleContributor.cs @@ -1,7 +1,9 @@ -using Volo.Abp.Bundling; +using System; +using Volo.Abp.Bundling; namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Theming; +[Obsolete("This class is obsolete and will be removed in the future versions. Use GlobalAssets instead.")] public class ComponentsComponentsBundleContributor : IBundleContributor { public void AddScripts(BundleContext context) diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.csproj b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.csproj index d3a900f185..80322f16de 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.csproj @@ -10,6 +10,7 @@
+ diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/AbpAspNetCoreComponentsServerModule.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/AbpAspNetCoreComponentsServerModule.cs index 501fcc0b9f..59a630175f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/AbpAspNetCoreComponentsServerModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/AbpAspNetCoreComponentsServerModule.cs @@ -2,7 +2,6 @@ using System.Net; using System.Net.Http; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting.StaticWebAssets; using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; @@ -31,7 +30,6 @@ public class AbpAspNetCoreComponentsServerModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - StaticWebAssetsLoader.UseStaticWebAssets(context.Services.GetHostingEnvironment(), context.Services.GetConfiguration()); context.Services.AddHttpClient(nameof(BlazorServerLookupApiRequestService)) .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpScripts.razor b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpScripts.razor index 02db9f2e30..8c9d070764 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpScripts.razor +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpScripts.razor @@ -1,8 +1,4 @@ -@using Volo.Abp -@implements IDisposable @inject IComponentBundleManager BundleManager -@inject PersistentComponentState PersistentComponentState - @if (ScriptFiles != null) { foreach (var file in ScriptFiles) @@ -20,37 +16,18 @@ private List? ScriptFiles { get; set; } - private PersistingComponentStateSubscription persistingSubscription; - protected override async Task OnInitializedAsync() { - if (BundleName == null) - { - throw new AbpException("The BundleName parameter of the AbpScripts component can not be null!"); - } - - persistingSubscription = PersistentComponentState.RegisterOnPersisting(PersistScriptFiles); + ScriptFiles = new List(); - if (PersistentComponentState.TryTakeFromJson>(nameof(ScriptFiles), out var restoredStyleFiles)) - { - ScriptFiles = restoredStyleFiles; - } - else + if (!BundleName.IsNullOrWhiteSpace()) { ScriptFiles = (await BundleManager.GetScriptBundleFilesAsync(BundleName!)).ToList(); } - if (WebAssemblyScriptFiles != null) + if (OperatingSystem.IsBrowser() && WebAssemblyScriptFiles != null) { - ScriptFiles?.AddRange(WebAssemblyScriptFiles); + ScriptFiles.AddIfNotContains(WebAssemblyScriptFiles); } } - - private Task PersistScriptFiles() - { - PersistentComponentState.PersistAsJson(nameof(ScriptFiles), ScriptFiles); - return Task.CompletedTask; - } - - public void Dispose() => persistingSubscription.Dispose(); } diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpStyles.razor b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpStyles.razor index bae9b382f3..bdcfd26bec 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpStyles.razor +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpStyles.razor @@ -1,8 +1,4 @@ -@using Volo.Abp -@implements IDisposable @inject IComponentBundleManager BundleManager -@inject PersistentComponentState PersistentComponentState - @if (StyleFiles != null) { foreach (var file in StyleFiles) @@ -20,37 +16,18 @@ private List? StyleFiles { get; set; } - private PersistingComponentStateSubscription persistingSubscription; - protected override async Task OnInitializedAsync() { - if (BundleName == null) - { - throw new AbpException("The BundleName parameter of the AbpStyles component can not be null!"); - } - - persistingSubscription = PersistentComponentState.RegisterOnPersisting(PersistStyleFiles); + StyleFiles = new List(); - if (PersistentComponentState.TryTakeFromJson>(nameof(StyleFiles), out var restoredStyleFiles)) - { - StyleFiles = restoredStyleFiles; - } - else + if (!BundleName.IsNullOrWhiteSpace()) { StyleFiles = (await BundleManager.GetStyleBundleFilesAsync(BundleName!)).ToList(); } if (OperatingSystem.IsBrowser() && WebAssemblyStyleFiles != null) { - StyleFiles?.AddRange(WebAssemblyStyleFiles); + StyleFiles.AddIfNotContains(WebAssemblyStyleFiles); } } - - private Task PersistStyleFiles() - { - PersistentComponentState.PersistAsJson(nameof(StyleFiles), StyleFiles); - return Task.CompletedTask; - } - - public void Dispose() => persistingSubscription.Dispose(); } diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/AbpAspNetCoreComponentsWebAssemblyThemingBundlingModule.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/AbpAspNetCoreComponentsWebAssemblyThemingBundlingModule.cs new file mode 100644 index 0000000000..27bfd4fa49 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/AbpAspNetCoreComponentsWebAssemblyThemingBundlingModule.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling; + +[DependsOn( + typeof(AbpAspNetCoreMvcUiBundlingAbstractionsModule) +)] +public class AbpAspNetCoreComponentsWebAssemblyThemingBundlingModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.GlobalAssets.Enabled = true; + options.GlobalAssets.GlobalStyleBundleName = BlazorWebAssemblyStandardBundles.Styles.Global; + options.GlobalAssets.GlobalScriptBundleName = BlazorWebAssemblyStandardBundles.Scripts.Global; + + options + .StyleBundles + .Add(BlazorWebAssemblyStandardBundles.Styles.Global, bundle => + { + bundle.AddContributors(typeof(BlazorWebAssemblyStyleContributor)); + }); + + options + .ScriptBundles + .Add(BlazorWebAssemblyStandardBundles.Scripts.Global, bundle => + { + bundle.AddContributors(typeof(BlazorWebAssemblyScriptContributor)); + }); + + options.MinificationIgnoredFiles.Add("_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"); + }); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/BlazorWebAssemblyScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/BlazorWebAssemblyScriptContributor.cs new file mode 100644 index 0000000000..d24e5d6976 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/BlazorWebAssemblyScriptContributor.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling; + +public class BlazorWebAssemblyScriptContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/abp.js"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/lang-utils.js"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/lang-utils.js"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/authentication-state-listener.js"); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/BlazorWebAssemblyStandardBundles.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/BlazorWebAssemblyStandardBundles.cs new file mode 100644 index 0000000000..49b6ff8ba1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/BlazorWebAssemblyStandardBundles.cs @@ -0,0 +1,14 @@ +namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling; + +public class BlazorWebAssemblyStandardBundles +{ + public static class Styles + { + public static string Global = "BlazorWebAssembly.Global"; + } + + public static class Scripts + { + public static string Global = "BlazorWebAssembly.Global"; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/BlazorWebAssemblyStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/BlazorWebAssemblyStyleContributor.cs new file mode 100644 index 0000000000..b50ba58bd4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/BlazorWebAssemblyStyleContributor.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling; + +public class BlazorWebAssemblyStyleContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/libs/bootstrap/css/bootstrap.min.css"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/libs/fontawesome/css/all.css"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/css/abp.css"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/libs/flag-icon/css/flag-icon.css"); + context.Files.AddIfNotContains("_content/Blazorise/blazorise.css"); + context.Files.AddIfNotContains("_content/Blazorise.Bootstrap5/blazorise.bootstrap5.css"); + context.Files.AddIfNotContains("_content/Blazorise.Snackbar/blazorise.snackbar.css"); + context.Files.AddIfNotContains("_content/Volo.Abp.BlazoriseUI/volo.abp.blazoriseui.css"); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/FodyWeavers.xml b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/FodyWeavers.xml new file mode 100644 index 0000000000..1715698ccd --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/FodyWeavers.xsd b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/FodyWeavers.xsd new file mode 100644 index 0000000000..ffa6fc4b78 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling.csproj b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling.csproj new file mode 100644 index 0000000000..c56c744bae --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling.csproj @@ -0,0 +1,16 @@ + + + + + + + net9.0 + enable + Nullable + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/AbpAspNetCoreComponentsWebAssemblyThemingModule.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/AbpAspNetCoreComponentsWebAssemblyThemingModule.cs index 38eff974b8..eb4e6dce44 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/AbpAspNetCoreComponentsWebAssemblyThemingModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/AbpAspNetCoreComponentsWebAssemblyThemingModule.cs @@ -1,9 +1,11 @@ using Volo.Abp.AspNetCore.Components.Web.Theming; +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling; using Volo.Abp.Modularity; namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming; [DependsOn( + typeof(AbpAspNetCoreComponentsWebAssemblyThemingBundlingModule), typeof(AbpAspNetCoreComponentsWebThemingModule), typeof(AbpAspNetCoreComponentsWebAssemblyModule) )] diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs index d9b5a90e6a..816dd51889 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs @@ -1,7 +1,9 @@ -using Volo.Abp.Bundling; +using System; +using Volo.Abp.Bundling; namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming; +[Obsolete("This class is obsolete and will be removed in the future versions. Use GlobalAssets instead.")] public class ComponentsComponentsBundleContributor : IBundleContributor { public void AddScripts(BundleContext context) diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.csproj b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.csproj index 902163f391..ebaa3e8d43 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.csproj @@ -10,6 +10,7 @@ + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/DaprAspNetCore/AbpDaprEndpointRouteBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/DaprAspNetCore/AbpDaprEndpointRouteBuilderExtensions.cs deleted file mode 100644 index 24f68ef9ab..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/DaprAspNetCore/AbpDaprEndpointRouteBuilderExtensions.cs +++ /dev/null @@ -1,296 +0,0 @@ -// ------------------------------------------------------------------------ -// Copyright 2021 The Dapr Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ------------------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Dapr -{ - /// - /// This class defines configurations for the subscribe endpoint. - /// - public class AbpSubscribeOptions - { - /// - /// Gets or Sets a value which indicates whether to enable or disable processing raw messages. - /// - public bool EnableRawPayload { get; set; } - - /// - /// An optional delegate used to configure the subscriptions. - /// - public Func, Task>? SubscriptionsCallback { get; set; } - } - - /// - /// This class defines subscribe endpoint response - /// - public class AbpSubscription - { - /// - /// Gets or sets the topic name. - /// - public string Topic { get; set; } = default!; - - /// - /// Gets or sets the pubsub name - /// - public string PubsubName { get; set; } = default!; - - /// - /// Gets or sets the route - /// - public string? Route { get; set; } - - /// - /// Gets or sets the routes - /// - public AbpRoutes? Routes { get; set; } - - /// - /// Gets or sets the metadata. - /// - public AbpMetadata? Metadata { get; set; } - - /// - /// Gets or sets the deadletter topic. - /// - public string? DeadLetterTopic { get; set; } - } - - /// - /// This class defines the metadata for subscribe endpoint. - /// - public class AbpMetadata : Dictionary - { - /// - /// Initializes a new instance of the Metadata class. - /// - public AbpMetadata() { } - - /// - /// Initializes a new instance of the Metadata class. - /// - /// - public AbpMetadata(IDictionary dictionary) : base(dictionary) { } - - /// - /// RawPayload key - /// - internal const string RawPayload = "rawPayload"; - } - - /// - /// This class defines the routes for subscribe endpoint. - /// - public class AbpRoutes - { - /// - /// Gets or sets the default route - /// - public string? Default { get; set; } - - /// - /// Gets or sets the routing rules - /// - public List? Rules { get; set; } - } - - /// - /// This class defines the rule for subscribe endpoint. - /// - public class AbpRule - { - /// - /// Gets or sets the CEL expression to match this route. - /// - public string Match { get; set; } = default!; - - /// - /// Gets or sets the path of the route. - /// - public string Path { get; set; } = default!; - } -} - -namespace Microsoft.AspNetCore.Builder -{ - using System.Collections.Generic; - using System.Linq; - using System.Text.Json; - using System.Text.Json.Serialization; - using Dapr; - using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.Routing; - using Microsoft.AspNetCore.Routing.Patterns; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Logging; - - /// - /// Contains extension methods for . - /// - public static class AbpDaprEndpointRouteBuilderExtensions - { - /// - /// Maps an endpoint that will respond to requests to /dapr/subscribe from the - /// Dapr runtime. - /// - /// The . - /// The . - public static IEndpointConventionBuilder MapAbpSubscribeHandler(this IEndpointRouteBuilder endpoints) - { - return CreateSubscribeEndPoint(endpoints); - } - - /// - /// Maps an endpoint that will respond to requests to /dapr/subscribe from the - /// Dapr runtime. - /// - /// The . - /// Configuration options - /// The . - /// - public static IEndpointConventionBuilder MapAbpSubscribeHandler(this IEndpointRouteBuilder endpoints, AbpSubscribeOptions options) - { - return CreateSubscribeEndPoint(endpoints, options); - } - - private static IEndpointConventionBuilder CreateSubscribeEndPoint(IEndpointRouteBuilder endpoints, AbpSubscribeOptions? options = null) - { - if (endpoints is null) - { - throw new System.ArgumentNullException(nameof(endpoints)); - } - - return endpoints.MapGet("dapr/subscribe", async context => - { - var logger = context.RequestServices.GetService()?.CreateLogger("DaprTopicSubscription"); - var dataSource = context.RequestServices.GetRequiredService(); - var subscriptions = dataSource.Endpoints - .OfType() - .Where(e => e.Metadata.GetOrderedMetadata().Any(t => t.Name != null)) // only endpoints which have TopicAttribute with not null Name. - .SelectMany(e => - { - var topicMetadata = e.Metadata.GetOrderedMetadata(); - var originalTopicMetadata = e.Metadata.GetOrderedMetadata(); - - var subs = new List<(string PubsubName, string Name, string? DeadLetterTopic, bool? EnableRawPayload, string Match, int Priority, Dictionary OriginalTopicMetadata, string? MetadataSeparator, RoutePattern RoutePattern)>(); - - for (int i = 0; i < topicMetadata.Count(); i++) - { - subs.Add((topicMetadata[i].PubsubName, - topicMetadata[i].Name, - (topicMetadata[i] as IDeadLetterTopicMetadata)?.DeadLetterTopic, - (topicMetadata[i] as IRawTopicMetadata)?.EnableRawPayload, - topicMetadata[i].Match, - topicMetadata[i].Priority, - originalTopicMetadata.Where(m => (topicMetadata[i] as IOwnedOriginalTopicMetadata)?.OwnedMetadatas?.Any(o => o.Equals(m.Id)) == true || string.IsNullOrEmpty(m.Id)) - .GroupBy(c => c.Name) - .ToDictionary(m => m.Key, m => m.Select(c => c.Value).Distinct().ToArray()), - (topicMetadata[i] as IOwnedOriginalTopicMetadata)?.MetadataSeparator, - e.RoutePattern)); - } - - return subs; - }) - .Distinct() - .GroupBy(e => new { e.PubsubName, e.Name }) - .Select(e => e.OrderBy(e => e.Priority)) - .Select(e => - { - var first = e.First(); - var rawPayload = e.Any(e => e.EnableRawPayload.GetValueOrDefault()); - var metadataSeparator = e.FirstOrDefault(e => !string.IsNullOrEmpty(e.MetadataSeparator)).MetadataSeparator?.ToString() ?? ","; - var rules = e.Where(e => !string.IsNullOrEmpty(e.Match)).ToList(); - var defaultRoutes = e.Where(e => string.IsNullOrEmpty(e.Match)).Select(e => RoutePatternToString(e.RoutePattern)).ToList(); - var defaultRoute = defaultRoutes.FirstOrDefault(); - - //multiple identical names. use comma separation. - var metadata = new AbpMetadata(e.SelectMany(c => c.OriginalTopicMetadata).GroupBy(c => c.Key).ToDictionary(c => c.Key, c => string.Join(metadataSeparator, c.SelectMany(c => c.Value).Distinct()))); - if (rawPayload || options?.EnableRawPayload is true) - { - metadata.Add(AbpMetadata.RawPayload, "true"); - } - - if (logger != null) - { - if (defaultRoutes.Count > 1) - { - logger.LogError("A default subscription to topic {name} on pubsub {pubsub} already exists.", first.Name, first.PubsubName); - } - - var duplicatePriorities = rules.GroupBy(e => e.Priority) - .Where(g => g.Count() > 1) - .ToDictionary(x => x.Key, y => y.Count()); - - foreach (var entry in duplicatePriorities) - { - logger.LogError("A subscription to topic {name} on pubsub {pubsub} has duplicate priorities for {priority}: found {count} occurrences.", first.Name, first.PubsubName, entry.Key, entry.Value); - } - } - - var subscription = new AbpSubscription - { - Topic = first.Name, - PubsubName = first.PubsubName, - Metadata = metadata.Count > 0 ? metadata : null, - }; - - if (first.DeadLetterTopic != null) - { - subscription.DeadLetterTopic = first.DeadLetterTopic; - } - - // Use the V2 routing rules structure - if (rules.Count > 0) - { - subscription.Routes = new AbpRoutes - { - Rules = rules.Select(e => new AbpRule - { - Match = e.Match, - Path = RoutePatternToString(e.RoutePattern), - }).ToList(), - Default = defaultRoute, - }; - } - // Use the V1 structure for backward compatibility. - else - { - subscription.Route = defaultRoute; - } - - return subscription; - }) - .OrderBy(e => (e.PubsubName, e.Topic)) - .ToList(); - - await options?.SubscriptionsCallback!(subscriptions)!; - await context.Response.WriteAsync(JsonSerializer.Serialize(subscriptions, - new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - })); - }); - } - - private static string RoutePatternToString(RoutePattern routePattern) - { - return string.Join("/", routePattern.PathSegments - .Select(segment => string.Concat(segment.Parts.Cast() - .Select(part => part.Content)))); - } - } -} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprEventBusModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprEventBusModule.cs index a291c4e769..a4a4f97f5d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprEventBusModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprEventBusModule.cs @@ -1,11 +1,16 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Dapr; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Volo.Abp.DependencyInjection; +using Volo.Abp.Dapr; using Volo.Abp.EventBus; using Volo.Abp.EventBus.Dapr; using Volo.Abp.EventBus.Distributed; @@ -21,50 +26,98 @@ public class AbpAspNetCoreMvcDaprEventBusModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - var subscribeOptions = context.Services.ExecutePreConfiguredActions(); - - Configure(options => + PostConfigure(options => { options.EndpointConfigureActions.Add(endpointContext => { - var rootServiceProvider = endpointContext.ScopeServiceProvider.GetRequiredService(); - subscribeOptions.SubscriptionsCallback = subscriptions => - { - var daprEventBusOptions = rootServiceProvider.GetRequiredService>().Value; - foreach (var handler in rootServiceProvider.GetRequiredService>().Value.Handlers) + var topicMetadatas = endpointContext.Endpoints.DataSources.SelectMany(x => x.Endpoints).OfType() + .Where(e => e.Metadata.GetOrderedMetadata().Any(t => t.Name != null)) + .SelectMany(e => e.Metadata.GetOrderedMetadata()) + .ToList(); + + var endpointConventionBuilder = endpointContext.Endpoints.MapPost( + "/api/abp/dapr/event", async httpContext => { - foreach (var @interface in handler.GetInterfaces().Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IDistributedEventHandler<>))) - { - var eventType = @interface.GetGenericArguments()[0]; - var eventName = EventNameAttribute.GetNameOrDefault(eventType); - - if (subscriptions.Any(x => x.PubsubName == daprEventBusOptions.PubSubName && x.Topic == eventName)) - { - // Controllers with a [Topic] attribute can replace built-in event handlers. - continue; - } - - var subscription = new AbpSubscription - { - PubsubName = daprEventBusOptions.PubSubName, - Topic = eventName, - Route = AbpAspNetCoreMvcDaprPubSubConsts.DaprEventCallbackUrl, - Metadata = new AbpMetadata - { - { - AbpMetadata.RawPayload, "true" - } - } - }; - subscriptions.Add(subscription); - } - } - - return Task.CompletedTask; - }; - - endpointContext.Endpoints.MapAbpSubscribeHandler(subscribeOptions); + await HandleEventAsync(httpContext); + }); + + var abpEvents = GetAbpEvents(endpointContext); + foreach (var @event in abpEvents.Where(x => !topicMetadatas.Any(t => t.PubsubName == x.PubsubName && t.Name == x.Name))) + { + endpointConventionBuilder.WithMetadata(new TopicAttribute( + @event.PubsubName, + @event.Name, + true)); + } + + endpointContext.Endpoints.MapSubscribeHandler(); }); }); } + + private List GetAbpEvents(EndpointRouteBuilderContext endpointContext) + { + var subscriptions = new List(); + var daprEventBusOptions = endpointContext.Endpoints.ServiceProvider.GetRequiredService>().Value; + + foreach (var @interface in endpointContext.Endpoints.ServiceProvider.GetRequiredService>().Value.Handlers + .SelectMany(x => x.GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDistributedEventHandler<>)))) + { + var eventType = @interface.GetGenericArguments()[0]; + var eventName = EventNameAttribute.GetNameOrDefault(eventType); + + var subscription = new TopicAttribute(daprEventBusOptions.PubSubName, eventName); + subscriptions.Add(subscription); + } + + return subscriptions; + } + + private async static Task HandleEventAsync(HttpContext httpContext) + { + var logger = httpContext.RequestServices.GetRequiredService>(); + + httpContext.ValidateDaprAppApiToken(); + + var daprSerializer = httpContext.RequestServices.GetRequiredService(); + var body = (await JsonDocument.ParseAsync(httpContext.Request.Body)); + + var pubSubName = body.RootElement.GetProperty("pubsubname").GetString(); + var topic = body.RootElement.GetProperty("topic").GetString(); + var data = body.RootElement.GetProperty("data").GetRawText(); + if (pubSubName.IsNullOrWhiteSpace() || topic.IsNullOrWhiteSpace() || data.IsNullOrWhiteSpace()) + { + logger.LogError("Invalid Dapr event request."); + httpContext.Response.StatusCode = 400; + return; + } + + var distributedEventBus = httpContext.RequestServices.GetRequiredService(); + + if (IsAbpDaprEventData(data)) + { + var daprEventData = daprSerializer.Deserialize(data, typeof(AbpDaprEventData)).As(); + var eventData = daprSerializer.Deserialize(daprEventData.JsonData, distributedEventBus.GetEventType(daprEventData.Topic)); + await distributedEventBus.TriggerHandlersAsync(distributedEventBus.GetEventType(daprEventData.Topic), eventData, daprEventData.MessageId, daprEventData.CorrelationId); + } + else + { + var eventData = daprSerializer.Deserialize(data, distributedEventBus.GetEventType(topic!)); + await distributedEventBus.TriggerHandlersAsync(distributedEventBus.GetEventType(topic!), eventData); + } + + httpContext.Response.StatusCode = 200; + } + + private static bool IsAbpDaprEventData(string data) + { + var document = JsonDocument.Parse(data); + var objects = document.RootElement.EnumerateObject().ToList(); + return objects.Count == 5 && + objects.Any(x => x.Name.Equals("PubSubName", StringComparison.CurrentCultureIgnoreCase)) && + objects.Any(x => x.Name.Equals("Topic", StringComparison.CurrentCultureIgnoreCase)) && + objects.Any(x => x.Name.Equals("MessageId", StringComparison.CurrentCultureIgnoreCase)) && + objects.Any(x => x.Name.Equals("JsonData", StringComparison.CurrentCultureIgnoreCase)) && + objects.Any(x => x.Name.Equals("CorrelationId", StringComparison.CurrentCultureIgnoreCase)); + } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprPubSubConsts.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprPubSubConsts.cs deleted file mode 100644 index 5f224abc7e..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprPubSubConsts.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus; - -public class AbpAspNetCoreMvcDaprPubSubConsts -{ - public const string DaprEventCallbackUrl = "api/abp/dapr/event"; -} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprEventsController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprEventsController.cs deleted file mode 100644 index 946c39f59c..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprEventsController.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Volo.Abp.Dapr; -using Volo.Abp.EventBus.Dapr; - -namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Controllers; - -[Area("abp")] -[RemoteService(Name = "abp")] -public class AbpAspNetCoreMvcDaprEventsController : AbpController -{ - [HttpPost(AbpAspNetCoreMvcDaprPubSubConsts.DaprEventCallbackUrl)] - public virtual async Task EventAsync() - { - HttpContext.ValidateDaprAppApiToken(); - - var daprSerializer = HttpContext.RequestServices.GetRequiredService(); - var body = (await JsonDocument.ParseAsync(HttpContext.Request.Body)); - - var pubSubName = body.RootElement.GetProperty("pubsubname").GetString(); - var topic = body.RootElement.GetProperty("topic").GetString(); - var data = body.RootElement.GetProperty("data").GetRawText(); - if (pubSubName.IsNullOrWhiteSpace() || topic.IsNullOrWhiteSpace() || data.IsNullOrWhiteSpace()) - { - Logger.LogError("Invalid Dapr event request."); - return BadRequest(); - } - - var distributedEventBus = HttpContext.RequestServices.GetRequiredService(); - - if (IsAbpDaprEventData(data)) - { - var daprEventData = daprSerializer.Deserialize(data, typeof(AbpDaprEventData)).As(); - var eventData = daprSerializer.Deserialize(daprEventData.JsonData, distributedEventBus.GetEventType(daprEventData.Topic)); - await distributedEventBus.TriggerHandlersAsync(distributedEventBus.GetEventType(daprEventData.Topic), eventData, daprEventData.MessageId, daprEventData.CorrelationId); - } - else - { - var eventData = daprSerializer.Deserialize(data, distributedEventBus.GetEventType(topic!)); - await distributedEventBus.TriggerHandlersAsync(distributedEventBus.GetEventType(topic!), eventData); - } - - return Ok(); - } - - protected virtual bool IsAbpDaprEventData(string data) - { - var document = JsonDocument.Parse(data); - var objects = document.RootElement.EnumerateObject().ToList(); - return objects.Count == 5 && - objects.Any(x => x.Name.Equals("PubSubName", StringComparison.CurrentCultureIgnoreCase)) && - objects.Any(x => x.Name.Equals("Topic", StringComparison.CurrentCultureIgnoreCase)) && - objects.Any(x => x.Name.Equals("MessageId", StringComparison.CurrentCultureIgnoreCase)) && - objects.Any(x => x.Name.Equals("JsonData", StringComparison.CurrentCultureIgnoreCase)) && - objects.Any(x => x.Name.Equals("CorrelationId", StringComparison.CurrentCultureIgnoreCase)); - } -} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpBundlingGlobalAssetsOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpBundlingGlobalAssetsOptions.cs new file mode 100644 index 0000000000..206129bec0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpBundlingGlobalAssetsOptions.cs @@ -0,0 +1,20 @@ +namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +public class AbpBundlingGlobalAssetsOptions +{ + public bool Enabled { get; set; } + + public string? GlobalStyleBundleName { get; set; } + + public string? GlobalScriptBundleName { get; set; } + + public string JavaScriptFileName { get; set; } + + public string CssFileName { get; set; } + + public AbpBundlingGlobalAssetsOptions() + { + JavaScriptFileName = "global.js"; + CssFileName = "global.css"; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpBundlingOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpBundlingOptions.cs index 64f63c41ec..7512bb8231 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpBundlingOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpBundlingOptions.cs @@ -28,6 +28,10 @@ public class AbpBundlingOptions public List PreloadStyles { get; } + public AbpBundlingGlobalAssetsOptions GlobalAssets { get; set; } + + public BundleParameterDictionary Parameters { get; set; } + public AbpBundlingOptions() { StyleBundles = new BundleConfigurationCollection(); @@ -37,5 +41,7 @@ public class AbpBundlingOptions DeferScripts = new List(); PreloadStylesByDefault = false; PreloadStyles = new List(); + GlobalAssets = new AbpBundlingGlobalAssetsOptions(); + Parameters = new BundleParameterDictionary(); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationContext.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationContext.cs index bc4dc14d11..af098a9b2b 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationContext.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationContext.cs @@ -16,11 +16,14 @@ public class BundleConfigurationContext : IBundleConfigurationContext public IAbpLazyServiceProvider LazyServiceProvider { get; } - public BundleConfigurationContext(IServiceProvider serviceProvider, IFileProvider fileProvider) + public BundleParameterDictionary Parameters { get; set; } + + public BundleConfigurationContext(IServiceProvider serviceProvider, IFileProvider fileProvider, BundleParameterDictionary? parameters = null) { Files = new List(); ServiceProvider = serviceProvider; LazyServiceProvider = ServiceProvider.GetRequiredService(); FileProvider = fileProvider; + Parameters = parameters ?? new BundleParameterDictionary(); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleParameterDictionary.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleParameterDictionary.cs new file mode 100644 index 0000000000..df4dc573a4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleParameterDictionary.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +public class BundleParameterDictionary : Dictionary +{ + public const string InteractiveAutoPropertyName = "InteractiveAuto"; + + public bool InteractiveAuto + { + get + { + return TryGetValue(InteractiveAutoPropertyName, out var value) && bool.Parse(value); + } + set + { + this[InteractiveAutoPropertyName] = value.ToString(); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpAspNetCoreMvcUiBundlingModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpAspNetCoreMvcUiBundlingModule.cs index f0f7f94439..2292c93a53 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpAspNetCoreMvcUiBundlingModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpAspNetCoreMvcUiBundlingModule.cs @@ -1,8 +1,21 @@ -using Volo.Abp.AspNetCore.Mvc.Libs; +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; +using Volo.Abp.AspNetCore.VirtualFileSystem; +using Volo.Abp.Bundling.Styles; +using Volo.Abp.AspNetCore.Mvc.Libs; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; using Volo.Abp.Data; using Volo.Abp.Minify; using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; @@ -23,4 +36,96 @@ public class AbpAspNetCoreMvcUiBundlingModule : AbpModule }); } } + + public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context) + { + var environment = context.GetEnvironmentOrNull(); + if (environment != null) + { + environment.WebRootFileProvider = + new CompositeFileProvider( + context.GetEnvironment().WebRootFileProvider, + context.ServiceProvider.GetRequiredService() + ); + } + + await InitialGlobalAssetsAsync(context); + } + + protected virtual async Task InitialGlobalAssetsAsync(ApplicationInitializationContext context) + { + var bundlingOptions = context.ServiceProvider.GetRequiredService>().Value; + var logger = context.ServiceProvider.GetRequiredService>(); + if (!bundlingOptions.GlobalAssets.Enabled) + { + return; + } + + var bundleManager = context.ServiceProvider.GetRequiredService(); + var webHostEnvironment = context.ServiceProvider.GetRequiredService(); + var dynamicFileProvider = context.ServiceProvider.GetRequiredService(); + if (!bundlingOptions.GlobalAssets.GlobalStyleBundleName.IsNullOrWhiteSpace()) + { + var styleFiles = await bundleManager.GetStyleBundleFilesAsync(bundlingOptions.GlobalAssets.GlobalStyleBundleName); + var styles = string.Empty; + foreach (var file in styleFiles) + { + var fileInfo = webHostEnvironment.WebRootFileProvider?.GetFileInfo(file.FileName); + if (fileInfo == null || !fileInfo.Exists) + { + logger.LogError($"Could not find the file: {file.FileName}"); + continue; + } + + var fileContent = await fileInfo.ReadAsStringAsync(); + if (!bundleManager.IsBundlingEnabled()) + { + fileContent = CssRelativePath.Adjust(fileContent, + file.FileName, + Path.Combine(Directory.GetCurrentDirectory(), "wwwroot")); + + styles += $"/*{file.FileName}*/{Environment.NewLine}{fileContent}{Environment.NewLine}{Environment.NewLine}"; + } + else + { + styles += $"{fileContent}{Environment.NewLine}{Environment.NewLine}"; + } + } + + dynamicFileProvider.AddOrUpdate( + new InMemoryFileInfo("/wwwroot/" + bundlingOptions.GlobalAssets.CssFileName, + Encoding.UTF8.GetBytes(styles), + bundlingOptions.GlobalAssets.CssFileName)); + } + + if (!bundlingOptions.GlobalAssets.GlobalScriptBundleName.IsNullOrWhiteSpace()) + { + var scriptFiles = await bundleManager.GetScriptBundleFilesAsync(bundlingOptions.GlobalAssets.GlobalScriptBundleName); + var scripts = string.Empty; + foreach (var file in scriptFiles) + { + var fileInfo = webHostEnvironment.WebRootFileProvider?.GetFileInfo(file.FileName); + if (fileInfo == null || !fileInfo.Exists) + { + logger.LogError($"Could not find the file: {file.FileName}"); + continue; + } + + var fileContent = await fileInfo.ReadAsStringAsync(); + if (!bundleManager.IsBundlingEnabled()) + { + scripts += $"{fileContent.EnsureEndsWith(';')}{Environment.NewLine}{Environment.NewLine}"; + } + else + { + scripts += $"//{file.FileName}{Environment.NewLine}{fileContent.EnsureEndsWith(';')}{Environment.NewLine}{Environment.NewLine}"; + } + } + + dynamicFileProvider.AddOrUpdate( + new InMemoryFileInfo("/wwwroot/" + bundlingOptions.GlobalAssets.JavaScriptFileName, + Encoding.UTF8.GetBytes(scripts), + bundlingOptions.GlobalAssets.JavaScriptFileName)); + } + } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs index 20c33e86e5..9ac43585c0 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs @@ -172,7 +172,7 @@ public class BundleManager : IBundleManager, ITransientDependency ); } - protected virtual bool IsBundlingEnabled() + public virtual bool IsBundlingEnabled() { switch (Options.Mode) { @@ -240,7 +240,7 @@ public class BundleManager : IBundleManager, ITransientDependency protected virtual BundleConfigurationContext CreateBundleConfigurationContext() { - return new BundleConfigurationContext(ServiceProvider, HostingEnvironment.WebRootFileProvider); + return new BundleConfigurationContext(ServiceProvider, HostingEnvironment.WebRootFileProvider, Options.Parameters); } protected virtual List GetContributors(BundleConfigurationCollection bundles, string bundleName) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/Styles/StyleBundler.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/Styles/StyleBundler.cs index 6623a2d667..b20e9af7ee 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/Styles/StyleBundler.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/Styles/StyleBundler.cs @@ -2,6 +2,8 @@ using System; using System.IO; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.VirtualFileSystem; +using Volo.Abp.Bundling.Styles; using Volo.Abp.Minify.Styles; namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.Styles; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js index 0f2b028daf..e3e1f8fe6c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js @@ -87,7 +87,7 @@ var abp = abp || {}; if (field.items.length === 1) { var firstItem = field.items[0]; if (!getVisibilityValue(firstItem.visible, record, tableInstance)) { - return ""; + return $(''); } var $button = $(''); @@ -181,7 +181,7 @@ var abp = abp || {}; if ($dropdownItemsContainer.find('li').length > 0) { $dropdownItemsContainer.appendTo($container); } else { - $dropdownButton.attr('disabled', 'disabled'); + $dropdownButton.addClass('d-none'); } $dropdownButton.prependTo($container); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/jquery-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/jquery-extensions.js index bae3e19c93..5928393329 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/jquery-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/jquery-extensions.js @@ -161,9 +161,17 @@ $.fn.needConfirmationOnUnsavedClose = function ($modal) { var $form = $(this); var formSaved = false; - var unEditedForm = JSON.stringify($form.serializeFormToObject()); + var unEditedForm; + + $modal.on("shown.bs.modal", function () { + unEditedForm = JSON.stringify($form.serializeFormToObject()); + }); $modal.on("hide.bs.modal", function (e) { + if(unEditedForm === undefined) { + return; + } + var currentForm = JSON.stringify($form.serializeFormToObject()); var thereAreUnsavedChanges = currentForm !== unEditedForm; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs index 10900f6ee4..097fd899a5 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs @@ -242,13 +242,19 @@ public class CachedObjectExtensionsDtoService : ICachedObjectExtensionsDtoServic e => e.GetProperties() ) ) - .Where(p => p.Type.IsEnum) + .Where(p => p.Type.IsEnum || TypeHelper.IsNullableEnum(p.Type)) .ToList(); foreach (var enumProperty in enumProperties) { - // ReSharper disable once AssignNullToNotNullAttribute (enumProperty.Type.FullName can not be null for this case) - objectExtensionsDto.Enums[enumProperty.Type.FullName!] = CreateExtensionEnumDto(enumProperty); + if (TypeHelper.IsNullableEnum(enumProperty.Type)) + { + objectExtensionsDto.Enums[Nullable.GetUnderlyingType(enumProperty.Type)!.FullName + "?"] = CreateExtensionEnumDto(enumProperty); + } + else + { + objectExtensionsDto.Enums[enumProperty.Type.FullName!] = CreateExtensionEnumDto(enumProperty); + } } } @@ -260,12 +266,23 @@ public class CachedObjectExtensionsDtoService : ICachedObjectExtensionsDtoServic LocalizationResource = enumProperty.GetLocalizationResourceNameOrNull() }; - foreach (var enumValue in enumProperty.Type.GetEnumValues()) + var enumType = enumProperty.Type.IsEnum + ? enumProperty.Type + : TypeHelper.IsNullableEnum(enumProperty.Type) + ? Nullable.GetUnderlyingType(enumProperty.Type) + : null; + + if (enumType == null) + { + return extensionEnumDto; + } + + foreach (var enumValue in enumType.GetEnumValues()) { extensionEnumDto.Fields.Add( new ExtensionEnumFieldDto { - Name = enumProperty.Type.GetEnumName(enumValue)!, + Name = enumType.GetEnumName(enumValue)!, Value = enumValue } ); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsErrorPage.Designer.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsErrorPage.Designer.cs index 9d30ea018c..4fe8e51000 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsErrorPage.Designer.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsErrorPage.Designer.cs @@ -27,20 +27,100 @@ using Volo.Abp.AspNetCore.RazorViews; #line hidden #nullable disable WriteLiteral(@" - - - - Error - The Libs folder is missing! - - -

⚠️ The Libs folder under the wwwroot/libs directory is empty!

- + + + + + + Error - The Libs Folder is Missing! + + + +
+

⚠️ The Libs folder is missing!

+
+

The Libs folder contains mandatory NPM Packages for running the project.

- -

Make sure you run the abp install-libs CLI tool command.

- -

For more information, check out the ABP CLI documentation

- +

Make sure you run the abp install-lib"); + WriteLiteral(@"s CLI tool command.

+

+ If your application does not use any client-side libraries, you can disable this check by setting + AbpMvcLibsOptions.CheckLibs to false, as shown below: +

+
+Configure<AbpMvcLibsOptions>(options =>
+{
+    options.CheckLibs = false;
+});
+

For more information, check out the ABP CLI documentation.

+
+
+ © ABP Framework +
+ "); } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsErrorPage.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsErrorPage.cshtml index 5a5d6c8d19..7b15ce94bf 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsErrorPage.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsErrorPage.cshtml @@ -5,18 +5,96 @@ Response.StatusCode = 500; } - - - - Error - The Libs folder is missing! - - -

⚠️ The Libs folder under the wwwroot/libs directory is empty!

- + + + + + + Error - The Libs Folder is Missing! + + + +
+

⚠️ The Libs folder is missing!

+
+

The Libs folder contains mandatory NPM Packages for running the project.

- -

Make sure you run the abp install-libs CLI tool command.

- -

For more information, check out the ABP CLI documentation

- +

Make sure you run the abp install-libs CLI tool command.

+

+ If your application does not use any client-side libraries, you can disable this check by setting + AbpMvcLibsOptions.CheckLibs to false, as shown below: +

+
+Configure<AbpMvcLibsOptions>(options =>
+{
+    options.CheckLibs = false;
+});
+

For more information, check out the ABP CLI documentation.

+
+
+ © ABP Framework +
+ diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsService.cs index 410260674b..dc6faf723c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Libs/AbpMvcLibsService.cs @@ -64,7 +64,7 @@ public class AbpMvcLibsService : IAbpMvcLibsService, ITransientDependency var webHostEnvironment = httpContext.RequestServices.GetRequiredService(); if (webHostEnvironment.WebRootPath.IsNullOrWhiteSpace()) { - logger.LogWarning("The 'WebRootPath' is not set! The 'CheckLibs' feature is disabled!"); + logger.LogInformation("The 'WebRootPath' is not set, The 'CheckLibs' feature not needed."); return Task.FromResult(true); } @@ -72,7 +72,8 @@ public class AbpMvcLibsService : IAbpMvcLibsService, ITransientDependency var libsFolder = fileProvider.GetDirectoryContents("/libs"); if (!libsFolder.Exists || !libsFolder.Any()) { - logger.LogError("The 'wwwroot/libs' folder does not exist or empty!"); + logger.LogError("The 'wwwroot/libs' folder does not exist or empty! " + + "If your application does not use any client-side libraries, you can disable this check by setting 'AbpMvcLibsOptions.CheckLibs' to 'false'"); return Task.FromResult(false); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Response/AbpNoContentActionFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Response/AbpNoContentActionFilter.cs index 455dd52f24..7a7c8701ce 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Response/AbpNoContentActionFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Response/AbpNoContentActionFilter.cs @@ -26,6 +26,12 @@ public class AbpNoContentActionFilter : IAsyncActionFilter, IAbpFilter, ITransie var returnType = context.ActionDescriptor.GetReturnType(); if (returnType == typeof(Task) || returnType == typeof(void)) { + if (context.HttpContext.Response.Body.CanSeek && context.HttpContext.Response.Body.Length != 0) + { + // Body has been written, so we should not change the status code. + return; + } + context.HttpContext.Response.StatusCode = (int)HttpStatusCode.NoContent; } } diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs index bd653faf55..f0ca14657b 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs @@ -15,6 +15,7 @@ using Volo.Abp.AspNetCore.Auditing; using Volo.Abp.AspNetCore.ExceptionHandling; using Volo.Abp.AspNetCore.Security; using Volo.Abp.AspNetCore.Security.Claims; +using Volo.Abp.AspNetCore.StaticFiles; using Volo.Abp.AspNetCore.Tracing; using Volo.Abp.AspNetCore.Uow; using Volo.Abp.AspNetCore.VirtualFileSystem; @@ -126,6 +127,38 @@ public static class AbpApplicationBuilderExtensions return app.UseMiddleware(); } + /// + /// Configures the application to serve static files that match the specified filename patterns with the WebRootFileProvider of the application. + /// + /// The used to configure the application pipeline. + /// The file name patterns to include when serving static files (e.g., "appsettings*.json"). + /// Supports glob patterns. See Glob patterns documentation. + /// + /// The instance. + public static IApplicationBuilder UseStaticFilesForPatterns(this IApplicationBuilder app, params string[] includeFileNamePatterns) + { + return UseStaticFilesForPatterns(app, includeFileNamePatterns, app.ApplicationServices.GetRequiredService().WebRootFileProvider); + } + + /// + /// Configures the application to serve static files that match the specified filename patterns with the specified file provider. + /// + /// The used to configure the application pipeline. + /// The file name patterns to include when serving static files (e.g., "appsettings*.json"). + /// Supports glob patterns. See Glob patterns documentation. + /// + /// The + /// The instance. + public static IApplicationBuilder UseStaticFilesForPatterns(this IApplicationBuilder app, string[] includeFileNamePatterns, IFileProvider fileProvider) + { + app.UseStaticFiles(new StaticFileOptions + { + FileProvider = new AbpStaticFileProvider(includeFileNamePatterns, fileProvider) + }); + + return app; + } + /// /// MapAbpStaticAssets is used to serve the files from the abp virtual file system embedded resources(js/css) and call the MapStaticAssets. /// @@ -144,7 +177,21 @@ public static class AbpApplicationBuilderExtensions throw new AbpException("The app(IApplicationBuilder) is not an IEndpointRouteBuilder."); } - app.UseVirtualStaticFiles(); + var environment = endpoints.ServiceProvider.GetRequiredService(); + if (environment.IsDevelopment()) + { + // MapStaticAssets in development mode will have a performance issue if there are many static files. + // https://github.com/dotnet/aspnetcore/issues/59673 + app.UseStaticFiles(); + + // Volo.Abp.AspNetCore.staticwebassets.endpoints.json is an empty file. Just compatible with the return type of MapAbpStaticAssets. + var tempStaticAssetsManifestPath = Path.Combine(AppContext.BaseDirectory, "Volo.Abp.AspNetCore.staticwebassets.endpoints.json"); + if (!File.Exists(tempStaticAssetsManifestPath)) + { + File.WriteAllText(tempStaticAssetsManifestPath, "{\"Version\":1,\"ManifestType\":\"Build\",\"Endpoints\":[]}"); + } + return endpoints.MapStaticAssets(tempStaticAssetsManifestPath); + } var options = app.ApplicationServices.GetRequiredService>().Value; foreach (var folder in options.AllowedExtraWebContentFolders) @@ -152,6 +199,8 @@ public static class AbpApplicationBuilderExtensions app.UseVirtualStaticFiles(folder); } + app.UseVirtualStaticFiles(); + return endpoints.MapStaticAssets(staticAssetsManifestPath); } diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs index 35d30e9f64..bd767f93fb 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using DeviceDetectorNET; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Localization; using Microsoft.Extensions.DependencyInjection; @@ -57,7 +58,7 @@ public class DefaultAbpRequestLocalizationOptionsProvider : ? new RequestLocalizationOptions() : new RequestLocalizationOptions { - DefaultRequestCulture = DefaultGetRequestCulture(defaultLanguage, languages), + DefaultRequestCulture = GetDefaultRequestCulture(defaultLanguage, languages), SupportedCultures = languages .Select(l => l.CultureName) .Distinct() @@ -87,15 +88,22 @@ public class DefaultAbpRequestLocalizationOptionsProvider : return _requestLocalizationOptions; } - private static RequestCulture DefaultGetRequestCulture(string? defaultLanguage, IReadOnlyList languages) + private static RequestCulture GetDefaultRequestCulture(string? defaultLanguage, IReadOnlyList languages) { if (defaultLanguage == null) { - var firstLanguage = languages.First(); + var firstLanguage = languages.FirstOrDefault() ?? new LanguageInfo("en", "en"); return new RequestCulture(firstLanguage.CultureName, firstLanguage.UiCultureName); } var (cultureName, uiCultureName) = LocalizationSettingHelper.ParseLanguageSetting(defaultLanguage); + + if (languages.Any() && languages.All(l => l.CultureName != cultureName)) + { + var firstLanguage = languages.First(); + return new RequestCulture(firstLanguage.CultureName, firstLanguage.UiCultureName); + } + return new RequestCulture(cultureName, uiCultureName); } @@ -106,4 +114,4 @@ public class DefaultAbpRequestLocalizationOptionsProvider : _requestLocalizationOptions = null; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreModule.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreModule.cs index cda6aea27f..64fab10ac6 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreModule.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreModule.cs @@ -1,5 +1,6 @@ using System; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting.StaticWebAssets; using Microsoft.AspNetCore.RequestLocalization; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; @@ -56,6 +57,8 @@ public class AbpAspNetCoreModule : AbpModule AddAspNetServices(context.Services); context.Services.AddObjectAccessor(); context.Services.AddAbpDynamicOptions(); + + StaticWebAssetsLoader.UseStaticWebAssets(context.Services.GetHostingEnvironment(), context.Services.GetConfiguration()); } private static void AddAspNetServices(IServiceCollection services) diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAspNetCoreAuditingUrlOptions.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAspNetCoreAuditingUrlOptions.cs new file mode 100644 index 0000000000..7d371aa331 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAspNetCoreAuditingUrlOptions.cs @@ -0,0 +1,10 @@ +namespace Volo.Abp.AspNetCore.Auditing; + +public class AbpAspNetCoreAuditingUrlOptions +{ + public bool IncludeSchema { get; set; } + + public bool IncludeHost { get; set; } + + public bool IncludeQuery { get; set; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AspNetCoreAuditLogContributor.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AspNetCoreAuditLogContributor.cs index fc010f9846..becdee806d 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AspNetCoreAuditLogContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AspNetCoreAuditLogContributor.cs @@ -1,9 +1,11 @@ using System; using System.Linq; +using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; using Volo.Abp.AspNetCore.ExceptionHandling; using Volo.Abp.AspNetCore.WebClientInfo; using Volo.Abp.Auditing; @@ -40,7 +42,7 @@ public class AspNetCoreAuditLogContributor : AuditLogContributor, ITransientDepe if (context.AuditInfo.Url == null) { - context.AuditInfo.Url = BuildUrl(httpContext); + context.AuditInfo.Url = GetUrl(context, httpContext); } var clientInfoProvider = context.ServiceProvider.GetRequiredService(); @@ -88,18 +90,29 @@ public class AspNetCoreAuditLogContributor : AuditLogContributor, ITransientDepe context.AuditInfo.HttpStatusCode = httpContext.Response.StatusCode; } - protected virtual string BuildUrl(HttpContext httpContext) + protected virtual string GetUrl(AuditLogContributionContext context, HttpContext httpContext) { - //TODO: Add options to include/exclude query, schema and host + var options = context.ServiceProvider.GetRequiredService>(); + var stringBuilder = new StringBuilder(); - var uriBuilder = new UriBuilder + if (options.Value.IncludeSchema) { - Scheme = httpContext.Request.Scheme, - Host = httpContext.Request.Host.Host, - Path = httpContext.Request.Path.ToString(), - Query = httpContext.Request.QueryString.ToString() - }; - - return uriBuilder.Uri.AbsolutePath; + stringBuilder.Append(httpContext.Request.Scheme); + stringBuilder.Append("://"); + } + + if (options.Value.IncludeHost) + { + stringBuilder.Append(httpContext.Request.Host.Host); + } + + stringBuilder.Append(httpContext.Request.Path.ToString()); + + if (options.Value.IncludeQuery) + { + stringBuilder.Append(httpContext.Request.QueryString.ToString()); + } + + return stringBuilder.ToString(); } } diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs index 4542dd32e1..2d231c46ee 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs @@ -23,6 +23,14 @@ public class AbpSecurityHeadersMiddleware : AbpMiddlewareBase, ITransientDepende public async override Task InvokeAsync(HttpContext context, RequestDelegate next) { + var endpoint = context.GetEndpoint(); + + if (endpoint?.Metadata.GetMetadata() != null) + { + await next.Invoke(context); + return; + } + /*X-Content-Type-Options header tells the browser to not try and “guess” what a mimetype of a resource might be, and to just take what mimetype the server has returned as fact.*/ AddHeader(context, "X-Content-Type-Options", "nosniff"); @@ -35,14 +43,6 @@ public class AbpSecurityHeadersMiddleware : AbpMiddlewareBase, ITransientDepende var requestAcceptTypeHtml = context.Request.Headers["Accept"].Any(x => x!.Contains("text/html") || x.Contains("*/*") || x.Contains("application/xhtml+xml")); - var endpoint = context.GetEndpoint(); - - if (endpoint?.Metadata.GetMetadata() != null) - { - await next.Invoke(context); - return; - } - if (!requestAcceptTypeHtml || !Options.Value.UseContentSecurityPolicyHeader || await AlwaysIgnoreContentTypes(context) diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/StaticFiles/AbpStaticFileProvider.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/StaticFiles/AbpStaticFileProvider.cs new file mode 100644 index 0000000000..f7893932f2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/StaticFiles/AbpStaticFileProvider.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.FileSystemGlobbing; +using Microsoft.Extensions.Primitives; + +namespace Volo.Abp.AspNetCore.StaticFiles; + +public class AbpStaticFileProvider : IFileProvider +{ + private readonly Matcher _matcher; + private readonly IFileProvider _fileProvider; + + /// The file provider to be used to get the files. + /// The file name patterns to include when serving static files (e.g., "appsettings*.json"). + /// Supports glob patterns. See Glob patterns documentation. + /// + public AbpStaticFileProvider(IReadOnlyList fileNamePatterns, IFileProvider fileProvider) + { + _fileProvider = fileProvider; + _matcher = new Matcher(StringComparison.OrdinalIgnoreCase); + _matcher.AddIncludePatterns(fileNamePatterns); + } + + public IDirectoryContents GetDirectoryContents(string subpath) + { + return new NotFoundDirectoryContents(); + } + + public IFileInfo GetFileInfo(string subpath) + { + return _matcher.Match(Path.GetFileName(subpath)).HasMatches ? + _fileProvider.GetFileInfo(subpath) : + new NotFoundFileInfo(subpath); + } + + public IChangeToken Watch(string filter) + { + return NullChangeToken.Singleton; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptor.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptor.cs index 6be28d24cf..0f8894ed10 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptor.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptor.cs @@ -191,7 +191,9 @@ public class AuditingInterceptor : AbpInterceptor, ITransientDependency } if (!options.IsEnabledForGetRequests && - invocation.Method.Name.StartsWith("Get", StringComparison.OrdinalIgnoreCase)) + (string.Equals(auditLogInfo.HttpMethod, "Get", StringComparison.OrdinalIgnoreCase) || + string.Equals(auditLogInfo.HttpMethod, "Head", StringComparison.OrdinalIgnoreCase) || + invocation.Method.Name.StartsWith("Get", StringComparison.OrdinalIgnoreCase))) { return false; } diff --git a/framework/src/Volo.Abp.Authorization/Microsoft/AspNetCore/Authorization/AbpAuthorizationServiceExtensions.cs b/framework/src/Volo.Abp.Authorization/Microsoft/AspNetCore/Authorization/AbpAuthorizationServiceExtensions.cs index c66d74b381..3dfc5218d0 100644 --- a/framework/src/Volo.Abp.Authorization/Microsoft/AspNetCore/Authorization/AbpAuthorizationServiceExtensions.cs +++ b/framework/src/Volo.Abp.Authorization/Microsoft/AspNetCore/Authorization/AbpAuthorizationServiceExtensions.cs @@ -108,6 +108,11 @@ public static class AbpAuthorizationServiceExtensions return (await authorizationService.AuthorizeAsync(resource, policyName)).Succeeded; } + /// + /// Checks if CurrentPrincipal meets a specific authorization policy, throwing an if not. + /// + /// The providing authorization. + /// The name of the policy to evaluate. public static async Task CheckAsync(this IAuthorizationService authorizationService, string policyName) { if (!await authorizationService.IsGrantedAsync(policyName)) @@ -117,6 +122,12 @@ public static class AbpAuthorizationServiceExtensions } } + /// + /// Checks if CurrentPrincipal meets a specific requirement for the specified resource, throwing an if not. + /// + /// The providing authorization. + /// The resource to evaluate the policy against. + /// The requirement to evaluate the policy against. public static async Task CheckAsync(this IAuthorizationService authorizationService, object resource, IAuthorizationRequirement requirement) { if (!await authorizationService.IsGrantedAsync(resource, requirement)) @@ -126,6 +137,12 @@ public static class AbpAuthorizationServiceExtensions } } + /// + /// Checks if CurrentPrincipal meets a specific authorization policy against the specified resource, throwing an if not. + /// + /// The providing authorization. + /// The resource to evaluate the policy against. + /// The policy to evaluate. public static async Task CheckAsync(this IAuthorizationService authorizationService, object resource, AuthorizationPolicy policy) { if (!await authorizationService.IsGrantedAsync(resource, policy)) @@ -135,6 +152,11 @@ public static class AbpAuthorizationServiceExtensions } } + /// + /// Checks if CurrentPrincipal meets a specific authorization policy, throwing an if not. + /// + /// The providing authorization. + /// The policy to evaluate. public static async Task CheckAsync(this IAuthorizationService authorizationService, AuthorizationPolicy policy) { if (!await authorizationService.IsGrantedAsync(policy)) @@ -143,6 +165,12 @@ public static class AbpAuthorizationServiceExtensions } } + /// + /// Checks if CurrentPrincipal meets a specific authorization policy against the specified resource, throwing an if not. + /// + /// The providing authorization. + /// The resource to evaluate the policy against. + /// The requirements to evaluate the policy against. public static async Task CheckAsync(this IAuthorizationService authorizationService, object resource, IEnumerable requirements) { if (!await authorizationService.IsGrantedAsync(resource, requirements)) @@ -152,6 +180,12 @@ public static class AbpAuthorizationServiceExtensions } } + /// + /// Checks if CurrentPrincipal meets a specific authorization policy against the specified resource, throwing an if not. + /// + /// The providing authorization. + /// The resource to evaluate the policy against. + /// The name of the policy to evaluate. public static async Task CheckAsync(this IAuthorizationService authorizationService, object resource, string policyName) { if (!await authorizationService.IsGrantedAsync(resource, policyName)) diff --git a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobWorkerOptions.cs b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobWorkerOptions.cs index 6fc0daca56..3bc31e7a39 100644 --- a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobWorkerOptions.cs +++ b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobWorkerOptions.cs @@ -33,6 +33,12 @@ public class AbpBackgroundJobWorkerOptions /// public double DefaultWaitFactor { get; set; } + /// + /// Distributed lock name for the worker. + /// Default value: "AbpBackgroundJobWorker". + /// + public string DistributedLockName { get; set; } + public AbpBackgroundJobWorkerOptions() { MaxJobFetchCount = 1000; @@ -40,5 +46,6 @@ public class AbpBackgroundJobWorkerOptions DefaultFirstWaitDuration = 60; DefaultTimeout = 172800; DefaultWaitFactor = 2.0; + DistributedLockName = "AbpBackgroundJobWorker"; } } diff --git a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs index 4aeaf59885..4313350098 100644 --- a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs +++ b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs @@ -13,8 +13,6 @@ namespace Volo.Abp.BackgroundJobs; public class BackgroundJobWorker : AsyncPeriodicBackgroundWorkerBase, IBackgroundJobWorker { - protected const string DistributedLockName = "AbpBackgroundJobWorker"; - protected AbpBackgroundJobOptions JobOptions { get; } protected AbpBackgroundJobWorkerOptions WorkerOptions { get; } @@ -39,7 +37,7 @@ public class BackgroundJobWorker : AsyncPeriodicBackgroundWorkerBase, IBackgroun protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext) { - await using (var handler = await DistributedLock.TryAcquireAsync(DistributedLockName, cancellationToken: StoppingToken)) + await using (var handler = await DistributedLock.TryAcquireAsync(WorkerOptions.DistributedLockName, cancellationToken: StoppingToken)) { if (handler != null) { diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor index 29010dded6..2547a7930c 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor @@ -112,7 +112,7 @@ Sortable="@column.Sortable" Displayable="column.Visible"> - @(GetConvertedFieldValue(context, column)) + @((MarkupString)GetConvertedFieldValue(context, column)) } @@ -140,7 +140,7 @@ { if (column.ValueConverter != null) { - @(GetConvertedFieldValue(context, column)) + @((MarkupString)GetConvertedFieldValue(context, column)) } else { diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/EntityAction.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/EntityAction.razor index 9bd07ce7da..d913647264 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Components/EntityAction.razor +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/EntityAction.razor @@ -17,7 +17,8 @@ Disabled=@Disabled> @if(!string.IsNullOrEmpty(Icon)) { - + var iconClass = Text.IsNullOrEmpty() ? "" : "me-1"; + } @Text diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs index 8695c0c418..333ae59e3f 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs @@ -85,7 +85,11 @@ public class AuthService : IAuthService, ITransientDependency } var accessToken = await AuthenticationService.GetAccessTokenAsync(configuration); - + + if (!Directory.Exists(CliPaths.Root)) + { + Directory.CreateDirectory(CliPaths.Root); + } File.WriteAllText(CliPaths.AccessToken, accessToken, Encoding.UTF8); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/CssRelativePathAdjuster.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/CssRelativePathAdjuster.cs deleted file mode 100644 index c1cb76aa22..0000000000 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/CssRelativePathAdjuster.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.IO; -using System.Text.RegularExpressions; - -namespace Volo.Abp.Cli.Bundling.Styles; - -internal static class CssRelativePathAdjuster -{ - private static readonly Regex _rxUrl = new Regex(@"url\s*\(\s*([""']?)([^:)]+)\1\s*\)", RegexOptions.IgnoreCase | RegexOptions.Compiled); - - public static string Adjust( - string cssFileContents, - string absoluteInputFilePath, - string absoluteOutputPath) - { - var matches = _rxUrl.Matches(cssFileContents); - - if (matches.Count <= 0) - { - return cssFileContents; - } - - var cssDirectoryPath = Path.GetDirectoryName(absoluteInputFilePath); - - foreach (Match match in matches) - { - string quoteDelimiter = match.Groups[1].Value; //url('') vs url("") - string relativePathToCss = match.Groups[2].Value; - - // Ignore root relative references - if (relativePathToCss.StartsWith("/", StringComparison.Ordinal)) - continue; - - //prevent query string from causing error - var pathAndQuery = relativePathToCss.Split(new[] { '?' }, 2, StringSplitOptions.RemoveEmptyEntries); - var pathOnly = pathAndQuery[0]; - var queryOnly = pathAndQuery.Length == 2 ? pathAndQuery[1] : string.Empty; - - string absolutePath = GetAbsolutePath(cssDirectoryPath, pathOnly); - string serverRelativeUrl = MakeRelative(absoluteOutputPath, absolutePath); - - if (!string.IsNullOrEmpty(queryOnly)) - serverRelativeUrl += "?" + queryOnly; - - string replace = string.Format("url({0}{1}{0})", quoteDelimiter, serverRelativeUrl); - - cssFileContents = cssFileContents.Replace(match.Groups[0].Value, replace); - } - - return cssFileContents; - } - - private static string GetAbsolutePath(string cssFilePath, string pathOnly) - { - return Path.GetFullPath(Path.Combine(cssFilePath, pathOnly)); - } - - private static readonly string _protocol = "file:///"; - private static string MakeRelative(string baseFile, string file) - { - if (string.IsNullOrEmpty(file)) - return file; - - Uri baseUri = new Uri(_protocol + baseFile, UriKind.RelativeOrAbsolute); - Uri fileUri = new Uri(_protocol + file, UriKind.RelativeOrAbsolute); - - if (baseUri.IsAbsoluteUri) - { - return Uri.UnescapeDataString(baseUri.MakeRelativeUri(fileUri).ToString()); - } - else - { - return baseUri.ToString(); - } - } -} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/StyleBundler.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/StyleBundler.cs index 0e46b91060..552c815ced 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/StyleBundler.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/Styles/StyleBundler.cs @@ -2,6 +2,7 @@ using System.IO; using System.Text; using Volo.Abp.Bundling; +using Volo.Abp.Bundling.Styles; using Volo.Abp.DependencyInjection; using Volo.Abp.Minify.Styles; @@ -44,7 +45,7 @@ public class StyleBundler : BundlerBase, IStyleBundler, ITransientDependency protected override string ProcessBeforeAddingToTheBundle(string referencePath, string bundleDirectory, string fileContent) { - return CssRelativePathAdjuster.Adjust( + return CssRelativePath.Adjust( fileContent, referencePath, bundleDirectory diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/InstallLibsService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/InstallLibsService.cs index dcdd670b68..212e09c631 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/InstallLibsService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/InstallLibsService.cs @@ -50,7 +50,7 @@ public class InstallLibsService : IInstallLibsService, ITransientDependency if (!NpmHelper.IsYarnAvailable()) { - Logger.LogWarning("YARN is not installed, which may cause package inconsistency, please use YARN instead of NPM. visit https://classic.yarnpkg.com/lang/en/docs/install/ and install YARN"); + Logger.LogWarning("YARN is not installed, which may cause package inconsistency. ABP uses 'npx yarn ' behind the scenes to prevent possible inconsistencies."); } Logger.LogInformation($"Found {projectPaths.Count} projects."); @@ -66,14 +66,7 @@ public class InstallLibsService : IInstallLibsService, ITransientDependency // angular if (projectPath.EndsWith("angular.json")) { - if (NpmHelper.IsYarnAvailable()) - { - NpmHelper.RunYarn(projectDirectory); - } - else - { - NpmHelper.RunNpmInstall(projectDirectory); - } + NpmHelper.RunYarn(projectDirectory); } // MVC or BLAZOR SERVER @@ -86,14 +79,7 @@ public class InstallLibsService : IInstallLibsService, ITransientDependency continue; } - if (NpmHelper.IsYarnAvailable()) - { - NpmHelper.RunYarn(projectDirectory); - } - else - { - NpmHelper.RunNpmInstall(projectDirectory); - } + NpmHelper.RunYarn(projectDirectory); await CleanAndCopyResources(projectDirectory); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs index 019feb93d0..2c88f1e8c5 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs @@ -358,7 +358,7 @@ public class NpmPackagesUpdater : ITransientDependency protected virtual void RunYarn(string fileDirectory) { Logger.LogInformation($"Running Yarn on {fileDirectory}"); - CmdHelper.RunCmd($"yarn", fileDirectory); + CmdHelper.RunCmd($"npx yarn", fileDirectory); } protected virtual void RunNpmInstall(string fileDirectory) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNpmPackageAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNpmPackageAdder.cs index c9aade0feb..07705808ae 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNpmPackageAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNpmPackageAdder.cs @@ -75,7 +75,7 @@ public class ProjectNpmPackageAdder : ITransientDependency using (DirectoryHelper.ChangeCurrentDirectory(directory)) { Logger.LogInformation("yarn add " + npmPackage.Name + versionPostfix); - CmdHelper.RunCmd("yarn add " + npmPackage.Name + versionPostfix); + CmdHelper.RunCmd("npx yarn add " + npmPackage.Name + versionPostfix); } } else @@ -130,7 +130,7 @@ public class ProjectNpmPackageAdder : ITransientDependency using (DirectoryHelper.ChangeCurrentDirectory(directory)) { Logger.LogInformation("yarn add " + npmPackage.Name + versionPostfix); - CmdHelper.RunCmd("yarn add " + npmPackage.Name + versionPostfix); + CmdHelper.RunCmd("npx yarn add " + npmPackage.Name + versionPostfix); if (skipInstallingLibs) { @@ -158,7 +158,7 @@ public class ProjectNpmPackageAdder : ITransientDependency using (DirectoryHelper.ChangeCurrentDirectory(directory)) { Logger.LogInformation("yarn remove " + npmPackage.Name); - CmdHelper.RunCmd("yarn remove " + npmPackage.Name); + CmdHelper.RunCmd("npx yarn remove " + npmPackage.Name); if (skipInstallingLibs) { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ThemePackageAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ThemePackageAdder.cs index 511943b632..62984ea6af 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ThemePackageAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ThemePackageAdder.cs @@ -43,18 +43,11 @@ public class ThemePackageAdder : ITransientDependency return; } - var yarnAvailable = NpmHelper.IsYarnAvailable(); foreach (var packageJsonFilePath in packageJsonFilePaths) { var directory = Path.GetDirectoryName(packageJsonFilePath).EnsureEndsWith(Path.DirectorySeparatorChar); - if (yarnAvailable) - { - NpmHelper.YarnAddPackage(package, version, directory); - } - else - { - NpmHelper.NpmInstallPackage(package, version, directory); - } + + NpmHelper.YarnAddPackage(package, version, directory); } } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs index 9f23645fb9..4ece393913 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using NuGet.Versioning; using System.IO; using System.Linq; @@ -39,6 +40,7 @@ public class VoloNugetPackagesVersionUpdater : ITransientDependency var latestVersionInfo = await _packageVersionCheckerService.GetLatestVersionOrNullAsync("Volo.Abp.Core", includeReleaseCandidates: includeReleaseCandidates); var latestReleaseCandidateVersionInfo = await _packageVersionCheckerService.GetLatestVersionOrNullAsync("Volo.Abp.Core", includeReleaseCandidates: true); var latestVersionFromMyGet = await GetLatestVersionFromMyGet("Volo.Abp.Core"); + var latestStableVersions = await _packageVersionCheckerService.GetLatestStableVersionsAsync(); async Task UpdateAsync(string filePath) { @@ -55,7 +57,8 @@ public class VoloNugetPackagesVersionUpdater : ITransientDependency latestVersionInfo.Version, latestReleaseCandidateVersionInfo.Version, latestVersionFromMyGet, - version); + version, + latestStableVersions: latestStableVersions); fs.Seek(0, SeekOrigin.Begin); fs.SetLength(0); @@ -83,6 +86,7 @@ public class VoloNugetPackagesVersionUpdater : ITransientDependency var latestVersionInfo = await _packageVersionCheckerService.GetLatestVersionOrNullAsync("Volo.Abp.Core"); var latestReleaseCandidateVersionInfo = await _packageVersionCheckerService.GetLatestVersionOrNullAsync("Volo.Abp.Core", includeReleaseCandidates: true); var latestVersionFromMyGet = await GetLatestVersionFromMyGet("Volo.Abp.Core"); + var latestStableVersions = await _packageVersionCheckerService.GetLatestStableVersionsAsync(); using (var fs = File.Open(projectPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) { @@ -97,7 +101,8 @@ public class VoloNugetPackagesVersionUpdater : ITransientDependency latestVersionInfo.Version, latestReleaseCandidateVersionInfo.Version, latestVersionFromMyGet, - version); + version, + latestStableVersions: latestStableVersions); fs.Seek(0, SeekOrigin.Begin); fs.SetLength(0); @@ -114,13 +119,20 @@ public class VoloNugetPackagesVersionUpdater : ITransientDependency protected virtual async Task UpdateInternalAsync(string projectPath, bool includeNightlyPreviews = false, bool includeReleaseCandidates = false, bool switchToStable = false) { + var latestStableVersions = await _packageVersionCheckerService.GetLatestStableVersionsAsync(); + using (var fs = File.Open(projectPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) { using (var sr = new StreamReader(fs, Encoding.Default, true)) { var fileContent = await sr.ReadToEndAsync(); - var updatedContent = await UpdateVoloPackagesAsync(fileContent, includeNightlyPreviews, includeReleaseCandidates, switchToStable); + var updatedContent = await UpdateVoloPackagesAsync( + fileContent, + includeNightlyPreviews, + includeReleaseCandidates, + switchToStable, + latestStableVersions: latestStableVersions); fs.Seek(0, SeekOrigin.Begin); fs.SetLength(0); @@ -153,7 +165,8 @@ public class VoloNugetPackagesVersionUpdater : ITransientDependency SemanticVersion latestNugetVersion = null, SemanticVersion latestNugetReleaseCandidateVersion = null, string latestMyGetVersion = null, - string specifiedVersion = null) + string specifiedVersion = null, + List latestStableVersions = null) { string packageId = null; @@ -206,29 +219,39 @@ public class VoloNugetPackagesVersionUpdater : ITransientDependency if (!specifiedVersion.IsNullOrWhiteSpace()) { - if (isLeptonXPackage || isStudioPackage) + var leptonXPackageVersion = latestStableVersions? + .FirstOrDefault(v => v.Version.Equals(specifiedVersion, StringComparison.InvariantCultureIgnoreCase))?.LeptonX?.Version; + + if ((isLeptonXPackage && string.IsNullOrWhiteSpace(leptonXPackageVersion)) || isStudioPackage) { Logger.LogWarning("Package: {PackageId} could not be updated. Please manually update the package version yourself to prevent version mismatches!", packageId); continue; } - if (await SpecifiedVersionExists(specifiedVersion, packageId)) + var isLeptonXPackageWithVersion = isLeptonXPackage && !string.IsNullOrWhiteSpace(leptonXPackageVersion); + + if (isLeptonXPackageWithVersion || await SpecifiedVersionExists(specifiedVersion, packageId)) + { + TryUpdatingPackage(isLeptonXPackageWithVersion ? leptonXPackageVersion : specifiedVersion); + } + else { - var specifiedSemanticVersion = SemanticVersion.Parse(specifiedVersion); + Logger.LogWarning("Package \"{PackageId}\" specified version v{SpecifiedVersion} does not exist!", packageId, specifiedVersion); + } + + void TryUpdatingPackage(string versionToUpdate) + { + var specifiedSemanticVersion = SemanticVersion.Parse(versionToUpdate); if (specifiedSemanticVersion > currentSemanticVersion) { - Logger.LogInformation("Updating package \"{PackageId}\" from v{CurrentVersion} to v{SpecifiedVersion}", packageId, currentVersion, specifiedVersion); - versionAttribute.Value = specifiedVersion; + Logger.LogInformation("Updating package \"{PackageId}\" from v{CurrentVersion} to v{SpecifiedVersion}", packageId, currentVersion, versionToUpdate); + versionAttribute.Value = versionToUpdate; } else { - Logger.LogWarning("Unable to update package \"{PackageId}\" version v{CurrentVersion} to v{SpecifiedVersion}", packageId, currentVersion, specifiedVersion); + Logger.LogWarning("Unable to update package \"{PackageId}\" version v{CurrentVersion} to v{SpecifiedVersion}", packageId, currentVersion, versionToUpdate); } } - else - { - Logger.LogWarning("Package \"{PackageId}\" specified version v{SpecifiedVersion} does not exist!", packageId, specifiedVersion); - } } else { @@ -252,7 +275,7 @@ public class VoloNugetPackagesVersionUpdater : ITransientDependency } else { - latestVersion = latestMyGetVersion == null ? await GetLatestVersionFromMyGet(packageId) : latestMyGetVersion; + latestVersion = latestMyGetVersion ?? await GetLatestVersionFromMyGet(packageId); } if(latestVersion == null) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/NpmHelper.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/NpmHelper.cs index a2d75d48d9..d3dd24c53e 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/NpmHelper.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/NpmHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -48,18 +49,20 @@ public class NpmHelper : ITransientDependency return version > SemanticVersion.Parse("1.20.0"); } - public void RunNpmInstall(string directory) + [Obsolete("This method is deprecated. Use 'RunYarn' instead (it uses 'npx', so there is no need for 'yarn' to be globally installed.")] + public void RunNpmInstall(string directory, params string[] args) { Logger.LogInformation($"Running npm install on {directory}"); - CmdHelper.RunCmd($"npm install", directory); + CmdHelper.RunCmd($"npm install {args.JoinAsString(" ")}", directory); } public void RunYarn(string directory) { Logger.LogInformation($"Running Yarn on {directory}"); - CmdHelper.RunCmd($"yarn", directory); + CmdHelper.RunCmd($"npx yarn", directory); } + [Obsolete("This method is deprecated. Use 'YarnAddPackage' instead (it uses 'npx', so there is no need for 'yarn' to be globally installed.")] public void NpmInstallPackage(string package, string version, string directory) { var packageVersion = !string.IsNullOrWhiteSpace(version) ? $"@{version}" : string.Empty; @@ -69,7 +72,7 @@ public class NpmHelper : ITransientDependency public void YarnAddPackage(string package, string version, string directory) { var packageVersion = !string.IsNullOrWhiteSpace(version) ? $"@{version}" : string.Empty; - CmdHelper.RunCmd("yarn add " + package + packageVersion, workingDirectory: directory); + CmdHelper.RunCmd("npx yarn add " + package + packageVersion, workingDirectory: directory); } public string GetInstalledNpmPackages() diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Version/PackageVersionCheckerService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Version/PackageVersionCheckerService.cs index d5764c1c0e..316c53e01e 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Version/PackageVersionCheckerService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Version/PackageVersionCheckerService.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.Cli.Http; @@ -111,6 +112,11 @@ public class PackageVersionCheckerService : ITransientDependency ? new LatestVersionInfo(semanticVersion, latestStableVersionResult.Message) : null; } + + public async Task> GetLatestStableVersionsAsync() + { + return await GetLatestStableVersionsInternalAsync(); + } private static ConcurrentDictionary CommercialPackagesCache { get; } = new (); @@ -225,7 +231,7 @@ public class PackageVersionCheckerService : ITransientDependency _apiKeyResult ??= await _apiKeyService.GetApiKeyOrNullAsync(); } - private async Task GetLatestStableVersionOrNullAsync() + private async Task> GetLatestStableVersionsInternalAsync() { try { @@ -241,16 +247,24 @@ public class PackageVersionCheckerService : ITransientDependency var content = await responseMessage.Content.ReadAsStringAsync(); var result = JsonSerializer.Deserialize>(content); - - return result.FirstOrDefault(x => x.Type.ToLowerInvariant() == "stable"); + return result.OrderBy(q => q.Type.ToLowerInvariant() == "stable").ThenBy(q => SemanticVersion.Parse(q.Version)).ToList(); } } catch { - return null; + return []; } } + private async Task GetLatestStableVersionOrNullAsync() + { + var latestStableVersionsResult = await GetLatestStableVersionsInternalAsync(); + + return latestStableVersionsResult.Count <= 0 + ? null + : latestStableVersionsResult.FirstOrDefault(); + } + public class NuGetSearchResultDto { public int TotalHits { get; set; } @@ -270,6 +284,11 @@ public class PackageVersionCheckerService : ITransientDependency public List Versions { get; set; } } + public class LeptonXThemeInfo + { + public string Version { get; set; } + } + public class LatestStableVersionResult { public string Version { get; set; } @@ -279,5 +298,8 @@ public class PackageVersionCheckerService : ITransientDependency public string Type { get; set; } public string Message { get; set; } + + [CanBeNull] + public LeptonXThemeInfo LeptonX { get; set; } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/Styles/CssRelativePath.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Bundling/Styles/CssRelativePath.cs similarity index 57% rename from framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/Styles/CssRelativePath.cs rename to framework/src/Volo.Abp.Core/Volo/Abp/Bundling/Styles/CssRelativePath.cs index c41f0ca4a4..4ee53124b0 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/Styles/CssRelativePath.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Bundling/Styles/CssRelativePath.cs @@ -2,18 +2,18 @@ using System.IO; using System.Text.RegularExpressions; -namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.Styles; +namespace Volo.Abp.Bundling.Styles; -internal static class CssRelativePath +public static class CssRelativePath { - private static readonly Regex _rxUrl = new Regex(@"url\s*\(\s*([""']?)([^:)]+)\1\s*\)", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private readonly static Regex RxUrl = new Regex(@"url\s*\(\s*([""']?)([^:)]+)\1\s*\)", RegexOptions.IgnoreCase | RegexOptions.Compiled); public static string Adjust( string cssFileContents, string absoluteInputFilePath, string absoluteOutputPath) { - var matches = _rxUrl.Matches(cssFileContents); + var matches = RxUrl.Matches(cssFileContents); if (matches.Count <= 0) { @@ -24,25 +24,29 @@ internal static class CssRelativePath foreach (Match match in matches) { - string quoteDelimiter = match.Groups[1].Value; //url('') vs url("") - string relativePathToCss = match.Groups[2].Value; + var quoteDelimiter = match.Groups[1].Value; //url('') vs url("") + var relativePathToCss = match.Groups[2].Value; // Ignore root relative references if (relativePathToCss.StartsWith("/", StringComparison.Ordinal)) + { continue; + } //prevent query string from causing error var pathAndQuery = relativePathToCss.Split(new[] { '?' }, 2, StringSplitOptions.RemoveEmptyEntries); var pathOnly = pathAndQuery[0]; var queryOnly = pathAndQuery.Length == 2 ? pathAndQuery[1] : string.Empty; - string absolutePath = GetAbsolutePath(cssDirectoryPath, pathOnly); - string serverRelativeUrl = MakeRelative(absoluteOutputPath, absolutePath); + var absolutePath = GetAbsolutePath(cssDirectoryPath, pathOnly); + var serverRelativeUrl = MakeRelative(absoluteOutputPath, absolutePath); if (!string.IsNullOrEmpty(queryOnly)) + { serverRelativeUrl += "?" + queryOnly; + } - string replace = string.Format("url({0}{1}{0})", quoteDelimiter, serverRelativeUrl); + var replace = string.Format("url({0}{1}{0})", quoteDelimiter, serverRelativeUrl); cssFileContents = cssFileContents.Replace(match.Groups[0].Value, replace); } @@ -55,22 +59,18 @@ internal static class CssRelativePath return Path.GetFullPath(Path.Combine(cssFilePath, pathOnly)); } - private static readonly string _protocol = "file:///"; + private const string Protocol = "file:///"; + private static string MakeRelative(string baseFile, string file) { if (string.IsNullOrEmpty(file)) + { return file; + } - Uri baseUri = new Uri(_protocol + baseFile, UriKind.RelativeOrAbsolute); - Uri fileUri = new Uri(_protocol + file, UriKind.RelativeOrAbsolute); + var baseUri = new Uri(Protocol + baseFile, UriKind.RelativeOrAbsolute); + var fileUri = new Uri(Protocol + file, UriKind.RelativeOrAbsolute); - if (baseUri.IsAbsoluteUri) - { - return Uri.UnescapeDataString(baseUri.MakeRelativeUri(fileUri).ToString()); - } - else - { - return baseUri.ToString(); - } + return baseUri.IsAbsoluteUri ? Uri.UnescapeDataString(baseUri.MakeRelativeUri(fileUri).ToString()) : baseUri.ToString(); } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs index 1efa6c4f29..f9fdea3e50 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs @@ -84,6 +84,14 @@ public static class TypeHelper return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); } + public static bool IsNullableEnum(Type type) + { + return type.IsGenericType && + type.GetGenericTypeDefinition() == typeof(Nullable<>) && + type.GenericTypeArguments.Length == 1 && + type.GenericTypeArguments[0].IsEnum; + } + public static Type GetFirstGenericArgumentIfNullable(this Type t) { if (t.GetGenericArguments().Length > 0 && t.GetGenericTypeDefinition() == typeof(Nullable<>)) @@ -309,6 +317,10 @@ public static class TypeHelper { return "object"; } + else if (type.IsEnum) + { + return "enum"; + } return type.FullName ?? type.Name; } diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/EmailSenderBase.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/EmailSenderBase.cs index dacbb195ab..4f07c8a1c5 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/EmailSenderBase.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/EmailSenderBase.cs @@ -92,7 +92,7 @@ public abstract class EmailSenderBase : IEmailSender public virtual async Task QueueAsync(string to, string subject, string body, bool isBodyHtml = true, AdditionalEmailSendingArgs? additionalEmailSendingArgs = null) { - ValidateEmailAddress(to); + await ValidateEmailAddressAsync(to); if (!BackgroundJobManager.IsAvailable()) { @@ -115,7 +115,7 @@ public abstract class EmailSenderBase : IEmailSender public virtual async Task QueueAsync(string from, string to, string subject, string body, bool isBodyHtml = true, AdditionalEmailSendingArgs? additionalEmailSendingArgs = null) { - ValidateEmailAddress(to); + await ValidateEmailAddressAsync(to); if (!BackgroundJobManager.IsAvailable()) { @@ -176,13 +176,16 @@ public abstract class EmailSenderBase : IEmailSender } } - private static void ValidateEmailAddress(string emailAddress) + protected virtual Task ValidateEmailAddressAsync(string emailAddress) { - if(ValidationHelper.IsValidEmailAddress(emailAddress)) + try { - return; + _ = new MailAddressCollection { emailAddress }; + return Task.CompletedTask; + } + catch (Exception e) + { + throw new ArgumentException($"Email address '{emailAddress}' is not valid!"); } - - throw new ArgumentException($"Email address '{emailAddress}' is not valid!"); } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj index db32e24bef..bfe9bea75a 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj @@ -22,7 +22,6 @@ -
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo.Abp.EntityFrameworkCore.Oracle.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo.Abp.EntityFrameworkCore.Oracle.csproj index 2948ef0cfb..8f14308ed6 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo.Abp.EntityFrameworkCore.Oracle.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo.Abp.EntityFrameworkCore.Oracle.csproj @@ -21,7 +21,6 @@ - diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/DbContextEventInbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/DbContextEventInbox.cs index f5953ea3b1..54ddfd0e5e 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/DbContextEventInbox.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/DbContextEventInbox.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; @@ -36,14 +37,21 @@ public class DbContextEventInbox : IDbContextEventInbox } [UnitOfWork] - public virtual async Task> GetWaitingEventsAsync(int maxCount, CancellationToken cancellationToken = default) + public virtual async Task> GetWaitingEventsAsync(int maxCount, Expression>? filter = null, CancellationToken cancellationToken = default) { var dbContext = await DbContextProvider.GetDbContextAsync(); + Expression>? transformedFilter = null; + if (filter != null) + { + transformedFilter = InboxOutboxFilterExpressionTransformer.Transform(filter)!; + } + var outgoingEventRecords = await dbContext .IncomingEvents .AsNoTracking() .Where(x => !x.Processed) + .WhereIf(transformedFilter != null, transformedFilter!) .OrderBy(x => x.CreationTime) .Take(maxCount) .ToListAsync(cancellationToken: cancellationToken); diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/DbContextEventOutbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/DbContextEventOutbox.cs index fecfd1e8ce..a541698b3e 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/DbContextEventOutbox.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/DbContextEventOutbox.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; @@ -28,13 +29,20 @@ public class DbContextEventOutbox : IDbContextEventOutbox> GetWaitingEventsAsync(int maxCount, CancellationToken cancellationToken = default) + public virtual async Task> GetWaitingEventsAsync(int maxCount, Expression>? filter = null, CancellationToken cancellationToken = default) { var dbContext = (IHasEventOutbox)await DbContextProvider.GetDbContextAsync(); + Expression>? transformedFilter = null; + if (filter != null) + { + transformedFilter = InboxOutboxFilterExpressionTransformer.Transform(filter)!; + } + var outgoingEventRecords = await dbContext .OutgoingEvents .AsNoTracking() + .WhereIf(transformedFilter != null, transformedFilter!) .OrderBy(x => x.CreationTime) .Take(maxCount) .ToListAsync(cancellationToken: cancellationToken); diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IncomingEventRecord.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IncomingEventRecord.cs index 6cb37a72d5..be7da15890 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IncomingEventRecord.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IncomingEventRecord.cs @@ -8,6 +8,7 @@ namespace Volo.Abp.EntityFrameworkCore.DistributedEvents; public class IncomingEventRecord : BasicAggregateRoot, + IIncomingEventInfo, IHasExtraProperties, IHasCreationTime { diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/OutgoingEventRecord.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/OutgoingEventRecord.cs index 7272c9ac30..625a93b25a 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/OutgoingEventRecord.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/OutgoingEventRecord.cs @@ -8,6 +8,7 @@ namespace Volo.Abp.EntityFrameworkCore.DistributedEvents; public class OutgoingEventRecord : BasicAggregateRoot, + IOutgoingEventInfo, IHasExtraProperties, IHasCreationTime { diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/EfCoreRuntimeDatabaseMigratorBase.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/EfCoreRuntimeDatabaseMigratorBase.cs index 9b9719ff1b..ff557a2a73 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/EfCoreRuntimeDatabaseMigratorBase.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/EfCoreRuntimeDatabaseMigratorBase.cs @@ -135,7 +135,7 @@ public abstract class EfCoreRuntimeDatabaseMigratorBase : ITransient throw; } - Logger.LogWarning($"{ex.GetType().Name} has been thrown. The operation will be tried {maxTryCount} times more. Exception:\n{ex.Message}. Stack Trace:\n{ex.StackTrace}"); + Logger.LogWarning(ex, $"{ex.GetType().Name} has been thrown. The operation will be tried {maxTryCount} times more."); await Task.Delay(RandomHelper.GetRandom(MinValueToWaitOnFailure, MaxValueToWaitOnFailure)); diff --git a/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IEventInbox.cs b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IEventInbox.cs index 3700f74232..c154330495 100644 --- a/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IEventInbox.cs +++ b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IEventInbox.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; @@ -9,7 +10,7 @@ public interface IEventInbox { Task EnqueueAsync(IncomingEventInfo incomingEvent); - Task> GetWaitingEventsAsync(int maxCount, CancellationToken cancellationToken = default); + Task> GetWaitingEventsAsync(int maxCount, Expression>? filter = null, CancellationToken cancellationToken = default); Task MarkAsProcessedAsync(Guid id); diff --git a/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IEventOutbox.cs b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IEventOutbox.cs index 018747945c..bc538ef839 100644 --- a/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IEventOutbox.cs +++ b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IEventOutbox.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; @@ -9,7 +10,7 @@ public interface IEventOutbox { Task EnqueueAsync(OutgoingEventInfo outgoingEvent); - Task> GetWaitingEventsAsync(int maxCount, CancellationToken cancellationToken = default); + Task> GetWaitingEventsAsync(int maxCount, Expression>? filter = null, CancellationToken cancellationToken = default); Task DeleteAsync(Guid id); diff --git a/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IIncomingEventInfo.cs b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IIncomingEventInfo.cs new file mode 100644 index 0000000000..e52325b71f --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IIncomingEventInfo.cs @@ -0,0 +1,17 @@ +using System; +using Volo.Abp.Data; + +namespace Volo.Abp.EventBus.Distributed; + +public interface IIncomingEventInfo : IHasExtraProperties +{ + Guid Id { get; } + + string MessageId { get; } + + string EventName { get; } + + byte[] EventData { get; } + + DateTime CreationTime { get; } +} diff --git a/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IOutgoingEventInfo.cs b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IOutgoingEventInfo.cs new file mode 100644 index 0000000000..58dd4a9713 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IOutgoingEventInfo.cs @@ -0,0 +1,15 @@ +using System; +using Volo.Abp.Data; + +namespace Volo.Abp.EventBus.Distributed; + +public interface IOutgoingEventInfo : IHasExtraProperties +{ + Guid Id { get; } + + string EventName { get; } + + byte[] EventData { get; } + + DateTime CreationTime { get; } +} diff --git a/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/InboxOutboxFilterExpressionTransformer.cs b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/InboxOutboxFilterExpressionTransformer.cs new file mode 100644 index 0000000000..935e760546 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/InboxOutboxFilterExpressionTransformer.cs @@ -0,0 +1,38 @@ +using System; +using System.Linq.Expressions; + +namespace Volo.Abp.EventBus.Distributed; + +public static class InboxOutboxFilterExpressionTransformer +{ + public static Expression> Transform(Expression> originalExpression) + { + var originalParam = originalExpression.Parameters[0]; + var newParam = Expression.Parameter(typeof(TTarget), originalParam.Name); + var body = ReplaceParameter(originalExpression.Body, originalParam, newParam); + return Expression.Lambda>(body, newParam); + } + + private static Expression ReplaceParameter(Expression body, ParameterExpression oldParam, ParameterExpression newParam) + { + var visitor = new ParameterReplacer(oldParam, newParam); + return visitor.Visit(body); + } + + private class ParameterReplacer : ExpressionVisitor + { + private readonly ParameterExpression _oldParam; + private readonly ParameterExpression _newParam; + + public ParameterReplacer(ParameterExpression oldParam, ParameterExpression newParam) + { + _oldParam = oldParam; + _newParam = newParam; + } + + protected override Expression VisitParameter(ParameterExpression node) + { + return node == _oldParam ? _newParam : base.VisitParameter(node); + } + } +} diff --git a/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IncomingEventInfo.cs b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IncomingEventInfo.cs index ffd135845a..1be24a3d02 100644 --- a/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IncomingEventInfo.cs +++ b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IncomingEventInfo.cs @@ -4,7 +4,7 @@ using Volo.Abp.Data; namespace Volo.Abp.EventBus.Distributed; -public class IncomingEventInfo : IHasExtraProperties +public class IncomingEventInfo : IIncomingEventInfo { public static int MaxEventNameLength { get; set; } = 256; diff --git a/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/OutgoingEventInfo.cs b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/OutgoingEventInfo.cs index 43e9c42bf8..74b5bca7d4 100644 --- a/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/OutgoingEventInfo.cs +++ b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/OutgoingEventInfo.cs @@ -4,7 +4,7 @@ using Volo.Abp.Data; namespace Volo.Abp.EventBus.Distributed; -public class OutgoingEventInfo : IHasExtraProperties +public class OutgoingEventInfo : IOutgoingEventInfo { public static int MaxEventNameLength { get; set; } = 256; diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/AbpEventBusRabbitMqModule.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/AbpEventBusRabbitMqModule.cs index 5d3db22726..92399b43b0 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/AbpEventBusRabbitMqModule.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/AbpEventBusRabbitMqModule.cs @@ -20,7 +20,7 @@ public class AbpEventBusRabbitMqModule : AbpModule { context .ServiceProvider - .GetRequiredService() + .GetRequiredService() .Initialize(); } } diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/IRabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/IRabbitMqDistributedEventBus.cs new file mode 100644 index 0000000000..fd88a098a2 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/IRabbitMqDistributedEventBus.cs @@ -0,0 +1,8 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EventBus.RabbitMq; + +public interface IRabbitMqDistributedEventBus : IDistributedEventBus +{ + void Initialize(); +} 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 1c8012f529..c84aa6d9e9 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 @@ -23,8 +23,8 @@ namespace Volo.Abp.EventBus.RabbitMq; /* TODO: How to handle unsubscribe to unbind on RabbitMq (may not be possible for) */ [Dependency(ReplaceServices = true)] -[ExposeServices(typeof(IDistributedEventBus), typeof(RabbitMqDistributedEventBus))] -public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDependency +[ExposeServices(typeof(IDistributedEventBus), typeof(RabbitMqDistributedEventBus), typeof(IRabbitMqDistributedEventBus))] +public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDistributedEventBus, ISingletonDependency { protected AbpRabbitMqEventBusOptions AbpRabbitMqEventBusOptions { get; } protected IConnectionPool ConnectionPool { get; } @@ -72,7 +72,7 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe EventTypes = new ConcurrentDictionary(); } - public void Initialize() + public virtual void Initialize() { Consumer = MessageConsumerFactory.Create( new ExchangeDeclareConfiguration( @@ -290,7 +290,7 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe var eventName = EventNameAttribute.GetNameOrDefault(eventType); var body = Serializer.Serialize(eventData); - return PublishAsync( eventName, body, headersArguments, eventId, correlationId); + return PublishAsync(eventName, body, headersArguments, eventId, correlationId); } protected virtual Task PublishAsync( diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs index 67facf5d42..cc91cad5df 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq.Expressions; namespace Volo.Abp.EventBus.Distributed; @@ -14,11 +15,21 @@ public class AbpEventBusBoxesOptions /// public int InboxWaitingEventMaxCount { get; set; } + /// + /// Default: null, means all events + /// + public Expression>? InboxProcessorFilter { get; set; } + /// /// Default: 1000 /// public int OutboxWaitingEventMaxCount { get; set; } + /// + /// Default: null, means all events + /// + public Expression>? OutboxProcessorFilter { get; set; } + /// /// Period time of and /// Default: 2 seconds @@ -34,7 +45,7 @@ public class AbpEventBusBoxesOptions /// Default: 2 hours /// public TimeSpan WaitTimeToDeleteProcessedInboxEvents { get; set; } - + /// /// Default: true /// @@ -44,7 +55,9 @@ public class AbpEventBusBoxesOptions { CleanOldEventTimeIntervalSpan = TimeSpan.FromHours(6); InboxWaitingEventMaxCount = 1000; + InboxProcessorFilter = null; OutboxWaitingEventMaxCount = 1000; + OutboxProcessorFilter = null; PeriodTimeSpan = TimeSpan.FromSeconds(2); DistributedLockWaitDuration = TimeSpan.FromSeconds(15); WaitTimeToDeleteProcessedInboxEvents = TimeSpan.FromHours(2); 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 72a20748c6..96a11f928e 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 @@ -132,7 +132,13 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB Serialize(eventData), Clock.Now ); - outgoingEventInfo.SetCorrelationId(CorrelationIdProvider.Get()!); + + var correlationId = CorrelationIdProvider.Get(); + if (correlationId != null) + { + outgoingEventInfo.SetCorrelationId(correlationId); + } + await eventOutbox.EnqueueAsync(outgoingEventInfo); return true; } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs index e2c0a3c0c6..06014701a2 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -27,7 +28,7 @@ public class InboxProcessor : IInboxProcessor, ITransientDependency protected DateTime? LastCleanTime { get; set; } - protected string DistributedLockName { get; private set; } = default!; + protected string DistributedLockName { get; set; } = default!; public ILogger Logger { get; set; } protected CancellationTokenSource StoppingTokenSource { get; } protected CancellationToken StoppingToken { get; } @@ -60,7 +61,7 @@ public class InboxProcessor : IInboxProcessor, ITransientDependency await RunAsync(); } - public Task StartAsync(InboxConfig inboxConfig, CancellationToken cancellationToken = default) + public virtual Task StartAsync(InboxConfig inboxConfig, CancellationToken cancellationToken = default) { InboxConfig = inboxConfig; Inbox = (IEventInbox)ServiceProvider.GetRequiredService(inboxConfig.ImplementationType); @@ -69,7 +70,7 @@ public class InboxProcessor : IInboxProcessor, ITransientDependency return Task.CompletedTask; } - public Task StopAsync(CancellationToken cancellationToken = default) + public virtual Task StopAsync(CancellationToken cancellationToken = default) { StoppingTokenSource.Cancel(); Timer.Stop(cancellationToken); @@ -92,7 +93,7 @@ public class InboxProcessor : IInboxProcessor, ITransientDependency while (true) { - var waitingEvents = await Inbox.GetWaitingEventsAsync(EventBusBoxesOptions.InboxWaitingEventMaxCount, StoppingToken); + var waitingEvents = await GetWaitingEventsAsync(); if (waitingEvents.Count <= 0) { break; @@ -129,6 +130,11 @@ public class InboxProcessor : IInboxProcessor, ITransientDependency } } + protected virtual async Task> GetWaitingEventsAsync() + { + return await Inbox.GetWaitingEventsAsync(EventBusBoxesOptions.InboxWaitingEventMaxCount, EventBusBoxesOptions.InboxProcessorFilter, StoppingToken); + } + protected virtual async Task DeleteOldEventsAsync() { if (LastCleanTime != null && LastCleanTime + EventBusBoxesOptions.CleanOldEventTimeIntervalSpan > Clock.Now) 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 b63133a388..04d07259a3 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 @@ -81,88 +81,88 @@ public class LocalDistributedEventBus : IDistributedEventBus, ISingletonDependen return Subscribe(typeof(TEvent), handler); } - public IDisposable Subscribe(Func action) where TEvent : class + public virtual IDisposable Subscribe(Func action) where TEvent : class { return _localEventBus.Subscribe(action); } - public IDisposable Subscribe(ILocalEventHandler handler) where TEvent : class + public virtual IDisposable Subscribe(ILocalEventHandler handler) where TEvent : class { return _localEventBus.Subscribe(handler); } - public IDisposable Subscribe() where TEvent : class where THandler : IEventHandler, new() + public virtual IDisposable Subscribe() where TEvent : class where THandler : IEventHandler, new() { return _localEventBus.Subscribe(); } - public IDisposable Subscribe(Type eventType, IEventHandler handler) + public virtual IDisposable Subscribe(Type eventType, IEventHandler handler) { return _localEventBus.Subscribe(eventType, handler); } - public IDisposable Subscribe(IEventHandlerFactory factory) where TEvent : class + public virtual IDisposable Subscribe(IEventHandlerFactory factory) where TEvent : class { return _localEventBus.Subscribe(factory); } - public IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) + public virtual IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) { return _localEventBus.Subscribe(eventType, factory); } - public void Unsubscribe(Func action) where TEvent : class + public virtual void Unsubscribe(Func action) where TEvent : class { _localEventBus.Unsubscribe(action); } - public void Unsubscribe(ILocalEventHandler handler) where TEvent : class + public virtual void Unsubscribe(ILocalEventHandler handler) where TEvent : class { _localEventBus.Unsubscribe(handler); } - public void Unsubscribe(Type eventType, IEventHandler handler) + public virtual void Unsubscribe(Type eventType, IEventHandler handler) { _localEventBus.Unsubscribe(eventType, handler); } - public void Unsubscribe(IEventHandlerFactory factory) where TEvent : class + public virtual void Unsubscribe(IEventHandlerFactory factory) where TEvent : class { _localEventBus.Unsubscribe(factory); } - public void Unsubscribe(Type eventType, IEventHandlerFactory factory) + public virtual void Unsubscribe(Type eventType, IEventHandlerFactory factory) { _localEventBus.Unsubscribe(eventType, factory); } - public void UnsubscribeAll() where TEvent : class + public virtual void UnsubscribeAll() where TEvent : class { _localEventBus.UnsubscribeAll(); } - public void UnsubscribeAll(Type eventType) + public virtual void UnsubscribeAll(Type eventType) { _localEventBus.UnsubscribeAll(eventType); } - public Task PublishAsync(TEvent eventData, bool onUnitOfWorkComplete = true) + public virtual Task PublishAsync(TEvent eventData, bool onUnitOfWorkComplete = true) where TEvent : class { return _localEventBus.PublishAsync(eventData, onUnitOfWorkComplete); } - public Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true) + public virtual Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true) { return _localEventBus.PublishAsync(eventType, eventData, onUnitOfWorkComplete); } - public Task PublishAsync(TEvent eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true) where TEvent : class + public virtual Task PublishAsync(TEvent eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true) where TEvent : class { return _localEventBus.PublishAsync(eventData, onUnitOfWorkComplete); } - public Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true) + public virtual Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true) { return _localEventBus.PublishAsync(eventType, eventData, onUnitOfWorkComplete); } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs index 99ee06ca10..2e610d4471 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs @@ -22,7 +22,7 @@ public class OutboxSender : IOutboxSender, ITransientDependency protected IEventOutbox Outbox { get; private set; } = default!; protected OutboxConfig OutboxConfig { get; private set; } = default!; protected AbpEventBusBoxesOptions EventBusBoxesOptions { get; } - protected string DistributedLockName { get; private set; } = default!; + protected string DistributedLockName { get; set; } = default!; public ILogger Logger { get; set; } protected CancellationTokenSource StoppingTokenSource { get; } @@ -77,14 +77,14 @@ public class OutboxSender : IOutboxSender, ITransientDependency { while (true) { - var waitingEvents = await Outbox.GetWaitingEventsAsync(EventBusBoxesOptions.OutboxWaitingEventMaxCount, StoppingToken); + var waitingEvents = await GetWaitingEventsAsync(); if (waitingEvents.Count <= 0) { break; } Logger.LogInformation($"Found {waitingEvents.Count} events in the outbox."); - + if (EventBusBoxesOptions.BatchPublishOutboxEvents) { await PublishOutgoingMessagesInBatchAsync(waitingEvents); @@ -107,6 +107,11 @@ public class OutboxSender : IOutboxSender, ITransientDependency } } + protected virtual async Task> GetWaitingEventsAsync() + { + return await Outbox.GetWaitingEventsAsync(EventBusBoxesOptions.OutboxWaitingEventMaxCount, EventBusBoxesOptions.OutboxProcessorFilter, StoppingToken); + } + protected virtual async Task PublishOutgoingMessagesAsync(List waitingEvents) { foreach (var waitingEvent in waitingEvents) @@ -119,7 +124,7 @@ public class OutboxSender : IOutboxSender, ITransientDependency ); await Outbox.DeleteAsync(waitingEvent.Id); - + Logger.LogInformation($"Sent the event to the message broker with id = {waitingEvent.Id:N}"); } } @@ -129,9 +134,9 @@ public class OutboxSender : IOutboxSender, ITransientDependency await DistributedEventBus .AsSupportsEventBoxes() .PublishManyFromOutboxAsync(waitingEvents, OutboxConfig); - + await Outbox.DeleteManyAsync(waitingEvents.Select(x => x.Id).ToArray()); - + Logger.LogInformation($"Sent {waitingEvents.Count} events to message broker"); } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs index ef5c5cdabc..151a149281 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs @@ -135,9 +135,9 @@ public abstract class EventBusBase : IEventBus { await new SynchronizationContextRemover(); - foreach (var handlerFactories in GetHandlerFactories(eventType)) + foreach (var handlerFactories in GetHandlerFactories(eventType).ToList()) { - foreach (var handlerFactory in handlerFactories.EventHandlerFactories) + foreach (var handlerFactory in handlerFactories.EventHandlerFactories.ToList()) { await TriggerHandlerAsync(handlerFactory, handlerFactories.EventType, eventData, exceptions, inboxConfig); } diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs index b954578ba6..4bc2a5433c 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs @@ -52,6 +52,11 @@ public static class FeatureCheckerExtensions return false; } + /// + /// Checks if the specified feature is enabled and throws an if it is not. + /// + /// The + /// The name of the feature to be checked. public static async Task CheckEnabledAsync(this IFeatureChecker featureChecker, string featureName) { if (!(await featureChecker.IsEnabledAsync(featureName))) @@ -61,6 +66,13 @@ public static class FeatureCheckerExtensions } } + /// + /// Checks if the specified features are enabled and throws an if they are not. + /// The check can either require all features to be enabled or just one, based on the parameter. + /// + /// The + /// True: Requires all features to be enabled. False: Requires at least one of the features to be enabled. + /// The names of the features to be checked. public static async Task CheckEnabledAsync(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames) { if (featureNames.IsNullOrEmpty()) diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientOptions.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientOptions.cs index 6743ab5174..bfe52508e4 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientOptions.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientOptions.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Net.Http; +using Volo.Abp.Http.Client.ClientProxying; using Volo.Abp.Http.Client.Proxying; namespace Volo.Abp.Http.Client; @@ -8,8 +10,17 @@ public class AbpHttpClientOptions { public Dictionary HttpClientProxies { get; set; } + public Dictionary>> ProxyHttpClientPreSendActions { get; } + public AbpHttpClientOptions() { HttpClientProxies = new Dictionary(); + ProxyHttpClientPreSendActions = new Dictionary>>(); + } + + public AbpHttpClientOptions AddPreSendAction(string remoteServiceName, Action action) + { + ProxyHttpClientPreSendActions.GetOrAdd(remoteServiceName, () => new List>()).Add(action); + return this; } } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs index a4306050a4..01c1e2a720 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs @@ -146,6 +146,11 @@ public class ClientProxyBase : ITransientDependency HttpResponseMessage response; try { + foreach (var preSendAction in ClientOptions.Value.ProxyHttpClientPreSendActions.Where(x => x.Key == clientConfig.RemoteServiceName).SelectMany(x => x.Value)) + { + preSendAction(clientConfig, requestContext, client); + } + response = await client.SendAsync( requestMessage, HttpCompletionOption.ResponseHeadersRead /*this will buffer only the headers, the content will be used as a stream*/, @@ -309,7 +314,11 @@ public class ClientProxyBase : ITransientDependency } //CorrelationId - requestMessage.Headers.Add(AbpCorrelationIdOptions.Value.HttpHeaderName, CorrelationIdProvider.Get()); + var correlationId = CorrelationIdProvider.Get(); + if (correlationId != null) + { + requestMessage.Headers.Add(AbpCorrelationIdOptions.Value.HttpHeaderName, correlationId); + } //TenantId if (CurrentTenant.Id.HasValue) diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs index 10089c52cd..e54e24ced0 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs @@ -126,7 +126,11 @@ public class ApiDescriptionFinder : IApiDescriptionFinder, ITransientDependency protected virtual void AddHeaders(HttpRequestMessage requestMessage) { //CorrelationId - requestMessage.Headers.Add(AbpCorrelationIdOptions.HttpHeaderName, CorrelationIdProvider.Get()); + var correlationId = CorrelationIdProvider.Get(); + if (correlationId != null) + { + requestMessage.Headers.Add(AbpCorrelationIdOptions.HttpHeaderName, correlationId); + } //TenantId if (CurrentTenant.Id.HasValue) diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationSettingHelper.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationSettingHelper.cs index e24dd32199..860a0c9d96 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationSettingHelper.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationSettingHelper.cs @@ -8,17 +8,25 @@ public static class LocalizationSettingHelper /// Gets a setting value like "en-US;en" and returns as splitted values like ("en-US", "en"). /// /// + /// /// - public static (string cultureName, string uiCultureName) ParseLanguageSetting([NotNull] string settingValue) + public static (string cultureName, string uiCultureName) ParseLanguageSetting([NotNull] string settingValue, string defaultCultureName = "en") { Check.NotNull(settingValue, nameof(settingValue)); if (!settingValue.Contains(";")) { - return (settingValue, settingValue); + return CultureHelper.IsValidCultureCode(settingValue) + ? (settingValue, settingValue) + : (defaultCultureName, defaultCultureName); } var splitted = settingValue.Split(';'); - return (splitted[0], splitted[1]); + if (splitted.Length == 2 && CultureHelper.IsValidCultureCode(splitted[0]) && CultureHelper.IsValidCultureCode(splitted[1])) + { + return (splitted[0], splitted[1]); + } + + return (defaultCultureName, defaultCultureName); } } diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventInbox.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventInbox.cs index 55b40fa29b..6a73af27f5 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventInbox.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventInbox.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Options; @@ -50,16 +51,24 @@ public class MongoDbContextEventInbox : IMongoDbContextEventInb } [UnitOfWork] - public virtual async Task> GetWaitingEventsAsync(int maxCount, CancellationToken cancellationToken = default) + public virtual async Task> GetWaitingEventsAsync(int maxCount, Expression>? filter = null, CancellationToken cancellationToken = default) { var dbContext = await DbContextProvider.GetDbContextAsync(cancellationToken); + Expression>? transformedFilter = null; + if (filter != null) + { + transformedFilter = InboxOutboxFilterExpressionTransformer.Transform(filter)!; + } + var outgoingEventRecords = await dbContext .IncomingEvents .AsQueryable() .Where(x => !x.Processed) + .WhereIf(transformedFilter != null, transformedFilter!) .OrderBy(x => x.CreationTime) .Take(maxCount) + .As>() .ToListAsync(cancellationToken: cancellationToken); return outgoingEventRecords diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs index cc6c4d42df..57b59038b5 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; @@ -40,14 +41,22 @@ public class MongoDbContextEventOutbox : IMongoDbContextEventOu } [UnitOfWork] - public virtual async Task> GetWaitingEventsAsync(int maxCount, CancellationToken cancellationToken = default) + public virtual async Task> GetWaitingEventsAsync(int maxCount, Expression>? filter = null, CancellationToken cancellationToken = default) { var dbContext = (IHasEventOutbox)await MongoDbContextProvider.GetDbContextAsync(cancellationToken); + Expression>? transformedFilter = null; + if (filter != null) + { + transformedFilter = InboxOutboxFilterExpressionTransformer.Transform(filter)!; + } + var outgoingEventRecords = await dbContext .OutgoingEvents.AsQueryable() + .WhereIf(transformedFilter != null, transformedFilter!) .OrderBy(x => x.CreationTime) .Take(maxCount) + .As>() .ToListAsync(cancellationToken: cancellationToken); return outgoingEventRecords diff --git a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs index e3577bee6e..30733b1e24 100644 --- a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs +++ b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs @@ -55,10 +55,9 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency return specificMapper.Map(source); } - var result = TryToMapCollection(scope, source, default); - if (result != null) + if (TryToMapCollection(scope, source, default, out var collectionResult)) { - return result; + return collectionResult; } } @@ -100,10 +99,9 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency return specificMapper.Map(source, destination); } - var result = TryToMapCollection(scope, source, destination); - if (result != null) + if (TryToMapCollection(scope, source, destination, out var collectionResult)) { - return result; + return collectionResult; } } @@ -122,11 +120,12 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency return AutoMap(source, destination); } - protected virtual TDestination? TryToMapCollection(IServiceScope serviceScope, TSource source, TDestination? destination) + protected virtual bool TryToMapCollection(IServiceScope serviceScope, TSource source, TDestination? destination, out TDestination collectionResult) { if (!IsCollectionGenericType(out var sourceArgumentType, out var destinationArgumentType, out var definitionGenericType)) { - return default; + collectionResult = default!; + return false; } var mapperType = typeof(IObjectMapper<,>).MakeGenericType(sourceArgumentType, destinationArgumentType); @@ -134,7 +133,8 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency if (specificMapper == null) { //skip, no specific mapper - return default; + collectionResult = default!; + return false; } var cacheKey = $"{mapperType.FullName}_{(destination == null ? "MapMethodWithSingleParameter" : "MapMethodWithDoubleParameters")}"; @@ -209,11 +209,13 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency if (destination != null && destination.GetType().IsArray) { //Return the new collection if destination is an array, We won't change array just same behavior as AutoMapper. - return (TDestination)result; + collectionResult = (TDestination)result; + return true; } //Return the destination if destination exists. The parameter reference equals with return object. - return destination ?? (TDestination)result; + collectionResult = destination ?? (TDestination)result; + return true; } protected virtual bool IsCollectionGenericType(out Type sourceArgumentType, out Type destinationArgumentType, out Type definitionGenericType) diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingOptions.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingOptions.cs index c8a06736b6..e4bbb4fe00 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingOptions.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingOptions.cs @@ -11,10 +11,17 @@ public class AbpSettingOptions public HashSet DeletedSettings { get; } + /// + /// Default: true. + /// This is useful when you change of an existing setting definition to true and don't want to lose the original value. + /// + public bool ReturnOriginalValueIfDecryptFailed { get; set; } + public AbpSettingOptions() { DefinitionProviders = new TypeList(); ValueProviders = new TypeList(); DeletedSettings = new HashSet(); + ReturnOriginalValueIfDecryptFailed = true; } } diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingEncryptionService.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingEncryptionService.cs index 45a3362d30..2af6b7b4af 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingEncryptionService.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingEncryptionService.cs @@ -1,6 +1,7 @@ using System; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.Security.Encryption; @@ -10,10 +11,12 @@ public class SettingEncryptionService : ISettingEncryptionService, ITransientDep { protected IStringEncryptionService StringEncryptionService { get; } public ILogger Logger { get; set; } + protected IOptions Options { get; } - public SettingEncryptionService(IStringEncryptionService stringEncryptionService) + public SettingEncryptionService(IStringEncryptionService stringEncryptionService, IOptions options) { StringEncryptionService = stringEncryptionService; + Options = options; Logger = NullLogger.Instance; } @@ -40,7 +43,14 @@ public class SettingEncryptionService : ISettingEncryptionService, ITransientDep } catch (Exception e) { + if (Options.Value.ReturnOriginalValueIfDecryptFailed) + { + Logger.LogWarning(e, "Failed to decrypt the setting: {0}. Returning the original value...", settingDefinition.Name); + return encryptedValue; + } + Logger.LogException(e); + return string.Empty; } } diff --git a/framework/src/Volo.Abp.Sms.TencentCloud/FodyWeavers.xml b/framework/src/Volo.Abp.Sms.TencentCloud/FodyWeavers.xml new file mode 100644 index 0000000000..2ad59ce186 --- /dev/null +++ b/framework/src/Volo.Abp.Sms.TencentCloud/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.Sms.TencentCloud/FodyWeavers.xsd b/framework/src/Volo.Abp.Sms.TencentCloud/FodyWeavers.xsd new file mode 100644 index 0000000000..ffa6fc4b78 --- /dev/null +++ b/framework/src/Volo.Abp.Sms.TencentCloud/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.Sms.TencentCloud/Volo.Abp.Sms.TencentCloud.abppkg b/framework/src/Volo.Abp.Sms.TencentCloud/Volo.Abp.Sms.TencentCloud.abppkg new file mode 100644 index 0000000000..f4bad072d2 --- /dev/null +++ b/framework/src/Volo.Abp.Sms.TencentCloud/Volo.Abp.Sms.TencentCloud.abppkg @@ -0,0 +1,3 @@ +{ + "role": "lib.framework" +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Sms.TencentCloud/Volo.Abp.Sms.TencentCloud.abppkg.analyze.json b/framework/src/Volo.Abp.Sms.TencentCloud/Volo.Abp.Sms.TencentCloud.abppkg.analyze.json new file mode 100644 index 0000000000..83fe6d66b9 --- /dev/null +++ b/framework/src/Volo.Abp.Sms.TencentCloud/Volo.Abp.Sms.TencentCloud.abppkg.analyze.json @@ -0,0 +1,63 @@ +{ + "name": "Volo.Abp.Sms.TencentCloud", + "hash": "", + "contents": [ + { + "namespace": "Volo.Abp.Sms.TencentCloud", + "dependsOnModules": [ + { + "declaringAssemblyName": "Volo.Abp.Sms", + "namespace": "Volo.Abp.Sms", + "name": "AbpSmsModule" + } + ], + "implementingInterfaces": [ + { + "name": "IAbpModule", + "namespace": "Volo.Abp.Modularity", + "declaringAssemblyName": "Volo.Abp.Core", + "fullName": "Volo.Abp.Modularity.IAbpModule" + }, + { + "name": "IOnPreApplicationInitialization", + "namespace": "Volo.Abp.Modularity", + "declaringAssemblyName": "Volo.Abp.Core", + "fullName": "Volo.Abp.Modularity.IOnPreApplicationInitialization" + }, + { + "name": "IOnApplicationInitialization", + "namespace": "Volo.Abp", + "declaringAssemblyName": "Volo.Abp.Core", + "fullName": "Volo.Abp.IOnApplicationInitialization" + }, + { + "name": "IOnPostApplicationInitialization", + "namespace": "Volo.Abp.Modularity", + "declaringAssemblyName": "Volo.Abp.Core", + "fullName": "Volo.Abp.Modularity.IOnPostApplicationInitialization" + }, + { + "name": "IOnApplicationShutdown", + "namespace": "Volo.Abp", + "declaringAssemblyName": "Volo.Abp.Core", + "fullName": "Volo.Abp.IOnApplicationShutdown" + }, + { + "name": "IPreConfigureServices", + "namespace": "Volo.Abp.Modularity", + "declaringAssemblyName": "Volo.Abp.Core", + "fullName": "Volo.Abp.Modularity.IPreConfigureServices" + }, + { + "name": "IPostConfigureServices", + "namespace": "Volo.Abp.Modularity", + "declaringAssemblyName": "Volo.Abp.Core", + "fullName": "Volo.Abp.Modularity.IPostConfigureServices" + } + ], + "contentType": "abpModule", + "name": "AbpSmsTencentCloudModule", + "summary": null + } + ] +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Sms.TencentCloud/Volo.Abp.Sms.TencentCloud.csproj b/framework/src/Volo.Abp.Sms.TencentCloud/Volo.Abp.Sms.TencentCloud.csproj new file mode 100644 index 0000000000..3077ff392e --- /dev/null +++ b/framework/src/Volo.Abp.Sms.TencentCloud/Volo.Abp.Sms.TencentCloud.csproj @@ -0,0 +1,26 @@ + + + + + + netstandard2.0;netstandard2.1;net8.0;net9.0 + enable + Nullable + Volo.Abp.Sms.TencentCloud + Volo.Abp.Sms.TencentCloud + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.Sms.TencentCloud/Volo/Abp/Sms/TencentCloud/AbpSmsTencentCloudModule.cs b/framework/src/Volo.Abp.Sms.TencentCloud/Volo/Abp/Sms/TencentCloud/AbpSmsTencentCloudModule.cs new file mode 100644 index 0000000000..b846a53e9d --- /dev/null +++ b/framework/src/Volo.Abp.Sms.TencentCloud/Volo/Abp/Sms/TencentCloud/AbpSmsTencentCloudModule.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Sms.TencentCloud; + +[DependsOn(typeof(AbpSmsModule))] +public class AbpSmsTencentCloudModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + + Configure(configuration.GetSection("AbpTencentCloudSms")); + } +} diff --git a/framework/src/Volo.Abp.Sms.TencentCloud/Volo/Abp/Sms/TencentCloud/AbpTencentCloudSmsOptions.cs b/framework/src/Volo.Abp.Sms.TencentCloud/Volo/Abp/Sms/TencentCloud/AbpTencentCloudSmsOptions.cs new file mode 100644 index 0000000000..cec86d9b36 --- /dev/null +++ b/framework/src/Volo.Abp.Sms.TencentCloud/Volo/Abp/Sms/TencentCloud/AbpTencentCloudSmsOptions.cs @@ -0,0 +1,14 @@ +namespace Volo.Abp.Sms.TencentCloud; + +public class AbpTencentCloudSmsOptions +{ + public string SmsSdkAppId { get; set; } = default!; + + public string SecretKey { get; set; } = default!; + + public string SecretId { get; set; } = default!; + + public string Endpoint { get; set; } = "sms.tencentcloudapi.com"; + + public string Region { get; set; } = "ap-guangzhou"; +} diff --git a/framework/src/Volo.Abp.Sms.TencentCloud/Volo/Abp/Sms/TencentCloud/TencentCloudSmsProperties.cs b/framework/src/Volo.Abp.Sms.TencentCloud/Volo/Abp/Sms/TencentCloud/TencentCloudSmsProperties.cs new file mode 100644 index 0000000000..7253a85ba6 --- /dev/null +++ b/framework/src/Volo.Abp.Sms.TencentCloud/Volo/Abp/Sms/TencentCloud/TencentCloudSmsProperties.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.Sms.TencentCloud; + +public static class TencentCloudSmsProperties +{ + public const string SignName = "SignName"; + + public const string TemplateId = "TemplateId"; +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Sms.TencentCloud/Volo/Abp/Sms/TencentCloud/TencentCloudSmsSender.cs b/framework/src/Volo.Abp.Sms.TencentCloud/Volo/Abp/Sms/TencentCloud/TencentCloudSmsSender.cs new file mode 100644 index 0000000000..fa3c585576 --- /dev/null +++ b/framework/src/Volo.Abp.Sms.TencentCloud/Volo/Abp/Sms/TencentCloud/TencentCloudSmsSender.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using TencentCloud.Common; +using TencentCloud.Common.Profile; +using TencentCloud.Sms.V20210111; +using TencentCloud.Sms.V20210111.Models; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Sms.TencentCloud; + +public class TencentCloudSmsSender : ISmsSender, ITransientDependency +{ + protected AbpTencentCloudSmsOptions Options { get; } + + public TencentCloudSmsSender(IOptionsMonitor options) + { + Options = options.CurrentValue; + } + + public virtual async Task SendAsync(SmsMessage smsMessage) + { + var client = CreateClient(); + + await client.SendSms(new SendSmsRequest() + { + SmsSdkAppId = Options.SmsSdkAppId, + SignName = smsMessage.Properties.GetOrDefault(TencentCloudSmsProperties.SignName) as string, + TemplateId = smsMessage.Properties.GetOrDefault(TencentCloudSmsProperties.TemplateId) as string, + TemplateParamSet = smsMessage.Text.Split(','), + PhoneNumberSet = [smsMessage.PhoneNumber] + }); + } + + protected virtual SmsClient CreateClient() + { + var credential = new Credential + { + SecretId = Options.SecretId, + SecretKey = Options.SecretKey + }; + var clientProfile = new ClientProfile + { + HttpProfile = new HttpProfile + { + Endpoint = Options.Endpoint + } + }; + return new SmsClient(credential, Options.Region, clientProfile); + } +} diff --git a/framework/src/Volo.Abp.Swashbuckle/Microsoft/AspNetCore/Builder/AbpSwaggerUIBuilderExtensions.cs b/framework/src/Volo.Abp.Swashbuckle/Microsoft/AspNetCore/Builder/AbpSwaggerUIBuilderExtensions.cs index f7d85e3256..b999005f25 100644 --- a/framework/src/Volo.Abp.Swashbuckle/Microsoft/AspNetCore/Builder/AbpSwaggerUIBuilderExtensions.cs +++ b/framework/src/Volo.Abp.Swashbuckle/Microsoft/AspNetCore/Builder/AbpSwaggerUIBuilderExtensions.cs @@ -16,7 +16,6 @@ public static class AbpSwaggerUIBuilderExtensions return app.UseSwaggerUI(options => { options.InjectJavascript("ui/abp.js"); - options.InjectJavascript("ui/abp.swagger.js"); options.IndexStream = () => resolver?.Resolver(); setupAction?.Invoke(options); diff --git a/framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/SwaggerHtmlResolver.cs b/framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/SwaggerHtmlResolver.cs index f9b39faf1d..be4ebfe4e8 100644 --- a/framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/SwaggerHtmlResolver.cs +++ b/framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/SwaggerHtmlResolver.cs @@ -10,12 +10,14 @@ public class SwaggerHtmlResolver : ISwaggerHtmlResolver, ITransientDependency { public virtual Stream Resolver() { + var scriptBundleScript = ""; + var abpSwaggerScript = ""; var stream = typeof(SwaggerUIOptions).GetTypeInfo().Assembly .GetManifestResourceStream("Swashbuckle.AspNetCore.SwaggerUI.index.html"); var html = new StreamReader(stream!) .ReadToEnd() - .Replace("src=\"index.js\"", "src=\"ui/index.js\""); + .Replace(scriptBundleScript, $"{scriptBundleScript}\n{abpSwaggerScript}"); return new MemoryStream(Encoding.UTF8.GetBytes(html)); } diff --git a/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js b/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js index 4e8790d38c..b8abb0df25 100644 --- a/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js +++ b/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js @@ -1,9 +1,10 @@ var abp = abp || {}; (function () { - - abp.SwaggerUIBundle = function (configObject) { - + + var oldSwaggerUIBundle = SwaggerUIBundle; + + SwaggerUIBundle = function (configObject) { var excludeUrl = ["swagger.json", "connect/token"] var firstRequest = true; var oidcSupportedFlows = configObject.oidcSupportedFlows || []; @@ -109,6 +110,9 @@ var abp = abp || {}; }); } - return SwaggerUIBundle(configObject); + return oldSwaggerUIBundle(configObject); } + + SwaggerUIBundle = Object.assign(SwaggerUIBundle, oldSwaggerUIBundle); + })(); diff --git a/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/index.js b/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/index.js deleted file mode 100644 index d7b6ebdb53..0000000000 --- a/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/index.js +++ /dev/null @@ -1,75 +0,0 @@ -//Copy from https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/src/Swashbuckle.AspNetCore.SwaggerUI/index.js - -/* Source: https://gist.github.com/lamberta/3768814 - * Parse a string function definition and return a function object. Does not use eval. - * @param {string} str - * @return {function} - * - * Example: - * var f = function (x, y) { return x * y; }; - * var g = parseFunction(f.toString()); - * g(33, 3); //=> 99 - */ -function parseFunction(str) { - if (!str) return void (0); - - var fn_body_idx = str.indexOf('{'), - fn_body = str.substring(fn_body_idx + 1, str.lastIndexOf('}')), - fn_declare = str.substring(0, fn_body_idx), - fn_params = fn_declare.substring(fn_declare.indexOf('(') + 1, fn_declare.lastIndexOf(')')), - args = fn_params.split(','); - - args.push(fn_body); - - function Fn() { - return Function.apply(this, args); - } - Fn.prototype = Function.prototype; - - return new Fn(); -} - -window.onload = function () { - var configObject = JSON.parse('%(ConfigObject)'); - var oauthConfigObject = JSON.parse('%(OAuthConfigObject)'); - - // Workaround for https://github.com/swagger-api/swagger-ui/issues/5945 - configObject.urls.forEach(function (item) { - if (item.url.startsWith("http") || item.url.startsWith("/")) return; - item.url = window.location.href.replace("index.html", item.url).split('#')[0]; - }); - - // If validatorUrl is not explicitly provided, disable the feature by setting to null - if (!configObject.hasOwnProperty("validatorUrl")) - configObject.validatorUrl = null - - // If oauth2RedirectUrl isn't specified, use the built-in default - if (!configObject.hasOwnProperty("oauth2RedirectUrl")) - configObject.oauth2RedirectUrl = (new URL("oauth2-redirect.html", window.location.href)).href; - - // Apply mandatory parameters - configObject.dom_id = "#swagger-ui"; - configObject.presets = [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset]; - configObject.layout = "StandaloneLayout"; - - // Parse and add interceptor functions - var interceptors = JSON.parse('%(Interceptors)'); - if (interceptors.RequestInterceptorFunction) - configObject.requestInterceptor = parseFunction(interceptors.RequestInterceptorFunction); - if (interceptors.ResponseInterceptorFunction) - configObject.responseInterceptor = parseFunction(interceptors.ResponseInterceptorFunction); - - if (configObject.plugins) { - configObject.plugins = configObject.plugins.map(eval); - } - - // Begin Swagger UI call region - - const ui = abp.SwaggerUIBundle(configObject); - - ui.initOAuth(oauthConfigObject); - - // End Swagger UI call region - - window.ui = ui -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController.cs index 0a2eae5c2b..ae6ca60bb8 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +using System; +using System.Text; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; namespace Volo.Abp.AspNetCore.Mvc.Response; @@ -55,4 +57,11 @@ public class NoContentTestController : AbpController { await Task.CompletedTask; } + + [HttpGet] + [Route("TestAsyncMethodChangeBody")] + public async Task TestAsyncMethodChangeBody() + { + await Response.Body.WriteAsync("TestBody"u8.ToArray()); + } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController_Tests.cs index 9a3cbcd87b..a7136bc6f9 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController_Tests.cs @@ -55,4 +55,12 @@ public class NoContentTestController_Tests : AspNetCoreMvcTestBase var result = await GetResponseAsync("/api/NoContent-Test/TestAsyncMethodWithResultFilter"); result.StatusCode.ShouldBe(HttpStatusCode.OK); } + + [Fact] + public async Task Should_Not_Set_No_Content_If_Body_Changed() + { + var result = await GetResponseAsync("/api/NoContent-Test/TestAsyncMethodChangeBody"); + result.StatusCode.ShouldBe(HttpStatusCode.OK); + } + } diff --git a/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AbpAutoMapperModule_Basic_Tests.cs b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AbpAutoMapperModule_Basic_Tests.cs index cdaf58b9a4..82a0369aa0 100644 --- a/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AbpAutoMapperModule_Basic_Tests.cs +++ b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AbpAutoMapperModule_Basic_Tests.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; +using System; +using Microsoft.Extensions.DependencyInjection; using Shouldly; using Volo.Abp.AutoMapper.SampleClasses; using Volo.Abp.ObjectMapping; @@ -36,6 +37,13 @@ public class AbpAutoMapperModule_Basic_Tests : AbpIntegratedTest(MyEnum.Value3); + dto.ShouldBe(MyEnumDto.Value2); //Value2 is same as Value3 + } + //[Fact] TODO: Disabled because of https://github.com/AutoMapper/AutoMapper/pull/2379#issuecomment-355899664 /*public void Should_Not_Map_Objects_With_AutoMap_Attributes() { diff --git a/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEnum.cs b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEnum.cs new file mode 100644 index 0000000000..d8b655b4d7 --- /dev/null +++ b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEnum.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.AutoMapper.SampleClasses; + +public enum MyEnum +{ + Value1 = 1, + Value2, + Value3 +} diff --git a/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEnumDto.cs b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEnumDto.cs new file mode 100644 index 0000000000..fb33818660 --- /dev/null +++ b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEnumDto.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.AutoMapper.SampleClasses; + +public enum MyEnumDto +{ + Value1 = 2, + Value2, + Value3 +} diff --git a/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyMapProfile.cs b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyMapProfile.cs index f36322ddaa..295ff6b08e 100644 --- a/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyMapProfile.cs +++ b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyMapProfile.cs @@ -9,6 +9,8 @@ public class MyMapProfile : Profile { CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap() .MapExtraProperties(ignoredProperties: new[] { "CityName" }); diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs index 5e9107a23b..4dd69041fc 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs @@ -1,8 +1,11 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Net.Http; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.Http.Client; using Volo.Abp.Http.Client.ClientProxying; +using Volo.Abp.Http.Client.Proxying; using Volo.Abp.Http.DynamicProxying; using Volo.Abp.Http.Localization; using Volo.Abp.Localization; @@ -60,5 +63,16 @@ public class AbpHttpClientTestModule : AbpModule options.FormDataConverts.Add(typeof(List), typeof(TestObjectToFormData)); options.PathConverts.Add(typeof(int), typeof(TestObjectToPath)); }); + + Configure(options => + { + options.AddPreSendAction("Default", (_, requestContext, httpclient) => + { + if (requestContext.Action.Name.Equals("TimeOutRequestAsync")) + { + httpclient.Timeout = TimeSpan.FromMilliseconds(1); + } + }); + }); } } diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/IRegularTestController.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/IRegularTestController.cs index 547f1bf5a7..21786155bf 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/IRegularTestController.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/IRegularTestController.cs @@ -43,4 +43,7 @@ public interface IRegularTestController Task DeleteByIdAsync(int id); Task AbortRequestAsync(CancellationToken cancellationToken = default); + + Task TimeOutRequestAsync(); + } diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestController.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestController.cs index b092e67bc8..46b355090b 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestController.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestController.cs @@ -152,6 +152,14 @@ public class RegularTestController : AbpController, IRegularTestController await Task.Delay(100, cancellationToken); return "AbortRequestAsync"; } + + [HttpGet] + [Route("timeout-request")] + public async Task TimeOutRequestAsync() + { + await Task.Delay(100); + return "TimeOutRequestAsync"; + } } public class Car diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestControllerClientProxy_Tests.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestControllerClientProxy_Tests.cs index f094c756a9..01ec97d734 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestControllerClientProxy_Tests.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestControllerClientProxy_Tests.cs @@ -1,4 +1,5 @@ using System; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -187,4 +188,11 @@ public class RegularTestControllerClientProxy_Tests : AbpHttpClientTestBase var exception = await Assert.ThrowsAsync(async () => await _controller.AbortRequestAsync(cts.Token)); exception.InnerException.InnerException.InnerException.Message.ShouldBe("The client aborted the request."); } + + [Fact] + public async Task TimeOutRequestAsync() + { + var exception = await Assert.ThrowsAsync(async () => await _controller.TimeOutRequestAsync()); + exception.InnerException.InnerException.Message.ShouldBe("The client aborted the request."); + } } diff --git a/framework/test/Volo.Abp.Sms.TencenCloud.Tests/Volo.Abp.Sms.TencentCloud.Tests.abppkg b/framework/test/Volo.Abp.Sms.TencenCloud.Tests/Volo.Abp.Sms.TencentCloud.Tests.abppkg new file mode 100644 index 0000000000..a686451fbc --- /dev/null +++ b/framework/test/Volo.Abp.Sms.TencenCloud.Tests/Volo.Abp.Sms.TencentCloud.Tests.abppkg @@ -0,0 +1,3 @@ +{ + "role": "lib.test" +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Sms.TencenCloud.Tests/Volo.Abp.Sms.TencentCloud.Tests.csproj b/framework/test/Volo.Abp.Sms.TencenCloud.Tests/Volo.Abp.Sms.TencentCloud.Tests.csproj new file mode 100644 index 0000000000..cd63f915d1 --- /dev/null +++ b/framework/test/Volo.Abp.Sms.TencenCloud.Tests/Volo.Abp.Sms.TencentCloud.Tests.csproj @@ -0,0 +1,26 @@ + + + + + + net9.0 + + + + + + + + + + + + + + + + Always + + + + diff --git a/framework/test/Volo.Abp.Sms.TencenCloud.Tests/Volo/Abp/Sms/TencentCloud/AbpSmsTencentCloudTestBase.cs b/framework/test/Volo.Abp.Sms.TencenCloud.Tests/Volo/Abp/Sms/TencentCloud/AbpSmsTencentCloudTestBase.cs new file mode 100644 index 0000000000..dfc666f21f --- /dev/null +++ b/framework/test/Volo.Abp.Sms.TencenCloud.Tests/Volo/Abp/Sms/TencentCloud/AbpSmsTencentCloudTestBase.cs @@ -0,0 +1,11 @@ +using Volo.Abp.Testing; + +namespace Volo.Abp.Sms.TencentCloud; + +public class AbpSmsTencentCloudTestBase : AbpIntegratedTest +{ + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } +} diff --git a/framework/test/Volo.Abp.Sms.TencenCloud.Tests/Volo/Abp/Sms/TencentCloud/AbpSmsTencentCloudTestsModule.cs b/framework/test/Volo.Abp.Sms.TencenCloud.Tests/Volo/Abp/Sms/TencentCloud/AbpSmsTencentCloudTestsModule.cs new file mode 100644 index 0000000000..5062c18867 --- /dev/null +++ b/framework/test/Volo.Abp.Sms.TencenCloud.Tests/Volo/Abp/Sms/TencentCloud/AbpSmsTencentCloudTestsModule.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Sms.TencentCloud; + +[DependsOn(typeof(AbpSmsTencentCloudModule))] +public class AbpSmsTencentCloudTestsModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + + Configure(configuration.GetSection("AbpTencentCloudSms")); + } +} diff --git a/framework/test/Volo.Abp.Sms.TencenCloud.Tests/Volo/Abp/Sms/TencentCloud/TencentCloudSmsSenderTests.cs b/framework/test/Volo.Abp.Sms.TencenCloud.Tests/Volo/Abp/Sms/TencentCloud/TencentCloudSmsSenderTests.cs new file mode 100644 index 0000000000..9198f9b86d --- /dev/null +++ b/framework/test/Volo.Abp.Sms.TencenCloud.Tests/Volo/Abp/Sms/TencentCloud/TencentCloudSmsSenderTests.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Xunit; + +namespace Volo.Abp.Sms.TencentCloud; + +public class TencentCloudSmsSenderTests : AbpSmsTencentCloudTestBase +{ + private readonly ISmsSender _smsSender; + private readonly IConfiguration _configuration; + + public TencentCloudSmsSenderTests() + { + _configuration = GetRequiredService(); + _smsSender = GetRequiredService(); + } + + [Fact] + public async Task SendSms_Test() + { + var config = _configuration.GetSection("AbpTencentCloudSms"); + + // Please fill in the real parameters in the appsettings.json file. + if (config["SecretId"] == "") + { + return; + } + + var msg = new SmsMessage(config["TargetPhoneNumber"], + config["TemplateParam"]); + msg.Properties.Add(TencentCloudSmsProperties.SignName, config["SignName"]); + msg.Properties.Add(TencentCloudSmsProperties.TemplateId, config["TemplateId"]); + + await _smsSender.SendAsync(msg); + } +} diff --git a/framework/test/Volo.Abp.Sms.TencenCloud.Tests/appsettings.json b/framework/test/Volo.Abp.Sms.TencenCloud.Tests/appsettings.json new file mode 100644 index 0000000000..44d4611caf --- /dev/null +++ b/framework/test/Volo.Abp.Sms.TencenCloud.Tests/appsettings.json @@ -0,0 +1,13 @@ +{ + "AbpTencentCloudSms": { + "SecretId": "", + "SecretKey": "", + "Region": "", + "SmsSdkAppId": "", + "Endpoint": "", + "TargetPhoneNumber": "", + "SignName": "", + "TemplateId": "", + "TemplateParam": "" + } +} \ No newline at end of file diff --git a/latest-versions.json b/latest-versions.json index b99c7202b6..43d4fbb9ac 100644 --- a/latest-versions.json +++ b/latest-versions.json @@ -1,8 +1,38 @@ [ + { + "version": "9.0.2", + "releaseDate": "", + "type": "stable", + "message": "", + "leptonx": { + "version": "4.0.3" + } + }, + { + "version": "9.0.1", + "releaseDate": "", + "type": "stable", + "message": "", + "leptonx": { + "version": "4.0.2" + } + }, { "version": "9.0.0", "releaseDate": "", "type": "stable", - "message": "" + "message": "", + "leptonx": { + "version": "4.0.0" + } + }, + { + "version": "8.3.1", + "releaseDate": "", + "type": "stable", + "message": "", + "leptonx": { + "version": "3.3.1" + } } -] \ No newline at end of file +] diff --git a/modules/basic-theme/Volo.Abp.BasicTheme.sln b/modules/basic-theme/Volo.Abp.BasicTheme.sln index 2e65f7a98d..ac0ab4ed3b 100644 --- a/modules/basic-theme/Volo.Abp.BasicTheme.sln +++ b/modules/basic-theme/Volo.Abp.BasicTheme.sln @@ -20,6 +20,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Mvc.UI. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BasicTheme.Installer", "src\Volo.Abp.BasicTheme.Installer\Volo.Abp.BasicTheme.Installer.csproj", "{3068A87F-3348-4981-8241-2630BC496117}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling", "src\Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling\Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling.csproj", "{D02053D9-10EF-4717-A792-A53F83347816}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -58,6 +60,10 @@ Global {3068A87F-3348-4981-8241-2630BC496117}.Debug|Any CPU.Build.0 = Debug|Any CPU {3068A87F-3348-4981-8241-2630BC496117}.Release|Any CPU.ActiveCfg = Release|Any CPU {3068A87F-3348-4981-8241-2630BC496117}.Release|Any CPU.Build.0 = Release|Any CPU + {D02053D9-10EF-4717-A792-A53F83347816}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D02053D9-10EF-4717-A792-A53F83347816}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D02053D9-10EF-4717-A792-A53F83347816}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D02053D9-10EF-4717-A792-A53F83347816}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {C8068E7F-4A04-4755-8976-C2A4C0ADC708} = {ED6D078F-B0A2-48E8-A09D-3B7CDF6CE3D1} @@ -68,5 +74,6 @@ Global {51B491ED-F959-4974-A876-528B5F16BC92} = {0BC55E3B-4964-48E3-A390-2ADD37980149} {8C336CB8-F7A9-4203-AE55-D8F5FDB2A958} = {0BC55E3B-4964-48E3-A390-2ADD37980149} {3068A87F-3348-4981-8241-2630BC496117} = {ED6D078F-B0A2-48E8-A09D-3B7CDF6CE3D1} + {D02053D9-10EF-4717-A792-A53F83347816} = {ED6D078F-B0A2-48E8-A09D-3B7CDF6CE3D1} EndGlobalSection EndGlobal diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/FirstLevelNavMenuItem.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/FirstLevelNavMenuItem.razor index 0ccd9b93db..32de8507cb 100644 --- a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/FirstLevelNavMenuItem.razor +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/FirstLevelNavMenuItem.razor @@ -1,61 +1,67 @@ -@using Volo.Abp.UI.Navigation +@using Volo.Abp.UI.Navigation @{ - var elementId = MenuItem.ElementId ?? "MenuItem_" + MenuItem.Name.Replace(".", "_"); - var cssClass = string.IsNullOrEmpty(MenuItem.CssClass) ? string.Empty : MenuItem.CssClass; - var disabled = MenuItem.IsDisabled ? "disabled" : string.Empty; - var url = MenuItem.Url == null ? "#" : MenuItem.Url.TrimStart('/', '~'); var customComponentType = MenuItem.GetComponentTypeOrDefault(); } -@if (MenuItem.IsLeaf) + +@if (customComponentType != null && typeof(ComponentBase).IsAssignableFrom(customComponentType)) { - if (customComponentType != null && typeof(ComponentBase).IsAssignableFrom(customComponentType)) - { - - } - else if (MenuItem.Url != null) - { - - } + } else { - + else + { + var itemUrl = item.Url == null ? "#" : item.Url.TrimStart('/', '~'); + + @if (item.Icon != null) + { + + } + + @item.DisplayName + + + } + } + + } diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/FirstLevelNavMenuItem.razor.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/FirstLevelNavMenuItem.razor.cs index 4cdd0ec8a4..ea448c3b7d 100644 --- a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/FirstLevelNavMenuItem.razor.cs +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/FirstLevelNavMenuItem.razor.cs @@ -1,4 +1,5 @@ using System; +using Blazorise; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Routing; using Volo.Abp.UI.Navigation; @@ -7,31 +8,26 @@ namespace Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic; public partial class FirstLevelNavMenuItem : IDisposable { - [Inject] private NavigationManager NavigationManager { get; set; } - [Parameter] - public ApplicationMenuItem MenuItem { get; set; } + public ApplicationMenuItem MenuItem { get; set; } = default!; + + private Dropdown _dropdown; - public bool IsSubMenuOpen { get; set; } + [Inject] + private NavigationManager NavigationManager { get; set; } protected override void OnInitialized() { NavigationManager.LocationChanged += OnLocationChanged; } - private void ToggleSubMenu() + protected virtual void OnLocationChanged(object sender, LocationChangedEventArgs e) { - IsSubMenuOpen = !IsSubMenuOpen; + _dropdown?.Hide(); } - public void Dispose() + public virtual void Dispose() { NavigationManager.LocationChanged -= OnLocationChanged; } - - private void OnLocationChanged(object sender, LocationChangedEventArgs e) - { - IsSubMenuOpen = false; - InvokeAsync(StateHasChanged); - } } diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/SecondLevelNavMenuItem.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/SecondLevelNavMenuItem.razor index 9200e83657..2e6f783556 100644 --- a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/SecondLevelNavMenuItem.razor +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/SecondLevelNavMenuItem.razor @@ -1,55 +1,48 @@ -@using Volo.Abp.UI.Navigation +@using Volo.Abp.UI.Navigation @{ - var elementId = MenuItem.ElementId ?? "MenuItem_" + MenuItem.Name.Replace(".", "_"); - var cssClass = string.IsNullOrEmpty(MenuItem.CssClass) ? string.Empty : MenuItem.CssClass; - var disabled = MenuItem.IsDisabled ? "disabled" : string.Empty; - var url = MenuItem.Url == null ? "#" : MenuItem.Url.TrimStart('/', '~'); var customComponentType = MenuItem.GetComponentTypeOrDefault(); } -@if (MenuItem.IsLeaf) + +@if (customComponentType != null && typeof(ComponentBase).IsAssignableFrom(customComponentType)) { - if (customComponentType != null && typeof(ComponentBase).IsAssignableFrom(customComponentType)) - { - - } - else if (MenuItem.Url != null) - { - - @if (MenuItem.Icon != null) - { - - } - - @MenuItem.DisplayName - - - } + } else { - -} \ No newline at end of file + + +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/SecondLevelNavMenuItem.razor.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/SecondLevelNavMenuItem.razor.cs index c736770884..b188b764ed 100644 --- a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/SecondLevelNavMenuItem.razor.cs +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/SecondLevelNavMenuItem.razor.cs @@ -1,4 +1,5 @@ using System; +using Blazorise; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Routing; using Volo.Abp.UI.Navigation; @@ -7,31 +8,30 @@ namespace Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic; public partial class SecondLevelNavMenuItem : IDisposable { - [Inject] private NavigationManager NavigationManager { get; set; } + [Parameter] + public ApplicationMenuItem MenuItem { get; set; } = default!; [Parameter] - public ApplicationMenuItem MenuItem { get; set; } + public Dropdown ParentDropdown { get; set; } = default!; + + private Dropdown _dropdown; - public bool IsSubMenuOpen { get; set; } + [Inject] + private NavigationManager NavigationManager { get; set; } protected override void OnInitialized() { NavigationManager.LocationChanged += OnLocationChanged; } - private void ToggleSubMenu() + protected virtual void OnLocationChanged(object sender, LocationChangedEventArgs e) { - IsSubMenuOpen = !IsSubMenuOpen; + ParentDropdown?.Hide(); + _dropdown?.Hide(); } - public void Dispose() + public virtual void Dispose() { NavigationManager.LocationChanged -= OnLocationChanged; } - - private void OnLocationChanged(object sender, LocationChangedEventArgs e) - { - IsSubMenuOpen = false; - InvokeAsync(StateHasChanged); - } } diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling/AbpAspNetCoreComponentsWebAssemblyBasicThemeBundlingModule.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling/AbpAspNetCoreComponentsWebAssemblyBasicThemeBundlingModule.cs new file mode 100644 index 0000000000..b6fcb74e34 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling/AbpAspNetCoreComponentsWebAssemblyBasicThemeBundlingModule.cs @@ -0,0 +1,20 @@ +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling; + +[DependsOn( + typeof(AbpAspNetCoreComponentsWebAssemblyThemingBundlingModule) +)] +public class AbpAspNetCoreComponentsWebAssemblyBasicThemeBundlingModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + var globalStyles = options.StyleBundles.Get(BlazorWebAssemblyStandardBundles.Styles.Global); + globalStyles.AddContributors(typeof(BasicThemeBundleStyleContributor)); + }); + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling/BasicThemeBundleStyleContributor.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling/BasicThemeBundleStyleContributor.cs new file mode 100644 index 0000000000..de65538929 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling/BasicThemeBundleStyleContributor.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling; + +public class BasicThemeBundleStyleContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web.BasicTheme/libs/abp/css/theme.css"); + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling/FodyWeavers.xml b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling/FodyWeavers.xml new file mode 100644 index 0000000000..7e9f94ead6 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling/FodyWeavers.xsd b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling/FodyWeavers.xsd new file mode 100644 index 0000000000..ffa6fc4b78 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling.csproj b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling.csproj new file mode 100644 index 0000000000..edf08d8ebe --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling.csproj @@ -0,0 +1,15 @@ + + + + + + + net9.0 + Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling + + + + + + + diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/AbpAspNetCoreComponentsWebAssemblyBasicThemeModule.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/AbpAspNetCoreComponentsWebAssemblyBasicThemeModule.cs index cbc24669d8..08454376c2 100644 --- a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/AbpAspNetCoreComponentsWebAssemblyBasicThemeModule.cs +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/AbpAspNetCoreComponentsWebAssemblyBasicThemeModule.cs @@ -3,6 +3,7 @@ using Volo.Abp.AspNetCore.Components.Web; using Volo.Abp.AspNetCore.Components.Web.BasicTheme; using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; using Volo.Abp.AspNetCore.Components.Web.Theming.Toolbars; +using Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Bundling; using Volo.Abp.AspNetCore.Components.WebAssembly.Theming; using Volo.Abp.Http.Client.IdentityModel.WebAssembly; using Volo.Abp.Modularity; @@ -10,6 +11,7 @@ using Volo.Abp.Modularity; namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme; [DependsOn( + typeof(AbpAspNetCoreComponentsWebAssemblyBasicThemeBundlingModule), typeof(AbpAspNetCoreComponentsWebBasicThemeModule), typeof(AbpAspNetCoreComponentsWebAssemblyThemingModule), typeof(AbpHttpClientIdentityModelWebAssemblyModule) diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/BasicThemeBundleContributor.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/BasicThemeBundleContributor.cs index 67b29987f1..ce07a26c63 100644 --- a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/BasicThemeBundleContributor.cs +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/BasicThemeBundleContributor.cs @@ -1,7 +1,9 @@ -using Volo.Abp.Bundling; +using System; +using Volo.Abp.Bundling; namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme; +[Obsolete("This class is obsolete and will be removed in the future versions. Use GlobalAssets instead.")] public class BasicThemeBundleContributor : IBundleContributor { public void AddScripts(BundleContext context) diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.csproj b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.csproj index d7ee34d13c..c070361f08 100644 --- a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.csproj +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.csproj @@ -12,6 +12,7 @@ + diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json index 04d78b830f..ca24ffb511 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json @@ -3,8 +3,8 @@ "name": "asp.net", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.shared": "~9.0.0", - "@abp/prismjs": "~9.0.0", - "@abp/highlight.js": "~9.0.0" + "@abp/aspnetcore.mvc.ui.theme.shared": "~9.0.3", + "@abp/prismjs": "~9.0.3", + "@abp/highlight.js": "~9.0.3" } } diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/yarn.lock b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/yarn.lock index 0ec23ef2e3..481d54e59a 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/yarn.lock +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/yarn.lock @@ -2,212 +2,212 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.shared@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-9.0.0.tgz#a77b4b70fa6217146afd6a57211f57e2360564f2" - integrity sha512-QmZrSp+rfqfnC9W4QZ+nNdAq0rOALzRn1rsDpGDHhIHwyXB03QPdjzIWaEvldUYHzqg84cEP0LcqW4y57Xr1iw== - dependencies: - "@abp/aspnetcore.mvc.ui" "~9.0.0" - "@abp/bootstrap" "~9.0.0" - "@abp/bootstrap-datepicker" "~9.0.0" - "@abp/bootstrap-daterangepicker" "~9.0.0" - "@abp/datatables.net-bs5" "~9.0.0" - "@abp/font-awesome" "~9.0.0" - "@abp/jquery-form" "~9.0.0" - "@abp/jquery-validation-unobtrusive" "~9.0.0" - "@abp/lodash" "~9.0.0" - "@abp/luxon" "~9.0.0" - "@abp/malihu-custom-scrollbar-plugin" "~9.0.0" - "@abp/moment" "~9.0.0" - "@abp/select2" "~9.0.0" - "@abp/sweetalert2" "~9.0.0" - "@abp/timeago" "~9.0.0" - "@abp/toastr" "~9.0.0" - -"@abp/aspnetcore.mvc.ui@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-9.0.0.tgz#93004dd056c130386c7b40c458f3a793d8c9e051" - integrity sha512-pvO5bh9baFcSbfW0s7tpMpbe6DKSC16jeyeluZLlvYxC0n2gkrv7H/hB5aMuBKIlh89aGY9DQIcMFC8iczL3Bg== +"@abp/aspnetcore.mvc.ui.theme.shared@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-9.0.3.tgz#18f0d27cbba7ff22576c1c6e54891190c7d900bb" + integrity sha512-WHMvMiZhqvPLQtqo3aI+wkhNbZ3gCkWqwhIiXHvRks+tO92ww52evPlZUNDSWSSkhcB9vJddx5Eqv4phJgcMAA== + dependencies: + "@abp/aspnetcore.mvc.ui" "~9.0.3" + "@abp/bootstrap" "~9.0.3" + "@abp/bootstrap-datepicker" "~9.0.3" + "@abp/bootstrap-daterangepicker" "~9.0.3" + "@abp/datatables.net-bs5" "~9.0.3" + "@abp/font-awesome" "~9.0.3" + "@abp/jquery-form" "~9.0.3" + "@abp/jquery-validation-unobtrusive" "~9.0.3" + "@abp/lodash" "~9.0.3" + "@abp/luxon" "~9.0.3" + "@abp/malihu-custom-scrollbar-plugin" "~9.0.3" + "@abp/moment" "~9.0.3" + "@abp/select2" "~9.0.3" + "@abp/sweetalert2" "~9.0.3" + "@abp/timeago" "~9.0.3" + "@abp/toastr" "~9.0.3" + +"@abp/aspnetcore.mvc.ui@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-9.0.3.tgz#a4e2e92cddca77d81d8445bcc1d2cc129ec4b486" + integrity sha512-wkn7R8sx5lhbaZeTAy5aJxLbR1f3TD8lNaNtM1Lsc7wWq9dI19wyJZiAWqCZij2HOr2IK5izSROVfTqoXyUwXQ== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-9.0.0.tgz#d6609e50568aac6a013abfcaa07817998e1eaa99" - integrity sha512-wxAeVsOVOJpidDn0g3FlJvpOsn5SVY5IpEG+FtUxSAKuWYJQWF6g5CaghrfS+FNDK9IHhxrgSMPMVBK9YlDSzw== +"@abp/bootstrap-datepicker@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-9.0.3.tgz#aa40f5a31eb5c06224c165129a882658f4a0a9a5" + integrity sha512-Jg4oeBPtX7G41JdYjHVhB/6KQcMJO4WWXbUvkNetSUGTZGqacmgWRAIA5abmRWaHMJP1A1soZ9Ny1GNy0Qu65Q== dependencies: bootstrap-datepicker "^1.10.0" -"@abp/bootstrap-daterangepicker@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-9.0.0.tgz#fc2871e7d886accd8e4748cb763b695e10701c00" - integrity sha512-qS6SQR6X7wMCVLcuXatJ2+m1GNlpN71p7VDritxfwAMHa7WBRAAi8apX2WItwBAuLQ2s1uSA0Wp1KstHG3VPLA== +"@abp/bootstrap-daterangepicker@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-9.0.3.tgz#a1c17721a1ff6613a3ee4ff971174b32a93d98de" + integrity sha512-wrvjb0bJudohxiYtecCGEa4VsvL/Kn/Z3q9w1jvIoallrTXQaACPtZDU+9yxK5mRf6zonwYI8JtNn4xKa+D1VA== dependencies: bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-9.0.0.tgz#5ab292e95ba7eef129cbaa1dc5f5d5c0af8530d7" - integrity sha512-jEr6su8BfoNxYvze/UlddE3DlezyPvQeiNhff8SDPqtRQ6XvjZ48O/JwtY01hK2njEDLOZINY5zJRGmO2OiOUw== +"@abp/bootstrap@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-9.0.3.tgz#dc0325c23282086c9d2be79588b228e8b2c66eb2" + integrity sha512-ypl0R3jky6qKmnEZjnSniwgN8unNmWD9d4bOtV5RgDF2MlBQT+CvfUcYnGom0JOsL+5RjSNvzFH/Q5aH/yuy7Q== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" bootstrap "^5.3.3" -"@abp/clipboard@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-9.0.0.tgz#21ae924dfef6ccf3c8a3913bb81caf22b57404e4" - integrity sha512-W6S7pihpQ6Wfj48Clk7hYgDvUtnRwkNIsk5t8s9+LxoAdJQGQFxh7SAW1QNavUv8QqsClNE1VtSU87c7rSoAbA== +"@abp/clipboard@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-9.0.3.tgz#bda02ce6da876890ef117697f30348b8c16aaf70" + integrity sha512-iprJtnshdpRgLIPxa5Tgj1IqkLvkl2QxTmjrqeiuRhU3S1ry0GnE6dx3jAealWUOoYr/bqXPbpXi3RN7R4baHw== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" clipboard "^2.0.11" -"@abp/core@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-9.0.0.tgz#dc5ea9139957e307f73ac1f40b179f991e3c2168" - integrity sha512-vzQyTpqVDx63pOI9e2QLUMWZCrSojHsRnzLlvl+/cKFi7jE1q/uSP66FPR2bwkD+DNLI/tn6is1Jx3skKA1FvA== +"@abp/core@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-9.0.3.tgz#8c508dc60628fbd9e8a9ba35d153316cacbf1fb4" + integrity sha512-4bHiyP2qPrEeXb/Ynl17b6LtfygWdkwsjHaadUbym7Lg5qYUQZ3KjW4U2DihYWCO46gsr/ckTEbV9edcRd+NxA== dependencies: - "@abp/utils" "~9.0.0" + "@abp/utils" "~9.0.3" -"@abp/datatables.net-bs5@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-9.0.0.tgz#4daee90bdb6446e854ce65f2a3afabb2a13e49ae" - integrity sha512-ln10tcRwxcB0nuSgqJ/66strHsqMHDC03c0ktxEplAN34yP7KInjFf/U9Svo74K0nKlkJdMCgmqmT4CnBJZlIg== +"@abp/datatables.net-bs5@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-9.0.3.tgz#c89813a69a1b568a171cb2a4b1bcc4b7a4ef1bef" + integrity sha512-SD6N4apScOR22S7EntengOTQ1HSm2WnvzUuP/6HG50isXyMheRalZxJ6zAsBwiWyYyJtA+UHkiQzwVhdV40aBQ== dependencies: - "@abp/datatables.net" "~9.0.0" + "@abp/datatables.net" "~9.0.3" datatables.net-bs5 "^2.1.8" -"@abp/datatables.net@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-9.0.0.tgz#82b28518293c9091ec72d5defae2f5d2903d8bbc" - integrity sha512-QhuX3bGZOckM/fhIIGj/StMJIX2BBtNbLzjTR7WnKMMxDkv0+oDvjw6OqqpYKSvj/q3vY6sRWuCNdEzNi0Fk6A== +"@abp/datatables.net@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-9.0.3.tgz#9cc09127ff6269476b6b0de16048e1e6480227c4" + integrity sha512-zLzHxdXnmL8pWKVzf3m4SIUMiPHHe3I9Cw8vezk5fK1v18Fk364o3Q8o8C1l1YDGEa7Sx1wsUoLDN+p4FzagXg== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" datatables.net "^2.1.8" -"@abp/font-awesome@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-9.0.0.tgz#51cce18434a51d050ebb0d822a8c193b7e32f73d" - integrity sha512-bEHRVzOk0V2kFMAvB/YXFYaDKWRFO0+ScO7jrEFtFgdHXBcqy+dk1aM/N69WalyNmjRRpnh03GhH1LRpwL4/xA== +"@abp/font-awesome@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-9.0.3.tgz#db6ece04fcdb4880cac8174deffb4fa04d43d2de" + integrity sha512-toI2zGayMeI4EUUMmittRWKBY/mLjiiX1zA2z70q/Hpm9zoCly2KwbN7zwtWbayhESO7edci9T7CXF5kNdiNow== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" "@fortawesome/fontawesome-free" "^6.6.0" -"@abp/highlight.js@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/highlight.js/-/highlight.js-9.0.0.tgz#6d924c9bb3d6b11dcd66a9a71aea873c9866ed6c" - integrity sha512-eGvgMUNYKW2B2h3QcpMRniaoQ1/9RJBXkn2VsyM8lVSfrfbOg26mMyw7J2BXvuEnXqtnVdFEG45cSFlAnTAVXQ== +"@abp/highlight.js@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/highlight.js/-/highlight.js-9.0.3.tgz#967b497badf854a9f0036082a39cf57eefd6d7c7" + integrity sha512-4/VhrgaiLb9kqO7HRAcmyoE63DFwMZToSjpSsXZPXMA2SM64cHjCz6cN5Ukh+GGOEolDVlbHoExON067rOz42w== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" "@highlightjs/cdn-assets" "~11.10.0" -"@abp/jquery-form@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-9.0.0.tgz#6518eb117ee35c6bc57543fd1fccb0a8cf8e22f4" - integrity sha512-6qpfPGHrjADmm7Ne3zcXBGPuLngG8ldAn5fk9k2taftpyX0iR67/nZxiQ0ujaQGgaai5Wb3imNXMG96XLnubkQ== +"@abp/jquery-form@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-9.0.3.tgz#907033a496af127bfbe4e220f43cda65331e7b98" + integrity sha512-kD/9S25kJSAj5f7bfHVMfzfF5/0OrLDj8E55M27zbusdcaFS/nYElzrU3DeAEusASdnssRAwyJCWyp2vvRIMzA== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-9.0.0.tgz#8ee87dbdec5e92ab04ae9642a49af1598af31c1a" - integrity sha512-KlRQ1htyFmAIMS9xbHvHIcdhAgtPFe/u7c87MiWlk/CdhDrI3J+n5biLvQUzxCC+YWlwx27zBtPKx2vLeMRH6Q== +"@abp/jquery-validation-unobtrusive@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-9.0.3.tgz#b9449d222d4855f941dbe7445f73fff1541975a4" + integrity sha512-U/zUDYyJnUYDmmloOx/rViVyg7b4Gxd4zgcEi1E1YP0zryQQh5CkoSEvzv7GyurB1/rVZuRszmo/nRgn3pHOKg== dependencies: - "@abp/jquery-validation" "~9.0.0" + "@abp/jquery-validation" "~9.0.3" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-9.0.0.tgz#681d8037c57b3875bd51c927e350ffce65d7c5d2" - integrity sha512-XgCUBFWbhTDcGepD/xkwA/1Hl4TyYitRttauDJk3tuyXhNtykvdugCCOPY4j2s6VoT1Sb/TFFvOcgEvlBwkKiw== +"@abp/jquery-validation@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-9.0.3.tgz#02eb010267121fd9310cdcf5ca7c7b54091fd5de" + integrity sha512-tqjM5S0/jDS88CIt8oCQbHYJTytA7Y+KdTsnRkrA+BWy2A5bjrd6YUZXa/2UUwXHa3tQgv/uXl9HpDEaqRtfYQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" jquery-validation "^1.21.0" -"@abp/jquery@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-9.0.0.tgz#6c67a9f927be598ab6201f8888e8a3cb8bba0686" - integrity sha512-cUXA7v6F1HqAc/w8fQGxHIfA/6BqO+4uHXDnSsJZtrI26DTLOgw5kBNnAz2W4xggGPCY7SKFHNRZuOW/QfVRPg== +"@abp/jquery@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-9.0.3.tgz#ba8b4f4c0825ccb55c793024d3bbeb9ee817b19c" + integrity sha512-lZoyiyz83VOQKbN+9gD8JC+WI+OACc3y2hU+v30v220DLKhwoiVQM6d5yfA2QEugTPTjm/s40Ocmh/nVjm5mtg== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" jquery "~3.7.1" -"@abp/lodash@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-9.0.0.tgz#a4d42b2dfeb37859ff9f0079c7a8a6cabc982efe" - integrity sha512-xoAsOkkgnQdPH2OyFTgF6OlCLVbyGF4u8CrFcZWCZmuIFT9cg0+ZEiq//Vog1pg3Lf//N37ceGpOag1LwMe6cQ== +"@abp/lodash@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-9.0.3.tgz#daadfb98339f1d6982a12b655b840f105b324830" + integrity sha512-wt4ZJmRlhnK9W3o8lex7LyL/7rzgu4STLtZ2Ga8Ec5uZ9kN0CDTYjIw0zVqB8iGNKcW8MCxIGB7u9JMU9tphiQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" lodash "^4.17.21" -"@abp/luxon@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-9.0.0.tgz#a54dda13cbbd362481994d20d2e07c669c6ce109" - integrity sha512-aPejQnoVPfm60LnjwhfUkIsB+poNWusrBE0IHllxhvOVE+01nfa1Lvpp5AUWzV+XchPGOCSf+a29ds4EQWfNbg== +"@abp/luxon@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-9.0.3.tgz#62a5e395c92cadcf0985453eee065fcbe5354402" + integrity sha512-qTPw2vhgkgNldFJziHiL+c2vF7p+7RW1orev8K8XLSe3dguzlXBL2dicyDWCX9ay4zYfEpXbA0oKFgJvhBfK/w== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" luxon "^3.5.0" -"@abp/malihu-custom-scrollbar-plugin@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-9.0.0.tgz#b8a49202d531c711874a2bcdae5081ae0ed34c3f" - integrity sha512-iSZ9lKMnv5F2SplR+A4blhFEDq79lKbvBQuS7h/Di3KEHFOdJY4pA8NP8OR+PXOF3JfAezGyjqy7ydn/JNPRwA== +"@abp/malihu-custom-scrollbar-plugin@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-9.0.3.tgz#b3723f777b61c47ed64527b4295df33cca5cae26" + integrity sha512-aRearvDO1OaV7wfsWR2/cieWR/hG9cX78YGE08goRq0xLcGnBkmraQHSGGSZbMwId5zf5Kh3ePVXChO7llitkQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-9.0.0.tgz#9583e97472ddf608a9a5a6b8a37fee602b419900" - integrity sha512-wgyJSKmBmHLXZJN3dy3zK4i2JvWGgqnvdtrKURy/raEurxlymv8JF5JA971kTv6Kz2OA/OSYttpVJkDGUpPAhA== +"@abp/moment@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-9.0.3.tgz#696748659e036a1bf707684340703a861f340a92" + integrity sha512-7SRIjSHOyDTbuZ41vpisft+c5L+E3fk0G+V5+rpsACGTm6SpRqnVuAl5egbrDcm9rQeP0+VSV+/6UXdM7koOCQ== dependencies: moment "^2.30.1" -"@abp/prismjs@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-9.0.0.tgz#2ac4a5ddc0c8e0462af027a4dd97a177a3d6f7a5" - integrity sha512-+evmBq5qV+Ix/TNqO2ljJn0jygW2rEgKNtABcBGmrZC8xiI7+2hjKmSE0e43qb+RDib8aJjwH8ya8wF7p68ZFw== +"@abp/prismjs@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-9.0.3.tgz#9675dacd5df511120dffde76e925a8344ca9bace" + integrity sha512-5nMAKRADa6BQh3orquGHCUzqoZyDhmvoQ7RBCJsM3e0W+wBPeV9qmF+dJjOf9d+c9dr2mpU2SrH45fO/BNqgWg== dependencies: - "@abp/clipboard" "~9.0.0" - "@abp/core" "~9.0.0" + "@abp/clipboard" "~9.0.3" + "@abp/core" "~9.0.3" prismjs "^1.29.0" -"@abp/select2@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-9.0.0.tgz#71503eebb746f40a0e565dba1efa05d6ae937b77" - integrity sha512-l+Hz2Qmc7AcyaTJOyuDaV99RbBWYxYxbeyVEc4xKbRFbJxYBUxT/DFlyozEaoz+OU1fl4szO3UCthJ2yZvxCdg== +"@abp/select2@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-9.0.3.tgz#d75afa45decc4ce5a25ecb257f76a9f4c4c1b1fd" + integrity sha512-Cs/cQHdqV0U/c3RXbTTfIQoTaJqjBZMGon8cHX6rmvCDhWKOU/B2E+R2wNlcMjl0Tkhfy3Jl1mwlD0KZ/LePbA== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" select2 "^4.0.13" -"@abp/sweetalert2@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-9.0.0.tgz#71e511117c18b515e5c658e9cc0667770af65d91" - integrity sha512-wxDqoA6RUhkbcN27Kzslu/m2ai5T9AkJRtgs7pV55LvPv1ohuCPEUeTVVh8S/5Pgmr7aPFAPxaaCO32aPcImPw== +"@abp/sweetalert2@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-9.0.3.tgz#b7619bcf664b7aaf51f096df1b60f604db36c84c" + integrity sha512-W9fQu1jGhzqliyqKU/OFtAHiS3b8kar3LN9Pij2g/Q8zS2esjdgdt4LoaI5SytRi7zXUqn2tCi/cDEDXf/X3zQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" sweetalert2 "^11.14.1" -"@abp/timeago@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-9.0.0.tgz#8f262ba2a74e1c133d0c129f8a0bc55c29441e1a" - integrity sha512-eaNdIUEtDXnuYqS5O7EGRV+j6XOLAsxz7gd5BIKR9lDujd8pEjSJkXKO+/0KxLTkUSy3JudIO37K1h2wTpbYZA== +"@abp/timeago@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-9.0.3.tgz#8e675aafcc862643303909c27bbb982aa1c3c22c" + integrity sha512-B2ZHs4IfZyy+YHLlS/KLhxbqvxTkEEz1y+Sk8HtVSk0Ula5UD+U1FvwcxOiYGIghmHlkqFBtegifZosPOMB9VQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" timeago "^1.6.7" -"@abp/toastr@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-9.0.0.tgz#74d74ecb3e7f2d63b2ff7735c89a40deb3dc3a13" - integrity sha512-n9uJvw8l2HoxeowPO4diEMxwchGh6puefhyRljFAAstzK77Mhn0u2J7kPynNv94rLSYTYoCxUeJ4TTN+/TVDGQ== +"@abp/toastr@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-9.0.3.tgz#1863fea4d709b524f8fea0233dfd0ff8b1bc5bb2" + integrity sha512-3eV17OBB8XLJC9dZWW6xzi4fpu5NZqNrRiKiYJf79IUvmuS0cBVKpNJVKRaDTp4DeDBHxCQOWmMnrNI/mh3bDQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" toastr "^2.1.4" -"@abp/utils@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-9.0.0.tgz#2264fd83105b061860edd98dbe0efdfe55803ed5" - integrity sha512-/SB4VYk3Av+ltXv6ovkFHG4iWiECLLitaYWcEHxVAO80ufNjrNnWpHFpXHP8u/RpXIp/CoNiHLr6j5TkZUTZjw== +"@abp/utils@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-9.0.3.tgz#02dfd981d210be8229d65f802f0721b42c9e17d7" + integrity sha512-45sQQ6IV3cB9KAIVtRn3IH1wVr9DfM0PMFiwVt3F+wKwNBAPsKNcMPt4/EhZCNY3IMaEseJ7K/lxt33O3sURuA== dependencies: just-compare "^2.3.0" diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/package.json b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/package.json index 8857cdc4b9..4d0012245b 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/package.json +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/package.json @@ -3,8 +3,8 @@ "name": "asp.net", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~9.0.0", - "@abp/prismjs": "~9.0.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~9.0.3", + "@abp/prismjs": "~9.0.3" }, "devDependencies": {} } diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/yarn.lock b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/yarn.lock index 01d7637c9e..303743b39b 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/yarn.lock +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/yarn.lock @@ -2,211 +2,211 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-9.0.0.tgz#e4b05d63446c24b612275e3949881e176d32527f" - integrity sha512-GHEt26xESe/TCg1q6ZjeUbxniqdxaiB1OPTZ9md7ONql6bEp8t9iUPeSlkyAZtQ7qBzwgkiLpkRFT+J21DftaQ== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~9.0.0" - -"@abp/aspnetcore.mvc.ui.theme.shared@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-9.0.0.tgz#a77b4b70fa6217146afd6a57211f57e2360564f2" - integrity sha512-QmZrSp+rfqfnC9W4QZ+nNdAq0rOALzRn1rsDpGDHhIHwyXB03QPdjzIWaEvldUYHzqg84cEP0LcqW4y57Xr1iw== - dependencies: - "@abp/aspnetcore.mvc.ui" "~9.0.0" - "@abp/bootstrap" "~9.0.0" - "@abp/bootstrap-datepicker" "~9.0.0" - "@abp/bootstrap-daterangepicker" "~9.0.0" - "@abp/datatables.net-bs5" "~9.0.0" - "@abp/font-awesome" "~9.0.0" - "@abp/jquery-form" "~9.0.0" - "@abp/jquery-validation-unobtrusive" "~9.0.0" - "@abp/lodash" "~9.0.0" - "@abp/luxon" "~9.0.0" - "@abp/malihu-custom-scrollbar-plugin" "~9.0.0" - "@abp/moment" "~9.0.0" - "@abp/select2" "~9.0.0" - "@abp/sweetalert2" "~9.0.0" - "@abp/timeago" "~9.0.0" - "@abp/toastr" "~9.0.0" - -"@abp/aspnetcore.mvc.ui@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-9.0.0.tgz#93004dd056c130386c7b40c458f3a793d8c9e051" - integrity sha512-pvO5bh9baFcSbfW0s7tpMpbe6DKSC16jeyeluZLlvYxC0n2gkrv7H/hB5aMuBKIlh89aGY9DQIcMFC8iczL3Bg== +"@abp/aspnetcore.mvc.ui.theme.basic@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-9.0.3.tgz#dcb95764ee11fc11ca593b722575275837cc58a9" + integrity sha512-NwTunUVwfaoZsNp1Js6B+jGPBw96taBhQ5hloKhtZFZxG2VMAN9Gy9fGxzUByYlpRB8CASR3kL+GRhrK2Ufc3A== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~9.0.3" + +"@abp/aspnetcore.mvc.ui.theme.shared@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-9.0.3.tgz#18f0d27cbba7ff22576c1c6e54891190c7d900bb" + integrity sha512-WHMvMiZhqvPLQtqo3aI+wkhNbZ3gCkWqwhIiXHvRks+tO92ww52evPlZUNDSWSSkhcB9vJddx5Eqv4phJgcMAA== + dependencies: + "@abp/aspnetcore.mvc.ui" "~9.0.3" + "@abp/bootstrap" "~9.0.3" + "@abp/bootstrap-datepicker" "~9.0.3" + "@abp/bootstrap-daterangepicker" "~9.0.3" + "@abp/datatables.net-bs5" "~9.0.3" + "@abp/font-awesome" "~9.0.3" + "@abp/jquery-form" "~9.0.3" + "@abp/jquery-validation-unobtrusive" "~9.0.3" + "@abp/lodash" "~9.0.3" + "@abp/luxon" "~9.0.3" + "@abp/malihu-custom-scrollbar-plugin" "~9.0.3" + "@abp/moment" "~9.0.3" + "@abp/select2" "~9.0.3" + "@abp/sweetalert2" "~9.0.3" + "@abp/timeago" "~9.0.3" + "@abp/toastr" "~9.0.3" + +"@abp/aspnetcore.mvc.ui@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-9.0.3.tgz#a4e2e92cddca77d81d8445bcc1d2cc129ec4b486" + integrity sha512-wkn7R8sx5lhbaZeTAy5aJxLbR1f3TD8lNaNtM1Lsc7wWq9dI19wyJZiAWqCZij2HOr2IK5izSROVfTqoXyUwXQ== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-9.0.0.tgz#d6609e50568aac6a013abfcaa07817998e1eaa99" - integrity sha512-wxAeVsOVOJpidDn0g3FlJvpOsn5SVY5IpEG+FtUxSAKuWYJQWF6g5CaghrfS+FNDK9IHhxrgSMPMVBK9YlDSzw== +"@abp/bootstrap-datepicker@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-9.0.3.tgz#aa40f5a31eb5c06224c165129a882658f4a0a9a5" + integrity sha512-Jg4oeBPtX7G41JdYjHVhB/6KQcMJO4WWXbUvkNetSUGTZGqacmgWRAIA5abmRWaHMJP1A1soZ9Ny1GNy0Qu65Q== dependencies: bootstrap-datepicker "^1.10.0" -"@abp/bootstrap-daterangepicker@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-9.0.0.tgz#fc2871e7d886accd8e4748cb763b695e10701c00" - integrity sha512-qS6SQR6X7wMCVLcuXatJ2+m1GNlpN71p7VDritxfwAMHa7WBRAAi8apX2WItwBAuLQ2s1uSA0Wp1KstHG3VPLA== +"@abp/bootstrap-daterangepicker@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-9.0.3.tgz#a1c17721a1ff6613a3ee4ff971174b32a93d98de" + integrity sha512-wrvjb0bJudohxiYtecCGEa4VsvL/Kn/Z3q9w1jvIoallrTXQaACPtZDU+9yxK5mRf6zonwYI8JtNn4xKa+D1VA== dependencies: bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-9.0.0.tgz#5ab292e95ba7eef129cbaa1dc5f5d5c0af8530d7" - integrity sha512-jEr6su8BfoNxYvze/UlddE3DlezyPvQeiNhff8SDPqtRQ6XvjZ48O/JwtY01hK2njEDLOZINY5zJRGmO2OiOUw== +"@abp/bootstrap@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-9.0.3.tgz#dc0325c23282086c9d2be79588b228e8b2c66eb2" + integrity sha512-ypl0R3jky6qKmnEZjnSniwgN8unNmWD9d4bOtV5RgDF2MlBQT+CvfUcYnGom0JOsL+5RjSNvzFH/Q5aH/yuy7Q== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" bootstrap "^5.3.3" -"@abp/clipboard@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-9.0.0.tgz#21ae924dfef6ccf3c8a3913bb81caf22b57404e4" - integrity sha512-W6S7pihpQ6Wfj48Clk7hYgDvUtnRwkNIsk5t8s9+LxoAdJQGQFxh7SAW1QNavUv8QqsClNE1VtSU87c7rSoAbA== +"@abp/clipboard@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-9.0.3.tgz#bda02ce6da876890ef117697f30348b8c16aaf70" + integrity sha512-iprJtnshdpRgLIPxa5Tgj1IqkLvkl2QxTmjrqeiuRhU3S1ry0GnE6dx3jAealWUOoYr/bqXPbpXi3RN7R4baHw== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" clipboard "^2.0.11" -"@abp/core@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-9.0.0.tgz#dc5ea9139957e307f73ac1f40b179f991e3c2168" - integrity sha512-vzQyTpqVDx63pOI9e2QLUMWZCrSojHsRnzLlvl+/cKFi7jE1q/uSP66FPR2bwkD+DNLI/tn6is1Jx3skKA1FvA== +"@abp/core@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-9.0.3.tgz#8c508dc60628fbd9e8a9ba35d153316cacbf1fb4" + integrity sha512-4bHiyP2qPrEeXb/Ynl17b6LtfygWdkwsjHaadUbym7Lg5qYUQZ3KjW4U2DihYWCO46gsr/ckTEbV9edcRd+NxA== dependencies: - "@abp/utils" "~9.0.0" + "@abp/utils" "~9.0.3" -"@abp/datatables.net-bs5@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-9.0.0.tgz#4daee90bdb6446e854ce65f2a3afabb2a13e49ae" - integrity sha512-ln10tcRwxcB0nuSgqJ/66strHsqMHDC03c0ktxEplAN34yP7KInjFf/U9Svo74K0nKlkJdMCgmqmT4CnBJZlIg== +"@abp/datatables.net-bs5@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-9.0.3.tgz#c89813a69a1b568a171cb2a4b1bcc4b7a4ef1bef" + integrity sha512-SD6N4apScOR22S7EntengOTQ1HSm2WnvzUuP/6HG50isXyMheRalZxJ6zAsBwiWyYyJtA+UHkiQzwVhdV40aBQ== dependencies: - "@abp/datatables.net" "~9.0.0" + "@abp/datatables.net" "~9.0.3" datatables.net-bs5 "^2.1.8" -"@abp/datatables.net@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-9.0.0.tgz#82b28518293c9091ec72d5defae2f5d2903d8bbc" - integrity sha512-QhuX3bGZOckM/fhIIGj/StMJIX2BBtNbLzjTR7WnKMMxDkv0+oDvjw6OqqpYKSvj/q3vY6sRWuCNdEzNi0Fk6A== +"@abp/datatables.net@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-9.0.3.tgz#9cc09127ff6269476b6b0de16048e1e6480227c4" + integrity sha512-zLzHxdXnmL8pWKVzf3m4SIUMiPHHe3I9Cw8vezk5fK1v18Fk364o3Q8o8C1l1YDGEa7Sx1wsUoLDN+p4FzagXg== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" datatables.net "^2.1.8" -"@abp/font-awesome@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-9.0.0.tgz#51cce18434a51d050ebb0d822a8c193b7e32f73d" - integrity sha512-bEHRVzOk0V2kFMAvB/YXFYaDKWRFO0+ScO7jrEFtFgdHXBcqy+dk1aM/N69WalyNmjRRpnh03GhH1LRpwL4/xA== +"@abp/font-awesome@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-9.0.3.tgz#db6ece04fcdb4880cac8174deffb4fa04d43d2de" + integrity sha512-toI2zGayMeI4EUUMmittRWKBY/mLjiiX1zA2z70q/Hpm9zoCly2KwbN7zwtWbayhESO7edci9T7CXF5kNdiNow== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" "@fortawesome/fontawesome-free" "^6.6.0" -"@abp/jquery-form@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-9.0.0.tgz#6518eb117ee35c6bc57543fd1fccb0a8cf8e22f4" - integrity sha512-6qpfPGHrjADmm7Ne3zcXBGPuLngG8ldAn5fk9k2taftpyX0iR67/nZxiQ0ujaQGgaai5Wb3imNXMG96XLnubkQ== +"@abp/jquery-form@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-9.0.3.tgz#907033a496af127bfbe4e220f43cda65331e7b98" + integrity sha512-kD/9S25kJSAj5f7bfHVMfzfF5/0OrLDj8E55M27zbusdcaFS/nYElzrU3DeAEusASdnssRAwyJCWyp2vvRIMzA== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-9.0.0.tgz#8ee87dbdec5e92ab04ae9642a49af1598af31c1a" - integrity sha512-KlRQ1htyFmAIMS9xbHvHIcdhAgtPFe/u7c87MiWlk/CdhDrI3J+n5biLvQUzxCC+YWlwx27zBtPKx2vLeMRH6Q== +"@abp/jquery-validation-unobtrusive@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-9.0.3.tgz#b9449d222d4855f941dbe7445f73fff1541975a4" + integrity sha512-U/zUDYyJnUYDmmloOx/rViVyg7b4Gxd4zgcEi1E1YP0zryQQh5CkoSEvzv7GyurB1/rVZuRszmo/nRgn3pHOKg== dependencies: - "@abp/jquery-validation" "~9.0.0" + "@abp/jquery-validation" "~9.0.3" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-9.0.0.tgz#681d8037c57b3875bd51c927e350ffce65d7c5d2" - integrity sha512-XgCUBFWbhTDcGepD/xkwA/1Hl4TyYitRttauDJk3tuyXhNtykvdugCCOPY4j2s6VoT1Sb/TFFvOcgEvlBwkKiw== +"@abp/jquery-validation@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-9.0.3.tgz#02eb010267121fd9310cdcf5ca7c7b54091fd5de" + integrity sha512-tqjM5S0/jDS88CIt8oCQbHYJTytA7Y+KdTsnRkrA+BWy2A5bjrd6YUZXa/2UUwXHa3tQgv/uXl9HpDEaqRtfYQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" jquery-validation "^1.21.0" -"@abp/jquery@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-9.0.0.tgz#6c67a9f927be598ab6201f8888e8a3cb8bba0686" - integrity sha512-cUXA7v6F1HqAc/w8fQGxHIfA/6BqO+4uHXDnSsJZtrI26DTLOgw5kBNnAz2W4xggGPCY7SKFHNRZuOW/QfVRPg== +"@abp/jquery@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-9.0.3.tgz#ba8b4f4c0825ccb55c793024d3bbeb9ee817b19c" + integrity sha512-lZoyiyz83VOQKbN+9gD8JC+WI+OACc3y2hU+v30v220DLKhwoiVQM6d5yfA2QEugTPTjm/s40Ocmh/nVjm5mtg== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" jquery "~3.7.1" -"@abp/lodash@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-9.0.0.tgz#a4d42b2dfeb37859ff9f0079c7a8a6cabc982efe" - integrity sha512-xoAsOkkgnQdPH2OyFTgF6OlCLVbyGF4u8CrFcZWCZmuIFT9cg0+ZEiq//Vog1pg3Lf//N37ceGpOag1LwMe6cQ== +"@abp/lodash@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-9.0.3.tgz#daadfb98339f1d6982a12b655b840f105b324830" + integrity sha512-wt4ZJmRlhnK9W3o8lex7LyL/7rzgu4STLtZ2Ga8Ec5uZ9kN0CDTYjIw0zVqB8iGNKcW8MCxIGB7u9JMU9tphiQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" lodash "^4.17.21" -"@abp/luxon@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-9.0.0.tgz#a54dda13cbbd362481994d20d2e07c669c6ce109" - integrity sha512-aPejQnoVPfm60LnjwhfUkIsB+poNWusrBE0IHllxhvOVE+01nfa1Lvpp5AUWzV+XchPGOCSf+a29ds4EQWfNbg== +"@abp/luxon@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-9.0.3.tgz#62a5e395c92cadcf0985453eee065fcbe5354402" + integrity sha512-qTPw2vhgkgNldFJziHiL+c2vF7p+7RW1orev8K8XLSe3dguzlXBL2dicyDWCX9ay4zYfEpXbA0oKFgJvhBfK/w== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" luxon "^3.5.0" -"@abp/malihu-custom-scrollbar-plugin@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-9.0.0.tgz#b8a49202d531c711874a2bcdae5081ae0ed34c3f" - integrity sha512-iSZ9lKMnv5F2SplR+A4blhFEDq79lKbvBQuS7h/Di3KEHFOdJY4pA8NP8OR+PXOF3JfAezGyjqy7ydn/JNPRwA== +"@abp/malihu-custom-scrollbar-plugin@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-9.0.3.tgz#b3723f777b61c47ed64527b4295df33cca5cae26" + integrity sha512-aRearvDO1OaV7wfsWR2/cieWR/hG9cX78YGE08goRq0xLcGnBkmraQHSGGSZbMwId5zf5Kh3ePVXChO7llitkQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-9.0.0.tgz#9583e97472ddf608a9a5a6b8a37fee602b419900" - integrity sha512-wgyJSKmBmHLXZJN3dy3zK4i2JvWGgqnvdtrKURy/raEurxlymv8JF5JA971kTv6Kz2OA/OSYttpVJkDGUpPAhA== +"@abp/moment@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-9.0.3.tgz#696748659e036a1bf707684340703a861f340a92" + integrity sha512-7SRIjSHOyDTbuZ41vpisft+c5L+E3fk0G+V5+rpsACGTm6SpRqnVuAl5egbrDcm9rQeP0+VSV+/6UXdM7koOCQ== dependencies: moment "^2.30.1" -"@abp/prismjs@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-9.0.0.tgz#2ac4a5ddc0c8e0462af027a4dd97a177a3d6f7a5" - integrity sha512-+evmBq5qV+Ix/TNqO2ljJn0jygW2rEgKNtABcBGmrZC8xiI7+2hjKmSE0e43qb+RDib8aJjwH8ya8wF7p68ZFw== +"@abp/prismjs@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-9.0.3.tgz#9675dacd5df511120dffde76e925a8344ca9bace" + integrity sha512-5nMAKRADa6BQh3orquGHCUzqoZyDhmvoQ7RBCJsM3e0W+wBPeV9qmF+dJjOf9d+c9dr2mpU2SrH45fO/BNqgWg== dependencies: - "@abp/clipboard" "~9.0.0" - "@abp/core" "~9.0.0" + "@abp/clipboard" "~9.0.3" + "@abp/core" "~9.0.3" prismjs "^1.29.0" -"@abp/select2@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-9.0.0.tgz#71503eebb746f40a0e565dba1efa05d6ae937b77" - integrity sha512-l+Hz2Qmc7AcyaTJOyuDaV99RbBWYxYxbeyVEc4xKbRFbJxYBUxT/DFlyozEaoz+OU1fl4szO3UCthJ2yZvxCdg== +"@abp/select2@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-9.0.3.tgz#d75afa45decc4ce5a25ecb257f76a9f4c4c1b1fd" + integrity sha512-Cs/cQHdqV0U/c3RXbTTfIQoTaJqjBZMGon8cHX6rmvCDhWKOU/B2E+R2wNlcMjl0Tkhfy3Jl1mwlD0KZ/LePbA== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" select2 "^4.0.13" -"@abp/sweetalert2@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-9.0.0.tgz#71e511117c18b515e5c658e9cc0667770af65d91" - integrity sha512-wxDqoA6RUhkbcN27Kzslu/m2ai5T9AkJRtgs7pV55LvPv1ohuCPEUeTVVh8S/5Pgmr7aPFAPxaaCO32aPcImPw== +"@abp/sweetalert2@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-9.0.3.tgz#b7619bcf664b7aaf51f096df1b60f604db36c84c" + integrity sha512-W9fQu1jGhzqliyqKU/OFtAHiS3b8kar3LN9Pij2g/Q8zS2esjdgdt4LoaI5SytRi7zXUqn2tCi/cDEDXf/X3zQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" sweetalert2 "^11.14.1" -"@abp/timeago@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-9.0.0.tgz#8f262ba2a74e1c133d0c129f8a0bc55c29441e1a" - integrity sha512-eaNdIUEtDXnuYqS5O7EGRV+j6XOLAsxz7gd5BIKR9lDujd8pEjSJkXKO+/0KxLTkUSy3JudIO37K1h2wTpbYZA== +"@abp/timeago@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-9.0.3.tgz#8e675aafcc862643303909c27bbb982aa1c3c22c" + integrity sha512-B2ZHs4IfZyy+YHLlS/KLhxbqvxTkEEz1y+Sk8HtVSk0Ula5UD+U1FvwcxOiYGIghmHlkqFBtegifZosPOMB9VQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" timeago "^1.6.7" -"@abp/toastr@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-9.0.0.tgz#74d74ecb3e7f2d63b2ff7735c89a40deb3dc3a13" - integrity sha512-n9uJvw8l2HoxeowPO4diEMxwchGh6puefhyRljFAAstzK77Mhn0u2J7kPynNv94rLSYTYoCxUeJ4TTN+/TVDGQ== +"@abp/toastr@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-9.0.3.tgz#1863fea4d709b524f8fea0233dfd0ff8b1bc5bb2" + integrity sha512-3eV17OBB8XLJC9dZWW6xzi4fpu5NZqNrRiKiYJf79IUvmuS0cBVKpNJVKRaDTp4DeDBHxCQOWmMnrNI/mh3bDQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" toastr "^2.1.4" -"@abp/utils@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-9.0.0.tgz#2264fd83105b061860edd98dbe0efdfe55803ed5" - integrity sha512-/SB4VYk3Av+ltXv6ovkFHG4iWiECLLitaYWcEHxVAO80ufNjrNnWpHFpXHP8u/RpXIp/CoNiHLr6j5TkZUTZjw== +"@abp/utils@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-9.0.3.tgz#02dfd981d210be8229d65f802f0721b42c9e17d7" + integrity sha512-45sQQ6IV3cB9KAIVtRn3IH1wVr9DfM0PMFiwVt3F+wKwNBAPsKNcMPt4/EhZCNY3IMaEseJ7K/lxt33O3sURuA== dependencies: just-compare "^2.3.0" diff --git a/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs b/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs index 6e2383dcbd..47447e1fdf 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs +++ b/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs @@ -1,4 +1,4 @@ -//#define MONGODB +#define MONGODB using System.Collections.Generic; using System.Globalization; @@ -26,9 +26,14 @@ using Volo.Abp.Autofac; using Volo.Abp.BlobStoring; using Volo.Abp.BlobStoring.Database; using Volo.Abp.Data; +#if MONGODB +using Volo.Abp.MongoDB; +#else using Volo.Abp.EntityFrameworkCore; +#endif using Volo.Abp.Identity; using Volo.Abp.Identity.Web; +using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement; using Volo.Abp.PermissionManagement.HttpApi; @@ -39,7 +44,11 @@ using Volo.Abp.VirtualFileSystem; using Volo.Blogging; using Volo.Blogging.Admin; using Volo.Blogging.Files; +#if MONGODB +using Volo.BloggingTestApp.MongoDB; +#else using Volo.BloggingTestApp.EntityFrameworkCore; +#endif namespace Volo.BloggingTestApp { @@ -78,7 +87,7 @@ namespace Volo.BloggingTestApp Configure(options => { options.RoutePrefix = null; - options.SingleBlogMode.Enabled = true; + options.SingleBlogMode.Enabled = false; }); Configure(options => @@ -146,6 +155,22 @@ namespace Volo.BloggingTestApp container.UseDatabase(); }); }); + + Configure(options => + { + options.Languages.Add(new LanguageInfo("ar", "ar", "العربية")); + options.Languages.Add(new LanguageInfo("en", "en", "English")); + options.Languages.Add(new LanguageInfo("cs", "cs", "Čeština")); + options.Languages.Add(new LanguageInfo("fi", "fi", "Finnish")); + options.Languages.Add(new LanguageInfo("fr", "fr", "Français")); + options.Languages.Add(new LanguageInfo("sk", "sk", "Slovak")); + options.Languages.Add(new LanguageInfo("hi", "hi", "Hindi")); + options.Languages.Add(new LanguageInfo("it", "it", "Italiano")); + options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe")); + options.Languages.Add(new LanguageInfo("pt-BR", "pt-BR", "Português")); + options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); + options.Languages.Add(new LanguageInfo("zh-Hant", "zh-Hant", "繁体中文")); + }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) diff --git a/modules/blogging/app/Volo.BloggingTestApp/Program.cs b/modules/blogging/app/Volo.BloggingTestApp/Program.cs index 2741daf2fe..cb0948096d 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/Program.cs +++ b/modules/blogging/app/Volo.BloggingTestApp/Program.cs @@ -1,6 +1,9 @@ using System; using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Serilog; using Serilog.Events; @@ -9,7 +12,7 @@ namespace Volo.BloggingTestApp { public class Program { - public static int Main(string[] args) + public async static Task Main(string[] args) { Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() //TODO: Should be configurable! @@ -22,7 +25,14 @@ namespace Volo.BloggingTestApp try { Log.Information("Starting web host."); - CreateHostBuilder(args).Build().Run(); + var builder = WebApplication.CreateBuilder(args); + builder.Host + .UseAutofac() + .UseSerilog(); + await builder.AddApplicationAsync(); + var app = builder.Build(); + await app.InitializeApplicationAsync(); + await app.RunAsync(); return 0; } catch (Exception ex) @@ -35,14 +45,5 @@ namespace Volo.BloggingTestApp Log.CloseAndFlush(); } } - - internal static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }) - .UseAutofac() - .UseSerilog(); } } diff --git a/modules/blogging/app/Volo.BloggingTestApp/Startup.cs b/modules/blogging/app/Volo.BloggingTestApp/Startup.cs deleted file mode 100644 index 0f4a310afc..0000000000 --- a/modules/blogging/app/Volo.BloggingTestApp/Startup.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Volo.Abp; -using Volo.Abp.Localization; - -namespace Volo.BloggingTestApp -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - services.AddApplication(); - - services.Configure(options => - { - options.Languages.Add(new LanguageInfo("ar", "ar", "العربية")); - options.Languages.Add(new LanguageInfo("en", "en", "English")); - options.Languages.Add(new LanguageInfo("cs", "cs", "Čeština")); - options.Languages.Add(new LanguageInfo("fi", "fi", "Finnish")); - options.Languages.Add(new LanguageInfo("fr", "fr", "Français")); - options.Languages.Add(new LanguageInfo("sk", "sk", "Slovak")); - options.Languages.Add(new LanguageInfo("hi", "hi", "Hindi")); - options.Languages.Add(new LanguageInfo("it", "it", "Italiano")); - options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe")); - options.Languages.Add(new LanguageInfo("pt-BR", "pt-BR", "Português")); - options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); - options.Languages.Add(new LanguageInfo("zh-Hant", "zh-Hant", "繁体中文")); - }); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) - { - app.InitializeApplication(); - } - } -} diff --git a/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj b/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj index 4e58d52df0..e157d32f25 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj +++ b/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj @@ -23,7 +23,7 @@ - + diff --git a/modules/blogging/app/Volo.BloggingTestApp/package.json b/modules/blogging/app/Volo.BloggingTestApp/package.json index 25c37cda9f..02f2a1e2b6 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/package.json +++ b/modules/blogging/app/Volo.BloggingTestApp/package.json @@ -3,7 +3,7 @@ "name": "volo.blogtestapp", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~9.0.0", - "@abp/blogging": "~9.0.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~9.0.3", + "@abp/blogging": "~9.0.3" } } diff --git a/modules/blogging/app/Volo.BloggingTestApp/yarn.lock b/modules/blogging/app/Volo.BloggingTestApp/yarn.lock index 4a01d58b56..fc10d219c0 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/yarn.lock +++ b/modules/blogging/app/Volo.BloggingTestApp/yarn.lock @@ -2,237 +2,237 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-9.0.0.tgz#e4b05d63446c24b612275e3949881e176d32527f" - integrity sha512-GHEt26xESe/TCg1q6ZjeUbxniqdxaiB1OPTZ9md7ONql6bEp8t9iUPeSlkyAZtQ7qBzwgkiLpkRFT+J21DftaQ== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~9.0.0" - -"@abp/aspnetcore.mvc.ui.theme.shared@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-9.0.0.tgz#a77b4b70fa6217146afd6a57211f57e2360564f2" - integrity sha512-QmZrSp+rfqfnC9W4QZ+nNdAq0rOALzRn1rsDpGDHhIHwyXB03QPdjzIWaEvldUYHzqg84cEP0LcqW4y57Xr1iw== - dependencies: - "@abp/aspnetcore.mvc.ui" "~9.0.0" - "@abp/bootstrap" "~9.0.0" - "@abp/bootstrap-datepicker" "~9.0.0" - "@abp/bootstrap-daterangepicker" "~9.0.0" - "@abp/datatables.net-bs5" "~9.0.0" - "@abp/font-awesome" "~9.0.0" - "@abp/jquery-form" "~9.0.0" - "@abp/jquery-validation-unobtrusive" "~9.0.0" - "@abp/lodash" "~9.0.0" - "@abp/luxon" "~9.0.0" - "@abp/malihu-custom-scrollbar-plugin" "~9.0.0" - "@abp/moment" "~9.0.0" - "@abp/select2" "~9.0.0" - "@abp/sweetalert2" "~9.0.0" - "@abp/timeago" "~9.0.0" - "@abp/toastr" "~9.0.0" - -"@abp/aspnetcore.mvc.ui@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-9.0.0.tgz#93004dd056c130386c7b40c458f3a793d8c9e051" - integrity sha512-pvO5bh9baFcSbfW0s7tpMpbe6DKSC16jeyeluZLlvYxC0n2gkrv7H/hB5aMuBKIlh89aGY9DQIcMFC8iczL3Bg== +"@abp/aspnetcore.mvc.ui.theme.basic@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-9.0.3.tgz#dcb95764ee11fc11ca593b722575275837cc58a9" + integrity sha512-NwTunUVwfaoZsNp1Js6B+jGPBw96taBhQ5hloKhtZFZxG2VMAN9Gy9fGxzUByYlpRB8CASR3kL+GRhrK2Ufc3A== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~9.0.3" + +"@abp/aspnetcore.mvc.ui.theme.shared@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-9.0.3.tgz#18f0d27cbba7ff22576c1c6e54891190c7d900bb" + integrity sha512-WHMvMiZhqvPLQtqo3aI+wkhNbZ3gCkWqwhIiXHvRks+tO92ww52evPlZUNDSWSSkhcB9vJddx5Eqv4phJgcMAA== + dependencies: + "@abp/aspnetcore.mvc.ui" "~9.0.3" + "@abp/bootstrap" "~9.0.3" + "@abp/bootstrap-datepicker" "~9.0.3" + "@abp/bootstrap-daterangepicker" "~9.0.3" + "@abp/datatables.net-bs5" "~9.0.3" + "@abp/font-awesome" "~9.0.3" + "@abp/jquery-form" "~9.0.3" + "@abp/jquery-validation-unobtrusive" "~9.0.3" + "@abp/lodash" "~9.0.3" + "@abp/luxon" "~9.0.3" + "@abp/malihu-custom-scrollbar-plugin" "~9.0.3" + "@abp/moment" "~9.0.3" + "@abp/select2" "~9.0.3" + "@abp/sweetalert2" "~9.0.3" + "@abp/timeago" "~9.0.3" + "@abp/toastr" "~9.0.3" + +"@abp/aspnetcore.mvc.ui@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-9.0.3.tgz#a4e2e92cddca77d81d8445bcc1d2cc129ec4b486" + integrity sha512-wkn7R8sx5lhbaZeTAy5aJxLbR1f3TD8lNaNtM1Lsc7wWq9dI19wyJZiAWqCZij2HOr2IK5izSROVfTqoXyUwXQ== dependencies: ansi-colors "^4.1.3" -"@abp/blogging@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/blogging/-/blogging-9.0.0.tgz#bba9e0288e215473b1851ab58d4898a2455c2afe" - integrity sha512-tsKqEYnVhDrBHVMOwSTLR+OudEnxBpj5RhDiLPA+MO+FSqiJpuxlDI2SlH2Sz7alCF1RnAM/alITvrKq9TR/gg== +"@abp/blogging@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/blogging/-/blogging-9.0.3.tgz#0ac6f5276b90487fc30514ac93d8d8b79620426b" + integrity sha512-D5T9IAlAkcMAomobvlG2d1ioLDv6fczLrxCqVtDB1a2yMad8aCZr6VtTjiWCSm2AtYkPjLKkIeXir3O7bzXEHw== dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~9.0.0" - "@abp/owl.carousel" "~9.0.0" - "@abp/prismjs" "~9.0.0" - "@abp/tui-editor" "~9.0.0" + "@abp/aspnetcore.mvc.ui.theme.shared" "~9.0.3" + "@abp/owl.carousel" "~9.0.3" + "@abp/prismjs" "~9.0.3" + "@abp/tui-editor" "~9.0.3" -"@abp/bootstrap-datepicker@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-9.0.0.tgz#d6609e50568aac6a013abfcaa07817998e1eaa99" - integrity sha512-wxAeVsOVOJpidDn0g3FlJvpOsn5SVY5IpEG+FtUxSAKuWYJQWF6g5CaghrfS+FNDK9IHhxrgSMPMVBK9YlDSzw== +"@abp/bootstrap-datepicker@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-9.0.3.tgz#aa40f5a31eb5c06224c165129a882658f4a0a9a5" + integrity sha512-Jg4oeBPtX7G41JdYjHVhB/6KQcMJO4WWXbUvkNetSUGTZGqacmgWRAIA5abmRWaHMJP1A1soZ9Ny1GNy0Qu65Q== dependencies: bootstrap-datepicker "^1.10.0" -"@abp/bootstrap-daterangepicker@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-9.0.0.tgz#fc2871e7d886accd8e4748cb763b695e10701c00" - integrity sha512-qS6SQR6X7wMCVLcuXatJ2+m1GNlpN71p7VDritxfwAMHa7WBRAAi8apX2WItwBAuLQ2s1uSA0Wp1KstHG3VPLA== +"@abp/bootstrap-daterangepicker@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-9.0.3.tgz#a1c17721a1ff6613a3ee4ff971174b32a93d98de" + integrity sha512-wrvjb0bJudohxiYtecCGEa4VsvL/Kn/Z3q9w1jvIoallrTXQaACPtZDU+9yxK5mRf6zonwYI8JtNn4xKa+D1VA== dependencies: bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-9.0.0.tgz#5ab292e95ba7eef129cbaa1dc5f5d5c0af8530d7" - integrity sha512-jEr6su8BfoNxYvze/UlddE3DlezyPvQeiNhff8SDPqtRQ6XvjZ48O/JwtY01hK2njEDLOZINY5zJRGmO2OiOUw== +"@abp/bootstrap@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-9.0.3.tgz#dc0325c23282086c9d2be79588b228e8b2c66eb2" + integrity sha512-ypl0R3jky6qKmnEZjnSniwgN8unNmWD9d4bOtV5RgDF2MlBQT+CvfUcYnGom0JOsL+5RjSNvzFH/Q5aH/yuy7Q== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" bootstrap "^5.3.3" -"@abp/clipboard@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-9.0.0.tgz#21ae924dfef6ccf3c8a3913bb81caf22b57404e4" - integrity sha512-W6S7pihpQ6Wfj48Clk7hYgDvUtnRwkNIsk5t8s9+LxoAdJQGQFxh7SAW1QNavUv8QqsClNE1VtSU87c7rSoAbA== +"@abp/clipboard@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-9.0.3.tgz#bda02ce6da876890ef117697f30348b8c16aaf70" + integrity sha512-iprJtnshdpRgLIPxa5Tgj1IqkLvkl2QxTmjrqeiuRhU3S1ry0GnE6dx3jAealWUOoYr/bqXPbpXi3RN7R4baHw== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" clipboard "^2.0.11" -"@abp/core@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-9.0.0.tgz#dc5ea9139957e307f73ac1f40b179f991e3c2168" - integrity sha512-vzQyTpqVDx63pOI9e2QLUMWZCrSojHsRnzLlvl+/cKFi7jE1q/uSP66FPR2bwkD+DNLI/tn6is1Jx3skKA1FvA== +"@abp/core@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-9.0.3.tgz#8c508dc60628fbd9e8a9ba35d153316cacbf1fb4" + integrity sha512-4bHiyP2qPrEeXb/Ynl17b6LtfygWdkwsjHaadUbym7Lg5qYUQZ3KjW4U2DihYWCO46gsr/ckTEbV9edcRd+NxA== dependencies: - "@abp/utils" "~9.0.0" + "@abp/utils" "~9.0.3" -"@abp/datatables.net-bs5@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-9.0.0.tgz#4daee90bdb6446e854ce65f2a3afabb2a13e49ae" - integrity sha512-ln10tcRwxcB0nuSgqJ/66strHsqMHDC03c0ktxEplAN34yP7KInjFf/U9Svo74K0nKlkJdMCgmqmT4CnBJZlIg== +"@abp/datatables.net-bs5@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-9.0.3.tgz#c89813a69a1b568a171cb2a4b1bcc4b7a4ef1bef" + integrity sha512-SD6N4apScOR22S7EntengOTQ1HSm2WnvzUuP/6HG50isXyMheRalZxJ6zAsBwiWyYyJtA+UHkiQzwVhdV40aBQ== dependencies: - "@abp/datatables.net" "~9.0.0" + "@abp/datatables.net" "~9.0.3" datatables.net-bs5 "^2.1.8" -"@abp/datatables.net@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-9.0.0.tgz#82b28518293c9091ec72d5defae2f5d2903d8bbc" - integrity sha512-QhuX3bGZOckM/fhIIGj/StMJIX2BBtNbLzjTR7WnKMMxDkv0+oDvjw6OqqpYKSvj/q3vY6sRWuCNdEzNi0Fk6A== +"@abp/datatables.net@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-9.0.3.tgz#9cc09127ff6269476b6b0de16048e1e6480227c4" + integrity sha512-zLzHxdXnmL8pWKVzf3m4SIUMiPHHe3I9Cw8vezk5fK1v18Fk364o3Q8o8C1l1YDGEa7Sx1wsUoLDN+p4FzagXg== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" datatables.net "^2.1.8" -"@abp/font-awesome@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-9.0.0.tgz#51cce18434a51d050ebb0d822a8c193b7e32f73d" - integrity sha512-bEHRVzOk0V2kFMAvB/YXFYaDKWRFO0+ScO7jrEFtFgdHXBcqy+dk1aM/N69WalyNmjRRpnh03GhH1LRpwL4/xA== +"@abp/font-awesome@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-9.0.3.tgz#db6ece04fcdb4880cac8174deffb4fa04d43d2de" + integrity sha512-toI2zGayMeI4EUUMmittRWKBY/mLjiiX1zA2z70q/Hpm9zoCly2KwbN7zwtWbayhESO7edci9T7CXF5kNdiNow== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" "@fortawesome/fontawesome-free" "^6.6.0" -"@abp/jquery-form@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-9.0.0.tgz#6518eb117ee35c6bc57543fd1fccb0a8cf8e22f4" - integrity sha512-6qpfPGHrjADmm7Ne3zcXBGPuLngG8ldAn5fk9k2taftpyX0iR67/nZxiQ0ujaQGgaai5Wb3imNXMG96XLnubkQ== +"@abp/jquery-form@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-9.0.3.tgz#907033a496af127bfbe4e220f43cda65331e7b98" + integrity sha512-kD/9S25kJSAj5f7bfHVMfzfF5/0OrLDj8E55M27zbusdcaFS/nYElzrU3DeAEusASdnssRAwyJCWyp2vvRIMzA== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-9.0.0.tgz#8ee87dbdec5e92ab04ae9642a49af1598af31c1a" - integrity sha512-KlRQ1htyFmAIMS9xbHvHIcdhAgtPFe/u7c87MiWlk/CdhDrI3J+n5biLvQUzxCC+YWlwx27zBtPKx2vLeMRH6Q== +"@abp/jquery-validation-unobtrusive@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-9.0.3.tgz#b9449d222d4855f941dbe7445f73fff1541975a4" + integrity sha512-U/zUDYyJnUYDmmloOx/rViVyg7b4Gxd4zgcEi1E1YP0zryQQh5CkoSEvzv7GyurB1/rVZuRszmo/nRgn3pHOKg== dependencies: - "@abp/jquery-validation" "~9.0.0" + "@abp/jquery-validation" "~9.0.3" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-9.0.0.tgz#681d8037c57b3875bd51c927e350ffce65d7c5d2" - integrity sha512-XgCUBFWbhTDcGepD/xkwA/1Hl4TyYitRttauDJk3tuyXhNtykvdugCCOPY4j2s6VoT1Sb/TFFvOcgEvlBwkKiw== +"@abp/jquery-validation@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-9.0.3.tgz#02eb010267121fd9310cdcf5ca7c7b54091fd5de" + integrity sha512-tqjM5S0/jDS88CIt8oCQbHYJTytA7Y+KdTsnRkrA+BWy2A5bjrd6YUZXa/2UUwXHa3tQgv/uXl9HpDEaqRtfYQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" jquery-validation "^1.21.0" -"@abp/jquery@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-9.0.0.tgz#6c67a9f927be598ab6201f8888e8a3cb8bba0686" - integrity sha512-cUXA7v6F1HqAc/w8fQGxHIfA/6BqO+4uHXDnSsJZtrI26DTLOgw5kBNnAz2W4xggGPCY7SKFHNRZuOW/QfVRPg== +"@abp/jquery@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-9.0.3.tgz#ba8b4f4c0825ccb55c793024d3bbeb9ee817b19c" + integrity sha512-lZoyiyz83VOQKbN+9gD8JC+WI+OACc3y2hU+v30v220DLKhwoiVQM6d5yfA2QEugTPTjm/s40Ocmh/nVjm5mtg== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" jquery "~3.7.1" -"@abp/lodash@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-9.0.0.tgz#a4d42b2dfeb37859ff9f0079c7a8a6cabc982efe" - integrity sha512-xoAsOkkgnQdPH2OyFTgF6OlCLVbyGF4u8CrFcZWCZmuIFT9cg0+ZEiq//Vog1pg3Lf//N37ceGpOag1LwMe6cQ== +"@abp/lodash@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-9.0.3.tgz#daadfb98339f1d6982a12b655b840f105b324830" + integrity sha512-wt4ZJmRlhnK9W3o8lex7LyL/7rzgu4STLtZ2Ga8Ec5uZ9kN0CDTYjIw0zVqB8iGNKcW8MCxIGB7u9JMU9tphiQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" lodash "^4.17.21" -"@abp/luxon@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-9.0.0.tgz#a54dda13cbbd362481994d20d2e07c669c6ce109" - integrity sha512-aPejQnoVPfm60LnjwhfUkIsB+poNWusrBE0IHllxhvOVE+01nfa1Lvpp5AUWzV+XchPGOCSf+a29ds4EQWfNbg== +"@abp/luxon@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-9.0.3.tgz#62a5e395c92cadcf0985453eee065fcbe5354402" + integrity sha512-qTPw2vhgkgNldFJziHiL+c2vF7p+7RW1orev8K8XLSe3dguzlXBL2dicyDWCX9ay4zYfEpXbA0oKFgJvhBfK/w== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" luxon "^3.5.0" -"@abp/malihu-custom-scrollbar-plugin@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-9.0.0.tgz#b8a49202d531c711874a2bcdae5081ae0ed34c3f" - integrity sha512-iSZ9lKMnv5F2SplR+A4blhFEDq79lKbvBQuS7h/Di3KEHFOdJY4pA8NP8OR+PXOF3JfAezGyjqy7ydn/JNPRwA== +"@abp/malihu-custom-scrollbar-plugin@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-9.0.3.tgz#b3723f777b61c47ed64527b4295df33cca5cae26" + integrity sha512-aRearvDO1OaV7wfsWR2/cieWR/hG9cX78YGE08goRq0xLcGnBkmraQHSGGSZbMwId5zf5Kh3ePVXChO7llitkQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-9.0.0.tgz#9583e97472ddf608a9a5a6b8a37fee602b419900" - integrity sha512-wgyJSKmBmHLXZJN3dy3zK4i2JvWGgqnvdtrKURy/raEurxlymv8JF5JA971kTv6Kz2OA/OSYttpVJkDGUpPAhA== +"@abp/moment@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-9.0.3.tgz#696748659e036a1bf707684340703a861f340a92" + integrity sha512-7SRIjSHOyDTbuZ41vpisft+c5L+E3fk0G+V5+rpsACGTm6SpRqnVuAl5egbrDcm9rQeP0+VSV+/6UXdM7koOCQ== dependencies: moment "^2.30.1" -"@abp/owl.carousel@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/owl.carousel/-/owl.carousel-9.0.0.tgz#44f6b35199940aa98b9b97bfaeb7872779133ae5" - integrity sha512-zwlrvm4wxvM/4OlUsJPhEy4I+N+wX27dwCQ640eAZirHlmRrOydAADDP964LDX6ay3PqvW8VzH/e0iEFqUBLWg== +"@abp/owl.carousel@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/owl.carousel/-/owl.carousel-9.0.3.tgz#7201945d0c37f0df09bf5c7144aa4551da1ede42" + integrity sha512-BMTzvOGaQ00kQboCdrZN/DW2jAwyrTPxOBHaoEOl9sQexlzGULiQAe+jtY9kE5N834Fy4QjXTHTh0/srdZpEQw== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" owl.carousel "^2.3.4" -"@abp/prismjs@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-9.0.0.tgz#2ac4a5ddc0c8e0462af027a4dd97a177a3d6f7a5" - integrity sha512-+evmBq5qV+Ix/TNqO2ljJn0jygW2rEgKNtABcBGmrZC8xiI7+2hjKmSE0e43qb+RDib8aJjwH8ya8wF7p68ZFw== +"@abp/prismjs@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-9.0.3.tgz#9675dacd5df511120dffde76e925a8344ca9bace" + integrity sha512-5nMAKRADa6BQh3orquGHCUzqoZyDhmvoQ7RBCJsM3e0W+wBPeV9qmF+dJjOf9d+c9dr2mpU2SrH45fO/BNqgWg== dependencies: - "@abp/clipboard" "~9.0.0" - "@abp/core" "~9.0.0" + "@abp/clipboard" "~9.0.3" + "@abp/core" "~9.0.3" prismjs "^1.29.0" -"@abp/select2@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-9.0.0.tgz#71503eebb746f40a0e565dba1efa05d6ae937b77" - integrity sha512-l+Hz2Qmc7AcyaTJOyuDaV99RbBWYxYxbeyVEc4xKbRFbJxYBUxT/DFlyozEaoz+OU1fl4szO3UCthJ2yZvxCdg== +"@abp/select2@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-9.0.3.tgz#d75afa45decc4ce5a25ecb257f76a9f4c4c1b1fd" + integrity sha512-Cs/cQHdqV0U/c3RXbTTfIQoTaJqjBZMGon8cHX6rmvCDhWKOU/B2E+R2wNlcMjl0Tkhfy3Jl1mwlD0KZ/LePbA== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" select2 "^4.0.13" -"@abp/sweetalert2@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-9.0.0.tgz#71e511117c18b515e5c658e9cc0667770af65d91" - integrity sha512-wxDqoA6RUhkbcN27Kzslu/m2ai5T9AkJRtgs7pV55LvPv1ohuCPEUeTVVh8S/5Pgmr7aPFAPxaaCO32aPcImPw== +"@abp/sweetalert2@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-9.0.3.tgz#b7619bcf664b7aaf51f096df1b60f604db36c84c" + integrity sha512-W9fQu1jGhzqliyqKU/OFtAHiS3b8kar3LN9Pij2g/Q8zS2esjdgdt4LoaI5SytRi7zXUqn2tCi/cDEDXf/X3zQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" sweetalert2 "^11.14.1" -"@abp/timeago@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-9.0.0.tgz#8f262ba2a74e1c133d0c129f8a0bc55c29441e1a" - integrity sha512-eaNdIUEtDXnuYqS5O7EGRV+j6XOLAsxz7gd5BIKR9lDujd8pEjSJkXKO+/0KxLTkUSy3JudIO37K1h2wTpbYZA== +"@abp/timeago@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-9.0.3.tgz#8e675aafcc862643303909c27bbb982aa1c3c22c" + integrity sha512-B2ZHs4IfZyy+YHLlS/KLhxbqvxTkEEz1y+Sk8HtVSk0Ula5UD+U1FvwcxOiYGIghmHlkqFBtegifZosPOMB9VQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" timeago "^1.6.7" -"@abp/toastr@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-9.0.0.tgz#74d74ecb3e7f2d63b2ff7735c89a40deb3dc3a13" - integrity sha512-n9uJvw8l2HoxeowPO4diEMxwchGh6puefhyRljFAAstzK77Mhn0u2J7kPynNv94rLSYTYoCxUeJ4TTN+/TVDGQ== +"@abp/toastr@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-9.0.3.tgz#1863fea4d709b524f8fea0233dfd0ff8b1bc5bb2" + integrity sha512-3eV17OBB8XLJC9dZWW6xzi4fpu5NZqNrRiKiYJf79IUvmuS0cBVKpNJVKRaDTp4DeDBHxCQOWmMnrNI/mh3bDQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" toastr "^2.1.4" -"@abp/tui-editor@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/tui-editor/-/tui-editor-9.0.0.tgz#d9f7721c3d8e361d901a0e0e11e786b2e12b234a" - integrity sha512-TVadK2Yi3wlwyvEVshh1PxkNRppYmqaPB6QE1cOhrWZAFbP1fh1oFvfqQiHN3o+9RGHFcEnhEK3D+49aHuDh3Q== +"@abp/tui-editor@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/tui-editor/-/tui-editor-9.0.3.tgz#f7d17844fa62562db99cc06d24ddeb6a80449a07" + integrity sha512-MS5lxKQBeGXT9PG/CliiOCtXYI6qpzI28icy3usIvzk9IaZRNedb+4HGhcmpoA61j5NaVow81S6pQo2KBaIBQw== dependencies: - "@abp/jquery" "~9.0.0" - "@abp/prismjs" "~9.0.0" + "@abp/jquery" "~9.0.3" + "@abp/prismjs" "~9.0.3" -"@abp/utils@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-9.0.0.tgz#2264fd83105b061860edd98dbe0efdfe55803ed5" - integrity sha512-/SB4VYk3Av+ltXv6ovkFHG4iWiECLLitaYWcEHxVAO80ufNjrNnWpHFpXHP8u/RpXIp/CoNiHLr6j5TkZUTZjw== +"@abp/utils@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-9.0.3.tgz#02dfd981d210be8229d65f802f0721b42c9e17d7" + integrity sha512-45sQQ6IV3cB9KAIVtRn3IH1wVr9DfM0PMFiwVt3F+wKwNBAPsKNcMPt4/EhZCNY3IMaEseJ7K/lxt33O3sURuA== dependencies: just-compare "^2.3.0" diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml index 926c8073ca..e0264603fb 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml @@ -2,12 +2,16 @@ @using Volo.Abp.AspNetCore.Mvc.UI.Packages.TuiEditor @using Volo.Blogging.Posts @using Microsoft.AspNetCore.Mvc.Localization +@using Microsoft.Extensions.Options +@using Volo.Blogging @using Volo.Blogging.Localization @using Volo.Blogging.Pages.Blogs.Posts @inject IHtmlLocalizer L @model Volo.Blogging.Pages.Blogs.Posts.EditModel +@inject IOptions BloggingUrlOptions @{ ViewBag.PageTitle = "Edit Blog Post"; + var blogShortNameRouteParam = BloggingUrlOptions.Value.SingleBlogMode.Enabled ? null : Model.BlogShortName; } @section styles { @@ -86,7 +90,7 @@ diff --git a/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package.json b/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package.json index e625cd894f..5393d31123 100644 --- a/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package.json +++ b/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package.json @@ -3,6 +3,6 @@ "name": "client-simulation-web", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~9.0.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~9.0.3" } } diff --git a/modules/client-simulation/demo/Volo.ClientSimulation.Demo/yarn.lock b/modules/client-simulation/demo/Volo.ClientSimulation.Demo/yarn.lock index 9e01130758..3de1cb98c3 100644 --- a/modules/client-simulation/demo/Volo.ClientSimulation.Demo/yarn.lock +++ b/modules/client-simulation/demo/Volo.ClientSimulation.Demo/yarn.lock @@ -2,194 +2,194 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-9.0.0.tgz#e4b05d63446c24b612275e3949881e176d32527f" - integrity sha512-GHEt26xESe/TCg1q6ZjeUbxniqdxaiB1OPTZ9md7ONql6bEp8t9iUPeSlkyAZtQ7qBzwgkiLpkRFT+J21DftaQ== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~9.0.0" - -"@abp/aspnetcore.mvc.ui.theme.shared@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-9.0.0.tgz#a77b4b70fa6217146afd6a57211f57e2360564f2" - integrity sha512-QmZrSp+rfqfnC9W4QZ+nNdAq0rOALzRn1rsDpGDHhIHwyXB03QPdjzIWaEvldUYHzqg84cEP0LcqW4y57Xr1iw== - dependencies: - "@abp/aspnetcore.mvc.ui" "~9.0.0" - "@abp/bootstrap" "~9.0.0" - "@abp/bootstrap-datepicker" "~9.0.0" - "@abp/bootstrap-daterangepicker" "~9.0.0" - "@abp/datatables.net-bs5" "~9.0.0" - "@abp/font-awesome" "~9.0.0" - "@abp/jquery-form" "~9.0.0" - "@abp/jquery-validation-unobtrusive" "~9.0.0" - "@abp/lodash" "~9.0.0" - "@abp/luxon" "~9.0.0" - "@abp/malihu-custom-scrollbar-plugin" "~9.0.0" - "@abp/moment" "~9.0.0" - "@abp/select2" "~9.0.0" - "@abp/sweetalert2" "~9.0.0" - "@abp/timeago" "~9.0.0" - "@abp/toastr" "~9.0.0" - -"@abp/aspnetcore.mvc.ui@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-9.0.0.tgz#93004dd056c130386c7b40c458f3a793d8c9e051" - integrity sha512-pvO5bh9baFcSbfW0s7tpMpbe6DKSC16jeyeluZLlvYxC0n2gkrv7H/hB5aMuBKIlh89aGY9DQIcMFC8iczL3Bg== +"@abp/aspnetcore.mvc.ui.theme.basic@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-9.0.3.tgz#dcb95764ee11fc11ca593b722575275837cc58a9" + integrity sha512-NwTunUVwfaoZsNp1Js6B+jGPBw96taBhQ5hloKhtZFZxG2VMAN9Gy9fGxzUByYlpRB8CASR3kL+GRhrK2Ufc3A== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~9.0.3" + +"@abp/aspnetcore.mvc.ui.theme.shared@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-9.0.3.tgz#18f0d27cbba7ff22576c1c6e54891190c7d900bb" + integrity sha512-WHMvMiZhqvPLQtqo3aI+wkhNbZ3gCkWqwhIiXHvRks+tO92ww52evPlZUNDSWSSkhcB9vJddx5Eqv4phJgcMAA== + dependencies: + "@abp/aspnetcore.mvc.ui" "~9.0.3" + "@abp/bootstrap" "~9.0.3" + "@abp/bootstrap-datepicker" "~9.0.3" + "@abp/bootstrap-daterangepicker" "~9.0.3" + "@abp/datatables.net-bs5" "~9.0.3" + "@abp/font-awesome" "~9.0.3" + "@abp/jquery-form" "~9.0.3" + "@abp/jquery-validation-unobtrusive" "~9.0.3" + "@abp/lodash" "~9.0.3" + "@abp/luxon" "~9.0.3" + "@abp/malihu-custom-scrollbar-plugin" "~9.0.3" + "@abp/moment" "~9.0.3" + "@abp/select2" "~9.0.3" + "@abp/sweetalert2" "~9.0.3" + "@abp/timeago" "~9.0.3" + "@abp/toastr" "~9.0.3" + +"@abp/aspnetcore.mvc.ui@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-9.0.3.tgz#a4e2e92cddca77d81d8445bcc1d2cc129ec4b486" + integrity sha512-wkn7R8sx5lhbaZeTAy5aJxLbR1f3TD8lNaNtM1Lsc7wWq9dI19wyJZiAWqCZij2HOr2IK5izSROVfTqoXyUwXQ== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-9.0.0.tgz#d6609e50568aac6a013abfcaa07817998e1eaa99" - integrity sha512-wxAeVsOVOJpidDn0g3FlJvpOsn5SVY5IpEG+FtUxSAKuWYJQWF6g5CaghrfS+FNDK9IHhxrgSMPMVBK9YlDSzw== +"@abp/bootstrap-datepicker@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-9.0.3.tgz#aa40f5a31eb5c06224c165129a882658f4a0a9a5" + integrity sha512-Jg4oeBPtX7G41JdYjHVhB/6KQcMJO4WWXbUvkNetSUGTZGqacmgWRAIA5abmRWaHMJP1A1soZ9Ny1GNy0Qu65Q== dependencies: bootstrap-datepicker "^1.10.0" -"@abp/bootstrap-daterangepicker@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-9.0.0.tgz#fc2871e7d886accd8e4748cb763b695e10701c00" - integrity sha512-qS6SQR6X7wMCVLcuXatJ2+m1GNlpN71p7VDritxfwAMHa7WBRAAi8apX2WItwBAuLQ2s1uSA0Wp1KstHG3VPLA== +"@abp/bootstrap-daterangepicker@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-9.0.3.tgz#a1c17721a1ff6613a3ee4ff971174b32a93d98de" + integrity sha512-wrvjb0bJudohxiYtecCGEa4VsvL/Kn/Z3q9w1jvIoallrTXQaACPtZDU+9yxK5mRf6zonwYI8JtNn4xKa+D1VA== dependencies: bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-9.0.0.tgz#5ab292e95ba7eef129cbaa1dc5f5d5c0af8530d7" - integrity sha512-jEr6su8BfoNxYvze/UlddE3DlezyPvQeiNhff8SDPqtRQ6XvjZ48O/JwtY01hK2njEDLOZINY5zJRGmO2OiOUw== +"@abp/bootstrap@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-9.0.3.tgz#dc0325c23282086c9d2be79588b228e8b2c66eb2" + integrity sha512-ypl0R3jky6qKmnEZjnSniwgN8unNmWD9d4bOtV5RgDF2MlBQT+CvfUcYnGom0JOsL+5RjSNvzFH/Q5aH/yuy7Q== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" bootstrap "^5.3.3" -"@abp/core@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-9.0.0.tgz#dc5ea9139957e307f73ac1f40b179f991e3c2168" - integrity sha512-vzQyTpqVDx63pOI9e2QLUMWZCrSojHsRnzLlvl+/cKFi7jE1q/uSP66FPR2bwkD+DNLI/tn6is1Jx3skKA1FvA== +"@abp/core@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-9.0.3.tgz#8c508dc60628fbd9e8a9ba35d153316cacbf1fb4" + integrity sha512-4bHiyP2qPrEeXb/Ynl17b6LtfygWdkwsjHaadUbym7Lg5qYUQZ3KjW4U2DihYWCO46gsr/ckTEbV9edcRd+NxA== dependencies: - "@abp/utils" "~9.0.0" + "@abp/utils" "~9.0.3" -"@abp/datatables.net-bs5@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-9.0.0.tgz#4daee90bdb6446e854ce65f2a3afabb2a13e49ae" - integrity sha512-ln10tcRwxcB0nuSgqJ/66strHsqMHDC03c0ktxEplAN34yP7KInjFf/U9Svo74K0nKlkJdMCgmqmT4CnBJZlIg== +"@abp/datatables.net-bs5@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-9.0.3.tgz#c89813a69a1b568a171cb2a4b1bcc4b7a4ef1bef" + integrity sha512-SD6N4apScOR22S7EntengOTQ1HSm2WnvzUuP/6HG50isXyMheRalZxJ6zAsBwiWyYyJtA+UHkiQzwVhdV40aBQ== dependencies: - "@abp/datatables.net" "~9.0.0" + "@abp/datatables.net" "~9.0.3" datatables.net-bs5 "^2.1.8" -"@abp/datatables.net@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-9.0.0.tgz#82b28518293c9091ec72d5defae2f5d2903d8bbc" - integrity sha512-QhuX3bGZOckM/fhIIGj/StMJIX2BBtNbLzjTR7WnKMMxDkv0+oDvjw6OqqpYKSvj/q3vY6sRWuCNdEzNi0Fk6A== +"@abp/datatables.net@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-9.0.3.tgz#9cc09127ff6269476b6b0de16048e1e6480227c4" + integrity sha512-zLzHxdXnmL8pWKVzf3m4SIUMiPHHe3I9Cw8vezk5fK1v18Fk364o3Q8o8C1l1YDGEa7Sx1wsUoLDN+p4FzagXg== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" datatables.net "^2.1.8" -"@abp/font-awesome@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-9.0.0.tgz#51cce18434a51d050ebb0d822a8c193b7e32f73d" - integrity sha512-bEHRVzOk0V2kFMAvB/YXFYaDKWRFO0+ScO7jrEFtFgdHXBcqy+dk1aM/N69WalyNmjRRpnh03GhH1LRpwL4/xA== +"@abp/font-awesome@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-9.0.3.tgz#db6ece04fcdb4880cac8174deffb4fa04d43d2de" + integrity sha512-toI2zGayMeI4EUUMmittRWKBY/mLjiiX1zA2z70q/Hpm9zoCly2KwbN7zwtWbayhESO7edci9T7CXF5kNdiNow== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" "@fortawesome/fontawesome-free" "^6.6.0" -"@abp/jquery-form@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-9.0.0.tgz#6518eb117ee35c6bc57543fd1fccb0a8cf8e22f4" - integrity sha512-6qpfPGHrjADmm7Ne3zcXBGPuLngG8ldAn5fk9k2taftpyX0iR67/nZxiQ0ujaQGgaai5Wb3imNXMG96XLnubkQ== +"@abp/jquery-form@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-9.0.3.tgz#907033a496af127bfbe4e220f43cda65331e7b98" + integrity sha512-kD/9S25kJSAj5f7bfHVMfzfF5/0OrLDj8E55M27zbusdcaFS/nYElzrU3DeAEusASdnssRAwyJCWyp2vvRIMzA== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-9.0.0.tgz#8ee87dbdec5e92ab04ae9642a49af1598af31c1a" - integrity sha512-KlRQ1htyFmAIMS9xbHvHIcdhAgtPFe/u7c87MiWlk/CdhDrI3J+n5biLvQUzxCC+YWlwx27zBtPKx2vLeMRH6Q== +"@abp/jquery-validation-unobtrusive@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-9.0.3.tgz#b9449d222d4855f941dbe7445f73fff1541975a4" + integrity sha512-U/zUDYyJnUYDmmloOx/rViVyg7b4Gxd4zgcEi1E1YP0zryQQh5CkoSEvzv7GyurB1/rVZuRszmo/nRgn3pHOKg== dependencies: - "@abp/jquery-validation" "~9.0.0" + "@abp/jquery-validation" "~9.0.3" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-9.0.0.tgz#681d8037c57b3875bd51c927e350ffce65d7c5d2" - integrity sha512-XgCUBFWbhTDcGepD/xkwA/1Hl4TyYitRttauDJk3tuyXhNtykvdugCCOPY4j2s6VoT1Sb/TFFvOcgEvlBwkKiw== +"@abp/jquery-validation@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-9.0.3.tgz#02eb010267121fd9310cdcf5ca7c7b54091fd5de" + integrity sha512-tqjM5S0/jDS88CIt8oCQbHYJTytA7Y+KdTsnRkrA+BWy2A5bjrd6YUZXa/2UUwXHa3tQgv/uXl9HpDEaqRtfYQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" jquery-validation "^1.21.0" -"@abp/jquery@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-9.0.0.tgz#6c67a9f927be598ab6201f8888e8a3cb8bba0686" - integrity sha512-cUXA7v6F1HqAc/w8fQGxHIfA/6BqO+4uHXDnSsJZtrI26DTLOgw5kBNnAz2W4xggGPCY7SKFHNRZuOW/QfVRPg== +"@abp/jquery@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-9.0.3.tgz#ba8b4f4c0825ccb55c793024d3bbeb9ee817b19c" + integrity sha512-lZoyiyz83VOQKbN+9gD8JC+WI+OACc3y2hU+v30v220DLKhwoiVQM6d5yfA2QEugTPTjm/s40Ocmh/nVjm5mtg== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" jquery "~3.7.1" -"@abp/lodash@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-9.0.0.tgz#a4d42b2dfeb37859ff9f0079c7a8a6cabc982efe" - integrity sha512-xoAsOkkgnQdPH2OyFTgF6OlCLVbyGF4u8CrFcZWCZmuIFT9cg0+ZEiq//Vog1pg3Lf//N37ceGpOag1LwMe6cQ== +"@abp/lodash@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-9.0.3.tgz#daadfb98339f1d6982a12b655b840f105b324830" + integrity sha512-wt4ZJmRlhnK9W3o8lex7LyL/7rzgu4STLtZ2Ga8Ec5uZ9kN0CDTYjIw0zVqB8iGNKcW8MCxIGB7u9JMU9tphiQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" lodash "^4.17.21" -"@abp/luxon@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-9.0.0.tgz#a54dda13cbbd362481994d20d2e07c669c6ce109" - integrity sha512-aPejQnoVPfm60LnjwhfUkIsB+poNWusrBE0IHllxhvOVE+01nfa1Lvpp5AUWzV+XchPGOCSf+a29ds4EQWfNbg== +"@abp/luxon@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-9.0.3.tgz#62a5e395c92cadcf0985453eee065fcbe5354402" + integrity sha512-qTPw2vhgkgNldFJziHiL+c2vF7p+7RW1orev8K8XLSe3dguzlXBL2dicyDWCX9ay4zYfEpXbA0oKFgJvhBfK/w== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" luxon "^3.5.0" -"@abp/malihu-custom-scrollbar-plugin@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-9.0.0.tgz#b8a49202d531c711874a2bcdae5081ae0ed34c3f" - integrity sha512-iSZ9lKMnv5F2SplR+A4blhFEDq79lKbvBQuS7h/Di3KEHFOdJY4pA8NP8OR+PXOF3JfAezGyjqy7ydn/JNPRwA== +"@abp/malihu-custom-scrollbar-plugin@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-9.0.3.tgz#b3723f777b61c47ed64527b4295df33cca5cae26" + integrity sha512-aRearvDO1OaV7wfsWR2/cieWR/hG9cX78YGE08goRq0xLcGnBkmraQHSGGSZbMwId5zf5Kh3ePVXChO7llitkQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-9.0.0.tgz#9583e97472ddf608a9a5a6b8a37fee602b419900" - integrity sha512-wgyJSKmBmHLXZJN3dy3zK4i2JvWGgqnvdtrKURy/raEurxlymv8JF5JA971kTv6Kz2OA/OSYttpVJkDGUpPAhA== +"@abp/moment@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-9.0.3.tgz#696748659e036a1bf707684340703a861f340a92" + integrity sha512-7SRIjSHOyDTbuZ41vpisft+c5L+E3fk0G+V5+rpsACGTm6SpRqnVuAl5egbrDcm9rQeP0+VSV+/6UXdM7koOCQ== dependencies: moment "^2.30.1" -"@abp/select2@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-9.0.0.tgz#71503eebb746f40a0e565dba1efa05d6ae937b77" - integrity sha512-l+Hz2Qmc7AcyaTJOyuDaV99RbBWYxYxbeyVEc4xKbRFbJxYBUxT/DFlyozEaoz+OU1fl4szO3UCthJ2yZvxCdg== +"@abp/select2@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-9.0.3.tgz#d75afa45decc4ce5a25ecb257f76a9f4c4c1b1fd" + integrity sha512-Cs/cQHdqV0U/c3RXbTTfIQoTaJqjBZMGon8cHX6rmvCDhWKOU/B2E+R2wNlcMjl0Tkhfy3Jl1mwlD0KZ/LePbA== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" select2 "^4.0.13" -"@abp/sweetalert2@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-9.0.0.tgz#71e511117c18b515e5c658e9cc0667770af65d91" - integrity sha512-wxDqoA6RUhkbcN27Kzslu/m2ai5T9AkJRtgs7pV55LvPv1ohuCPEUeTVVh8S/5Pgmr7aPFAPxaaCO32aPcImPw== +"@abp/sweetalert2@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-9.0.3.tgz#b7619bcf664b7aaf51f096df1b60f604db36c84c" + integrity sha512-W9fQu1jGhzqliyqKU/OFtAHiS3b8kar3LN9Pij2g/Q8zS2esjdgdt4LoaI5SytRi7zXUqn2tCi/cDEDXf/X3zQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" sweetalert2 "^11.14.1" -"@abp/timeago@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-9.0.0.tgz#8f262ba2a74e1c133d0c129f8a0bc55c29441e1a" - integrity sha512-eaNdIUEtDXnuYqS5O7EGRV+j6XOLAsxz7gd5BIKR9lDujd8pEjSJkXKO+/0KxLTkUSy3JudIO37K1h2wTpbYZA== +"@abp/timeago@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-9.0.3.tgz#8e675aafcc862643303909c27bbb982aa1c3c22c" + integrity sha512-B2ZHs4IfZyy+YHLlS/KLhxbqvxTkEEz1y+Sk8HtVSk0Ula5UD+U1FvwcxOiYGIghmHlkqFBtegifZosPOMB9VQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" timeago "^1.6.7" -"@abp/toastr@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-9.0.0.tgz#74d74ecb3e7f2d63b2ff7735c89a40deb3dc3a13" - integrity sha512-n9uJvw8l2HoxeowPO4diEMxwchGh6puefhyRljFAAstzK77Mhn0u2J7kPynNv94rLSYTYoCxUeJ4TTN+/TVDGQ== +"@abp/toastr@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-9.0.3.tgz#1863fea4d709b524f8fea0233dfd0ff8b1bc5bb2" + integrity sha512-3eV17OBB8XLJC9dZWW6xzi4fpu5NZqNrRiKiYJf79IUvmuS0cBVKpNJVKRaDTp4DeDBHxCQOWmMnrNI/mh3bDQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" toastr "^2.1.4" -"@abp/utils@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-9.0.0.tgz#2264fd83105b061860edd98dbe0efdfe55803ed5" - integrity sha512-/SB4VYk3Av+ltXv6ovkFHG4iWiECLLitaYWcEHxVAO80ufNjrNnWpHFpXHP8u/RpXIp/CoNiHLr6j5TkZUTZjw== +"@abp/utils@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-9.0.3.tgz#02dfd981d210be8229d65f802f0721b42c9e17d7" + integrity sha512-45sQQ6IV3cB9KAIVtRn3IH1wVr9DfM0PMFiwVt3F+wKwNBAPsKNcMPt4/EhZCNY3IMaEseJ7K/lxt33O3sURuA== dependencies: just-compare "^2.3.0" diff --git a/modules/cms-kit/angular/package.json b/modules/cms-kit/angular/package.json index 898a2d3e9c..fa29d30939 100644 --- a/modules/cms-kit/angular/package.json +++ b/modules/cms-kit/angular/package.json @@ -15,11 +15,11 @@ }, "private": true, "dependencies": { - "@abp/ng.account": "~9.0.0", - "@abp/ng.identity": "~9.0.0", - "@abp/ng.setting-management": "~9.0.0", - "@abp/ng.tenant-management": "~9.0.0", - "@abp/ng.theme.basic": "~9.0.0", + "@abp/ng.account": "~9.0.3", + "@abp/ng.identity": "~9.0.3", + "@abp/ng.setting-management": "~9.0.3", + "@abp/ng.tenant-management": "~9.0.3", + "@abp/ng.theme.basic": "~9.0.3", "@angular/animations": "~10.0.0", "@angular/common": "~10.0.0", "@angular/compiler": "~10.0.0", diff --git a/modules/cms-kit/angular/projects/cms-kit/package.json b/modules/cms-kit/angular/projects/cms-kit/package.json index a7ab8c38c1..4a800fe89b 100644 --- a/modules/cms-kit/angular/projects/cms-kit/package.json +++ b/modules/cms-kit/angular/projects/cms-kit/package.json @@ -4,8 +4,8 @@ "peerDependencies": { "@angular/common": "^9.1.11", "@angular/core": "^9.1.11", - "@abp/ng.core": ">=9.0.0", - "@abp/ng.theme.shared": ">=9.0.0" + "@abp/ng.core": ">=9.0.3", + "@abp/ng.theme.shared": ">=9.0.3" }, "dependencies": { "tslib": "^2.0.0" diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/package.json b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/package.json index 0150cbe740..552cf134c8 100644 --- a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/package.json +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/package.json @@ -3,6 +3,6 @@ "name": "my-app-identityserver", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~9.0.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~9.0.3" } } diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/yarn.lock b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/yarn.lock index 9e01130758..3de1cb98c3 100644 --- a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/yarn.lock +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/yarn.lock @@ -2,194 +2,194 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-9.0.0.tgz#e4b05d63446c24b612275e3949881e176d32527f" - integrity sha512-GHEt26xESe/TCg1q6ZjeUbxniqdxaiB1OPTZ9md7ONql6bEp8t9iUPeSlkyAZtQ7qBzwgkiLpkRFT+J21DftaQ== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~9.0.0" - -"@abp/aspnetcore.mvc.ui.theme.shared@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-9.0.0.tgz#a77b4b70fa6217146afd6a57211f57e2360564f2" - integrity sha512-QmZrSp+rfqfnC9W4QZ+nNdAq0rOALzRn1rsDpGDHhIHwyXB03QPdjzIWaEvldUYHzqg84cEP0LcqW4y57Xr1iw== - dependencies: - "@abp/aspnetcore.mvc.ui" "~9.0.0" - "@abp/bootstrap" "~9.0.0" - "@abp/bootstrap-datepicker" "~9.0.0" - "@abp/bootstrap-daterangepicker" "~9.0.0" - "@abp/datatables.net-bs5" "~9.0.0" - "@abp/font-awesome" "~9.0.0" - "@abp/jquery-form" "~9.0.0" - "@abp/jquery-validation-unobtrusive" "~9.0.0" - "@abp/lodash" "~9.0.0" - "@abp/luxon" "~9.0.0" - "@abp/malihu-custom-scrollbar-plugin" "~9.0.0" - "@abp/moment" "~9.0.0" - "@abp/select2" "~9.0.0" - "@abp/sweetalert2" "~9.0.0" - "@abp/timeago" "~9.0.0" - "@abp/toastr" "~9.0.0" - -"@abp/aspnetcore.mvc.ui@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-9.0.0.tgz#93004dd056c130386c7b40c458f3a793d8c9e051" - integrity sha512-pvO5bh9baFcSbfW0s7tpMpbe6DKSC16jeyeluZLlvYxC0n2gkrv7H/hB5aMuBKIlh89aGY9DQIcMFC8iczL3Bg== +"@abp/aspnetcore.mvc.ui.theme.basic@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-9.0.3.tgz#dcb95764ee11fc11ca593b722575275837cc58a9" + integrity sha512-NwTunUVwfaoZsNp1Js6B+jGPBw96taBhQ5hloKhtZFZxG2VMAN9Gy9fGxzUByYlpRB8CASR3kL+GRhrK2Ufc3A== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~9.0.3" + +"@abp/aspnetcore.mvc.ui.theme.shared@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-9.0.3.tgz#18f0d27cbba7ff22576c1c6e54891190c7d900bb" + integrity sha512-WHMvMiZhqvPLQtqo3aI+wkhNbZ3gCkWqwhIiXHvRks+tO92ww52evPlZUNDSWSSkhcB9vJddx5Eqv4phJgcMAA== + dependencies: + "@abp/aspnetcore.mvc.ui" "~9.0.3" + "@abp/bootstrap" "~9.0.3" + "@abp/bootstrap-datepicker" "~9.0.3" + "@abp/bootstrap-daterangepicker" "~9.0.3" + "@abp/datatables.net-bs5" "~9.0.3" + "@abp/font-awesome" "~9.0.3" + "@abp/jquery-form" "~9.0.3" + "@abp/jquery-validation-unobtrusive" "~9.0.3" + "@abp/lodash" "~9.0.3" + "@abp/luxon" "~9.0.3" + "@abp/malihu-custom-scrollbar-plugin" "~9.0.3" + "@abp/moment" "~9.0.3" + "@abp/select2" "~9.0.3" + "@abp/sweetalert2" "~9.0.3" + "@abp/timeago" "~9.0.3" + "@abp/toastr" "~9.0.3" + +"@abp/aspnetcore.mvc.ui@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-9.0.3.tgz#a4e2e92cddca77d81d8445bcc1d2cc129ec4b486" + integrity sha512-wkn7R8sx5lhbaZeTAy5aJxLbR1f3TD8lNaNtM1Lsc7wWq9dI19wyJZiAWqCZij2HOr2IK5izSROVfTqoXyUwXQ== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-9.0.0.tgz#d6609e50568aac6a013abfcaa07817998e1eaa99" - integrity sha512-wxAeVsOVOJpidDn0g3FlJvpOsn5SVY5IpEG+FtUxSAKuWYJQWF6g5CaghrfS+FNDK9IHhxrgSMPMVBK9YlDSzw== +"@abp/bootstrap-datepicker@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-9.0.3.tgz#aa40f5a31eb5c06224c165129a882658f4a0a9a5" + integrity sha512-Jg4oeBPtX7G41JdYjHVhB/6KQcMJO4WWXbUvkNetSUGTZGqacmgWRAIA5abmRWaHMJP1A1soZ9Ny1GNy0Qu65Q== dependencies: bootstrap-datepicker "^1.10.0" -"@abp/bootstrap-daterangepicker@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-9.0.0.tgz#fc2871e7d886accd8e4748cb763b695e10701c00" - integrity sha512-qS6SQR6X7wMCVLcuXatJ2+m1GNlpN71p7VDritxfwAMHa7WBRAAi8apX2WItwBAuLQ2s1uSA0Wp1KstHG3VPLA== +"@abp/bootstrap-daterangepicker@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-9.0.3.tgz#a1c17721a1ff6613a3ee4ff971174b32a93d98de" + integrity sha512-wrvjb0bJudohxiYtecCGEa4VsvL/Kn/Z3q9w1jvIoallrTXQaACPtZDU+9yxK5mRf6zonwYI8JtNn4xKa+D1VA== dependencies: bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-9.0.0.tgz#5ab292e95ba7eef129cbaa1dc5f5d5c0af8530d7" - integrity sha512-jEr6su8BfoNxYvze/UlddE3DlezyPvQeiNhff8SDPqtRQ6XvjZ48O/JwtY01hK2njEDLOZINY5zJRGmO2OiOUw== +"@abp/bootstrap@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-9.0.3.tgz#dc0325c23282086c9d2be79588b228e8b2c66eb2" + integrity sha512-ypl0R3jky6qKmnEZjnSniwgN8unNmWD9d4bOtV5RgDF2MlBQT+CvfUcYnGom0JOsL+5RjSNvzFH/Q5aH/yuy7Q== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" bootstrap "^5.3.3" -"@abp/core@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-9.0.0.tgz#dc5ea9139957e307f73ac1f40b179f991e3c2168" - integrity sha512-vzQyTpqVDx63pOI9e2QLUMWZCrSojHsRnzLlvl+/cKFi7jE1q/uSP66FPR2bwkD+DNLI/tn6is1Jx3skKA1FvA== +"@abp/core@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-9.0.3.tgz#8c508dc60628fbd9e8a9ba35d153316cacbf1fb4" + integrity sha512-4bHiyP2qPrEeXb/Ynl17b6LtfygWdkwsjHaadUbym7Lg5qYUQZ3KjW4U2DihYWCO46gsr/ckTEbV9edcRd+NxA== dependencies: - "@abp/utils" "~9.0.0" + "@abp/utils" "~9.0.3" -"@abp/datatables.net-bs5@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-9.0.0.tgz#4daee90bdb6446e854ce65f2a3afabb2a13e49ae" - integrity sha512-ln10tcRwxcB0nuSgqJ/66strHsqMHDC03c0ktxEplAN34yP7KInjFf/U9Svo74K0nKlkJdMCgmqmT4CnBJZlIg== +"@abp/datatables.net-bs5@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-9.0.3.tgz#c89813a69a1b568a171cb2a4b1bcc4b7a4ef1bef" + integrity sha512-SD6N4apScOR22S7EntengOTQ1HSm2WnvzUuP/6HG50isXyMheRalZxJ6zAsBwiWyYyJtA+UHkiQzwVhdV40aBQ== dependencies: - "@abp/datatables.net" "~9.0.0" + "@abp/datatables.net" "~9.0.3" datatables.net-bs5 "^2.1.8" -"@abp/datatables.net@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-9.0.0.tgz#82b28518293c9091ec72d5defae2f5d2903d8bbc" - integrity sha512-QhuX3bGZOckM/fhIIGj/StMJIX2BBtNbLzjTR7WnKMMxDkv0+oDvjw6OqqpYKSvj/q3vY6sRWuCNdEzNi0Fk6A== +"@abp/datatables.net@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-9.0.3.tgz#9cc09127ff6269476b6b0de16048e1e6480227c4" + integrity sha512-zLzHxdXnmL8pWKVzf3m4SIUMiPHHe3I9Cw8vezk5fK1v18Fk364o3Q8o8C1l1YDGEa7Sx1wsUoLDN+p4FzagXg== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" datatables.net "^2.1.8" -"@abp/font-awesome@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-9.0.0.tgz#51cce18434a51d050ebb0d822a8c193b7e32f73d" - integrity sha512-bEHRVzOk0V2kFMAvB/YXFYaDKWRFO0+ScO7jrEFtFgdHXBcqy+dk1aM/N69WalyNmjRRpnh03GhH1LRpwL4/xA== +"@abp/font-awesome@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-9.0.3.tgz#db6ece04fcdb4880cac8174deffb4fa04d43d2de" + integrity sha512-toI2zGayMeI4EUUMmittRWKBY/mLjiiX1zA2z70q/Hpm9zoCly2KwbN7zwtWbayhESO7edci9T7CXF5kNdiNow== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" "@fortawesome/fontawesome-free" "^6.6.0" -"@abp/jquery-form@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-9.0.0.tgz#6518eb117ee35c6bc57543fd1fccb0a8cf8e22f4" - integrity sha512-6qpfPGHrjADmm7Ne3zcXBGPuLngG8ldAn5fk9k2taftpyX0iR67/nZxiQ0ujaQGgaai5Wb3imNXMG96XLnubkQ== +"@abp/jquery-form@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-9.0.3.tgz#907033a496af127bfbe4e220f43cda65331e7b98" + integrity sha512-kD/9S25kJSAj5f7bfHVMfzfF5/0OrLDj8E55M27zbusdcaFS/nYElzrU3DeAEusASdnssRAwyJCWyp2vvRIMzA== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-9.0.0.tgz#8ee87dbdec5e92ab04ae9642a49af1598af31c1a" - integrity sha512-KlRQ1htyFmAIMS9xbHvHIcdhAgtPFe/u7c87MiWlk/CdhDrI3J+n5biLvQUzxCC+YWlwx27zBtPKx2vLeMRH6Q== +"@abp/jquery-validation-unobtrusive@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-9.0.3.tgz#b9449d222d4855f941dbe7445f73fff1541975a4" + integrity sha512-U/zUDYyJnUYDmmloOx/rViVyg7b4Gxd4zgcEi1E1YP0zryQQh5CkoSEvzv7GyurB1/rVZuRszmo/nRgn3pHOKg== dependencies: - "@abp/jquery-validation" "~9.0.0" + "@abp/jquery-validation" "~9.0.3" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-9.0.0.tgz#681d8037c57b3875bd51c927e350ffce65d7c5d2" - integrity sha512-XgCUBFWbhTDcGepD/xkwA/1Hl4TyYitRttauDJk3tuyXhNtykvdugCCOPY4j2s6VoT1Sb/TFFvOcgEvlBwkKiw== +"@abp/jquery-validation@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-9.0.3.tgz#02eb010267121fd9310cdcf5ca7c7b54091fd5de" + integrity sha512-tqjM5S0/jDS88CIt8oCQbHYJTytA7Y+KdTsnRkrA+BWy2A5bjrd6YUZXa/2UUwXHa3tQgv/uXl9HpDEaqRtfYQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" jquery-validation "^1.21.0" -"@abp/jquery@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-9.0.0.tgz#6c67a9f927be598ab6201f8888e8a3cb8bba0686" - integrity sha512-cUXA7v6F1HqAc/w8fQGxHIfA/6BqO+4uHXDnSsJZtrI26DTLOgw5kBNnAz2W4xggGPCY7SKFHNRZuOW/QfVRPg== +"@abp/jquery@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-9.0.3.tgz#ba8b4f4c0825ccb55c793024d3bbeb9ee817b19c" + integrity sha512-lZoyiyz83VOQKbN+9gD8JC+WI+OACc3y2hU+v30v220DLKhwoiVQM6d5yfA2QEugTPTjm/s40Ocmh/nVjm5mtg== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" jquery "~3.7.1" -"@abp/lodash@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-9.0.0.tgz#a4d42b2dfeb37859ff9f0079c7a8a6cabc982efe" - integrity sha512-xoAsOkkgnQdPH2OyFTgF6OlCLVbyGF4u8CrFcZWCZmuIFT9cg0+ZEiq//Vog1pg3Lf//N37ceGpOag1LwMe6cQ== +"@abp/lodash@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-9.0.3.tgz#daadfb98339f1d6982a12b655b840f105b324830" + integrity sha512-wt4ZJmRlhnK9W3o8lex7LyL/7rzgu4STLtZ2Ga8Ec5uZ9kN0CDTYjIw0zVqB8iGNKcW8MCxIGB7u9JMU9tphiQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" lodash "^4.17.21" -"@abp/luxon@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-9.0.0.tgz#a54dda13cbbd362481994d20d2e07c669c6ce109" - integrity sha512-aPejQnoVPfm60LnjwhfUkIsB+poNWusrBE0IHllxhvOVE+01nfa1Lvpp5AUWzV+XchPGOCSf+a29ds4EQWfNbg== +"@abp/luxon@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-9.0.3.tgz#62a5e395c92cadcf0985453eee065fcbe5354402" + integrity sha512-qTPw2vhgkgNldFJziHiL+c2vF7p+7RW1orev8K8XLSe3dguzlXBL2dicyDWCX9ay4zYfEpXbA0oKFgJvhBfK/w== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" luxon "^3.5.0" -"@abp/malihu-custom-scrollbar-plugin@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-9.0.0.tgz#b8a49202d531c711874a2bcdae5081ae0ed34c3f" - integrity sha512-iSZ9lKMnv5F2SplR+A4blhFEDq79lKbvBQuS7h/Di3KEHFOdJY4pA8NP8OR+PXOF3JfAezGyjqy7ydn/JNPRwA== +"@abp/malihu-custom-scrollbar-plugin@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-9.0.3.tgz#b3723f777b61c47ed64527b4295df33cca5cae26" + integrity sha512-aRearvDO1OaV7wfsWR2/cieWR/hG9cX78YGE08goRq0xLcGnBkmraQHSGGSZbMwId5zf5Kh3ePVXChO7llitkQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-9.0.0.tgz#9583e97472ddf608a9a5a6b8a37fee602b419900" - integrity sha512-wgyJSKmBmHLXZJN3dy3zK4i2JvWGgqnvdtrKURy/raEurxlymv8JF5JA971kTv6Kz2OA/OSYttpVJkDGUpPAhA== +"@abp/moment@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-9.0.3.tgz#696748659e036a1bf707684340703a861f340a92" + integrity sha512-7SRIjSHOyDTbuZ41vpisft+c5L+E3fk0G+V5+rpsACGTm6SpRqnVuAl5egbrDcm9rQeP0+VSV+/6UXdM7koOCQ== dependencies: moment "^2.30.1" -"@abp/select2@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-9.0.0.tgz#71503eebb746f40a0e565dba1efa05d6ae937b77" - integrity sha512-l+Hz2Qmc7AcyaTJOyuDaV99RbBWYxYxbeyVEc4xKbRFbJxYBUxT/DFlyozEaoz+OU1fl4szO3UCthJ2yZvxCdg== +"@abp/select2@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-9.0.3.tgz#d75afa45decc4ce5a25ecb257f76a9f4c4c1b1fd" + integrity sha512-Cs/cQHdqV0U/c3RXbTTfIQoTaJqjBZMGon8cHX6rmvCDhWKOU/B2E+R2wNlcMjl0Tkhfy3Jl1mwlD0KZ/LePbA== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" select2 "^4.0.13" -"@abp/sweetalert2@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-9.0.0.tgz#71e511117c18b515e5c658e9cc0667770af65d91" - integrity sha512-wxDqoA6RUhkbcN27Kzslu/m2ai5T9AkJRtgs7pV55LvPv1ohuCPEUeTVVh8S/5Pgmr7aPFAPxaaCO32aPcImPw== +"@abp/sweetalert2@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-9.0.3.tgz#b7619bcf664b7aaf51f096df1b60f604db36c84c" + integrity sha512-W9fQu1jGhzqliyqKU/OFtAHiS3b8kar3LN9Pij2g/Q8zS2esjdgdt4LoaI5SytRi7zXUqn2tCi/cDEDXf/X3zQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" sweetalert2 "^11.14.1" -"@abp/timeago@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-9.0.0.tgz#8f262ba2a74e1c133d0c129f8a0bc55c29441e1a" - integrity sha512-eaNdIUEtDXnuYqS5O7EGRV+j6XOLAsxz7gd5BIKR9lDujd8pEjSJkXKO+/0KxLTkUSy3JudIO37K1h2wTpbYZA== +"@abp/timeago@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-9.0.3.tgz#8e675aafcc862643303909c27bbb982aa1c3c22c" + integrity sha512-B2ZHs4IfZyy+YHLlS/KLhxbqvxTkEEz1y+Sk8HtVSk0Ula5UD+U1FvwcxOiYGIghmHlkqFBtegifZosPOMB9VQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" timeago "^1.6.7" -"@abp/toastr@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-9.0.0.tgz#74d74ecb3e7f2d63b2ff7735c89a40deb3dc3a13" - integrity sha512-n9uJvw8l2HoxeowPO4diEMxwchGh6puefhyRljFAAstzK77Mhn0u2J7kPynNv94rLSYTYoCxUeJ4TTN+/TVDGQ== +"@abp/toastr@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-9.0.3.tgz#1863fea4d709b524f8fea0233dfd0ff8b1bc5bb2" + integrity sha512-3eV17OBB8XLJC9dZWW6xzi4fpu5NZqNrRiKiYJf79IUvmuS0cBVKpNJVKRaDTp4DeDBHxCQOWmMnrNI/mh3bDQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" toastr "^2.1.4" -"@abp/utils@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-9.0.0.tgz#2264fd83105b061860edd98dbe0efdfe55803ed5" - integrity sha512-/SB4VYk3Av+ltXv6ovkFHG4iWiECLLitaYWcEHxVAO80ufNjrNnWpHFpXHP8u/RpXIp/CoNiHLr6j5TkZUTZjw== +"@abp/utils@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-9.0.3.tgz#02dfd981d210be8229d65f802f0721b42c9e17d7" + integrity sha512-45sQQ6IV3cB9KAIVtRn3IH1wVr9DfM0PMFiwVt3F+wKwNBAPsKNcMPt4/EhZCNY3IMaEseJ7K/lxt33O3sURuA== dependencies: just-compare "^2.3.0" diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Host/package.json b/modules/cms-kit/host/Volo.CmsKit.Web.Host/package.json index 8b5210b25f..10771644f8 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Host/package.json +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Host/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~9.0.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~9.0.3" } } diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Host/yarn.lock b/modules/cms-kit/host/Volo.CmsKit.Web.Host/yarn.lock index 9e01130758..3de1cb98c3 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Host/yarn.lock +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Host/yarn.lock @@ -2,194 +2,194 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-9.0.0.tgz#e4b05d63446c24b612275e3949881e176d32527f" - integrity sha512-GHEt26xESe/TCg1q6ZjeUbxniqdxaiB1OPTZ9md7ONql6bEp8t9iUPeSlkyAZtQ7qBzwgkiLpkRFT+J21DftaQ== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~9.0.0" - -"@abp/aspnetcore.mvc.ui.theme.shared@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-9.0.0.tgz#a77b4b70fa6217146afd6a57211f57e2360564f2" - integrity sha512-QmZrSp+rfqfnC9W4QZ+nNdAq0rOALzRn1rsDpGDHhIHwyXB03QPdjzIWaEvldUYHzqg84cEP0LcqW4y57Xr1iw== - dependencies: - "@abp/aspnetcore.mvc.ui" "~9.0.0" - "@abp/bootstrap" "~9.0.0" - "@abp/bootstrap-datepicker" "~9.0.0" - "@abp/bootstrap-daterangepicker" "~9.0.0" - "@abp/datatables.net-bs5" "~9.0.0" - "@abp/font-awesome" "~9.0.0" - "@abp/jquery-form" "~9.0.0" - "@abp/jquery-validation-unobtrusive" "~9.0.0" - "@abp/lodash" "~9.0.0" - "@abp/luxon" "~9.0.0" - "@abp/malihu-custom-scrollbar-plugin" "~9.0.0" - "@abp/moment" "~9.0.0" - "@abp/select2" "~9.0.0" - "@abp/sweetalert2" "~9.0.0" - "@abp/timeago" "~9.0.0" - "@abp/toastr" "~9.0.0" - -"@abp/aspnetcore.mvc.ui@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-9.0.0.tgz#93004dd056c130386c7b40c458f3a793d8c9e051" - integrity sha512-pvO5bh9baFcSbfW0s7tpMpbe6DKSC16jeyeluZLlvYxC0n2gkrv7H/hB5aMuBKIlh89aGY9DQIcMFC8iczL3Bg== +"@abp/aspnetcore.mvc.ui.theme.basic@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-9.0.3.tgz#dcb95764ee11fc11ca593b722575275837cc58a9" + integrity sha512-NwTunUVwfaoZsNp1Js6B+jGPBw96taBhQ5hloKhtZFZxG2VMAN9Gy9fGxzUByYlpRB8CASR3kL+GRhrK2Ufc3A== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~9.0.3" + +"@abp/aspnetcore.mvc.ui.theme.shared@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-9.0.3.tgz#18f0d27cbba7ff22576c1c6e54891190c7d900bb" + integrity sha512-WHMvMiZhqvPLQtqo3aI+wkhNbZ3gCkWqwhIiXHvRks+tO92ww52evPlZUNDSWSSkhcB9vJddx5Eqv4phJgcMAA== + dependencies: + "@abp/aspnetcore.mvc.ui" "~9.0.3" + "@abp/bootstrap" "~9.0.3" + "@abp/bootstrap-datepicker" "~9.0.3" + "@abp/bootstrap-daterangepicker" "~9.0.3" + "@abp/datatables.net-bs5" "~9.0.3" + "@abp/font-awesome" "~9.0.3" + "@abp/jquery-form" "~9.0.3" + "@abp/jquery-validation-unobtrusive" "~9.0.3" + "@abp/lodash" "~9.0.3" + "@abp/luxon" "~9.0.3" + "@abp/malihu-custom-scrollbar-plugin" "~9.0.3" + "@abp/moment" "~9.0.3" + "@abp/select2" "~9.0.3" + "@abp/sweetalert2" "~9.0.3" + "@abp/timeago" "~9.0.3" + "@abp/toastr" "~9.0.3" + +"@abp/aspnetcore.mvc.ui@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-9.0.3.tgz#a4e2e92cddca77d81d8445bcc1d2cc129ec4b486" + integrity sha512-wkn7R8sx5lhbaZeTAy5aJxLbR1f3TD8lNaNtM1Lsc7wWq9dI19wyJZiAWqCZij2HOr2IK5izSROVfTqoXyUwXQ== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-9.0.0.tgz#d6609e50568aac6a013abfcaa07817998e1eaa99" - integrity sha512-wxAeVsOVOJpidDn0g3FlJvpOsn5SVY5IpEG+FtUxSAKuWYJQWF6g5CaghrfS+FNDK9IHhxrgSMPMVBK9YlDSzw== +"@abp/bootstrap-datepicker@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-9.0.3.tgz#aa40f5a31eb5c06224c165129a882658f4a0a9a5" + integrity sha512-Jg4oeBPtX7G41JdYjHVhB/6KQcMJO4WWXbUvkNetSUGTZGqacmgWRAIA5abmRWaHMJP1A1soZ9Ny1GNy0Qu65Q== dependencies: bootstrap-datepicker "^1.10.0" -"@abp/bootstrap-daterangepicker@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-9.0.0.tgz#fc2871e7d886accd8e4748cb763b695e10701c00" - integrity sha512-qS6SQR6X7wMCVLcuXatJ2+m1GNlpN71p7VDritxfwAMHa7WBRAAi8apX2WItwBAuLQ2s1uSA0Wp1KstHG3VPLA== +"@abp/bootstrap-daterangepicker@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-9.0.3.tgz#a1c17721a1ff6613a3ee4ff971174b32a93d98de" + integrity sha512-wrvjb0bJudohxiYtecCGEa4VsvL/Kn/Z3q9w1jvIoallrTXQaACPtZDU+9yxK5mRf6zonwYI8JtNn4xKa+D1VA== dependencies: bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-9.0.0.tgz#5ab292e95ba7eef129cbaa1dc5f5d5c0af8530d7" - integrity sha512-jEr6su8BfoNxYvze/UlddE3DlezyPvQeiNhff8SDPqtRQ6XvjZ48O/JwtY01hK2njEDLOZINY5zJRGmO2OiOUw== +"@abp/bootstrap@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-9.0.3.tgz#dc0325c23282086c9d2be79588b228e8b2c66eb2" + integrity sha512-ypl0R3jky6qKmnEZjnSniwgN8unNmWD9d4bOtV5RgDF2MlBQT+CvfUcYnGom0JOsL+5RjSNvzFH/Q5aH/yuy7Q== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" bootstrap "^5.3.3" -"@abp/core@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-9.0.0.tgz#dc5ea9139957e307f73ac1f40b179f991e3c2168" - integrity sha512-vzQyTpqVDx63pOI9e2QLUMWZCrSojHsRnzLlvl+/cKFi7jE1q/uSP66FPR2bwkD+DNLI/tn6is1Jx3skKA1FvA== +"@abp/core@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-9.0.3.tgz#8c508dc60628fbd9e8a9ba35d153316cacbf1fb4" + integrity sha512-4bHiyP2qPrEeXb/Ynl17b6LtfygWdkwsjHaadUbym7Lg5qYUQZ3KjW4U2DihYWCO46gsr/ckTEbV9edcRd+NxA== dependencies: - "@abp/utils" "~9.0.0" + "@abp/utils" "~9.0.3" -"@abp/datatables.net-bs5@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-9.0.0.tgz#4daee90bdb6446e854ce65f2a3afabb2a13e49ae" - integrity sha512-ln10tcRwxcB0nuSgqJ/66strHsqMHDC03c0ktxEplAN34yP7KInjFf/U9Svo74K0nKlkJdMCgmqmT4CnBJZlIg== +"@abp/datatables.net-bs5@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-9.0.3.tgz#c89813a69a1b568a171cb2a4b1bcc4b7a4ef1bef" + integrity sha512-SD6N4apScOR22S7EntengOTQ1HSm2WnvzUuP/6HG50isXyMheRalZxJ6zAsBwiWyYyJtA+UHkiQzwVhdV40aBQ== dependencies: - "@abp/datatables.net" "~9.0.0" + "@abp/datatables.net" "~9.0.3" datatables.net-bs5 "^2.1.8" -"@abp/datatables.net@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-9.0.0.tgz#82b28518293c9091ec72d5defae2f5d2903d8bbc" - integrity sha512-QhuX3bGZOckM/fhIIGj/StMJIX2BBtNbLzjTR7WnKMMxDkv0+oDvjw6OqqpYKSvj/q3vY6sRWuCNdEzNi0Fk6A== +"@abp/datatables.net@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-9.0.3.tgz#9cc09127ff6269476b6b0de16048e1e6480227c4" + integrity sha512-zLzHxdXnmL8pWKVzf3m4SIUMiPHHe3I9Cw8vezk5fK1v18Fk364o3Q8o8C1l1YDGEa7Sx1wsUoLDN+p4FzagXg== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" datatables.net "^2.1.8" -"@abp/font-awesome@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-9.0.0.tgz#51cce18434a51d050ebb0d822a8c193b7e32f73d" - integrity sha512-bEHRVzOk0V2kFMAvB/YXFYaDKWRFO0+ScO7jrEFtFgdHXBcqy+dk1aM/N69WalyNmjRRpnh03GhH1LRpwL4/xA== +"@abp/font-awesome@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-9.0.3.tgz#db6ece04fcdb4880cac8174deffb4fa04d43d2de" + integrity sha512-toI2zGayMeI4EUUMmittRWKBY/mLjiiX1zA2z70q/Hpm9zoCly2KwbN7zwtWbayhESO7edci9T7CXF5kNdiNow== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" "@fortawesome/fontawesome-free" "^6.6.0" -"@abp/jquery-form@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-9.0.0.tgz#6518eb117ee35c6bc57543fd1fccb0a8cf8e22f4" - integrity sha512-6qpfPGHrjADmm7Ne3zcXBGPuLngG8ldAn5fk9k2taftpyX0iR67/nZxiQ0ujaQGgaai5Wb3imNXMG96XLnubkQ== +"@abp/jquery-form@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-9.0.3.tgz#907033a496af127bfbe4e220f43cda65331e7b98" + integrity sha512-kD/9S25kJSAj5f7bfHVMfzfF5/0OrLDj8E55M27zbusdcaFS/nYElzrU3DeAEusASdnssRAwyJCWyp2vvRIMzA== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-9.0.0.tgz#8ee87dbdec5e92ab04ae9642a49af1598af31c1a" - integrity sha512-KlRQ1htyFmAIMS9xbHvHIcdhAgtPFe/u7c87MiWlk/CdhDrI3J+n5biLvQUzxCC+YWlwx27zBtPKx2vLeMRH6Q== +"@abp/jquery-validation-unobtrusive@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-9.0.3.tgz#b9449d222d4855f941dbe7445f73fff1541975a4" + integrity sha512-U/zUDYyJnUYDmmloOx/rViVyg7b4Gxd4zgcEi1E1YP0zryQQh5CkoSEvzv7GyurB1/rVZuRszmo/nRgn3pHOKg== dependencies: - "@abp/jquery-validation" "~9.0.0" + "@abp/jquery-validation" "~9.0.3" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-9.0.0.tgz#681d8037c57b3875bd51c927e350ffce65d7c5d2" - integrity sha512-XgCUBFWbhTDcGepD/xkwA/1Hl4TyYitRttauDJk3tuyXhNtykvdugCCOPY4j2s6VoT1Sb/TFFvOcgEvlBwkKiw== +"@abp/jquery-validation@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-9.0.3.tgz#02eb010267121fd9310cdcf5ca7c7b54091fd5de" + integrity sha512-tqjM5S0/jDS88CIt8oCQbHYJTytA7Y+KdTsnRkrA+BWy2A5bjrd6YUZXa/2UUwXHa3tQgv/uXl9HpDEaqRtfYQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" jquery-validation "^1.21.0" -"@abp/jquery@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-9.0.0.tgz#6c67a9f927be598ab6201f8888e8a3cb8bba0686" - integrity sha512-cUXA7v6F1HqAc/w8fQGxHIfA/6BqO+4uHXDnSsJZtrI26DTLOgw5kBNnAz2W4xggGPCY7SKFHNRZuOW/QfVRPg== +"@abp/jquery@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-9.0.3.tgz#ba8b4f4c0825ccb55c793024d3bbeb9ee817b19c" + integrity sha512-lZoyiyz83VOQKbN+9gD8JC+WI+OACc3y2hU+v30v220DLKhwoiVQM6d5yfA2QEugTPTjm/s40Ocmh/nVjm5mtg== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" jquery "~3.7.1" -"@abp/lodash@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-9.0.0.tgz#a4d42b2dfeb37859ff9f0079c7a8a6cabc982efe" - integrity sha512-xoAsOkkgnQdPH2OyFTgF6OlCLVbyGF4u8CrFcZWCZmuIFT9cg0+ZEiq//Vog1pg3Lf//N37ceGpOag1LwMe6cQ== +"@abp/lodash@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-9.0.3.tgz#daadfb98339f1d6982a12b655b840f105b324830" + integrity sha512-wt4ZJmRlhnK9W3o8lex7LyL/7rzgu4STLtZ2Ga8Ec5uZ9kN0CDTYjIw0zVqB8iGNKcW8MCxIGB7u9JMU9tphiQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" lodash "^4.17.21" -"@abp/luxon@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-9.0.0.tgz#a54dda13cbbd362481994d20d2e07c669c6ce109" - integrity sha512-aPejQnoVPfm60LnjwhfUkIsB+poNWusrBE0IHllxhvOVE+01nfa1Lvpp5AUWzV+XchPGOCSf+a29ds4EQWfNbg== +"@abp/luxon@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-9.0.3.tgz#62a5e395c92cadcf0985453eee065fcbe5354402" + integrity sha512-qTPw2vhgkgNldFJziHiL+c2vF7p+7RW1orev8K8XLSe3dguzlXBL2dicyDWCX9ay4zYfEpXbA0oKFgJvhBfK/w== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" luxon "^3.5.0" -"@abp/malihu-custom-scrollbar-plugin@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-9.0.0.tgz#b8a49202d531c711874a2bcdae5081ae0ed34c3f" - integrity sha512-iSZ9lKMnv5F2SplR+A4blhFEDq79lKbvBQuS7h/Di3KEHFOdJY4pA8NP8OR+PXOF3JfAezGyjqy7ydn/JNPRwA== +"@abp/malihu-custom-scrollbar-plugin@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-9.0.3.tgz#b3723f777b61c47ed64527b4295df33cca5cae26" + integrity sha512-aRearvDO1OaV7wfsWR2/cieWR/hG9cX78YGE08goRq0xLcGnBkmraQHSGGSZbMwId5zf5Kh3ePVXChO7llitkQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-9.0.0.tgz#9583e97472ddf608a9a5a6b8a37fee602b419900" - integrity sha512-wgyJSKmBmHLXZJN3dy3zK4i2JvWGgqnvdtrKURy/raEurxlymv8JF5JA971kTv6Kz2OA/OSYttpVJkDGUpPAhA== +"@abp/moment@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-9.0.3.tgz#696748659e036a1bf707684340703a861f340a92" + integrity sha512-7SRIjSHOyDTbuZ41vpisft+c5L+E3fk0G+V5+rpsACGTm6SpRqnVuAl5egbrDcm9rQeP0+VSV+/6UXdM7koOCQ== dependencies: moment "^2.30.1" -"@abp/select2@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-9.0.0.tgz#71503eebb746f40a0e565dba1efa05d6ae937b77" - integrity sha512-l+Hz2Qmc7AcyaTJOyuDaV99RbBWYxYxbeyVEc4xKbRFbJxYBUxT/DFlyozEaoz+OU1fl4szO3UCthJ2yZvxCdg== +"@abp/select2@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-9.0.3.tgz#d75afa45decc4ce5a25ecb257f76a9f4c4c1b1fd" + integrity sha512-Cs/cQHdqV0U/c3RXbTTfIQoTaJqjBZMGon8cHX6rmvCDhWKOU/B2E+R2wNlcMjl0Tkhfy3Jl1mwlD0KZ/LePbA== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" select2 "^4.0.13" -"@abp/sweetalert2@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-9.0.0.tgz#71e511117c18b515e5c658e9cc0667770af65d91" - integrity sha512-wxDqoA6RUhkbcN27Kzslu/m2ai5T9AkJRtgs7pV55LvPv1ohuCPEUeTVVh8S/5Pgmr7aPFAPxaaCO32aPcImPw== +"@abp/sweetalert2@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-9.0.3.tgz#b7619bcf664b7aaf51f096df1b60f604db36c84c" + integrity sha512-W9fQu1jGhzqliyqKU/OFtAHiS3b8kar3LN9Pij2g/Q8zS2esjdgdt4LoaI5SytRi7zXUqn2tCi/cDEDXf/X3zQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" sweetalert2 "^11.14.1" -"@abp/timeago@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-9.0.0.tgz#8f262ba2a74e1c133d0c129f8a0bc55c29441e1a" - integrity sha512-eaNdIUEtDXnuYqS5O7EGRV+j6XOLAsxz7gd5BIKR9lDujd8pEjSJkXKO+/0KxLTkUSy3JudIO37K1h2wTpbYZA== +"@abp/timeago@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-9.0.3.tgz#8e675aafcc862643303909c27bbb982aa1c3c22c" + integrity sha512-B2ZHs4IfZyy+YHLlS/KLhxbqvxTkEEz1y+Sk8HtVSk0Ula5UD+U1FvwcxOiYGIghmHlkqFBtegifZosPOMB9VQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" timeago "^1.6.7" -"@abp/toastr@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-9.0.0.tgz#74d74ecb3e7f2d63b2ff7735c89a40deb3dc3a13" - integrity sha512-n9uJvw8l2HoxeowPO4diEMxwchGh6puefhyRljFAAstzK77Mhn0u2J7kPynNv94rLSYTYoCxUeJ4TTN+/TVDGQ== +"@abp/toastr@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-9.0.3.tgz#1863fea4d709b524f8fea0233dfd0ff8b1bc5bb2" + integrity sha512-3eV17OBB8XLJC9dZWW6xzi4fpu5NZqNrRiKiYJf79IUvmuS0cBVKpNJVKRaDTp4DeDBHxCQOWmMnrNI/mh3bDQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" toastr "^2.1.4" -"@abp/utils@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-9.0.0.tgz#2264fd83105b061860edd98dbe0efdfe55803ed5" - integrity sha512-/SB4VYk3Av+ltXv6ovkFHG4iWiECLLitaYWcEHxVAO80ufNjrNnWpHFpXHP8u/RpXIp/CoNiHLr6j5TkZUTZjw== +"@abp/utils@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-9.0.3.tgz#02dfd981d210be8229d65f802f0721b42c9e17d7" + integrity sha512-45sQQ6IV3cB9KAIVtRn3IH1wVr9DfM0PMFiwVt3F+wKwNBAPsKNcMPt4/EhZCNY3IMaEseJ7K/lxt33O3sURuA== dependencies: just-compare "^2.3.0" diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/package.json b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/package.json index 5619ca083e..0f3f3e2d3f 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/package.json +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~9.0.0", - "@abp/cms-kit": "9.0.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~9.0.3", + "@abp/cms-kit": "9.0.3" } } diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/yarn.lock b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/yarn.lock index 3c5c5f9e97..e8b2551ae9 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/yarn.lock +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/yarn.lock @@ -2,302 +2,302 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-9.0.0.tgz#e4b05d63446c24b612275e3949881e176d32527f" - integrity sha512-GHEt26xESe/TCg1q6ZjeUbxniqdxaiB1OPTZ9md7ONql6bEp8t9iUPeSlkyAZtQ7qBzwgkiLpkRFT+J21DftaQ== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~9.0.0" - -"@abp/aspnetcore.mvc.ui.theme.shared@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-9.0.0.tgz#a77b4b70fa6217146afd6a57211f57e2360564f2" - integrity sha512-QmZrSp+rfqfnC9W4QZ+nNdAq0rOALzRn1rsDpGDHhIHwyXB03QPdjzIWaEvldUYHzqg84cEP0LcqW4y57Xr1iw== - dependencies: - "@abp/aspnetcore.mvc.ui" "~9.0.0" - "@abp/bootstrap" "~9.0.0" - "@abp/bootstrap-datepicker" "~9.0.0" - "@abp/bootstrap-daterangepicker" "~9.0.0" - "@abp/datatables.net-bs5" "~9.0.0" - "@abp/font-awesome" "~9.0.0" - "@abp/jquery-form" "~9.0.0" - "@abp/jquery-validation-unobtrusive" "~9.0.0" - "@abp/lodash" "~9.0.0" - "@abp/luxon" "~9.0.0" - "@abp/malihu-custom-scrollbar-plugin" "~9.0.0" - "@abp/moment" "~9.0.0" - "@abp/select2" "~9.0.0" - "@abp/sweetalert2" "~9.0.0" - "@abp/timeago" "~9.0.0" - "@abp/toastr" "~9.0.0" - -"@abp/aspnetcore.mvc.ui@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-9.0.0.tgz#93004dd056c130386c7b40c458f3a793d8c9e051" - integrity sha512-pvO5bh9baFcSbfW0s7tpMpbe6DKSC16jeyeluZLlvYxC0n2gkrv7H/hB5aMuBKIlh89aGY9DQIcMFC8iczL3Bg== +"@abp/aspnetcore.mvc.ui.theme.basic@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-9.0.3.tgz#dcb95764ee11fc11ca593b722575275837cc58a9" + integrity sha512-NwTunUVwfaoZsNp1Js6B+jGPBw96taBhQ5hloKhtZFZxG2VMAN9Gy9fGxzUByYlpRB8CASR3kL+GRhrK2Ufc3A== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~9.0.3" + +"@abp/aspnetcore.mvc.ui.theme.shared@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-9.0.3.tgz#18f0d27cbba7ff22576c1c6e54891190c7d900bb" + integrity sha512-WHMvMiZhqvPLQtqo3aI+wkhNbZ3gCkWqwhIiXHvRks+tO92ww52evPlZUNDSWSSkhcB9vJddx5Eqv4phJgcMAA== + dependencies: + "@abp/aspnetcore.mvc.ui" "~9.0.3" + "@abp/bootstrap" "~9.0.3" + "@abp/bootstrap-datepicker" "~9.0.3" + "@abp/bootstrap-daterangepicker" "~9.0.3" + "@abp/datatables.net-bs5" "~9.0.3" + "@abp/font-awesome" "~9.0.3" + "@abp/jquery-form" "~9.0.3" + "@abp/jquery-validation-unobtrusive" "~9.0.3" + "@abp/lodash" "~9.0.3" + "@abp/luxon" "~9.0.3" + "@abp/malihu-custom-scrollbar-plugin" "~9.0.3" + "@abp/moment" "~9.0.3" + "@abp/select2" "~9.0.3" + "@abp/sweetalert2" "~9.0.3" + "@abp/timeago" "~9.0.3" + "@abp/toastr" "~9.0.3" + +"@abp/aspnetcore.mvc.ui@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-9.0.3.tgz#a4e2e92cddca77d81d8445bcc1d2cc129ec4b486" + integrity sha512-wkn7R8sx5lhbaZeTAy5aJxLbR1f3TD8lNaNtM1Lsc7wWq9dI19wyJZiAWqCZij2HOr2IK5izSROVfTqoXyUwXQ== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-9.0.0.tgz#d6609e50568aac6a013abfcaa07817998e1eaa99" - integrity sha512-wxAeVsOVOJpidDn0g3FlJvpOsn5SVY5IpEG+FtUxSAKuWYJQWF6g5CaghrfS+FNDK9IHhxrgSMPMVBK9YlDSzw== +"@abp/bootstrap-datepicker@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-9.0.3.tgz#aa40f5a31eb5c06224c165129a882658f4a0a9a5" + integrity sha512-Jg4oeBPtX7G41JdYjHVhB/6KQcMJO4WWXbUvkNetSUGTZGqacmgWRAIA5abmRWaHMJP1A1soZ9Ny1GNy0Qu65Q== dependencies: bootstrap-datepicker "^1.10.0" -"@abp/bootstrap-daterangepicker@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-9.0.0.tgz#fc2871e7d886accd8e4748cb763b695e10701c00" - integrity sha512-qS6SQR6X7wMCVLcuXatJ2+m1GNlpN71p7VDritxfwAMHa7WBRAAi8apX2WItwBAuLQ2s1uSA0Wp1KstHG3VPLA== +"@abp/bootstrap-daterangepicker@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-9.0.3.tgz#a1c17721a1ff6613a3ee4ff971174b32a93d98de" + integrity sha512-wrvjb0bJudohxiYtecCGEa4VsvL/Kn/Z3q9w1jvIoallrTXQaACPtZDU+9yxK5mRf6zonwYI8JtNn4xKa+D1VA== dependencies: bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-9.0.0.tgz#5ab292e95ba7eef129cbaa1dc5f5d5c0af8530d7" - integrity sha512-jEr6su8BfoNxYvze/UlddE3DlezyPvQeiNhff8SDPqtRQ6XvjZ48O/JwtY01hK2njEDLOZINY5zJRGmO2OiOUw== +"@abp/bootstrap@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-9.0.3.tgz#dc0325c23282086c9d2be79588b228e8b2c66eb2" + integrity sha512-ypl0R3jky6qKmnEZjnSniwgN8unNmWD9d4bOtV5RgDF2MlBQT+CvfUcYnGom0JOsL+5RjSNvzFH/Q5aH/yuy7Q== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" bootstrap "^5.3.3" -"@abp/clipboard@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-9.0.0.tgz#21ae924dfef6ccf3c8a3913bb81caf22b57404e4" - integrity sha512-W6S7pihpQ6Wfj48Clk7hYgDvUtnRwkNIsk5t8s9+LxoAdJQGQFxh7SAW1QNavUv8QqsClNE1VtSU87c7rSoAbA== +"@abp/clipboard@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-9.0.3.tgz#bda02ce6da876890ef117697f30348b8c16aaf70" + integrity sha512-iprJtnshdpRgLIPxa5Tgj1IqkLvkl2QxTmjrqeiuRhU3S1ry0GnE6dx3jAealWUOoYr/bqXPbpXi3RN7R4baHw== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" clipboard "^2.0.11" -"@abp/cms-kit.admin@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/cms-kit.admin/-/cms-kit.admin-9.0.0.tgz#584337c9d751c9eae26be8f276683b5c3c23a499" - integrity sha512-SYixIuy7McA9UhaKmun8IG0QBmegvk8KBrE/6Uvo1Cmd6Z/oT9SS+i/f09+7UVtU+7x5kkL+9sIs/7sS79cY0g== +"@abp/cms-kit.admin@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/cms-kit.admin/-/cms-kit.admin-9.0.3.tgz#4d2e175662b8e44eb0f894b0f5b1df09548e98fa" + integrity sha512-RmAl1TkmTi0zVRemBcHD4rNYFs2G6hhzFb2OHPZYpH5b6rFE6OPZjNHOXvH+gBVfuqDv2mc1FNuNc7RZGksKnw== dependencies: - "@abp/codemirror" "~9.0.0" - "@abp/jstree" "~9.0.0" - "@abp/markdown-it" "~9.0.0" - "@abp/slugify" "~9.0.0" - "@abp/tui-editor" "~9.0.0" - "@abp/uppy" "~9.0.0" + "@abp/codemirror" "~9.0.3" + "@abp/jstree" "~9.0.3" + "@abp/markdown-it" "~9.0.3" + "@abp/slugify" "~9.0.3" + "@abp/tui-editor" "~9.0.3" + "@abp/uppy" "~9.0.3" -"@abp/cms-kit.public@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/cms-kit.public/-/cms-kit.public-9.0.0.tgz#bdc8f60bc5b1ffa640baced8c5562f93f7e8b754" - integrity sha512-s141fUfN9rcRdT4KKjR7PthITnFSx6RXrxPaXiOfNXPdnVQmgKRg7HhO92KJQKO+CeYbiMNO3Fpt9MSffvkEJQ== +"@abp/cms-kit.public@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/cms-kit.public/-/cms-kit.public-9.0.3.tgz#fd5478a953c8f84eaedc029dbb22071cc557d249" + integrity sha512-CFIidxnrBIo2AextML/Cdmt5wBRXmNjET9ZgkT6joItJTofu5U3hnTNRaliZ5sRFnowHBKC19UWmdxemBFZZSg== dependencies: - "@abp/highlight.js" "~9.0.0" - "@abp/star-rating-svg" "~9.0.0" + "@abp/highlight.js" "~9.0.3" + "@abp/star-rating-svg" "~9.0.3" -"@abp/cms-kit@9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/cms-kit/-/cms-kit-9.0.0.tgz#19364cf6ccfbf6ec927c21ce302a85dfe4215f1b" - integrity sha512-ZjakEuhKvDh1r3tVnDUxkQWj+9TJuqaFEI2vEiHysB81rM5mqKaOtATfhtC75CN5LDqiKCAzDs+6ty4YmTe1PQ== +"@abp/cms-kit@9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/cms-kit/-/cms-kit-9.0.3.tgz#998189b381c1d1c8c396a593309da734e5eb7964" + integrity sha512-Gi3V+8280pEmigZbxbAu0tMuNCjUCX1fVYEjH3NWWdw3dU0iHpAUqow7NU1psmqFe2Sfiesn1NtVYGVzYHKdYw== dependencies: - "@abp/cms-kit.admin" "~9.0.0" - "@abp/cms-kit.public" "~9.0.0" + "@abp/cms-kit.admin" "~9.0.3" + "@abp/cms-kit.public" "~9.0.3" -"@abp/codemirror@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/codemirror/-/codemirror-9.0.0.tgz#d398edce6ececdaf1f62e216f98a91b26fb595bd" - integrity sha512-tXcdoXjpco8VnjE/on4ax9USfz3lu03Y+TehAt1tbVQRBdhkGL+qFB/MrewRKUNM8TjSewuT7D4RxpmThABpNw== +"@abp/codemirror@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/codemirror/-/codemirror-9.0.3.tgz#07337675159eabfb96549d9cc4624fae6466e7ff" + integrity sha512-y9pTi89Cp+8Os/E3tvU7oFABvYGbj5Wj1EBWRP6M+eojU1t2z95T7i4dGaOFEDht4UYNTiy10XnS3SZ0m7RDAw== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" codemirror "^5.65.1" -"@abp/core@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-9.0.0.tgz#dc5ea9139957e307f73ac1f40b179f991e3c2168" - integrity sha512-vzQyTpqVDx63pOI9e2QLUMWZCrSojHsRnzLlvl+/cKFi7jE1q/uSP66FPR2bwkD+DNLI/tn6is1Jx3skKA1FvA== +"@abp/core@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-9.0.3.tgz#8c508dc60628fbd9e8a9ba35d153316cacbf1fb4" + integrity sha512-4bHiyP2qPrEeXb/Ynl17b6LtfygWdkwsjHaadUbym7Lg5qYUQZ3KjW4U2DihYWCO46gsr/ckTEbV9edcRd+NxA== dependencies: - "@abp/utils" "~9.0.0" + "@abp/utils" "~9.0.3" -"@abp/datatables.net-bs5@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-9.0.0.tgz#4daee90bdb6446e854ce65f2a3afabb2a13e49ae" - integrity sha512-ln10tcRwxcB0nuSgqJ/66strHsqMHDC03c0ktxEplAN34yP7KInjFf/U9Svo74K0nKlkJdMCgmqmT4CnBJZlIg== +"@abp/datatables.net-bs5@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-9.0.3.tgz#c89813a69a1b568a171cb2a4b1bcc4b7a4ef1bef" + integrity sha512-SD6N4apScOR22S7EntengOTQ1HSm2WnvzUuP/6HG50isXyMheRalZxJ6zAsBwiWyYyJtA+UHkiQzwVhdV40aBQ== dependencies: - "@abp/datatables.net" "~9.0.0" + "@abp/datatables.net" "~9.0.3" datatables.net-bs5 "^2.1.8" -"@abp/datatables.net@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-9.0.0.tgz#82b28518293c9091ec72d5defae2f5d2903d8bbc" - integrity sha512-QhuX3bGZOckM/fhIIGj/StMJIX2BBtNbLzjTR7WnKMMxDkv0+oDvjw6OqqpYKSvj/q3vY6sRWuCNdEzNi0Fk6A== +"@abp/datatables.net@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-9.0.3.tgz#9cc09127ff6269476b6b0de16048e1e6480227c4" + integrity sha512-zLzHxdXnmL8pWKVzf3m4SIUMiPHHe3I9Cw8vezk5fK1v18Fk364o3Q8o8C1l1YDGEa7Sx1wsUoLDN+p4FzagXg== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" datatables.net "^2.1.8" -"@abp/font-awesome@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-9.0.0.tgz#51cce18434a51d050ebb0d822a8c193b7e32f73d" - integrity sha512-bEHRVzOk0V2kFMAvB/YXFYaDKWRFO0+ScO7jrEFtFgdHXBcqy+dk1aM/N69WalyNmjRRpnh03GhH1LRpwL4/xA== +"@abp/font-awesome@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-9.0.3.tgz#db6ece04fcdb4880cac8174deffb4fa04d43d2de" + integrity sha512-toI2zGayMeI4EUUMmittRWKBY/mLjiiX1zA2z70q/Hpm9zoCly2KwbN7zwtWbayhESO7edci9T7CXF5kNdiNow== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" "@fortawesome/fontawesome-free" "^6.6.0" -"@abp/highlight.js@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/highlight.js/-/highlight.js-9.0.0.tgz#6d924c9bb3d6b11dcd66a9a71aea873c9866ed6c" - integrity sha512-eGvgMUNYKW2B2h3QcpMRniaoQ1/9RJBXkn2VsyM8lVSfrfbOg26mMyw7J2BXvuEnXqtnVdFEG45cSFlAnTAVXQ== +"@abp/highlight.js@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/highlight.js/-/highlight.js-9.0.3.tgz#967b497badf854a9f0036082a39cf57eefd6d7c7" + integrity sha512-4/VhrgaiLb9kqO7HRAcmyoE63DFwMZToSjpSsXZPXMA2SM64cHjCz6cN5Ukh+GGOEolDVlbHoExON067rOz42w== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" "@highlightjs/cdn-assets" "~11.10.0" -"@abp/jquery-form@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-9.0.0.tgz#6518eb117ee35c6bc57543fd1fccb0a8cf8e22f4" - integrity sha512-6qpfPGHrjADmm7Ne3zcXBGPuLngG8ldAn5fk9k2taftpyX0iR67/nZxiQ0ujaQGgaai5Wb3imNXMG96XLnubkQ== +"@abp/jquery-form@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-9.0.3.tgz#907033a496af127bfbe4e220f43cda65331e7b98" + integrity sha512-kD/9S25kJSAj5f7bfHVMfzfF5/0OrLDj8E55M27zbusdcaFS/nYElzrU3DeAEusASdnssRAwyJCWyp2vvRIMzA== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-9.0.0.tgz#8ee87dbdec5e92ab04ae9642a49af1598af31c1a" - integrity sha512-KlRQ1htyFmAIMS9xbHvHIcdhAgtPFe/u7c87MiWlk/CdhDrI3J+n5biLvQUzxCC+YWlwx27zBtPKx2vLeMRH6Q== +"@abp/jquery-validation-unobtrusive@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-9.0.3.tgz#b9449d222d4855f941dbe7445f73fff1541975a4" + integrity sha512-U/zUDYyJnUYDmmloOx/rViVyg7b4Gxd4zgcEi1E1YP0zryQQh5CkoSEvzv7GyurB1/rVZuRszmo/nRgn3pHOKg== dependencies: - "@abp/jquery-validation" "~9.0.0" + "@abp/jquery-validation" "~9.0.3" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-9.0.0.tgz#681d8037c57b3875bd51c927e350ffce65d7c5d2" - integrity sha512-XgCUBFWbhTDcGepD/xkwA/1Hl4TyYitRttauDJk3tuyXhNtykvdugCCOPY4j2s6VoT1Sb/TFFvOcgEvlBwkKiw== +"@abp/jquery-validation@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-9.0.3.tgz#02eb010267121fd9310cdcf5ca7c7b54091fd5de" + integrity sha512-tqjM5S0/jDS88CIt8oCQbHYJTytA7Y+KdTsnRkrA+BWy2A5bjrd6YUZXa/2UUwXHa3tQgv/uXl9HpDEaqRtfYQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" jquery-validation "^1.21.0" -"@abp/jquery@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-9.0.0.tgz#6c67a9f927be598ab6201f8888e8a3cb8bba0686" - integrity sha512-cUXA7v6F1HqAc/w8fQGxHIfA/6BqO+4uHXDnSsJZtrI26DTLOgw5kBNnAz2W4xggGPCY7SKFHNRZuOW/QfVRPg== +"@abp/jquery@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-9.0.3.tgz#ba8b4f4c0825ccb55c793024d3bbeb9ee817b19c" + integrity sha512-lZoyiyz83VOQKbN+9gD8JC+WI+OACc3y2hU+v30v220DLKhwoiVQM6d5yfA2QEugTPTjm/s40Ocmh/nVjm5mtg== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" jquery "~3.7.1" -"@abp/jstree@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jstree/-/jstree-9.0.0.tgz#08190074df54e45fe414b7a349c25d6b1f1058a1" - integrity sha512-ZbXPazP2Yy/qDMw/b4U8Hz1ZxbxQej/PAREaAawd2R2GJoGi10U8iTt5WKJJQP8Fm42kFgQa/8EtVcckwIo4FA== +"@abp/jstree@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jstree/-/jstree-9.0.3.tgz#9b59718dbeaacff6815964ed8e502c80d391ce22" + integrity sha512-+gge9T1G/n1edqDWRelyP+0V3jns1J6yObs7rg5ozpczwNwnJdZ74bHVT8phZqjF+ExpTkBI8GlyvtcUt93CAA== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" jstree "^3.3.17" -"@abp/lodash@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-9.0.0.tgz#a4d42b2dfeb37859ff9f0079c7a8a6cabc982efe" - integrity sha512-xoAsOkkgnQdPH2OyFTgF6OlCLVbyGF4u8CrFcZWCZmuIFT9cg0+ZEiq//Vog1pg3Lf//N37ceGpOag1LwMe6cQ== +"@abp/lodash@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-9.0.3.tgz#daadfb98339f1d6982a12b655b840f105b324830" + integrity sha512-wt4ZJmRlhnK9W3o8lex7LyL/7rzgu4STLtZ2Ga8Ec5uZ9kN0CDTYjIw0zVqB8iGNKcW8MCxIGB7u9JMU9tphiQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" lodash "^4.17.21" -"@abp/luxon@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-9.0.0.tgz#a54dda13cbbd362481994d20d2e07c669c6ce109" - integrity sha512-aPejQnoVPfm60LnjwhfUkIsB+poNWusrBE0IHllxhvOVE+01nfa1Lvpp5AUWzV+XchPGOCSf+a29ds4EQWfNbg== +"@abp/luxon@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-9.0.3.tgz#62a5e395c92cadcf0985453eee065fcbe5354402" + integrity sha512-qTPw2vhgkgNldFJziHiL+c2vF7p+7RW1orev8K8XLSe3dguzlXBL2dicyDWCX9ay4zYfEpXbA0oKFgJvhBfK/w== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" luxon "^3.5.0" -"@abp/malihu-custom-scrollbar-plugin@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-9.0.0.tgz#b8a49202d531c711874a2bcdae5081ae0ed34c3f" - integrity sha512-iSZ9lKMnv5F2SplR+A4blhFEDq79lKbvBQuS7h/Di3KEHFOdJY4pA8NP8OR+PXOF3JfAezGyjqy7ydn/JNPRwA== +"@abp/malihu-custom-scrollbar-plugin@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-9.0.3.tgz#b3723f777b61c47ed64527b4295df33cca5cae26" + integrity sha512-aRearvDO1OaV7wfsWR2/cieWR/hG9cX78YGE08goRq0xLcGnBkmraQHSGGSZbMwId5zf5Kh3ePVXChO7llitkQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/markdown-it@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/markdown-it/-/markdown-it-9.0.0.tgz#f61e74e7f9205f3015074896cf184f90b8a11a1c" - integrity sha512-frjJ539DiM4H0nHwnt77T1ryoazJhy9836L7HEFiYsfXJHs92VohUr/K27899I3gRdA6h0tbLYumFK3ug83qiw== +"@abp/markdown-it@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/markdown-it/-/markdown-it-9.0.3.tgz#d753ccffd6fdd11cab6f318deaa332bd24fe5d3b" + integrity sha512-AKMVASGySUIbUgHs7jEhh2dXaQeqbL2300YhlL0UCovEGmhUcwN/hv+zkVOM36a1RWs0Ysh3vHb7WgRP0GQSAA== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" markdown-it "^14.1.0" -"@abp/moment@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-9.0.0.tgz#9583e97472ddf608a9a5a6b8a37fee602b419900" - integrity sha512-wgyJSKmBmHLXZJN3dy3zK4i2JvWGgqnvdtrKURy/raEurxlymv8JF5JA971kTv6Kz2OA/OSYttpVJkDGUpPAhA== +"@abp/moment@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-9.0.3.tgz#696748659e036a1bf707684340703a861f340a92" + integrity sha512-7SRIjSHOyDTbuZ41vpisft+c5L+E3fk0G+V5+rpsACGTm6SpRqnVuAl5egbrDcm9rQeP0+VSV+/6UXdM7koOCQ== dependencies: moment "^2.30.1" -"@abp/prismjs@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-9.0.0.tgz#2ac4a5ddc0c8e0462af027a4dd97a177a3d6f7a5" - integrity sha512-+evmBq5qV+Ix/TNqO2ljJn0jygW2rEgKNtABcBGmrZC8xiI7+2hjKmSE0e43qb+RDib8aJjwH8ya8wF7p68ZFw== +"@abp/prismjs@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-9.0.3.tgz#9675dacd5df511120dffde76e925a8344ca9bace" + integrity sha512-5nMAKRADa6BQh3orquGHCUzqoZyDhmvoQ7RBCJsM3e0W+wBPeV9qmF+dJjOf9d+c9dr2mpU2SrH45fO/BNqgWg== dependencies: - "@abp/clipboard" "~9.0.0" - "@abp/core" "~9.0.0" + "@abp/clipboard" "~9.0.3" + "@abp/core" "~9.0.3" prismjs "^1.29.0" -"@abp/select2@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-9.0.0.tgz#71503eebb746f40a0e565dba1efa05d6ae937b77" - integrity sha512-l+Hz2Qmc7AcyaTJOyuDaV99RbBWYxYxbeyVEc4xKbRFbJxYBUxT/DFlyozEaoz+OU1fl4szO3UCthJ2yZvxCdg== +"@abp/select2@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-9.0.3.tgz#d75afa45decc4ce5a25ecb257f76a9f4c4c1b1fd" + integrity sha512-Cs/cQHdqV0U/c3RXbTTfIQoTaJqjBZMGon8cHX6rmvCDhWKOU/B2E+R2wNlcMjl0Tkhfy3Jl1mwlD0KZ/LePbA== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" select2 "^4.0.13" -"@abp/slugify@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/slugify/-/slugify-9.0.0.tgz#4bf4cade338697b68502cc73ac830d8dab10bbb8" - integrity sha512-tjHsQndDNDsQYubzQY8d+I/o1pQx4ehaSIw6xt0WyozogRegrxQ9QJUxOEQYtfNAgap0GipnxxvZSRBJINB4Cg== +"@abp/slugify@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/slugify/-/slugify-9.0.3.tgz#e073e5301b4c0d7bcb56272477e10f191a858a23" + integrity sha512-cCBjLW/Nc8FIP5U4U0fPq3zscS2N54evp2C5Zae/ADoPESBgR4F+p2q9+ieyMUCpyiHoYYhk+klHoSNibkxzQg== dependencies: slugify "^1.6.6" -"@abp/star-rating-svg@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/star-rating-svg/-/star-rating-svg-9.0.0.tgz#1e0d2ca6e589d3f7b63e6cfe6f7ad6c723f86a81" - integrity sha512-OZtWoFG2n5KdnSYeMH4SMYaeET3ISmyREBy5rNVM4peacNejyEHrPadzHDMWcGFMjqveTn/jmN19Jyn9+HG0Vw== +"@abp/star-rating-svg@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/star-rating-svg/-/star-rating-svg-9.0.3.tgz#8e9943c5912fd8abb798c9ee04b497405397fb41" + integrity sha512-suEl3T+Nt91196pQ6GS4foojGQubsZjbPQ/N0TN7cqw0FPLBGQitb8V+7mG6Y5hGscWpTWXGfMR3iRiedlJ46A== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" star-rating-svg "^3.5.0" -"@abp/sweetalert2@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-9.0.0.tgz#71e511117c18b515e5c658e9cc0667770af65d91" - integrity sha512-wxDqoA6RUhkbcN27Kzslu/m2ai5T9AkJRtgs7pV55LvPv1ohuCPEUeTVVh8S/5Pgmr7aPFAPxaaCO32aPcImPw== +"@abp/sweetalert2@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-9.0.3.tgz#b7619bcf664b7aaf51f096df1b60f604db36c84c" + integrity sha512-W9fQu1jGhzqliyqKU/OFtAHiS3b8kar3LN9Pij2g/Q8zS2esjdgdt4LoaI5SytRi7zXUqn2tCi/cDEDXf/X3zQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" sweetalert2 "^11.14.1" -"@abp/timeago@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-9.0.0.tgz#8f262ba2a74e1c133d0c129f8a0bc55c29441e1a" - integrity sha512-eaNdIUEtDXnuYqS5O7EGRV+j6XOLAsxz7gd5BIKR9lDujd8pEjSJkXKO+/0KxLTkUSy3JudIO37K1h2wTpbYZA== +"@abp/timeago@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-9.0.3.tgz#8e675aafcc862643303909c27bbb982aa1c3c22c" + integrity sha512-B2ZHs4IfZyy+YHLlS/KLhxbqvxTkEEz1y+Sk8HtVSk0Ula5UD+U1FvwcxOiYGIghmHlkqFBtegifZosPOMB9VQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" timeago "^1.6.7" -"@abp/toastr@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-9.0.0.tgz#74d74ecb3e7f2d63b2ff7735c89a40deb3dc3a13" - integrity sha512-n9uJvw8l2HoxeowPO4diEMxwchGh6puefhyRljFAAstzK77Mhn0u2J7kPynNv94rLSYTYoCxUeJ4TTN+/TVDGQ== +"@abp/toastr@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-9.0.3.tgz#1863fea4d709b524f8fea0233dfd0ff8b1bc5bb2" + integrity sha512-3eV17OBB8XLJC9dZWW6xzi4fpu5NZqNrRiKiYJf79IUvmuS0cBVKpNJVKRaDTp4DeDBHxCQOWmMnrNI/mh3bDQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" toastr "^2.1.4" -"@abp/tui-editor@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/tui-editor/-/tui-editor-9.0.0.tgz#d9f7721c3d8e361d901a0e0e11e786b2e12b234a" - integrity sha512-TVadK2Yi3wlwyvEVshh1PxkNRppYmqaPB6QE1cOhrWZAFbP1fh1oFvfqQiHN3o+9RGHFcEnhEK3D+49aHuDh3Q== +"@abp/tui-editor@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/tui-editor/-/tui-editor-9.0.3.tgz#f7d17844fa62562db99cc06d24ddeb6a80449a07" + integrity sha512-MS5lxKQBeGXT9PG/CliiOCtXYI6qpzI28icy3usIvzk9IaZRNedb+4HGhcmpoA61j5NaVow81S6pQo2KBaIBQw== dependencies: - "@abp/jquery" "~9.0.0" - "@abp/prismjs" "~9.0.0" + "@abp/jquery" "~9.0.3" + "@abp/prismjs" "~9.0.3" -"@abp/uppy@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/uppy/-/uppy-9.0.0.tgz#4def1e889093a4299f1c2445a87793cb3da2d4d7" - integrity sha512-Y9IbGu/t3rJefF7FSrsiHs9V+C/lAMtolivPwKhnOzxmUtoxbyc0O2W2uExZEihevbnk3PWe7dkFY5mZ30i6BA== +"@abp/uppy@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/uppy/-/uppy-9.0.3.tgz#d1013a4b382d181e473487d712f9db78cf0b6f7b" + integrity sha512-sZXEadlRBZCTO+Vbcd2TyzrjWV311DC045X91GkQmeu0Yqc6M5QE3TEo00FfutZovJA4MblY0EXktmgSIkoZkQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" uppy "^4.4.1" -"@abp/utils@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-9.0.0.tgz#2264fd83105b061860edd98dbe0efdfe55803ed5" - integrity sha512-/SB4VYk3Av+ltXv6ovkFHG4iWiECLLitaYWcEHxVAO80ufNjrNnWpHFpXHP8u/RpXIp/CoNiHLr6j5TkZUTZjw== +"@abp/utils@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-9.0.3.tgz#02dfd981d210be8229d65f802f0721b42c9e17d7" + integrity sha512-45sQQ6IV3cB9KAIVtRn3IH1wVr9DfM0PMFiwVt3F+wKwNBAPsKNcMPt4/EhZCNY3IMaEseJ7K/lxt33O3sURuA== dependencies: just-compare "^2.3.0" diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/IMenuItemAdminAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/IMenuItemAdminAppService.cs index a8908129d2..f97fc69ee1 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/IMenuItemAdminAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/IMenuItemAdminAppService.cs @@ -21,4 +21,6 @@ public interface IMenuItemAdminAppService : IApplicationService Task MoveMenuItemAsync(Guid id, MenuItemMoveInput input); Task> GetPageLookupAsync(PageLookupInputDto input); + + Task> GetPermissionLookupAsync(PermissionLookupInputDto inputDto); } diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/MenuItemCreateInput.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/MenuItemCreateInput.cs index f5ec9c6444..3e2f78aaba 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/MenuItemCreateInput.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/MenuItemCreateInput.cs @@ -27,4 +27,6 @@ public class MenuItemCreateInput : ExtensibleObject public string CssClass { get; set; } public Guid? PageId { get; set; } + + public string RequiredPermissionName { get; set; } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/MenuItemUpdateInput.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/MenuItemUpdateInput.cs index 8767a6192a..7a5ebc76b5 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/MenuItemUpdateInput.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/MenuItemUpdateInput.cs @@ -24,6 +24,8 @@ public class MenuItemUpdateInput : ExtensibleObject, IHasConcurrencyStamp public string CssClass { get; set; } public Guid? PageId { get; set; } + + public string RequiredPermissionName { get; set; } public string ConcurrencyStamp { get; set; } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/PermissionLookupDto.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/PermissionLookupDto.cs new file mode 100644 index 0000000000..37000242e7 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/PermissionLookupDto.cs @@ -0,0 +1,8 @@ +namespace Volo.CmsKit.Admin.Menus; + +public class PermissionLookupDto +{ + public string Name { get; set; } + + public string DisplayName { get; set; } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/PermissionLookupInputDto.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/PermissionLookupInputDto.cs new file mode 100644 index 0000000000..bb4b639da6 --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Menus/PermissionLookupInputDto.cs @@ -0,0 +1,6 @@ +namespace Volo.CmsKit.Admin.Menus; + +public class PermissionLookupInputDto +{ + public string Filter { get; set; } +} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Blogs/BlogPostAdminAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Blogs/BlogPostAdminAppService.cs index adace8bd9a..dd39cddf88 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Blogs/BlogPostAdminAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Blogs/BlogPostAdminAppService.cs @@ -111,9 +111,15 @@ public class BlogPostAdminAppService : CmsKitAppServiceBase, IBlogPostAdminAppSe { var blogs = (await BlogRepository.GetListAsync()).ToDictionary(x => x.Id); - var blogPosts = await BlogPostRepository.GetListAsync(input.Filter, input.BlogId, input.AuthorId, input.TagId, + var blogPosts = await BlogPostRepository.GetListAsync( + input.Filter, + input.BlogId, + input.AuthorId, + input.TagId, statusFilter: input.Status, - input.MaxResultCount, input.SkipCount, input.Sorting); + maxResultCount: input.MaxResultCount, + skipCount: input.SkipCount, + sorting: input.Sorting); var count = await BlogPostRepository.GetCountAsync(input.Filter, input.BlogId, input.AuthorId, tagId: input.TagId); diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Menus/MenuItemAdminAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Menus/MenuItemAdminAppService.cs index 65952c1a15..13abdf9607 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Menus/MenuItemAdminAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Menus/MenuItemAdminAppService.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Dtos; +using Volo.Abp.Authorization.Permissions; using Volo.Abp.Data; using Volo.Abp.Features; using Volo.Abp.GlobalFeatures; @@ -23,15 +25,18 @@ public class MenuItemAdminAppService : CmsKitAdminAppServiceBase, IMenuItemAdmin protected MenuItemManager MenuManager { get; } protected IMenuItemRepository MenuItemRepository { get; } protected IPageRepository PageRepository { get; } + protected IPermissionDefinitionManager PermissionDefinitionManager { get; } public MenuItemAdminAppService( MenuItemManager menuManager, IMenuItemRepository menuRepository, - IPageRepository pageRepository) + IPageRepository pageRepository, + IPermissionDefinitionManager permissionDefinitionManager) { MenuManager = menuManager; MenuItemRepository = menuRepository; PageRepository = pageRepository; + PermissionDefinitionManager = permissionDefinitionManager; } public virtual async Task> GetListAsync() @@ -70,7 +75,8 @@ public class MenuItemAdminAppService : CmsKitAdminAppServiceBase, IMenuItemAdmin input.Target, input.ElementId, input.CssClass, - CurrentTenant.Id + CurrentTenant.Id, + input.RequiredPermissionName ); if (input.PageId.HasValue) @@ -103,6 +109,7 @@ public class MenuItemAdminAppService : CmsKitAdminAppServiceBase, IMenuItemAdmin menuItem.Target = input.Target; menuItem.ElementId = input.ElementId; menuItem.CssClass = input.CssClass; + menuItem.RequiredPermissionName = input.RequiredPermissionName; menuItem.SetConcurrencyStampIfNotNull(input.ConcurrencyStamp); input.MapExtraPropertiesTo(menuItem); await MenuItemRepository.UpdateAsync(menuItem); @@ -138,4 +145,21 @@ public class MenuItemAdminAppService : CmsKitAdminAppServiceBase, IMenuItemAdmin ObjectMapper.Map, List>(pages) ); } + + public virtual async Task> GetPermissionLookupAsync(PermissionLookupInputDto inputDto) + { + var permissions = await PermissionDefinitionManager.GetPermissionsAsync(); + + var permissionLookupDtos= permissions + .WhereIf(!inputDto.Filter.IsNullOrWhiteSpace(), p => p.Name.Contains(inputDto.Filter, StringComparison.OrdinalIgnoreCase)) + .Select(x => new PermissionLookupDto + { + Name = x.Name, + DisplayName = x.DisplayName.Localize(StringLocalizerFactory) + }).ToList(); + + return new ListResultDto( + permissionLookupDtos + ); + } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/ClientProxies/Volo/CmsKit/Admin/Menus/MenuItemAdminClientProxy.Generated.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/ClientProxies/Volo/CmsKit/Admin/Menus/MenuItemAdminClientProxy.Generated.cs index 70daab4da0..c707d6ad64 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/ClientProxies/Volo/CmsKit/Admin/Menus/MenuItemAdminClientProxy.Generated.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/ClientProxies/Volo/CmsKit/Admin/Menus/MenuItemAdminClientProxy.Generated.cs @@ -72,4 +72,12 @@ public partial class MenuItemAdminClientProxy : ClientProxyBase> GetPermissionLookupAsync(PermissionLookupInputDto inputDto) + { + return await RequestAsync>(nameof(GetPermissionLookupAsync), new ClientProxyRequestTypeValue + { + { typeof(PermissionLookupInputDto), inputDto } + }); + } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/ClientProxies/cms-kit-admin-generate-proxy.json b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/ClientProxies/cms-kit-admin-generate-proxy.json index 8f0e7e677d..9e5f9a843e 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/ClientProxies/cms-kit-admin-generate-proxy.json +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/ClientProxies/cms-kit-admin-generate-proxy.json @@ -40,7 +40,7 @@ "typeAsString": "System.Nullable`1[[System.Guid, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Private.CoreLib", "type": "System.Guid?", "typeSimple": "string?", - "isOptional": false, + "isOptional": true, "defaultValue": null } ], @@ -2167,6 +2167,23 @@ "type": "Volo.Abp.Application.Dtos.PagedResultDto", "typeSimple": "Volo.Abp.Application.Dtos.PagedResultDto" } + }, + { + "name": "GetPermissionLookupAsync", + "parametersOnMethod": [ + { + "name": "inputDto", + "typeAsString": "Volo.CmsKit.Admin.Menus.PermissionLookupInputDto, Volo.CmsKit.Admin.Application.Contracts", + "type": "Volo.CmsKit.Admin.Menus.PermissionLookupInputDto", + "typeSimple": "Volo.CmsKit.Admin.Menus.PermissionLookupInputDto", + "isOptional": false, + "defaultValue": null + } + ], + "returnValue": { + "type": "Volo.Abp.Application.Dtos.ListResultDto", + "typeSimple": "Volo.Abp.Application.Dtos.ListResultDto" + } } ] } @@ -2484,6 +2501,43 @@ }, "allowAnonymous": false, "implementFrom": "Volo.CmsKit.Admin.Menus.IMenuItemAdminAppService" + }, + "GetPermissionLookupAsyncByInputDto": { + "uniqueName": "GetPermissionLookupAsyncByInputDto", + "name": "GetPermissionLookupAsync", + "httpMethod": "GET", + "url": "api/cms-kit-admin/menu-items/lookup/permissions", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "inputDto", + "typeAsString": "Volo.CmsKit.Admin.Menus.PermissionLookupInputDto, Volo.CmsKit.Admin.Application.Contracts", + "type": "Volo.CmsKit.Admin.Menus.PermissionLookupInputDto", + "typeSimple": "Volo.CmsKit.Admin.Menus.PermissionLookupInputDto", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "inputDto", + "name": "Filter", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "inputDto" + } + ], + "returnValue": { + "type": "Volo.Abp.Application.Dtos.ListResultDto", + "typeSimple": "Volo.Abp.Application.Dtos.ListResultDto" + }, + "allowAnonymous": false, + "implementFrom": "Volo.CmsKit.Admin.Menus.IMenuItemAdminAppService" } } }, diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Menus/MenuItemAdminController.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Menus/MenuItemAdminController.cs index 8540e5306e..e3cac24f54 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Menus/MenuItemAdminController.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Menus/MenuItemAdminController.cs @@ -78,4 +78,11 @@ public class MenuItemAdminController : CmsKitAdminController, IMenuItemAdminAppS { return MenuItemAdminAppService.GetPageLookupAsync(input); } + + [HttpGet] + [Route("lookup/permissions")] + public Task> GetPermissionLookupAsync(PermissionLookupInputDto inputDto) + { + return MenuItemAdminAppService.GetPermissionLookupAsync(inputDto); + } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/CmsKitAdminWebModule.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/CmsKitAdminWebModule.cs index 70c2b575f4..45cbbd818d 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/CmsKitAdminWebModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/CmsKitAdminWebModule.cs @@ -13,6 +13,7 @@ using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.ObjectExtending; using Volo.Abp.ObjectExtending.Modularity; +using Volo.Abp.SettingManagement.Web; using Volo.Abp.SettingManagement.Web.Pages.SettingManagement; using Volo.Abp.Threading; using Volo.Abp.UI.Navigation; @@ -29,7 +30,8 @@ namespace Volo.CmsKit.Admin.Web; [DependsOn( typeof(CmsKitAdminApplicationContractsModule), - typeof(CmsKitCommonWebModule) + typeof(CmsKitCommonWebModule), + typeof(AbpSettingManagementWebModule) )] public class CmsKitAdminWebModule : AbpModule { diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/CreateModal.cshtml b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/CreateModal.cshtml index a79912e51c..8bce5cf45e 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/CreateModal.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/CreateModal.cshtml @@ -60,6 +60,11 @@ +
+ + +
+
diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/CreateModal.cshtml.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/CreateModal.cshtml.cs index eaab7e8903..49a1354b43 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/CreateModal.cshtml.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/CreateModal.cshtml.cs @@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using AutoMapper; using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Authorization.Permissions; using Volo.Abp.Features; using Volo.Abp.GlobalFeatures; using Volo.Abp.ObjectExtending; @@ -32,7 +33,6 @@ public class CreateModalModel : CmsKitAdminPageModel public virtual async Task OnGetAsync(Guid? parentId) { ViewModel.ParentId = parentId; - IsPageFeatureEnabled = GlobalFeatureManager.Instance.IsEnabled() && await FeatureChecker.IsEnabledAsync(CmsKitFeatures.PageEnable); } @@ -72,6 +72,8 @@ public class CreateModalModel : CmsKitAdminPageModel public string ElementId { get; set; } public string CssClass { get; set; } + + public string RequiredPermissionName { get; set; } } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/UpdateModal.cshtml b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/UpdateModal.cshtml index a190808560..48e37b6468 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/UpdateModal.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/UpdateModal.cshtml @@ -64,6 +64,30 @@ } +
+ + +
+
diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/UpdateModal.cshtml.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/UpdateModal.cshtml.cs index b64427e516..9cb5af1bba 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/UpdateModal.cshtml.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/UpdateModal.cshtml.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using AutoMapper; @@ -28,6 +29,8 @@ public class UpdateModalModel : CmsKitAdminPageModel public Guid Id { get; set; } public bool IsPageFeatureEnabled { get; set; } + + public IReadOnlyList Permissions { get; set; } public UpdateModalModel(IMenuItemAdminAppService menuAdminAppService, IFeatureChecker featureChecker) { @@ -39,7 +42,7 @@ public class UpdateModalModel : CmsKitAdminPageModel public async Task OnGetAsync() { var menuItemDto = await MenuAdminAppService.GetAsync(Id); - + Permissions = (await MenuAdminAppService.GetPermissionLookupAsync(new PermissionLookupInputDto())).Items; IsPageFeatureEnabled = GlobalFeatureManager.Instance.IsEnabled() && await FeatureChecker.IsEnabledAsync(CmsKitFeatures.PageEnable); @@ -76,6 +79,8 @@ public class UpdateModalModel : CmsKitAdminPageModel public Guid? PageId { get; set; } public string? PageTitle { get; set; } + + public string RequiredPermissionName { get; set; } [HiddenInput] public string ConcurrencyStamp { get; set; } diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/createModal.js b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/createModal.js index 0c4103c6f8..3459328956 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/createModal.js +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/createModal.js @@ -8,6 +8,7 @@ $(function () { var $url = $('#ViewModel_Url'); var $displayName = $('#ViewModel_DisplayName'); var $menuItemForm = $('#menu-item-form'); + var $selectRequiredPermission = $('#requiredPermissionName'); $pageId.on('change', function (params) { $url.prop('disabled', $pageId.val()); @@ -20,6 +21,51 @@ $(function () { } }) + function initSelectRequiredPermission(){ + function formatDisplayName(item) { + if (!item.id) { + return item.text; + } + var $displayName = $(`${item.id}`); + $displayName.tooltip(); + return $displayName; + } + + $selectRequiredPermission.select2({ + ajax:{ + url: '/api/cms-kit-admin/menu-items/lookup/permissions', + delay: 250, + dataType: "json", + data: function (params) { + let query = {}; + query["filter"] = params.term; + return query; + }, + processResults: function (data) { + let retVal = []; + let items = data["items"]; + $('body').tooltip('dispose'); + items.forEach(function (item, index) { + retVal.push({ + id: item["name"], + text: item["name"], + displayName: item["displayName"] + }) + }); + return { + results: retVal + }; + } + }, + templateResult: formatDisplayName, + width: '100%', + dropdownParent: $('#menu-create-modal'), + language: abp.localization.currentCulture.cultureName + }); + } + + initSelectRequiredPermission(); + $menuItemForm.on('submit', function (e) { $('[href="#url"]').tab('show'); }); diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/updateModal.js b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/updateModal.js index 173c6459e5..eb15bf7733 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/updateModal.js +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Menus/MenuItems/updateModal.js @@ -8,6 +8,7 @@ $(function () { var $url = $('#ViewModel_Url'); var $displayName = $('#ViewModel_DisplayName'); var $menuItemForm = $('#menu-item-form'); + var $selectRequiredPermission = $('#requiredPermissionName'); $pageId.on('change', function (params) { $url.prop('disabled', $pageId.val()); @@ -21,6 +22,52 @@ $(function () { $pageId.trigger('change'); + function initSelectRequiredPermission(){ + function formatDisplayName(item) { + if (!item.id) { + return item.text; + } + var $displayName = $(`${item.id}`); + $displayName.tooltip(); + return $displayName; + } + + $selectRequiredPermission.select2({ + ajax:{ + url: '/api/cms-kit-admin/menu-items/lookup/permissions', + delay: 250, + dataType: "json", + data: function (params) { + let query = {}; + query["filter"] = params.term; + return query; + }, + processResults: function (data) { + let retVal = []; + let items = data["items"]; + $('body').tooltip('dispose'); + items.forEach(function (item, index) { + retVal.push({ + id: item["name"], + text: item["name"], + displayName: item["displayName"] + }) + }); + return { + results: retVal + }; + } + }, + templateResult: formatDisplayName, + width: '100%', + dropdownParent: $('#menu-update-modal'), + language: abp.localization.currentCulture.cultureName + }); + } + + initSelectRequiredPermission(); + $('[data-ts-toggle="tooltip"]').tooltip() + $menuItemForm.on('submit', function (e) { $('[href="#url"]').tab('show'); }); diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Volo.CmsKit.Admin.Web.abppkg.analyze.json b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Volo.CmsKit.Admin.Web.abppkg.analyze.json index b8b9d3e51e..b42079c218 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Volo.CmsKit.Admin.Web.abppkg.analyze.json +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Volo.CmsKit.Admin.Web.abppkg.analyze.json @@ -14,6 +14,11 @@ "declaringAssemblyName": "Volo.CmsKit.Common.Web", "namespace": "Volo.CmsKit.Web", "name": "CmsKitCommonWebModule" + }, + { + "declaringAssemblyName": "Volo.Abp.SettingManagement.Web", + "namespace": "Volo.Abp.SettingManagement.Web", + "name": "AbpSettingManagementWebModule" } ], "implementingInterfaces": [ diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Volo.CmsKit.Admin.Web.csproj b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Volo.CmsKit.Admin.Web.csproj index 9323e3044e..6dbaea3eb9 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Volo.CmsKit.Admin.Web.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Volo.CmsKit.Admin.Web.csproj @@ -15,6 +15,8 @@ + + diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Application.Contracts/Volo/CmsKit/Menus/MenuItemDto.cs b/modules/cms-kit/src/Volo.CmsKit.Common.Application.Contracts/Volo/CmsKit/Menus/MenuItemDto.cs index 3d785cb9f1..b913f8b49e 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Common.Application.Contracts/Volo/CmsKit/Menus/MenuItemDto.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Common.Application.Contracts/Volo/CmsKit/Menus/MenuItemDto.cs @@ -26,6 +26,8 @@ public class MenuItemDto : ExtensibleAuditedEntityDto, IHasConcurrencyStam public string CssClass { get; set; } public Guid? PageId { get; set; } + + public string RequiredPermissionName { get; set; } public string ConcurrencyStamp { get; set; } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Web/CmsKitCommonWebModule.cs b/modules/cms-kit/src/Volo.CmsKit.Common.Web/CmsKitCommonWebModule.cs index 5f27f785f8..e8a30a75bc 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Common.Web/CmsKitCommonWebModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Common.Web/CmsKitCommonWebModule.cs @@ -7,15 +7,13 @@ using Volo.CmsKit.Reactions; using Volo.CmsKit.Web.Icons; using Markdig; using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.SettingManagement.Web; namespace Volo.CmsKit.Web; [DependsOn( typeof(AbpAspNetCoreMvcUiThemeSharedModule), typeof(CmsKitCommonApplicationContractsModule), - typeof(AbpAutoMapperModule), - typeof(AbpSettingManagementWebModule) + typeof(AbpAutoMapperModule) )] public class CmsKitCommonWebModule : AbpModule { diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Web/Volo.CmsKit.Common.Web.abppkg.analyze.json b/modules/cms-kit/src/Volo.CmsKit.Common.Web/Volo.CmsKit.Common.Web.abppkg.analyze.json index 56c251adff..6b69cfe938 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Common.Web/Volo.CmsKit.Common.Web.abppkg.analyze.json +++ b/modules/cms-kit/src/Volo.CmsKit.Common.Web/Volo.CmsKit.Common.Web.abppkg.analyze.json @@ -19,11 +19,6 @@ "declaringAssemblyName": "Volo.Abp.AutoMapper", "namespace": "Volo.Abp.AutoMapper", "name": "AbpAutoMapperModule" - }, - { - "declaringAssemblyName": "Volo.Abp.SettingManagement.Web", - "namespace": "Volo.Abp.SettingManagement.Web", - "name": "AbpSettingManagementWebModule" } ], "implementingInterfaces": [ diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Web/Volo.CmsKit.Common.Web.csproj b/modules/cms-kit/src/Volo.CmsKit.Common.Web/Volo.CmsKit.Common.Web.csproj index f051498795..8e8fe81033 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Common.Web/Volo.CmsKit.Common.Web.csproj +++ b/modules/cms-kit/src/Volo.CmsKit.Common.Web/Volo.CmsKit.Common.Web.csproj @@ -16,7 +16,6 @@ - diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ar.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ar.json index 517e3f4c8e..e0938f4285 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ar.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ar.json @@ -15,6 +15,7 @@ "CmsKit.Ratings": "التقييمات", "CmsKit.Reactions": "تفاعلات", "CmsKit.Tags": "العلامات", + "CmsKit.MarkedItems": "العناصر المميزة", "CmsKit:0002": "المحتوى موجود بالفعل!", "CmsKit:0003": "الكيان {0} غير قابل للعلامة.", "CmsKit:Blog:0001": "slug المحدد ({Slug}) موجود بالفعل!", @@ -28,6 +29,9 @@ "CmsKit:MarkedItem:0001": "لا يمكن أن تمييز الكيان {EntityType}.", "CmsKit:MarkedItem:0002": "لم يتم العثور على تعريف لنوع الكيان '{EntityType}'.", "CmsKit:MarkedItem:0003": "يوجد بالفعل تعريف لنوع الكيان '{EntityType}'. يجب أن يكون لكل نوع كيان تعريف واحد فقط.", + "ToggleFavorite": "تفضيل/إلغاء", + "FavoritesFilterMessage": "يرجى تسجيل الدخول لتصفية المفضلات الخاصة بك", + "FilterOnFavorites": "تصفية المفضلات", "CmsKit:Tag:0002": "الكيان غير قابل للوسم!", "CommentAuthorizationExceptionMessage": "هذه التعليقات غير مسموح بها للعرض العام.", "CommentDeletionConfirmationMessage": "سيتم حذف هذا التعليق وجميع الردود!", @@ -254,6 +258,7 @@ "ChooseAnActionForBlog": "اختر إجراءً للمدونة", "AssignBlogPostsToOtherBlog": "تعيين مشاركات المدونة إلى مدونة أخرى", "SelectAnBlogToAssign": "حدد مدونة لتعيين مشاركات المدونة إليها", - "DeleteAllBlogPostsOfThisBlog": "حذف جميع مشاركات المدونة" + "DeleteAllBlogPostsOfThisBlog": "حذف جميع مشاركات المدونة", + "RequiredPermissionName": "اسم الإذن المطلوب", } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/cs.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/cs.json index 946336668f..b0c4863bdf 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/cs.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/cs.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "Vyberte akci pro blog", "AssignBlogPostsToOtherBlog": "Přiřaďte blogové příspěvky k jinému blogu", "SelectAnBlogToAssign": "Vyberte blog, ke kterému chcete přiřadit blogové příspěvky", - "DeleteAllBlogPostsOfThisBlog": "Smazat všechny blogové příspěvky tohoto blogu" + "DeleteAllBlogPostsOfThisBlog": "Smazat všechny blogové příspěvky tohoto blogu", + "RequiredPermissionName": "Je vyžadováno oprávnění" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/de-DE.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/de-DE.json index 49b5f96ff5..767c47497d 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/de-DE.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/de-DE.json @@ -165,6 +165,7 @@ "ChooseAnActionForBlog": "Wählen Sie eine Aktion für den Blog", "AssignBlogPostsToOtherBlog": "Blogbeiträge einem anderen Blog zuweisen", "SelectAnBlogToAssign": "Wählen Sie einen Blog aus, um Blogbeiträge zuzuweisen", - "DeleteAllBlogPostsOfThisBlog": "Alle Blogbeiträge dieses Blogs löschen" + "DeleteAllBlogPostsOfThisBlog": "Alle Blogbeiträge dieses Blogs löschen", + "RequiredPermissionName": "Erforderlicher Berechtigungsname" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/de.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/de.json index e950983d9c..6ee49f4675 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/de.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/de.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "Wählen Sie eine Aktion für den Blog", "AssignBlogPostsToOtherBlog": "Blogbeiträge einem anderen Blog zuweisen", "SelectAnBlogToAssign": "Wählen Sie einen Blog aus, um Blogbeiträge zuzuweisen", - "DeleteAllBlogPostsOfThisBlog": "Alle Blogbeiträge dieses Blogs löschen" + "DeleteAllBlogPostsOfThisBlog": "Alle Blogbeiträge dieses Blogs löschen", + "RequiredPermissionName": "Erforderlicher Berechtigungsname" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/el.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/el.json index 5c3bd58461..af12ccfdf8 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/el.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/el.json @@ -188,6 +188,7 @@ "ChooseAnActionForBlog": "Επιλέξτε μια ενέργεια για το ιστολόγιο", "AssignBlogPostsToOtherBlog": "Ανάθεση αναρτήσεων ιστολογίου σε άλλο ιστολόγιο", "SelectAnBlogToAssign": "Επιλέξτε ένα ιστολόγιο για να αναθέσετε αναρτήσεις ιστολογίου", - "DeleteAllBlogPostsOfThisBlog": "Διαγραφή όλων των αναρτήσεων ιστολογίου αυτού του ιστολογίου" + "DeleteAllBlogPostsOfThisBlog": "Διαγραφή όλων των αναρτήσεων ιστολογίου αυτού του ιστολογίου", + "RequiredPermissionName": "Απαιτούμενο όνομα δικαιώματος" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en-GB.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en-GB.json index b8b9060f45..76125e555a 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en-GB.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en-GB.json @@ -32,6 +32,7 @@ "ChooseAnActionForBlog": "Choose an action for the blog", "AssignBlogPostsToOtherBlog": "Assign blog posts to another blog", "SelectAnBlogToAssign": "Select a blog to assign", - "DeleteAllBlogPostsOfThisBlog": "Delete all blog posts of this blog" + "DeleteAllBlogPostsOfThisBlog": "Delete all blog posts of this blog", + "RequiredPermissionName": "Required permission name" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json index 26cea2c98a..da43ebcc17 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json @@ -15,6 +15,7 @@ "CmsKit.Ratings": "Ratings", "CmsKit.Reactions": "Reactions", "CmsKit.Tags": "Tags", + "CmsKit.MarkedItems": "Marked Items", "CmsKit:0002": "Content already exists!", "CmsKit:0003": "The entity {0} is not taggable.", "CmsKit:Blog:0001": "The given slug ({Slug}) already exists!", @@ -27,6 +28,9 @@ "CmsKit:Reaction:0001": "The entity {EntityType} can't have reactions.", "CmsKit:Tag:0002": "The entity is not taggable!", "CmsKit:MarkedItem:ToggleConfirmation": "Are you sure you want to toggle the marked item?", + "ToggleFavorite": "Add/Remove Favorite", + "FavoritesFilterMessage": "Please log in to filter your favorites", + "FilterOnFavorites": "Filter On Favorites", "CommentAuthorizationExceptionMessage": "Those comments are not allowed for public display.", "CmsKit:Modals:Login": "Login", "CmsKit:Modals:LoginModalDefaultMessage": "Please login to continue!", @@ -272,6 +276,7 @@ "ChooseAnActionForBlog": "Choose an action for the blog", "AssignBlogPostsToOtherBlog": "Assign blog posts to another blog", "SelectAnBlogToAssign": "Select a blog to assign", - "DeleteAllBlogPostsOfThisBlog": "Delete all blog posts of this blog" + "DeleteAllBlogPostsOfThisBlog": "Delete all blog posts of this blog", + "RequiredPermissionName": "Required permission name" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/es.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/es.json index ffe5b410be..985ccd10d5 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/es.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/es.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "Elija una acción para el blog", "AssignBlogPostsToOtherBlog": "Asignar publicaciones de blog a otro blog", "SelectAnBlogToAssign": "Seleccione un blog para asignar publicaciones de blog", - "DeleteAllBlogPostsOfThisBlog": "Eliminar todas las publicaciones de blog de este blog" + "DeleteAllBlogPostsOfThisBlog": "Eliminar todas las publicaciones de blog de este blog", + "RequiredPermissionName": "Nombre de permiso requerido" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/fa.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/fa.json index fa6695709d..da26b16bd7 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/fa.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/fa.json @@ -187,6 +187,7 @@ "ChooseAnActionForBlog": "یک عمل برای وبلاگ انتخاب کنید", "AssignBlogPostsToOtherBlog": "پست های وبلاگ را به وبلاگ دیگری اختصاص دهید", "SelectAnBlogToAssign": "یک وبلاگ برای اختصاص دادن انتخاب کنید", - "DeleteAllBlogPostsOfThisBlog": "تمام پست های وبلاگ این وبلاگ را حذف کنید" + "DeleteAllBlogPostsOfThisBlog": "تمام پست های وبلاگ این وبلاگ را حذف کنید", + "RequiredPermissionName": "نام مجوز مورد نیاز" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/fi.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/fi.json index f13dc7620c..637672dbdd 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/fi.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/fi.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "Valitse toiminto blogille", "AssignBlogPostsToOtherBlog": "Määritä blogiviestit toiseen blogiin", "SelectAnBlogToAssign": "Valitse blogi, johon haluat määrittää", - "DeleteAllBlogPostsOfThisBlog": "Poista tämän blogin kaikki blogiviestit" + "DeleteAllBlogPostsOfThisBlog": "Poista tämän blogin kaikki blogiviestit", + "RequiredPermissionName": "Tarvittava lupa" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/fr.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/fr.json index f2828aa40e..805a9eeed1 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/fr.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/fr.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "Choisissez une action pour le blog", "AssignBlogPostsToOtherBlog": "Attribuer des articles de blog à un autre blog", "SelectAnBlogToAssign": "Sélectionnez un blog à attribuer", - "DeleteAllBlogPostsOfThisBlog": "Supprimer tous les articles de blog de ce blog" + "DeleteAllBlogPostsOfThisBlog": "Supprimer tous les articles de blog de ce blog", + "RequiredPermissionName": "Nom de permission requis" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/hi.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/hi.json index ad2dbc18d9..e5c0f008f5 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/hi.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/hi.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "ब्लॉग के लिए कोई कार्रवाई चुनें", "AssignBlogPostsToOtherBlog": "अन्य ब्लॉग को ब्लॉग पोस्ट असाइन करें", "SelectAnBlogToAssign": "असाइन करने के लिए एक ब्लॉग चुनें", - "DeleteAllBlogPostsOfThisBlog": "इस ब्लॉग के सभी ब्लॉग पोस्ट हटाएं" + "DeleteAllBlogPostsOfThisBlog": "इस ब्लॉग के सभी ब्लॉग पोस्ट हटाएं", + "RequiredPermissionName": "आवश्यक अनुमति नाम" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/hr.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/hr.json index 8e35745e21..8530b5bfc9 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/hr.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/hr.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "Odaberite radnju za blog", "AssignBlogPostsToOtherBlog": "Dodijelite postove na blogu drugom blogu", "SelectAnBlogToAssign": "Odaberite blog za dodjelu", - "DeleteAllBlogPostsOfThisBlog": "Izbrišite sve postove na blogu" + "DeleteAllBlogPostsOfThisBlog": "Izbrišite sve postove na blogu", + "RequiredPermissionName": "Potrebno ime dozvole" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/hu.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/hu.json index 9a3e4fb64e..52fedf88cd 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/hu.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/hu.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "Válasszon egy műveletet a bloghoz", "AssignBlogPostsToOtherBlog": "Blogbejegyzések hozzárendelése egy másik bloghoz", "SelectAnBlogToAssign": "Válasszon egy blogot a hozzárendeléshez", - "DeleteAllBlogPostsOfThisBlog": "Ez a művelet törli az összes blogbejegyzést ebből a blogból. Biztos vagy benne?" + "DeleteAllBlogPostsOfThisBlog": "Ez a művelet törli az összes blogbejegyzést ebből a blogból. Biztos vagy benne?", + "RequiredPermissionName": "Szükséges engedély neve" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/is.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/is.json index bf02d1ff68..cfc4032dcc 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/is.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/is.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "Veldu aðgerð fyrir blogg", "AssignBlogPostsToOtherBlog": "Úthluta bloggfærslum til annars bloggs", "SelectAnBlogToAssign": "Veldu blogg til að úthluta", - "DeleteAllBlogPostsOfThisBlog": "Eyða öllum bloggfærslum þessa bloggs" + "DeleteAllBlogPostsOfThisBlog": "Eyða öllum bloggfærslum þessa bloggs", + "RequiredPermissionName": "Nafn á nauðsynlegri leyfi" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/it.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/it.json index 7506c439f3..5e3e2d0937 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/it.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/it.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "Scegli un'azione per il blog", "AssignBlogPostsToOtherBlog": "Assegna i post del blog ad un altro blog", "SelectAnBlogToAssign": "Seleziona un blog a cui assegnare i post del blog", - "DeleteAllBlogPostsOfThisBlog": "Elimina tutti i post del blog di questo blog" + "DeleteAllBlogPostsOfThisBlog": "Elimina tutti i post del blog di questo blog", + "RequiredPermissionName": "Nome del permesso richiesto" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/nl.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/nl.json index 1d367dfa5c..8e4c97af34 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/nl.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/nl.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "Kies een actie voor de blog", "AssignBlogPostsToOtherBlog": "Wijs blogberichten toe aan een andere blog", "SelectAnBlogToAssign": "Selecteer een blog om toe te wijzen", - "DeleteAllBlogPostsOfThisBlog": "Verwijder alle blogberichten van deze blog" + "DeleteAllBlogPostsOfThisBlog": "Verwijder alle blogberichten van deze blog", + "RequiredPermissionName": "Vereiste toestemming" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/pl-PL.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/pl-PL.json index 0b084f585c..6ac083c3bc 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/pl-PL.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/pl-PL.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "Wybierz akcję dla bloga", "AssignBlogPostsToOtherBlog": "Przypisz posty na blogu do innego bloga", "SelectAnBlogToAssign": "Wybierz blog, do którego chcesz przypisać posty na blogu", - "DeleteAllBlogPostsOfThisBlog": "Usuń wszystkie posty na blogu tego bloga" + "DeleteAllBlogPostsOfThisBlog": "Usuń wszystkie posty na blogu tego bloga", + "RequiredPermissionName": "Wymagana nazwa uprawnienia" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/pt-BR.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/pt-BR.json index 3991bc0595..d557fca98e 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/pt-BR.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/pt-BR.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "Escolha uma ação para o blog", "AssignBlogPostsToOtherBlog": "Atribuir postagens de blog a outro blog", "SelectAnBlogToAssign": "Selecione um blog para atribuir", - "DeleteAllBlogPostsOfThisBlog": "Excluir todas as postagens de blog deste blog" + "DeleteAllBlogPostsOfThisBlog": "Excluir todas as postagens de blog deste blog", + "RequiredPermissionName": "Nome da permissão necessária" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ro-RO.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ro-RO.json index c6b486a11a..f37b7a0161 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ro-RO.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ro-RO.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "Alegeţi o acţiune pentru blog", "AssignBlogPostsToOtherBlog": "Atribuiţi postările de blog la alt blog", "SelectAnBlogToAssign": "Selectaţi un blog pentru a atribui postările de blog", - "DeleteAllBlogPostsOfThisBlog": "Ştergeţi toate postările de blog ale acestui blog" + "DeleteAllBlogPostsOfThisBlog": "Ştergeţi toate postările de blog ale acestui blog", + "RequiredPermissionName": "Numele permisiunii necesare" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ru.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ru.json index c23671c843..7ff7851e73 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ru.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ru.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "Выберите действие для блога", "AssignBlogPostsToOtherBlog": "Назначить сообщения в блоге другому блогу", "SelectAnBlogToAssign": "Выберите блог для назначения", - "DeleteAllBlogPostsOfThisBlog": "Удалить все сообщения в блоге этого блога" + "DeleteAllBlogPostsOfThisBlog": "Удалить все сообщения в блоге этого блога", + "RequiredPermissionName": "Имя требуемого разрешения" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/sk.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/sk.json index a1ff2cfb01..6fadf538f0 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/sk.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/sk.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "Vyberte akciu pre blog", "AssignBlogPostsToOtherBlog": "Priradiť blogové príspevky k inému blogu", "SelectAnBlogToAssign": "Vyberte blog, na ktorý chcete priradiť blogové príspevky", - "DeleteAllBlogPostsOfThisBlog": "Zmazať všetky blogové príspevky tohto blogu" + "DeleteAllBlogPostsOfThisBlog": "Zmazať všetky blogové príspevky tohto blogu", + "RequiredPermissionName": "Požadovaný názov oprávnenia" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/sl.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/sl.json index b4d525c3e4..57a7c22f67 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/sl.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/sl.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "Izberite dejanje za blog", "AssignBlogPostsToOtherBlog": "Dodeli objave v blogu drugemu blogu", "SelectAnBlogToAssign": "Izberite blog, ki mu želite dodeliti objave", - "DeleteAllBlogPostsOfThisBlog": "Izbriši vse objave v tem blogu" + "DeleteAllBlogPostsOfThisBlog": "Izbriši vse objave v tem blogu", + "RequiredPermissionName": "Ime zahtevane dovoljenja" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/sv.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/sv.json index f9604657ce..4d63eb822f 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/sv.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/sv.json @@ -258,6 +258,7 @@ "ChooseAnActionForBlog": "Välj en åtgärd för bloggen", "AssignBlogPostsToOtherBlog": "Tilldela blogginlägg till en annan blogg", "SelectAnBlogToAssign": "Välj en blogg att tilldela", - "DeleteAllBlogPostsOfThisBlog": "Radera alla blogginlägg i denna blogg" + "DeleteAllBlogPostsOfThisBlog": "Radera alla blogginlägg i denna blogg", + "RequiredPermissionName": "Nödvändigt behörighetsnamn" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json index 0e5d6d7c11..3e1166133c 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json @@ -222,6 +222,7 @@ "ChooseAnActionForBlog": "Blog için bir eylem seçin", "AssignBlogPostsToOtherBlog": "Diğer bloglara blog yazıları atayın", "SelectAnBlogToAssign": "Atanacak bir blog seçin", - "DeleteAllBlogPostsOfThisBlog": "Bu blogun tüm blog yazılarını sil" + "DeleteAllBlogPostsOfThisBlog": "Bu blogun tüm blog yazılarını sil", + "RequiredPermissionName": "Gerekli izin adı" } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/vi.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/vi.json index 0a2159e104..9d56cadb8f 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/vi.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/vi.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "Chọn một hành động cho blog", "AssignBlogPostsToOtherBlog": "Gán bài đăng trên blog cho blog khác", "SelectAnBlogToAssign": "Chọn một blog để gán", - "DeleteAllBlogPostsOfThisBlog": "Xóa tất cả bài đăng trên blog của blog này" + "DeleteAllBlogPostsOfThisBlog": "Xóa tất cả bài đăng trên blog của blog này", + "RequiredPermissionName": "Tên quyền cần thiết" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/zh-Hans.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/zh-Hans.json index 18ae3004cb..e3ad52a8ec 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/zh-Hans.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/zh-Hans.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "选择博客的操作", "AssignBlogPostsToOtherBlog": "将博客文章分配给其他博客", "SelectAnBlogToAssign": "选择要分配的博客", - "DeleteAllBlogPostsOfThisBlog": "删除此博客的所有博客文章" + "DeleteAllBlogPostsOfThisBlog": "删除此博客的所有博客文章", + "RequiredPermissionName": "所需权限名称" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/zh-Hant.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/zh-Hant.json index 80c30a6be3..185fa3a3d5 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/zh-Hant.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/zh-Hant.json @@ -231,6 +231,7 @@ "ChooseAnActionForBlog": "選擇部落格的操作", "AssignBlogPostsToOtherBlog": "將部落格文章分配給其他部落格", "SelectAnBlogToAssign": "選擇要分配的部落格", - "DeleteAllBlogPostsOfThisBlog": "刪除此部落格的所有部落格文章" + "DeleteAllBlogPostsOfThisBlog": "刪除此部落格的所有部落格文章", + "RequiredPermissionName": "所需權限名稱" } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Menus/MenuItemConsts.cs b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Menus/MenuItemConsts.cs index 6484d5280e..25779876db 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Menus/MenuItemConsts.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Menus/MenuItemConsts.cs @@ -4,4 +4,5 @@ public static class MenuItemConsts { public const int MaxDisplayNameLength = 64; public const int MaxUrlLength = 1024; + public const int MaxRequiredPermissionNameLength = 128; } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Pages/PageCacheItem.cs b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Pages/PageCacheItem.cs similarity index 96% rename from modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Pages/PageCacheItem.cs rename to modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Pages/PageCacheItem.cs index d4f7925160..d4d662a8a6 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Pages/PageCacheItem.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Pages/PageCacheItem.cs @@ -3,10 +3,11 @@ using Volo.Abp.ObjectExtending; namespace Volo.CmsKit.Pages; +[Serializable] public class PageCacheItem : ExtensibleObject { public Guid Id { get; set; } - + public string Title { get; set; } public string Slug { get; set; } @@ -23,4 +24,4 @@ public class PageCacheItem : ExtensibleObject { return $"CmsPage_{slug}"; } -} \ No newline at end of file +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/DefaultBlogFeatureProvider.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/DefaultBlogFeatureProvider.cs index b24e992cf3..dc74bd5ad6 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/DefaultBlogFeatureProvider.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/DefaultBlogFeatureProvider.cs @@ -16,6 +16,7 @@ public class DefaultBlogFeatureProvider : IDefaultBlogFeatureProvider, ITransien new BlogFeature(blogId, ReactionsFeature.Name), new BlogFeature(blogId, RatingsFeature.Name), new BlogFeature(blogId, TagsFeature.Name), + new BlogFeature(blogId, MarkedItemsFeature.Name), new BlogFeature(blogId, BlogPostScrollIndexFeature.Name), new BlogFeature(blogId, BlogConsts.PreventXssFeatureName) }); diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/IBlogPostRepository.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/IBlogPostRepository.cs index 885d0de800..e4d8980196 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/IBlogPostRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Blogs/IBlogPostRepository.cs @@ -14,6 +14,7 @@ public interface IBlogPostRepository : IBasicRepository Guid? blogId = null, Guid? authorId = null, Guid? tagId = null, + Guid? favoriteUserId = null, BlogPostStatus? statusFilter = null, CancellationToken cancellationToken = default); @@ -22,6 +23,7 @@ public interface IBlogPostRepository : IBasicRepository Guid? blogId = null, Guid? authorId = null, Guid? tagId = null, + Guid? favoriteUserId = null, BlogPostStatus? statusFilter = null, int maxResultCount = int.MaxValue, int skipCount = 0, diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs index e3824063e6..10ffbf964b 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs @@ -12,6 +12,7 @@ using Volo.CmsKit.Blogs; using Volo.CmsKit.Comments; using Volo.CmsKit.GlobalFeatures; using Volo.CmsKit.Localization; +using Volo.CmsKit.MarkedItems; using Volo.CmsKit.MediaDescriptors; using Volo.CmsKit.Menus; using Volo.CmsKit.Pages; @@ -91,6 +92,24 @@ public class CmsKitDomainModule : AbpModule { // TODO: Configure TagEntityTypes here... } + + if (GlobalFeatureManager.Instance.IsEnabled()) + { + Configure(options => + { + if (GlobalFeatureManager.Instance.IsEnabled()) + { + options.EntityTypes.Add( + new MarkedItemEntityTypeDefinition( + BlogPostConsts.EntityType, + StandardMarkedItems.Favorite + ) + ); + } + + // TODO: Add more entities that can be marked. + }); + } } public override void PostConfigureServices(ServiceConfigurationContext context) diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/MarkedItems/MarkedItemManager.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/MarkedItems/MarkedItemManager.cs index 15c858f5c4..bb9f884dba 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/MarkedItems/MarkedItemManager.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/MarkedItems/MarkedItemManager.cs @@ -2,6 +2,8 @@ using JetBrains.Annotations; using System.Threading.Tasks; using Volo.Abp; +using System.Collections.Generic; +using System.Threading; namespace Volo.CmsKit.MarkedItems; public class MarkedItemManager : CmsKitDomainServiceBase @@ -20,7 +22,7 @@ public class MarkedItemManager : CmsKitDomainServiceBase public virtual async Task ToggleUserMarkedItemAsync( Guid creatorId, - [NotNull] string entityType, + [NotNull] string entityType, [NotNull] string entityId) { Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); @@ -49,4 +51,12 @@ public class MarkedItemManager : CmsKitDomainServiceBase ); return true; } + public async Task> GetEntityIdsFilteredByUserAsync( + [NotNull] Guid userId, + [NotNull] string entityType, + [CanBeNull] Guid? tenantId = null, + CancellationToken cancellationToken = default) + { + return await UserMarkedItemRepository.GetEntityIdsFilteredByUserAsync(userId, entityType, tenantId, cancellationToken); + } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Menus/MenuItem.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Menus/MenuItem.cs index 56cef52826..418bb19408 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Menus/MenuItem.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Menus/MenuItem.cs @@ -40,6 +40,8 @@ public class MenuItem : AuditedAggregateRoot, IMultiTenant public Guid? PageId { get; protected set; } public Guid? TenantId { get; protected set; } + + public string RequiredPermissionName { get; set; } protected MenuItem() { @@ -55,7 +57,8 @@ public class MenuItem : AuditedAggregateRoot, IMultiTenant [CanBeNull] string target = null, [CanBeNull] string elementId = null, [CanBeNull] string cssClass = null, - [CanBeNull] Guid? tenantId = null) + [CanBeNull] Guid? tenantId = null, + [CanBeNull] string requiredPermissionName = null) : base(id) { SetDisplayName(displayName); @@ -68,6 +71,7 @@ public class MenuItem : AuditedAggregateRoot, IMultiTenant ElementId = elementId; CssClass = cssClass; TenantId = tenantId; + RequiredPermissionName = requiredPermissionName; } public void SetDisplayName([NotNull] string displayName) diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogPostRepository.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogPostRepository.cs index 1160387151..a93683ff46 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogPostRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogPostRepository.cs @@ -11,6 +11,7 @@ using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; using Volo.CmsKit.EntityFrameworkCore; +using Volo.CmsKit.MarkedItems; using Volo.CmsKit.Tags; using Volo.CmsKit.Users; @@ -18,12 +19,15 @@ namespace Volo.CmsKit.Blogs; public class EfCoreBlogPostRepository : EfCoreRepository, IBlogPostRepository { + private readonly MarkedItemManager _markedItemManager; private EntityTagManager _entityTagManager; public EfCoreBlogPostRepository( IDbContextProvider dbContextProvider, + MarkedItemManager markedItemManager, EntityTagManager entityTagManager) : base(dbContextProvider) { + _markedItemManager = markedItemManager; _entityTagManager = entityTagManager; } @@ -50,17 +54,21 @@ public class EfCoreBlogPostRepository : EfCoreRepository entityIdFilters = null; - if (tagId.HasValue) - { - entityIdFilters = await _entityTagManager.GetEntityIdsFilteredByTagAsync(tagId.Value, CurrentTenant.Id, cancellationToken); - } + var tagFilteredEntityIds = tagId.HasValue + ? await _entityTagManager.GetEntityIdsFilteredByTagAsync(tagId.Value, CurrentTenant.Id, cancellationToken) + : null; + + var favoriteUserFilteredEntityIds = favoriteUserId.HasValue + ? await _markedItemManager.GetEntityIdsFilteredByUserAsync(favoriteUserId.Value, BlogPostConsts.EntityType) + : null; var queryable = (await GetDbSetAsync()) - .WhereIf(entityIdFilters != null, x => entityIdFilters.Contains(x.Id.ToString())) + .WhereIf(tagFilteredEntityIds != null, x => tagFilteredEntityIds.Contains(x.Id.ToString())) + .WhereIf(favoriteUserFilteredEntityIds != null, x => favoriteUserFilteredEntityIds.Contains(x.Id.ToString())) .WhereIf(blogId.HasValue, x => x.BlogId == blogId) .WhereIf(authorId.HasValue, x => x.AuthorId == authorId) .WhereIf(statusFilter.HasValue, x => x.Status == statusFilter) @@ -75,6 +83,7 @@ public class EfCoreBlogPostRepository : EfCoreRepository(); var usersDbSet = dbContext.Set(); - List entityIdFilters = null; - if (tagId.HasValue) - { - entityIdFilters = await _entityTagManager.GetEntityIdsFilteredByTagAsync(tagId.Value, CurrentTenant.Id, cancellationToken); - } + var tagFilteredEntityIds = tagId.HasValue + ? await _entityTagManager.GetEntityIdsFilteredByTagAsync(tagId.Value, CurrentTenant.Id, cancellationToken) + : null; + + var favoriteUserFilteredEntityIds = favoriteUserId.HasValue + ? await _markedItemManager.GetEntityIdsFilteredByUserAsync(favoriteUserId.Value, BlogPostConsts.EntityType) + : null; var queryable = (await GetDbSetAsync()) - .WhereIf(entityIdFilters != null, x => entityIdFilters.Contains(x.Id.ToString())) + .WhereIf(tagFilteredEntityIds != null, x => tagFilteredEntityIds.Contains(x.Id.ToString())) + .WhereIf(favoriteUserFilteredEntityIds != null, x => favoriteUserFilteredEntityIds.Contains(x.Id.ToString())) .WhereIf(blogId.HasValue, x => x.BlogId == blogId) .WhereIf(!string.IsNullOrWhiteSpace(filter), x => x.Title.Contains(filter) || x.Slug.Contains(filter)) .WhereIf(authorId.HasValue, x => x.AuthorId == authorId) diff --git a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs index b453f52fc5..c5b93022c6 100644 --- a/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs +++ b/modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs @@ -261,6 +261,8 @@ public static class CmsKitDbContextModelCreatingExtensions b.Property(x => x.DisplayName).IsRequired().HasMaxLength(MenuItemConsts.MaxDisplayNameLength); b.Property(x => x.Url).IsRequired().HasMaxLength(MenuItemConsts.MaxUrlLength); + + b.Property(x => x.RequiredPermissionName).HasMaxLength(MenuItemConsts.MaxRequiredPermissionNameLength); }); } else diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs index d27fdf16b6..0212e5975c 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs @@ -12,6 +12,7 @@ using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.MongoDB; using Volo.CmsKit.Blogs; +using Volo.CmsKit.MarkedItems; using Volo.CmsKit.Tags; using Volo.CmsKit.Users; @@ -19,10 +20,15 @@ namespace Volo.CmsKit.MongoDB.Blogs; public class MongoBlogPostRepository : MongoDbRepository, IBlogPostRepository { + private readonly MarkedItemManager _markedItemManager; private EntityTagManager _entityTagManager; - public MongoBlogPostRepository(IMongoDbContextProvider dbContextProvider, EntityTagManager entityTagManager) : base( + public MongoBlogPostRepository( + IMongoDbContextProvider dbContextProvider, + MarkedItemManager markedItemManager, + EntityTagManager entityTagManager) : base( dbContextProvider) { + _markedItemManager = markedItemManager; _entityTagManager = entityTagManager; } @@ -48,15 +54,19 @@ public class MongoBlogPostRepository : MongoDbRepository>(entityIdFilters.Any(), x => entityIdFilters.Contains(x.Id)) + .WhereIf>(tagFilteredEntityIds.Any(), x => tagFilteredEntityIds.Contains(x.Id)) + .WhereIf>(favoriteUserFilteredEntityIds.Any(), x => favoriteUserFilteredEntityIds.Contains(x.Id)) .WhereIf>(!string.IsNullOrWhiteSpace(filter), x => x.Title.Contains(filter) || x.Slug.Contains(filter)) .WhereIf>(blogId.HasValue, x => x.BlogId == blogId) .WhereIf>(authorId.HasValue, x => x.AuthorId == authorId) @@ -69,6 +79,7 @@ public class MongoBlogPostRepository : MongoDbRepository().AsQueryable(); var queryable = blogPostQueryable - .WhereIf(entityIdFilters.Any(), x => entityIdFilters.Contains(x.Id)) + .WhereIf(tagFilteredEntityIds.Any(), x => tagFilteredEntityIds.Contains(x.Id)) + .WhereIf(favoriteUserFilteredEntityIds.Any(), x => favoriteUserFilteredEntityIds.Contains(x.Id)) .WhereIf(blogId.HasValue, x => x.BlogId == blogId) .WhereIf(!string.IsNullOrWhiteSpace(filter), x => x.Title.Contains(filter) || x.Slug.Contains(filter)) .WhereIf(authorId.HasValue, x => x.AuthorId == authorId) @@ -132,6 +146,28 @@ public class MongoBlogPostRepository : MongoDbRepository> GetFavoriteEntityIdsByUserId(Guid? userId, CancellationToken cancellationToken) + { + var entityIdFilters = new List(); + if (!userId.HasValue) + { + return entityIdFilters; + } + + var entityIds = + await _markedItemManager.GetEntityIdsFilteredByUserAsync(userId.Value, BlogPostConsts.EntityType, CurrentTenant.Id, cancellationToken); + + foreach (var entityId in entityIds) + { + if (Guid.TryParse(entityId, out var parsedEntityId)) + { + entityIdFilters.Add(parsedEntityId); + } + } + + return entityIdFilters; + } + public virtual async Task SlugExistsAsync(Guid blogId, [NotNull] string slug, CancellationToken cancellationToken = default) { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/BlogPostGetListInput.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/BlogPostGetListInput.cs index 5e3ffe9311..762d8663d6 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/BlogPostGetListInput.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/BlogPostGetListInput.cs @@ -8,4 +8,5 @@ public class BlogPostGetListInput : PagedAndSortedResultRequestDto public Guid? AuthorId { get; set; } public Guid? TagId { get; set; } + public bool? FilterOnFavorites { get; set; } } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Blogs/BlogPostPublicAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Blogs/BlogPostPublicAppService.cs index 9b831ca8a6..561b6a9bcb 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Blogs/BlogPostPublicAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Blogs/BlogPostPublicAppService.cs @@ -54,7 +54,10 @@ public class BlogPostPublicAppService : CmsKitPublicAppServiceBase, IBlogPostPub { var blog = await BlogRepository.GetBySlugAsync(blogSlug); + Guid? favoriteUserId = await GetFavoriteUserIdAsync(input.FilterOnFavorites); + var blogPosts = await BlogPostRepository.GetListAsync(null, blog.Id, input.AuthorId, input.TagId, + favoriteUserId, BlogPostStatus.Published, input.MaxResultCount, input.SkipCount, input.Sorting); @@ -100,4 +103,14 @@ public class BlogPostPublicAppService : CmsKitPublicAppServiceBase, IBlogPostPub return tag.Name; } + + protected virtual async Task GetFavoriteUserIdAsync(bool? filterOnFavorites) + { + if (!filterOnFavorites.GetValueOrDefault() || !CurrentUser.IsAuthenticated) + { + return null; + } + + return CurrentUser.GetId(); + } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Menus/CmsKitPublicMenuContributor.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Menus/CmsKitPublicMenuContributor.cs index 7d9bc797b9..d7b879227a 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Menus/CmsKitPublicMenuContributor.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Menus/CmsKitPublicMenuContributor.cs @@ -1,7 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Authorization.Permissions; using Volo.Abp.Features; using Volo.Abp.GlobalFeatures; using Volo.Abp.UI.Navigation; @@ -55,7 +57,7 @@ public class CmsKitPublicMenuContributor : IMenuContributor private ApplicationMenuItem CreateApplicationMenuItem(MenuItemDto menuItem) { - return new ApplicationMenuItem( + var menu = new ApplicationMenuItem( menuItem.DisplayName, menuItem.DisplayName, menuItem.Url, @@ -65,5 +67,11 @@ public class CmsKitPublicMenuContributor : IMenuContributor menuItem.ElementId, menuItem.CssClass ); + if (!menuItem.RequiredPermissionName.IsNullOrWhiteSpace()) + { + menu.RequirePermissions(menuItem.RequiredPermissionName); + } + + return menu; } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml index c181872586..d8c09fd773 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml @@ -87,32 +87,32 @@ Func GetCommentActionArea(Guid id, Guid authorId, bool isReply) => @
@if (!isReply) - { - @if (CurrentUser.IsAuthenticated) - { - - @L["Reply"] - - - - @L["Delete"] - - } - else - { - - @L["LoginToReply"] - - } - } + { + @if (CurrentUser.IsAuthenticated) + { + + @L["Reply"] + + } + else + { + + @L["LoginToReply"] + + } + } @if (authorId == CurrentUser.Id) - { - - @L["Edit"] - - } + { + + @L["Edit"] + + + + @L["Delete"] + + }
; } @{ diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/MarkedItemToggle/default.css b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/MarkedItemToggle/default.css index af5f994177..02e8fccc9e 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/MarkedItemToggle/default.css +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/MarkedItemToggle/default.css @@ -2,6 +2,6 @@ } .cms-markedItem-area i { - font-size: 1.5em; + font-size: 1.05rem; cursor: pointer; } \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Modals/Login/LoginModal.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Modals/Login/LoginModal.cshtml index 1472d4b227..d8ca6083a6 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Modals/Login/LoginModal.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Modals/Login/LoginModal.cshtml @@ -5,7 +5,8 @@ @model Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Modals.Login.LoginModalModel @{ Layout = null; - var message = String.IsNullOrEmpty(Model.ViewModel.Message) ? L["CmsKit:Modals:LoginModalDefaultMessage"] : Model.ViewModel.Message; + var message = String.IsNullOrEmpty(Model.ViewModel.Message) ? L["CmsKit:Modals:LoginModalDefaultMessage"] : + Model.ViewModel.Message; } @@ -15,8 +16,9 @@

@message

-
- @L["CmsKit:Modals:Login"] + + @L["CmsKit:Modals:Login"] +
\ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKitPageRouteValueTransformer.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKitPageRouteValueTransformer.cs index d9bba6cd0b..53f7fe50ba 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKitPageRouteValueTransformer.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKitPageRouteValueTransformer.cs @@ -2,9 +2,11 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; +using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.Features; using Volo.CmsKit.Features; +using Volo.CmsKit.Pages; using Volo.CmsKit.Public.Pages; namespace Volo.CmsKit.Public.Web.Pages; @@ -13,14 +15,16 @@ public class CmsKitPageRouteValueTransformer : DynamicRouteValueTransformer, ITr { protected IFeatureChecker FeatureChecker { get; } protected IPagePublicAppService PagePublicAppService { get; } + protected IDistributedCache PageCache { get; } - public CmsKitPageRouteValueTransformer(IFeatureChecker featureChecker, IPagePublicAppService pagePublicAppService) + public CmsKitPageRouteValueTransformer(IFeatureChecker featureChecker, IPagePublicAppService pagePublicAppService, IDistributedCache pageCache) { FeatureChecker = featureChecker; PagePublicAppService = pagePublicAppService; + PageCache = pageCache; } - public override async ValueTask TransformAsync(HttpContext httpContext, RouteValueDictionary values) + public async override ValueTask TransformAsync(HttpContext httpContext, RouteValueDictionary values) { if (values.TryGetValue("slug", out var slugParameter) && slugParameter is not null) { @@ -30,7 +34,12 @@ public class CmsKitPageRouteValueTransformer : DynamicRouteValueTransformer, ITr } var slug = slugParameter.ToString().TrimStart('/'); - var exist = await PagePublicAppService.DoesSlugExistAsync(slug); + + var exist = await PageCache.GetAsync(PageCacheItem.GetKey(slug)) != null; + if (!exist) + { + exist = await PagePublicAppService.DoesSlugExistAsync(slug); + } if (exist) { @@ -40,4 +49,4 @@ public class CmsKitPageRouteValueTransformer : DynamicRouteValueTransformer, ITr return values; } -} \ No newline at end of file +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml index 6074ed573f..23f214f1e5 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml @@ -1,6 +1,10 @@ @page +@using Volo.CmsKit.Blogs @using Volo.CmsKit.Public.Web.Pages +@using Volo.Abp.GlobalFeatures +@using Volo.CmsKit.GlobalFeatures +@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.MarkedItemToggle @using Volo.CmsKit.Public.Web.Pages.Public.CmsKit.Blogs @using Volo.CmsKit.Web @@ -8,6 +12,11 @@ @model IndexModel +@{ + const string dummyImageSource = "/cms-kit/dummy-placeholder-320x180.png"; + var isMarkedItemFeatureEnabled = GlobalFeatureManager.Instance.IsEnabled() && Model.MarkedItemsFeature?.IsEnabled == true; +} + @section styles { } @@ -15,13 +24,13 @@ @section scripts { + @if (isMarkedItemFeatureEnabled) + { + + } } -@{ - const string dummyImageSource = "/cms-kit/dummy-placeholder-320x180.png"; -} - @if (Model.AuthorId.HasValue) { @@ -61,11 +70,37 @@ @if (Model.Blogs.TotalCount > 0) { + @if (isMarkedItemFeatureEnabled) + { + var filterOnFavorties = Model.FilterOnFavorites.GetValueOrDefault(); + string icon = filterOnFavorties ? "heart" : "heart-o"; + + + + } @foreach (var blog in Model.Blogs.Items) { + @if (isMarkedItemFeatureEnabled) + { +
+ + @await Component.InvokeAsync(typeof(MarkedItemToggleViewComponent), new + { + entityId = blog.Id.ToString(), + entityType = BlogPostConsts.EntityType + }) + +
+ } @if (blog.CoverImageMediaId != null) { diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml.cs b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml.cs index e47dd40816..ada15afe51 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml.cs @@ -1,9 +1,13 @@ using System; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Volo.Abp.Application.Dtos; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination; +using Volo.Abp.GlobalFeatures; +using Volo.CmsKit.Blogs; using Volo.CmsKit.Contents; +using Volo.CmsKit.GlobalFeatures; using Volo.CmsKit.Public.Blogs; using Volo.CmsKit.Users; @@ -22,6 +26,9 @@ public class IndexModel : CmsKitPublicPageModelBase [BindProperty(SupportsGet = true)] public Guid? AuthorId { get; set; } + [BindProperty(SupportsGet = true)] + public bool? FilterOnFavorites { get; set; } + [BindProperty(SupportsGet = true)] public Guid? TagId { get; set; } @@ -32,12 +39,17 @@ public class IndexModel : CmsKitPublicPageModelBase public CmsUserDto SelectedAuthor { get; protected set; } public string FilteredTagName { get; protected set; } + public BlogFeatureDto MarkedItemsFeature { get; private set; } protected IBlogPostPublicAppService BlogPostPublicAppService { get; } + public IBlogFeatureAppService BlogFeatureAppService { get; } - public IndexModel(IBlogPostPublicAppService blogPostPublicAppService) + public IndexModel( + IBlogPostPublicAppService blogPostPublicAppService, + IBlogFeatureAppService blogFeatureAppService) { BlogPostPublicAppService = blogPostPublicAppService; + BlogFeatureAppService = blogFeatureAppService; } public virtual async Task OnGetAsync() @@ -49,7 +61,8 @@ public class IndexModel : CmsKitPublicPageModelBase SkipCount = PageSize * (CurrentPage - 1), MaxResultCount = PageSize, AuthorId = AuthorId, - TagId = TagId + TagId = TagId, + FilterOnFavorites = FilterOnFavorites }); if (AuthorId != null) @@ -62,6 +75,13 @@ public class IndexModel : CmsKitPublicPageModelBase FilteredTagName = await BlogPostPublicAppService.GetTagNameAsync(TagId.Value); } + if (GlobalFeatureManager.Instance.IsEnabled() && + Blogs.Items.Any()) + { + var blogId = Blogs.Items.First().BlogId; + MarkedItemsFeature = await BlogFeatureAppService.GetOrDefaultAsync(blogId, GlobalFeatures.MarkedItemsFeature.Name); + } + return Page(); } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/filter-on-favorites.js b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/filter-on-favorites.js new file mode 100644 index 0000000000..0fb87bc0ef --- /dev/null +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/filter-on-favorites.js @@ -0,0 +1,27 @@ +$(function () { + var l = abp.localization.getResource('CmsKit'); + + let filterButton = $('.favorite-button'); + let filterOnFavorites = filterButton.attr('filter-on-favorites'); + const loginModal = new abp.ModalManager(abp.appPath + 'CmsKit/Shared/Modals/Login/LoginModal'); + + $('.favorite-button').on('click', function () { + if (!abp.currentUser.isAuthenticated) { + const currentPageRoute = window.location.pathname; + loginModal.open({ message: l("FavoritesFilterMessage"), returnUrl: currentPageRoute }); + return; + } + + let currentUrl = new URL(window.location.href); + let searchParams = currentUrl.searchParams; + + // Toggle the 'filterOnFavorites' parameter + if (filterOnFavorites) { + searchParams.delete('filterOnFavorites'); + } else { + searchParams.set('filterOnFavorites', 'true'); + } + + window.location.href = currentUrl.pathname + '?' + searchParams.toString(); + }); +}); \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/index.css b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/index.css index e9a4f2fa19..83b1e8482c 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/index.css +++ b/modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/index.css @@ -17,4 +17,9 @@ display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; +} + +.favorite-toggle { + width: 40px; + height: 40px; } \ No newline at end of file diff --git a/modules/docs/.abpstudio/state.json b/modules/docs/.abpstudio/state.json new file mode 100644 index 0000000000..fefde3c294 --- /dev/null +++ b/modules/docs/.abpstudio/state.json @@ -0,0 +1,16 @@ +{ + "selectedKubernetesProfile": null, + "solutionRunner": { + "selectedProfile": "Default", + "targetFrameworks": [], + "applicationsStartingWithoutBuild": [], + "applicationBatchStartStates": [ + { + "profile": "Default", + "applicationOrFolder": "VoloDocs.Web", + "value": 0 + } + ], + "folderBatchStartStates": [] + } +} \ No newline at end of file diff --git a/modules/docs/Default.abprun.json b/modules/docs/Default.abprun.json new file mode 100644 index 0000000000..f9b4dd4507 --- /dev/null +++ b/modules/docs/Default.abprun.json @@ -0,0 +1,11 @@ +{ + "metadata": {}, + "applications": { + "VoloDocs.Web": { + "type": "dotnet-project", + "path": "app/VoloDocs.Web/VoloDocs.Web.csproj", + "launchUrl": "https://localhost:5001", + "kubernetesService": null + } + } +} \ No newline at end of file diff --git a/modules/docs/Volo.Docs.abpsln b/modules/docs/Volo.Docs.abpsln index 688fe0befa..44288f9b36 100644 --- a/modules/docs/Volo.Docs.abpsln +++ b/modules/docs/Volo.Docs.abpsln @@ -3,5 +3,11 @@ "Volo.Docs": { "path": "Volo.Docs.abpmdl" } + }, + "id": "c1d848e0-0b53-461b-824a-8533ba1fd82b", + "runProfiles": { + "Default": { + "path": "Default.abprun.json" + } } } \ No newline at end of file diff --git a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20240707104226_Initial.Designer.cs b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20241231072012_Initial.Designer.cs similarity index 98% rename from modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20240707104226_Initial.Designer.cs rename to modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20241231072012_Initial.Designer.cs index a270fbcb4e..ad7dfa9576 100644 --- a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20240707104226_Initial.Designer.cs +++ b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20241231072012_Initial.Designer.cs @@ -13,7 +13,7 @@ using VoloDocs.EntityFrameworkCore; namespace Migrations { [DbContext(typeof(VoloDocsDbContext))] - [Migration("20240707104226_Initial")] + [Migration("20241231072012_Initial")] partial class Initial { /// @@ -22,7 +22,7 @@ namespace Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("ProductVersion", "9.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -40,6 +40,10 @@ namespace Migrations .HasColumnType("nvarchar(40)") .HasColumnName("ConcurrencyStamp"); + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + b.Property("Description") .HasMaxLength(256) .HasColumnType("nvarchar(256)"); @@ -116,6 +120,10 @@ namespace Migrations .HasColumnType("nvarchar(40)") .HasColumnName("ConcurrencyStamp"); + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + b.Property("EntityVersion") .HasColumnType("int"); @@ -281,9 +289,13 @@ namespace Migrations .HasMaxLength(64) .HasColumnType("nvarchar(64)"); + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + b.Property("IpAddresses") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)"); b.Property("LastAccessed") .HasColumnType("datetime2"); diff --git a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20240707104226_Initial.cs b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20241231072012_Initial.cs similarity index 99% rename from modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20240707104226_Initial.cs rename to modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20241231072012_Initial.cs index 12da8d1bca..25791fabf4 100644 --- a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20240707104226_Initial.cs +++ b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20241231072012_Initial.cs @@ -23,6 +23,7 @@ namespace Migrations RegexDescription = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), Description = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), ValueType = table.Column(type: "int", nullable: false), + CreationTime = table.Column(type: "datetime2", nullable: false), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) }, @@ -137,6 +138,7 @@ namespace Migrations IsStatic = table.Column(type: "bit", nullable: false), IsPublic = table.Column(type: "bit", nullable: false), EntityVersion = table.Column(type: "int", nullable: false), + CreationTime = table.Column(type: "datetime2", nullable: false), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) }, @@ -181,9 +183,10 @@ namespace Migrations TenantId = table.Column(type: "uniqueidentifier", nullable: true), UserId = table.Column(type: "uniqueidentifier", nullable: false), ClientId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), - IpAddresses = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + IpAddresses = table.Column(type: "nvarchar(2048)", maxLength: 2048, nullable: true), SignedIn = table.Column(type: "datetime2", nullable: false), - LastAccessed = table.Column(type: "datetime2", nullable: true) + LastAccessed = table.Column(type: "datetime2", nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) }, constraints: table => { diff --git a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs index 37601938e1..17e18940bb 100644 --- a/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs +++ b/modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs @@ -19,7 +19,7 @@ namespace Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("ProductVersion", "9.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -37,6 +37,10 @@ namespace Migrations .HasColumnType("nvarchar(40)") .HasColumnName("ConcurrencyStamp"); + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + b.Property("Description") .HasMaxLength(256) .HasColumnType("nvarchar(256)"); @@ -113,6 +117,10 @@ namespace Migrations .HasColumnType("nvarchar(40)") .HasColumnName("ConcurrencyStamp"); + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + b.Property("EntityVersion") .HasColumnType("int"); @@ -278,9 +286,13 @@ namespace Migrations .HasMaxLength(64) .HasColumnType("nvarchar(64)"); + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + b.Property("IpAddresses") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)"); b.Property("LastAccessed") .HasColumnType("datetime2"); diff --git a/modules/docs/app/VoloDocs.Web/package.json b/modules/docs/app/VoloDocs.Web/package.json index 9b2bca3795..0437f4319b 100644 --- a/modules/docs/app/VoloDocs.Web/package.json +++ b/modules/docs/app/VoloDocs.Web/package.json @@ -3,7 +3,7 @@ "name": "volo.docstestapp", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~9.0.0", - "@abp/docs": "~9.0.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~9.0.3", + "@abp/docs": "~9.0.3" } } diff --git a/modules/docs/app/VoloDocs.Web/yarn.lock b/modules/docs/app/VoloDocs.Web/yarn.lock index a17bc5cd05..21d865b050 100644 --- a/modules/docs/app/VoloDocs.Web/yarn.lock +++ b/modules/docs/app/VoloDocs.Web/yarn.lock @@ -2,238 +2,238 @@ # yarn lockfile v1 -"@abp/anchor-js@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/anchor-js/-/anchor-js-9.0.0.tgz#6b87875ba144ad551df43f7dece4e8ac306a4d2a" - integrity sha512-0cSp5zOKsddu6riwAhSEw0y5OOK+0aAdiXPAx7Ivnj1WuzMBn6A42oxV00rt/3vJAtAP3l5Tt6aFSG8FHgHkQQ== +"@abp/anchor-js@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/anchor-js/-/anchor-js-9.0.3.tgz#a6183929f07d778cf3deed99bc2dc71ca619a49e" + integrity sha512-0c1RiXvpauYXfY/RlVLsD6OUoLNbLJOJGMSqoSolEcLgVwv5NXDC/q+UQOnAFjDC7f/34FQvsoxtspNFfoLUuw== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" anchor-js "^5.0.0" -"@abp/aspnetcore.mvc.ui.theme.basic@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-9.0.0.tgz#e4b05d63446c24b612275e3949881e176d32527f" - integrity sha512-GHEt26xESe/TCg1q6ZjeUbxniqdxaiB1OPTZ9md7ONql6bEp8t9iUPeSlkyAZtQ7qBzwgkiLpkRFT+J21DftaQ== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~9.0.0" - -"@abp/aspnetcore.mvc.ui.theme.shared@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-9.0.0.tgz#a77b4b70fa6217146afd6a57211f57e2360564f2" - integrity sha512-QmZrSp+rfqfnC9W4QZ+nNdAq0rOALzRn1rsDpGDHhIHwyXB03QPdjzIWaEvldUYHzqg84cEP0LcqW4y57Xr1iw== - dependencies: - "@abp/aspnetcore.mvc.ui" "~9.0.0" - "@abp/bootstrap" "~9.0.0" - "@abp/bootstrap-datepicker" "~9.0.0" - "@abp/bootstrap-daterangepicker" "~9.0.0" - "@abp/datatables.net-bs5" "~9.0.0" - "@abp/font-awesome" "~9.0.0" - "@abp/jquery-form" "~9.0.0" - "@abp/jquery-validation-unobtrusive" "~9.0.0" - "@abp/lodash" "~9.0.0" - "@abp/luxon" "~9.0.0" - "@abp/malihu-custom-scrollbar-plugin" "~9.0.0" - "@abp/moment" "~9.0.0" - "@abp/select2" "~9.0.0" - "@abp/sweetalert2" "~9.0.0" - "@abp/timeago" "~9.0.0" - "@abp/toastr" "~9.0.0" - -"@abp/aspnetcore.mvc.ui@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-9.0.0.tgz#93004dd056c130386c7b40c458f3a793d8c9e051" - integrity sha512-pvO5bh9baFcSbfW0s7tpMpbe6DKSC16jeyeluZLlvYxC0n2gkrv7H/hB5aMuBKIlh89aGY9DQIcMFC8iczL3Bg== +"@abp/aspnetcore.mvc.ui.theme.basic@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-9.0.3.tgz#dcb95764ee11fc11ca593b722575275837cc58a9" + integrity sha512-NwTunUVwfaoZsNp1Js6B+jGPBw96taBhQ5hloKhtZFZxG2VMAN9Gy9fGxzUByYlpRB8CASR3kL+GRhrK2Ufc3A== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~9.0.3" + +"@abp/aspnetcore.mvc.ui.theme.shared@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-9.0.3.tgz#18f0d27cbba7ff22576c1c6e54891190c7d900bb" + integrity sha512-WHMvMiZhqvPLQtqo3aI+wkhNbZ3gCkWqwhIiXHvRks+tO92ww52evPlZUNDSWSSkhcB9vJddx5Eqv4phJgcMAA== + dependencies: + "@abp/aspnetcore.mvc.ui" "~9.0.3" + "@abp/bootstrap" "~9.0.3" + "@abp/bootstrap-datepicker" "~9.0.3" + "@abp/bootstrap-daterangepicker" "~9.0.3" + "@abp/datatables.net-bs5" "~9.0.3" + "@abp/font-awesome" "~9.0.3" + "@abp/jquery-form" "~9.0.3" + "@abp/jquery-validation-unobtrusive" "~9.0.3" + "@abp/lodash" "~9.0.3" + "@abp/luxon" "~9.0.3" + "@abp/malihu-custom-scrollbar-plugin" "~9.0.3" + "@abp/moment" "~9.0.3" + "@abp/select2" "~9.0.3" + "@abp/sweetalert2" "~9.0.3" + "@abp/timeago" "~9.0.3" + "@abp/toastr" "~9.0.3" + +"@abp/aspnetcore.mvc.ui@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-9.0.3.tgz#a4e2e92cddca77d81d8445bcc1d2cc129ec4b486" + integrity sha512-wkn7R8sx5lhbaZeTAy5aJxLbR1f3TD8lNaNtM1Lsc7wWq9dI19wyJZiAWqCZij2HOr2IK5izSROVfTqoXyUwXQ== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-9.0.0.tgz#d6609e50568aac6a013abfcaa07817998e1eaa99" - integrity sha512-wxAeVsOVOJpidDn0g3FlJvpOsn5SVY5IpEG+FtUxSAKuWYJQWF6g5CaghrfS+FNDK9IHhxrgSMPMVBK9YlDSzw== +"@abp/bootstrap-datepicker@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-9.0.3.tgz#aa40f5a31eb5c06224c165129a882658f4a0a9a5" + integrity sha512-Jg4oeBPtX7G41JdYjHVhB/6KQcMJO4WWXbUvkNetSUGTZGqacmgWRAIA5abmRWaHMJP1A1soZ9Ny1GNy0Qu65Q== dependencies: bootstrap-datepicker "^1.10.0" -"@abp/bootstrap-daterangepicker@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-9.0.0.tgz#fc2871e7d886accd8e4748cb763b695e10701c00" - integrity sha512-qS6SQR6X7wMCVLcuXatJ2+m1GNlpN71p7VDritxfwAMHa7WBRAAi8apX2WItwBAuLQ2s1uSA0Wp1KstHG3VPLA== +"@abp/bootstrap-daterangepicker@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-9.0.3.tgz#a1c17721a1ff6613a3ee4ff971174b32a93d98de" + integrity sha512-wrvjb0bJudohxiYtecCGEa4VsvL/Kn/Z3q9w1jvIoallrTXQaACPtZDU+9yxK5mRf6zonwYI8JtNn4xKa+D1VA== dependencies: bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-9.0.0.tgz#5ab292e95ba7eef129cbaa1dc5f5d5c0af8530d7" - integrity sha512-jEr6su8BfoNxYvze/UlddE3DlezyPvQeiNhff8SDPqtRQ6XvjZ48O/JwtY01hK2njEDLOZINY5zJRGmO2OiOUw== +"@abp/bootstrap@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-9.0.3.tgz#dc0325c23282086c9d2be79588b228e8b2c66eb2" + integrity sha512-ypl0R3jky6qKmnEZjnSniwgN8unNmWD9d4bOtV5RgDF2MlBQT+CvfUcYnGom0JOsL+5RjSNvzFH/Q5aH/yuy7Q== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" bootstrap "^5.3.3" -"@abp/clipboard@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-9.0.0.tgz#21ae924dfef6ccf3c8a3913bb81caf22b57404e4" - integrity sha512-W6S7pihpQ6Wfj48Clk7hYgDvUtnRwkNIsk5t8s9+LxoAdJQGQFxh7SAW1QNavUv8QqsClNE1VtSU87c7rSoAbA== +"@abp/clipboard@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-9.0.3.tgz#bda02ce6da876890ef117697f30348b8c16aaf70" + integrity sha512-iprJtnshdpRgLIPxa5Tgj1IqkLvkl2QxTmjrqeiuRhU3S1ry0GnE6dx3jAealWUOoYr/bqXPbpXi3RN7R4baHw== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" clipboard "^2.0.11" -"@abp/core@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-9.0.0.tgz#dc5ea9139957e307f73ac1f40b179f991e3c2168" - integrity sha512-vzQyTpqVDx63pOI9e2QLUMWZCrSojHsRnzLlvl+/cKFi7jE1q/uSP66FPR2bwkD+DNLI/tn6is1Jx3skKA1FvA== +"@abp/core@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-9.0.3.tgz#8c508dc60628fbd9e8a9ba35d153316cacbf1fb4" + integrity sha512-4bHiyP2qPrEeXb/Ynl17b6LtfygWdkwsjHaadUbym7Lg5qYUQZ3KjW4U2DihYWCO46gsr/ckTEbV9edcRd+NxA== dependencies: - "@abp/utils" "~9.0.0" + "@abp/utils" "~9.0.3" -"@abp/datatables.net-bs5@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-9.0.0.tgz#4daee90bdb6446e854ce65f2a3afabb2a13e49ae" - integrity sha512-ln10tcRwxcB0nuSgqJ/66strHsqMHDC03c0ktxEplAN34yP7KInjFf/U9Svo74K0nKlkJdMCgmqmT4CnBJZlIg== +"@abp/datatables.net-bs5@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-9.0.3.tgz#c89813a69a1b568a171cb2a4b1bcc4b7a4ef1bef" + integrity sha512-SD6N4apScOR22S7EntengOTQ1HSm2WnvzUuP/6HG50isXyMheRalZxJ6zAsBwiWyYyJtA+UHkiQzwVhdV40aBQ== dependencies: - "@abp/datatables.net" "~9.0.0" + "@abp/datatables.net" "~9.0.3" datatables.net-bs5 "^2.1.8" -"@abp/datatables.net@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-9.0.0.tgz#82b28518293c9091ec72d5defae2f5d2903d8bbc" - integrity sha512-QhuX3bGZOckM/fhIIGj/StMJIX2BBtNbLzjTR7WnKMMxDkv0+oDvjw6OqqpYKSvj/q3vY6sRWuCNdEzNi0Fk6A== +"@abp/datatables.net@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-9.0.3.tgz#9cc09127ff6269476b6b0de16048e1e6480227c4" + integrity sha512-zLzHxdXnmL8pWKVzf3m4SIUMiPHHe3I9Cw8vezk5fK1v18Fk364o3Q8o8C1l1YDGEa7Sx1wsUoLDN+p4FzagXg== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" datatables.net "^2.1.8" -"@abp/docs@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/docs/-/docs-9.0.0.tgz#37d785447de4230a92d742f7aa0560e702ae1671" - integrity sha512-wo7iImXCO+KRowEkYqPFK3Xkqi7k4tOBn3Mctye4BtPCRnwW1kIQuzAWJr7algXgE8OF3+jjMpy1A4tqTTrgLw== +"@abp/docs@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/docs/-/docs-9.0.3.tgz#a7f873a4c40d1e4b99442e1ccaf40db074778164" + integrity sha512-uVLMBqMTHDDmzQ5PZB9xkA/moHGeIRjqkUXED/6JtE6iphkNySR+Uw+nvRQGwfym6HgGbTHc+77Cc87P13ylxA== dependencies: - "@abp/anchor-js" "~9.0.0" - "@abp/clipboard" "~9.0.0" - "@abp/malihu-custom-scrollbar-plugin" "~9.0.0" - "@abp/popper.js" "~9.0.0" - "@abp/prismjs" "~9.0.0" + "@abp/anchor-js" "~9.0.3" + "@abp/clipboard" "~9.0.3" + "@abp/malihu-custom-scrollbar-plugin" "~9.0.3" + "@abp/popper.js" "~9.0.3" + "@abp/prismjs" "~9.0.3" -"@abp/font-awesome@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-9.0.0.tgz#51cce18434a51d050ebb0d822a8c193b7e32f73d" - integrity sha512-bEHRVzOk0V2kFMAvB/YXFYaDKWRFO0+ScO7jrEFtFgdHXBcqy+dk1aM/N69WalyNmjRRpnh03GhH1LRpwL4/xA== +"@abp/font-awesome@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-9.0.3.tgz#db6ece04fcdb4880cac8174deffb4fa04d43d2de" + integrity sha512-toI2zGayMeI4EUUMmittRWKBY/mLjiiX1zA2z70q/Hpm9zoCly2KwbN7zwtWbayhESO7edci9T7CXF5kNdiNow== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" "@fortawesome/fontawesome-free" "^6.6.0" -"@abp/jquery-form@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-9.0.0.tgz#6518eb117ee35c6bc57543fd1fccb0a8cf8e22f4" - integrity sha512-6qpfPGHrjADmm7Ne3zcXBGPuLngG8ldAn5fk9k2taftpyX0iR67/nZxiQ0ujaQGgaai5Wb3imNXMG96XLnubkQ== +"@abp/jquery-form@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-9.0.3.tgz#907033a496af127bfbe4e220f43cda65331e7b98" + integrity sha512-kD/9S25kJSAj5f7bfHVMfzfF5/0OrLDj8E55M27zbusdcaFS/nYElzrU3DeAEusASdnssRAwyJCWyp2vvRIMzA== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-9.0.0.tgz#8ee87dbdec5e92ab04ae9642a49af1598af31c1a" - integrity sha512-KlRQ1htyFmAIMS9xbHvHIcdhAgtPFe/u7c87MiWlk/CdhDrI3J+n5biLvQUzxCC+YWlwx27zBtPKx2vLeMRH6Q== +"@abp/jquery-validation-unobtrusive@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-9.0.3.tgz#b9449d222d4855f941dbe7445f73fff1541975a4" + integrity sha512-U/zUDYyJnUYDmmloOx/rViVyg7b4Gxd4zgcEi1E1YP0zryQQh5CkoSEvzv7GyurB1/rVZuRszmo/nRgn3pHOKg== dependencies: - "@abp/jquery-validation" "~9.0.0" + "@abp/jquery-validation" "~9.0.3" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-9.0.0.tgz#681d8037c57b3875bd51c927e350ffce65d7c5d2" - integrity sha512-XgCUBFWbhTDcGepD/xkwA/1Hl4TyYitRttauDJk3tuyXhNtykvdugCCOPY4j2s6VoT1Sb/TFFvOcgEvlBwkKiw== +"@abp/jquery-validation@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-9.0.3.tgz#02eb010267121fd9310cdcf5ca7c7b54091fd5de" + integrity sha512-tqjM5S0/jDS88CIt8oCQbHYJTytA7Y+KdTsnRkrA+BWy2A5bjrd6YUZXa/2UUwXHa3tQgv/uXl9HpDEaqRtfYQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" jquery-validation "^1.21.0" -"@abp/jquery@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-9.0.0.tgz#6c67a9f927be598ab6201f8888e8a3cb8bba0686" - integrity sha512-cUXA7v6F1HqAc/w8fQGxHIfA/6BqO+4uHXDnSsJZtrI26DTLOgw5kBNnAz2W4xggGPCY7SKFHNRZuOW/QfVRPg== +"@abp/jquery@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-9.0.3.tgz#ba8b4f4c0825ccb55c793024d3bbeb9ee817b19c" + integrity sha512-lZoyiyz83VOQKbN+9gD8JC+WI+OACc3y2hU+v30v220DLKhwoiVQM6d5yfA2QEugTPTjm/s40Ocmh/nVjm5mtg== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" jquery "~3.7.1" -"@abp/lodash@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-9.0.0.tgz#a4d42b2dfeb37859ff9f0079c7a8a6cabc982efe" - integrity sha512-xoAsOkkgnQdPH2OyFTgF6OlCLVbyGF4u8CrFcZWCZmuIFT9cg0+ZEiq//Vog1pg3Lf//N37ceGpOag1LwMe6cQ== +"@abp/lodash@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-9.0.3.tgz#daadfb98339f1d6982a12b655b840f105b324830" + integrity sha512-wt4ZJmRlhnK9W3o8lex7LyL/7rzgu4STLtZ2Ga8Ec5uZ9kN0CDTYjIw0zVqB8iGNKcW8MCxIGB7u9JMU9tphiQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" lodash "^4.17.21" -"@abp/luxon@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-9.0.0.tgz#a54dda13cbbd362481994d20d2e07c669c6ce109" - integrity sha512-aPejQnoVPfm60LnjwhfUkIsB+poNWusrBE0IHllxhvOVE+01nfa1Lvpp5AUWzV+XchPGOCSf+a29ds4EQWfNbg== +"@abp/luxon@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-9.0.3.tgz#62a5e395c92cadcf0985453eee065fcbe5354402" + integrity sha512-qTPw2vhgkgNldFJziHiL+c2vF7p+7RW1orev8K8XLSe3dguzlXBL2dicyDWCX9ay4zYfEpXbA0oKFgJvhBfK/w== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" luxon "^3.5.0" -"@abp/malihu-custom-scrollbar-plugin@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-9.0.0.tgz#b8a49202d531c711874a2bcdae5081ae0ed34c3f" - integrity sha512-iSZ9lKMnv5F2SplR+A4blhFEDq79lKbvBQuS7h/Di3KEHFOdJY4pA8NP8OR+PXOF3JfAezGyjqy7ydn/JNPRwA== +"@abp/malihu-custom-scrollbar-plugin@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-9.0.3.tgz#b3723f777b61c47ed64527b4295df33cca5cae26" + integrity sha512-aRearvDO1OaV7wfsWR2/cieWR/hG9cX78YGE08goRq0xLcGnBkmraQHSGGSZbMwId5zf5Kh3ePVXChO7llitkQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-9.0.0.tgz#9583e97472ddf608a9a5a6b8a37fee602b419900" - integrity sha512-wgyJSKmBmHLXZJN3dy3zK4i2JvWGgqnvdtrKURy/raEurxlymv8JF5JA971kTv6Kz2OA/OSYttpVJkDGUpPAhA== +"@abp/moment@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-9.0.3.tgz#696748659e036a1bf707684340703a861f340a92" + integrity sha512-7SRIjSHOyDTbuZ41vpisft+c5L+E3fk0G+V5+rpsACGTm6SpRqnVuAl5egbrDcm9rQeP0+VSV+/6UXdM7koOCQ== dependencies: moment "^2.30.1" -"@abp/popper.js@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-9.0.0.tgz#daeb5d966af64cb386633f0b17d68ab01af41557" - integrity sha512-Uc9yxvIySV/iDtXdPSb8J2wlw6OhnsC5pKRLdvV7lmFjxawVJF0eJQ7Wg5k77tMirEESL3VZMT2mekwT47c8zA== +"@abp/popper.js@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-9.0.3.tgz#cc383e54900aa087a5c97ffbc99733cd17ed9554" + integrity sha512-AVc7zBa/wU/fg0NSR90TV1zHYXXr/L5OxvoTK//YVNIERzd0/VhgYQ9ltzU2XmZZrCtrlkfAo4d1cOnrm773sg== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" "@popperjs/core" "^2.11.8" -"@abp/prismjs@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-9.0.0.tgz#2ac4a5ddc0c8e0462af027a4dd97a177a3d6f7a5" - integrity sha512-+evmBq5qV+Ix/TNqO2ljJn0jygW2rEgKNtABcBGmrZC8xiI7+2hjKmSE0e43qb+RDib8aJjwH8ya8wF7p68ZFw== +"@abp/prismjs@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-9.0.3.tgz#9675dacd5df511120dffde76e925a8344ca9bace" + integrity sha512-5nMAKRADa6BQh3orquGHCUzqoZyDhmvoQ7RBCJsM3e0W+wBPeV9qmF+dJjOf9d+c9dr2mpU2SrH45fO/BNqgWg== dependencies: - "@abp/clipboard" "~9.0.0" - "@abp/core" "~9.0.0" + "@abp/clipboard" "~9.0.3" + "@abp/core" "~9.0.3" prismjs "^1.29.0" -"@abp/select2@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-9.0.0.tgz#71503eebb746f40a0e565dba1efa05d6ae937b77" - integrity sha512-l+Hz2Qmc7AcyaTJOyuDaV99RbBWYxYxbeyVEc4xKbRFbJxYBUxT/DFlyozEaoz+OU1fl4szO3UCthJ2yZvxCdg== +"@abp/select2@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-9.0.3.tgz#d75afa45decc4ce5a25ecb257f76a9f4c4c1b1fd" + integrity sha512-Cs/cQHdqV0U/c3RXbTTfIQoTaJqjBZMGon8cHX6rmvCDhWKOU/B2E+R2wNlcMjl0Tkhfy3Jl1mwlD0KZ/LePbA== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" select2 "^4.0.13" -"@abp/sweetalert2@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-9.0.0.tgz#71e511117c18b515e5c658e9cc0667770af65d91" - integrity sha512-wxDqoA6RUhkbcN27Kzslu/m2ai5T9AkJRtgs7pV55LvPv1ohuCPEUeTVVh8S/5Pgmr7aPFAPxaaCO32aPcImPw== +"@abp/sweetalert2@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-9.0.3.tgz#b7619bcf664b7aaf51f096df1b60f604db36c84c" + integrity sha512-W9fQu1jGhzqliyqKU/OFtAHiS3b8kar3LN9Pij2g/Q8zS2esjdgdt4LoaI5SytRi7zXUqn2tCi/cDEDXf/X3zQ== dependencies: - "@abp/core" "~9.0.0" + "@abp/core" "~9.0.3" sweetalert2 "^11.14.1" -"@abp/timeago@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-9.0.0.tgz#8f262ba2a74e1c133d0c129f8a0bc55c29441e1a" - integrity sha512-eaNdIUEtDXnuYqS5O7EGRV+j6XOLAsxz7gd5BIKR9lDujd8pEjSJkXKO+/0KxLTkUSy3JudIO37K1h2wTpbYZA== +"@abp/timeago@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-9.0.3.tgz#8e675aafcc862643303909c27bbb982aa1c3c22c" + integrity sha512-B2ZHs4IfZyy+YHLlS/KLhxbqvxTkEEz1y+Sk8HtVSk0Ula5UD+U1FvwcxOiYGIghmHlkqFBtegifZosPOMB9VQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" timeago "^1.6.7" -"@abp/toastr@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-9.0.0.tgz#74d74ecb3e7f2d63b2ff7735c89a40deb3dc3a13" - integrity sha512-n9uJvw8l2HoxeowPO4diEMxwchGh6puefhyRljFAAstzK77Mhn0u2J7kPynNv94rLSYTYoCxUeJ4TTN+/TVDGQ== +"@abp/toastr@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-9.0.3.tgz#1863fea4d709b524f8fea0233dfd0ff8b1bc5bb2" + integrity sha512-3eV17OBB8XLJC9dZWW6xzi4fpu5NZqNrRiKiYJf79IUvmuS0cBVKpNJVKRaDTp4DeDBHxCQOWmMnrNI/mh3bDQ== dependencies: - "@abp/jquery" "~9.0.0" + "@abp/jquery" "~9.0.3" toastr "^2.1.4" -"@abp/utils@~9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-9.0.0.tgz#2264fd83105b061860edd98dbe0efdfe55803ed5" - integrity sha512-/SB4VYk3Av+ltXv6ovkFHG4iWiECLLitaYWcEHxVAO80ufNjrNnWpHFpXHP8u/RpXIp/CoNiHLr6j5TkZUTZjw== +"@abp/utils@~9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-9.0.3.tgz#02dfd981d210be8229d65f802f0721b42c9e17d7" + integrity sha512-45sQQ6IV3cB9KAIVtRn3IH1wVr9DfM0PMFiwVt3F+wKwNBAPsKNcMPt4/EhZCNY3IMaEseJ7K/lxt33O3sURuA== dependencies: just-compare "^2.3.0" diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/IDocumentAdminAppService.cs b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/IDocumentAdminAppService.cs index 12bdbc87be..8931cfa5fd 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/IDocumentAdminAppService.cs +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/IDocumentAdminAppService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; +using Volo.Docs.Admin.Projects; namespace Volo.Docs.Admin.Documents { @@ -21,5 +22,7 @@ namespace Volo.Docs.Admin.Documents Task ReindexAsync(Guid documentId); Task> GetFilterItemsAsync(); + + Task> GetProjectsAsync(); } } diff --git a/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs b/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs index 8e70be19e4..a6359c2c9b 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs +++ b/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs @@ -8,6 +8,7 @@ using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; using Volo.Abp.Caching; +using Volo.Docs.Admin.Projects; using Volo.Docs.Caching; using Volo.Docs.Documents; using Volo.Docs.Documents.FullSearch.Elastic; @@ -220,6 +221,12 @@ namespace Volo.Docs.Admin.Documents return ObjectMapper.Map, List>(documents); } + public virtual async Task> GetProjectsAsync() + { + var projects = await _projectRepository.GetListWithoutDetailsAsync(); + return ObjectMapper.Map, List>(projects); + } + private async Task UpdateDocumentUpdateInfoCache(Document document) { diff --git a/modules/docs/src/Volo.Docs.Admin.HttpApi.Client/ClientProxies/Volo/Docs/Admin/DocumentsAdminClientProxy.Generated.cs b/modules/docs/src/Volo.Docs.Admin.HttpApi.Client/ClientProxies/Volo/Docs/Admin/DocumentsAdminClientProxy.Generated.cs index 8a6e2b7173..b113d74451 100644 --- a/modules/docs/src/Volo.Docs.Admin.HttpApi.Client/ClientProxies/Volo/Docs/Admin/DocumentsAdminClientProxy.Generated.cs +++ b/modules/docs/src/Volo.Docs.Admin.HttpApi.Client/ClientProxies/Volo/Docs/Admin/DocumentsAdminClientProxy.Generated.cs @@ -9,6 +9,7 @@ using Volo.Abp.Http.Client; using Volo.Abp.Http.Client.ClientProxying; using Volo.Abp.Http.Modeling; using Volo.Docs.Admin.Documents; +using Volo.Docs.Admin.Projects; // ReSharper disable once CheckNamespace namespace Volo.Docs.Admin; @@ -69,4 +70,9 @@ public partial class DocumentsAdminClientProxy : ClientProxyBase>(nameof(GetFilterItemsAsync)); } + + public virtual async Task> GetProjectsAsync() + { + return await RequestAsync>(nameof(GetProjectsAsync)); + } } diff --git a/modules/docs/src/Volo.Docs.Admin.HttpApi.Client/ClientProxies/docs-admin-generate-proxy.json b/modules/docs/src/Volo.Docs.Admin.HttpApi.Client/ClientProxies/Volo/Docs/Admin/docs-admin-generate-proxy.json similarity index 97% rename from modules/docs/src/Volo.Docs.Admin.HttpApi.Client/ClientProxies/docs-admin-generate-proxy.json rename to modules/docs/src/Volo.Docs.Admin.HttpApi.Client/ClientProxies/Volo/Docs/Admin/docs-admin-generate-proxy.json index 7875b68b43..e433e06ef9 100644 --- a/modules/docs/src/Volo.Docs.Admin.HttpApi.Client/ClientProxies/docs-admin-generate-proxy.json +++ b/modules/docs/src/Volo.Docs.Admin.HttpApi.Client/ClientProxies/Volo/Docs/Admin/docs-admin-generate-proxy.json @@ -125,6 +125,14 @@ "type": "System.Collections.Generic.List", "typeSimple": "[Volo.Docs.Admin.Documents.DocumentInfoDto]" } + }, + { + "name": "GetProjectsAsync", + "parametersOnMethod": [], + "returnValue": { + "type": "System.Collections.Generic.List", + "typeSimple": "[Volo.Docs.Admin.Projects.ProjectWithoutDetailsDto]" + } } ] } @@ -558,6 +566,21 @@ }, "allowAnonymous": null, "implementFrom": "Volo.Docs.Admin.Documents.IDocumentAdminAppService" + }, + "GetProjectsAsync": { + "uniqueName": "GetProjectsAsync", + "name": "GetProjectsAsync", + "httpMethod": "GET", + "url": "api/docs/admin/documents/GetProjects", + "supportedVersions": [], + "parametersOnMethod": [], + "parameters": [], + "returnValue": { + "type": "System.Collections.Generic.List", + "typeSimple": "[Volo.Docs.Admin.Projects.ProjectWithoutDetailsDto]" + }, + "allowAnonymous": null, + "implementFrom": "Volo.Docs.Admin.Documents.IDocumentAdminAppService" } } }, diff --git a/modules/docs/src/Volo.Docs.Admin.HttpApi.Client/Volo.Docs.Admin.HttpApi.Client.abppkg b/modules/docs/src/Volo.Docs.Admin.HttpApi.Client/Volo.Docs.Admin.HttpApi.Client.abppkg index 7deef5e383..c78fc5b9ee 100644 --- a/modules/docs/src/Volo.Docs.Admin.HttpApi.Client/Volo.Docs.Admin.HttpApi.Client.abppkg +++ b/modules/docs/src/Volo.Docs.Admin.HttpApi.Client/Volo.Docs.Admin.HttpApi.Client.abppkg @@ -1,3 +1,15 @@ { - "role": "lib.http-api-client" + "role": "lib.http-api-client", + "proxies": { + "csharp": { + "VoloDocs.Web-docs-admin": { + "applicationName": "VoloDocs.Web", + "module": "docs-admin", + "url": "https://localhost:5001", + "folder": "ClientProxies\\Volo\\Docs\\Admin", + "serviceType": "application", + "withoutContracts": true + } + } + } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.HttpApi/Volo/Docs/Admin/DocumentsAdminController.cs b/modules/docs/src/Volo.Docs.Admin.HttpApi/Volo/Docs/Admin/DocumentsAdminController.cs index eb78095bf2..ddf303ffaf 100644 --- a/modules/docs/src/Volo.Docs.Admin.HttpApi/Volo/Docs/Admin/DocumentsAdminController.cs +++ b/modules/docs/src/Volo.Docs.Admin.HttpApi/Volo/Docs/Admin/DocumentsAdminController.cs @@ -7,6 +7,7 @@ using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.AspNetCore.Mvc; using Volo.Docs.Admin.Documents; +using Volo.Docs.Admin.Projects; namespace Volo.Docs.Admin { @@ -71,5 +72,12 @@ namespace Volo.Docs.Admin { return await _documentAdminAppService.GetFilterItemsAsync(); } + + [HttpGet] + [Route("GetProjects")] + public virtual Task> GetProjectsAsync() + { + return _documentAdminAppService.GetProjectsAsync(); + } } } diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml.cs b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml.cs index 194ad6780f..53d78baf39 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml.cs @@ -2,23 +2,24 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Volo.Docs.Admin.Documents; using Volo.Docs.Admin.Projects; namespace Volo.Docs.Admin.Pages.Docs.Admin.Documents; -[Authorize(DocsAdminPermissions.Projects.Default)] +[Authorize(DocsAdminPermissions.Documents.Default)] public class IndexModel : DocsAdminPageModel { - private readonly IProjectAdminAppService _projectAdminAppService; + private readonly IDocumentAdminAppService _documentAdminAppService; public List Projects { get; set; } - public IndexModel(IProjectAdminAppService projectAdminAppService) + public IndexModel(IDocumentAdminAppService documentAdminAppService) { - _projectAdminAppService = projectAdminAppService; + _documentAdminAppService = documentAdminAppService; } public virtual async Task OnGet() { - Projects = await _projectAdminAppService.GetListWithoutDetailsAsync(); + Projects = await _documentAdminAppService.GetProjectsAsync(); return Page(); } } diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.abppkg b/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.abppkg index 930c4018b3..4e12d28da1 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.abppkg +++ b/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.abppkg @@ -1,3 +1,14 @@ { - "role": "lib.mvc" + "role": "lib.mvc", + "proxies": { + "Javascript": { + "VoloDocs.Web-docs-admin": { + "applicationName": "VoloDocs.Web", + "module": "docs-admin", + "url": "https://localhost:5001", + "output": "wwwroot/client-proxies", + "serviceType": "application" + } + } + } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.Web/wwwroot/client-proxies/docs-admin-proxy.js b/modules/docs/src/Volo.Docs.Admin.Web/wwwroot/client-proxies/docs-admin-proxy.js index 303ca5fdb0..3a1c6930cf 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/wwwroot/client-proxies/docs-admin-proxy.js +++ b/modules/docs/src/Volo.Docs.Admin.Web/wwwroot/client-proxies/docs-admin-proxy.js @@ -68,6 +68,13 @@ }, ajaxParams)); }; + volo.docs.admin.documentsAdmin.getProjects = function(ajaxParams) { + return abp.ajax($.extend(true, { + url: abp.appPath + 'api/docs/admin/documents/GetProjects', + type: 'GET' + }, ajaxParams)); + }; + })(); // controller volo.docs.admin.projectsAdmin diff --git a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/NavigationNode.cs b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/NavigationNode.cs index 2f1afce342..f19b9f41df 100644 --- a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/NavigationNode.cs +++ b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/NavigationNode.cs @@ -15,6 +15,12 @@ namespace Volo.Docs.Documents [JsonPropertyName("items")] public List Items { get; set; } + + [JsonPropertyName("isLazyExpandable")] + public bool IsLazyExpandable { get; set; } + + [JsonPropertyName("isIndex")] + public bool IsIndex { get; set; } public bool IsLeaf => !HasChildItems; diff --git a/modules/docs/src/Volo.Docs.Web/Areas/Documents/DocumentNavigationController.cs b/modules/docs/src/Volo.Docs.Web/Areas/Documents/DocumentNavigationController.cs new file mode 100644 index 0000000000..cac52e8d5f --- /dev/null +++ b/modules/docs/src/Volo.Docs.Web/Areas/Documents/DocumentNavigationController.cs @@ -0,0 +1,80 @@ +using System; +using System.Threading.Tasks; +using Asp.Versioning; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Docs.Areas.Models.DocumentNavigation; +using Volo.Docs.Documents; +using Volo.Docs.Utils; + +namespace Volo.Docs.Areas.Documents; + +[RemoteService(Name = DocsRemoteServiceConsts.RemoteServiceName)] +[Area(DocsRemoteServiceConsts.ModuleName)] +[ControllerName("DocumentNavigation")] +[Route("/docs/document-navigation")] +public class DocumentNavigationController : AbpController +{ + private readonly IDocumentAppService _documentAppService; + private readonly IDocsLinkGenerator _docsLinkGenerator; + + public DocumentNavigationController(IDocumentAppService documentAppService, IDocsLinkGenerator docsLinkGenerator) + { + _documentAppService = documentAppService; + _docsLinkGenerator = docsLinkGenerator; + } + + [HttpGet] + [Route("")] + public virtual async Task GetNavigationAsync(GetNavigationNodeWithLinkModel input) + { + var navigationNode = await _documentAppService.GetNavigationAsync(new GetNavigationDocumentInput + { + LanguageCode = input.LanguageCode, + Version = input.Version, + ProjectId = input.ProjectId + }); + + NormalPath(navigationNode, input); + + return navigationNode; + } + + protected virtual void NormalPath(NavigationNode node, GetNavigationNodeWithLinkModel input) + { + if (node.HasChildItems) + { + foreach (var item in node.Items) + { + NormalPath(item, input); + } + } + + if (UrlHelper.IsExternalLink(node.Path)) + { + return; + } + + node.Path = RemoveFileExtensionFromPath(node.Path, input.ProjectFormat); + if (node.Path.IsNullOrWhiteSpace()) + { + node.Path = "javascript:;"; + return; + } + + node.Path = _docsLinkGenerator.GenerateLink(input.ProjectName, input.LanguageCode, input.RouteVersion, node.Path); + } + + private string RemoveFileExtensionFromPath(string path, string projectFormat) + { + if (path == null) + { + return null; + } + + return path.EndsWith("." + projectFormat) + ? path.Left(path.Length - projectFormat.Length - 1) + : path; + } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs b/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs index 6d2cb8c9ba..e6c75d0161 100644 --- a/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs +++ b/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs @@ -72,11 +72,14 @@ namespace Volo.Docs.Areas.Documents.TagHelpers var isAnyNodeOpenedInThisLevel = IsAnyNodeOpenedInThisLevel(node); - node.Items?.ForEach(innerNode => + if (!node.IsLazyExpandable || isAnyNodeOpenedInThisLevel) { - content += GetParentNode(innerNode, isAnyNodeOpenedInThisLevel); - }); - + node.Items?.ForEach(innerNode => + { + content += GetParentNode(innerNode, isAnyNodeOpenedInThisLevel); + }); + } + var result = node.IsEmpty ? content : GetLeafNode(node, content); return result; @@ -121,6 +124,11 @@ namespace Volo.Docs.Areas.Documents.TagHelpers listItemCss += " selected-tree"; } + if (node.IsLazyExpandable) + { + listItemCss += " lazy-expand"; + } + string listInnerItem; if (node.Path.IsNullOrEmpty() && node.IsLeaf) { diff --git a/modules/docs/src/Volo.Docs.Web/Areas/Models/DocumentNavigation/GetNavigationNodeWithLinkModel.cs b/modules/docs/src/Volo.Docs.Web/Areas/Models/DocumentNavigation/GetNavigationNodeWithLinkModel.cs new file mode 100644 index 0000000000..085cc2de0e --- /dev/null +++ b/modules/docs/src/Volo.Docs.Web/Areas/Models/DocumentNavigation/GetNavigationNodeWithLinkModel.cs @@ -0,0 +1,28 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Volo.Abp.Validation; +using Volo.Docs.Language; +using Volo.Docs.Projects; + +namespace Volo.Docs.Areas.Models.DocumentNavigation; + +public class GetNavigationNodeWithLinkModel +{ + public Guid ProjectId { get; set; } + + [DynamicStringLength(typeof(ProjectConsts), nameof(ProjectConsts.MaxVersionNameLength))] + public string Version { get; set; } + + [Required] + [DynamicStringLength(typeof(LanguageConsts), nameof(LanguageConsts.MaxLanguageCodeLength))] + public string LanguageCode { get; set; } + + [Required] + public string ProjectName { get; set; } + + [Required] + public string ProjectFormat { get; set; } + + [Required] + public string RouteVersion { get; set; } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml index 2167119a0d..fb1f232cb3 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml @@ -16,6 +16,7 @@ @using Volo.Abp.AspNetCore.Mvc.UI.Theming @using Volo.Docs @using Volo.Docs.Areas.Documents.TagHelpers +@using Volo.Docs.Documents @using Volo.Docs.Localization @using Volo.Docs.Pages.Documents.Project @using Volo.Docs.Pages.Documents.Shared.ErrorComponent @@ -52,6 +53,17 @@ @if (Model.LoadSuccess) { + @@ -292,6 +304,73 @@ + @if (Model.Navigation != null && Model.Navigation.Items != null) + { + var currentNode = Model.Navigation.Items.FirstOrDefault(n => n.IsSelected(Model.DocumentNameWithExtension)); + var navigation = Model.Navigation.FindNavigation(Model.DocumentNameWithExtension); + var documentExtension = System.IO.Path.GetExtension(Model.DocumentNameWithExtension); + NavigationNode previousNode = null; + if (currentNode != null) + { + while (currentNode != null) + { + if (!string.IsNullOrWhiteSpace(currentNode.Path) && previousNode != currentNode) + { + previousNode = currentNode; + PrintNode(currentNode); + } + else + { + var indexDocument = FindIndexNode(currentNode); + if (indexDocument != null && indexDocument != previousNode) + { + PrintNode(indexDocument, currentNode.Text); + } + else if(currentNode != previousNode) + { + + } + + previousNode = indexDocument; + } + + currentNode = currentNode?.Items?.FirstOrDefault(n => n.IsSelected(Model.DocumentNameWithExtension)); + } + } + + void PrintNode(NavigationNode node, string text = null) + { + var extension = System.IO.Path.GetExtension(node.Path); + var path = string.IsNullOrWhiteSpace(extension) ? node.Path : node.Path.Substring(0, node.Path.Length - extension.Length); + + } + + NavigationNode FindIndexNode(NavigationNode node) + { + var nextNode = node.Items?.FirstOrDefault(n => n.IsSelected(Model.DocumentNameWithExtension)); + while (nextNode != null && string.IsNullOrEmpty(nextNode.Path)) + { + nextNode = nextNode.Items.FirstOrDefault(n => n.IsSelected(Model.DocumentNameWithExtension)); + } + + if(nextNode == null) + { + return node.Items?.FirstOrDefault(n => n.IsIndex); + } + + var lastSlashIndex = nextNode.Path.LastIndexOf('/'); + var path = lastSlashIndex < 0 ? nextNode.Path : nextNode.Path.Substring(0, lastSlashIndex); + return node.Items.FirstOrDefault(n => n.Path != null && (n.IsSelected(path + documentExtension) || n.IsSelected(path + "/index" + documentExtension))) ?? node.Items.FirstOrDefault(n => n.IsIndex); + } + }