diff --git a/Directory.Packages.props b/Directory.Packages.props index 09c9a476fd..7263869bdf 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -34,6 +34,9 @@ + + + @@ -182,4 +185,4 @@ - + \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json index b81733d247..456e8ebd05 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json @@ -987,7 +987,7 @@ "Summary": "Summary", "TrainingPack": "Training pack", "TrainingPackDiscount": "Training pack discount", - "Purchase_OnboardingTraining_Description": "This live training is valid for a class of 8 students and this discount is only valid when purchased with the new license. Learn more ", + "Purchase_OnboardingTraining_Description": "This live training is valid for a class of 8 students and this discount is only valid when purchased with the new license. Learn more ", "Purchase_Save": "{0}% Save {1}", "RemoveBasket": "Remove from basket", "WhyABPIOPlatform?": "Why ABP.IO Platform?", diff --git a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/POST.md b/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/POST.md index 03caff8007..98685933ba 100644 --- a/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/POST.md +++ b/docs/en/Community-Articles/2020-09-09-Replacing-Email-Template-and-Sending-Emails/POST.md @@ -470,6 +470,12 @@ namespace TemplateReplace * I highly recommend you to [check it out](https://commercial.abp.io/modules/Volo.TextTemplateManagement). +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## References * [Text Templating](https://docs.abp.io/en/abp/latest/Text-Templating) diff --git a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/POST.md b/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/POST.md index 6445d83e85..f946db23b9 100644 --- a/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/POST.md +++ b/docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/POST.md @@ -4,6 +4,12 @@ Hi, in this step by step article, we will see how we can integrate the Telerik Blazor Components to our Blazor UI. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Creating the Solution > ABP Framework offers startup templates to get into business faster. @@ -532,6 +538,12 @@ namespace TelerikComponents.Blazor.Pages ![final-result](./final-result.jpg) +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Conclusion In this article, I've tried to explain how we can integrate [Telerik Blazor Component](https://www.telerik.com/blazor-ui) to our Blazor UI. ABP Framework designed as modular, so that it can work with any UI library/framework. \ No newline at end of file diff --git a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/POST.md b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/POST.md index 0ec86d40c4..2f5cde6d2d 100644 --- a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/POST.md +++ b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/POST.md @@ -1,11 +1,19 @@ # Using Elsa Workflow with ABP Framework +> **UPDATE:** There is a newer version of this article, which covers Elsa 3 with ABP Framework. You can check it at https://abp.io/community/articles/using-elsa-3-workflow-with-abp-framework-usqk8afg + **Elsa Core** is an open-source workflows library that can be used in any kind of .NET Core application. Using such a workflow library can be useful to implement business rules visually or programmatically. ![elsa-overview](./elsa-overview.gif) This article shows how we can use this workflow library within our ABP-based application. We will start with a couple of examples and then we will integrate the **Elsa Dashboard** (you can see it in the above gif) into our application to be able to design our workflows visually. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Source Code You can find the source of the example solution used in this article [here](https://github.com/abpframework/abp-samples/tree/master/ElsaDemo). @@ -453,4 +461,10 @@ namespace ElsaDemo.Web.Menus * Now we can click the "Workflow" menu item, display the Elsa Dashboard and designing workflows. -![elsa-demo-result](./elsa-demo-result.gif) \ No newline at end of file +![elsa-demo-result](./elsa-demo-result.gif) + +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- diff --git a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/POST.md b/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/POST.md index ae688d6981..81c0b6ea34 100644 --- a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/POST.md +++ b/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/POST.md @@ -10,6 +10,12 @@ You can see the ER Diagram of our application below. This diagram will be helpfu When we've examined the ER Diagram, we can see the one-to-many relationship between the **Author** and the **Book** tables. Also, the many-to-many relationship (**BookCategory** table) between the **Book** and the **Category** tables. (There can be more than one category on each book and vice-versa in our scenario). +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ### Source Code You can find the source code of the application at https://github.com/EngincanV/ABP-Many-to-Many-Relationship-Demo . @@ -1664,6 +1670,12 @@ namespace BookStore.Web.Pages.Books ![Edit Book Modal](./book-update-modal.png) +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ### Conclusion In this article, I've tried to explain how to create a many-to-many relationship by using the ABP framework. (by following DDD principles) diff --git a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/POST.md b/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/POST.md index 4095c62092..2953c552d3 100644 --- a/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/POST.md +++ b/docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/POST.md @@ -4,6 +4,12 @@ In this article we will see how we can integrate the Syncfusion MVC Components into our ABP application. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Source Code You can find the source code of the application at https://github.com/EngincanV/ABP-Syncfusion-Components-Demo. @@ -314,6 +320,12 @@ After injecting the Syncfusion style and script into our application, our config ![](./calendar-component.png) +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ### Conclusion In this article, we've explained how to integrate the **Syncfusion Components** into our applications. After following this article, you can use the Syncfusion components in your application. diff --git a/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/POST.md b/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/POST.md index a6ad7596d4..d4102d8256 100644 --- a/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/POST.md +++ b/docs/en/Community-Articles/2022-02-06-How-to-Hide-ABP-Related-Endpoints-on-Swagger-UI/POST.md @@ -8,6 +8,12 @@ In the issue, there are helpful comments about hiding the ABP related endpoints I thought it would be better to enable/disable showing endpoints on runtime by simply selecting a checkbox on the Setting Management page and in this article I wanted to show you how, so let's dive in. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Source Code You can find the source code of the application at https://github.com/EngincanV/ABP-Hide-Swagger-Endpoint-Demo. @@ -445,6 +451,10 @@ services.AddAbpSwaggerGen( > For more info, please see the [Swagger Integration](https://docs.abp.io/en/abp/latest/API/Swagger-Integration#hide-abp-endpoints-on-swagger-ui) docs. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv --- Thanks for reading. \ No newline at end of file diff --git a/docs/en/Community-Articles/2022-04-06-Concurrency-Check-in-ABP-Based-Applications/POST.md b/docs/en/Community-Articles/2022-04-06-Concurrency-Check-in-ABP-Based-Applications/POST.md index b16d6bd9f1..cf8c3c122a 100644 --- a/docs/en/Community-Articles/2022-04-06-Concurrency-Check-in-ABP-Based-Applications/POST.md +++ b/docs/en/Community-Articles/2022-04-06-Concurrency-Check-in-ABP-Based-Applications/POST.md @@ -2,6 +2,12 @@ In this article, we'll create a basic application to demonstrate how "Concurrency Check/Control" can be implemented in an ABP project. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Creating the Solution For this article, we will create a simple BookStore application and add CRUD functionality to the pages. Hence we deal with the concurrency situation. @@ -546,3 +552,9 @@ Then we can run the application, navigate to the **/Books** endpoint and see the * After the first user updated the record, the second user tries to update the same record without getting the last state of the record. And therefore `AbpDbConcurrencyException` is thrown because **ConcurrencyStamp** values are different from each other. * The second user should close and re-open the model to get the last state of the record and then they can make changes to the current record. + +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- \ No newline at end of file diff --git a/docs/en/Community-Articles/2022-11-22-gRPC-JSON-Transcoding/POST.md b/docs/en/Community-Articles/2022-11-22-gRPC-JSON-Transcoding/POST.md index 9663ac1f83..e0f1f38aa9 100644 --- a/docs/en/Community-Articles/2022-11-22-gRPC-JSON-Transcoding/POST.md +++ b/docs/en/Community-Articles/2022-11-22-gRPC-JSON-Transcoding/POST.md @@ -4,6 +4,12 @@ In this article, I'll show you one of the new features that came with .NET 7: ** > I've created a community article to highlight some interesting features (briefly) that are now available with the release of .NET 7. You can check it from [here](https://community.abp.io/posts/whats-new-with-.net-7-tlq2g43w). +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## What is gRPC? What are the pros and cons? [gRPC](https://grpc.io/) is a high-performance RPC framework and uses HTTP/2 and Protobuf (protocol buffers). @@ -189,6 +195,12 @@ In this article, I've briefly introduced the JSON Transcoding feature that was s > See the [gRPC JSON transcoding in ASP.NET Core gRPC apps](https://learn.microsoft.com/en-us/aspnet/core/grpc/json-transcoding?view=aspnetcore-7.0) documentation for more information. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## References * https://devblogs.microsoft.com/dotnet/announcing-grpc-json-transcoding-for-dotnet/ diff --git a/docs/en/Community-Articles/2022-18-11-whats-new-with-net7/POST.md b/docs/en/Community-Articles/2022-18-11-whats-new-with-net7/POST.md index fb964fa730..671592d63e 100644 --- a/docs/en/Community-Articles/2022-18-11-whats-new-with-net7/POST.md +++ b/docs/en/Community-Articles/2022-18-11-whats-new-with-net7/POST.md @@ -6,6 +6,12 @@ In this article, I will highlight a few interesting features that are now availa There are many new features coming with this release. We are going to examine new features within 4 sub-sections: ASP.NET Core, C#11, EF 7 and MAUI. Let's start with ASP.NET Core. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## ASP.NET Core We will see the following features in this section: @@ -290,6 +296,12 @@ In this article, I've highlighted some features that were shipped with .NET 7. Thanks for reading the article and I hope you find it helpful. See you in the next one! +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## References * https://devblogs.microsoft.com/dotnet/announcing-dotnet-7/ diff --git a/docs/en/Community-Articles/2023-07-03-Image-Compression-And-Resize/POST.md b/docs/en/Community-Articles/2023-07-03-Image-Compression-And-Resize/POST.md index 9023417b20..1a1aa55f97 100644 --- a/docs/en/Community-Articles/2023-07-03-Image-Compression-And-Resize/POST.md +++ b/docs/en/Community-Articles/2023-07-03-Image-Compression-And-Resize/POST.md @@ -8,6 +8,12 @@ ABP Framework provides services to compress and resize images and implements the > Refer to the documentation for more info: [Image Manipulation](https://docs.abp.io/en/abp/7.3/Image-Manipulation) +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ### Source Code You can find the source code of the application at [https://github.com/abpframework/abp-samples/tree/master/ImageManipulation](https://github.com/abpframework/abp-samples/tree/master/ImageManipulation). Don't hesitate to check the source code, if you are stuck on any point. @@ -281,6 +287,12 @@ The results are impressive for the example above: * The original image was 12 KB and now the compressed & resized image has been reduced to 8 KB. * The original image was 225x225 and now resized as 200x200. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Conclusion In this article, I have shown you how to compress and/or resize images with ABP Framework's Image Manipulation System by just defining some attributes to the top of the controller actions. diff --git a/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/POST.md b/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/POST.md index 38cfab67e9..3d62f38886 100644 --- a/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/POST.md +++ b/docs/en/Community-Articles/2023-08-12-ABP-Commercial-GDPR-Module-Overview/POST.md @@ -2,6 +2,12 @@ In this article, I will highlight ABP Commercial's [GDPR Module](https://commercial.abp.io/modules/Volo.Gdpr) and show you how to provide personal data to the GDPR Module that is collected by your application and make it aligned with personal data download results. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## GDPR Module [GDPR Module](https://docs.abp.io/en/commercial/latest/modules/gdpr) allows users to download and delete the data collected by the application. It's used for Personal Data Management obligating by the GDPR regulation and provides the following features: @@ -143,6 +149,12 @@ Then, when we clicked the download button, a zip file will be downloaded. We can {"City":"Istanbul","Postcode":"..."} ``` +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Conclusion In this article, I've explained the GDPR Module's data collection system and given you a brief overview of the module. GDPR Module allows you to request to download personal data, delete/anonymize your own personal data and delete your account permanently. It's pre-installed in the Application and Application(single layer) Pro Startup templates, but you can easily configure it to your existing application. diff --git a/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/POST.md b/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/POST.md index 9142a36be9..9586b51cf5 100644 --- a/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/POST.md +++ b/docs/en/Community-Articles/2023-10-30-NET-8-ASP-NET-Core-Metrics/POST.md @@ -2,6 +2,12 @@ In this article, I'll show you the new built-in metrics of .NET 8, which are basically numerical measurements reported over time in your application and can be used to monitor the health of your application and generate reports according to those numerical values. We will see what the metrics are, why to use them, and how to use them in detail. So, let's dive in. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## What are Metrics? Metrics are numerical measurements reported over time. These measurements are crucial for monitoring the health of an application and generating alerts when necessary. In the context of a web service, various metrics can be tracked, such as: @@ -185,6 +191,12 @@ If you want to build a dashboard, you can see [this documentation from Microsoft Also, you can check [this video](https://www.youtube.com/watch?v=A2pKhNQoQUU) if you want to see it in action. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Conclusion In this article, I've shown you the new built-in metrics of .NET8, described what metrics are, which pre-built metrics are provided by ASP.NET Core, how to use & view these pre-built metrics and finally, I have shown you how to create custom metrics and view them in a dashboard. If you want to learn more about the **Metrics System of .NET**, you can check out the references that I have shared below. diff --git a/docs/en/Community-Articles/2023-11-16-Upgrading-Your-Existing-Projects-to-NET8/POST.md b/docs/en/Community-Articles/2023-11-16-Upgrading-Your-Existing-Projects-to-NET8/POST.md index 3ed34680c1..c247f1e3e9 100644 --- a/docs/en/Community-Articles/2023-11-16-Upgrading-Your-Existing-Projects-to-NET8/POST.md +++ b/docs/en/Community-Articles/2023-11-16-Upgrading-Your-Existing-Projects-to-NET8/POST.md @@ -4,6 +4,12 @@ A new .NET version was released on November 14, 2023 and ABP 8.0 RC.1 shipped ba Despite all the related dependency upgrades and changes made on the ABP Framework and ABP Commercial sides, we still need to make some changes. Let's see the required actions that need to be taken in the following sections. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Installing the .NET 8.0 SDK To get started with ASP.NET Core in .NET 8.0, you need to install the .NET 8 SDK. You can install it at [https://dotnet.microsoft.com/en-us/download/dotnet/8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0). @@ -99,3 +105,9 @@ After that, you need to check the migration guide documents, listed below: That's it! These were all the related steps that need to be taken to upgrade your application to .NET 8 and ABP 8.0. Now, you can enjoy the .NET 8 & ABP 8.0 and benefit from the performance improvements and new features. Happy Coding 🤗 + +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- \ No newline at end of file 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 d100838b1a..aeefc03c0c 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 @@ -2,6 +2,12 @@ In this post, I describe the new **"keyed service"** support for the dependency injection container, which came with .NET 8.0. Then, I'll show you an example of usage within the ABP Framework. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## What Are Keyed Services? ASP.NET Core ships with a built-in dependency injection container, which is a pretty basic DI container that supports minimal features a dependency injection container is supposed to have. For that reason, most of the .NET users use third-party containers like [Autofac](https://autofac.org/), or Ninject. @@ -203,6 +209,12 @@ public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransie Please refer to the [Dependency Injection document](https://abp.io/docs/latest/framework/fundamentals/dependency-injection#exposekeyedservice-attribute) for further info. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Summary In this post, I described the new **"keyed service"** support added to the built-in dependency injection container that was released in .NET 8, and wanted to announce it's being supported from v8.0.2 for ABP Framework. It's a really good addition to the built-in dependency injection container and can be pretty useful for certain use-cases. diff --git a/docs/en/Community-Articles/2024-04-19-Performing-Case-Insensitive-Search-In-Postgresql/POST.md b/docs/en/Community-Articles/2024-04-19-Performing-Case-Insensitive-Search-In-Postgresql/POST.md index e4f763f279..5eb4a5bd98 100644 --- a/docs/en/Community-Articles/2024-04-19-Performing-Case-Insensitive-Search-In-Postgresql/POST.md +++ b/docs/en/Community-Articles/2024-04-19-Performing-Case-Insensitive-Search-In-Postgresql/POST.md @@ -120,6 +120,12 @@ After these configurations, you should create a migration and apply it to your d However, this solution comes with some problems, for example, by using non-deterministic collations, it's not yet possible to use pattern-matching operators such as `LIKE` on columns. This is a huge problem because it makes it hard to use LINQ. For example, you can't use the `.EndsWith` or `.StartsWith` methods, because they are [translated to `LIKE` command on the SQL level](https://www.npgsql.org/efcore/mapping/translations.html). +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Conclusion In PostgreSQL, you can perform case-insensitive searches by using the `citext` data type or by utilizing collation settings. Nevertheless, if you have an ABP-based PostgreSQL application or a plain .NET application with PostgreSQL as the database option, to make a decision to pick one of these options, you can follow the following points: diff --git a/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/POST.md b/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/POST.md index e6417b70ce..deb162bcba 100644 --- a/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/POST.md +++ b/docs/en/Community-Articles/2024-05-10-Sentiment-Analysis-within-ABP-Based-Application/POST.md @@ -4,6 +4,12 @@ In this article, first I will briefly explain what sentiment analysis is and the We will use ML.NET Framework, which is an open-source machine learning framework created by the dotnet team and also we will create a layered ABP Solution by using the application template and finally we will use CMS Kit's Comment Feature and extend its behavior by adding spam detection while creating or updating a comment, at that point we will make sentiment analysis. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Sentiment Analysis [Sentiment Analysis (or opinion mining)](https://en.wikipedia.org/wiki/Sentiment_analysis) refers to determining the emotion from the given input. The primary goal of sentiment analysis is to identify, extract, and categorize (positive, negative, or neutral) the sentiments expressed in textual data. @@ -333,6 +339,12 @@ Then, finally, we can run the application to see the final results: Once the model is trained and evaluated, we can save the trained model and use it directly for further use. In this way, you don’t have to retrain the model every time when you want to make predictions. It’s essential to save the trained model for future use and a must for the production-ready code. I created a separate article dedicated to that topic, and if you are interested, you can read it from [here](https://engincanv.github.io/machine-learning/sentiment-analysis/best-practises/2024/05/16/reusing-and-optimizing-machine-learning-models-in-dotnet.html). +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Conclusion In this article, I briefly explain what sentiment analysis is, created a sample ABP-based application, integrated the CMS Kit Module and finally, applied sentiment analysis to make spam checks whenever a new comment has been submitted or updated. You can get the source code of the demo from [https://github.com/EngincanV/SentimentAnalysisDemo](https://github.com/EngincanV/SentimentAnalysisDemo) diff --git a/docs/en/Community-Articles/2024-11-01-Hybrid-Cache-Net-9/POST.md b/docs/en/Community-Articles/2024-11-01-Hybrid-Cache-Net-9/POST.md index 3cd4bdc706..a9b869a822 100644 --- a/docs/en/Community-Articles/2024-11-01-Hybrid-Cache-Net-9/POST.md +++ b/docs/en/Community-Articles/2024-11-01-Hybrid-Cache-Net-9/POST.md @@ -6,6 +6,12 @@ It offers a flexible caching solution that combines the best aspects of local an In this article, we’ll explore **HybridCache** in .NET 9 and how it integrates with ABP Framework using `AbpHybridCache`. This new feature offers a robust solution for applications that need to scale while maintaining efficient caching strategies. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## What is HybridCache? **HybridCache** is designed to merge different caching layers, commonly including an in-memory cache (for high-speed access) and a distributed cache (for scalability across multiple instances). This hybrid approach allows for: @@ -109,6 +115,12 @@ As you can see from the figure, it only set the cache item to the **LocalCache** **Note:** If you configure distributed caching options, `HybridCache` service uses the distributed cache and sets the **BackendCache**. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Conclusion The **HybridCache** library in .NET 9 provides a powerful tool for applications needing both high-speed caching and consistency in distributed environments. diff --git a/docs/en/Community-Articles/2025-02-04-OpenIddict-Custom-Logic/POST.md b/docs/en/Community-Articles/2025-02-04-OpenIddict-Custom-Logic/POST.md index f5602650ae..e8b8c9a1b3 100644 --- a/docs/en/Community-Articles/2025-02-04-OpenIddict-Custom-Logic/POST.md +++ b/docs/en/Community-Articles/2025-02-04-OpenIddict-Custom-Logic/POST.md @@ -6,6 +6,12 @@ OpenIddict provides an event-driven model ([event models](https://documentation. In this article, we will explore OpenIddict event models, their key use cases, and how to implement them effectively. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Understanding OpenIddict Event Model OpenIddict events are primarily used within the OpenIddict server component. These events provide hooks into the OpenID Connect flow, allowing developers to modify behavior at different stages of authentication & authorization processes. @@ -101,6 +107,12 @@ That's it! After these steps, your `SignOutEventHandler.HandleAsync()` method sh Each event provides access to the relevant context, allowing you to access and modify the authentication flow's behavior. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Conclusion ABP Framework integrates OpenIddict as its authentication and authorization module. OpenIddict provides an event-driven model that allows developers to customize authentication and authorization processes within their ABP applications. It's pre-installed & pre-configured in the ABP's startup templates. diff --git a/docs/en/Community-Articles/2025-03-24-How-to-Change-the-CurrentUser-in-ABP/POST.md b/docs/en/Community-Articles/2025-03-24-How-to-Change-the-CurrentUser-in-ABP/POST.md index b74aecdbe4..4213ad0e48 100644 --- a/docs/en/Community-Articles/2025-03-24-How-to-Change-the-CurrentUser-in-ABP/POST.md +++ b/docs/en/Community-Articles/2025-03-24-How-to-Change-the-CurrentUser-in-ABP/POST.md @@ -4,6 +4,12 @@ In this article, we'll explore the [`CurrentUser` service](https://abp.io/docs/latest/framework/infrastructure/current-user), its use cases, and how to change it when necessary. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Understanding the ICurrentUser Service The `ICurrentUser` interface is the primary service in ABP Framework for obtaining information about the logged-in user. It provides some key properties, such as `Id`, `UserName`, `TenantId`, `Roles` (roleNames), and more... @@ -108,6 +114,12 @@ Here's what the code does step by step: This pattern is particularly useful in background jobs, event handlers, or any scenario where you need to perform operations under a specific user's context, regardless of the actual authenticated user. +--- +> 🛠 Liked this post? I now share all my content on Substack — real-world .NET, AI, and scalable software design. +> 👉 Subscribe here → engincanveske.substack.com +> 🎥 Also, check out my YouTube channel for hands-on demos and deep dives: https://www.youtube.com/@engincanv +--- + ## Conclusion The `CurrentUser` service in ABP Framework provides a simple way to access information about the authenticated user. While it works automatically in most scenarios, there are cases where you need to explicitly change the current user identity, particularly in background processing scenarios. diff --git a/docs/en/Community-Articles/2025-04-20-IDX10204/header.png b/docs/en/Community-Articles/2025-04-20-IDX10204/header.png index a71c9a66ff..13109a4515 100644 Binary files a/docs/en/Community-Articles/2025-04-20-IDX10204/header.png and b/docs/en/Community-Articles/2025-04-20-IDX10204/header.png differ diff --git a/docs/en/Community-Articles/2025-04-20-IDX10204/payload.png b/docs/en/Community-Articles/2025-04-20-IDX10204/payload.png index a83d6e1051..04b927bbb2 100644 Binary files a/docs/en/Community-Articles/2025-04-20-IDX10204/payload.png and b/docs/en/Community-Articles/2025-04-20-IDX10204/payload.png differ diff --git a/docs/en/Community-Articles/2025-04-25-AI-Comment-Summarization/summary-card.png b/docs/en/Community-Articles/2025-04-25-AI-Comment-Summarization/summary-card.png index 35151efafb..9a36f3cd35 100644 Binary files a/docs/en/Community-Articles/2025-04-25-AI-Comment-Summarization/summary-card.png and b/docs/en/Community-Articles/2025-04-25-AI-Comment-Summarization/summary-card.png differ diff --git a/docs/en/Community-Articles/2025-05-19-RouteTenantResolveContributor/POST.md b/docs/en/Community-Articles/2025-05-19-RouteTenantResolveContributor/POST.md new file mode 100644 index 0000000000..6007e5371f --- /dev/null +++ b/docs/en/Community-Articles/2025-05-19-RouteTenantResolveContributor/POST.md @@ -0,0 +1,268 @@ +# Resolving Tenant from Route in ABP Framework + +The ABP Framework provides multi-tenancy support with various ways to resolve tenant information, including: Cookie, Header, Domain, Route, and more. + +This article will demonstrate how to resolve tenant information from the route. + +## Tenant Information in Routes + +In the ABP Framework, tenant information in routes is handled by the `RouteTenantResolveContributor`. + +Let's say your application is hosted at `https://abp.io` and you have a tenant named `acme`. You can add the `{__tenant}` variable to your controller or page routes like this: + +```csharp +[Route("{__tenant}/[Controller]")] +public class MyController : MyProjectNameController +{ + [HttpGet] + public IActionResult Get() + { + return Ok("Hello My Page"); + } +} +``` + +```cshtml +@page "{__tenant?}/mypage" +@model MyPageModel + + + +

My Page

+ + +``` + +When you access `https://abp.io/acme/my` or `https://abp.io/acme/mypage`, ABP will automatically resolve the tenant information from the route. + +## Adding __tenant to Global Routes + +While we've shown how to add `{__tenant}` to individual controllers or pages, you might want to add it globally to your entire application. Here's how to implement this: + +```cs +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.ApplicationModels; + +namespace MyCompanyName; + +public class AddTenantRouteToPages : IPageRouteModelConvention, IApplicationModelConvention +{ + public void Apply(PageRouteModel model) + { + var selectorCount = model.Selectors.Count; + var selectorModels = new List(); + for (var i = 0; i < selectorCount; i++) + { + var selector = model.Selectors[i]; + selectorModels.Add(new SelectorModel + { + AttributeRouteModel = new AttributeRouteModel + { + Template = AttributeRouteModel.CombineTemplates("{__tenant:regex(^[a-zA-Z0-9]+$)}", selector.AttributeRouteModel!.Template!.RemovePreFix("/")) + } + }); + } + foreach (var selectorModel in selectorModels) + { + model.Selectors.Add(selectorModel); + } + } +} + +public class AddTenantRouteToControllers :IApplicationModelConvention +{ + public void Apply(ApplicationModel application) + { + var controllers = application.Controllers; + foreach (var controller in controllers) + { + var selector = controller.Selectors.FirstOrDefault(); + if (selector == null || selector.AttributeRouteModel == null) + { + controller.Selectors.Add(new SelectorModel + { + AttributeRouteModel = new AttributeRouteModel + { + Template = AttributeRouteModel.CombineTemplates("{__tenant:regex(^[[a-zA-Z0-9]]+$)}", controller.ControllerName) + } + }); + controller.Selectors.Add(new SelectorModel + { + AttributeRouteModel = new AttributeRouteModel + { + Template = controller.ControllerName + } + }); + } + else + { + var template = selector.AttributeRouteModel?.Template; + template = template.IsNullOrWhiteSpace() ? "{__tenant:regex(^[[a-zA-Z0-9]]+$)}" : AttributeRouteModel.CombineTemplates("{__tenant:regex(^[[a-zA-Z0-9]]+$)}", template.RemovePreFix("/")); + controller.Selectors.Add(new SelectorModel + { + AttributeRouteModel = new AttributeRouteModel + { + Template = template + } + }); + } + } + } +} +``` + +Register the services: + +```cs +public override void ConfigureServices(ServiceConfigurationContext context) +{ + //... + + PostConfigure(options => + { + options.Conventions.Add(new AddTenantRouteToPages()); + }); + + PostConfigure(options => + { + options.Conventions.Add(new AddTenantRouteToControllers()); + }); + + // Configure cookie path to prevent authentication cookie loss + context.Services.ConfigureApplicationCookie(x => + { + x.Cookie.Path = "/"; + }); + //... +} +``` + +After implementing this, you'll notice that all controllers in your Swagger UI will have the `{__tenant}` route added: + +![Swagger UI](./swagger-ui.png) + +## Handling Navigation Links + +To ensure navigation links automatically include tenant information, we need to add middleware that dynamically adds the tenant to the PathBase: + +```cs +public override void OnApplicationInitialization(ApplicationInitializationContext context) +{ + //... + app.Use(async (httpContext, next) => + { + var tenantMatch = Regex.Match(httpContext.Request.Path, "^/([^/.]+)(?:/.*)?$"); + if (tenantMatch.Groups.Count > 1 && !string.IsNullOrEmpty(tenantMatch.Groups[1].Value)) + { + var tenantName = tenantMatch.Groups[1].Value; + if (!tenantName.IsNullOrWhiteSpace()) + { + var tenantStore = httpContext.RequestServices.GetRequiredService(); + var tenantNormalizer = httpContext.RequestServices.GetRequiredService(); + var tenantInfo = await tenantStore.FindAsync(tenantNormalizer.NormalizeName(tenantName)!); + if (tenantInfo != null) + { + if (httpContext.Request.Path.StartsWithSegments(new PathString(tenantName.EnsureStartsWith('/')), out var matchedPath, out var remainingPath)) + { + var originalPath = httpContext.Request.Path; + var originalPathBase = httpContext.Request.PathBase; + httpContext.Request.Path = remainingPath; + httpContext.Request.PathBase = originalPathBase.Add(matchedPath); + try + { + await next(httpContext); + } + finally + { + httpContext.Request.Path = originalPath; + httpContext.Request.PathBase = originalPathBase; + } + return; + } + } + } + } + + await next(httpContext); + }); + app.UseRouting(); + app.MapAbpStaticAssets(); + //... +} +``` + +![ui](ui.png) + +After setting the PathBase, we need to add a custom tenant resolver to extract tenant information from the `PathBase`: + +```cs +public class MyRouteTenantResolveContributor : RouteTenantResolveContributor +{ + public const string ContributorName = "MyRoute"; + + public override string Name => ContributorName; + + protected override Task GetTenantIdOrNameFromHttpContextOrNullAsync(ITenantResolveContext context, HttpContext httpContext) + { + var tenantId = httpContext.GetRouteValue(context.GetAbpAspNetCoreMultiTenancyOptions().TenantKey) ?? httpContext.Request.PathBase.ToString(); + var tenantIdStr = tenantId?.ToString()?.RemovePreFix("/"); + return Task.FromResult(!tenantIdStr.IsNullOrWhiteSpace() ? Convert.ToString(tenantIdStr) : null); + } +} +``` + +Register the MyRouteTenantResolveContributor with the ABP Framework: + +```cs +public override void ConfigureServices(ServiceConfigurationContext context) +{ + //... + Configure(options => + { + options.TenantResolvers.Add(new MyRouteTenantResolveContributor()); + }); + //... +} +``` + +### Modifying abp.appPath + +```csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + //... + context.Services.AddOptions().Configure((options, rootServiceProvider) => + { + var currentTenant = rootServiceProvider.GetRequiredService(); + if (!currentTenant.Name.IsNullOrWhiteSpace()) + { + options.BaseUrl = currentTenant.Name.EnsureStartsWith('/').EnsureEndsWith('/'); + } + }); + + context.Services.RemoveAll(x => x.ServiceType == typeof(IOptions)); + context.Services.Add(ServiceDescriptor.Scoped(typeof(IOptions<>), typeof(OptionsManager<>))); + //... +} +``` + +Browser console output: + +```cs +> https://localhost:44303/acme/ +> abp.appPath +> '/acme/' +``` + +## Summary + +By following these steps, you can implement tenant resolution from routes in the ABP Framework and handle navigation links appropriately. This approach provides a clean and maintainable way to manage multi-tenancy in your application. + + +## References + +- [ABP Multi-Tenancy](https://docs.abp.io/en/abp/latest/Multi-Tenancy) +- [Routing in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing) +- [HTML base tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) diff --git a/docs/en/Community-Articles/2025-05-19-RouteTenantResolveContributor/cover.png b/docs/en/Community-Articles/2025-05-19-RouteTenantResolveContributor/cover.png new file mode 100644 index 0000000000..ebd7978ac8 Binary files /dev/null and b/docs/en/Community-Articles/2025-05-19-RouteTenantResolveContributor/cover.png differ diff --git a/docs/en/Community-Articles/2025-05-19-RouteTenantResolveContributor/swagger-ui.png b/docs/en/Community-Articles/2025-05-19-RouteTenantResolveContributor/swagger-ui.png new file mode 100644 index 0000000000..72da9bd571 Binary files /dev/null and b/docs/en/Community-Articles/2025-05-19-RouteTenantResolveContributor/swagger-ui.png differ diff --git a/docs/en/Community-Articles/2025-05-19-RouteTenantResolveContributor/ui.png b/docs/en/Community-Articles/2025-05-19-RouteTenantResolveContributor/ui.png new file mode 100644 index 0000000000..3ae3f6650c Binary files /dev/null and b/docs/en/Community-Articles/2025-05-19-RouteTenantResolveContributor/ui.png differ diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index fc3844317d..d031e87466 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -83,47 +83,59 @@ { "text": "Overview", "path": "tutorials/book-store", - "isIndex": true + "isIndex": true, + "isInSeries": true }, { "text": "1: Creating the Server Side", - "path": "tutorials/book-store/part-01.md" + "path": "tutorials/book-store/part-01.md", + "isInSeries": true + }, { "text": "2: The Book List Page", - "path": "tutorials/book-store/part-02.md" + "path": "tutorials/book-store/part-02.md", + "isInSeries": true }, { "text": "3: Creating, Updating and Deleting Books", - "path": "tutorials/book-store/part-03.md" + "path": "tutorials/book-store/part-03.md", + "isInSeries": true }, { "text": "4: Integration Tests", - "path": "tutorials/book-store/part-04.md" + "path": "tutorials/book-store/part-04.md", + "isInSeries": true }, { "text": "5: Authorization", - "path": "tutorials/book-store/part-05.md" + "path": "tutorials/book-store/part-05.md", + "isInSeries": true }, { "text": "6: Authors: Domain Layer", - "path": "tutorials/book-store/part-06.md" + "path": "tutorials/book-store/part-06.md", + "isInSeries": true }, { "text": "7: Authors: Database Integration", - "path": "tutorials/book-store/part-07.md" + "path": "tutorials/book-store/part-07.md", + "isInSeries": true }, { "text": "8: Authors: Application Layer", - "path": "tutorials/book-store/part-08.md" + "path": "tutorials/book-store/part-08.md", + "isInSeries": true }, { "text": "9: Authors: User Interface", - "path": "tutorials/book-store/part-09.md" + "path": "tutorials/book-store/part-09.md", + "isInSeries": true }, { "text": "10: Book to Author Relation", - "path": "tutorials/book-store/part-10.md" + "path": "tutorials/book-store/part-10.md", + "isInSeries": true } ] }, @@ -135,27 +147,33 @@ { "text": "Overview", "path": "tutorials/book-store-with-abp-suite", - "isIndex": true + "isIndex": true, + "isInSeries": true }, { "text": "1: Creating the Solution", - "path": "tutorials/book-store-with-abp-suite/part-01.md" + "path": "tutorials/book-store-with-abp-suite/part-01.md", + "isInSeries": true }, { "text": "2: Creating the Books", - "path": "tutorials/book-store-with-abp-suite/part-02.md" + "path": "tutorials/book-store-with-abp-suite/part-02.md", + "isInSeries": true }, { "text": "3: Creating the Authors", - "path": "tutorials/book-store-with-abp-suite/part-03.md" + "path": "tutorials/book-store-with-abp-suite/part-03.md", + "isInSeries": true }, { "text": "4: Book to Author Relation", - "path": "tutorials/book-store-with-abp-suite/part-04.md" + "path": "tutorials/book-store-with-abp-suite/part-04.md", + "isInSeries": true }, { "text": "5: Customizing the Generated Code", - "path": "tutorials/book-store-with-abp-suite/part-05.md" + "path": "tutorials/book-store-with-abp-suite/part-05.md", + "isInSeries": true } ] }, @@ -2070,19 +2088,23 @@ "items": [ { "text": "Deploy to Azure Web App Service", - "path": "solution-templates/layered-web-application/deployment/azure-deployment/azure-deployment.md" + "path": "solution-templates/layered-web-application/deployment/azure-deployment/azure-deployment.md", + "isInSeries": true }, { "text": "Creating an Azure Web App Service Environment", - "path": "solution-templates/layered-web-application/deployment/azure-deployment/step1-create-azure-resources.md" + "path": "solution-templates/layered-web-application/deployment/azure-deployment/step1-create-azure-resources.md", + "isInSeries": true }, { "text": "Customizing the Configuration of Your ABP Application", - "path": "solution-templates/layered-web-application/deployment/azure-deployment/step2-configuration-application.md" + "path": "solution-templates/layered-web-application/deployment/azure-deployment/step2-configuration-application.md", + "isInSeries": true }, { "text": "Deploying Application With GitHub Actions", - "path": "solution-templates/layered-web-application/deployment/azure-deployment/step3-deployment-github-action.md" + "path": "solution-templates/layered-web-application/deployment/azure-deployment/step3-deployment-github-action.md", + "isInSeries": true } ] }, diff --git a/docs/en/framework/data/entity-framework-core/index.md b/docs/en/framework/data/entity-framework-core/index.md index a50edb2d4a..e462662410 100644 --- a/docs/en/framework/data/entity-framework-core/index.md +++ b/docs/en/framework/data/entity-framework-core/index.md @@ -142,24 +142,27 @@ Configure(options => Add actions for the `ConfigureConventions` and `OnModelCreating` methods of the `DbContext` as shown below: ````csharp -options.DefaultConventionAction = (dbContext, builder) => +Configure(options => { - // This action is called for ConfigureConventions method of all DbContexts. -}; + options.ConfigureDefaultConvention((dbContext, builder) => + { + // This action is called for ConfigureConventions method of all DbContexts. + }); -options.ConfigureConventions((dbContext, builder) => -{ - // This action is called for ConfigureConventions method of specific DbContext. -}); + options.ConfigureConventions((dbContext, builder) => + { + // This action is called for ConfigureConventions method of specific DbContext. + }); -options.DefaultOnModelCreatingAction = (dbContext, builder) => -{ - // This action is called for OnModelCreating method of all DbContexts. -}; + options.ConfigureDefaultOnModelCreating((dbContext, builder) => + { + // This action is called for OnModelCreating method of all DbContexts. + }); -options.ConfigureOnModelCreating((dbContext, builder) => -{ - // This action is called for OnModelCreating method of specific DbContext. + options.ConfigureOnModelCreating((dbContext, builder) => + { + // This action is called for OnModelCreating method of specific DbContext. + }); }); ```` @@ -972,4 +975,4 @@ public class MyCustomEfCoreBulkOperationProvider * [Entities](../../architecture/domain-driven-design/entities.md) * [Repositories](../../architecture/domain-driven-design/repositories.md) -* [Video tutorial](https://abp.io/video-courses/essentials/abp-ef-core) \ No newline at end of file +* [Video tutorial](https://abp.io/video-courses/essentials/abp-ef-core) diff --git a/docs/en/framework/infrastructure/features.md b/docs/en/framework/infrastructure/features.md index ef5d97bd91..a6bc52e48b 100644 --- a/docs/en/framework/infrastructure/features.md +++ b/docs/en/framework/infrastructure/features.md @@ -424,7 +424,7 @@ namespace FeaturesDemo return Task.FromResult("true"); } - return null; + return Task.FromResult(null); } } } 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 index eb6c95e5bb..0f64d49a3b 100644 Binary files a/docs/en/get-started/images/abp-studio-no-layers-new-solution-dialog-ui-framework-0.9.13.png 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/single-layer-web-application.md b/docs/en/get-started/single-layer-web-application.md index 67d840d768..5c0a256c42 100644 --- a/docs/en/get-started/single-layer-web-application.md +++ b/docs/en/get-started/single-layer-web-application.md @@ -3,7 +3,7 @@ ````json //[doc-params] { - "UI": ["MVC", "Blazor", "BlazorServer", "NG"], + "UI": ["MVC", "Blazor", "BlazorServer", "BlazorWebApp", "NG"], "DB": ["EF", "Mongo"] } ```` diff --git a/docs/en/modules/payment-custom-gateway.md b/docs/en/modules/payment-custom-gateway.md index 40cd073da6..cdf847b528 100644 --- a/docs/en/modules/payment-custom-gateway.md +++ b/docs/en/modules/payment-custom-gateway.md @@ -6,7 +6,7 @@ This document explains creating custom a payment gateway that's different than t ## Creating Core Operations -- Create **MyPaymentGateway.cs** and implement `IPaymentGateway` +- Create **MyPaymentGateway.cs** in the **Domain** layer of your project and implement `IPaymentGateway`. ```csharp public class MyPaymentGateway : IPaymentGateway, ITransientDependency @@ -57,7 +57,7 @@ This document explains creating custom a payment gateway that's different than t } ``` -- You should configure your gateway in `PaymentOptions` +- You should also configure `PaymentOptions` for your gateway in the **Domain** layer of your project as shown below. ```csharp Configure(options => @@ -119,7 +119,7 @@ There are 2 types of pages that are supported by default. You can define a pre-p

Operation Done

``` -- Configure your pages in `PaymentWebOptions` +- Configure your pages using `PaymentWebOptions` in **Web** layer of your project. ```csharp Configure(options => diff --git a/docs/en/studio/release-notes.md b/docs/en/studio/release-notes.md index ef498fcac5..cfb8e9fb61 100644 --- a/docs/en/studio/release-notes.md +++ b/docs/en/studio/release-notes.md @@ -2,6 +2,19 @@ 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.26 (2025-04-30) + +* Fixed the issue where C# applications would not stop when requested. +* Added idle session timeout feature to Blazor WebAssembly applications. +* Added “Setup as a modular solution” option to application startup templates. +* Automatically added remote service base URL after generating C# proxies. +* Configured Helm charts for Kubernetes health check endpoints. +* Fixed auditing issue in Blazor WebAssembly applications. +* Fixed login error after registering a new user in Blazor WebApp. +* Implemented password login flow in Studio CLI. +* Supported non-root user mode in Docker Compose configurations. +* Upgraded templates to version `9.1.1`. + ## 0.9.25 (2025-03-12) * Added ready/health check for solution runner. diff --git a/docs/en/studio/version-mapping.md b/docs/en/studio/version-mapping.md index cd82da602a..88d9be25fe 100644 --- a/docs/en/studio/version-mapping.md +++ b/docs/en/studio/version-mapping.md @@ -4,6 +4,7 @@ This document provides a general overview of the relationship between various ve | **ABP Studio Version** | **ABP Version of Startup Template** | |------------------------|---------------------------| +| 0.9.26 | 9.1.1 | | 0.9.24 - 0.9.25 | 9.1.0 | | 0.9.22 - 0.9.23 | 9.0.4 | | 0.9.20 - 0.9.21 | 9.0.3 | diff --git a/docs/en/ui-themes/lepton-x/images/account-layout-background-style.png b/docs/en/ui-themes/lepton-x/images/account-layout-background-style.png index 5b2f32661d..c8391da4d3 100644 Binary files a/docs/en/ui-themes/lepton-x/images/account-layout-background-style.png and b/docs/en/ui-themes/lepton-x/images/account-layout-background-style.png differ diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js index e11a8f4d99..d232dbd28a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js @@ -55,7 +55,7 @@ $.validator.defaults.ignore = ''; //TODO: Would be better if we can apply only f function _createContainer() { _removeContainer(); - _$modalContainer = $('
'); + _$modalContainer = $('
'); var existsModals = $('[id^="Abp_Modal_"]'); if (existsModals.length) { existsModals.last().after(_$modalContainer) diff --git a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs index 6c4fcf07f9..a2c7193437 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs @@ -357,10 +357,10 @@ public abstract class AbpCrudPageBase< } } - protected virtual Task CloseCreateModalAsync() + protected virtual async Task CloseCreateModalAsync() { + await InvokeAsync(CreateModal!.Hide); NewEntity = new TCreateViewModel(); - return InvokeAsync(CreateModal!.Hide); } protected virtual Task ClosingCreateModal(ModalClosingEventArgs eventArgs) @@ -474,11 +474,11 @@ public abstract class AbpCrudPageBase< protected virtual async Task OnCreatedEntityAsync() { - NewEntity = new TCreateViewModel(); await GetEntitiesAsync(); await InvokeAsync(CreateModal!.Hide); await Notify.Success(GetCreateMessage()); + NewEntity = new TCreateViewModel(); } protected virtual string GetCreateMessage() diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryAsyncExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryAsyncExtensions.cs index 2115d00200..e84778ae50 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryAsyncExtensions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryAsyncExtensions.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Linq.Dynamic.Core; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; @@ -12,7 +14,7 @@ public static class RepositoryAsyncExtensions { #region Contains - public static async Task ContainsAsync( + public async static Task ContainsAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] T item, CancellationToken cancellationToken = default) @@ -26,7 +28,7 @@ public static class RepositoryAsyncExtensions #region Any/All - public static async Task AnyAsync( + public async static Task AnyAsync( [NotNull] this IReadOnlyRepository repository, CancellationToken cancellationToken = default) where T : class, IEntity @@ -35,7 +37,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.AnyAsync(queryable, cancellationToken); } - public static async Task AnyAsync( + public async static Task AnyAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> predicate, CancellationToken cancellationToken = default) @@ -45,7 +47,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.AnyAsync(queryable, predicate, cancellationToken); } - public static async Task AllAsync( + public async static Task AllAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> predicate, CancellationToken cancellationToken = default) @@ -59,7 +61,7 @@ public static class RepositoryAsyncExtensions #region Count/LongCount - public static async Task CountAsync( + public async static Task CountAsync( [NotNull] this IReadOnlyRepository repository, CancellationToken cancellationToken = default) where T : class, IEntity @@ -68,7 +70,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.CountAsync(queryable, cancellationToken); } - public static async Task CountAsync( + public async static Task CountAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> predicate, CancellationToken cancellationToken = default) @@ -78,7 +80,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.CountAsync(queryable, predicate, cancellationToken); } - public static async Task LongCountAsync( + public async static Task LongCountAsync( [NotNull] this IReadOnlyRepository repository, CancellationToken cancellationToken = default) where T : class, IEntity @@ -87,7 +89,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.LongCountAsync(queryable, cancellationToken); } - public static async Task LongCountAsync( + public async static Task LongCountAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> predicate, CancellationToken cancellationToken = default) @@ -101,41 +103,41 @@ public static class RepositoryAsyncExtensions #region First/FirstOrDefault - public static async Task FirstAsync( + public async static Task FirstAsync( [NotNull] this IReadOnlyRepository repository, CancellationToken cancellationToken = default) where T : class, IEntity { - var queryable = await repository.GetQueryableAsync(); + var queryable = AddOrderById(await repository.GetQueryableAsync()); return await repository.AsyncExecuter.FirstAsync(queryable, cancellationToken); } - public static async Task FirstAsync( + public async static Task FirstAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> predicate, CancellationToken cancellationToken = default) where T : class, IEntity { - var queryable = await repository.GetQueryableAsync(); + var queryable = AddOrderById(await repository.GetQueryableAsync()); return await repository.AsyncExecuter.FirstAsync(queryable, predicate, cancellationToken); } - public static async Task FirstOrDefaultAsync( + public async static Task FirstOrDefaultAsync( [NotNull] this IReadOnlyRepository repository, CancellationToken cancellationToken = default) where T : class, IEntity { - var queryable = await repository.GetQueryableAsync(); + var queryable = AddOrderById(await repository.GetQueryableAsync()); return await repository.AsyncExecuter.FirstOrDefaultAsync(queryable, cancellationToken); } - public static async Task FirstOrDefaultAsync( + public async static Task FirstOrDefaultAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> predicate, CancellationToken cancellationToken = default) where T : class, IEntity { - var queryable = await repository.GetQueryableAsync(); + var queryable = AddOrderById(await repository.GetQueryableAsync()); return await repository.AsyncExecuter.FirstOrDefaultAsync(queryable, predicate, cancellationToken); } @@ -143,49 +145,63 @@ public static class RepositoryAsyncExtensions #region Last/LastOrDefault - public static async Task LastAsync( + public async static Task LastAsync( [NotNull] this IReadOnlyRepository repository, CancellationToken cancellationToken = default) where T : class, IEntity { - var queryable = await repository.GetQueryableAsync(); + var queryable = AddOrderById(await repository.GetQueryableAsync()); return await repository.AsyncExecuter.LastAsync(queryable, cancellationToken); } - public static async Task LastAsync( + public async static Task LastAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> predicate, CancellationToken cancellationToken = default) where T : class, IEntity { - var queryable = await repository.GetQueryableAsync(); + var queryable = AddOrderById(await repository.GetQueryableAsync()); return await repository.AsyncExecuter.LastAsync(queryable, predicate, cancellationToken); } - public static async Task LastOrDefaultAsync( + public async static Task LastOrDefaultAsync( [NotNull] this IReadOnlyRepository repository, CancellationToken cancellationToken = default) where T : class, IEntity { - var queryable = await repository.GetQueryableAsync(); + var queryable = AddOrderById(await repository.GetQueryableAsync()); return await repository.AsyncExecuter.LastOrDefaultAsync(queryable, cancellationToken); } - public static async Task LastOrDefaultAsync( + public async static Task LastOrDefaultAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> predicate, CancellationToken cancellationToken = default) where T : class, IEntity { - var queryable = await repository.GetQueryableAsync(); + var queryable = AddOrderById(await repository.GetQueryableAsync()); return await repository.AsyncExecuter.LastOrDefaultAsync(queryable, predicate, cancellationToken); } #endregion + #region OrderById + + private static IQueryable AddOrderById([NotNull] this IQueryable queryable) + where T : class, IEntity + { + if (typeof(T).GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEntity<>))) + { + queryable = queryable.OrderBy(nameof(IEntity.Id)); + } + return queryable; + } + + #endregion + #region Single/SingleOrDefault - public static async Task SingleAsync( + public async static Task SingleAsync( [NotNull] this IReadOnlyRepository repository, CancellationToken cancellationToken = default) where T : class, IEntity @@ -194,7 +210,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.SingleAsync(queryable, cancellationToken); } - public static async Task SingleAsync( + public async static Task SingleAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> predicate, CancellationToken cancellationToken = default) @@ -204,7 +220,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.SingleAsync(queryable, predicate, cancellationToken); } - public static async Task SingleOrDefaultAsync( + public async static Task SingleOrDefaultAsync( [NotNull] this IReadOnlyRepository repository, CancellationToken cancellationToken = default) where T : class, IEntity @@ -213,7 +229,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.SingleOrDefaultAsync(queryable, cancellationToken); } - public static async Task SingleOrDefaultAsync( + public async static Task SingleOrDefaultAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> predicate, CancellationToken cancellationToken = default) @@ -227,7 +243,7 @@ public static class RepositoryAsyncExtensions #region Min - public static async Task MinAsync( + public async static Task MinAsync( [NotNull] this IReadOnlyRepository repository, CancellationToken cancellationToken = default) where T : class, IEntity @@ -236,7 +252,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.MinAsync(queryable, cancellationToken); } - public static async Task MinAsync( + public async static Task MinAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -250,7 +266,7 @@ public static class RepositoryAsyncExtensions #region Max - public static async Task MaxAsync( + public async static Task MaxAsync( [NotNull] this IReadOnlyRepository repository, CancellationToken cancellationToken = default) where T : class, IEntity @@ -259,7 +275,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.MaxAsync(queryable, cancellationToken); } - public static async Task MaxAsync( + public async static Task MaxAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -273,7 +289,7 @@ public static class RepositoryAsyncExtensions #region Sum - public static async Task SumAsync( + public async static Task SumAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -283,7 +299,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.SumAsync(queryable, selector, cancellationToken); } - public static async Task SumAsync( + public async static Task SumAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -293,7 +309,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.SumAsync(queryable, selector, cancellationToken); } - public static async Task SumAsync( + public async static Task SumAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -303,7 +319,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.SumAsync(queryable, selector, cancellationToken); } - public static async Task SumAsync( + public async static Task SumAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -313,7 +329,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.SumAsync(queryable, selector, cancellationToken); } - public static async Task SumAsync( + public async static Task SumAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -323,7 +339,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.SumAsync(queryable, selector, cancellationToken); } - public static async Task SumAsync( + public async static Task SumAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -333,7 +349,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.SumAsync(queryable, selector, cancellationToken); } - public static async Task SumAsync( + public async static Task SumAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -343,7 +359,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.SumAsync(queryable, selector, cancellationToken); } - public static async Task SumAsync( + public async static Task SumAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -353,7 +369,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.SumAsync(queryable, selector, cancellationToken); } - public static async Task SumAsync( + public async static Task SumAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -363,7 +379,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.SumAsync(queryable, selector, cancellationToken); } - public static async Task SumAsync( + public async static Task SumAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -377,7 +393,7 @@ public static class RepositoryAsyncExtensions #region Average - public static async Task AverageAsync( + public async static Task AverageAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -387,7 +403,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.AverageAsync(queryable, selector, cancellationToken); } - public static async Task AverageAsync( + public async static Task AverageAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -397,7 +413,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.AverageAsync(queryable, selector, cancellationToken); } - public static async Task AverageAsync( + public async static Task AverageAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -407,7 +423,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.AverageAsync(queryable, selector, cancellationToken); } - public static async Task AverageAsync( + public async static Task AverageAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -417,7 +433,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.AverageAsync(queryable, selector, cancellationToken); } - public static async Task AverageAsync( + public async static Task AverageAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -427,7 +443,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.AverageAsync(queryable, selector, cancellationToken); } - public static async Task AverageAsync( + public async static Task AverageAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -437,7 +453,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.AverageAsync(queryable, selector, cancellationToken); } - public static async Task AverageAsync( + public async static Task AverageAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -447,7 +463,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.AverageAsync(queryable, selector, cancellationToken); } - public static async Task AverageAsync( + public async static Task AverageAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -457,7 +473,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.AverageAsync(queryable, selector, cancellationToken); } - public static async Task AverageAsync( + public async static Task AverageAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> selector, CancellationToken cancellationToken = default) @@ -471,7 +487,7 @@ public static class RepositoryAsyncExtensions #region ToList/Array - public static async Task> ToListAsync( + public async static Task> ToListAsync( [NotNull] this IReadOnlyRepository repository, CancellationToken cancellationToken = default) where T : class, IEntity @@ -480,7 +496,7 @@ public static class RepositoryAsyncExtensions return await repository.AsyncExecuter.ToListAsync(queryable, cancellationToken); } - public static async Task ToArrayAsync( + public async static Task ToArrayAsync( [NotNull] this IReadOnlyRepository repository, CancellationToken cancellationToken = default) where T : class, IEntity diff --git a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs index 63d6aa82ad..dd5df32b8a 100644 --- a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs +++ b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs @@ -236,6 +236,8 @@ public class DefaultExceptionToErrorInfoConverter : IExceptionToErrorInfoConvert errorInfo.ValidationErrors = GetValidationErrorInfos((exception as AbpValidationException)!); } + TryToLocalizeExceptionMessage(exception, errorInfo); + return errorInfo; } 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 9d1b706d8b..e5d726526b 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 @@ -64,7 +64,7 @@ public class MongoDbContextEventInbox : IMongoDbContextEventInb var outgoingEventRecords = await dbContext .IncomingEvents .AsQueryable() - .Where(x => !x.Processed) + .Where(x => x.Processed == false) .WhereIf(transformedFilter != null, transformedFilter!) .OrderBy(x => x.CreationTime) .Take(maxCount) diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/EfCoreAsyncQueryableProvider_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/EfCoreAsyncQueryableProvider_Tests.cs index 4c3b355367..2666695fd9 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/EfCoreAsyncQueryableProvider_Tests.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/EfCoreAsyncQueryableProvider_Tests.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; using Shouldly; using Volo.Abp.Domain.Repositories; using Volo.Abp.TestApp.Domain; @@ -55,4 +56,20 @@ public class EfCoreAsyncQueryableProvider_Tests : EntityFrameworkCoreTestBase await uow.CompleteAsync(); } } + + [Fact] + public async Task LastOrDefaultAsync_With_DefaultOrderBy() + { + using (var uow = _unitOfWorkManager.Begin()) + { + var lastOrDefault = await _personRepository.LastOrDefaultAsync(); + lastOrDefault.ShouldNotBeNull(); + + await Assert.ThrowsAsync(async () => + await (await _personRepository.GetQueryableAsync()).LastOrDefaultAsync() + ); + + await uow.CompleteAsync(); + } + } } diff --git a/latest-versions.json b/latest-versions.json index 340ed62653..da8c7b754d 100644 --- a/latest-versions.json +++ b/latest-versions.json @@ -1,5 +1,14 @@ [ - { + { + "version": "9.1.3", + "releaseDate": "", + "type": "stable", + "message": "", + "leptonx": { + "version": "4.1.3" + } + }, + { "version": "9.1.1", "releaseDate": "", "type": "stable", diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs index c17eb1e28f..cef45c2f4f 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -75,7 +75,7 @@ public class LoginModel : AccountPageModel public virtual async Task OnGetAsync() { - LoginInput = new LoginInputModel(); + LoginInput ??= new LoginInputModel(); ExternalProviders = await GetExternalProviders(); diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/Default.cshtml b/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/Default.cshtml index 5f7298ca25..6c4aa7f3b8 100644 --- a/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/Default.cshtml +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/Default.cshtml @@ -9,7 +9,7 @@ @Model.CurrentLanguage.DisplayName -