@ -0,0 +1,203 @@ |
|||||
|
# ABP Platform 9.3 RC Has Been Released |
||||
|
|
||||
|
We are happy to release [ABP](https://abp.io) version **9.3 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version. |
||||
|
|
||||
|
Try this version and provide feedback for a more stable version of ABP v9.3! Thanks to you in advance. |
||||
|
|
||||
|
## Get Started with the 9.3 RC |
||||
|
|
||||
|
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). |
||||
|
|
||||
|
By default, ABP Studio uses stable versions to create solutions. Therefore, if you want to create a solution with a preview version, first you need to create a solution and then switch your solution to the preview version from the ABP Studio UI: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
## Migration Guide |
||||
|
|
||||
|
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 or earlier: [ABP Version 9.3 Migration Guide](https://abp.io/docs/9.3/release-info/migration-guides/abp-9-3) |
||||
|
|
||||
|
## What's New with ABP v9.3? |
||||
|
|
||||
|
In this section, I will introduce some major features released in this version. |
||||
|
Here is a brief list of titles explained in the next sections: |
||||
|
|
||||
|
* Cron Expression Support for Background Workers |
||||
|
* Docs Module: PDF Export |
||||
|
* Angular UI: Standalone Package Structure |
||||
|
* Upgraded to Blazorise v1.7.7 |
||||
|
* Audit Logging Module: Excel Export |
||||
|
|
||||
|
### Cron Expression Support for Background Workers |
||||
|
|
||||
|
We've enhanced the [Background Workers System](https://abp.io/docs/9.3/framework/infrastructure/background-workers) by adding support for Cron expressions when using [Hangfire](https://abp.io/docs/9.3/framework/infrastructure/background-workers/hangfire) or [Quartz](https://abp.io/docs/9.3/framework/infrastructure/background-workers/quartz) as the background worker manager. This new feature provides more flexibility in scheduling background tasks compared to the simple period-based timing system. |
||||
|
|
||||
|
Now you can define complex scheduling patterns using standard Cron expressions. For example, you can schedule a task to run: "Every day at midnight", "Every Monday at 9 AM", or "First day of every month". |
||||
|
|
||||
|
Here's how you can use it in your background worker: |
||||
|
|
||||
|
```csharp |
||||
|
public class MyPeriodicBackgroundWorker : AsyncPeriodicBackgroundWorkerBase |
||||
|
{ |
||||
|
public MyPeriodicBackgroundWorker( |
||||
|
AbpAsyncTimer timer, |
||||
|
IServiceScopeFactory serviceScopeFactory) |
||||
|
: base(timer, serviceScopeFactory) |
||||
|
{ |
||||
|
// You can either use Period for simple intervals |
||||
|
Timer.Period = 600000; //10 minutes |
||||
|
|
||||
|
// 👇 or use CronExpression for more complex scheduling 👇 |
||||
|
CronExpression = "0 0/10 * * * ?"; //Run every 10 minutes |
||||
|
} |
||||
|
|
||||
|
protected async override Task DoWorkAsync( |
||||
|
PeriodicBackgroundWorkerContext context) |
||||
|
{ |
||||
|
// Your background work... |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
The `CronExpression` property takes precedence over the `Period` property when both are set. This feature is available when you use either the [Hangfire](https://abp.io/docs/9.3/framework/infrastructure/background-workers/hangfire) or [Quartz](https://abp.io/docs/9.3/framework/infrastructure/background-workers/quartz) background worker managers. |
||||
|
|
||||
|
> See the [Background Workers documentation](https://abp.io/docs/9.3/framework/infrastructure/background-workers) for more information about configuring and using background workers with Cron expressions. |
||||
|
|
||||
|
### Docs Module: PDF Export |
||||
|
|
||||
|
We're excited to introduce a new feature in the Docs Module that allows users to export documentation as PDF files. This feature makes it easier for users to access documentation offline or share it with team members who might not have immediate access to the online documentation system. |
||||
|
|
||||
|
**Administrators can generate PDF files from the back-office side**: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
and **then a "Download PDF" button appears in the document system** (as shown in the image below - the bottom right of the navigation menu -), allowing users to download the compiled documentation as a PDF file: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
The feature supports multiple versions of documentation, different language variants, and ensures proper formatting of all content including code blocks and technical documentation. |
||||
|
|
||||
|
### Angular UI: Standalone Package Structure |
||||
|
|
||||
|
ABP v9.3 introduces support for Angular's standalone components architecture while maintaining **full compatibility with existing module-based applications**. This update aligns with Angular's strategic direction toward standalone components as the recommended approach for building Angular applications. |
||||
|
|
||||
|
The key improvements include: |
||||
|
|
||||
|
* **Dual-support routing configurations** that work seamlessly with both module-based and standalone approaches |
||||
|
* **ABP Suite integration** for generating code that supports standalone components |
||||
|
* **Updated schematics** that provide templates for both development patterns |
||||
|
|
||||
|
This enhancement gives developers the flexibility to choose their preferred Angular architecture. Existing module-based applications **continue to work without modifications**, while new projects can leverage the standalone approach for simplified dependency management, reduced boilerplate code, and better lazy-loading capabilities. |
||||
|
|
||||
|
> For developers interested in migrating to standalone components or starting new projects, we'll be publishing a comprehensive blog post with detailed guidance and best practices. In the meantime, you can check [#22829](https://github.com/abpframework/abp/pull/22829) for implementation details of the standalone package structure and make the necessary changes to your project. |
||||
|
|
||||
|
### Upgraded to Blazorise v1.7.7 |
||||
|
|
||||
|
Upgraded the [Blazorise](https://blazorise.com/) library to v1.7.7 for Blazor UI. If you are upgrading your project to v9.3.0, please ensure that all the Blazorise-related packages are using v1.7.7 in your application. Otherwise, you might get errors due to incompatible versions. |
||||
|
|
||||
|
> See [#23013](https://github.com/abpframework/abp/pull/23013) for the updated NuGet packages. |
||||
|
|
||||
|
### Audit Logging Module: Excel Export |
||||
|
|
||||
|
In this version, we've added Excel export capabilities to the [Audit Logging Module](https://abp.io/docs/latest/modules/audit-logging-pro), allowing administrators to export audit logs and entity changes to Excel files for further analysis or reporting purposes. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
This feature enables users to: |
||||
|
|
||||
|
- Export audit logs with filtering options |
||||
|
- Export entity changes with detailed information |
||||
|
- Receive email notifications when exports are completed or fail |
||||
|
- Download exported files via secure links |
||||
|
|
||||
|
The export process runs in the background, and once completed, users receive an email with a download link. This approach ensures that even large audit log exports don't block the UI or time out during processing. |
||||
|
|
||||
|
You can configure various aspects of this feature using the `AuditLogExcelFileOptions` in your module's configuration: |
||||
|
|
||||
|
```csharp |
||||
|
Configure<AuditLogExcelFileOptions>(options => |
||||
|
{ |
||||
|
// How long to keep exported files before cleanup |
||||
|
options.FileRetentionHours = 48; |
||||
|
|
||||
|
// Base URL for download links in notification emails |
||||
|
options.DownloadBaseUrl = "https://yourdomain.com"; |
||||
|
|
||||
|
// Configure the cleanup worker schedule |
||||
|
options.ExcelFileCleanupOptions.Period = (int)TimeSpan.FromHours(24).TotalMilliseconds; |
||||
|
|
||||
|
// Use cron expression for more advanced scheduling (requires Hangfire or Quartz) |
||||
|
options.ExcelFileCleanupOptions.CronExpression = "0 2 * * *"; // Run at 2 AM daily |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
The module includes pre-configured email templates for notifications about completed or failed exports, ensuring users are always informed about the status of their export requests. |
||||
|
|
||||
|
> **Note**: This feature requires a configured BLOB storage provider to store the generated Excel files. See the [BLOB Storing documentation](https://abp.io/docs/9.3/framework/infrastructure/blob-storing) for more information. |
||||
|
|
||||
|
For more details about the Audit Logging Module and its Excel export capabilities, please refer to the [official documentation](https://abp.io/docs/9.3/modules/audit-logging-pro). |
||||
|
|
||||
|
## Community News |
||||
|
|
||||
|
### Announcing ABP Studio 1.0 General Availability 🚀 |
||||
|
|
||||
|
 |
||||
|
|
||||
|
We are thrilled to announce that ABP Studio has reached version 1.0 and is now generally available! This marks a significant milestone for our integrated development environment designed specifically for ABP developers. The stable release brings several powerful features including: |
||||
|
|
||||
|
* Enhanced Solution Runner with health monitoring capabilities |
||||
|
* Theme style selection during project creation (Basic, LeptonX Lite, and LeptonX Themes) |
||||
|
* New "Container" application type for better Docker container management |
||||
|
* Improved handling of multiple DbContexts for migration operations |
||||
|
|
||||
|
> For a detailed overview of these features and to learn more about what's coming next, check out our [announcement post](https://abp.io/community/articles/announcing-abp-studio-1-0-general-availability-82yw62bt). |
||||
|
|
||||
|
### ABP Community Talks 2025.05: Empower Elsa Workflows with AI in .NET + ABP Framework |
||||
|
|
||||
|
In this episode of ABP Community Talks, 2025.05, we are thrilled to host [**Sipke Schoorstra**](https://github.com/sfmskywalker), the creator of the [Elsa Workflows](https://docs.elsaworkflows.io/) library! This month's session is all about **"Empower Elsa Workflows with AI in .NET + ABP Framework"**. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
Sipke will join us to demonstrate how you can leverage AI within Elsa Workflows using .NET and the ABP Framework. The session will explore practical techniques and showcase how to integrate AI capabilities to enhance and automate your business processes within the Elsa workflow engine. |
||||
|
|
||||
|
> 👉 Don't miss this opportunity to learn directly from the creator of Elsa and see real-world examples of building intelligent, automated workflows! You can register from [here](https://kommunity.com/volosoft/events/abp-community-talks-202505empower-elsa-workflows-with-ai-in-netabp-framework-3965dd32). |
||||
|
|
||||
|
### ABP Bootcamp: Mastering Infrastructure & Features |
||||
|
|
||||
|
We are excited to announce the very first **ABP Bootcamp: Mastering Infrastructure & Features**! This is a live training program designed to give you hands-on, practical experience with ABP's core infrastructure and features. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
Join the ABP Bootcamp to learn directly from the core team in a focused, hands-on program designed for busy developers. Over four days, you'll gain a deep understanding of ABP's infrastructure, best practices, and practical skills you can immediately apply to your projects. |
||||
|
|
||||
|
> **Seats are limited!** Don't miss this opportunity to level up your ABP skills with direct guidance from the experts. |
||||
|
> |
||||
|
> 👉 [See full details and reserve your seat!](https://abp.io/bootcamp) |
||||
|
|
||||
|
### New ABP Community Articles |
||||
|
|
||||
|
There are exciting articles contributed by the ABP community as always. I will highlight some of them here: |
||||
|
|
||||
|
* [Prabhjot Singh](https://abp.io/community/members/prabhjot) has published 3 new articles: |
||||
|
* [Accessing Multiple Remote ABP based Backends Using HttpApi.Client](https://abp.io/community/articles/consume-multi-backends-using-clients-6f4vcggh) |
||||
|
* [Adopting the new .slnx format to organize applications and services](https://abp.io/community/articles/adopting-the-new-.slnx-format-to-organize-applications-6cm3vl8k) |
||||
|
* [Replacing Dynamic client proxies with Static client proxies](https://abp.io/community/articles/replacing-dynamic-client-proxies-with-static-client-proxies-g30lf0vx) |
||||
|
* [Liming Ma](https://github.com/maliming) has published 2 new articles: |
||||
|
* [Resolving Tenant from Route in ABP Framework](https://abp.io/community/articles/resolving-tenant-from-route-in-abp-framework-ah7oru97) |
||||
|
* [Integrating .NET AI Chat Template with ABP Framework](https://abp.io/community/articles/integrating-.net-ai-chat-template-with-abp-framework-qavb5p2j) |
||||
|
* [Engincan Veske](https://engincanveske.substack.com/) has published 2 new articles: |
||||
|
* [Understanding HttpApi.Client Project & Remote Services in an ABP Based Application](https://abp.io/community/articles/http-api-client-and-remote-services-in-abp-based-application-xkknsp6m) |
||||
|
* [Using Elsa 3 with the ABP Framework: A Comprehensive Guide](https://abp.io/community/articles/using-elsa-3-workflow-with-abp-framework-usqk8afg) |
||||
|
* [Enis Necipoğlu](https://github.com/enisn) has published 2 new articles: |
||||
|
* [White Labeling in ABP Framework](https://abp.io/community/articles/white-labeling-in-abp-framework-5trwmrfm) by [Enis Necipoğlu](https://github.com/enisn) |
||||
|
* [You do it wrong! Customizing ABP Login Page Correctly](https://abp.io/community/articles/you-do-it-wrong-customizing-abp-login-page-correctly-bna7wzt5) |
||||
|
* [New in ABP Studio: Docker Container Management](https://abp.io/community/articles/abp-studio-docker-container-management-ex7r27y8) by [Yunus Emre Kalkan](https://github.com/yekalkan) |
||||
|
* [Solving MongoDB GUID Issues After an ABP Framework Upgrade](https://abp.io/community/articles/solving-mongodb-guid-issues-after-an-abp-framework-upgrade-tv8waw1n) by [Burak Demir](https://abp.io/community/members/burakdemir) |
||||
|
|
||||
|
|
||||
|
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. |
||||
|
|
||||
|
## Conclusion |
||||
|
|
||||
|
This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://abp.io/docs/9.3/release-info/road-map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v9.3 RC and provide feedback to help us release a more stable version. |
||||
|
|
||||
|
Thanks for being a part of this community! |
||||
|
After Width: | Height: | Size: 455 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 300 KiB |
|
After Width: | Height: | Size: 239 KiB |
|
After Width: | Height: | Size: 676 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 214 KiB |
|
After Width: | Height: | Size: 852 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 749 KiB |
|
After Width: | Height: | Size: 779 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 697 KiB |
|
After Width: | Height: | Size: 852 KiB |
|
After Width: | Height: | Size: 738 KiB |
|
After Width: | Height: | Size: 993 KiB |
|
After Width: | Height: | Size: 802 KiB |
|
After Width: | Height: | Size: 633 KiB |
|
After Width: | Height: | Size: 970 KiB |
|
After Width: | Height: | Size: 779 KiB |
|
After Width: | Height: | Size: 518 KiB |
|
After Width: | Height: | Size: 630 KiB |
|
After Width: | Height: | Size: 674 KiB |
|
After Width: | Height: | Size: 228 KiB |
|
After Width: | Height: | Size: 267 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 230 KiB |
|
After Width: | Height: | Size: 206 KiB |
|
After Width: | Height: | Size: 206 KiB |
|
After Width: | Height: | Size: 241 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 347 KiB |
|
After Width: | Height: | Size: 210 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 925 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 163 KiB |
|
After Width: | Height: | Size: 968 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 859 KiB |
|
After Width: | Height: | Size: 604 KiB |
|
After Width: | Height: | Size: 984 KiB |
|
After Width: | Height: | Size: 201 KiB |
|
After Width: | Height: | Size: 994 KiB |
|
After Width: | Height: | Size: 983 KiB |
|
After Width: | Height: | Size: 788 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 980 KiB |
|
After Width: | Height: | Size: 269 KiB |
|
After Width: | Height: | Size: 259 KiB |
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 259 KiB |
|
After Width: | Height: | Size: 472 KiB |
|
After Width: | Height: | Size: 354 KiB |
|
After Width: | Height: | Size: 217 KiB |
|
After Width: | Height: | Size: 254 KiB |
|
After Width: | Height: | Size: 307 KiB |
|
After Width: | Height: | Size: 307 KiB |
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 265 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 355 KiB |
|
After Width: | Height: | Size: 346 KiB |
|
After Width: | Height: | Size: 602 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 516 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
@ -0,0 +1,101 @@ |
|||||
|
# WeAreDevelopers 2025: A Speaker’s Impressions |
||||
|
|
||||
|
 |
||||
|
|
||||
|
After speaking at DotNext Moscow, I had high expectations for WeAreDevelopers 2025—and the event delivered on all fronts. Held in Berlin / Germany, it brought together a truly global crowd of developers, tech leaders, and innovators. As a speaker and software architect , I’m sharing my first-hand highlights, favorite moments, and candid scenes from this 2025’s conference. |
||||
|
|
||||
|
## 🗣 My Talk |
||||
|
|
||||
|
We have a good experience on multi-tenancy topic in SaaS development. My talk's topic was "Building Multi-Tenant ASP.NET Core Applications: Best Practices and Real-World Solutions". It was on the stage 4, 11 July Friday 10:20 am - 10:50 am and [this my presentation file](https://github.com/ebicoglu/presentations/blob/main/multi-tenancy-wearedevelopers-2025_30mins.pptx). |
||||
|
|
||||
|
 |
||||
|
|
||||
|
 |
||||
|
 |
||||
|
 |
||||
|
|
||||
|
|
||||
|
## 🏛 Huge Venue |
||||
|
|
||||
|
 |
||||
|
*The image is credited to WeAreDevelopers organization* |
||||
|
|
||||
|
First of all, I had been in numerous software conferences, I must say that I've never seen such a big software event. The event spanned **500+ sessions across 20+ stages**, including the HR Leaders Summit for **2 full days**. |
||||
|
|
||||
|
 |
||||
|
 |
||||
|
|
||||
|
------ |
||||
|
|
||||
|
## 🎤 Opening Keynote from GitHub |
||||
|
|
||||
|
GitHub CEO Thomas Dohmke initiated the conference on the main stage with a talk on *“Agents for the Sake of Happiness”*. Having introduced Copilot three years ago here, he now launched bold predictions about autonomous AI—a fascinating evolution... He demonstrated GitHub Co-Pilot's AI and created a snake game. Altough it didn't work as he planned, we're developers we know live coding is hard. Actually that's because we shouldn't rely on AI. AI is not deterministic even though we set all those temperature, TopP, TopK parameters to minimum. |
||||
|
|
||||
|
> AI is a good but not trustable friend! |
||||
|
|
||||
|
 |
||||
|
|
||||
|
------ |
||||
|
|
||||
|
## 🧭 11 Parallel Stages: Rush |
||||
|
|
||||
|
There were 11 stages where 11 different topics were being explained. And the sessions were 30 minutes. Actually that's the downside of this event. Because there were so nice talks that needs to be minimum 40 minutes. But anyway I understand the organization team because there are many smart speakers whose needs to be included in this event. So as a attendee I was on a hurry to pick the next talk even when I was listening to a talk :) |
||||
|
|
||||
|
The venue consists of 3 buildings. So if you pick a talk on another building, you have 10 mins to go to toilet or drink something and catch the next session on that far building... |
||||
|
|
||||
|
There was HR track with **3 stages and 2 full days** of HR/Talent Acquisition programming, it attracted a notable overlap of developers and HR pros. Themes included AI‑powered recruiting, remote work culture, mental health, diversity & inclusion, and building AI agents |
||||
|
|
||||
|
 |
||||
|
 |
||||
|
|
||||
|
|
||||
|
------ |
||||
|
|
||||
|
## 🤖 AI & AI & AI & Others... |
||||
|
|
||||
|
I'm one of those AI lovers. I love learning cutting-edge information. And as I see AI is being more trendy everyday. That's why most of the talks were about AI. Everything related to AI. I generaly attended AI related talks because I'm also working on some AI topics in Volosoft at the moment. |
||||
|
|
||||
|
|
||||
|
------ |
||||
|
|
||||
|
## 🤝 Expo Floor & Networking |
||||
|
|
||||
|
The expo was a developer’s playground—cloud services, open‑source tools, startups, and enterprise platforms. I found new partners and reconnected with peers in a buzzing atmosphere. Everywhere was full of talking's even outside. If you want to get fresh air and drink coffee, you can go out and listen to the outside talks. |
||||
|
|
||||
|
 |
||||
|
 |
||||
|
|
||||
|
|
||||
|
|
||||
|
Networking wasn't just daytime chatter—hallway meetups and evening socials were unforgettable. |
||||
|
|
||||
|
 |
||||
|
 |
||||
|
|
||||
|
------ |
||||
|
|
||||
|
## 😂 Candid & Fun Moments |
||||
|
|
||||
|
Swag stations, sponsor games, “developer selfies”—these lighter moments kept the vibe upbeat and human. |
||||
|
|
||||
|
 |
||||
|
 |
||||
|
|
||||
|
------ |
||||
|
|
||||
|
## ✅ Final Thoughts & Looking Ahead |
||||
|
|
||||
|
WeAreDevelopers 2025 was an unforgettable three-day ride: **15,000 tech minds**, **500+ sessions**, and a true **bridge between developers and HR** |
||||
|
I’m leaving with: |
||||
|
|
||||
|
- Fresh strategies in GenAI and SaaS growth |
||||
|
- Stronger HR-tech understanding and crossover potential |
||||
|
- New professional connections—and fun memories |
||||
|
|
||||
|
|
||||
|
------ |
||||
|
|
||||
|
 |
||||
|
|
||||
|
|
||||
|
|
||||
@ -0,0 +1,250 @@ |
|||||
|
# Using Hangfire Dashboard in ABP API Website 🚀 |
||||
|
|
||||
|
## Introduction |
||||
|
|
||||
|
In this article, I'll show you how to integrate and use the Hangfire Dashboard in an ABP API website. |
||||
|
|
||||
|
Typically, API websites use `JWT Bearer` authentication, but the Hangfire Dashboard isn't compatible with `JWT Bearer` authentication. Therefore, we need to implement `Cookies` and `OpenIdConnect` authentication for the Hangfire Dashboard access. |
||||
|
|
||||
|
## Creating a New ABP Demo Project 🛠️ |
||||
|
|
||||
|
We'll create a new ABP Demo `Tiered` project that includes `AuthServer`, `API`, and `Web` projects. |
||||
|
|
||||
|
```bash |
||||
|
abp new AbpHangfireDemoApp -t app --tiered |
||||
|
``` |
||||
|
|
||||
|
Now let's add the Hangfire Dashboard to the `API` project and configure it to use `Cookies` and `OpenIdConnect` authentication for accessing the dashboard. |
||||
|
|
||||
|
## Adding a New Hangfire Application 🔧 |
||||
|
|
||||
|
We need to add a new Hangfire application to the `appsettings.json` file in the `DbMigrator` project: |
||||
|
|
||||
|
> **Note:** Replace `44371` with your `API` project's port. |
||||
|
|
||||
|
```json |
||||
|
"OpenIddict": { |
||||
|
"Applications": { |
||||
|
//... |
||||
|
"AbpHangfireDemoApp_Hangfire": { |
||||
|
"ClientId": "AbpHangfireDemoApp_Hangfire", |
||||
|
"RootUrl": "https://localhost:44371/" |
||||
|
} |
||||
|
//... |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
2. Update the `OpenIddictDataSeedContributor`'s `CreateApplicationsAsync` method in the `Domain` project to seed the new Hangfire application. |
||||
|
|
||||
|
```csharp |
||||
|
//Hangfire Client |
||||
|
var hangfireClientId = configurationSection["AbpHangfireDemoApp_Hangfire:ClientId"]; |
||||
|
if (!hangfireClientId.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
var hangfireClientRootUrl = configurationSection["AbpHangfireDemoApp_Hangfire:RootUrl"]!.EnsureEndsWith('/'); |
||||
|
|
||||
|
await CreateApplicationAsync( |
||||
|
applicationType: OpenIddictConstants.ApplicationTypes.Web, |
||||
|
name: hangfireClientId!, |
||||
|
type: OpenIddictConstants.ClientTypes.Confidential, |
||||
|
consentType: OpenIddictConstants.ConsentTypes.Implicit, |
||||
|
displayName: "Hangfire Application", |
||||
|
secret: configurationSection["AbpHangfireDemoApp_Hangfire:ClientSecret"] ?? "1q2w3e*", |
||||
|
grantTypes: new List<string> //Hybrid flow |
||||
|
{ |
||||
|
OpenIddictConstants.GrantTypes.AuthorizationCode, OpenIddictConstants.GrantTypes.Implicit |
||||
|
}, |
||||
|
scopes: commonScopes, |
||||
|
redirectUris: new List<string> { $"{hangfireClientRootUrl}signin-oidc" }, |
||||
|
postLogoutRedirectUris: new List<string> { $"{hangfireClientRootUrl}signout-callback-oidc" }, |
||||
|
clientUri: hangfireClientRootUrl, |
||||
|
logoUri: "/images/clients/aspnetcore.svg" |
||||
|
); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
3. Run the `DbMigrator` project to seed the new Hangfire application. |
||||
|
|
||||
|
### Adding Hangfire Dashboard to the `API` Project 📦 |
||||
|
|
||||
|
1. Add the following packages and modules dependencies to the `API` project: |
||||
|
|
||||
|
```bash |
||||
|
<PackageReference Include="Volo.Abp.BackgroundJobs.HangFire" Version="9.2.0" /> |
||||
|
<PackageReference Include="Volo.Abp.AspNetCore.Authentication.OpenIdConnect" Version="9.2.0" /> |
||||
|
<PackageReference Include="Hangfire.SqlServer" Version="1.8.20" /> |
||||
|
``` |
||||
|
|
||||
|
```cs |
||||
|
typeof(AbpBackgroundJobsHangfireModule), |
||||
|
typeof(AbpAspNetCoreAuthenticationOpenIdConnectModule) |
||||
|
``` |
||||
|
|
||||
|
2. Add the `HangfireClientId` and `HangfireClientSecret` to the `appsettings.json` file in the `API` project: |
||||
|
|
||||
|
```csharp |
||||
|
"AuthServer": { |
||||
|
"Authority": "https://localhost:44358", |
||||
|
"RequireHttpsMetadata": true, |
||||
|
"MetaAddress": "https://localhost:44358", |
||||
|
"SwaggerClientId": "AbpHangfireDemoApp_Swagger", |
||||
|
"HangfireClientId": "AbpHangfireDemoApp_Hangfire", |
||||
|
"HangfireClientSecret": "1q2w3e*" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
3. Add the `ConfigureHangfire` method to the `API` project to configure Hangfire: |
||||
|
|
||||
|
```csharp |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
var hostingEnvironment = context.Services.GetHostingEnvironment(); |
||||
|
|
||||
|
//... |
||||
|
|
||||
|
//Add Hangfire |
||||
|
ConfigureHangfire(context, configuration); |
||||
|
//... |
||||
|
} |
||||
|
|
||||
|
private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration) |
||||
|
{ |
||||
|
context.Services.AddHangfire(config => |
||||
|
{ |
||||
|
config.UseSqlServerStorage(configuration.GetConnectionString("Default")); |
||||
|
}); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
4. Modify the `ConfigureAuthentication` method to add new `Cookies` and `OpenIdConnect` authentication schemes: |
||||
|
|
||||
|
```csharp |
||||
|
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) |
||||
|
{ |
||||
|
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) |
||||
|
.AddAbpJwtBearer(options => |
||||
|
{ |
||||
|
options.Authority = configuration["AuthServer:Authority"]; |
||||
|
options.RequireHttpsMetadata = configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata"); |
||||
|
options.Audience = "AbpHangfireDemoApp"; |
||||
|
|
||||
|
options.ForwardDefaultSelector = httpContext => httpContext.Request.Path.StartsWithSegments("/hangfire", StringComparison.OrdinalIgnoreCase) |
||||
|
? CookieAuthenticationDefaults.AuthenticationScheme |
||||
|
: null; |
||||
|
}) |
||||
|
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme) |
||||
|
.AddAbpOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => |
||||
|
{ |
||||
|
options.Authority = configuration["AuthServer:Authority"]; |
||||
|
options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); |
||||
|
options.ResponseType = OpenIdConnectResponseType.Code; |
||||
|
|
||||
|
options.ClientId = configuration["AuthServer:HangfireClientId"]; |
||||
|
options.ClientSecret = configuration["AuthServer:HangfireClientSecret"]; |
||||
|
|
||||
|
options.UsePkce = true; |
||||
|
options.SaveTokens = true; |
||||
|
options.GetClaimsFromUserInfoEndpoint = true; |
||||
|
|
||||
|
options.Scope.Add("roles"); |
||||
|
options.Scope.Add("email"); |
||||
|
options.Scope.Add("phone"); |
||||
|
options.Scope.Add("AbpHangfireDemoApp"); |
||||
|
|
||||
|
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; |
||||
|
}); |
||||
|
|
||||
|
//... |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
5. Add a custom middleware and `UseAbpHangfireDashboard` after `UseAuthorization` in the `OnApplicationInitialization` method: |
||||
|
|
||||
|
```csharp |
||||
|
//... |
||||
|
app.UseAuthorization(); |
||||
|
|
||||
|
app.Use(async (httpContext, next) => |
||||
|
{ |
||||
|
if (httpContext.Request.Path.StartsWithSegments("/hangfire", StringComparison.OrdinalIgnoreCase)) |
||||
|
{ |
||||
|
var authenticateResult = await httpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); |
||||
|
if (!authenticateResult.Succeeded) |
||||
|
{ |
||||
|
await httpContext.ChallengeAsync( |
||||
|
OpenIdConnectDefaults.AuthenticationScheme, |
||||
|
new AuthenticationProperties |
||||
|
{ |
||||
|
RedirectUri = httpContext.Request.Path + httpContext.Request.QueryString |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
await next.Invoke(); |
||||
|
}); |
||||
|
app.UseAbpHangfireDashboard("/hangfire", options => |
||||
|
{ |
||||
|
options.AsyncAuthorization = new[] |
||||
|
{ |
||||
|
new AbpHangfireAuthorizationFilter() |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
//... |
||||
|
``` |
||||
|
|
||||
|
Perfect! 🎉 Now you can run the `AuthServer` and `API` projects and access the Hangfire Dashboard at `https://localhost:44371/hangfire`. |
||||
|
|
||||
|
> **Note:** Replace `44371` with your `API` project's port. |
||||
|
|
||||
|
The first time you access the Hangfire Dashboard, you'll be redirected to the login page of the `AuthServer` project. After you log in, you'll be redirected back to the Hangfire Dashboard. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
## Key Points 🔑 |
||||
|
|
||||
|
### 1. Authentication Scheme Selection |
||||
|
|
||||
|
The default authentication scheme in API websites is `JWT Bearer`. We've implemented `Cookies` and `OpenIdConnect` specifically for the Hangfire Dashboard. |
||||
|
|
||||
|
We've configured the `JwtBearerOptions`'s `ForwardDefaultSelector` to use `CookieAuthenticationDefaults.AuthenticationScheme` for Hangfire Dashboard requests. |
||||
|
|
||||
|
This means that if the request path starts with `/hangfire`, the request will be authenticated using the `Cookies` authentication scheme; otherwise, it will use the `JwtBearer` authentication scheme. |
||||
|
|
||||
|
```csharp |
||||
|
options.ForwardDefaultSelector = httpContext => httpContext.Request.Path.StartsWithSegments("/hangfire", StringComparison.OrdinalIgnoreCase) |
||||
|
? CookieAuthenticationDefaults.AuthenticationScheme |
||||
|
: null; |
||||
|
``` |
||||
|
|
||||
|
### 2. Custom Middleware for Authentication |
||||
|
|
||||
|
We've also implemented a custom middleware to handle `Cookies` authentication for the Hangfire Dashboard. If the current request isn't authenticated with the `Cookies` authentication scheme, it will be redirected to the login page. |
||||
|
|
||||
|
```csharp |
||||
|
app.Use(async (httpContext, next) => |
||||
|
{ |
||||
|
if (httpContext.Request.Path.StartsWithSegments("/hangfire", StringComparison.OrdinalIgnoreCase)) |
||||
|
{ |
||||
|
var authenticateResult = await httpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); |
||||
|
if (!authenticateResult.Succeeded) |
||||
|
{ |
||||
|
await httpContext.ChallengeAsync( |
||||
|
OpenIdConnectDefaults.AuthenticationScheme, |
||||
|
new AuthenticationProperties |
||||
|
{ |
||||
|
RedirectUri = httpContext.Request.Path + httpContext.Request.QueryString |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
await next.Invoke(); |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
## References 📚 |
||||
|
|
||||
|
- [ABP Hangfire Background Job Manager](https://abp.io/docs/latest/framework/infrastructure/background-jobs/hangfire) |
||||
|
- [Use cookie authentication in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-9.0) |
||||
|
After Width: | Height: | Size: 4.9 MiB |
@ -0,0 +1,28 @@ |
|||||
|
**It is going to get hotter with ABP’s Summer Campaign!** |
||||
|
|
||||
|
Since it’s summer time, we wanted to make it even hotter by announcing a summer campaign! From July 21 to 31 we are offering a 20% discount on all ABP licenses. Now is the best time to invest in ABP and start developing asp net applications faster without wasting your time with repetitive tasks. |
||||
|
|
||||
|
## Summer Campaign Terms |
||||
|
|
||||
|
Please review the following terms and conditions carefully. |
||||
|
|
||||
|
* This offer is available for extensions and new purchases. |
||||
|
* Developer seat purchases are also included to the campaign. |
||||
|
* Campaign is available from July 21st to July 31st. |
||||
|
* Discounts are valid on selected licenses only. |
||||
|
* This offer cannot be combined with other promotions or discounts. |
||||
|
|
||||
|
**Why Choose ABP?** |
||||
|
|
||||
|
ABP offers a powerful infrastructure, simplifying modern ASP.NET core development. It helps develop modern ASP.NET applications, including ASP.NET core MVC web applications, blazor front-end projects, and angular .NET Core solutions. |
||||
|
|
||||
|
-The core framework and pre-built modules are designed with microservice architecture in mind. |
||||
|
-ABP provides a module system that allows you to develop reusable application modules. |
||||
|
-Helps implement a DDD based layered architecture and build a maintainable code base. |
||||
|
-Easily manage SaaS applications with integrated multi-tenancy, from database to UI. |
||||
|
|
||||
|
**This Offer Ends July 31, So Hurry Up!** |
||||
|
|
||||
|
This summer campaign is running from July 21 to July 31, so don’t miss your chance. Now is the perfect opportunity to enhance your asp net web development with ABP and benefit from our exclusive features. |
||||
|
|
||||
|
Get Your Discount Now: [https://abp.io/pricing?utm_source=abpwebsite&utm_medium=referral&utm_campaign=summer25_blog](https://abp.io/pricing?utm_source=abpwebsite&utm_medium=referral&utm_campaign=summer25_blog) |
||||
@ -0,0 +1,221 @@ |
|||||
|
# Module Installer Projects |
||||
|
|
||||
|
Each ABP module includes an `.Installer` project (e.g., `Volo.Abp.Account.Installer`) that serves as a **Virtual File System container** for module installation and resource management. These projects are essential for the ABP CLI to understand and install modules properly. |
||||
|
|
||||
|
## Purpose of Installer Projects |
||||
|
|
||||
|
Installer projects have three main purposes: |
||||
|
|
||||
|
1. **Virtual File System Integration**: Register the module's embedded resources with ABP's Virtual File System |
||||
|
2. **Resource Packaging**: Package module metadata files (`.abpmdl` and `.abppkg`) as embedded resources |
||||
|
3. **CLI Integration**: Enable the ABP CLI to understand module structure and install modules automatically |
||||
|
|
||||
|
## Structure of Installer Projects |
||||
|
|
||||
|
### Project Files |
||||
|
|
||||
|
- **`{ModuleName}.Installer.csproj`**: References `Volo.Abp.VirtualFileSystem` and embeds module metadata files |
||||
|
- **`InstallationNotes.md`**: Documentation for the module |
||||
|
- **`Volo/Abp/{ModuleName}/Abp{ModuleName}InstallerModule.cs`**: The core module class that registers embedded resources |
||||
|
|
||||
|
### Example Installer Module |
||||
|
|
||||
|
```csharp |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace Volo.Abp.Account; |
||||
|
|
||||
|
[DependsOn(typeof(AbpVirtualFileSystemModule))] |
||||
|
public class AbpAccountInstallerModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpAccountInstallerModule>(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### Project Configuration |
||||
|
|
||||
|
The `.csproj` file embeds module metadata as content: |
||||
|
|
||||
|
```xml |
||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net9.0</TargetFramework> |
||||
|
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.VirtualFileSystem\Volo.Abp.VirtualFileSystem.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<!-- Embed module definition file --> |
||||
|
<Content Include="..\..\Volo.Abp.Account.abpmdl"> |
||||
|
<Pack>true</Pack> |
||||
|
<PackagePath>content\</PackagePath> |
||||
|
</Content> |
||||
|
|
||||
|
<!-- Embed package definition files --> |
||||
|
<Content Include="..\..\**\*.abppkg*"> |
||||
|
<Pack>true</Pack> |
||||
|
<PackagePath>content\</PackagePath> |
||||
|
</Content> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
|
``` |
||||
|
|
||||
|
## Module Metadata Files |
||||
|
|
||||
|
### `.abpmdl` (Module Definition) |
||||
|
|
||||
|
The module definition file describes the module's structure and packages: |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"folders": { |
||||
|
"items": { |
||||
|
"src": {}, |
||||
|
"test": {} |
||||
|
} |
||||
|
}, |
||||
|
"packages": { |
||||
|
"Volo.Abp.Account.Web": { |
||||
|
"path": "src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.abppkg", |
||||
|
"folder": "src" |
||||
|
}, |
||||
|
"Volo.Abp.Account.Application": { |
||||
|
"path": "src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.abppkg", |
||||
|
"folder": "src" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### `.abppkg` (Package Definition) |
||||
|
|
||||
|
Each package has a definition file that specifies its role: |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"role": "lib.application" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Common roles: |
||||
|
- `lib.application`: Application layer package |
||||
|
- `lib.mvc`: MVC/Web layer package |
||||
|
- `lib.domain`: Domain layer package |
||||
|
- `lib.domain-shared`: Shared domain layer package |
||||
|
- `lib.efcore`: Entity Framework Core package |
||||
|
|
||||
|
## How Installer Projects Work |
||||
|
|
||||
|
### 1. CLI Installation Process |
||||
|
|
||||
|
When you run `abp add-module Volo.Abp.Account`: |
||||
|
|
||||
|
1. **Download Installer Package**: CLI downloads `Volo.Abp.Account.Installer` from NuGet |
||||
|
2. **Read Module Definition**: CLI reads the embedded `.abpmdl` file to understand module structure |
||||
|
3. **Read Package Definitions**: CLI reads `.abppkg` files to understand package roles |
||||
|
4. **Install Packages**: CLI installs appropriate packages to correct project types based on roles |
||||
|
5. **Add Dependencies**: CLI adds module dependencies to project module classes |
||||
|
|
||||
|
### 2. Virtual File System Integration |
||||
|
|
||||
|
The `InstallerModule` registers itself with the Virtual File System: |
||||
|
|
||||
|
```csharp |
||||
|
options.FileSets.AddEmbedded<AbpAccountInstallerModule>(); |
||||
|
``` |
||||
|
|
||||
|
This makes embedded resources available at runtime and enables: |
||||
|
- Access to module metadata |
||||
|
- Resource file management |
||||
|
- Module configuration |
||||
|
|
||||
|
## Creating Installer Projects for New Modules |
||||
|
|
||||
|
### Required Files |
||||
|
|
||||
|
1. **Project File**: `{ModuleName}.Installer.csproj` |
||||
|
2. **Module Class**: `Abp{ModuleName}InstallerModule.cs` |
||||
|
3. **Documentation**: `InstallationNotes.md` |
||||
|
4. **Module Definition**: `{ModuleName}.abpmdl` (in module root) |
||||
|
5. **Package Definitions**: `{PackageName}.abppkg` (in each package) |
||||
|
|
||||
|
### Template Structure |
||||
|
|
||||
|
``` |
||||
|
modules/your-module/ |
||||
|
├── src/ |
||||
|
│ ├── Volo.Abp.YourModule.Installer/ |
||||
|
│ │ ├── Volo.Abp.YourModule.Installer.csproj |
||||
|
│ │ ├── InstallationNotes.md |
||||
|
│ │ └── Volo/ |
||||
|
│ │ └── Abp/ |
||||
|
│ │ └── YourModule/ |
||||
|
│ │ └── AbpYourModuleInstallerModule.cs |
||||
|
│ └── [other packages]/ |
||||
|
├── Volo.Abp.YourModule.abpmdl |
||||
|
└── [other module files] |
||||
|
``` |
||||
|
|
||||
|
### Package Definition Examples |
||||
|
|
||||
|
For different package types: |
||||
|
|
||||
|
```json |
||||
|
// Application package |
||||
|
{ "role": "lib.application" } |
||||
|
|
||||
|
// MVC package |
||||
|
{ "role": "lib.mvc" } |
||||
|
|
||||
|
// Domain package |
||||
|
{ "role": "lib.domain" } |
||||
|
|
||||
|
// EF Core package |
||||
|
{ "role": "lib.efcore" } |
||||
|
``` |
||||
|
|
||||
|
## Why Installer Projects Appear "Empty" |
||||
|
|
||||
|
Installer projects appear minimal because their primary function is infrastructure, not business logic: |
||||
|
|
||||
|
- **No Business Logic**: Business logic belongs in the actual module packages |
||||
|
- **Pure Infrastructure**: They only handle module installation and resource management |
||||
|
- **CLI Integration**: They enable automated module installation through the ABP CLI |
||||
|
- **Resource Management**: They package and distribute module metadata |
||||
|
|
||||
|
## Best Practices |
||||
|
|
||||
|
1. **Follow Naming Convention**: Use `{ModuleName}.Installer` for the project name |
||||
|
2. **Include Documentation**: Always provide `InstallationNotes.md` with module information |
||||
|
3. **Proper Dependencies**: Only depend on `Volo.Abp.VirtualFileSystem` |
||||
|
4. **Embed All Metadata**: Include both `.abpmdl` and `.abppkg` files |
||||
|
5. **Test Installation**: Verify your installer works with `abp add-module` command |
||||
|
|
||||
|
## Troubleshooting |
||||
|
|
||||
|
### Common Issues |
||||
|
|
||||
|
1. **Missing .abpmdl file**: Ensure the module definition file exists in the module root |
||||
|
2. **Missing .abppkg files**: Each package needs a definition file |
||||
|
3. **Incorrect roles**: Use appropriate roles for each package type |
||||
|
4. **CLI not finding module**: Verify the installer package is published to NuGet |
||||
|
|
||||
|
### Verification Steps |
||||
|
|
||||
|
1. Build the installer project: `dotnet build` |
||||
|
2. Check embedded resources: Verify `.abpmdl` and `.abppkg` files are embedded |
||||
|
3. Test CLI installation: `abp add-module YourModule` |
||||
|
4. Verify dependencies: Check that module dependencies are added correctly |
||||
|
|
||||
|
This installer system enables ABP's sophisticated module architecture, allowing for automated installation with proper dependency resolution and project type matching. |
||||
@ -1,12 +1,34 @@ |
|||||
using Volo.Abp.AspNetCore.MultiTenancy; |
using System.Linq; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.AspNetCore.MultiTenancy; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
namespace Microsoft.AspNetCore.Builder; |
namespace Microsoft.AspNetCore.Builder; |
||||
|
|
||||
public static class AbpAspNetCoreMultiTenancyApplicationBuilderExtensions |
public static class AbpAspNetCoreMultiTenancyApplicationBuilderExtensions |
||||
{ |
{ |
||||
|
private const string AuthenticationMiddlewareSetKey = "__AuthenticationMiddlewareSet"; |
||||
|
|
||||
public static IApplicationBuilder UseMultiTenancy(this IApplicationBuilder app) |
public static IApplicationBuilder UseMultiTenancy(this IApplicationBuilder app) |
||||
{ |
{ |
||||
return app |
var multiTenancyOptions = app.ApplicationServices.GetRequiredService<IOptions<AbpTenantResolveOptions>>(); |
||||
.UseMiddleware<MultiTenancyMiddleware>(); |
var hasCurrentUserTenantResolveContributor = multiTenancyOptions.Value.TenantResolvers.Any(r => r is CurrentUserTenantResolveContributor); |
||||
|
if (hasCurrentUserTenantResolveContributor) |
||||
|
{ |
||||
|
var authenticationMiddlewareSet = app.Properties.TryGetValue(AuthenticationMiddlewareSetKey, out var value) && value is true; |
||||
|
if (!authenticationMiddlewareSet) |
||||
|
{ |
||||
|
var logger = app.ApplicationServices.GetService<ILogger<MultiTenancyMiddleware>>(); |
||||
|
logger?.LogWarning( |
||||
|
"MultiTenancyMiddleware is being registered before the authentication middleware. " + |
||||
|
"This may lead to incorrect tenant resolution if the resolution depends on the authenticated user. " + |
||||
|
"Ensure app.UseAuthentication() is called before app.UseMultiTenancy()." |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return app.UseMiddleware<MultiTenancyMiddleware>(); |
||||
} |
} |
||||
} |
} |
||||
|
|||||