|
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 749 KiB After Width: | Height: | Size: 712 KiB |
|
Before Width: | Height: | Size: 779 KiB After Width: | Height: | Size: 709 KiB |
|
Before Width: | Height: | Size: 697 KiB After Width: | Height: | Size: 667 KiB |
|
Before Width: | Height: | Size: 852 KiB After Width: | Height: | Size: 825 KiB |
|
Before Width: | Height: | Size: 738 KiB After Width: | Height: | Size: 714 KiB |
|
Before Width: | Height: | Size: 802 KiB After Width: | Height: | Size: 742 KiB |
|
Before Width: | Height: | Size: 633 KiB After Width: | Height: | Size: 620 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 2.1 MiB |
|
Before Width: | Height: | Size: 602 KiB After Width: | Height: | Size: 594 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 44 KiB |
@ -0,0 +1,79 @@ |
|||
# ABP.IO Platform 9.3 Final Has Been Released! |
|||
|
|||
We are glad to announce that [ABP](https://abp.io/) 9.3 stable version has been released today. |
|||
|
|||
## What's New With Version 9.3? |
|||
|
|||
All the new features were explained in detail in the [9.3 RC Announcement Post](https://abp.io/community/announcements/announcing-abp-9-3-release-candidate-4dqgiryf), so there is no need to review them again. You can check it out for more details. |
|||
|
|||
## Getting Started with 9.3 |
|||
|
|||
### 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. |
|||
|
|||
> **Note**: ABP Studio **v1.2.1** has been released with support for **ABP 9.3**. If you already have ABP Studio installed, update it to v1.2.1 (or later, if available) to create new applications targeting 9.3. ABP Studio checks for updates automatically and will prompt you in-app modal to update to the latest version, or you can download the latest installer from the [Studio](https://abp.io/studio) page. See the [upgrading guide](https://abp.io/docs/latest/studio/installation#upgrading) for details. After updating, the New Solution wizard will create applications with ABP 9.3 by default. You can check the [ABP Studio and ABP Startup Template Version Mappings](https://abp.io/docs/latest/studio/version-mapping) documentation to see the corresponding ABP versions for other versions of Studio. |
|||
|
|||
### 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. 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 **Upgrade ABP Packages** action button to instantly upgrade your solution: |
|||
|
|||
 |
|||
|
|||
### 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. |
|||
|
|||
## 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 v9.2: [ABP Version 9.3 Migration Guide](https://abp.io/docs/9.3/release-info/migration-guides/abp-9-3) |
|||
|
|||
## Community News |
|||
|
|||
### New ABP Community Articles |
|||
|
|||
As always, exciting articles have been contributed by the ABP community. I will highlight some of them here: |
|||
|
|||
* [Fahri Gedik](https://abp.io/community/members/fahrigedik) has published 2 new articles: |
|||
* [A Modern Approach to Angular Dependency Injection using inject function](https://abp.io/community/articles/a-modern-approach-to-angular-dependency-injection-using-8np4o1ap) |
|||
* [Angular Application Builder: Transitioning from Webpack to Esbuild](https://abp.io/community/articles/angular-application-builder-transitioning-from-webpack-to-3yzhzfl0) |
|||
* [Benjamin Fadina](https://abp.io/community/members/benjaminsqlserver@gmail.com) has published several videos on various topics such as **Blazor Web Assembly Using ABP.IO**, **CQRS Implementation with MediatR in ABP** and more. You can see all his videos [here](https://abp.io/community/members/benjaminsqlserver@gmail.com). |
|||
* [Mansur Besleney](https://abp.io/community/members/mansur.besleney) has published [How to Build Persistent Background Jobs with ABP Framework and Quartz](https://abp.io/community/articles/how-to-build-persistent-background-jobs-with-abp-framework-n9aloh93) |
|||
* [Halil Ibrahim Kalkan](https://x.com/hibrahimkalkan) has published [Multitenancy with Separate Databases in .NET and ABP](https://abp.io/community/articles/multitenancy-with-separate-databases-in-dotnet-and-abp-51nvl4u9) |
|||
* [Alex Maiereanu](https://abp.io/community/members/alex.maiereanu@3sstudio.com) has published [ABP-Hangfire-AzurePostgreSQL](https://abp.io/community/articles/abphangfireazurepostgresql-s1jnf3yg) |
|||
* [Jack Fistelmann](https://abp.io/community/members/jfistelmann) has published [ABP and maildev](https://abp.io/community/articles/abp-and-maildev-gy13cr1p) |
|||
* [Harsh Gupta](https://abp.io/community/members/harshgupta) has published [How to Add a Module in the ABP.io Application?](https://abp.io/community/articles/how-to-add-a-module-in-the-abp.io-application-sdeajkn6) |
|||
* [Tarık Özdemir](https://abp.io/community/members/mtozdemir) has published [AI-First Architecture for .NET Projects: A Modern Blueprint Inspired by McKinsey](https://abp.io/community/articles/AI-First%20Architecture%20for%20.NET%20Projects%3A%20A%20Modern%20Blueprint-h2wgcoq3) |
|||
* [Liming Ma](https://github.com/maliming) has published [Using Hangfire Dashboard in ABP API Website](https://abp.io/community/articles/using-hangfire-dashboard-in-abp-api-website--r32ox497) |
|||
|
|||
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/create) to the ABP Community. |
|||
|
|||
## About the Next Version |
|||
|
|||
The next feature version will be 10.0. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version. |
|||
|
After Width: | Height: | Size: 676 KiB |
|
After Width: | Height: | Size: 34 KiB |
@ -1,100 +1,200 @@ |
|||
<p> ABP Framework is an open source infrastructure that enables developers to create modern web applications by following the best practices and conventions of software development. In 2022, ABP Framework continued to thrive, achieving significant milestones and making waves in the software development community. With more than 9K GitHub stars and over 10 millions of downloads on NuGet, ABP Framework has become a go-to framework for developers seeking a reliable and efficient way to build web applications.</p> |
|||
|
|||
<p>As ABP Team, we owe our success to our vibrant community, and we are immensely grateful for the support and contributions of each and every member. With your help, we achieved a lot in 2022. We remained committed to our values of transparency, openness, and collaboration, engaging with our community members as much as possible to ensure that we are creating a framework that meets their needs.</p> |
|||
|
|||
<p>One of the major highlights of 2022 was the release of .NET Core 7, which provided a powerful platform for ABP Framework to build upon. Additionally, ABP Commercial and our training programs continued to help developers and businesses to leverage the power of the ABP Framework, enabling them to build modern web applications more efficiently and effectively than ever before.</p> |
|||
|
|||
<p>In this article, we'll take a closer look at the key highlights of 2022 for ABP Framework, from major updates to achivements and the community insights. We are excited to share our progress with you and provide insights into how ABP Framework is continuing to shape the future of software development. So, let's dive in!</p> |
|||
</ br> |
|||
<img src="https://i.ibb.co/qjgK7Dj/2022-Highlights.png"> |
|||
|
|||
|
|||
<h2> NuGet Downloads </h2> |
|||
<p> NuGet is a package manager designed specifically for the .NET ecosystem. It simplifies the process of creating and consuming packages, thanks to the NuGet client tools. By using these tools, developers can easily manage their project dependencies and improve their workflow.</p> |
|||
<p> In 2022, <a href="https://www.nuget.org/packages/Volo.Abp.Core/7.0.1" target="_blank" rel="nofollow">ABP Core NuGet package</a> reached more than <b>10 million</b> of downloads! </p> |
|||
<p> On the other hand, overall <a href="https://www.nuget.org/profiles/volosoft" target="_blank" rel="nofollow">Volosoft NuGet Packages</a> reached <b> more than half a billion</b> downloads!</p> |
|||
<p> Thank you all for your interest and support towards Volosoft and ABP packages.</p> |
|||
|
|||
|
|||
<h2> E-Books </h2> |
|||
<p> Our published e-book amount is reached <b>3</b>! This year, with our founder <a href="https://github.com/hikalkan" target="_blank" rel="nofollow">Halil İbrahim Kalkan</a>'s contributions we now have <b>3 published e-books</b>. </p> |
|||
<ul> |
|||
<li> <b>Mastering ABP Framework Book</b>: You can learn more details about it from <a href="https://abp.io/books/mastering-abp-framework" target="_blank">here</a> or <a href="https://www.amazon.com/gp/product/B097Z2DM8Q" target="_blank" rel="nofollow">purchase from Amazon</a> or <a href="https://www.packtpub.com/product/mastering-abp-framework/9781801079242" target="_blank" rel="nofollow"> purchase from Packt Publishing's website</a>.</li> |
|||
<li> <b>Implementing Domain Driven Design</b>: You can download it for free from <a href="https://abp.io/books/implementing-domain-driven-design" target="_blank"> here</a>. </li> |
|||
<li> <b>Building Microservice Solutions</b>: You can download it for free from <a href="https://abp.io/books/building-microservice-solutions" target="_blank">here</a>. </li> |
|||
</ul> |
|||
|
|||
|
|||
<h2> Tutorial Videos </h2> |
|||
<p> In 2022, we tried to be as much active as we could. To give you more insight and let you understand ABP Framework with short videos according to your interests, we published 48 tutorial videos. Though the videos were created by overall team members of ABP Framework, someone deserves a special mention here. Shout out to our ABP Core Team member <a href="https://github.com/braim23" target="_blank" rel="nofollow">Hamza Albreem</a> for his hard work.</p> |
|||
<ul> |
|||
<li> 6 videos have been published on How to Build a To Do App in a Single Layer which can be found in <a href="https://www.youtube.com/playlist?list=PLsNclT2aHJcPqZxk7D4tU8LtTeCFcN_ci" target="_blank"> ABP To-Do Application [Single Layer] Playlist</a>. </li> |
|||
<li> 8 videos have been published on How to Build a To-Do App in Multi Layers which can be found in <a href="https://www.youtube.com/playlist?list=PLsNclT2aHJcM8LFSBnmmoNYBGwJ9-H8dG" target="_blank" rel="nofollow"> ABP To Do Application [Multi-Layers] Playlist</a>. </li> |
|||
<li> 1 video has been published on ABP Framework Essentials which can be found in <a href="https://www.youtube.com/playlist?list=PLsNclT2aHJcNupH2wz83y7htugpLoUZ_B" target="_blank" rel="nofollow"> ABP Framework Essentials Playlist</a>. </li> |
|||
<li> 13 videos have been published to introduce ABP Modules which can be found in <a href="https://www.youtube.com/playlist?list=PLsNclT2aHJcNQC8t7vighWWX6aDR5ZYbc" target="_blank" rel="nofollow"> ABP Modules Playlist</a>.</li> |
|||
<li> And many other videos can be found on <a href="https://www.youtube.com/c/@volosoft" target="_blank" rel="nofollow">Volosoft YouTube Channel</a>.</li> |
|||
</ul> |
|||
|
|||
<h2> GitHub Stars </h2> |
|||
<p> ABP Framework GitHub repository reached <b>more than 9K stars</b>. We appreciate your interest and support for <a href="https://github.com/abpframework/abp" target="_blank" rel="nofollow">ABP Framework GitHub repository</a>. We are working hard to be worthy of your interest and reach out to more people to simplify and streamline their development processes.</p> |
|||
|
|||
<h2> Community Talks </h2> |
|||
<p><a href="https://community.abp.io/events" target="_blank">ABP Community Talks</a> is our monthly event that brings together members of the ABP Framework community to discuss and exchange ideas. Prior to each event, we collect suggestions from our contributors, monitor trending topics in the industry, and review updates and news related to the ABP Platform to curate the topics for discussion. Once the topics are finalized, we announce them through our social media and community channels to ensure everyone is aware and can join in on the conversation.</p> |
|||
<p> We did 10 <a href="https://www.youtube.com/playlist?list=PLsNclT2aHJcOsPustEkzG6DywiO8eh0lB" target="_blank" rel="nofollow">ABP Community Talks Episodes</a> of and 1 <a href="https://www.youtube.com/watch?v=RFArBh60RSA" target="_blank" rel="nofollow"> ABP Suite webinar</a>. You can take a look at them and check out our videos we have on <a href="https://www.youtube.com/c/@volosoft" target="_blank" rel="nofollow">Volosoft YouTube Channel</a>. </p> |
|||
|
|||
<h2> ABP Community Contributions </h2> |
|||
<p> The ABP Community is a hub that offers resources such as articles, video tutorials, and updates on ABP's development progress and events for ABP Framework, .NET, and software development. Developers can also connect with others, help each other, and share their expertise in <a href="https://community.abp.io/">ABP Community</a>.</p> |
|||
<ul> You can check out each source from the list below. |
|||
<li>ABP Community Events: You can reach them from <a href="https://community.abp.io/events" target="_blank"> here</a>.</li> |
|||
<li>ABP Community Posts: You can reach them from <a href="https://community.abp.io/posts" target="_blank"> here</a></li> |
|||
<li>ABP Community Videos: You can reach them from <a href="https://community.abp.io/videos" target="_blank"> here</a>. </li> |
|||
<li>ABP Community Stackoverflow: You can reach them from <a href="https://stackoverflow.com/questions/tagged/abp" target="_blank"> here</a>. </li> |
|||
</ul> |
|||
<p> In 2022, the community's contribution reached a point where <b>more than 100 resources</b>. Thank you for all your effort! Please keep it going! It is becoming a more and more rich resource thanks to your variety of contributions and help.</p> |
|||
|
|||
|
|||
<h2> ABP Community Discord Server</h2> |
|||
<p> To take community interaction to the next level, we created the official <a href="https://community.abp.io/discord" target="_blank"> ABP Discord server</a>, providing a platform for the ABP Community to connect and communicate instantly through chatting.</p> |
|||
<p> We were so excited <a href="https://blog.abp.io/abp/Official-ABP-Discord-Server-is-Here" target="_blank">announcing the official ABP Discord Server</a>. In the first week of announcing it, the server quickly attracted over 500 members. We're grateful for your interest and support, which confirms the need for a dedicated platform for community interaction.</p> |
|||
> <a href="https://discord.gg/wbcQAsUrs9" target="_blank" rel="nofollow">Join ABP Discord Server Now</a>
|
|||
|
|||
|
|||
<h2> ABP Framework GitHub Contributions</h2> |
|||
<p> In 2022, ABP Core Team worked hard to achieve milestones and give the best value with ABP Framework so users can benefit from its features. Additional to our team's work, ABP Framework is perfected in 2022 with <a href="https://github.com/abpframework/abp/graphs/contributors" target="_blank" rel="nofollow"> ABP Community members' contributions</a>, <b>3157 commits</b> pushed from <b> 48 different contributors</b>.</p> |
|||
<p> We appreciate your hard work and effort you put into making ABP Framework better and improved.</p> |
|||
|
|||
|
|||
<h2> Events/Summits</h2> |
|||
<p> We try to contribute to the developers community as much as we can since day 1. This year was no different. We tried to give value through sponsorships for developer communities. Especially with us leaving the pandemic behind every day, we try to keep up with the in-person events as well as online events. We plan to do more in next year. So, stay tuned!</p> |
|||
<p> This year, we sponsored to 4 events. They were, <a href="https://kommunity.com/devnot/events/designing-monolith-first-for-microservice-architecture-e74fec40" target="_blank" rel="nofollow"> DevNot |
|||
Designing Monolith First for Microservice Architecture event</a>, <a href="https://www.dnfsummit.org/" target="_blank" rel="nofollow"> DNF Summit 2022</a>, <a href="https://summit.devnot.com/" target="_blank" rel="nofollow"> Developer Summit 2022</a>, and <a href="https://www.dotnetconf.net/" target="_blank" rel="nofollow"> .NET Conference 2022</a>. |
|||
|
|||
|
|||
<h2> ABP Releases </h2> |
|||
<p> ABP Framework released 4 versions from 5.1 to 7.1 in 2022. You can check the release logs from <a href="https://github.com/abpframework/abp/releases" target="_blank" rel="nofollow"> ABP Framework Release Logs</a>. </p> |
|||
<p> The most important milestone in these releases is that we upgraded ABP Framework to .NET 7.0 in <a href="https://blog.abp.io/abp/ABP.IO-Platform-7.0-RC-Has-Been-Published" target="_blank"> ABP v7.0</a>.</p> |
|||
<p> Additionally, we switched to OpenIddict for the startup templates in <a href="https://blog.abp.io/abp/ABP.IO-Platform-6.0-RC-Has-Been-Published" target="_blank"> ABP v6.0</a>.</p> |
|||
|
|||
|
|||
<h2> ABP Commercial</h2> |
|||
<p> It has been a successful year for ABP Commercial as well as ABP Framework. We have already reached to more than 100 countries over the years of ABP Commercial's release. This year, we continued to be streamline businesses' development processes with ABP Commercial. </p> |
|||
<ul> |
|||
<li> We have served to different sizes of businesses from <b> more than 50 countries</b> and <b> more than 40 industries </b>.</a></li> |
|||
<li> We performed <b>286 hours of training</b> to simplify users' learning curve of ABP Framework. </li> |
|||
<li> 1771 support tickets resolved in the premium support forum in which ABP Commercial users can ask their questions directly to ABP Core Team members via <a href="https://support.abp.io/" target="_blank"> ABP Commercial Support Center</a> in addition to community support we provide for ABP Framework users/developers. </li> |
|||
<li> We received 39 new testimonials, all from satisfied customers which led us to the other headline, Gartner Badges.</li> |
|||
</ul> |
|||
|
|||
|
|||
<h2> LeptonX Theme </h2> |
|||
<p>The Lepton Theme is a module that offers a theme for abp.io-based applications, featuring an Admin Dashboard designed by the ABP Platform. We released a version we called LeptonX Theme which is an upgraded version of Lepton Theme. You can view a live preview of the <a href="https://leptontheme.com/" target="_blank"> LeptonX Theme</a>. While the LeptonX theme is currently exclusive to ABP Commercial users, ABP Framework users can still access the Lite version. You can see the documentation for ABP LeptonX Theme light from <a href="https://docs.abp.io/en/abp/7.0/Themes/Index" target="_blank"> here</a>.</p> |
|||
|
|||
|
|||
<h2> Gartner Badges </h2> |
|||
<p> Gartner badges are given as an award to the listed softwares within their software review/suggestion platforms. To be able to get these awards, certain criterias have to be met such as ease of use, likelihood of recommend, functionality, etc. and they are calculated completely according to the users' real reviews. </p> |
|||
<p> In 2022, ABP Commercial reached to such success thanks to its users' support on Gartner, it has been recognized with 2 badges in Application Development category. </p> |
|||
<ul> ABP Commercial was selected in the following platforms of Gartner: |
|||
<li> <b>Software Advice's Front Runner:</b> <a href="https://www.softwareadvice.com/app-development/abp-commercial-profile/" target="_blank" rel="nofollow"> ABP Commercial's Software Advice profile</a> was given 2022 Front Runners badge according to its high success in terms of usability and customer satisfaction. <a href="https://blog.abp.io/abp/abpcommercial-2022-front-runner-in-app-development-category" target="_blank"> You can learn more about it from here</a>. </li> |
|||
<li> <b>GetApp's Category Leader</b>: <a href="https://www.getapp.com/development-tools-software/a/abp-commercial/" target="_blank" rel="nofollow"> ABP Commercial's GetApp profile</a> was given 2022 Category Leader badge among 368 other softwares in its category. <a href="https://blog.abp.io/abp/abpcommercial-2022-category-leader-in-app-development-category"> You can learn more about it from here</a>. </li> |
|||
</ul> |
|||
<p> Thank you all for all these recognition you deemed us worthy of. </p> |
|||
<p> ABP Framework is an open source infrastructure that enables developers to create modern web applications by following the best practices and conventions of software development. In 2022, ABP Framework continued to thrive, achieving significant milestones and making waves in the software development community. With more than 9K GitHub stars and over 10 millions of downloads on NuGet, ABP Framework has become a go-to framework for developers seeking a reliable and efficient way to build web applications.</p> |
|||
|
|||
|
|||
|
|||
<p>As ABP Team, we owe our success to our vibrant community, and we are immensely grateful for the support and contributions of each and every member. With your help, we achieved a lot in 2022. We remained committed to our values of transparency, openness, and collaboration, engaging with our community members as much as possible to ensure that we are creating a framework that meets their needs.</p> |
|||
|
|||
|
|||
|
|||
<p>One of the major highlights of 2022 was the release of .NET Core 7, which provided a powerful platform for ABP Framework to build upon. Additionally, ABP Commercial and our training programs continued to help developers and businesses to leverage the power of the ABP Framework, enabling them to build modern web applications more efficiently and effectively than ever before.</p> |
|||
|
|||
|
|||
|
|||
<p>In this article, we'll take a closer look at the key highlights of 2022 for ABP Framework, from major updates to achivements and the community insights. We are excited to share our progress with you and provide insights into how ABP Framework is continuing to shape the future of software development. So, let's dive in!</p> |
|||
|
|||
</ br> |
|||
|
|||
<img src="https://i.ibb.co/qjgK7Dj/2022-Highlights.png"> |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
<h2> NuGet Downloads </h2> |
|||
|
|||
<p> NuGet is a package manager designed specifically for the .NET ecosystem. It simplifies the process of creating and consuming packages, thanks to the NuGet client tools. By using these tools, developers can easily manage their project dependencies and improve their workflow.</p> |
|||
|
|||
<p> In 2022, <a href="https://www.nuget.org/packages/Volo.Abp.Core/7.0.1" target="_blank" rel="nofollow">ABP Core NuGet package</a> reached more than <b>10 million</b> of downloads! </p> |
|||
|
|||
<p> On the other hand, overall <a href="https://www.nuget.org/profiles/volosoft" target="_blank" rel="nofollow">Volosoft NuGet Packages</a> reached <b> more than half a billion</b> downloads!</p> |
|||
|
|||
<p> Thank you all for your interest and support towards Volosoft and ABP packages.</p> |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
<h2> E-Books </h2> |
|||
|
|||
<p> Our published e-book amount is reached <b>3</b>! This year, with our founder <a href="https://github.com/hikalkan" target="_blank" rel="nofollow">Halil İbrahim Kalkan</a>'s contributions we now have <b>3 published e-books</b>. </p> |
|||
|
|||
<ul> |
|||
|
|||
<li> <b>Mastering ABP Framework Book</b>: You can learn more details about it from <a href="https://abp.io/books/mastering-abp-framework" target="_blank">here</a> or <a href="https://www.amazon.com/gp/product/B097Z2DM8Q" target="_blank" rel="nofollow">purchase from Amazon</a> or <a href="https://www.packtpub.com/product/mastering-abp-framework/9781801079242" target="_blank" rel="nofollow"> purchase from Packt Publishing's website</a>.</li> |
|||
|
|||
<li> <b>Implementing Domain Driven Design</b>: You can download it for free from <a href="https://abp.io/books/implementing-domain-driven-design" target="_blank"> here</a>. </li> |
|||
|
|||
<li> <b>Building Microservice Solutions</b>: You can download it for free from <a href="https://abp.io/books/building-microservice-solutions" target="_blank">here</a>. </li> |
|||
|
|||
</ul> |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
<h2> Tutorial Videos </h2> |
|||
|
|||
<p> In 2022, we tried to be as much active as we could. To give you more insight and let you understand ABP Framework with short videos according to your interests, we published 48 tutorial videos. Though the videos were created by overall team members of ABP Framework, someone deserves a special mention here. Shout out to our ABP Core Team member <a href="https://github.com/braim23" target="_blank" rel="nofollow">Hamza Albreem</a> for his hard work.</p> |
|||
|
|||
<ul> |
|||
|
|||
<li> 6 videos have been published on How to Build a To Do App in a Single Layer which can be found in <a href="https://www.youtube.com/playlist?list=PLsNclT2aHJcPqZxk7D4tU8LtTeCFcN_ci" target="_blank"> ABP To-Do Application [Single Layer] Playlist</a>. </li> |
|||
|
|||
<li> 8 videos have been published on How to Build a To-Do App in Multi Layers which can be found in <a href="https://www.youtube.com/playlist?list=PLsNclT2aHJcM8LFSBnmmoNYBGwJ9-H8dG" target="_blank" rel="nofollow"> ABP To Do Application [Multi-Layers] Playlist</a>. </li> |
|||
|
|||
<li> 1 video has been published on ABP Framework Essentials which can be found in <a href="https://www.youtube.com/playlist?list=PLsNclT2aHJcNupH2wz83y7htugpLoUZ_B" target="_blank" rel="nofollow"> ABP Framework Essentials Playlist</a>. </li> |
|||
|
|||
<li> 13 videos have been published to introduce ABP Modules which can be found in <a href="https://www.youtube.com/playlist?list=PLsNclT2aHJcNQC8t7vighWWX6aDR5ZYbc" target="_blank" rel="nofollow"> ABP Modules Playlist</a>.</li> |
|||
|
|||
<li> And many other videos can be found on <a href="https://www.youtube.com/c/@volosoft" target="_blank" rel="nofollow">Volosoft YouTube Channel</a>.</li> |
|||
|
|||
</ul> |
|||
|
|||
|
|||
|
|||
<h2> GitHub Stars </h2> |
|||
|
|||
<p> ABP Framework GitHub repository reached <b>more than 9K stars</b>. We appreciate your interest and support for <a href="https://github.com/abpframework/abp" target="_blank" rel="nofollow">ABP Framework GitHub repository</a>. We are working hard to be worthy of your interest and reach out to more people to simplify and streamline their development processes.</p> |
|||
|
|||
|
|||
|
|||
<h2> Community Talks </h2> |
|||
|
|||
<p><a href="https://community.abp.io/events" target="_blank">ABP Community Talks</a> is our monthly event that brings together members of the ABP Framework community to discuss and exchange ideas. Prior to each event, we collect suggestions from our contributors, monitor trending topics in the industry, and review updates and news related to the ABP Platform to curate the topics for discussion. Once the topics are finalized, we announce them through our social media and community channels to ensure everyone is aware and can join in on the conversation.</p> |
|||
|
|||
<p> We did 10 <a href="https://www.youtube.com/playlist?list=PLsNclT2aHJcOsPustEkzG6DywiO8eh0lB" target="_blank" rel="nofollow">ABP Community Talks Episodes</a> of and 1 <a href="https://www.youtube.com/watch?v=RFArBh60RSA" target="_blank" rel="nofollow"> ABP Suite webinar</a>. You can take a look at them and check out our videos we have on <a href="https://www.youtube.com/c/@volosoft" target="_blank" rel="nofollow">Volosoft YouTube Channel</a>. </p> |
|||
|
|||
|
|||
|
|||
<h2> ABP Community Contributions </h2> |
|||
|
|||
<p> The ABP Community is a hub that offers resources such as articles, video tutorials, and updates on ABP's development progress and events for ABP Framework, .NET, and software development. Developers can also connect with others, help each other, and share their expertise in <a href="https://community.abp.io/">ABP Community</a>.</p> |
|||
|
|||
<ul> You can check out each source from the list below. |
|||
|
|||
<li>ABP Community Events: You can reach them from <a href="https://community.abp.io/events" target="_blank"> here</a>.</li> |
|||
|
|||
<li>ABP Community Posts: You can reach them from <a href="https://community.abp.io/posts" target="_blank"> here</a></li> |
|||
|
|||
<li>ABP Community Videos: You can reach them from <a href="https://community.abp.io/videos" target="_blank"> here</a>. </li> |
|||
|
|||
<li>ABP Community Stackoverflow: You can reach them from <a href="https://stackoverflow.com/questions/tagged/abp" target="_blank"> here</a>. </li> |
|||
|
|||
</ul> |
|||
|
|||
<p> In 2022, the community's contribution reached a point where <b>more than 100 resources</b>. Thank you for all your effort! Please keep it going! It is becoming a more and more rich resource thanks to your variety of contributions and help.</p> |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
<h2> ABP Community Discord Server</h2> |
|||
|
|||
<p> To take community interaction to the next level, we created the official <a href="https://community.abp.io/discord" target="_blank"> ABP Discord server</a>, providing a platform for the ABP Community to connect and communicate instantly through chatting.</p> |
|||
|
|||
<p> We were so excited <a href="https://blog.abp.io/abp/Official-ABP-Discord-Server-is-Here" target="_blank">announcing the official ABP Discord Server</a>. In the first week of announcing it, the server quickly attracted over 500 members. We're grateful for your interest and support, which confirms the need for a dedicated platform for community interaction.</p> |
|||
|
|||
> <a href="https://abp.io/join-discord" target="_blank" rel="nofollow">Join ABP Discord Server Now</a> |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
<h2> ABP Framework GitHub Contributions</h2> |
|||
|
|||
<p> In 2022, ABP Core Team worked hard to achieve milestones and give the best value with ABP Framework so users can benefit from its features. Additional to our team's work, ABP Framework is perfected in 2022 with <a href="https://github.com/abpframework/abp/graphs/contributors" target="_blank" rel="nofollow"> ABP Community members' contributions</a>, <b>3157 commits</b> pushed from <b> 48 different contributors</b>.</p> |
|||
|
|||
<p> We appreciate your hard work and effort you put into making ABP Framework better and improved.</p> |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
<h2> Events/Summits</h2> |
|||
|
|||
<p> We try to contribute to the developers community as much as we can since day 1. This year was no different. We tried to give value through sponsorships for developer communities. Especially with us leaving the pandemic behind every day, we try to keep up with the in-person events as well as online events. We plan to do more in next year. So, stay tuned!</p> |
|||
|
|||
<p> This year, we sponsored to 4 events. They were, <a href="https://kommunity.com/devnot/events/designing-monolith-first-for-microservice-architecture-e74fec40" target="_blank" rel="nofollow"> DevNot |
|||
|
|||
Designing Monolith First for Microservice Architecture event</a>, <a href="https://www.dnfsummit.org/" target="_blank" rel="nofollow"> DNF Summit 2022</a>, <a href="https://summit.devnot.com/" target="_blank" rel="nofollow"> Developer Summit 2022</a>, and <a href="https://www.dotnetconf.net/" target="_blank" rel="nofollow"> .NET Conference 2022</a>. |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
<h2> ABP Releases </h2> |
|||
|
|||
<p> ABP Framework released 4 versions from 5.1 to 7.1 in 2022. You can check the release logs from <a href="https://github.com/abpframework/abp/releases" target="_blank" rel="nofollow"> ABP Framework Release Logs</a>. </p> |
|||
|
|||
<p> The most important milestone in these releases is that we upgraded ABP Framework to .NET 7.0 in <a href="https://blog.abp.io/abp/ABP.IO-Platform-7.0-RC-Has-Been-Published" target="_blank"> ABP v7.0</a>.</p> |
|||
|
|||
<p> Additionally, we switched to OpenIddict for the startup templates in <a href="https://blog.abp.io/abp/ABP.IO-Platform-6.0-RC-Has-Been-Published" target="_blank"> ABP v6.0</a>.</p> |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
<h2> ABP Commercial</h2> |
|||
|
|||
<p> It has been a successful year for ABP Commercial as well as ABP Framework. We have already reached to more than 100 countries over the years of ABP Commercial's release. This year, we continued to be streamline businesses' development processes with ABP Commercial. </p> |
|||
|
|||
<ul> |
|||
|
|||
<li> We have served to different sizes of businesses from <b> more than 50 countries</b> and <b> more than 40 industries </b>.</a></li> |
|||
|
|||
<li> We performed <b>286 hours of training</b> to simplify users' learning curve of ABP Framework. </li> |
|||
|
|||
<li> 1771 support tickets resolved in the premium support forum in which ABP Commercial users can ask their questions directly to ABP Core Team members via <a href="https://support.abp.io/" target="_blank"> ABP Commercial Support Center</a> in addition to community support we provide for ABP Framework users/developers. </li> |
|||
|
|||
<li> We received 39 new testimonials, all from satisfied customers which led us to the other headline, Gartner Badges.</li> |
|||
|
|||
</ul> |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
<h2> LeptonX Theme </h2> |
|||
|
|||
<p>The Lepton Theme is a module that offers a theme for abp.io-based applications, featuring an Admin Dashboard designed by the ABP Platform. We released a version we called LeptonX Theme which is an upgraded version of Lepton Theme. You can view a live preview of the <a href="https://leptontheme.com/" target="_blank"> LeptonX Theme</a>. While the LeptonX theme is currently exclusive to ABP Commercial users, ABP Framework users can still access the Lite version. You can see the documentation for ABP LeptonX Theme light from <a href="https://docs.abp.io/en/abp/7.0/Themes/Index" target="_blank"> here</a>.</p> |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
<h2> Gartner Badges </h2> |
|||
|
|||
<p> Gartner badges are given as an award to the listed softwares within their software review/suggestion platforms. To be able to get these awards, certain criterias have to be met such as ease of use, likelihood of recommend, functionality, etc. and they are calculated completely according to the users' real reviews. </p> |
|||
|
|||
<p> In 2022, ABP Commercial reached to such success thanks to its users' support on Gartner, it has been recognized with 2 badges in Application Development category. </p> |
|||
|
|||
<ul> ABP Commercial was selected in the following platforms of Gartner: |
|||
|
|||
<li> <b>Software Advice's Front Runner:</b> <a href="https://www.softwareadvice.com/app-development/abp-commercial-profile/" target="_blank" rel="nofollow"> ABP Commercial's Software Advice profile</a> was given 2022 Front Runners badge according to its high success in terms of usability and customer satisfaction. <a href="https://blog.abp.io/abp/abpcommercial-2022-front-runner-in-app-development-category" target="_blank"> You can learn more about it from here</a>. </li> |
|||
|
|||
<li> <b>GetApp's Category Leader</b>: <a href="https://www.getapp.com/development-tools-software/a/abp-commercial/" target="_blank" rel="nofollow"> ABP Commercial's GetApp profile</a> was given 2022 Category Leader badge among 368 other softwares in its category. <a href="https://blog.abp.io/abp/abpcommercial-2022-category-leader-in-app-development-category"> You can learn more about it from here</a>. </li> |
|||
|
|||
</ul> |
|||
|
|||
<p> Thank you all for all these recognition you deemed us worthy of. </p> |
|||
|
|||
|
|||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 27 KiB |
@ -0,0 +1,548 @@ |
|||
|
|||
# How to Build Persistent Background Jobs with ABP Framework and Quartz |
|||
|
|||
## Introduction |
|||
|
|||
In modern SaaS applications, automated background processing is essential for delivering reliable user experiences. Whether you're sending subscription reminders, processing payments, or generating reports, background jobs ensure critical tasks happen on schedule without blocking your main application flow. |
|||
|
|||
### What is `Quartz.NET`? |
|||
|
|||
`Quartz.NET` is a powerful, open-source job scheduling library for .NET applications that provides cron-based scheduling for complex time patterns, job persistence across application restarts, clustering support for high-availability scenarios, flexible trigger types, and the ability to pass parameters to jobs through job data maps. It's the de facto standard for enterprise-grade job scheduling in the .NET ecosystem. |
|||
|
|||
### Quartz Storage Options: In-Memory vs Persistent |
|||
|
|||
When configuring **Quartz**, you have two primary storage options, each with significant implications for how your application behaves: |
|||
|
|||
### 🧠 In-Memory Storage (`RAMJobStore`) |
|||
- Keeps all job information in application memory. |
|||
- **Very fast** – no database overhead. |
|||
- **Volatile** – all jobs, triggers, and schedules are lost when the application stops or restarts. |
|||
- Best suited for: |
|||
- Development environments. |
|||
- Scenarios where job loss is acceptable. |
|||
|
|||
### 🗃️ Persistent Storage (`JobStoreTX` or similar) |
|||
- Stores all job information in a database. |
|||
- **Reliable** – schedules persist across: |
|||
- Application restarts |
|||
- Server crashes |
|||
- Deployments |
|||
- **Supports horizontal scaling** – multiple application instances can share the same job queue. |
|||
- **Slight performance overhead** due to database I/O. |
|||
- Best choice for: |
|||
- Production systems. |
|||
- Any scenario where **business continuity and reliability** are critical. |
|||
|
|||
### How ABP Simplifies Quartz Integration |
|||
|
|||
ABP handles Quartz configuration, dependency injection, and lifecycle management automatically. Developers define jobs using `QuartzBackgroundWorkerBase` and access services via `ICachedServiceProvider`, following ABP's standard conventions and leveraging optimal service caching for background job scenarios. |
|||
|
|||
### Benefits of the Integration |
|||
|
|||
- Full support for ABP’s cross-cutting concerns (e.g., multi-tenancy, localization) |
|||
- Robust scheduling powered by Quartz |
|||
- Built-in logging, error handling, and performance monitoring |
|||
- Scales easily without modifying business logic |
|||
|
|||
### Real-World Use Case: Subscription Reminders |
|||
|
|||
In this tutorial, we'll build a subscription reminder system that monitors client subscriptions, identifies those nearing expiration, sends professional email reminders seven days before expiration, tracks reminder history to prevent duplicates, and runs automatically every day at 9:00 AM using Quartz scheduling with PostgreSQL persistence. This system demonstrates how ABP and Quartz work together to solve real business problems with clean, maintainable code that follows enterprise-grade patterns. |
|||
|
|||
## Installing and Configuring Quartz |
|||
|
|||
Getting Quartz up and running in an ABP application is straightforward thanks to ABP's dedicated integration package. We'll replace the default background job system with Quartz for persistent job storage and robust scheduling capabilities. |
|||
|
|||
### Adding the Quartz Package |
|||
|
|||
The easiest way to add Quartz support to your ABP application is using the ABP CLI. Open a terminal in your project directory and run: |
|||
|
|||
```bash |
|||
abp add-package Volo.Abp.BackgroundWorkers.Quartz |
|||
``` |
|||
|
|||
This command automatically adds the necessary NuGet package reference and updates your module dependencies. The ABP CLI handles all the heavy lifting, ensuring you get the correct version that matches your ABP Framework version. |
|||
|
|||
### Configuring Quartz for Persistent Storage |
|||
|
|||
Once the package is installed, you need to configure Quartz to use your database (in my case it is PostgreSQL) for job persistence. This configuration goes in your main module's `PreConfigureServices` method: |
|||
|
|||
```csharp |
|||
[DependsOn( |
|||
// ... other dependencies |
|||
typeof(AbpBackgroundJobsQuartzModule), |
|||
typeof(AbpBackgroundWorkersQuartzModule) |
|||
)] |
|||
public class MySaaSApplicationModule : AbpModule |
|||
{ |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var hostingEnvironment = context.Services.GetHostingEnvironment(); |
|||
var configuration = context.Services.GetConfiguration(); |
|||
|
|||
ConfigureAuthentication(context, configuration); |
|||
ConfigureUrls(configuration); |
|||
ConfigureImpersonation(context, configuration); |
|||
ConfigureQuartz(); // Add this line |
|||
} |
|||
|
|||
private void ConfigureQuartz() |
|||
{ |
|||
PreConfigure<AbpQuartzOptions>(options => |
|||
{ |
|||
options.Properties = new NameValueCollection |
|||
{ |
|||
["quartz.scheduler.instanceName"] = "QuartzScheduler", |
|||
["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz", |
|||
["quartz.jobStore.tablePrefix"] = "qrtz_", |
|||
["quartz.jobStore.dataSource"] = "myDS", |
|||
["quartz.dataSource.myDS.connectionString"] = _configuration.GetConnectionString("Default"), |
|||
["quartz.dataSource.myDS.provider"] = "Npgsql", |
|||
["quartz.serializer.type"] = "json" |
|||
}; |
|||
}); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
This configuration tells Quartz to store all job information in your PostgreSQL database using tables prefixed with "qrtz_". The key points are: |
|||
|
|||
- **Job Store Type**: Uses ADO.NET with transaction support for reliable job persistence |
|||
- **Connection String**: Shares your application's existing database connection |
|||
- **Table Prefix**: Keeps Quartz tables separate with the "qrtz_" prefix |
|||
- **JSON Serialization**: Makes job data readable and debuggable |
|||
- **PostgreSQL Provider**: Uses Npgsql for optimal PostgreSQL integration |
|||
|
|||
When your application starts, ABP automatically initializes the Quartz scheduler with these settings. Any background workers you create will be registered and scheduled automatically, with their state persisted to the database for reliability across application restarts. |
|||
|
|||
For detailed installation options and advanced configuration scenarios, check the official [ABP documentation.](https://abp.io/docs/latest/framework/infrastructure/background-workers/quartz) |
|||
|
|||
|
|||
## Database Setup for Quartz |
|||
|
|||
With Quartz configured for persistent storage, we need to create the necessary database tables where Quartz will store job definitions, triggers, and execution history. Rather than running SQL scripts directly against the database, we'll use Entity Framework migrations to maintain consistency with ABP's database management approach. |
|||
|
|||
### Creating an Empty Migration for Quartz Tables |
|||
|
|||
Instead of executing raw SQL scripts against the database, we created an empty Entity Framework migration and populated it with the required Quartz table definitions. This approach keeps all database changes within the migration system, ensuring they're version-controlled, repeatable, and consistent across different environments. |
|||
|
|||
To create the empty migration, we used the standard Entity Framework CLI command: |
|||
|
|||
```bash |
|||
dotnet ef migrations add AddQuartzTables |
|||
``` |
|||
|
|||
This generates a new migration file with empty `Up` and `Down` methods that we can populate with the Quartz table creation scripts. |
|||
|
|||
### Adding Quartz SQL Schema to Migration |
|||
|
|||
Once the empty migration was created, we populated it with the PostgreSQL-specific SQL needed to create all Quartz tables. The SQL scripts were obtained from the official Quartz repository, which provides database schema scripts for various database providers: |
|||
|
|||
```csharp |
|||
public partial class AddQuartzTables : Migration |
|||
{ |
|||
protected override void Up(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.Sql(@" |
|||
CREATE TABLE qrtz_job_details ( |
|||
sched_name VARCHAR(120) NOT NULL, |
|||
job_name VARCHAR(200) NOT NULL, |
|||
job_group VARCHAR(200) NOT NULL, |
|||
description VARCHAR(250) NULL, |
|||
job_class_name VARCHAR(250) NOT NULL, |
|||
is_durable BOOLEAN NOT NULL, |
|||
is_nonconcurrent BOOLEAN NOT NULL, |
|||
is_update_data BOOLEAN NOT NULL, |
|||
requests_recovery BOOLEAN NOT NULL, |
|||
job_data BYTEA NULL, |
|||
PRIMARY KEY (sched_name, job_name, job_group) |
|||
); |
|||
|
|||
CREATE TABLE qrtz_triggers ( |
|||
sched_name VARCHAR(120) NOT NULL, |
|||
trigger_name VARCHAR(200) NOT NULL, |
|||
trigger_group VARCHAR(200) NOT NULL, |
|||
job_name VARCHAR(200) NOT NULL, |
|||
job_group VARCHAR(200) NOT NULL, |
|||
-- ... additional columns and constraints |
|||
PRIMARY KEY (sched_name, trigger_name, trigger_group), |
|||
FOREIGN KEY (sched_name, job_name, job_group) REFERENCES qrtz_job_details(sched_name, job_name, job_group) |
|||
); |
|||
|
|||
-- Additional tables: qrtz_simple_triggers, qrtz_cron_triggers, |
|||
-- qrtz_simprop_triggers, qrtz_blob_triggers, qrtz_calendars, |
|||
-- qrtz_paused_trigger_grps, qrtz_fired_triggers, qrtz_scheduler_state, qrtz_locks |
|||
"); |
|||
} |
|||
|
|||
protected override void Down(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.Sql(@" |
|||
DROP TABLE IF EXISTS qrtz_locks; |
|||
DROP TABLE IF EXISTS qrtz_scheduler_state; |
|||
-- ... drop all other Quartz tables in reverse order |
|||
DROP TABLE IF EXISTS qrtz_triggers; |
|||
DROP TABLE IF EXISTS qrtz_job_details; |
|||
"); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
The complete SQL scripts for all supported database providers, including PostgreSQL, MySQL, SQL Server, and others, can be found in the official `Quartz.NET` repository. You should use the script that matches your specific database provider and version requirements. |
|||
|
|||
### Why Use Migrations Instead of Direct SQL Scripts? |
|||
|
|||
This migration-based approach offers several important advantages over running SQL scripts directly: |
|||
|
|||
**Version Control Integration**: The migration becomes part of your codebase, tracked in source control alongside your application changes. This means every developer and deployment environment gets the exact same database schema. |
|||
|
|||
**Rollback Capability**: The `Down` method provides a clean way to remove Quartz tables if needed, something that's much harder to manage with standalone SQL scripts. |
|||
|
|||
**Environment Consistency**: Whether you're setting up a development machine, staging server, or production deployment, running DBMigrator or `dotnet ef database update` command ensures the same schema is created everywhere. |
|||
|
|||
**Integration with ABP's Database Management**: This approach aligns perfectly with how ABP manages all other database changes, keeping your database evolution strategy consistent. |
|||
|
|||
The Quartz tables created by this migration handle all aspects of job persistence - from storing job definitions and triggers to tracking execution history and managing scheduler state. With these tables in place, your Quartz scheduler can reliably persist jobs across application restarts and coordinate work across multiple application instances if needed. |
|||
|
|||
After creating this migration, running DBMigrator `dotnet ef database update` will create all the necessary Quartz infrastructure in your PostgreSQL database, ready to store and manage your background jobs. |
|||
|
|||
For complete SQL scripts for your specific database provider, visit the official [Quartz documentation.](https://www.quartz-scheduler.net/documentation/quartz-3.x/quick-start.html#creating-and-initializing-database) |
|||
## Building the Business Logic |
|||
|
|||
Before implementing our Quartz background job, we needed to create the essential business entities and services that our subscription reminder system would work with. Since this article focuses on Quartz integration rather than general ABP development patterns, we'll keep this section brief and move quickly to the background job implementation. |
|||
|
|||
### Core Entities and Services |
|||
|
|||
For our subscription reminder system, we created the following core components: |
|||
|
|||
**Entities:** |
|||
- **`Client`**: Represents customers with subscription information (Name, Email, SubscriptionEnd, IsActive) |
|||
- **`ReminderLog`**: Tracks when reminder emails have been sent to prevent duplicates |
|||
|
|||
**Application Services:** |
|||
- **`ClientAppService`**: Handles CRUD operations and provides methods to find clients with expiring subscriptions |
|||
- **`ReminderLogAppService`**: Manages reminder history and prevents duplicate notifications |
|||
- **`EmailService`**: Sends professional HTML reminder emails via SMTP |
|||
|
|||
**Data Transfer Objects (DTOs):** |
|||
- Complete set of DTOs for both entities following ABP conventions |
|||
- Input/output DTOs for all service operations |
|||
|
|||
### Business Logic Overview |
|||
|
|||
The system follows standard ABP patterns with entities inheriting from `FullAuditedAggregateRoot`, services implementing `ICrudAppService` interfaces, and proper AutoMapper configurations for entity-DTO mapping. We also included a data seeder to create sample clients for testing purposes. |
|||
|
|||
The key business methods our background job will use are: |
|||
- `GetExpiringClientsAsync()` - Finds clients whose subscriptions expire in the next 7 days |
|||
- `CreateAsync()` - Logs when a reminder has been sent |
|||
- `SendSubscriptionExpiryReminderAsync()` - Sends professional email reminders |
|||
|
|||
### Focus on Background Operations |
|||
|
|||
Rather than diving deep into ABP entity creation, repository patterns, or service layer implementation details, we'll move directly to the heart of this article: implementing robust background jobs with Quartz. The entities and services we created simply provide the business context for our background job to operate within. |
|||
|
|||
The real value of this tutorial lies in showing how ABP's `QuartzBackgroundWorkerBase` integrates seamlessly with your business logic to create reliable, persistent background operations that survive application restarts and scale across multiple instances. |
|||
|
|||
Let's now implement the background job that ties everything together and demonstrates the power of ABP + Quartz integration. |
|||
|
|||
|
|||
## Implementing the Background Job (The ABP Way) |
|||
|
|||
This is where the magic happens. ABP's integration with Quartz provides a clean, powerful way to create background jobs that follow framework conventions while leveraging Quartz's robust scheduling capabilities. Let's dive into how we implemented our subscription reminder job and explore the advanced features ABP provides. |
|||
|
|||
### Creating a QuartzBackgroundWorkerBase Job |
|||
|
|||
Instead of implementing Quartz's raw `IJob` interface, ABP provides `QuartzBackgroundWorkerBase`, which integrates seamlessly with ABP's dependency injection, logging, and lifecycle management systems: |
|||
|
|||
```csharp |
|||
public class SubscriptionExpiryNotifierJob : QuartzBackgroundWorkerBase |
|||
{ |
|||
public SubscriptionExpiryNotifierJob() |
|||
{ |
|||
// Configure the job to run daily at 9:00 AM |
|||
JobDetail = JobBuilder.Create<SubscriptionExpiryNotifierJob>() |
|||
.WithIdentity(nameof(SubscriptionExpiryNotifierJob)) |
|||
.Build(); |
|||
|
|||
Trigger = TriggerBuilder.Create() |
|||
.WithIdentity(nameof(SubscriptionExpiryNotifierJob)) |
|||
.WithCronSchedule("0 0 9 * * ?") // Every day at 9:00 AM |
|||
.Build(); |
|||
|
|||
ScheduleJob = async scheduler => |
|||
{ |
|||
if (!await scheduler.CheckExists(JobDetail.Key)) |
|||
{ |
|||
await scheduler.ScheduleJob(JobDetail, Trigger); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
public override async Task Execute(IJobExecutionContext context) |
|||
{ |
|||
// Use ICachedServiceProvider for better performance and proper scoping |
|||
var serviceProvider = ServiceProvider.GetRequiredService<ICachedServiceProvider>(); |
|||
|
|||
// These services will be cached and reused throughout the job execution |
|||
var clientAppService = serviceProvider.GetRequiredService<IClientAppService>(); |
|||
var reminderLogAppService = serviceProvider.GetRequiredService<IReminderLogAppService>(); |
|||
var emailService = serviceProvider.GetRequiredService<IEmailService>(); |
|||
|
|||
Logger.LogInformation("🔄 Starting subscription expiry notification job..."); |
|||
|
|||
// 1. Get clients expiring in 7 days |
|||
var expiringClients = await clientAppService.GetExpiringClientsAsync(7); |
|||
|
|||
Logger.LogInformation("📋 Found {Count} clients with expiring subscriptions", expiringClients.Count); |
|||
|
|||
// 2. Process each client |
|||
foreach (var client in expiringClients) |
|||
{ |
|||
await ProcessClientAsync(client, emailService, reminderLogAppService); |
|||
} |
|||
|
|||
Logger.LogInformation("✅ Job completed successfully"); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### Key Implementation Features |
|||
|
|||
**Constructor-Based Configuration**: Unlike traditional Quartz jobs that require external scheduling code, ABP's approach lets you define both the job and its schedule directly in the constructor. This keeps related configuration together and makes the job self-contained. |
|||
|
|||
**ABP Service Integration**: The `ICachedServiceProvider` gives you access to any service in ABP's dependency injection container, enabling you to use application services, repositories, domain services, or any other ABP component with optimized caching and proper scoping. |
|||
|
|||
**Built-in Logging**: The `Logger` property provides access to ABP's logging infrastructure, automatically including context like correlation IDs and tenant information in multi-tenant applications. |
|||
|
|||
**Custom Scheduling Logic**: The `ScheduleJob` property allows you to customize how the job gets registered with Quartz. In our example, we check if the job already exists before scheduling it, preventing duplicate registrations during application restarts. |
|||
|
|||
### Understanding Quartz Trigger Types |
|||
|
|||
Quartz provides several trigger types to handle different scheduling requirements. Choosing the right trigger type is crucial for your job's behavior and performance. |
|||
|
|||
#### CronTrigger - Complex Time-Based Scheduling |
|||
|
|||
CronTrigger uses cron expressions for sophisticated scheduling patterns. This is what we used for our daily subscription reminders: |
|||
|
|||
```csharp |
|||
// Daily at 9:00 AM |
|||
Trigger = TriggerBuilder.Create() |
|||
.WithIdentity("DailyReminder") |
|||
.WithCronSchedule("0 0 9 * * ?") |
|||
.Build(); |
|||
|
|||
// Every weekday at 2:30 PM |
|||
Trigger = TriggerBuilder.Create() |
|||
.WithIdentity("WeekdayReport") |
|||
.WithCronSchedule("0 30 14 ? * MON-FRI") |
|||
.Build(); |
|||
|
|||
// First day of every month at midnight |
|||
Trigger = TriggerBuilder.Create() |
|||
.WithIdentity("MonthlyCleanup") |
|||
.WithCronSchedule("0 0 0 1 * ?") |
|||
.Build(); |
|||
``` |
|||
|
|||
**Cron Expression Format**: `Seconds Minutes Hours Day-of-Month Month Day-of-Week Year(optional)` |
|||
- `0 0 9 * * ?` = 9:00 AM every day |
|||
- `0 */15 * * * ?` = Every 15 minutes |
|||
- `0 0 12 ? * SUN` = Every Sunday at noon |
|||
|
|||
#### SimpleTrigger - Interval-Based Scheduling |
|||
|
|||
SimpleTrigger is perfect for jobs that need to run at regular intervals or a specific number of times: |
|||
|
|||
```csharp |
|||
// Run every 30 seconds indefinitely |
|||
Trigger = TriggerBuilder.Create() |
|||
.WithIdentity("HealthCheck") |
|||
.StartNow() |
|||
.WithSimpleSchedule(x => x |
|||
.WithIntervalInSeconds(30) |
|||
.RepeatForever()) |
|||
.Build(); |
|||
|
|||
// Run every 5 minutes, but only 10 times |
|||
Trigger = TriggerBuilder.Create() |
|||
.WithIdentity("LimitedRetry") |
|||
.StartNow() |
|||
.WithSimpleSchedule(x => x |
|||
.WithIntervalInMinutes(5) |
|||
.WithRepeatCount(9)) // 0-based, so 9 = 10 executions |
|||
.Build(); |
|||
|
|||
// One-time execution after 1 hour delay |
|||
Trigger = TriggerBuilder.Create() |
|||
.WithIdentity("DelayedCleanup") |
|||
.StartAt(DateTimeOffset.UtcNow.AddHours(1)) |
|||
.Build(); |
|||
``` |
|||
|
|||
#### CalendarIntervalTrigger - Calendar-Aware Intervals |
|||
|
|||
CalendarIntervalTrigger handles intervals that need to respect calendar boundaries: |
|||
|
|||
```csharp |
|||
// Every month on the same day (handles varying month lengths) |
|||
Trigger = TriggerBuilder.Create() |
|||
.WithIdentity("MonthlyBilling") |
|||
.WithCalendarIntervalSchedule(x => x |
|||
.WithIntervalInMonths(1)) |
|||
.Build(); |
|||
|
|||
// Every week, starting Monday |
|||
Trigger = TriggerBuilder.Create() |
|||
.WithIdentity("WeeklyReport") |
|||
.WithCalendarIntervalSchedule(x => x |
|||
.WithIntervalInWeeks(1)) |
|||
.Build(); |
|||
``` |
|||
|
|||
#### DailyTimeIntervalTrigger - Time Windows |
|||
|
|||
DailyTimeIntervalTrigger runs jobs within specific time windows on certain days: |
|||
|
|||
```csharp |
|||
// Every 2 hours between 8 AM and 6 PM, Monday through Friday |
|||
Trigger = TriggerBuilder.Create() |
|||
.WithIdentity("BusinessHoursSync") |
|||
.WithDailyTimeIntervalSchedule(x => x |
|||
.OnMondayThroughFriday() |
|||
.StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(8, 0)) |
|||
.EndingDailyAt(TimeOfDay.HourAndMinuteOfDay(18, 0)) |
|||
.WithIntervalInHours(2)) |
|||
.Build(); |
|||
``` |
|||
|
|||
### Choosing the Right Trigger Type |
|||
|
|||
For different scenarios, you'd choose different trigger types: |
|||
|
|||
- **Daily/Weekly/Monthly Operations**: Use **CronTrigger** for maximum flexibility |
|||
- **High-Frequency Tasks**: Use **SimpleTrigger** for performance (every few seconds/minutes) |
|||
- **Business Calendar Operations**: Use **CalendarIntervalTrigger** for month-end reports, quarterly tasks |
|||
- **Business Hours Operations**: Use **DailyTimeIntervalTrigger** for operations that should only run during specific hours |
|||
|
|||
### Automatic Job Registration |
|||
|
|||
One of ABP's most powerful features is automatic job discovery and registration. When your application starts, ABP automatically: |
|||
|
|||
1. **Scans for Background Workers**: ABP discovers all classes inheriting from `QuartzBackgroundWorkerBase` |
|||
2. **Registers with DI Container**: Each job is registered as a service in the dependency injection container |
|||
3. **Schedules with Quartz**: ABP calls the `ScheduleJob` delegate to register the job with the Quartz scheduler |
|||
4. **Handles Lifecycle**: ABP manages starting and stopping jobs with the application lifecycle |
|||
|
|||
This means you simply create your job class, and ABP handles everything else. No manual registration, no startup code, no configuration files - it just works. |
|||
|
|||
### Understanding Misfire Handling |
|||
|
|||
Misfires occur when a scheduled job cannot execute at its intended time, typically due to system downtime, resource constraints, or the scheduler being paused. Quartz provides several misfire instructions to handle these scenarios: |
|||
|
|||
#### CronTrigger Misfire Instructions |
|||
|
|||
For cron-based schedules like our daily reminder job, Quartz offers these misfire behaviors: |
|||
|
|||
**`MisfireInstruction.DoNothing`** (Default): |
|||
```csharp |
|||
Trigger = TriggerBuilder.Create() |
|||
.WithIdentity(nameof(SubscriptionExpiryNotifierJob)) |
|||
.WithCronSchedule("0 0 9 * * ?", x => x.WithMisfireHandlingInstructionDoNothing()) |
|||
.Build(); |
|||
``` |
|||
- Skips all missed executions |
|||
- Waits for the next naturally scheduled time |
|||
- Best for jobs where missing executions is acceptable |
|||
|
|||
**`MisfireInstruction.FireOnceNow`**: |
|||
```csharp |
|||
.WithCronSchedule("0 0 9 * * ?", x => x.WithMisfireHandlingInstructionFireAndProceed()) |
|||
``` |
|||
- Immediately executes one missed job upon recovery |
|||
- Then continues with the normal schedule |
|||
- Useful when you need to catch up on missed work |
|||
|
|||
**`MisfireInstruction.IgnoreMisfires`**: |
|||
```csharp |
|||
.WithCronSchedule("0 0 9 * * ?", x => x.WithMisfireHandlingInstructionIgnoreMisfires()) |
|||
``` |
|||
- Executes all missed jobs immediately upon recovery |
|||
- Can cause a burst of executions after extended downtime |
|||
- Use carefully to avoid overwhelming the system |
|||
|
|||
#### SimpleTrigger Misfire Instructions |
|||
|
|||
Simple triggers have their own set of misfire behaviors: |
|||
|
|||
**`MisfireInstruction.FireNow`**: Execute immediately when recovered |
|||
**`MisfireInstruction.RescheduleNowWithExistingRepeatCount`**: Start over with remaining repeat count |
|||
**`MisfireInstruction.RescheduleNowWithRemainingRepeatCount`**: Continue as if no misfire occurred |
|||
**`MisfireInstruction.RescheduleNextWithExistingCount`**: Wait for next interval, keep original repeat count |
|||
|
|||
### Real-World Misfire Considerations |
|||
|
|||
For our subscription reminder system, we chose the default `DoNothing` behavior because: |
|||
|
|||
- **Business Logic**: Sending yesterday's reminder today might confuse customers |
|||
- **Duplicate Prevention**: Our job checks for existing reminders, so running late won't cause duplicate emails |
|||
- **Resource Management**: We avoid overwhelming the email system after extended downtime |
|||
|
|||
However, for other scenarios you might choose differently: |
|||
- **Financial reporting**: Use `FireOnceNow` to ensure reports are always generated |
|||
- **Data synchronization**: Use `IgnoreMisfires` to process all missed sync operations |
|||
- **Cache warming**: Use `DoNothing` since stale cache warming provides no value |
|||
|
|||
### Advanced Job Features |
|||
|
|||
**Error Handling and Resilience**: Our job implementation includes comprehensive error handling for individual client processing, ensuring one failed email doesn't stop the entire batch: |
|||
|
|||
```csharp |
|||
try |
|||
{ |
|||
await emailService.SendSubscriptionExpiryReminderAsync(/*...*/); |
|||
await LogReminderAsync(client.Id, client.SubscriptionEnd, "Email sent successfully", reminderLogAppService); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Logger.LogError(ex, "❌ Failed to send reminder to {ClientName}", client.Name); |
|||
await LogReminderAsync(client.Id, client.SubscriptionEnd, $"Failed: {ex.Message}", reminderLogAppService); |
|||
} |
|||
``` |
|||
|
|||
**Duplicate Prevention**: The job checks for existing reminders to prevent sending multiple emails on the same day, even if the job runs multiple times: |
|||
|
|||
```csharp |
|||
private async Task<bool> AlreadySentTodayAsync(Guid clientId, IReminderLogAppService reminderLogAppService) |
|||
{ |
|||
var todayReminders = await reminderLogAppService.GetByClientIdAsync(clientId); |
|||
var today = DateTime.UtcNow.Date; |
|||
|
|||
return todayReminders.Any(r => r.ReminderDate.Date == today); |
|||
} |
|||
``` |
|||
|
|||
This implementation demonstrates how ABP's `QuartzBackgroundWorkerBase` provides a clean, powerful foundation for building robust background jobs that integrate seamlessly with your business logic while leveraging Quartz's enterprise-grade scheduling capabilities. |
|||
|
|||
## Conclusion |
|||
|
|||
You've successfully built a production-ready subscription reminder system that demonstrates the powerful synergy between ABP Framework and `Quartz.NET`. This isn't just a tutorial example - it's a robust, enterprise-grade solution that handles real business requirements. |
|||
|
|||
### What We Accomplished |
|||
|
|||
**✅ Enterprise-Grade Reliability**: PostgreSQL persistence ensures jobs survive restarts and deployments |
|||
**✅ ABP Best Practices**: Used `QuartzBackgroundWorkerBase`, `ICachedServiceProvider`, and ABP's logging infrastructure |
|||
**✅ Real Business Value**: Automated subscription reminders with duplicate prevention and audit logging |
|||
**✅ Flexible Scheduling**: Explored cron expressions, trigger types, and misfire handling strategies |
|||
|
|||
### The Power of ABP + Quartz Integration |
|||
|
|||
The combination delivers exceptional value through automatic job discovery, persistent scheduling, built-in dependency injection, and seamless framework integration. You get enterprise reliability with developer-friendly simplicity. |
|||
|
|||
### Final Thoughts |
|||
|
|||
Complex background processing doesn't have to be complicated to implement. ABP's thoughtful abstractions combined with Quartz's proven engine create a development experience that's both powerful and enjoyable. |
|||
|
|||
Whether you're building subscription management, financial reporting, or data synchronization, these patterns provide a solid foundation for reliable, maintainable solutions. |
|||
|
|||
You can reach sample project's source code from [here](https://github.com/MansurBesleney/MySaaSApplication) |
|||
|
|||
**Happy coding, and may your background jobs never miss a beat!** 🚀 |
|||
|
After Width: | Height: | Size: 126 KiB |
@ -0,0 +1,138 @@ |
|||
# Integration Services in ABP — What they are, when to use them, and how they behave 🚦 |
|||
|
|||
If you’ve been building with ABP for a while, you’ve probably used Application Services for your UI and APIs in your .NET and ASP.NET Core apps. Integration Services are similar—but with a different mission: they exist for service-to-service or module-to-module communication, not for end users. |
|||
|
|||
If you want the formal spec, see the official doc: [Integration Services](../../framework/api-development/integration-services.md). This post is the practical, no-fluff guide. |
|||
|
|||
## What is an Integration Service? |
|||
|
|||
An Integration Service is an application service or ASP.NET Core MVC controller marked with the `[IntegrationService]` attribute. That marker tells ABP “this endpoint is for internal communication.” |
|||
|
|||
- They are not exposed by default (safer for reusable modules and monoliths). |
|||
- When exposed, their route prefix is `/integration-api` (so you can easily protect them at your gateway or firewall). |
|||
- Auditing is disabled by default for them (less noise for machine-to-machine calls). |
|||
|
|||
Quick look: |
|||
|
|||
```csharp |
|||
[IntegrationService] |
|||
public interface IProductIntegrationService : IApplicationService |
|||
{ |
|||
Task<List<ProductDto>> GetProductsByIdsAsync(List<Guid> ids); |
|||
} |
|||
|
|||
public class ProductIntegrationService : ApplicationService, IProductIntegrationService |
|||
{ |
|||
public Task<List<ProductDto>> GetProductsByIdsAsync(List<Guid> ids) |
|||
{ |
|||
// fetch and return minimal product info for other services/modules |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Are they HTTP endpoints? |
|||
|
|||
- By default: no (they won’t be reachable over HTTP in the ASP.NET Core routing pipeline). |
|||
- If you need them over HTTP (typically for microservices), explicitly enable: |
|||
|
|||
```csharp |
|||
Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options.ExposeIntegrationServices = true; |
|||
}); |
|||
``` |
|||
|
|||
Once exposed, ABP puts them under `/integration-api/...` instead of `/api/...` in the ASP.NET Core routing pipeline. That’s your hint to restrict them from public internet access. |
|||
|
|||
## Enable auditing (optional) |
|||
|
|||
If you want audit logs for integration calls, enable it explicitly: |
|||
|
|||
```csharp |
|||
Configure<AbpAuditingOptions>(options => |
|||
{ |
|||
options.IsEnabledForIntegrationServices = true; |
|||
}); |
|||
``` |
|||
|
|||
## When should you use Integration Services? |
|||
|
|||
- Internal, synchronous operations between services or modules. |
|||
- You need a “thin” API designed for other services (not for UI): minimal DTOs, no view concerns, predictable contracts. |
|||
- You want to hide these endpoints from public clients, or only allow them inside your private network or k8s cluster. |
|||
- You’re packaging a reusable module that might be used in both monolith and microservice deployments. |
|||
|
|||
## When NOT to use them |
|||
|
|||
- Public APIs or anything intended for browsers/mobile apps → use regular application services/controllers. |
|||
- Asynchronous cross-service workflows → consider domain events + outbox/inbox; use Integration Services for sync calls. |
|||
- Complex, chatty UI endpoints → those belong to your external API surface, not internal integration. |
|||
|
|||
## Common use-cases and examples |
|||
|
|||
- Identity lookups across services: an Ordering service needs basic user info from the Identity service. |
|||
- Permission checks from another module: a CMS module asks a Permission service for access decisions. |
|||
- Product data hydrations: a Cart service needs minimal product details (price, name) from Catalog. |
|||
- Internal admin/maintenance operations that aren’t meant for end users but are needed by other services. |
|||
|
|||
## Example: microservice-to-microservice call |
|||
|
|||
1) Mark and expose the integration service in the target service: |
|||
|
|||
```csharp |
|||
[IntegrationService] |
|||
public interface IUserIntegrationService : IApplicationService |
|||
{ |
|||
Task<UserBriefDto?> FindByIdAsync(Guid id); |
|||
} |
|||
|
|||
Configure<AbpAspNetCoreMvcOptions>(o => o.ExposeIntegrationServices = true); |
|||
``` |
|||
|
|||
2) In the caller service, add an HTTP client proxy only for Integration Services if you like to keep things clean: |
|||
|
|||
```csharp |
|||
services.AddHttpClientProxies( |
|||
typeof(TargetServiceApplicationModule).Assembly, |
|||
remoteServiceConfigurationName: "TargetService", |
|||
asDefaultServices: true, |
|||
applicationServiceTypes: ApplicationServiceTypes.IntegrationServices); |
|||
``` |
|||
|
|||
3) Call it just like a local service (ABP’s HTTP proxy handles the wire): |
|||
|
|||
```csharp |
|||
public class OrderAppService : ApplicationService |
|||
{ |
|||
private readonly IUserIntegrationService _userIntegrationService; |
|||
|
|||
public OrderAppService(IUserIntegrationService userIntegrationService) |
|||
{ |
|||
_userIntegrationService = userIntegrationService; |
|||
} |
|||
|
|||
public async Task PlaceOrderAsync(CreateOrderDto input) |
|||
{ |
|||
var user = await _userIntegrationService.FindByIdAsync(CurrentUser.GetId()); |
|||
// validate user status, continue placing order... |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Monolith vs. Microservices |
|||
|
|||
- Monolith: keep them unexposed and call via DI in-process. You get the same clear contract with zero network overhead. |
|||
- Microservices: expose them and route behind your gateway. The `/integration-api` prefix makes it easy to firewall/gateway-restrict. |
|||
|
|||
## Practical tips |
|||
|
|||
- Keep integration DTOs lean and stable. These are machine contracts—don’t mix UI concerns. |
|||
- Name them clearly (e.g., `UserIntegrationService`) so intent is obvious. |
|||
- Guard your ASP.NET Core gateway application: block `/integration-api/*` from public traffic. |
|||
- Enable auditing only if you truly need the logs for these calls. |
|||
|
|||
## Further reading |
|||
|
|||
- Official docs: [Integration Services](../../framework/api-development/integration-services.md) |
|||
|
|||
That’s it! Integration Services give you a clean, intentional way to design internal APIs—great in monoliths, essential in microservices. |
|||
|
After Width: | Height: | Size: 4.8 MiB |
|
After Width: | Height: | Size: 377 KiB |
|
After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 1.7 MiB |
@ -0,0 +1,83 @@ |
|||
# 🚀 Best Practices for Azure DevOps CI/CD Pipelines |
|||
|
|||
**CI/CD (Continuous Integration / Continuous Delivery)** is not just fancy tech talk - it's now a must-have for modern software teams. |
|||
Microsoft's **Azure DevOps** helps make these processes easier to manage. |
|||
But how do you create pipelines that work well for your team? Let's look at some practical tips that will make your life easier. |
|||
|
|||
--- |
|||
|
|||
## 1. 📜 Define Your Pipeline as Code |
|||
|
|||
Don't use the manual setup method that's hard to track. Azure DevOps lets you use **YAML files** for your pipelines, which gives you: |
|||
|
|||
- A record of all changes - who made them and when |
|||
- The same setup across all environments |
|||
- The ability to undo changes when something goes wrong |
|||
|
|||
This stops the common problem where something works on one computer but not another. |
|||
|
|||
 |
|||
|
|||
--- |
|||
|
|||
## 2. 🔑 Store Sensitive Information Safely |
|||
|
|||
Never put passwords directly in your code, even temporarily. |
|||
Each environment should have its own settings, and keep sensitive information in **Azure Key Vault** or **Library Variable Groups**. |
|||
|
|||
You'll avoid security problems later. |
|||
|
|||
<!--  --> |
|||
--- |
|||
|
|||
## 3. 🏗️ Keep Building and Releasing Separate |
|||
|
|||
Think of **Building** like cooking a meal - you prepare everything and package it up. |
|||
**Releasing** is like delivering that meal to different people. |
|||
|
|||
Keeping these as separate steps means: |
|||
|
|||
- You create your package once, then send it to multiple places |
|||
- You save time and resources by not rebuilding the same thing over and over |
|||
|
|||
 |
|||
|
|||
--- |
|||
|
|||
## 4. 🧪 Add Automatic Testing |
|||
|
|||
Don't waste time testing the same things manually over and over. |
|||
Set up **different types of tests** to run automatically. When tests run every time you make changes: |
|||
|
|||
- You catch problems before your customers do |
|||
- Your software quality stays high without extra manual work |
|||
|
|||
Azure DevOps has tools to help you see test results easily without searching through technical logs. |
|||
|
|||
--- |
|||
|
|||
## 5. 🛡️ Add Safety Checks |
|||
|
|||
Automatic doesn't mean pushing everything to your live system right away. |
|||
For important environments, add **human approval steps** or **automatic checks** like security scans. |
|||
|
|||
This helps you avoid emergency problems in the middle of the night. |
|||
|
|||
 |
|||
|
|||
|
|||
--- |
|||
|
|||
## ✅ Conclusion |
|||
|
|||
Good Azure DevOps pipelines aren't just about automation - they help you feel confident in your process. |
|||
Remember these main points: |
|||
|
|||
✔ Use YAML files to keep everything visible and trackable |
|||
✔ Keep passwords and sensitive data in secure storage (not in your code) |
|||
✔ Build once, deploy to many places |
|||
✔ Let automatic tests find problems before users do |
|||
✔ Add safety checks for important systems |
|||
|
|||
 |
|||
--- |
|||
@ -0,0 +1,398 @@ |
|||
# ABP Now Supports Angular Standalone Applications |
|||
|
|||
We are excited to announce that **ABP now supports Angular’s standalone component structure** in the latest Studio update. This article walks you through how to generate a standalone application, outlines the migration steps, and highlights the benefits of this shift over traditional module-based architecture. |
|||
|
|||
--- |
|||
|
|||
## Why Standalone? |
|||
|
|||
Angular's standalone component architecture, which is introduced in version 14 and made default in version 19, is a major leap forward for Angular development. Here is why it matters: |
|||
|
|||
### 🔧 Simplified Project Structure |
|||
|
|||
Standalone components eliminate the need for `NgModule` wrappers. This leads to: |
|||
|
|||
- Fewer files to manage |
|||
- Cleaner folder organization |
|||
- Reduced boilerplate |
|||
|
|||
Navigating and understanding your codebase becomes easier for everyone on your team. |
|||
|
|||
### 🚀 Faster Bootstrapping |
|||
|
|||
Standalone apps simplify app initialization: |
|||
|
|||
```ts |
|||
bootstrapApplication(AppComponent, appConfig); |
|||
``` |
|||
|
|||
This avoids the need for `AppModule` and speeds up startup times. |
|||
|
|||
### 📦 Smaller Bundle Sizes |
|||
|
|||
Since components declare their own dependencies, Angular can more effectively tree-shake unused code. Result? Smaller bundle sizes and faster load times. |
|||
|
|||
### 🧪 Easier Testing & Reusability |
|||
|
|||
Standalone components are self-contained. They declare their dependencies within the `imports` array, making them: |
|||
|
|||
- Easier to test in isolation |
|||
- Easier to reuse in different contexts |
|||
|
|||
### 🧠 Clearer Dependency Management |
|||
|
|||
Standalone components explicitly define what they need. No more hidden dependencies buried in shared modules. |
|||
|
|||
### 🔄 Gradual Adoption |
|||
|
|||
You can mix and match standalone and module-based components. This allows for **incremental migration**, reducing risk in larger codebases. Here is the related document for the [standalone migration](https://angular.dev/reference/migrations/standalone). |
|||
|
|||
--- |
|||
|
|||
## Getting Started: Creating a Standalone Angular App |
|||
|
|||
Angular CLI makes it easy to start: |
|||
|
|||
```bash |
|||
ng new my-app |
|||
``` |
|||
|
|||
With Angular 19, new apps follow this bootstrapping model: |
|||
|
|||
```ts |
|||
// main.ts |
|||
import { bootstrapApplication } from "@angular/platform-browser"; |
|||
import { appConfig } from "./app/app.config"; |
|||
import { AppComponent } from "./app/app.component"; |
|||
|
|||
bootstrapApplication(AppComponent, appConfig).catch((err) => |
|||
console.error(err) |
|||
); |
|||
``` |
|||
|
|||
The `app.config.ts` file replaces `AppModule`: |
|||
|
|||
```ts |
|||
// app.config.ts |
|||
import { ApplicationConfig, provideZoneChangeDetection } from "@angular/core"; |
|||
import { provideRouter } from "@angular/router"; |
|||
import { routes } from "./app.routes"; |
|||
|
|||
export const appConfig: ApplicationConfig = { |
|||
providers: [ |
|||
provideZoneChangeDetection({ eventCoalescing: true }), |
|||
provideRouter(routes), |
|||
], |
|||
}; |
|||
``` |
|||
|
|||
Routing is defined in a simple `Routes` array: |
|||
|
|||
```ts |
|||
// app.routes.ts |
|||
import { Routes } from "@angular/router"; |
|||
|
|||
export const routes: Routes = []; |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## ABP Studio Support for Standalone Structure |
|||
|
|||
Starting with the latest release (insert version number here), ABP Studio fully supports Angular's standalone structure. While the new format is encouraged, module-based structure will continue to be supported for backwards compatibility. |
|||
|
|||
To try it out, simply update your ABP Studio to create apps with the latest version. |
|||
|
|||
--- |
|||
|
|||
## What’s New in ABP Studio Templates? |
|||
|
|||
When you generate an app using the latest ABP Studio, the project structure aligns with Angular's standalone architecture. |
|||
|
|||
This migration is split into four parts: |
|||
|
|||
1. **Package updates** |
|||
2. **Schematics updates** |
|||
3. **Suite code generation updates** |
|||
4. **Template refactors** |
|||
|
|||
--- |
|||
|
|||
## Package Migration Details |
|||
|
|||
Migration has been applied to packages in the [ABP GitHub repository](https://github.com/abpframework/abp/tree/dev/npm/ng-packs/packages). Here is an example from the Identity package. |
|||
|
|||
### 🧩 Migrating Components |
|||
|
|||
Components are made standalone, using: |
|||
|
|||
```bash |
|||
ng g @angular/core:standalone |
|||
``` |
|||
|
|||
Example: |
|||
|
|||
```ts |
|||
@Component({ |
|||
selector: 'abp-roles', |
|||
templateUrl: './roles.component.html', |
|||
providers: [...], |
|||
imports: [ |
|||
ReactiveFormsModule, |
|||
LocalizationPipe, |
|||
... |
|||
], |
|||
}) |
|||
export class RolesComponent implements OnInit { ... } |
|||
``` |
|||
|
|||
### 🛣 Updating Routing |
|||
|
|||
Old lazy-loaded routes using `forLazy()`: |
|||
|
|||
```ts |
|||
{ |
|||
path: 'identity', |
|||
loadChildren: () => import('@abp/ng.identity').then(m => m.IdentityModule.forLazy({...})) |
|||
} |
|||
``` |
|||
|
|||
Now replaced with: |
|||
|
|||
```ts |
|||
{ |
|||
path: 'identity', |
|||
loadChildren: () => import('@abp/ng.identity').then(c => c.createRoutes({...})) |
|||
} |
|||
``` |
|||
|
|||
### 🧱 Replacing Module Declarations |
|||
|
|||
The old setup: |
|||
|
|||
```ts |
|||
// identity.module.ts |
|||
@NgModule({ |
|||
imports: [IdentityRoutingModule, RolesComponent, UsersComponent], |
|||
}) |
|||
export class IdentityModule {...} |
|||
``` |
|||
|
|||
```ts |
|||
//identity-routing.module |
|||
const routes: Routes = [...]; |
|||
@NgModule({ |
|||
imports: [RouterModule.forChild(routes)], |
|||
exports: [RouterModule], |
|||
}) |
|||
export class IdentityRoutingModule {} |
|||
``` |
|||
|
|||
New setup: |
|||
|
|||
```ts |
|||
// identity-routes.ts |
|||
export function provideIdentity(options: IdentityConfigOptions = {}): Provider[] { |
|||
return [...]; |
|||
} |
|||
export const createRoutes = (options: IdentityConfigOptions = {}): Routes => [ |
|||
{ |
|||
path: '', |
|||
component: RouterOutletComponent, |
|||
providers: provideIdentity(options), |
|||
children: [ |
|||
{ |
|||
path: 'roles', |
|||
component: ReplaceableRouteContainerComponent, |
|||
data: { |
|||
requiredPolicy: 'AbpIdentity.Roles', |
|||
replaceableComponent: { |
|||
key: eIdentityComponents.Roles, |
|||
defaultComponent: RolesComponent, |
|||
}, |
|||
}, |
|||
title: 'AbpIdentity::Roles', |
|||
}, |
|||
... |
|||
], |
|||
}, |
|||
]; |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## ABP Schematics Migration Details |
|||
|
|||
You can reach details by checking [ABP Schematics codebase](https://github.com/abpframework/abp/tree/dev/npm/ng-packs/packages/schematics). |
|||
|
|||
### 📚 Library creation |
|||
|
|||
When you run the `abp create-lib` command, the prompter will ask you the `templateType`. It supports both module and standalone templates. |
|||
|
|||
```ts |
|||
"templateType": { |
|||
"type": "string", |
|||
"description": "Type of the template", |
|||
"enum": ["module", "standalone"], |
|||
"x-prompt": { |
|||
"message": "Select the type of template to generate:", |
|||
"type": "list", |
|||
"items": [ |
|||
{ "value": "module", "label": "Module Template" }, |
|||
{ "value": "standalone", "label": "Standalone Template" } |
|||
] |
|||
} |
|||
}, |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## ABP Suite Code Generation Migration Details |
|||
|
|||
ABP Suite will also be supporting both structures. If you have a project that is generated with the previous versions, the Suite will detect the structure in that way and generate the related code accordingly. Conversely, here is what is changed for the standalone migration: |
|||
|
|||
**❌ Discarded module files** |
|||
|
|||
```ts |
|||
// entity-one.module.ts |
|||
@NgModule({ |
|||
declarations: [], |
|||
imports: [EntityOneComponent, EntityOneRoutingModule], |
|||
}) |
|||
export class EntityOneModule {} |
|||
``` |
|||
|
|||
```ts |
|||
// entity-one-routing.module.ts |
|||
export const routes: Routes = [ |
|||
{ |
|||
path: "", |
|||
component: EntityOneComponent, |
|||
canActivate: [authGuard, permissionGuard], |
|||
}, |
|||
]; |
|||
|
|||
@NgModule({ |
|||
imports: [RouterModule.forChild(routes)], |
|||
exports: [RouterModule], |
|||
}) |
|||
export class EntityOneRoutingModule {} |
|||
``` |
|||
|
|||
```ts |
|||
// app-routing.module.ts |
|||
{ |
|||
path: 'entity-ones', |
|||
loadChildren: () => |
|||
import('./entity-ones/entity-one/entity-one.module').then(m => m.EntityOneModule), |
|||
}, |
|||
``` |
|||
|
|||
**✅ Added routes configuration** |
|||
|
|||
```ts |
|||
// entity-one.routes.ts |
|||
export const ENTITY_ONE_ROUTES: Routes = [ |
|||
{ |
|||
path: "", |
|||
loadComponent: () => { |
|||
return import("./components/entity-one.component").then( |
|||
(c) => c.EntityOneComponent |
|||
); |
|||
}, |
|||
canActivate: [authGuard, permissionGuard], |
|||
}, |
|||
]; |
|||
``` |
|||
|
|||
```ts |
|||
// app.routes.ts |
|||
{ path: 'entity-ones', children: ENTITY_ONE_ROUTES }, |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## Template Migration Details |
|||
|
|||
### 🧭 Routing: `app.routes.ts` |
|||
|
|||
```ts |
|||
// app.routes.ts |
|||
import { Routes } from '@angular/router'; |
|||
|
|||
export const APP_ROUTES: Routes = [ |
|||
{ |
|||
path: '', |
|||
pathMatch: 'full', |
|||
loadComponent: () => import('./home/home.component').then(m => m.HomeComponent), |
|||
}, |
|||
{ |
|||
path: 'account', |
|||
loadChildren: () => import('@abp/ng.account').then(m => m.createRoutes()), |
|||
}, |
|||
... |
|||
]; |
|||
``` |
|||
|
|||
### ⚙ Configuration: `app.config.ts` |
|||
|
|||
```ts |
|||
// app.config.ts |
|||
export const appConfig: ApplicationConfig = { |
|||
providers: [ |
|||
provideRouter(APP_ROUTES), |
|||
APP_ROUTE_PROVIDER, |
|||
provideAbpCore( |
|||
withOptions({ |
|||
environment, |
|||
registerLocaleFn: registerLocale(), |
|||
... |
|||
}) |
|||
), |
|||
provideAbpOAuth(), |
|||
provideAbpThemeShared(), |
|||
... |
|||
], |
|||
}; |
|||
|
|||
``` |
|||
|
|||
### 🧼 Removed: `shared.module.ts` |
|||
|
|||
This file has been removed to reduce unnecessary shared imports. Components now explicitly import what they need—leading to better encapsulation and less coupling. |
|||
|
|||
--- |
|||
|
|||
## Common Problems |
|||
|
|||
You may encounter these common problems that you would need to manage. |
|||
|
|||
### 1. Missing Imports |
|||
|
|||
In standalone structure, components must declare all their dependencies in `imports`. Forgetting this often causes template errors. |
|||
|
|||
### 2. Mixed Structures |
|||
|
|||
Combining modules and standalone in the same feature leads to confusion. Migrate features fully or keep them module-based. |
|||
|
|||
### 3. Routing Errors |
|||
|
|||
Incorrect migration from `forLazy()` to `createRoutes()` or `loadComponent` can break navigation. Double-check route configs. |
|||
|
|||
### 4. Service Injection |
|||
|
|||
Services provided in old modules may be missing. Add them in the component’s `providers` or `app.config.ts`. |
|||
|
|||
### 5. Shared Module Habit |
|||
|
|||
Reintroducing a shared module reduces the benefits of standalone. Import dependencies directly where needed. |
|||
|
|||
--- |
|||
|
|||
## Conclusion |
|||
|
|||
Angular’s standalone component architecture is a significant improvement for scalability, simplicity, and performance. With latest version of ABP Studio, you can adopt this modern approach with ease—without losing support for existing module-based projects. |
|||
|
|||
**Ready to modernize your Angular development?** |
|||
|
|||
Update your ABP Studio today and start building with standalone power! |
|||
@ -0,0 +1,213 @@ |
|||
# App Services vs Domain Services: Deep Dive into Two Core Service Types in ABP Framework |
|||
|
|||
In ABP's layered architecture, we frequently encounter two types of services that appear similar but serve distinctly different purposes: Application Services and Domain Services. Understanding the differences between them is crucial for building clear and maintainable enterprise applications. |
|||
|
|||
## Architectural Positioning |
|||
|
|||
In ABP's layered architecture: |
|||
|
|||
- **Application Services** reside in the application layer and are responsible for coordinating use case execution |
|||
- **Domain Services** reside in the domain layer and are responsible for implementing core business logic |
|||
|
|||
This layered design follows Domain-Driven Design (DDD) principles, ensuring clear separation of business logic and system maintainability. |
|||
|
|||
## Application Services: Use Case Orchestrators |
|||
|
|||
### Core Responsibilities |
|||
|
|||
Application Services are stateless services primarily used to implement application use cases. They act as a bridge between the presentation layer and domain layer, responsible for: |
|||
|
|||
- **Parameter Validation**: Input validation is automatically handled by ABP using data annotations |
|||
- **Authorization**: Checking user permissions and access control using `[Authorize]` attribute or manual authorization checks via `IAuthorizationService` |
|||
- **Transaction Management**: Methods automatically run as Unit of Work (transactional by default) |
|||
- **Use Case Orchestration**: Organizing and coordinating multiple domain objects to complete specific business use cases |
|||
- **Data Transformation**: Handling conversion between DTOs and domain objects using ObjectMapper |
|||
|
|||
### Design Principles |
|||
|
|||
1. **DTO Boundaries**: Application service methods should only accept and return DTOs, never directly expose domain entities |
|||
2. **Use Case Oriented**: Each method should correspond to a clear user use case |
|||
3. **Thin Layer Design**: Avoid implementing complex business logic in application services |
|||
|
|||
### Typical Execution Flow |
|||
|
|||
A standard application service method typically follows this pattern: |
|||
|
|||
```csharp |
|||
[Authorize(BookPermissions.Create)] // Declarative authorization |
|||
public virtual async Task<BookDto> CreateBookAsync(CreateBookDto input) // input is automatically validated |
|||
{ |
|||
// Get related data |
|||
var author = await _authorRepository.GetAsync(input.AuthorId); |
|||
|
|||
// Call domain service to execute business logic (if needed) |
|||
// You can also use the entity constructor directly if no complex business logic is required |
|||
var book = await _bookManager.CreateAsync(input.Title, author, input.Price); |
|||
|
|||
// Persist changes |
|||
await _bookRepository.InsertAsync(book); |
|||
|
|||
// Return DTO |
|||
return ObjectMapper.Map<Book, BookDto>(book); |
|||
} |
|||
``` |
|||
|
|||
### Integration Services: Special kind of Application Service |
|||
|
|||
It's worth mentioning that ABP also provides a special type of application service—Integration Services. They are application services marked with the `[IntegrationService]` attribute, designed for inter-module or inter-microservice communication. |
|||
|
|||
We have a community article dedicated to integration services: [Integration Services Explained — What they are, when to use them, and how they behave](https://abp.io/community/articles/integration-services-explained-what-they-are-when-to-use-lienmsy8) |
|||
|
|||
## Domain Services: Guardians of Business Logic |
|||
|
|||
### Core Responsibilities |
|||
|
|||
Domain Services implement core business logic and are particularly needed when: |
|||
|
|||
- **Core domain logic depends on services**: You need to implement logic that requires repositories or other external services |
|||
- **Logic spans multiple aggregates**: The business logic is related to more than one aggregate/entity and doesn't properly fit in any single aggregate |
|||
- **Complex business rules**: Complex domain rules that don't naturally belong in a single entity |
|||
|
|||
### Design Principles |
|||
|
|||
1. **Domain Object Interaction**: Method parameters and return values should be domain objects (entities, value objects), never DTOs |
|||
2. **Business Logic Focus**: Focus on implementing pure business rules |
|||
3. **Stateless Design**: Maintain the stateless nature of services |
|||
4. **State-Changing Operations Only**: Domain services should only define methods that mutate data, not query methods |
|||
5. **No Authorization Logic**: Domain services should not perform authorization checks or depend on current user context |
|||
6. **Specific Method Names**: Use descriptive, business-meaningful method names (e.g., `AssignToAsync`) instead of generic names (e.g., `UpdateAsync`) |
|||
|
|||
### Implementation Example |
|||
|
|||
```csharp |
|||
public class IssueManager : DomainService |
|||
{ |
|||
private readonly IRepository<Issue, Guid> _issueRepository; |
|||
|
|||
public virtual async Task AssignToAsync(Issue issue, Guid userId) |
|||
{ |
|||
// Business rule: Check user's unfinished task count |
|||
var openIssueCount = await _issueRepository.GetCountAsync(i => i.AssignedUserId == userId && !i.IsClosed); |
|||
|
|||
if (openIssueCount >= 3) |
|||
{ |
|||
throw new BusinessException("IssueTracking:ConcurrentOpenIssueLimit"); |
|||
} |
|||
|
|||
// Execute assignment logic |
|||
issue.AssignedUserId = userId; |
|||
issue.AssignedDate = Clock.Now; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Key Differences Comparison |
|||
|
|||
| Dimension | Application Services | Domain Services | |
|||
|-----------|---------------------|-----------------| |
|||
| **Layer Position** | Application Layer | Domain Layer | |
|||
| **Primary Responsibility** | Use Case Orchestration | Business Logic Implementation | |
|||
| **Data Interaction** | DTOs | Domain Objects | |
|||
| **Callers** | Presentation Layer/Client Applications | Application Services/Other Domain Services | |
|||
| **Authorization** | Responsible for permission checks | No authorization logic | |
|||
| **Transaction Management** | Manages transaction boundaries (Unit of Work) | Participates in transactions but doesn't manage | |
|||
| **Current User Context** | Can access current user information | Should not depend on current user context | |
|||
| **Return Types** | Returns DTOs | Returns domain objects only | |
|||
| **Query Operations** | Can perform query operations | Should not define GET/query methods | |
|||
| **Naming Convention** | `*AppService` | `*Manager` or `*Service` | |
|||
|
|||
## Collaboration Patterns in Practice |
|||
|
|||
In real-world development, these two types of services typically work together: |
|||
|
|||
```csharp |
|||
// Application Service |
|||
public class BookAppService : ApplicationService |
|||
{ |
|||
private readonly BookManager _bookManager; |
|||
private readonly IRepository<Book> _bookRepository; |
|||
|
|||
[Authorize(BookPermissions.Update)] |
|||
public virtual async Task<BookDto> UpdatePriceAsync(Guid id, decimal newPrice) |
|||
{ |
|||
var book = await _bookRepository.GetAsync(id); |
|||
|
|||
await _bookManager.ChangePriceAsync(book, newPrice); |
|||
|
|||
await _bookRepository.UpdateAsync(book); |
|||
|
|||
return ObjectMapper.Map<Book, BookDto>(book); |
|||
} |
|||
} |
|||
|
|||
// Domain Service |
|||
public class BookManager : DomainService |
|||
{ |
|||
public virtual async Task ChangePriceAsync(Book book, decimal newPrice) |
|||
{ |
|||
// Domain service focuses on business rules |
|||
if (newPrice <= 0) |
|||
{ |
|||
throw new BusinessException("Book:InvalidPrice"); |
|||
} |
|||
|
|||
if (book.IsDiscounted && newPrice > book.OriginalPrice) |
|||
{ |
|||
throw new BusinessException("Book:DiscountedPriceCannotExceedOriginal"); |
|||
} |
|||
|
|||
if (book.Price == newPrice) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Additional business logic: Check if price change requires approval |
|||
if (await RequiresApprovalAsync(book, newPrice)) |
|||
{ |
|||
throw new BusinessException("Book:PriceChangeRequiresApproval"); |
|||
} |
|||
|
|||
book.ChangePrice(newPrice); |
|||
} |
|||
|
|||
private Task<bool> RequiresApprovalAsync(Book book, decimal newPrice) |
|||
{ |
|||
// Example business rule: Large price increases require approval |
|||
var increasePercentage = ((newPrice - book.Price) / book.Price) * 100; |
|||
return Task.FromResult(increasePercentage > 50); // 50% increase threshold |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Best Practice Recommendations |
|||
|
|||
### Application Services |
|||
- Create a corresponding application service for each aggregate root |
|||
- Use clear naming conventions (e.g., `IBookAppService`) |
|||
- Implement standard CRUD operation methods (`GetAsync`, `CreateAsync`, `UpdateAsync`, `DeleteAsync`) |
|||
- Avoid inter-application service calls within the same module/application |
|||
- Always return DTOs, never expose domain entities directly |
|||
- Use the `[Authorize]` attribute for declarative authorization or manual checks via `IAuthorizationService` |
|||
- Methods automatically run as Unit of Work (transactional) |
|||
- Input validation is handled automatically by ABP |
|||
|
|||
### Domain Services |
|||
- Use the `Manager` suffix for naming (e.g., `BookManager`) |
|||
- Only define state-changing methods, avoid query methods (use repositories directly in Application Services for queries) |
|||
- Throw `BusinessException` with clear, unique error codes for domain validation failures |
|||
- Keep methods pure, avoid involving user context or authorization logic |
|||
- Accept and return domain objects only, never DTOs |
|||
- Use descriptive, business-meaningful method names (e.g., `AssignToAsync`, `ChangePriceAsync`) |
|||
- Do not implement interfaces unless there's a specific need for multiple implementations |
|||
|
|||
## Summary |
|||
|
|||
Application Services and Domain Services each have their distinct roles in the ABP framework: Application Services serve as use case orchestrators, handling authorization, validation, transaction management, and DTO transformations; Domain Services focus purely on business logic implementation without any infrastructure concerns. Integration Services are a special type of Application Service designed for inter-service communication. |
|||
|
|||
Correctly understanding and applying these service patterns is key to building high-quality ABP applications. Through clear separation of responsibilities, we can not only build more maintainable code but also flexibly switch between monolithic and microservice architectures—this is precisely the elegance of ABP framework design. |
|||
|
|||
## References |
|||
|
|||
- [Application Services](https://abp.io/docs/latest/framework/architecture/domain-driven-design/application-services) |
|||
- [Integration Services](https://abp.io/docs/latest/framework/api-development/integration-services) |
|||
- [Domain Services](https://abp.io/docs/latest/framework/architecture/domain-driven-design/domain-services) |
|||
|
After Width: | Height: | Size: 638 KiB |
@ -0,0 +1,338 @@ |
|||
# Best Free Alternatives to AutoMapper in .NET — Why We Moved to Mapperly |
|||
|
|||
--- |
|||
|
|||
## Introduction |
|||
|
|||
[AutoMapper](https://automapper.io/) has been one of the most popular mapping library for .NET apps. It has been free and [open-source](https://github.com/LuckyPennySoftware/AutoMapper) since 2009. On 16 April 2025, Jimmy Bogard (the owner of the project) decided to make it commercial for his own reasons. You can read [this announcement](https://www.jimmybogard.com/automapper-and-mediatr-licensing-update/) about what happened to AutoMapper. |
|||
|
|||
|
|||
|
|||
### Why AutoMapper’s licensing change matters |
|||
|
|||
In ABP Framework we have been also using AutoMapper for object mappings. After its commercial transition, we also needed to replace it. Because ABP Framework is open-source and under [LGPL-3.0 license](https://github.com/abpframework/abp#LGPL-3.0-1-ov-file). |
|||
|
|||
**TL;DR** |
|||
|
|||
> That's why, **we decided to replace AutoMapper with Mapperly**. |
|||
|
|||
In this article, we'll discuss the alternatives of AutoMapper so that you can cut down on costs and maximize performance while retaining control over your codebase. Also I'll explain why we chose Mapperly. |
|||
|
|||
Also AutoMapper uses heavily reflection. And reflection comes with a performance cost if used indiscriminately, and compile-time safety is limited. Let's see how we can overcome these... |
|||
|
|||
|
|||
|
|||
## Cost-Free Alternatives to AutoMapper |
|||
|
|||
Check out the comparison table for key features vs. AutoMapper. |
|||
|
|||
| | **AutoMapper (Paid)** | **Mapster (Free)** | **Mapperly (Free)** | **AgileMapper (Free)** | **Manual Mapping** | |
|||
| ------------------- | ----------------------------------------------- | ----------------------------------------- | -------------------------------------------- | ------------------------------------------- | ------------------------------------------------ | |
|||
| **License & Cost** | Paid/commercial | Free, MIT License | Free, MIT License | Free, Apache 2.0 | Free (no library) | |
|||
| **Performance** | Slower due to reflection & conventions | Very fast (runtime & compile-time modes) | Very fast (compile-time code generation) | Good, faster than AutoMapper | Fastest (direct assignment) | |
|||
| **Ease of Setup** | Easy, but configuration-heavy | Easy, minimal config | Easy, but different approach from AutoMapper | Simple, flexible configuration | Manual coding required | |
|||
| **Features** | Rich features, conventions, nested mappings | Strong typed mappings, projection support | Strong typed, compile-time safe mappings | Dynamic & conditional mapping | Whatever you code | |
|||
| **Maintainability** | Hidden mappings can be hard to debug | Explicit & predictable | Very explicit, compiler-verified mappings | Readable, good balance | Very explicit, most maintainable | |
|||
| **Best For** | Large teams used to AutoMapper & willing to pay | Teams wanting performance + free tool | Teams prioritizing type-safety & performance | Developers needing flexibility & simplicity | Small/medium projects, performance-critical apps | |
|||
|
|||
There are other libraries such as [**ExpressMapper**](https://github.com/fluentsprings/ExpressMapper) **(308 GitHub stars)**, [**ValueInjecter**](https://github.com/omuleanu/ValueInjecter) **(258 GitHub stars)**, [**AgileMapper**](https://github.com/agileobjects/AgileMapper) **(463 GitHub stars)**. These are not very popular but also free and offer a different balance of simplicity and features. |
|||
|
|||
|
|||
|
|||
## Why We Chose Mapperly |
|||
|
|||
We filtered down all the alternatives into 2: **Mapster** and **Mapperly**. |
|||
|
|||
The crucial factor was maintainability! As you see from the screenshots below, Mapster is already stopped development. Mapster’s development appears stalled, and its future maintenance is uncertain. On the other hand, Mapperly regularly gets commits. The community support is valuable. |
|||
|
|||
We looked up different alternatives of AutoMapper also, here's the initial issue of AutoMapper replacement [github.com/abpframework/abp/issues/23243](https://github.com/abpframework/abp/issues/23243). |
|||
|
|||
The ABP team started Mapperly integration with this initial commit [github.com/abpframework/abp/commit/178d3f56d42b4e5acb7e349470f4a644d4c5214e](https://github.com/abpframework/abp/commit/178d3f56d42b4e5acb7e349470f4a644d4c5214e). And this is our Mapperly integration package : [github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.Mapperly.](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.Mapperly.) |
|||
|
|||
 |
|||
|
|||
Here are some considerations for developers who are used to ABP and AutoMapper. |
|||
|
|||
### [Mapster](https://github.com/MapsterMapper/Mapster): |
|||
|
|||
* ✔ It is similar to AutoMapper, configuring mappings through code. |
|||
* ✔ Support for dependency injection and complex runtime configuration. |
|||
* ❌ It is looking additional Mapster maintainers ([Call for additional Mapster maintainers MapsterMapper/Mapster#752](https://github.com/MapsterMapper/Mapster/discussions/752)) |
|||
|
|||
### [Mapperly](https://github.com/riok/Mapperly): |
|||
|
|||
- ✔ It generates mapping code(` source generator`) during the build process. |
|||
- ✔ It is actively being developed and maintained. |
|||
- ❌ It is a static `map` method, which is not friendly to dependency injection. |
|||
- ❌ The configuration method is completely different from AutoMapper, and there is a learning curve. |
|||
|
|||
|
|||
|
|||
**Mapperly** → generates mapping code at **compile time** using source generators. |
|||
|
|||
**Mapster** → has two modes: |
|||
|
|||
- By default, it uses **runtime code generation** (via expression trees and compilation). |
|||
|
|||
- But with **Mapster.Tool** (source generator), it can also generate mappings at **compile time**. |
|||
|
|||
|
|||
|
|||
This is important because it guarantees the mappings are working well. Also they provide type safety and improved performance. Another advantages of these libraries, they eliminate runtime surprises and offer better IDE support. |
|||
|
|||
--- |
|||
|
|||
## When Mapperly Will Come To ABP |
|||
|
|||
Mapperly integration will be delivered with ABP v10. If you have already defined AutoMapper configurations, you can still keep and use them. But the framework will use Mapperly. So there'll be 2 mapping integrations in your app. You can also remove AutoMapper from your final application and use one mapping library: Mapperly. It's up to you! Check [AutoMapper pricing table](https://automapper.io/#pricing). |
|||
|
|||
|
|||
|
|||
## Migrating from AutoMapper to Mapperly |
|||
|
|||
In ABP v10, we will be migrating from AutoMapper to Mapperly. The document about the migration is not delivered by the time I wrote this article, but you can reach the document in our dev docs branch |
|||
|
|||
* [github.com/abpframework/abp/blob/dev/docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md](https://github.com/abpframework/abp/blob/dev/docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md). |
|||
|
|||
Also for ABP, you can check out how you will define DTO mappings based on Mapperly at this document |
|||
|
|||
* [github.com/abpframework/abp/blob/dev/docs/en/framework/infrastructure/object-to-object-mapping.md](https://github.com/abpframework/abp/blob/dev/docs/en/framework/infrastructure/object-to-object-mapping.md) |
|||
|
|||
|
|||
|
|||
## Mapping Code Examples for AutoMapper, Mapster, AgileMapper |
|||
|
|||
### AutoMapper vs Mapster vs Mapperly Performance |
|||
|
|||
Here are concise, drop-in **side-by-side C# snippets** that map the same model with AutoMapper, Mapster, AgileMapper, and manual mapping. |
|||
|
|||
Models used in all examples |
|||
|
|||
We'll use these models to show the mapping examples for AutoMapper, Mapster, AgileMapper. |
|||
|
|||
```csharp |
|||
public class Order |
|||
{ |
|||
public int Id { get; set; } |
|||
public Customer Customer { get; set; } = default!; |
|||
public List<OrderLine> Lines { get; set; } = new(); |
|||
public DateTime CreatedAt { get; set; } |
|||
} |
|||
|
|||
public class Customer |
|||
{ |
|||
public int Id { get; set; } |
|||
public string Name { get; set; } = ""; |
|||
public string? Email { get; set; } |
|||
} |
|||
|
|||
public class OrderLine |
|||
{ |
|||
public int ProductId { get; set; } |
|||
public int Quantity { get; set; } |
|||
public decimal UnitPrice { get; set; } |
|||
} |
|||
|
|||
public class OrderDto |
|||
{ |
|||
public int Id { get; set; } |
|||
public string CustomerName { get; set; } = ""; |
|||
public int ItemCount { get; set; } |
|||
public decimal Total { get; set; } |
|||
public string CreatedAtIso { get; set; } = ""; |
|||
} |
|||
``` |
|||
|
|||
|
|||
|
|||
#### AutoMapper Example (Paid) |
|||
|
|||
```csharp |
|||
public sealed class OrderProfile : Profile |
|||
{ |
|||
public OrderProfile() |
|||
{ |
|||
CreateMap<Order, OrderDto>() |
|||
.ForMember(d => d.CustomerName, m => m.MapFrom(s => s.Customer.Name)) |
|||
.ForMember(d => d.ItemCount, m => m.MapFrom(s => s.Lines.Sum(l => l.Quantity))) |
|||
.ForMember(d => d.Total, m => m.MapFrom(s => s.Lines.Sum(l => l.Quantity * l.UnitPrice))) |
|||
.ForMember(d => d.CreatedAtIso,m => m.MapFrom(s => s.CreatedAt.ToString("O"))); |
|||
} |
|||
} |
|||
|
|||
// registration |
|||
services.AddAutoMapper(typeof(OrderProfile)); |
|||
|
|||
// mapping |
|||
var dto = mapper.Map<OrderDto>(order); |
|||
|
|||
// EF Core projection (common pattern) |
|||
var list = dbContext.Orders |
|||
.ProjectTo<OrderDto>(mapper.ConfigurationProvider) |
|||
.ToList(); |
|||
``` |
|||
|
|||
**NuGet Packages:** |
|||
|
|||
- https://www.nuget.org/packages/AutoMapper |
|||
- https://www.nuget.org/packages/AutoMapper.Extensions.Microsoft.DependencyInjection |
|||
|
|||
--- |
|||
|
|||
#### Mapperly (Free, Apache-2.0) |
|||
|
|||
This is compile-time generated mapping. |
|||
|
|||
```csharp |
|||
[Mapper] // generates the implementation at build time |
|||
public partial class OrderMapper |
|||
{ |
|||
// Simple property mapping: Customer.Name -> CustomerName |
|||
[MapProperty(nameof(Order.Customer) + "." + nameof(Customer.Name), nameof(OrderDto.CustomerName))] |
|||
public partial OrderDto ToDto(Order source); |
|||
|
|||
// Update an existing target (like MapToExisting) |
|||
[MapProperty(nameof(Order.Customer) + "." + nameof(Customer.Name), nameof(OrderDto.CustomerName))] |
|||
public partial void UpdateDto(Order source, OrderDto target); |
|||
|
|||
public OrderDto Map(Order s) |
|||
{ |
|||
var d = ToDto(s); |
|||
AfterMap(s, d); |
|||
return d; |
|||
} |
|||
|
|||
public void Map(Order source, OrderDto d) |
|||
{ |
|||
UpdateDto(source, d); |
|||
AfterMap(source, d); |
|||
} |
|||
|
|||
private void AfterMap(Order source, OrderDto d) |
|||
{ |
|||
d.ItemCount = source.Lines.Sum(l => l.Quantity); |
|||
d.Total = source.Lines.Sum(l => l.Quantity * l.UnitPrice); |
|||
d.CreatedAtIso = source.CreatedAt.ToString("O"); |
|||
} |
|||
} |
|||
|
|||
|
|||
//USAGE |
|||
var mapper = new OrderMapper(); |
|||
var order = new Order |
|||
{ |
|||
Id = 1, |
|||
Customer = new Customer { Id = 1, Name = "John Doe", Email = "johndoe@abp.io" }, |
|||
Lines = |
|||
[ |
|||
new OrderLine {ProductId = 1, Quantity = 2, UnitPrice = 10.0m}, |
|||
new OrderLine {ProductId = 2, Quantity = 1, UnitPrice = 20.0m} |
|||
] |
|||
}; |
|||
|
|||
// Map to a new object |
|||
var dto = mapper.Map(order); |
|||
|
|||
// Map to an existing object |
|||
var target = new OrderDto(); |
|||
mapper.Map(order, target); |
|||
``` |
|||
|
|||
**NuGet Packages:** |
|||
|
|||
* https://www.nuget.org/packages/Riok.Mapperly/ |
|||
|
|||
--- |
|||
|
|||
#### Mapster Example (Free, MIT) |
|||
|
|||
```csharp |
|||
TypeAdapterConfig<Order, OrderDto>.NewConfig() |
|||
.Map(d => d.CustomerName, s => s.Customer.Name) |
|||
.Map(d => d.ItemCount, s => s.Lines.Sum(l => l.Quantity)) |
|||
.Map(d => d.Total, s => s.Lines.Sum(l => l.Quantity * l.UnitPrice)) |
|||
.Map(d => d.CreatedAtIso, s => s.CreatedAt.ToString("O")); |
|||
|
|||
// one-off |
|||
var dto = order.Adapt<OrderDto>(); |
|||
|
|||
// DI-friendly registration |
|||
services.AddSingleton(TypeAdapterConfig.GlobalSettings); |
|||
services.AddScoped<IMapper, ServiceMapper>(); |
|||
|
|||
// EF Core projection (strong suit) |
|||
var mappedList = dbContext.Orders |
|||
.ProjectToType<OrderDto>() // Mapster projection |
|||
.ToList(); |
|||
``` |
|||
|
|||
**NuGet Packages:** |
|||
|
|||
- https://www.nuget.org/packages/Mapster |
|||
- https://www.nuget.org/packages/Mapster.DependencyInjection |
|||
- https://www.nuget.org/packages/Mapster.SourceGenerator (for performance improvement) |
|||
|
|||
--- |
|||
|
|||
#### AgileMapper Example (Free, Apache-2.0) |
|||
|
|||
```csharp |
|||
var mapper = Mapper.CreateNew(cfg => |
|||
{ |
|||
cfg.WhenMapping |
|||
.From<Order>() |
|||
.To<OrderDto>() |
|||
.Map(ctx => ctx.Source.Customer.Name).To(dto => dto.CustomerName) |
|||
.Map(ctx => ctx.Source.Lines.Sum(l => l.Quantity)).To(dto => dto.ItemCount) |
|||
.Map(ctx => ctx.Source.Lines.Sum(l => l.Quantity * l.UnitPrice)).To(dto => dto.Total) |
|||
.Map(ctx => ctx.Source.CreatedAt.ToString("O")).To(dto => dto.CreatedAtIso); |
|||
}); |
|||
|
|||
var mappedDto = mapper.Map(order).ToANew<OrderDto>(); |
|||
``` |
|||
|
|||
**NuGet Packages:** |
|||
|
|||
* https://www.nuget.org/packages/AgileObjects.AgileMapper |
|||
|
|||
|
|||
--- |
|||
|
|||
#### Manual (Pure) Mapping (no library) |
|||
|
|||
Straightforward, fastest, and most explicit. Good for simple applications which doesn't need long term maintenance. Hand-written mapping is faster, safer, and more maintainable. And for tiny mappings, you can still use manual mapping. |
|||
|
|||
* Examples of when manual mapping is better than libraries. |
|||
|
|||
```csharp |
|||
public static class OrderMapping |
|||
{ |
|||
public static OrderDto ToDto(this Order s) => new() |
|||
{ |
|||
Id = s.Id, |
|||
CustomerName = s.Customer.Name, |
|||
ItemCount = s.Lines.Sum(l => l.Quantity), |
|||
Total = s.Lines.Sum(l => l.Quantity * l.UnitPrice), |
|||
CreatedAtIso = s.CreatedAt.ToString("O") |
|||
}; |
|||
} |
|||
|
|||
// usage |
|||
var dto = order.ToDto(); |
|||
|
|||
// EF Core projection (best for perf + SQL translation) |
|||
var mappedList = dbContext.Orders.Select(s => new OrderDto |
|||
{ |
|||
Id = s.Id, |
|||
CustomerName = s.Customer.Name, |
|||
ItemCount = s.Lines.Sum(l => l.Quantity), |
|||
Total = s.Lines.Sum(l => l.Quantity * l.UnitPrice), |
|||
CreatedAtIso = s.CreatedAt.ToString("O") |
|||
}).ToList(); |
|||
``` |
|||
|
|||
|
|||
|
|||
### Conclusion |
|||
|
|||
If you rely on AutoMapper today, it’s time to evaluate alternatives. For ABP Framework, we chose **Mapperly** due to active development, strong community, and compile-time performance. But your team may prefer **Mapster** for flexibility or even manual mapping for small apps. Your requirements might be different, your project is not a framework so you decide the best one for you. |
|||
|
After Width: | Height: | Size: 477 KiB |
|
After Width: | Height: | Size: 163 KiB |
@ -0,0 +1,174 @@ |
|||
# Building a Permission-Based Authorization System for ASP.NET Core |
|||
|
|||
In this article, we'll explore different authorization approaches in ASP.NET Core and examine how ABP's permission-based authorization system works. |
|||
|
|||
First, we'll look at some of the core authorization types that come with ASP.NET Core, such as role-based, claims-based, policy-based, and resource-based authorization. We'll briefly review the pros and cons of each approach. |
|||
|
|||
Then, we'll dive into [ABP's Permission-Based Authorization System](https://abp.io/docs/latest/framework/fundamentals/authorization#permission-system). This is a more advanced approach that gives you fine-grained control over what users can do in your application. We'll also explore ABP's Permission Management Module, which makes managing permissions through the UI easily. |
|||
|
|||
## Understanding ASP.NET Core Authorization Types |
|||
|
|||
Before diving into permission-based authorization, let's examine some of the core authorization types available in ASP.NET Core: |
|||
|
|||
- **[Role-Based Authorization](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-9.0)** checks if the current user belongs to specific roles (like **"Admin"** or **"User"**) and grants access based on these roles. (For example, only users in the **"Manager"** role can access the employee salary management page.) |
|||
|
|||
- **[Claims-Based Authorization](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/claims?view=aspnetcore-9.0)** uses key-value pairs (claims) that describe user attributes, such as age, department, or security clearance. (For example, only users with a **"Department=Finance"** claim can view financial reports.) This provides more granular control but requires careful claim management (such as grouping claims under policies). |
|||
|
|||
- **[Policy-Based Authorization](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-9.0)** combines multiple requirements (roles, claims, custom logic) into reusable policies. It offers flexibility and centralized management, and **this is exactly why ABP's permission system is built on top of it!** (We'll discuss this in more detail later.) |
|||
|
|||
- **[Resource-Based Authorization](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased?view=aspnetcore-9.0)** determines access by examining both the user and the specific item they want to access. (For example, a user can edit only their own blog posts, not others' posts.) Unlike policy-based authorization which applies the same rules everywhere, resource-based authorization makes decisions based on the actual data being accessed, requiring more complex implementation. |
|||
|
|||
Here's a quick comparison of these approaches: |
|||
|
|||
| Authorization Type | Pros | Cons | |
|||
|-------------------|------|------| |
|||
| **Role-Based** | Simple implementation, easy to understand | Becomes inflexible with complex role hierarchies | |
|||
| **Claims-Based** | Granular control, flexible user attributes | Complex claim management, potential for claim explosion | |
|||
| **Policy-Based** | Centralized logic, combines multiple requirements | Can become complex with numerous policies | |
|||
| **Resource-Based** | Fine-grained per-resource control | Implementation complexity, resource-specific code | |
|||
|
|||
## What is Permission-Based Authorization? |
|||
|
|||
Permission-based authorization takes a different approach from other authorization types by defining specific permissions (like **"CreateUser"**, **"DeleteOrder"**, **"ViewReports"**) that represent granular actions within your application. These permissions can be assigned to users directly or through roles, providing both flexibility and clear action-based access control. |
|||
|
|||
ABP Framework's permission system is built on top of this approach and extends ASP.NET Core's policy-based authorization system, working seamlessly with it. |
|||
|
|||
## ABP Framework's Permission System |
|||
|
|||
ABP extends [ASP.NET Core Authorization](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/introduction?view=aspnetcore-9.0) by adding **permissions** as automatic [policies](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-9.0) and allows the authorization system to be used in application services as well. |
|||
|
|||
This system provides a clean abstraction while maintaining full compatibility with ASP.NET Core's authorization infrastructure. |
|||
|
|||
ABP also provides a [Permission Management Module](https://abp.io/docs/latest/modules/permission-management) that offers a complete UI and API for managing permissions. This allows you to easily manage permissions in the UI, assign permissions to roles or users, and much more. (We'll see how to use it in the following sections.) |
|||
|
|||
### Defining Permissions in ABP |
|||
|
|||
In ABP, permissions are defined in classes (typically under the `*.Application.Contracts` project) that inherit from the `PermissionDefinitionProvider` class. Here's how you can define permissions for a book management system: |
|||
|
|||
```csharp |
|||
public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider |
|||
{ |
|||
public override void Define(IPermissionDefinitionContext context) |
|||
{ |
|||
var bookStoreGroup = context.AddGroup("BookStore"); |
|||
|
|||
var booksPermission = bookStoreGroup.AddPermission("BookStore.Books", L("Permission:Books")); |
|||
booksPermission.AddChild("BookStore.Books.Create", L("Permission:Books.Create")); |
|||
booksPermission.AddChild("BookStore.Books.Edit", L("Permission:Books.Edit")); |
|||
booksPermission.AddChild("BookStore.Books.Delete", L("Permission:Books.Delete")); |
|||
} |
|||
|
|||
private static LocalizableString L(string name) |
|||
{ |
|||
return LocalizableString.Create<BookStoreResource>(name); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
ABP automatically discovers this class and registers the permissions/policies in the system. You can then assign these permissions/policies to users/roles. There are two ways to do this: |
|||
|
|||
* Using the [Permission Management Module](https://abp.io/docs/latest/modules/permission-management) |
|||
* Using the `IPermissionManager` service (via code) |
|||
|
|||
#### Setting Permissions to Roles and Users via Permission Management Module |
|||
|
|||
When you define a permission, it also becomes usable in the ASP.NET Core authorization system as a **policy name**. If you are using the [Permission Management Module](https://abp.io/docs/latest/modules/permission-management), you can manage the permissions through the UI: |
|||
|
|||
 |
|||
|
|||
In the permission management UI, you can grant permissions to roles and users through the **Role Management** and **User Management** pages within the "permissions" modals. You can then easily check these permissions in your code. In the screenshot above, you can see the permission modal for the user's page, clearly showing the permissions granted to the user by their role. (**(R)** in the UI indicates that the permission is granted by one of the current user's roles.) |
|||
|
|||
#### Setting Permissions to Roles and Users via Code |
|||
|
|||
You can also set permissions for roles and users programmatically. You just need to inject the `IPermissionManager` service and use its `SetForRoleAsync` and `SetForUserAsync` methods (or similar methods): |
|||
|
|||
```csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IPermissionManager _permissionManager; |
|||
|
|||
public MyService(IPermissionManager permissionManager) |
|||
{ |
|||
_permissionManager = permissionManager; |
|||
} |
|||
|
|||
public async Task GrantPermissionForUserAsync(Guid userId, string permissionName) |
|||
{ |
|||
await _permissionManager.SetForUserAsync(userId, permissionName, true); |
|||
} |
|||
|
|||
public async Task ProhibitPermissionForUserAsync(Guid userId, string permissionName) |
|||
{ |
|||
await _permissionManager.SetForUserAsync(userId, permissionName, false); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### Checking Permissions in AppServices and Controllers |
|||
|
|||
ABP provides multiple ways to check permissions. The most common approach is using the `[Authorize]` attribute and passing the permission/policy name. |
|||
|
|||
Here is an example of how to check permissions in an application service: |
|||
|
|||
```csharp |
|||
[Authorize("BookStore.Books")] |
|||
public class BookAppService : ApplicationService, IBookAppService |
|||
{ |
|||
[Authorize("BookStore.Books.Create")] |
|||
public async Task<BookDto> CreateAsync(CreateBookDto input) |
|||
{ |
|||
//logic here |
|||
} |
|||
} |
|||
``` |
|||
|
|||
> Notice that you can use the `[Authorize]` attribute at both class and method levels. In the example above, the `CreateAsync` method is marked with the `[Authorize]` attribute, so it will check the user's permission before executing the method. Since the application service class also has a permission requirement, both permissions must be granted to the user to execute the method! |
|||
|
|||
And here is an example of how to check permissions in a controller: |
|||
|
|||
```csharp |
|||
[Authorize("BookStore.Books")] |
|||
public class CreateBookController : AbpController |
|||
{ |
|||
//omitted for brevity... |
|||
} |
|||
``` |
|||
|
|||
### Programmatic Permission Checking |
|||
|
|||
To conditionally control authorization in your code, you can use the `IAuthorizationService` service: |
|||
|
|||
```csharp |
|||
public class BookAppService : ApplicationService, IBookAppService |
|||
{ |
|||
public async Task<BookDto> CreateAsync(CreateBookDto input) |
|||
{ |
|||
// Checks the permission and throws an exception if the user does not have the permission |
|||
await AuthorizationService.CheckAsync(BookStorePermissions.Books.Create); |
|||
|
|||
// Your logic here |
|||
} |
|||
|
|||
public async Task<bool> CanUserCreateBooksAsync() |
|||
{ |
|||
// Checks if the permission is granted for the current user |
|||
return await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
You can use the `IAuthorizationService`'s helpful methods for authorization checking, as shown in the example above: |
|||
|
|||
- `IsGrantedAsync` checks if the current user has the given permission. |
|||
- `CheckAsync` throws an exception if the current user does not have the given permission. |
|||
- `AuthorizeAsync` checks if the current user has the given permission and returns an `AuthorizationResult`, which has a `Succeeded` property that you can use to verify if the user has the permission. |
|||
|
|||
Also notice that we did not inject the `IAuthorizationService` in the constructor, because we are using the `ApplicationService` base class, which already provides property injection for it. This means we can directly use it in our application services, just like other helpful base services (such as `ICurrentUser` and `ICurrentTenant`). |
|||
|
|||
## Conclusion |
|||
|
|||
Permission-based authorization in ABP Framework provides a powerful and flexible approach to securing your applications. By building on ASP.NET Core's policy-based authorization, ABP offers a clean abstraction that simplifies permission management while maintaining the full power of the underlying system. |
|||
|
|||
The ability to check permissions in both application services and controllers makes ABP Framework's authorization system very flexible and powerful, yet easy to use. |
|||
|
|||
Additionally, the Permission Management Module makes it very easy to manage permissions and roles through the UI. You can learn more about how it works in the [documentation](https://abp.io/docs/latest/modules/permission-management). |
|||
|
After Width: | Height: | Size: 324 KiB |
|
After Width: | Height: | Size: 25 KiB |
@ -0,0 +1,169 @@ |
|||
# Best Practices for Designing Backward‑Compatible REST APIs in a Microservice Solution for .NET Developers |
|||
|
|||
## Introduction |
|||
With microservice architecture, each service develops and ships independently at its own pace, and clients infrequently update in lockstep. **Backward compatibility** means that when you release new versions, current consumers continue to function without changing code. This article provides a practical, 6–7 minute tutorial specific to **.NET developers**. |
|||
|
|||
--- |
|||
## What Counts as “Breaking”? (and what doesn’t) |
|||
A change is **breaking** if a client that previously conformed can **fail at compile time or runtime**, or exhibit **different business‑critical behavior**, **without** changing that client in any way. In other words: if an old client needs to be altered in order to continue functioning as it did, your change is breaking. |
|||
|
|||
### Examples of breaking changes |
|||
- **Deleting or renaming an endpoint** or modifying its URL/route. |
|||
- **Making an existing field required** (e.g., requiring `address`). |
|||
- **Data type or format changes** (e.g., `price: string` → `price: number`, or date format changes). |
|||
- **Altering default behavior or ordering** that clients implicitly depend on (hidden contracts). |
|||
- **Changing the error model** or HTTP status codes in a manner that breaks pre-existing error handling. |
|||
- **Renaming fields** or **making optional fields required** in requests or responses. |
|||
- **Reinterpreting semantics** (e.g., `status="closed"` formerly included archived items, but no longer does). |
|||
|
|||
### Examples of non‑breaking changes |
|||
- **Optional fields or query parameters can be added** (clients may disregard them). |
|||
- **Adding new enum values** (if the clients default to a safe behavior for unrecognized values). |
|||
- **Adding a new endpoint** while leaving the previous one unchanged. |
|||
- **Performance enhancements** that leave input/output unchanged. |
|||
- **Including metadata** (e.g., pagination links) without changing the current payload shape. |
|||
|
|||
> Golden rule: **Old clients should continue to work exactly as they did before—without any changes.** |
|||
|
|||
--- |
|||
## Versioning Strategy |
|||
Versioning is your master control lever for managing change. Typical methods: |
|||
|
|||
1) **URI Segment** (simplest) |
|||
``` |
|||
GET /api/v1/orders |
|||
GET /api/v2/orders |
|||
``` |
|||
Pros: Cache/gateway‑friendly; explicit in docs. Cons: URL noise. |
|||
|
|||
2) **Header‑Based** |
|||
``` |
|||
GET /api/orders |
|||
x-api-version: 2.0 |
|||
``` |
|||
Pros: Clean URLs; multiple reader support. Cons: Needs proxy/CDN rules. |
|||
|
|||
3) **Media Type** |
|||
Accept: application/json;v=2 |
|||
|
|||
Pros: Semantically accurate. <br> Cons: More complicated to test and implement. <br> **Recommendation:** For the majority of teams, favor **URI segments**, with an optional **`x-api-version`** header for flexibility. |
|||
|
|||
### Quick Setup in ASP.NET Core (Asp.Versioning) |
|||
```csharp |
|||
// Program.cs |
|||
using Asp.Versioning; |
|||
|
|||
builder.Services.AddControllers(); |
|||
builder.Services.AddApiVersioning(o => |
|||
{ |
|||
o.DefaultApiVersion = new ApiVersion(1, 0); |
|||
o.AssumeDefaultVersionWhenUnspecified = true; |
|||
o.ReportApiVersions = true; // response header: api-supported-versions |
|||
o.ApiVersionReader = ApiVersionReader.Combine( |
|||
new UrlSegmentApiVersionReader(), |
|||
new HeaderApiVersionReader("x-api-version") |
|||
); |
|||
}); |
|||
|
|||
builder.Services.AddVersionedApiExplorer(o => |
|||
{ |
|||
o.GroupNameFormat = "'v'VVV"; // v1, v2 |
|||
o.SubstituteApiVersionInUrl = true; |
|||
}); |
|||
``` |
|||
```csharp |
|||
// Controller |
|||
using Asp.Versioning; |
|||
|
|||
[ApiController] |
|||
[Route("api/v{version:apiVersion}/orders")] |
|||
public class OrdersController : ControllerBase |
|||
{ |
|||
[HttpGet] |
|||
[ApiVersion("1.0", Deprecated = true)] |
|||
public IActionResult GetV1() => Ok(new { message = "v1" }); |
|||
|
|||
[HttpGet] |
|||
[MapToApiVersion("2.0")] |
|||
public IActionResult GetV2() => Ok(new { message = "v2", includes = new []{"items"} }); |
|||
} |
|||
``` |
|||
|
|||
--- |
|||
## Schema Evolution Playbook (JSON & DTO) |
|||
Obey the following rules for compatibility‑safe evolution: |
|||
|
|||
- **Add‑only changes**: Favor adding **optional** fields; do not remove/rename fields. |
|||
- **Maintain defaults**: When the new field is disregarded, the old functionality must not change. |
|||
- **Enum extension**: Clients should handle unknown enum values gracefully (default behavior). |
|||
- **Deprecation pipeline**: Mark fields/endpoints as deprecated **at least one version** prior to removal and publicize extensively. - **Stability by contract**: Record any unspoken contracts (ordering, casing, formats) that clients depend on. |
|||
|
|||
### Example: adding a non‑breaking field |
|||
```csharp |
|||
public record OrderDto( |
|||
Guid Id, |
|||
decimal Total, |
|||
string Currency, |
|||
string? SalesChannel // new, optional |
|||
); |
|||
``` |
|||
|
|||
--- |
|||
## Compatibility‑Safe API Behaviors |
|||
- **Error model**: Use a standard structure (e.g., RFC 7807 `ProblemDetails`). Avoid ad‑hoc error shapes on a per-endpoint basis. |
|||
- **Versioning/Deprecation communication** through headers: |
|||
- `api-supported-versions: 1.0, 2.0` |
|||
- `Deprecation: true` (in deprecated endpoints) |
|||
- `Sunset: Wed, 01 Oct 2025 00:00:00 GMT` (planned deprecation date) |
|||
- **Idempotency**: Use an `Idempotency-Key` header for retry-safe POSTs. |
|||
- **Optimistic concurrency**: Utilize `ETag`/`If-Match` to prevent lost updates. |
|||
- **Pagination**: Prefer cursor tokens (`nextPageToken`) to protect clients from sorting/index changes. |
|||
- **Time**: Employ ISO‑8601 in UTC; record time‑zone semantics and rounding conventions. |
|||
|
|||
--- |
|||
## Rollout & Deprecation Policy |
|||
A good deprecation policy is **announce → coexist → remove**: |
|||
|
|||
1) **Announce**: Release changelog, docs, and comms (mail/Slack) with v2 information and the sunset date. |
|||
2) **Coexist**: Operate v1 and v2 side by side. Employ gateway percentage routing for progressive cutover. |
|||
3) **Observability**: Monitor errors/latency/usage **by version**. When v1 traffic falls below ~5%, plan for removal. 4) **Remove**: Post sunset date, return **410 (Gone)** with a link to migration documentation. |
|||
|
|||
**Canary & Blue‑Green**: Initialize v2 with a small traffic portion and compare error/latency budgets prior to scaling up. |
|||
|
|||
--- |
|||
## Contract & Compatibility Testing |
|||
- **Consumer‑Driven Contracts**: Write expectations using Pact.NET; verify at provider CI. |
|||
- **Golden files / snapshots**: Freeze representative JSON payloads and automatically detect regressions. |
|||
- **Version-specific smoke tests**: Maintain separate, minimal test suites for v1 and v2. |
|||
- **SemVer discipline**: Minor = backward‑compatible; Major = breaking (avoid when possible). |
|||
|
|||
Minimal example (xUnit + snapshot style): |
|||
```csharp |
|||
[Fact] |
|||
public async Task Orders_v1_contract_should_match_snapshot() |
|||
{ |
|||
var resp = await _client.GetStringAsync("/api/v1/orders"); |
|||
Approvals.VerifyJson(resp); // snapshot comparison |
|||
} |
|||
``` |
|||
|
|||
--- |
|||
## Tooling & Docs (for .NET) |
|||
- **Asp.Versioning (NuGet)**: API versioning + ApiExplorer integration. |
|||
- **Swashbuckle / NSwag**: Generate an OpenAPI definition **for every version** (`/swagger/v1/swagger.json`, `/swagger/v2/swagger.json`). Display both in Swagger UI. |
|||
- **Polly**: Client‑side retries/fallbacks to handle transient failures and ensure resilience. |
|||
- **Serilog + OpenTelemetry**: Collect metrics/logs/traces by version for observability and SLOs. |
|||
|
|||
Swagger UI configuration by group name: |
|||
```csharp |
|||
app.UseSwagger(); |
|||
app.UseSwaggerUI(c => |
|||
{ |
|||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1"); |
|||
c.SwaggerEndpoint("/swagger/v2/swagger.json", "API v2"); |
|||
}); |
|||
``` |
|||
--- |
|||
|
|||
## Conclusion |
|||
Backward compatibility is not a version number—it is **disciplined change management**. When you use add‑only schema evolution, a well‑defined versioning strategy, strict contract testing, and rolling rollout, you maintain microservice independence and safeguard consumer experience. |
|||
|
After Width: | Height: | Size: 755 KiB |