Browse Source

Merge branch 'dev' into auto-merge/rel-9-3/3855

pull/23268/head
selman koc 1 year ago
committed by GitHub
parent
commit
8aa092d733
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  2. 7
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  3. 4
      common.props
  4. 203
      docs/en/Blog-Posts/2025-06-18 v9_3_Preview/POST.md
  5. BIN
      docs/en/Blog-Posts/2025-06-18 v9_3_Preview/abp-studio.png
  6. BIN
      docs/en/Blog-Posts/2025-06-18 v9_3_Preview/audit-logs-export-to-excel.png
  7. BIN
      docs/en/Blog-Posts/2025-06-18 v9_3_Preview/bootcamp.png
  8. BIN
      docs/en/Blog-Posts/2025-06-18 v9_3_Preview/community-talk-2025-5.png
  9. BIN
      docs/en/Blog-Posts/2025-06-18 v9_3_Preview/cover-image.png
  10. BIN
      docs/en/Blog-Posts/2025-06-18 v9_3_Preview/download-pdf-on-docs.png
  11. BIN
      docs/en/Blog-Posts/2025-06-18 v9_3_Preview/generate-pdf-docs.png
  12. BIN
      docs/en/Blog-Posts/2025-06-18 v9_3_Preview/studio-switch-to-preview.png
  13. 250
      docs/en/Community-Articles/2025-06-20-Using-Hangfire-Dashboard-in-ABP-API-website/POST.md
  14. BIN
      docs/en/Community-Articles/2025-06-20-Using-Hangfire-Dashboard-in-ABP-API-website/gif.gif
  15. 13
      docs/en/framework/architecture/multi-tenancy/index.md
  16. 63
      docs/en/framework/infrastructure/background-jobs/hangfire.md
  17. 6
      framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo/Abp/AspNetCore/Authentication/OpenIdConnect/AbpAspNetCoreAuthenticationOpenIdConnectModule.cs
  18. 1
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/BlazorWebAssemblyScriptContributor.cs
  19. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalFooterTagHelperService.cs
  20. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationExtensions.cs
  21. 30
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollection.cs
  22. 15
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs
  23. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JQueryValidation/JQueryValidationScriptContributor.cs
  24. 158
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationPartSorter.cs
  25. 13
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs
  26. 12
      framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AbpBackgroundWorkersModule.cs
  27. 10
      framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkersApplicationInitializationContextExtensions.cs
  28. 16
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs
  29. 50
      framework/src/Volo.Abp.HangFire/Volo/Abp/Hangfire/AbpHangfireAuthorizationFilter.cs
  30. 5
      framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpTenantResolveOptions.cs
  31. 10
      framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolverNames.cs
  32. 6
      framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantResolver.cs
  33. 1
      framework/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/MultiTenancy/AspNetCoreMultiTenancy_Without_DomainResolver_Tests.cs
  34. 79
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ApplicationPart/ApplicationPartSorter_Tests.cs
  35. 8
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController.cs
  36. 14
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController_Tests.cs
  37. 87
      framework/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/FallbackTenantResolveContributor_Tests.cs
  38. 9
      latest-versions.json
  39. 20
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.css
  40. 12
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.scss
  41. 21
      modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Pages/Index.cshtml
  42. 7
      modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250630055813_Initial.Designer.cs
  43. 1
      modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250630055813_Initial.cs
  44. 5
      modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs
  45. 14
      modules/openiddict/app/OpenIddict.Demo.Server/Pages/Index.cshtml
  46. 15
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AuthorizeController.cs
  47. 12
      modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpApplicationDescriptor.cs
  48. 31
      modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpApplicationManager.cs
  49. 7
      modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpOpenIddictApplicationStore.cs
  50. 2
      modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/IAbpApplicationManager.cs
  51. 2
      modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/IAbpOpenIdApplicationStore.cs
  52. 9
      modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/OpenIddictApplication.cs
  53. 3
      modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/OpenIddictApplicationExtensions.cs
  54. 9
      modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/OpenIddictApplicationModel.cs
  55. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/ar.json
  56. 2
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/cs.json
  57. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/de.json
  58. 2
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/el.json
  59. 2
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en-GB.json
  60. 3
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json
  61. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/es.json
  62. 2
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fa.json
  63. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fi.json
  64. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fr.json
  65. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/hi.json
  66. 3
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/hr.json
  67. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/hu.json
  68. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/is.json
  69. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/it.json
  70. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/nl.json
  71. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl-PL.json
  72. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pt-BR.json
  73. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/ro-RO.json
  74. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/ru.json
  75. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/sk.json
  76. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/sl.json
  77. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/sv.json
  78. 3
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/tr.json
  79. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/vi.json
  80. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hans.json
  81. 3
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hant.json
  82. 11
      npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.html
  83. 3
      npm/packs/jquery-validation/abp.resourcemapping.js
  84. 27
      npm/packs/jquery-validation/src/abp.jquery.validate.js
  85. 5
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20250707124203_Initial.Designer.cs
  86. 1
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20250707124203_Initial.cs
  87. 3
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/MyProjectNameDbContextModelSnapshot.cs
  88. 5
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20250707124326_Initial.Designer.cs
  89. 1
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20250707124326_Initial.cs
  90. 3
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/MyProjectNameDbContextModelSnapshot.cs
  91. 5
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20250707124118_Initial.Designer.cs
  92. 1
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20250707124118_Initial.cs
  93. 3
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/MyProjectNameDbContextModelSnapshot.cs
  94. 5
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20250707124141_Initial.Designer.cs
  95. 1
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20250707124141_Initial.cs
  96. 3
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/MyProjectNameDbContextModelSnapshot.cs
  97. 5
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20250707124211_Initial.Designer.cs
  98. 1
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20250707124211_Initial.cs
  99. 3
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/MyProjectNameDbContextModelSnapshot.cs
  100. 5
      templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20250707124242_Initial.Designer.cs

4
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json

@ -721,6 +721,8 @@
"NuGetApiKey": "NuGet API key", "NuGetApiKey": "NuGet API key",
"QuestionCount": "Question Count", "QuestionCount": "Question Count",
"MakeAnnouncement": "Make Announcement", "MakeAnnouncement": "Make Announcement",
"MakeAnnouncementInfo": "Check it if you want to make an announcement for this post" "MakeAnnouncementInfo": "Check it if you want to make an announcement for this post",
"Permission:ViewCounts": "View counts",
"ReadCount": "Read Count"
} }
} }

7
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json

@ -534,7 +534,7 @@
"WhenShouldIRenewMyLicenseExplanation2": "{0} for Team Licenses;", "WhenShouldIRenewMyLicenseExplanation2": "{0} for Team Licenses;",
"WhenShouldIRenewMyLicenseExplanation3": "{0} for Business and Enterprise Licenses;", "WhenShouldIRenewMyLicenseExplanation3": "{0} for Business and Enterprise Licenses;",
"WhenShouldIRenewMyLicenseExplanation4": "However, if you renew your license more than {0} days after the expiry date, the renewal price will be the same as the initial purchase price of the license, with no discounts applied to your renewal.", "WhenShouldIRenewMyLicenseExplanation4": "However, if you renew your license more than {0} days after the expiry date, the renewal price will be the same as the initial purchase price of the license, with no discounts applied to your renewal.",
"DoesTheSubscriptionRenewAutomaticallyExplanationAutoRenewal": "ABP Platform allows you to auto-renew your license. This is an optional free service. You can toggle this feature when you purchase a new license or later enable it from your <a href=\"{0}\">organization management page</a>. If you want to turn on or off the auto-renewal, visit the <a href=\"{0}\">organization management page</a>, go to the 'Payments Method' section and either check or uncheck the 'Automatic Renewal' checkbox. When you turn off the auto-renewal feature, it will be your responsibility to renew your license manually.", "DoesTheSubscriptionRenewAutomaticallyExplanationAutoRenewal": "ABP Platform allows you to auto-renew your license. This is an optional free service. You can toggle this feature when you purchase a new license or later enable it from your <a href=\"{0}\">organization management page</a>. If you want to turn on or off the auto-renewal, visit the <a href=\"{0}\">organization management page</a>, go to the 'Payments Method' section and either check or uncheck the 'Automatic Renewal' checkbox. When you turn off the auto-renewal feature, it will be your responsibility to renew your license manually. <br> The <strong>renewals (manual)</strong> are non-refundable. On the other hand, all subscription <strong>auto-renewals</strong> are non-refundable after <strong>10 calendar days</strong> from the auto-renewal date. If you don't wish to continue your license, it is your responsibility to manage the renewal settings and cancel the subscription before the automatic renewal date.",
"TrialPlanExplanation": "Yes, to start your free trial, please contact <a href=\"mailto:sales@volosoft.com?subject=Trial License Request\">sales@volosoft.com</a>. We also offer a 30-day money-back guarantee for the Team license, with no questions asked! You can request a full refund within the first 30 days of purchasing the license. For Business and Enterprise licenses, we provide a 60% refund if requested within 30 days of purchase. This policy is due to the inclusion of the full source code for all modules and themes in the Business and Enterprise licenses.", "TrialPlanExplanation": "Yes, to start your free trial, please contact <a href=\"mailto:sales@volosoft.com?subject=Trial License Request\">sales@volosoft.com</a>. We also offer a 30-day money-back guarantee for the Team license, with no questions asked! You can request a full refund within the first 30 days of purchasing the license. For Business and Enterprise licenses, we provide a 60% refund if requested within 30 days of purchase. This policy is due to the inclusion of the full source code for all modules and themes in the Business and Enterprise licenses.",
"BlazoriseLicenseExplanation": "We have an agreement between Volosoft and Megabit, according to which the Blazorise license is bundled with the ABP Platform’s commercial licenses. Therefore, our paid users do not need to purchase an additional Blazorise license.", "BlazoriseLicenseExplanation": "We have an agreement between Volosoft and Megabit, according to which the Blazorise license is bundled with the ABP Platform’s commercial licenses. Therefore, our paid users do not need to purchase an additional Blazorise license.",
"HowToUpgradeExplanation1": "When you create a new application using the ABP startup templates, all the modules and themes are used as NuGet and NPM packages. This setup allows for easy upgrades to newer versions of the packages.", "HowToUpgradeExplanation1": "When you create a new application using the ABP startup templates, all the modules and themes are used as NuGet and NPM packages. This setup allows for easy upgrades to newer versions of the packages.",
@ -1425,7 +1425,7 @@
"TotalDevelopers": "Total {0} developer(s)", "TotalDevelopers": "Total {0} developer(s)",
"CustomPurchaseExplanation": "Tailored to your specific needs", "CustomPurchaseExplanation": "Tailored to your specific needs",
"WhereDidYouHearAboutUs": "Where did you hear about us?", "WhereDidYouHearAboutUs": "Where did you hear about us?",
"Twitter": "Twitter", "Twitter": "Twitter (X)",
"Facebook": "Facebook", "Facebook": "Facebook",
"Youtube": "YouTube", "Youtube": "YouTube",
"Google": "Google", "Google": "Google",
@ -1891,6 +1891,7 @@
"CreatePostSEOTitleInfo": "SEO URL is a clean, readable, keyword-rich URL that helps both users and search engines understand what this post is about. Keep it short with 60 characters. SEO titles over 60 characters will be truncated. Use hyphens (-) to separate words (not underscores). Include target keywords near the start. Lowercase only. No stop words unless needed (e.g: \"and\", \"or\", \"the\").", "CreatePostSEOTitleInfo": "SEO URL is a clean, readable, keyword-rich URL that helps both users and search engines understand what this post is about. Keep it short with 60 characters. SEO titles over 60 characters will be truncated. Use hyphens (-) to separate words (not underscores). Include target keywords near the start. Lowercase only. No stop words unless needed (e.g: \"and\", \"or\", \"the\").",
"SEOTitle": "SEO URL", "SEOTitle": "SEO URL",
"InvalidYouTubeUrl": "The URL you entered is not a valid YouTube video link. Please make sure it points to a specific video and try again.", "InvalidYouTubeUrl": "The URL you entered is not a valid YouTube video link. Please make sure it points to a specific video and try again.",
"SelectAnOption": "Select an option" "SelectAnOption": "Select an option",
"MostPopular": "Most Popular"
} }
} }

4
common.props

@ -1,8 +1,8 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Version>9.3.0-rc.3</Version> <Version>10.0.0-preview</Version>
<LeptonXVersion>4.3.0-rc.3</LeptonXVersion> <LeptonXVersion>5.0.0-preview</LeptonXVersion>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn> <NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl> <PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io/</PackageProjectUrl> <PackageProjectUrl>https://abp.io/</PackageProjectUrl>

203
docs/en/Blog-Posts/2025-06-18 v9_3_Preview/POST.md

@ -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:
![studio-switch-to-preview.png](studio-switch-to-preview.png)
## 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**:
![PDF generation settings in the admin side](generate-pdf-docs.png)
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:
![Download PDF button in the documentation system](download-pdf-on-docs.png)
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.
![](audit-logs-export-to-excel.png)
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 🚀
![](abp-studio.png)
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"**.
![](community-talk-2025-5.png)
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.
![ABP Bootcamp: Mastering Infrastructure & Features](bootcamp.png)
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!

BIN
docs/en/Blog-Posts/2025-06-18 v9_3_Preview/abp-studio.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

BIN
docs/en/Blog-Posts/2025-06-18 v9_3_Preview/audit-logs-export-to-excel.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

BIN
docs/en/Blog-Posts/2025-06-18 v9_3_Preview/bootcamp.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

BIN
docs/en/Blog-Posts/2025-06-18 v9_3_Preview/community-talk-2025-5.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

BIN
docs/en/Blog-Posts/2025-06-18 v9_3_Preview/cover-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 KiB

BIN
docs/en/Blog-Posts/2025-06-18 v9_3_Preview/download-pdf-on-docs.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
docs/en/Blog-Posts/2025-06-18 v9_3_Preview/generate-pdf-docs.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
docs/en/Blog-Posts/2025-06-18 v9_3_Preview/studio-switch-to-preview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

250
docs/en/Community-Articles/2025-06-20-Using-Hangfire-Dashboard-in-ABP-API-website/POST.md

@ -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.
![Hangfire Dashboard](gif.gif)
## 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)

BIN
docs/en/Community-Articles/2025-06-20-Using-Hangfire-Dashboard-in-ABP-API-website/gif.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 MiB

13
docs/en/framework/architecture/multi-tenancy/index.md

@ -380,6 +380,19 @@ namespace MultiTenancyDemo.Web
* A tenant resolver should set `context.TenantIdOrName` if it can determine it. If not, just leave it as is to allow the next resolver to determine it. * A tenant resolver should set `context.TenantIdOrName` if it can determine it. If not, just leave it as is to allow the next resolver to determine it.
* `context.ServiceProvider` can be used if you need to additional services to resolve from the [dependency injection](../../fundamentals/dependency-injection.md) system. * `context.ServiceProvider` can be used if you need to additional services to resolve from the [dependency injection](../../fundamentals/dependency-injection.md) system.
##### The Fallback Tenant
If you want to always fallback to a tenant (in case of no tenant was found by the tenant resolution logic), you can set the `AbpTenantResolveOptions.FallbackTenant` option:
```csharp
Configure<AbpTenantResolveOptions>(options =>
{
options.FallbackTenant = "acme";
});
```
The `FallbackTenant` value can be a tenant name or tenant's Id. This option can be helpful on development time or some specific scenarios to set a constant tenant for the application. It is a simple and consistent way to ensure that a tenant context is always available when needed. However, when you do that, no way to switch to the host side. It is not something you will need it most of the time, but here if you need such a resolution logic.
#### Multi-Tenancy Middleware #### Multi-Tenancy Middleware
Multi-Tenancy middleware is an ASP.NET Core request pipeline [middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware) that determines the current tenant from the HTTP request and sets the `ICurrentTenant` properties. Multi-Tenancy middleware is an ASP.NET Core request pipeline [middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware) that determines the current tenant from the HTTP request and sets the `ICurrentTenant` properties.

63
docs/en/framework/infrastructure/background-jobs/hangfire.md

@ -159,14 +159,24 @@ app.UseAbpHangfireDashboard("/hangfire", options =>
`AbpHangfireAuthorizationFilter` class has the following fields: `AbpHangfireAuthorizationFilter` class has the following fields:
* **`enableTenant` (`bool`, default: `false`):** Enables/disables accessing the Hangfire dashboard on tenant users. * **`enableTenant` (`bool`, default: `false`):** Enables/disables accessing the Hangfire dashboard on tenant users.
* **`requiredPermissionName` (`string`, default: `null`):** Hangfire dashboard is accessible only if the current user has the specified permission. In this case, if we specify a permission name, we don't need to set `enableTenant` `true` because the permission system already does it. * **`requiredPermissionName` (`string`, default: `null`):** Hangfire dashboard is accessible only if the current user has the specified permission.
* **`requiredRoleNames` (`string[]`, default: `[]`):** Hangfire dashboard is accessible only if the current user has one of the specified roles.
If you want to require an additional permission, you can pass it into the constructor as below: If you want to require more policies, you can use the `PolicyBuilder` property of the `AbpHangfireAuthorizationFilter` class.
```csharp ```csharp
app.UseAbpHangfireDashboard("/hangfire", options => app.UseAbpHangfireDashboard("/hangfire", options =>
{ {
options.AsyncAuthorization = new[] { new AbpHangfireAuthorizationFilter(requiredPermissionName: "MyHangFireDashboardPermissionName") }; var hangfireAuthorizationFilter = new AbpHangfireAuthorizationFilter(requiredPermissionName: "MyHangFireDashboardPermissionName");
//hangfireAuthorizationFilter.PolicyBuilder.AddRequirements(new PermissionRequirement("YourPermissionName"));
//hangfireAuthorizationFilter.PolicyBuilder.RequireRole("YourCustomRole");
//hangfireAuthorizationFilter.PolicyBuilder.Requirements.Add(new YourCustomRequirement());
options.AsyncAuthorization = new[]
{
hangfireAuthorizationFilter
};
}); });
``` ```
@ -190,18 +200,20 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi
options.Authority = configuration["AuthServer:Authority"]; options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata"); options.RequireHttpsMetadata = configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata");
options.Audience = "MyProjectName"; options.Audience = "MyProjectName";
});
context.Services.AddAuthentication() options.ForwardDefaultSelector = httpContext => httpContext.Request.Path.StartsWithSegments("/hangfire", StringComparison.OrdinalIgnoreCase)
.AddCookie("Cookies") ? CookieAuthenticationDefaults.AuthenticationScheme
.AddOpenIdConnect("oidc", options => : null;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddAbpOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{ {
options.Authority = configuration["AuthServer:Authority"]; options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata"); options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
options.ResponseType = OpenIdConnectResponseType.CodeIdToken; options.ResponseType = OpenIdConnectResponseType.Code;
options.ClientId = configuration["AuthServer:ClientId"]; options.ClientId = configuration["AuthServer:HangfireClientId"];
options.ClientSecret = configuration["AuthServer:ClientSecret"]; options.ClientSecret = configuration["AuthServer:HangfireClientSecret"];
options.UsePkce = true; options.UsePkce = true;
options.SaveTokens = true; options.SaveTokens = true;
@ -211,6 +223,8 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi
options.Scope.Add("email"); options.Scope.Add("email");
options.Scope.Add("phone"); options.Scope.Add("phone");
options.Scope.Add("MyProjectName"); options.Scope.Add("MyProjectName");
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}); });
} }
``` ```
@ -218,26 +232,27 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi
```csharp ```csharp
app.Use(async (httpContext, next) => app.Use(async (httpContext, next) =>
{ {
if (httpContext.Request.Path.StartsWithSegments("/hangfire")) if (httpContext.Request.Path.StartsWithSegments("/hangfire", StringComparison.OrdinalIgnoreCase))
{ {
var result = await httpContext.AuthenticateAsync("Cookies"); var authenticateResult = await httpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
if (result.Succeeded) if (!authenticateResult.Succeeded)
{ {
httpContext.User = result.Principal; await httpContext.ChallengeAsync(
await next(httpContext); OpenIdConnectDefaults.AuthenticationScheme,
new AuthenticationProperties
{
RedirectUri = httpContext.Request.Path + httpContext.Request.QueryString
});
return; return;
} }
await httpContext.ChallengeAsync("oidc");
}
else
{
await next(httpContext);
} }
await next.Invoke();
}); });
app.UseAbpHangfireDashboard("/hangfire", options => app.UseAbpHangfireDashboard("/hangfire", options =>
{ {
options.AsyncAuthorization = new[] {new AbpHangfireAuthorizationFilter()}; options.AsyncAuthorization = new[]
{
new AbpHangfireAuthorizationFilter()
};
}); });
``` ```

6
framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo/Abp/AspNetCore/Authentication/OpenIdConnect/AbpAspNetCoreAuthenticationOpenIdConnectModule.cs

@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Authentication.OAuth; using Volo.Abp.AspNetCore.Authentication.OAuth;
using Volo.Abp.AspNetCore.Security;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.RemoteServices; using Volo.Abp.RemoteServices;
@ -16,5 +17,10 @@ public class AbpAspNetCoreAuthenticationOpenIdConnectModule : AbpModule
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
context.Services.AddHttpClient(); context.Services.AddHttpClient();
Configure<AbpSecurityHeadersOptions>(options =>
{
options.IgnoredScriptNoncePaths.Add("/signout-oidc");
});
} }
} }

1
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/BlazorWebAssemblyScriptContributor.cs

@ -10,7 +10,6 @@ public class BlazorWebAssemblyScriptContributor : BundleContributor
context.Files.AddIfNotContains("_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"); context.Files.AddIfNotContains("_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js");
context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/abp.js"); context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/abp.js");
context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/lang-utils.js"); context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/lang-utils.js");
context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/lang-utils.js");
context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/authentication-state-listener.js"); context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/authentication-state-listener.js");
} }
} }

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalFooterTagHelperService.cs

@ -96,7 +96,7 @@ public class AbpModalFooterTagHelperService : AbpTagHelperService<AbpModalFooter
element.Attributes.Add("type", "button"); element.Attributes.Add("type", "button");
element.Attributes.Add("data-bs-dismiss", "modal"); element.Attributes.Add("data-bs-dismiss", "modal");
element.AddCssClass("btn"); element.AddCssClass("btn");
element.AddCssClass("btn-outline-primary"); element.AddCssClass("btn-outline-secondary");
element.InnerHtml.Append(_localizer["Cancel"]); element.InnerHtml.Append(_localizer["Cancel"]);
return element.ToHtmlString(); return element.ToHtmlString();

12
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationExtensions.cs

@ -23,6 +23,18 @@ public static class BundleConfigurationExtensions
return bundleConfiguration; return bundleConfiguration;
} }
public static BundleConfiguration RemoveFiles(this BundleConfiguration bundleConfiguration, params string[] files)
{
bundleConfiguration.Contributors.RemoveBundleFile(files.ToArray());
return bundleConfiguration;
}
public static BundleConfiguration RemoveFiles(this BundleConfiguration bundleConfiguration, Func<string, bool> predicate)
{
bundleConfiguration.Contributors.RemoveBundleFile(predicate);
return bundleConfiguration;
}
public static BundleConfiguration AddContributors(this BundleConfiguration bundleConfiguration, params IBundleContributor[] contributors) public static BundleConfiguration AddContributors(this BundleConfiguration bundleConfiguration, params IBundleContributor[] contributors)
{ {
Check.NotNull(contributors, nameof(contributors)); Check.NotNull(contributors, nameof(contributors));

30
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollection.cs

@ -76,6 +76,36 @@ public class BundleContributorCollection
} }
} }
public void RemoveBundleFile(string fileName)
{
RemoveBundleFile([fileName]);
}
public void RemoveBundleFile(string[] fileNames)
{
var contributors = _contributors
.Where(x => x is BundleFileContributor bundleContributor &&
bundleContributor.Files.Any(f => fileNames.Any(name => name.Equals(f.FileName, StringComparison.OrdinalIgnoreCase))))
.Cast<BundleFileContributor>();
foreach (var contributor in contributors)
{
contributor.Files.RemoveAll(x => fileNames.Any(name => name.Equals(x.FileName, StringComparison.OrdinalIgnoreCase)));
}
}
public void RemoveBundleFile(Func<string, bool> predicate)
{
var contributors = _contributors
.Where(x => x is BundleFileContributor bundleContributor &&
bundleContributor.Files.Any(f => predicate(f.FileName)))
.Cast<BundleFileContributor>();
foreach (var contributor in contributors)
{
contributor.Files.RemoveAll(x => predicate(x.FileName));
}
}
public IReadOnlyList<IBundleContributor> GetAll() public IReadOnlyList<IBundleContributor> GetAll()
{ {
return _contributors.ToImmutableList(); return _contributors.ToImmutableList();

15
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs

@ -1,4 +1,7 @@
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; using System;
using System.Linq;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
public static class BundleContributorCollectionExtensions public static class BundleContributorCollectionExtensions
{ {
@ -16,4 +19,14 @@ public static class BundleContributorCollectionExtensions
{ {
contributors.Add(new BundleFileContributor(files)); contributors.Add(new BundleFileContributor(files));
} }
public static void RemoveFile(this BundleContributorCollection contributors, params string[] files)
{
contributors.RemoveBundleFile(files.ToArray());
}
public static void RemoveFile(this BundleContributorCollection contributors, Func<string, bool> predicate)
{
contributors.RemoveBundleFile(predicate);
}
} }

4
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JQueryValidation/JQueryValidationScriptContributor.cs

@ -15,6 +15,10 @@ public class JQueryValidationScriptContributor : BundleContributor
public override void ConfigureBundle(BundleConfigurationContext context) public override void ConfigureBundle(BundleConfigurationContext context)
{ {
context.Files.AddIfNotContains("/libs/jquery-validation/jquery.validate.js"); context.Files.AddIfNotContains("/libs/jquery-validation/jquery.validate.js");
if (context.FileProvider.GetFileInfo("/libs/jquery-validation/abp.jquery.validate.js").Exists)
{
context.Files.AddIfNotContains("/libs/jquery-validation/abp.jquery.validate.js");
}
} }
public override void ConfigureDynamicResources(BundleConfigurationContext context) public override void ConfigureDynamicResources(BundleConfigurationContext context)

158
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationPartSorter.cs

@ -1,6 +1,4 @@
using System; using System.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -15,152 +13,36 @@ public static class ApplicationPartSorter
{ {
public static void Sort(ApplicationPartManager partManager, IModuleContainer moduleContainer) public static void Sort(ApplicationPartManager partManager, IModuleContainer moduleContainer)
{ {
/* Performing a double Reverse() to preserve the original order for non-sorted parts var orderedModuleAssemblies = moduleContainer.Modules
*/ .Select((moduleDescriptor, index) => new { moduleDescriptor.Assembly, index })
.ToDictionary(x => x.Assembly, x => x.index);
var dependencyDictionary = CreateDependencyDictionary(partManager, moduleContainer); var modulesAssemblies = moduleContainer.Modules.Select(x => x.Assembly).ToList();
var sortedTypes = partManager.ApplicationParts
.Where(x => modulesAssemblies.Contains(GetApplicationPartAssembly(x)))
.OrderBy(x => orderedModuleAssemblies[GetApplicationPartAssembly(x)])
.ToList();
var sortedParts = partManager var sortIndex = 0;
.ApplicationParts var sortedParts = partManager.ApplicationParts
.Reverse() //First Revers .Select(x => modulesAssemblies.Contains(GetApplicationPartAssembly(x)) ? sortedTypes[sortIndex++] : x)
.SortByDependencies(p => dependencyDictionary[p]); .ToList();
sortedParts.Reverse(); //Reverse again
//Replace the original parts with the sorted parts
partManager.ApplicationParts.Clear(); partManager.ApplicationParts.Clear();
sortedParts.Reverse();
foreach (var applicationPart in sortedParts) foreach (var applicationPart in sortedParts)
{ {
partManager.ApplicationParts.Add(applicationPart); partManager.ApplicationParts.Add(applicationPart);
} }
} }
private static Dictionary<ApplicationPart, List<ApplicationPart>> CreateDependencyDictionary( private static Assembly GetApplicationPartAssembly(ApplicationPart part)
ApplicationPartManager partManager, IModuleContainer moduleContainer)
{
var dependencyDictionary = new Dictionary<ApplicationPart, List<ApplicationPart>>();
foreach (var applicationPart in partManager.ApplicationParts)
{
dependencyDictionary[applicationPart] =
CreateDependencyList(applicationPart, partManager, moduleContainer);
}
return dependencyDictionary;
}
private static List<ApplicationPart> CreateDependencyList(
ApplicationPart applicationPart,
ApplicationPartManager partManager,
IModuleContainer moduleContainer)
{ {
var list = new List<ApplicationPart>(); return part switch
if (applicationPart is AssemblyPart assemblyPart)
{ {
AddDependencies(list, assemblyPart, partManager, moduleContainer); AssemblyPart assemblyPart => assemblyPart.Assembly,
} CompiledRazorAssemblyPart compiledRazorAssemblyPart => compiledRazorAssemblyPart.Assembly,
else if (applicationPart is CompiledRazorAssemblyPart compiledRazorAssemblyPart) _ => throw new AbpException("Unknown application part type")
{ };
AddDependencies(list, compiledRazorAssemblyPart, partManager, moduleContainer);
}
return list;
}
private static void AddDependencies(
List<ApplicationPart> list,
AssemblyPart assemblyPart,
ApplicationPartManager partManager,
IModuleContainer moduleContainer)
{
var dependedAssemblyParts = GetDependedAssemblyParts(
partManager,
moduleContainer,
assemblyPart
);
list.AddRange(dependedAssemblyParts);
foreach (var dependedAssemblyPart in dependedAssemblyParts)
{
var viewsPart = GetViewsPartOrNull(partManager, dependedAssemblyPart);
if (viewsPart != null)
{
list.Add(viewsPart);
}
}
}
private static void AddDependencies(
List<ApplicationPart> list,
CompiledRazorAssemblyPart compiledRazorAssemblyPart,
ApplicationPartManager partManager,
IModuleContainer moduleContainer)
{
if (!compiledRazorAssemblyPart.Name.EndsWith(".Views"))
{
return;
}
var originalAssemblyPart = GetOriginalAssemblyPartOrNull(compiledRazorAssemblyPart, partManager);
if (originalAssemblyPart == null)
{
return;
}
list.Add(originalAssemblyPart);
}
private static AssemblyPart[] GetDependedAssemblyParts(
ApplicationPartManager partManager,
IModuleContainer moduleContainer,
AssemblyPart assemblyPart)
{
var moduleDescriptor = GetModuleDescriptorForAssemblyOrNull(moduleContainer, assemblyPart.Assembly);
if (moduleDescriptor == null)
{
return Array.Empty<AssemblyPart>();
}
var moduleDependedAssemblies = moduleDescriptor
.Dependencies
.SelectMany(d => d.AllAssemblies)
.ToArray();
return partManager.ApplicationParts
.OfType<AssemblyPart>()
.Where(a => a.Assembly.IsIn(moduleDependedAssemblies))
.Distinct()
.ToArray();
}
private static CompiledRazorAssemblyPart? GetViewsPartOrNull(ApplicationPartManager partManager,
ApplicationPart assemblyPart)
{
var viewsAssemblyName = assemblyPart.Name + ".Views";
return partManager
.ApplicationParts
.OfType<CompiledRazorAssemblyPart>()
.FirstOrDefault(p => p.Name == viewsAssemblyName);
}
private static AssemblyPart? GetOriginalAssemblyPartOrNull(
CompiledRazorAssemblyPart compiledRazorAssemblyPart,
ApplicationPartManager partManager)
{
var originalAssemblyName = compiledRazorAssemblyPart.Name.RemovePostFix(".Views");
return partManager.ApplicationParts
.OfType<AssemblyPart>()
.FirstOrDefault(p => p.Name == originalAssemblyName);
}
private static IAbpModuleDescriptor? GetModuleDescriptorForAssemblyOrNull(
IModuleContainer moduleContainer,
Assembly assembly)
{
return moduleContainer
.Modules
.FirstOrDefault(m => m.AllAssemblies.Contains(assembly));
} }
} }

13
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs

@ -25,7 +25,9 @@ public class AbpSecurityHeadersMiddleware : AbpMiddlewareBase, ITransientDepende
{ {
var endpoint = context.GetEndpoint(); var endpoint = context.GetEndpoint();
if (endpoint?.Metadata.GetMetadata<IgnoreAbpSecurityHeaderAttribute>() != null) if (endpoint?.Metadata.GetMetadata<IgnoreAbpSecurityHeaderAttribute>() != null ||
await AlwaysIgnoreContentTypes(context) ||
Options.Value.IgnoredScriptNoncePaths.Any(x => context.Request.Path.StartsWithSegments(x.EnsureStartsWith('/'), StringComparison.OrdinalIgnoreCase)))
{ {
await next.Invoke(context); await next.Invoke(context);
return; return;
@ -41,13 +43,13 @@ public class AbpSecurityHeadersMiddleware : AbpMiddlewareBase, ITransientDepende
AddHeader(context, "X-Frame-Options", "SAMEORIGIN"); AddHeader(context, "X-Frame-Options", "SAMEORIGIN");
var requestAcceptTypeHtml = context.Request.Headers["Accept"].Any(x => var requestAcceptTypeHtml = context.Request.Headers["Accept"].Any(x =>
x!.Contains("text/html") || x.Contains("*/*") || x.Contains("application/xhtml+xml")); x!.Contains("text/html", StringComparison.OrdinalIgnoreCase) ||
x.Contains("*/*", StringComparison.OrdinalIgnoreCase) ||
x.Contains("application/xhtml+xml", StringComparison.OrdinalIgnoreCase));
if (!requestAcceptTypeHtml if (!requestAcceptTypeHtml
|| !Options.Value.UseContentSecurityPolicyHeader || !Options.Value.UseContentSecurityPolicyHeader
|| await AlwaysIgnoreContentTypes(context) || endpoint == null)
|| endpoint == null
|| Options.Value.IgnoredScriptNoncePaths.Any(x => context.Request.Path.StartsWithSegments(x.EnsureStartsWith('/'), StringComparison.OrdinalIgnoreCase)))
{ {
AddOtherHeaders(context); AddOtherHeaders(context);
await next.Invoke(context); await next.Invoke(context);
@ -60,7 +62,6 @@ public class AbpSecurityHeadersMiddleware : AbpMiddlewareBase, ITransientDepende
context.Items.Add(AbpAspNetCoreConsts.ScriptNonceKey, randomValue); context.Items.Add(AbpAspNetCoreConsts.ScriptNonceKey, randomValue);
} }
context.Response.OnStarting(() => context.Response.OnStarting(() =>
{ {
if (context.Response.Headers.ContainsKey("Content-Security-Policy")) if (context.Response.Headers.ContainsKey("Content-Security-Policy"))

12
framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AbpBackgroundWorkersModule.cs

@ -1,5 +1,7 @@
using System.Threading.Tasks; using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.Threading; using Volo.Abp.Threading;
@ -16,9 +18,11 @@ public class AbpBackgroundWorkersModule : AbpModule
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkerOptions>>().Value; var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkerOptions>>().Value;
if (options.IsEnabled) if (options.IsEnabled)
{ {
var hostApplicationLifetime = context.ServiceProvider.GetService<IHostApplicationLifetime>();
var cancellationToken = hostApplicationLifetime?.ApplicationStopping ?? CancellationToken.None;
await context.ServiceProvider await context.ServiceProvider
.GetRequiredService<IBackgroundWorkerManager>() .GetRequiredService<IBackgroundWorkerManager>()
.StartAsync(); .StartAsync(cancellationToken);
} }
} }
@ -27,9 +31,11 @@ public class AbpBackgroundWorkersModule : AbpModule
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkerOptions>>().Value; var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkerOptions>>().Value;
if (options.IsEnabled) if (options.IsEnabled)
{ {
var hostApplicationLifetime = context.ServiceProvider.GetService<IHostApplicationLifetime>();
var cancellationToken = hostApplicationLifetime?.ApplicationStopping ?? CancellationToken.None;
await context.ServiceProvider await context.ServiceProvider
.GetRequiredService<IBackgroundWorkerManager>() .GetRequiredService<IBackgroundWorkerManager>()
.StopAsync(); .StopAsync(cancellationToken);
} }
} }

10
framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkersApplicationInitializationContextExtensions.cs

@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Volo.Abp.BackgroundWorkers; namespace Volo.Abp.BackgroundWorkers;
@ -28,6 +29,15 @@ public static class BackgroundWorkersApplicationInitializationContextExtensions
throw new AbpException($"Given type ({workerType.AssemblyQualifiedName}) must implement the {typeof(IBackgroundWorker).AssemblyQualifiedName} interface, but it doesn't!"); throw new AbpException($"Given type ({workerType.AssemblyQualifiedName}) must implement the {typeof(IBackgroundWorker).AssemblyQualifiedName} interface, but it doesn't!");
} }
if (cancellationToken == default)
{
var hostApplicationLifetime = context.ServiceProvider.GetService<IHostApplicationLifetime>();
if (hostApplicationLifetime != null)
{
cancellationToken = hostApplicationLifetime.ApplicationStopping;
}
}
await context.ServiceProvider await context.ServiceProvider
.GetRequiredService<IBackgroundWorkerManager>() .GetRequiredService<IBackgroundWorkerManager>()
.AddAsync((IBackgroundWorker)context.ServiceProvider.GetRequiredService(workerType), cancellationToken); .AddAsync((IBackgroundWorker)context.ServiceProvider.GetRequiredService(workerType), cancellationToken);

16
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Cli.Args; using Volo.Abp.Cli.Args;
using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Cli.Commands; namespace Volo.Abp.Cli.Commands;
@ -13,11 +14,14 @@ namespace Volo.Abp.Cli.Commands;
public class CleanCommand : IConsoleCommand, ITransientDependency public class CleanCommand : IConsoleCommand, ITransientDependency
{ {
public const string Name = "clean"; public const string Name = "clean";
public ILogger<CleanCommand> Logger { get; set; } public ILogger<CleanCommand> Logger { get; set; }
public CleanCommand() protected ICmdHelper CmdHelper { get; }
public CleanCommand(ICmdHelper cmdHelper)
{ {
CmdHelper = cmdHelper;
Logger = NullLogger<CleanCommand>.Instance; Logger = NullLogger<CleanCommand>.Instance;
} }
@ -26,6 +30,10 @@ public class CleanCommand : IConsoleCommand, ITransientDependency
var binEntries = Directory.EnumerateDirectories(Directory.GetCurrentDirectory(), "bin", SearchOption.AllDirectories); var binEntries = Directory.EnumerateDirectories(Directory.GetCurrentDirectory(), "bin", SearchOption.AllDirectories);
var objEntries = Directory.EnumerateDirectories(Directory.GetCurrentDirectory(), "obj", SearchOption.AllDirectories); var objEntries = Directory.EnumerateDirectories(Directory.GetCurrentDirectory(), "obj", SearchOption.AllDirectories);
Logger.LogInformation("Cleaning the solution with 'dotnet clean' command...");
CmdHelper.RunCmd($"dotnet clean", workingDirectory: Directory.GetCurrentDirectory());
Logger.LogInformation($"Removing 'bin' and 'obj' folders...");
foreach (var path in binEntries.Concat(objEntries)) foreach (var path in binEntries.Concat(objEntries))
{ {
if (path.IndexOf("node_modules", StringComparison.OrdinalIgnoreCase) > 0) if (path.IndexOf("node_modules", StringComparison.OrdinalIgnoreCase) > 0)
@ -38,9 +46,9 @@ public class CleanCommand : IConsoleCommand, ITransientDependency
Directory.Delete(path, true); Directory.Delete(path, true);
} }
} }
Logger.LogInformation($"'bin' and 'obj' folders removed successfully!");
Logger.LogInformation($"BIN and OBJ folders have been successfully deleted!"); Logger.LogInformation("Solution cleaned successfully!");
return Task.CompletedTask; return Task.CompletedTask;
} }

50
framework/src/Volo.Abp.HangFire/Volo/Abp/Hangfire/AbpHangfireAuthorizationFilter.cs

@ -1,53 +1,49 @@
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Hangfire.Dashboard; using Hangfire.Dashboard;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Authorization.Permissions; using Volo.Abp.Authorization;
using Volo.Abp.Users; using Volo.Abp.MultiTenancy;
namespace Volo.Abp.Hangfire; namespace Volo.Abp.Hangfire;
public class AbpHangfireAuthorizationFilter : IDashboardAsyncAuthorizationFilter public class AbpHangfireAuthorizationFilter : IDashboardAsyncAuthorizationFilter
{ {
private readonly bool _enableTenant; private readonly bool _enableTenant;
private readonly string? _requiredPermissionName; private readonly AuthorizationPolicyBuilder _policyBuilder;
public AbpHangfireAuthorizationFilter(bool enableTenant = false, string? requiredPermissionName = null) public virtual AuthorizationPolicyBuilder PolicyBuilder => _policyBuilder;
{
_enableTenant = requiredPermissionName.IsNullOrWhiteSpace() ? enableTenant : true;
_requiredPermissionName = requiredPermissionName;
}
public async Task<bool> AuthorizeAsync(DashboardContext context) public AbpHangfireAuthorizationFilter(bool enableTenant = false, string? requiredPermissionName = null, params string[]? requiredRoleNames)
{ {
if (!IsLoggedIn(context, _enableTenant)) _enableTenant = enableTenant;
_policyBuilder = new AuthorizationPolicyBuilder().RequireAuthenticatedUser();
if (!requiredPermissionName.IsNullOrWhiteSpace())
{ {
return false; _policyBuilder.Requirements.Add(new PermissionRequirement(requiredPermissionName));
} }
if (_requiredPermissionName.IsNullOrEmpty()) if (!requiredRoleNames.IsNullOrEmpty())
{ {
return true; foreach (var roleName in requiredRoleNames!)
{
_policyBuilder.RequireRole(roleName);
}
} }
return await IsPermissionGrantedAsync(context, _requiredPermissionName!);
} }
private static bool IsLoggedIn(DashboardContext context, bool enableTenant) public virtual async Task<bool> AuthorizeAsync(DashboardContext context)
{ {
var currentUser = context.GetHttpContext().RequestServices.GetRequiredService<ICurrentUser>(); var currentTenant = context.GetHttpContext().RequestServices.GetRequiredService<ICurrentTenant>();
if (currentTenant.IsAvailable && !_enableTenant)
if (!enableTenant)
{ {
return currentUser.IsAuthenticated && !currentUser.TenantId.HasValue; return false;
} }
return currentUser.IsAuthenticated; var authorizationService = context.GetHttpContext().RequestServices.GetRequiredService<IAuthorizationService>();
} var authorizationPolicy = _policyBuilder.Build();
return (await authorizationService.AuthorizeAsync(context.GetHttpContext().User, authorizationPolicy)).Succeeded;
private static async Task<bool> IsPermissionGrantedAsync(DashboardContext context, string requiredPermissionName)
{
var permissionChecker = context.GetHttpContext().RequestServices.GetRequiredService<IPermissionChecker>();
return await permissionChecker.IsGrantedAsync(requiredPermissionName);
} }
} }

5
framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpTenantResolveOptions.cs

@ -8,6 +8,11 @@ public class AbpTenantResolveOptions
[NotNull] [NotNull]
public List<ITenantResolveContributor> TenantResolvers { get; } public List<ITenantResolveContributor> TenantResolvers { get; }
/// <summary>
/// Fallback tenant to use when no other resolver resolves a tenant.
/// </summary>
public string? FallbackTenant { get; set; }
public AbpTenantResolveOptions() public AbpTenantResolveOptions()
{ {
TenantResolvers = new List<ITenantResolveContributor>(); TenantResolvers = new List<ITenantResolveContributor>();

10
framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolverNames.cs

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Volo.Abp.MultiTenancy;
public static class TenantResolverNames
{
public const string FallbackTenant = "FallbackTenant";
}

6
framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantResolver.cs

@ -39,6 +39,12 @@ public class TenantResolver : ITenantResolver, ITransientDependency
} }
} }
if (result.TenantIdOrName.IsNullOrEmpty() && !string.IsNullOrWhiteSpace(_options.FallbackTenant))
{
result.TenantIdOrName = _options.FallbackTenant;
result.AppliedResolvers.Add(TenantResolverNames.FallbackTenant);
}
return result; return result;
} }
} }

1
framework/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/MultiTenancy/AspNetCoreMultiTenancy_Without_DomainResolver_Tests.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;

79
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ApplicationPart/ApplicationPartSorter_Tests.cs

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using NSubstitute;
using Shouldly;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationPart;
public class ApplicationPartSorter_Tests
{
[Fact]
public void Should_Sort_ApplicationParts_By_Module_Dependencies()
{
var moduleDescriptors = new List<IAbpModuleDescriptor>();
var partManager = new ApplicationPartManager();
for (var i = 0; i < 10; i++)
{
var assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"ModuleA{i}.dll"), AssemblyBuilderAccess.Run);
partManager.ApplicationParts.Add(new AssemblyPart(assembly));
moduleDescriptors.Add(CreateModuleDescriptor(assembly));
}
var randomApplicationParts = partManager.ApplicationParts.OrderBy(x => Guid.NewGuid()).ToList(); // Shuffle the parts
// Additional part
randomApplicationParts.AddFirst(new CompiledRazorAssemblyPart(typeof(AbpAspNetCoreModule).Assembly));
randomApplicationParts.Insert(5, new CompiledRazorAssemblyPart(typeof(AbpAspNetCoreMvcModule).Assembly));
randomApplicationParts.AddLast(new CompiledRazorAssemblyPart(typeof(AbpVirtualFileSystemModule).Assembly));
partManager.ApplicationParts.Clear();
foreach (var part in randomApplicationParts)
{
partManager.ApplicationParts.Add(part);
}
var moduleContainer = CreateFakeModuleContainer(moduleDescriptors);
ApplicationPartSorter.Sort(partManager, moduleContainer);
// Act
partManager.ApplicationParts.Count.ShouldBe(13); // 10 modules + 3 additional parts
var applicationParts = partManager.ApplicationParts.Reverse().ToList(); // Reverse the order to match the expected output
applicationParts[0].ShouldBeOfType<CompiledRazorAssemblyPart>().Assembly.ShouldBe(typeof(AbpAspNetCoreModule).Assembly);
applicationParts[1].ShouldBeOfType<AssemblyPart>().Assembly.GetName().Name.ShouldStartWith("ModuleA0");
applicationParts[2].ShouldBeOfType<AssemblyPart>().Assembly.GetName().Name.ShouldStartWith("ModuleA1");
applicationParts[3].ShouldBeOfType<AssemblyPart>().Assembly.GetName().Name.ShouldStartWith("ModuleA2");
applicationParts[4].ShouldBeOfType<AssemblyPart>().Assembly.GetName().Name.ShouldStartWith("ModuleA3");
applicationParts[5].ShouldBeOfType<CompiledRazorAssemblyPart>().Assembly.ShouldBe(typeof(AbpAspNetCoreMvcModule).Assembly);
applicationParts[6].ShouldBeOfType<AssemblyPart>().Assembly.GetName().Name.ShouldStartWith("ModuleA4");
applicationParts[7].ShouldBeOfType<AssemblyPart>().Assembly.GetName().Name.ShouldStartWith("ModuleA5");
applicationParts[8].ShouldBeOfType<AssemblyPart>().Assembly.GetName().Name.ShouldStartWith("ModuleA6");
applicationParts[9].ShouldBeOfType<AssemblyPart>().Assembly.GetName().Name.ShouldStartWith("ModuleA7");
applicationParts[10].ShouldBeOfType<AssemblyPart>().Assembly.GetName().Name.ShouldStartWith("ModuleA8");
applicationParts[11].ShouldBeOfType<AssemblyPart>().Assembly.GetName().Name.ShouldStartWith("ModuleA9");
applicationParts[12].ShouldBeOfType<CompiledRazorAssemblyPart>().Assembly.ShouldBe(typeof(AbpVirtualFileSystemModule).Assembly);
}
private static IModuleContainer CreateFakeModuleContainer(List<IAbpModuleDescriptor> moduleDescriptors)
{
var fakeModuleContainer = Substitute.For<IModuleContainer>();
fakeModuleContainer.Modules.Returns(moduleDescriptors);
return fakeModuleContainer;
}
private static IAbpModuleDescriptor CreateModuleDescriptor(Assembly assembly)
{
var moduleDescriptor = Substitute.For<IAbpModuleDescriptor>();
moduleDescriptor.Assembly.Returns(assembly);
return moduleDescriptor;
}
}

8
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController.cs

@ -2,10 +2,18 @@
namespace Volo.Abp.AspNetCore.Mvc.Security.Headers; namespace Volo.Abp.AspNetCore.Mvc.Security.Headers;
[Route("SecurityHeadersTest")]
public class SecurityHeadersTestController : AbpController public class SecurityHeadersTestController : AbpController
{ {
[HttpGet("Get")]
public ActionResult Get() public ActionResult Get()
{ {
return Content("OK"); return Content("OK");
} }
[HttpGet("ignored")]
public ActionResult Get_Ignored()
{
return Content("OK");
}
} }

14
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController_Tests.cs

@ -15,6 +15,8 @@ public class SecurityHeadersTestController_Tests : AspNetCoreMvcTestBase
{ {
options.UseContentSecurityPolicyHeader = true; options.UseContentSecurityPolicyHeader = true;
options.Headers["Referrer-Policy"] = "no-referrer"; options.Headers["Referrer-Policy"] = "no-referrer";
options.IgnoredScriptNoncePaths.Add("/SecurityHeadersTest/ignored");
}); });
base.ConfigureServices(services); base.ConfigureServices(services);
@ -27,7 +29,6 @@ public class SecurityHeadersTestController_Tests : AspNetCoreMvcTestBase
responseMessage.Headers.ShouldContain(x => x.Key == "X-Content-Type-Options" & x.Value.First().ToString() == "nosniff"); responseMessage.Headers.ShouldContain(x => x.Key == "X-Content-Type-Options" & x.Value.First().ToString() == "nosniff");
responseMessage.Headers.ShouldContain(x => x.Key == "X-XSS-Protection" & x.Value.First().ToString() == "1; mode=block"); responseMessage.Headers.ShouldContain(x => x.Key == "X-XSS-Protection" & x.Value.First().ToString() == "1; mode=block");
responseMessage.Headers.ShouldContain(x => x.Key == "X-Frame-Options" & x.Value.First().ToString() == "SAMEORIGIN"); responseMessage.Headers.ShouldContain(x => x.Key == "X-Frame-Options" & x.Value.First().ToString() == "SAMEORIGIN");
responseMessage.Headers.ShouldContain(x => x.Key == "X-Content-Type-Options" & x.Value.First().ToString() == "nosniff");
} }
[Fact] [Fact]
@ -37,4 +38,15 @@ public class SecurityHeadersTestController_Tests : AspNetCoreMvcTestBase
responseMessage.Headers.ShouldNotBeEmpty(); responseMessage.Headers.ShouldNotBeEmpty();
responseMessage.Headers.ShouldContain(x => x.Key == "Referrer-Policy" && x.Value.First().ToString() == "no-referrer"); responseMessage.Headers.ShouldContain(x => x.Key == "Referrer-Policy" && x.Value.First().ToString() == "no-referrer");
} }
[Fact]
public async Task SecurityHeaders_Should_Be_Ignored()
{
var responseMessage = await GetResponseAsync("/SecurityHeadersTest/ignored");
responseMessage.Headers.ShouldNotBeEmpty();
responseMessage.Headers.ShouldNotContain(x => x.Key == "X-Content-Type-Options" & x.Value.First().ToString() == "nosniff");
responseMessage.Headers.ShouldNotContain(x => x.Key == "X-XSS-Protection" & x.Value.First().ToString() == "1; mode=block");
responseMessage.Headers.ShouldNotContain(x => x.Key == "X-Frame-Options" & x.Value.First().ToString() == "SAMEORIGIN");
responseMessage.Headers.ShouldNotContain(x => x.Key == "Referrer-Policy" && x.Value.First().ToString() == "no-referrer");
}
} }

87
framework/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/FallbackTenantResolveContributor_Tests.cs

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Shouldly;
using Volo.Abp.MultiTenancy.ConfigurationStore;
using Xunit;
namespace Volo.Abp.MultiTenancy;
public class FallbackTenantResolveContributor_Tests : MultiTenancyTestBase
{
private readonly Guid _testTenantId = Guid.NewGuid();
private readonly string _testTenantName = "acme";
private readonly string _testTenantNormalizedName = "ACME";
private readonly AbpTenantResolveOptions _options;
private readonly ITenantResolver _tenantResolver;
public FallbackTenantResolveContributor_Tests()
{
_options = ServiceProvider.GetRequiredService<IOptions<AbpTenantResolveOptions>>().Value;
_tenantResolver = ServiceProvider.GetRequiredService<ITenantResolver>();
}
protected override void BeforeAddApplication(IServiceCollection services)
{
services.Configure<AbpDefaultTenantStoreOptions>(options =>
{
options.Tenants = new[]
{
new TenantConfiguration(_testTenantId, _testTenantName, _testTenantNormalizedName)
};
});
services.Configure<AbpTenantResolveOptions>(options =>
{
options.FallbackTenant = _testTenantName;
});
}
[Fact]
public async Task Should_Resolve_To_Fallback_Tenant_If_No_Other_Contributor_Succeeds()
{
var result = await _tenantResolver.ResolveTenantIdOrNameAsync();
result.TenantIdOrName.ShouldBe(_testTenantName);
result.AppliedResolvers.ShouldContain(TenantResolverNames.FallbackTenant);
}
[Fact]
public async Task Should_Not_Override_Resolved_Tenant()
{
// Arrange
var customTenantName = "resolved-tenant";
_options.TenantResolvers.Insert(0, new TestTenantResolveContributor(customTenantName));
// Act
var result = await _tenantResolver.ResolveTenantIdOrNameAsync();
// Assert
result.TenantIdOrName.ShouldBe(customTenantName);
result.AppliedResolvers.First().ShouldBe("Test");
result.AppliedResolvers.ShouldNotContain(TenantResolverNames.FallbackTenant);
}
public class TestTenantResolveContributor : TenantResolveContributorBase
{
private readonly string _tenant;
public TestTenantResolveContributor(string tenant)
{
_tenant = tenant;
}
public override string Name => "Test";
public override Task ResolveAsync(ITenantResolveContext context)
{
context.TenantIdOrName = _tenant;
return Task.CompletedTask;
}
}
}

9
latest-versions.json

@ -1,4 +1,13 @@
[ [
{
"version": "9.2.1",
"releaseDate": "",
"type": "stable",
"message": "",
"leptonx": {
"version": "4.2.1"
}
},
{ {
"version": "9.2.0", "version": "9.2.0",
"releaseDate": "", "releaseDate": "",

20
modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.css

@ -10,7 +10,7 @@ body a {
text-decoration: none; text-decoration: none;
} }
body .btn-primary { body .btn-primary {
background-color: #b84297 !important; background-color: #E83090 !important;
font-size: 12px; font-size: 12px;
} }
body .for-mobile { body .for-mobile {
@ -298,7 +298,7 @@ body.scrolledMore .alert-criteria p.alert-p {
} }
.docs-page .docs-sidebar .docs-tree-list ul li.selected-tree > span .fa { .docs-page .docs-sidebar .docs-tree-list ul li.selected-tree > span .fa {
transform: rotate(90deg); transform: rotate(90deg);
color: #b84297; color: #E83090;
} }
.docs-page .docs-sidebar .docs-tree-list ul li.selected-tree.last-link > span .fa { .docs-page .docs-sidebar .docs-tree-list ul li.selected-tree.last-link > span .fa {
transform: rotate(0deg); transform: rotate(0deg);
@ -482,7 +482,7 @@ body.scrolledMore .alert-criteria p.alert-p {
margin-bottom: 1rem; margin-bottom: 1rem;
margin-left: 0; margin-left: 0;
padding: 1em 1.5em; padding: 1em 1.5em;
background-color: #e3edf2; background-color: rgb(227, 237, 242);
font-size: 1em; font-size: 1em;
border-radius: 12px; border-radius: 12px;
color: #385766; color: #385766;
@ -549,7 +549,7 @@ body.scrolledMore .alert-criteria p.alert-p {
background-color: #f4f6fa; background-color: #f4f6fa;
border-color: #f4f6fa; border-color: #f4f6fa;
border-radius: 12px; border-radius: 12px;
background: rgba(190, 223, 238, 0.82); background: hsla(199, 59%, 84%, 0.82);
-webkit-backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
z-index: 3; z-index: 3;
@ -600,16 +600,16 @@ body.scrolledMore .alert-criteria p.alert-p {
font-weight: normal; font-weight: normal;
} }
.docs-page .docs-page-index .docs-inner-anchors .navbar .nav-pills .nav-link.active { .docs-page .docs-page-index .docs-inner-anchors .navbar .nav-pills .nav-link.active {
border-left: 1px solid #b84297; border-left: 1px solid #E83090;
background: none; background: none;
color: #b84297; color: #E83090;
font-weight: normal; font-weight: normal;
} }
.docs-page .docs-page-index .docs-inner-anchors .navbar .nav-pills .nav-pills .nav-link.active { .docs-page .docs-page-index .docs-inner-anchors .navbar .nav-pills .nav-pills .nav-link.active {
color: #b84297; color: #E83090;
} }
.docs-page .docs-page-index .docs-inner-anchors .navbar .nav-pills .nav-pills .nav-pills .nav-link.active { .docs-page .docs-page-index .docs-inner-anchors .navbar .nav-pills .nav-pills .nav-pills .nav-link.active {
color: #b84297; color: #E83090;
} }
.docs-page .docs-page-index .docs-inner-anchors .index-scroll { .docs-page .docs-page-index .docs-inner-anchors .index-scroll {
margin-left: -30px; margin-left: -30px;
@ -667,7 +667,7 @@ body.scrolledMore .alert-criteria p.alert-p {
display: none; display: none;
} }
body .close-mmenu, body .close-mmenu,
body .close-dmenu { body .close-dmenu {
position: absolute; position: absolute;
top: -78px; top: -78px;
left: 25px; left: 25px;
@ -720,7 +720,7 @@ body .close-dmenu {
display: none; display: none;
} }
.docs-page .docs-sidebar .docs-top .navbar.navbar-logo .navbar-collapse { .docs-page .docs-sidebar .docs-top .navbar.navbar-logo .navbar-collapse {
background: #38003d; background: rgb(56, 0, 61);
position: fixed; position: fixed;
top: 86px; top: 86px;
left: 0; left: 0;

12
modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.scss

@ -16,7 +16,7 @@ body {
} }
.btn-primary { .btn-primary {
background-color: #b84297 !important; background-color: #E83090 !important;
font-size: 12px; font-size: 12px;
} }
@ -368,7 +368,7 @@ body {
> span { > span {
.fa { .fa {
transform: rotate(90deg); transform: rotate(90deg);
color: #b84297; color: #E83090;
} }
} }
@ -734,9 +734,9 @@ body {
font-weight: normal; font-weight: normal;
&.active { &.active {
border-left: 1px solid #b84297; border-left: 1px solid #E83090;
background: none; background: none;
color: #b84297; color: #E83090;
font-weight: normal; font-weight: normal;
} }
} }
@ -744,14 +744,14 @@ body {
.nav-pills { .nav-pills {
.nav-link { .nav-link {
&.active { &.active {
color: #b84297; color: #E83090;
} }
} }
.nav-pills { .nav-pills {
.nav-link { .nav-link {
&.active { &.active {
color: #b84297; color: #E83090;
} }
} }
} }

21
modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Pages/Index.cshtml

@ -15,17 +15,38 @@
@if (HttpContext.User.Identity != null && HttpContext.User.Identity.IsAuthenticated) @if (HttpContext.User.Identity != null && HttpContext.User.Identity.IsAuthenticated)
{ {
<ul class="list-group mt-3 text-start"> <ul class="list-group mt-3 text-start">
<p>Current User</p>
@foreach (var claim in HttpContext.User.Claims) @foreach (var claim in HttpContext.User.Claims)
{ {
<li class="list-group-item">@claim.Type : @claim.Value</li> <li class="list-group-item">@claim.Type : @claim.Value</li>
} }
</ul> </ul>
<ul class="list-group mt-3 text-start">
<p>oidc</p>
@{
var oidc = await HttpContext.AuthenticateAsync("oidc");
if (oidc.Principal != null)
{
foreach (var claim in oidc.Principal.Claims)
{
<li class="list-group-item">@claim.Type : @claim.Value</li>
}
}
}
</ul>
<p>HttpContext.GetTokenAsync("access_token") <p>HttpContext.GetTokenAsync("access_token")
<br/> <br/>
@await HttpContext.GetTokenAsync("access_token") @await HttpContext.GetTokenAsync("access_token")
</p> </p>
<p>HttpContext.GetTokenAsync("id_token")
<br/>
@await HttpContext.GetTokenAsync("id_token")
</p>
var client = new HttpClient(); var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44303/api/claims"); var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44303/api/claims");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await HttpContext.GetTokenAsync("access_token")); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await HttpContext.GetTokenAsync("access_token"));

7
modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250215074649_Initial.Designer.cs → modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250630055813_Initial.Designer.cs

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace OpenIddict.Demo.Server.Migrations namespace OpenIddict.Demo.Server.Migrations
{ {
[DbContext(typeof(ServerDbContext))] [DbContext(typeof(ServerDbContext))]
[Migration("20250215074649_Initial")] [Migration("20250630055813_Initial")]
partial class Initial partial class Initial
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -22,7 +22,7 @@ namespace OpenIddict.Demo.Server.Migrations
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer)
.HasAnnotation("ProductVersion", "9.0.0") .HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 128); .HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
@ -938,6 +938,9 @@ namespace OpenIddict.Demo.Server.Migrations
.HasColumnType("nvarchar(max)") .HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties"); .HasColumnName("ExtraProperties");
b.Property<string>("FrontChannelLogoutUri")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted") b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bit") .HasColumnType("bit")

1
modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250215074649_Initial.cs → modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250630055813_Initial.cs

@ -378,6 +378,7 @@ namespace OpenIddict.Demo.Server.Migrations
RedirectUris = table.Column<string>(type: "nvarchar(max)", nullable: true), RedirectUris = table.Column<string>(type: "nvarchar(max)", nullable: true),
Requirements = table.Column<string>(type: "nvarchar(max)", nullable: true), Requirements = table.Column<string>(type: "nvarchar(max)", nullable: true),
Settings = table.Column<string>(type: "nvarchar(max)", nullable: true), Settings = table.Column<string>(type: "nvarchar(max)", nullable: true),
FrontChannelLogoutUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
ClientUri = table.Column<string>(type: "nvarchar(max)", nullable: true), ClientUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
LogoUri = table.Column<string>(type: "nvarchar(max)", nullable: true), LogoUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: false), ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: false),

5
modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs

@ -19,7 +19,7 @@ namespace OpenIddict.Demo.Server.Migrations
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer)
.HasAnnotation("ProductVersion", "9.0.0") .HasAnnotation("ProductVersion", "9.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 128); .HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
@ -935,6 +935,9 @@ namespace OpenIddict.Demo.Server.Migrations
.HasColumnType("nvarchar(max)") .HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties"); .HasColumnName("ExtraProperties");
b.Property<string>("FrontChannelLogoutUri")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted") b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bit") .HasColumnType("bit")

14
modules/openiddict/app/OpenIddict.Demo.Server/Pages/Index.cshtml

@ -4,7 +4,13 @@
ViewData["Title"] = "Home page"; ViewData["Title"] = "Home page";
} }
<div class="text-center"> @if (HttpContext.User.Identity != null && HttpContext.User.Identity.IsAuthenticated)
<h1 class="display-4">Welcome</h1> {
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p> <ul class="list-group mt-3 text-start">
</div> <p>Current User</p>
@foreach (var claim in HttpContext.User.Claims)
{
<li class="list-group-item">@claim.Type : @claim.Value</li>
}
</ul>
}

15
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AuthorizeController.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq; using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
@ -148,6 +149,13 @@ public class AuthorizeController : AbpOpenIdDictControllerBase
case OpenIddictConstants.ConsentTypes.Explicit when authorizations.Any() && !request.HasPromptValue(OpenIddictConstants.PromptValues.Consent): case OpenIddictConstants.ConsentTypes.Explicit when authorizations.Any() && !request.HasPromptValue(OpenIddictConstants.PromptValues.Consent):
var principal = await SignInManager.CreateUserPrincipalAsync(user); var principal = await SignInManager.CreateUserPrincipalAsync(user);
var sid = dynamicPrincipal.FindFirst(JwtRegisteredClaimNames.Sid);
if (sid != null)
{
principal.RemoveClaims(JwtRegisteredClaimNames.Sid);
principal.AddClaim(JwtRegisteredClaimNames.Sid, sid.Value);
}
if (result.Properties != null && result.Properties.IsPersistent) if (result.Properties != null && result.Properties.IsPersistent)
{ {
var claim = new Claim(AbpClaimTypes.RememberMe, true.ToString()).SetDestinations(OpenIddictConstants.Destinations.AccessToken); var claim = new Claim(AbpClaimTypes.RememberMe, true.ToString()).SetDestinations(OpenIddictConstants.Destinations.AccessToken);
@ -247,6 +255,13 @@ public class AuthorizeController : AbpOpenIdDictControllerBase
var principal = await SignInManager.CreateUserPrincipalAsync(user); var principal = await SignInManager.CreateUserPrincipalAsync(user);
var sid = User.FindFirst(JwtRegisteredClaimNames.Sid);
if (sid != null)
{
principal.RemoveClaims(JwtRegisteredClaimNames.Sid);
principal.AddClaim(JwtRegisteredClaimNames.Sid, sid.Value);
}
var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme);
if (result.Succeeded && result.Properties != null && result.Properties.IsPersistent) if (result.Succeeded && result.Properties != null && result.Properties.IsPersistent)
{ {

12
modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpApplicationDescriptor.cs

@ -1,16 +1,22 @@
using OpenIddict.Abstractions; using System;
using OpenIddict.Abstractions;
namespace Volo.Abp.OpenIddict.Applications; namespace Volo.Abp.OpenIddict.Applications;
public class AbpApplicationDescriptor : OpenIddictApplicationDescriptor public class AbpApplicationDescriptor : OpenIddictApplicationDescriptor
{ {
/// <summary>
/// Gets or sets the front-channel logout URI associated with the application.
/// </summary>
public virtual Uri FrontChannelLogoutUri { get; set; }
/// <summary> /// <summary>
/// URI to further information about client. /// URI to further information about client.
/// </summary> /// </summary>
public string ClientUri { get; set; } public virtual string ClientUri { get; set; }
/// <summary> /// <summary>
/// URI to client logo. /// URI to client logo.
/// </summary> /// </summary>
public string LogoUri { get; set; } public virtual string LogoUri { get; set; }
} }

31
modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpApplicationManager.cs

@ -44,6 +44,17 @@ public class AbpApplicationManager : OpenIddictApplicationManager<OpenIddictAppl
if (descriptor is AbpApplicationDescriptor model) if (descriptor is AbpApplicationDescriptor model)
{ {
if (!application.FrontChannelLogoutUri.IsNullOrWhiteSpace())
{
if (!Uri.TryCreate(application.FrontChannelLogoutUri, UriKind.Absolute, out var uri) || IsImplicitFileUri(uri))
{
throw new ArgumentException(OpenIddictResources.GetResourceString("ID0214"));
}
model.FrontChannelLogoutUri = uri;
}
model.ClientUri = application.ClientUri; model.ClientUri = application.ClientUri;
model.LogoUri = application.LogoUri; model.LogoUri = application.LogoUri;
} }
@ -55,15 +66,24 @@ public class AbpApplicationManager : OpenIddictApplicationManager<OpenIddictAppl
if (descriptor is AbpApplicationDescriptor model) if (descriptor is AbpApplicationDescriptor model)
{ {
application.FrontChannelLogoutUri = model.FrontChannelLogoutUri?.OriginalString;
application.ClientUri = model.ClientUri; application.ClientUri = model.ClientUri;
application.LogoUri = model.LogoUri; application.LogoUri = model.LogoUri;
} }
} }
public virtual async ValueTask<string> GetFrontChannelLogoutUriAsync(object application, CancellationToken cancellationToken = default)
{
Check.NotNull(application, nameof(application));
Check.AssignableTo<OpenIddictApplicationModel>(application.GetType(), nameof(application));
return await Store.As<IAbpOpenIdApplicationStore>().GetFrontChannelLogoutUriAsync(application.As<OpenIddictApplicationModel>(), cancellationToken);
}
public virtual async ValueTask<string> GetClientUriAsync(object application, CancellationToken cancellationToken = default) public virtual async ValueTask<string> GetClientUriAsync(object application, CancellationToken cancellationToken = default)
{ {
Check.NotNull(application, nameof(application)); Check.NotNull(application, nameof(application));
Check.AssignableTo<IAbpOpenIdApplicationStore>(application.GetType(), nameof(application)); Check.AssignableTo<OpenIddictApplicationModel>(application.GetType(), nameof(application));
return await Store.As<IAbpOpenIdApplicationStore>().GetClientUriAsync(application.As<OpenIddictApplicationModel>(), cancellationToken); return await Store.As<IAbpOpenIdApplicationStore>().GetClientUriAsync(application.As<OpenIddictApplicationModel>(), cancellationToken);
} }
@ -71,8 +91,15 @@ public class AbpApplicationManager : OpenIddictApplicationManager<OpenIddictAppl
public virtual async ValueTask<string> GetLogoUriAsync(object application, CancellationToken cancellationToken = default) public virtual async ValueTask<string> GetLogoUriAsync(object application, CancellationToken cancellationToken = default)
{ {
Check.NotNull(application, nameof(application)); Check.NotNull(application, nameof(application));
Check.AssignableTo<IAbpOpenIdApplicationStore>(application.GetType(), nameof(application)); Check.AssignableTo<OpenIddictApplicationModel>(application.GetType(), nameof(application));
return await Store.As<IAbpOpenIdApplicationStore>().GetLogoUriAsync(application.As<OpenIddictApplicationModel>(), cancellationToken); return await Store.As<IAbpOpenIdApplicationStore>().GetLogoUriAsync(application.As<OpenIddictApplicationModel>(), cancellationToken);
} }
protected virtual bool IsImplicitFileUri(Uri uri)
{
Check.NotNull(uri, nameof(uri));
return uri.IsAbsoluteUri && uri.IsFile && !uri.OriginalString.StartsWith(uri.Scheme, StringComparison.OrdinalIgnoreCase);
}
} }

7
modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpOpenIddictApplicationStore.cs

@ -635,6 +635,13 @@ public class AbpOpenIddictApplicationStore : AbpOpenIddictStoreBase<IOpenIddictA
} }
} }
public virtual async ValueTask<string> GetFrontChannelLogoutUriAsync(OpenIddictApplicationModel application, CancellationToken cancellationToken = default)
{
Check.NotNull(application, nameof(application));
return await new ValueTask<string>(application.FrontChannelLogoutUri);
}
public virtual ValueTask<string> GetClientUriAsync(OpenIddictApplicationModel application, CancellationToken cancellationToken = default) public virtual ValueTask<string> GetClientUriAsync(OpenIddictApplicationModel application, CancellationToken cancellationToken = default)
{ {
Check.NotNull(application, nameof(application)); Check.NotNull(application, nameof(application));

2
modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/IAbpApplicationManager.cs

@ -6,6 +6,8 @@ namespace Volo.Abp.OpenIddict.Applications;
public interface IAbpApplicationManager : IOpenIddictApplicationManager public interface IAbpApplicationManager : IOpenIddictApplicationManager
{ {
ValueTask<string> GetFrontChannelLogoutUriAsync(object application, CancellationToken cancellationToken = default);
ValueTask<string> GetClientUriAsync(object application, CancellationToken cancellationToken = default); ValueTask<string> GetClientUriAsync(object application, CancellationToken cancellationToken = default);
ValueTask<string> GetLogoUriAsync(object application, CancellationToken cancellationToken = default); ValueTask<string> GetLogoUriAsync(object application, CancellationToken cancellationToken = default);

2
modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/IAbpOpenIdApplicationStore.cs

@ -6,6 +6,8 @@ namespace Volo.Abp.OpenIddict.Applications;
public interface IAbpOpenIdApplicationStore : IOpenIddictApplicationStore<OpenIddictApplicationModel> public interface IAbpOpenIdApplicationStore : IOpenIddictApplicationStore<OpenIddictApplicationModel>
{ {
ValueTask<string> GetFrontChannelLogoutUriAsync(OpenIddictApplicationModel application, CancellationToken cancellationToken = default);
ValueTask<string> GetClientUriAsync(OpenIddictApplicationModel application, CancellationToken cancellationToken = default); ValueTask<string> GetClientUriAsync(OpenIddictApplicationModel application, CancellationToken cancellationToken = default);
ValueTask<string> GetLogoUriAsync(OpenIddictApplicationModel application, CancellationToken cancellationToken = default); ValueTask<string> GetLogoUriAsync(OpenIddictApplicationModel application, CancellationToken cancellationToken = default);

9
modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/OpenIddictApplication.cs

@ -94,13 +94,18 @@ public class OpenIddictApplication : FullAuditedAggregateRoot<Guid>
/// </summary> /// </summary>
public virtual string Settings { get; set; } public virtual string Settings { get; set; }
/// <summary>
/// Gets or sets the front-channel logout URI associated with the application.
/// </summary>
public virtual string FrontChannelLogoutUri { get; set; }
/// <summary> /// <summary>
/// URI to further information about client. /// URI to further information about client.
/// </summary> /// </summary>
public string ClientUri { get; set; } public virtual string ClientUri { get; set; }
/// <summary> /// <summary>
/// URI to client logo. /// URI to client logo.
/// </summary> /// </summary>
public string LogoUri { get; set; } public virtual string LogoUri { get; set; }
} }

3
modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/OpenIddictApplicationExtensions.cs

@ -27,6 +27,7 @@ public static class OpenIddictApplicationExtensions
RedirectUris = model.RedirectUris, RedirectUris = model.RedirectUris,
Requirements = model.Requirements, Requirements = model.Requirements,
Settings = model.Settings, Settings = model.Settings,
FrontChannelLogoutUri = model.FrontChannelLogoutUri,
ClientUri = model.ClientUri, ClientUri = model.ClientUri,
LogoUri = model.LogoUri LogoUri = model.LogoUri
}; };
@ -59,6 +60,7 @@ public static class OpenIddictApplicationExtensions
entity.RedirectUris = model.RedirectUris; entity.RedirectUris = model.RedirectUris;
entity.Requirements = model.Requirements; entity.Requirements = model.Requirements;
entity.Settings = model.Settings; entity.Settings = model.Settings;
entity.FrontChannelLogoutUri = model.FrontChannelLogoutUri;
entity.ClientUri = model.ClientUri; entity.ClientUri = model.ClientUri;
entity.LogoUri = model.LogoUri; entity.LogoUri = model.LogoUri;
@ -100,6 +102,7 @@ public static class OpenIddictApplicationExtensions
RedirectUris = entity.RedirectUris, RedirectUris = entity.RedirectUris,
Requirements = entity.Requirements, Requirements = entity.Requirements,
Settings = entity.Settings, Settings = entity.Settings,
FrontChannelLogoutUri = entity.FrontChannelLogoutUri,
ClientUri = entity.ClientUri, ClientUri = entity.ClientUri,
LogoUri = entity.LogoUri LogoUri = entity.LogoUri
}; };

9
modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/OpenIddictApplicationModel.cs

@ -89,13 +89,18 @@ public class OpenIddictApplicationModel : ExtensibleObject
/// </summary> /// </summary>
public virtual string Settings { get; set; } public virtual string Settings { get; set; }
/// <summary>
/// Gets or sets the front-channel logout URI associated with the application.
/// </summary>
public virtual string FrontChannelLogoutUri { get; set; }
/// <summary> /// <summary>
/// URI to further information about client. /// URI to further information about client.
/// </summary> /// </summary>
public string ClientUri { get; set; } public virtual string ClientUri { get; set; }
/// <summary> /// <summary>
/// URI to client logo. /// URI to client logo.
/// </summary> /// </summary>
public string LogoUri { get; set; } public virtual string LogoUri { get; set; }
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/ar.json

@ -16,8 +16,8 @@
"Permission:Edit": "تحرير", "Permission:Edit": "تحرير",
"Permission:Delete": "حذف", "Permission:Delete": "حذف",
"Permission:ManageConnectionStrings": "Manage connection strings", "Permission:ManageConnectionStrings": "Manage connection strings",
"Permission:ManageFeatures": "إدارة الميزات", "Permission:ManageFeatures": "الميزات",
"DisplayName:AdminEmailAddress": "عنوان البريد الإلكتروني للمسؤول", "DisplayName:AdminEmailAddress": "عنوان البريد الإلكتروني للمسؤول",
"DisplayName:AdminPassword": "كلمة مرور المسؤول" "DisplayName:AdminPassword": "كلمة مرور المسؤول"
} }
} }

2
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/cs.json

@ -16,7 +16,7 @@
"Permission:Edit": "Upravit", "Permission:Edit": "Upravit",
"Permission:Delete": "Smazat", "Permission:Delete": "Smazat",
"Permission:ManageConnectionStrings": "Spravovat connection stringy", "Permission:ManageConnectionStrings": "Spravovat connection stringy",
"Permission:ManageFeatures": "Spravovat funkce", "Permission:ManageFeatures": "Funkce",
"DisplayName:AdminEmailAddress": "Email adresa správce", "DisplayName:AdminEmailAddress": "Email adresa správce",
"DisplayName:AdminPassword": "Heslo správce" "DisplayName:AdminPassword": "Heslo správce"
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/de.json

@ -16,8 +16,8 @@
"Permission:Edit": "Bearbeiten", "Permission:Edit": "Bearbeiten",
"Permission:Delete": "Löschen", "Permission:Delete": "Löschen",
"Permission:ManageConnectionStrings": "Connection Strings verwalten", "Permission:ManageConnectionStrings": "Connection Strings verwalten",
"Permission:ManageFeatures": "Features verwalten", "Permission:ManageFeatures": "Funktionen",
"DisplayName:AdminEmailAddress": "Admin-E-Mail-Adresse", "DisplayName:AdminEmailAddress": "Admin-E-Mail-Adresse",
"DisplayName:AdminPassword": "Admin-Passwort" "DisplayName:AdminPassword": "Admin-Passwort"
} }
} }

2
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/el.json

@ -15,7 +15,7 @@
"Permission:Edit": "Επεξεργασία", "Permission:Edit": "Επεξεργασία",
"Permission:Delete": "Διαγραφή", "Permission:Delete": "Διαγραφή",
"Permission:ManageConnectionStrings": "Διαχείριση συμβολοσειρών σύνδεσης", "Permission:ManageConnectionStrings": "Διαχείριση συμβολοσειρών σύνδεσης",
"Permission:ManageFeatures": "Διαχείριση λειτουργιών", "Permission:ManageFeatures": "Λειτουργίες",
"DisplayName:AdminEmailAddress": "Διεύθυνση email διαχειριστή", "DisplayName:AdminEmailAddress": "Διεύθυνση email διαχειριστή",
"DisplayName:AdminPassword": "Κωδικός διαχειριστή" "DisplayName:AdminPassword": "Κωδικός διαχειριστή"
} }

2
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en-GB.json

@ -15,7 +15,7 @@
"Permission:Edit": "Edit", "Permission:Edit": "Edit",
"Permission:Delete": "Delete", "Permission:Delete": "Delete",
"Permission:ManageConnectionStrings": "Manage connection strings", "Permission:ManageConnectionStrings": "Manage connection strings",
"Permission:ManageFeatures": "Manage features", "Permission:ManageFeatures": "Features",
"DisplayName:AdminEmailAddress": "Admin Email Address", "DisplayName:AdminEmailAddress": "Admin Email Address",
"DisplayName:AdminPassword": "Admin Password" "DisplayName:AdminPassword": "Admin Password"
} }

3
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json

@ -16,8 +16,9 @@
"Permission:Edit": "Edit", "Permission:Edit": "Edit",
"Permission:Delete": "Delete", "Permission:Delete": "Delete",
"Permission:ManageConnectionStrings": "Manage connection strings", "Permission:ManageConnectionStrings": "Manage connection strings",
"Permission:ManageFeatures": "Manage features", "Permission:ManageFeatures": "Features",
"DisplayName:AdminEmailAddress": "Admin Email Address", "DisplayName:AdminEmailAddress": "Admin Email Address",
"DisplayName:AdminPassword": "Admin Password" "DisplayName:AdminPassword": "Admin Password"
} }
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/es.json

@ -16,8 +16,8 @@
"Permission:Edit": "Editar", "Permission:Edit": "Editar",
"Permission:Delete": "Borrar", "Permission:Delete": "Borrar",
"Permission:ManageConnectionStrings": "Gestión de cadenas de conexión", "Permission:ManageConnectionStrings": "Gestión de cadenas de conexión",
"Permission:ManageFeatures": "Gestión de características", "Permission:ManageFeatures": "Características",
"DisplayName:AdminEmailAddress": "Dirección e-mail de administrador", "DisplayName:AdminEmailAddress": "Dirección e-mail de administrador",
"DisplayName:AdminPassword": "Contraseña de administrador" "DisplayName:AdminPassword": "Contraseña de administrador"
} }
} }

2
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fa.json

@ -15,7 +15,7 @@
"Permission:Edit": "ویرایش", "Permission:Edit": "ویرایش",
"Permission:Delete": "حذف", "Permission:Delete": "حذف",
"Permission:ManageConnectionStrings": "مدیریت کانکشن استرینگها", "Permission:ManageConnectionStrings": "مدیریت کانکشن استرینگها",
"Permission:ManageFeatures": "مدیریت ویژگی ها", "Permission:ManageFeatures": "ویژگی‌ها",
"DisplayName:AdminEmailAddress": "آدرس ایمیل مدیر", "DisplayName:AdminEmailAddress": "آدرس ایمیل مدیر",
"DisplayName:AdminPassword": "گذرواژه مدیریت" "DisplayName:AdminPassword": "گذرواژه مدیریت"
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fi.json

@ -16,8 +16,8 @@
"Permission:Edit": "Muokkaus", "Permission:Edit": "Muokkaus",
"Permission:Delete": "Poisto", "Permission:Delete": "Poisto",
"Permission:ManageConnectionStrings": "Hallitse tietokantayhteyksiä", "Permission:ManageConnectionStrings": "Hallitse tietokantayhteyksiä",
"Permission:ManageFeatures": "Hallitse ominaisuuksia", "Permission:ManageFeatures": "Ominaisuudet",
"DisplayName:AdminEmailAddress": "Järjestelmänvalvojan sähköpostiosoite", "DisplayName:AdminEmailAddress": "Järjestelmänvalvojan sähköpostiosoite",
"DisplayName:AdminPassword": "Järjestelmänvalvojan salasana" "DisplayName:AdminPassword": "Järjestelmänvalvojan salasana"
} }
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fr.json

@ -16,8 +16,8 @@
"Permission:Edit": "Modifier", "Permission:Edit": "Modifier",
"Permission:Delete": "Supprimer", "Permission:Delete": "Supprimer",
"Permission:ManageConnectionStrings": "Gérer les chaînes de connexion", "Permission:ManageConnectionStrings": "Gérer les chaînes de connexion",
"Permission:ManageFeatures": "Gérer les fonctionnalités", "Permission:ManageFeatures": "Fonctionnalités",
"DisplayName:AdminEmailAddress": "Adresse de messagerie d’administrateur", "DisplayName:AdminEmailAddress": "Adresse de messagerie d’administrateur",
"DisplayName:AdminPassword": "Mot de passe d’administrateur" "DisplayName:AdminPassword": "Mot de passe d’administrateur"
} }
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/hi.json

@ -16,8 +16,8 @@
"Permission:Edit": "संपादित करें", "Permission:Edit": "संपादित करें",
"Permission:Delete": "हटाएं", "Permission:Delete": "हटाएं",
"Permission:ManageConnectionStrings": "कनेक्शन स्ट्रिंग्स प्रबंधित करें", "Permission:ManageConnectionStrings": "कनेक्शन स्ट्रिंग्स प्रबंधित करें",
"Permission:ManageFeatures": "सुविधाओं को प्रबंधित करें", "Permission:ManageFeatures": "सुविधाएं",
"DisplayName:AdminEmailAddress": "ईमेल पता", "DisplayName:AdminEmailAddress": "ईमेल पता",
"DisplayName:AdminPassword": "व्यवस्थापक का पारण शब्द" "DisplayName:AdminPassword": "व्यवस्थापक का पारण शब्द"
} }
} }

3
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/hr.json

@ -16,8 +16,9 @@
"Permission:Edit": "Uredi", "Permission:Edit": "Uredi",
"Permission:Delete": "Izbrisati", "Permission:Delete": "Izbrisati",
"Permission:ManageConnectionStrings": "Upravljanje vezom na bazu podataka", "Permission:ManageConnectionStrings": "Upravljanje vezom na bazu podataka",
"Permission:ManageFeatures": "Upravljanje značajkama", "Permission:ManageFeatures": "Značajke",
"DisplayName:AdminEmailAddress": "Adresa e-pošte administratora", "DisplayName:AdminEmailAddress": "Adresa e-pošte administratora",
"DisplayName:AdminPassword": "Administratorska lozinka" "DisplayName:AdminPassword": "Administratorska lozinka"
} }
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/hu.json

@ -16,8 +16,8 @@
"Permission:Edit": "Szerkesztés", "Permission:Edit": "Szerkesztés",
"Permission:Delete": "Törlés", "Permission:Delete": "Törlés",
"Permission:ManageConnectionStrings": "Kapcsolati beállítások kezelése", "Permission:ManageConnectionStrings": "Kapcsolati beállítások kezelése",
"Permission:ManageFeatures": "Funkciók kezelése", "Permission:ManageFeatures": "Funkciók",
"DisplayName:AdminEmailAddress": "Admin email cím", "DisplayName:AdminEmailAddress": "Admin email cím",
"DisplayName:AdminPassword": "Admin jelszó" "DisplayName:AdminPassword": "Admin jelszó"
} }
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/is.json

@ -16,8 +16,8 @@
"Permission:Edit": "Breyta", "Permission:Edit": "Breyta",
"Permission:Delete": "Eyða", "Permission:Delete": "Eyða",
"Permission:ManageConnectionStrings": "Umsjá tengistrengja", "Permission:ManageConnectionStrings": "Umsjá tengistrengja",
"Permission:ManageFeatures": "Umsjá eiginleika", "Permission:ManageFeatures": "Eiginleikar",
"DisplayName:AdminEmailAddress": "Netfang stjórnanda (admin)", "DisplayName:AdminEmailAddress": "Netfang stjórnanda (admin)",
"DisplayName:AdminPassword": "Lykilorð stjórnanda (admin)" "DisplayName:AdminPassword": "Lykilorð stjórnanda (admin)"
} }
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/it.json

@ -16,8 +16,8 @@
"Permission:Edit": "Modifica", "Permission:Edit": "Modifica",
"Permission:Delete": "Elimina", "Permission:Delete": "Elimina",
"Permission:ManageConnectionStrings": "Gestisci le stringhe di connessione", "Permission:ManageConnectionStrings": "Gestisci le stringhe di connessione",
"Permission:ManageFeatures": "Gestisci le funzionalità", "Permission:ManageFeatures": "Funzionalità",
"DisplayName:AdminEmailAddress": "Indirizzo e-mail dell'amministratore", "DisplayName:AdminEmailAddress": "Indirizzo e-mail dell'amministratore",
"DisplayName:AdminPassword": "Password dell'amministratore" "DisplayName:AdminPassword": "Password dell'amministratore"
} }
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/nl.json

@ -16,8 +16,8 @@
"Permission:Edit": "Bewerk", "Permission:Edit": "Bewerk",
"Permission:Delete": "Verwijder", "Permission:Delete": "Verwijder",
"Permission:ManageConnectionStrings": "Beheer connection strings", "Permission:ManageConnectionStrings": "Beheer connection strings",
"Permission:ManageFeatures": "Beheer functies", "Permission:ManageFeatures": "Functies",
"DisplayName:AdminEmailAddress": "Admin e-mail adres", "DisplayName:AdminEmailAddress": "Admin e-mail adres",
"DisplayName:AdminPassword": "Admin wachtwoord" "DisplayName:AdminPassword": "Admin wachtwoord"
} }
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl-PL.json

@ -16,8 +16,8 @@
"Permission:Edit": "Edytuj", "Permission:Edit": "Edytuj",
"Permission:Delete": "Usuń", "Permission:Delete": "Usuń",
"Permission:ManageConnectionStrings": "Zarządzaj connection string'ami", "Permission:ManageConnectionStrings": "Zarządzaj connection string'ami",
"Permission:ManageFeatures": "Zarządzaj fukcjami", "Permission:ManageFeatures": "Funkcje",
"DisplayName:AdminEmailAddress": "Adres e-mail administratora", "DisplayName:AdminEmailAddress": "Adres e-mail administratora",
"DisplayName:AdminPassword": "Hasło administratora" "DisplayName:AdminPassword": "Hasło administratora"
} }
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pt-BR.json

@ -16,8 +16,8 @@
"Permission:Edit": "Editar", "Permission:Edit": "Editar",
"Permission:Delete": "Excluir", "Permission:Delete": "Excluir",
"Permission:ManageConnectionStrings": "Gerenciar conexões (connection strings)", "Permission:ManageConnectionStrings": "Gerenciar conexões (connection strings)",
"Permission:ManageFeatures": "Gerenciar Funcionalidades", "Permission:ManageFeatures": "Recursos",
"DisplayName:AdminEmailAddress": "Endereço de e-mail do administrador", "DisplayName:AdminEmailAddress": "Endereço de e-mail do administrador",
"DisplayName:AdminPassword": "Senha do administrador" "DisplayName:AdminPassword": "Senha do administrador"
} }
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/ro-RO.json

@ -16,8 +16,8 @@
"Permission:Edit": "Editează", "Permission:Edit": "Editează",
"Permission:Delete": "Şterge", "Permission:Delete": "Şterge",
"Permission:ManageConnectionStrings": "Administrează stringurile de conexiune", "Permission:ManageConnectionStrings": "Administrează stringurile de conexiune",
"Permission:ManageFeatures": "Administrarea caracteristicilor", "Permission:ManageFeatures": "Caracteristici",
"DisplayName:AdminEmailAddress": "Adresa de email admin", "DisplayName:AdminEmailAddress": "Adresa de email admin",
"DisplayName:AdminPassword": "Parola admin" "DisplayName:AdminPassword": "Parola admin"
} }
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/ru.json

@ -16,8 +16,8 @@
"Permission:Edit": "Редактировать", "Permission:Edit": "Редактировать",
"Permission:Delete": "Удалить", "Permission:Delete": "Удалить",
"Permission:ManageConnectionStrings": "Управление строками подключения", "Permission:ManageConnectionStrings": "Управление строками подключения",
"Permission:ManageFeatures": "Управление функциями", "Permission:ManageFeatures": "Возможности",
"DisplayName:AdminEmailAddress": "Адрес электронной почты администратора", "DisplayName:AdminEmailAddress": "Адрес электронной почты администратора",
"DisplayName:AdminPassword": "Пароль администратора" "DisplayName:AdminPassword": "Пароль администратора"
} }
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/sk.json

@ -16,8 +16,8 @@
"Permission:Edit": "Upraviť", "Permission:Edit": "Upraviť",
"Permission:Delete": "Zmazať", "Permission:Delete": "Zmazať",
"Permission:ManageConnectionStrings": "Správa connection stringov", "Permission:ManageConnectionStrings": "Správa connection stringov",
"Permission:ManageFeatures": "Správa funkcií", "Permission:ManageFeatures": "Funkcie",
"DisplayName:AdminEmailAddress": "Emailová adresa administrátora", "DisplayName:AdminEmailAddress": "Emailová adresa administrátora",
"DisplayName:AdminPassword": "Heslo administrátora" "DisplayName:AdminPassword": "Heslo administrátora"
} }
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/sl.json

@ -16,8 +16,8 @@
"Permission:Edit": "Urejanje", "Permission:Edit": "Urejanje",
"Permission:Delete": "Brisanje", "Permission:Delete": "Brisanje",
"Permission:ManageConnectionStrings": "Upravljanje connection string-ov", "Permission:ManageConnectionStrings": "Upravljanje connection string-ov",
"Permission:ManageFeatures": "Upravljanje funkcionalnosti", "Permission:ManageFeatures": "Lastnosti",
"DisplayName:AdminEmailAddress": "Admin e-poštni naslov", "DisplayName:AdminEmailAddress": "Admin e-poštni naslov",
"DisplayName:AdminPassword": "Skrbniško geslo" "DisplayName:AdminPassword": "Skrbniško geslo"
} }
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/sv.json

@ -16,8 +16,8 @@
"Permission:Edit": "Redigera", "Permission:Edit": "Redigera",
"Permission:Delete": "Radera", "Permission:Delete": "Radera",
"Permission:ManageConnectionStrings": "Hantera anslutningssträngar", "Permission:ManageConnectionStrings": "Hantera anslutningssträngar",
"Permission:ManageFeatures": "Hantera funktioner", "Permission:ManageFeatures": "Funktioner",
"DisplayName:AdminEmailAddress": "Admin E-postadress", "DisplayName:AdminEmailAddress": "Admin E-postadress",
"DisplayName:AdminPassword": "Lösenord för administratör" "DisplayName:AdminPassword": "Lösenord för administratör"
} }
} }

3
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/tr.json

@ -16,8 +16,9 @@
"Permission:Edit": "Düzenleme", "Permission:Edit": "Düzenleme",
"Permission:Delete": "Silme", "Permission:Delete": "Silme",
"Permission:ManageConnectionStrings": "Bağlantı cümlelerini yönet", "Permission:ManageConnectionStrings": "Bağlantı cümlelerini yönet",
"Permission:ManageFeatures": "Özellikleri yönet", "Permission:ManageFeatures": "Özellikler",
"DisplayName:AdminEmailAddress": "Admin Eposta Adresi", "DisplayName:AdminEmailAddress": "Admin Eposta Adresi",
"DisplayName:AdminPassword": "Admin Şifresi" "DisplayName:AdminPassword": "Admin Şifresi"
} }
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/vi.json

@ -16,8 +16,8 @@
"Permission:Edit": "Sửa", "Permission:Edit": "Sửa",
"Permission:Delete": "Xóa", "Permission:Delete": "Xóa",
"Permission:ManageConnectionStrings": "Quản lý chuỗi kết nối", "Permission:ManageConnectionStrings": "Quản lý chuỗi kết nối",
"Permission:ManageFeatures": "Quản lý các tính năng", "Permission:ManageFeatures": "Tính năng",
"DisplayName:AdminEmailAddress": "Địa chỉ Email Quản trị viên", "DisplayName:AdminEmailAddress": "Địa chỉ Email Quản trị viên",
"DisplayName:AdminPassword": "Mật khẩu quản trị" "DisplayName:AdminPassword": "Mật khẩu quản trị"
} }
} }

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hans.json

@ -16,8 +16,8 @@
"Permission:Edit": "编辑", "Permission:Edit": "编辑",
"Permission:Delete": "删除", "Permission:Delete": "删除",
"Permission:ManageConnectionStrings": "管理连接字符串", "Permission:ManageConnectionStrings": "管理连接字符串",
"Permission:ManageFeatures": "管理功能", "Permission:ManageFeatures": "功能",
"DisplayName:AdminEmailAddress": "管理员电子邮件地址", "DisplayName:AdminEmailAddress": "管理员电子邮件地址",
"DisplayName:AdminPassword": "管理员密码" "DisplayName:AdminPassword": "管理员密码"
} }
} }

3
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hant.json

@ -16,8 +16,9 @@
"Permission:Edit": "編輯", "Permission:Edit": "編輯",
"Permission:Delete": "刪除", "Permission:Delete": "刪除",
"Permission:ManageConnectionStrings": "管理資料庫連線字串", "Permission:ManageConnectionStrings": "管理資料庫連線字串",
"Permission:ManageFeatures": "管理功能", "Permission:ManageFeatures": "功能",
"DisplayName:AdminEmailAddress": "管理者信箱", "DisplayName:AdminEmailAddress": "管理者信箱",
"DisplayName:AdminPassword": "管理者密碼" "DisplayName:AdminPassword": "管理者密碼"
} }
} }

11
npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.html

@ -64,17 +64,24 @@
[prop]="prop.name" [prop]="prop.name"
[sortable]="prop.sortable" [sortable]="prop.sortable"
> >
<ng-template ngx-datatable-header-template let-column="column"> <ng-template ngx-datatable-header-template let-column="column" let-sortFn="sortFn">
@if (prop.tooltip) { @if (prop.tooltip) {
<span <span
[ngbTooltip]="prop.tooltip.text | abpLocalization" [ngbTooltip]="prop.tooltip.text | abpLocalization"
[placement]="prop.tooltip.placement || 'auto'" [placement]="prop.tooltip.placement || 'auto'"
container="body" container="body"
[class.pointer]="prop.sortable"
(click)="prop.sortable && sortFn(column)"
> >
{{ column.name }} <i class="fa fa-info-circle" aria-hidden="true"></i> {{ column.name }} <i class="fa fa-info-circle" aria-hidden="true"></i>
</span> </span>
} @else { } @else {
{{ column.name }} <span
[class.pointer]="prop.sortable"
(click)="prop.sortable && sortFn(column)"
>
{{ column.name }}
</span>
} }
</ng-template> </ng-template>
<ng-template let-row="row" let-i="index" ngx-datatable-cell-template> <ng-template let-row="row" let-i="index" ngx-datatable-cell-template>

3
npm/packs/jquery-validation/abp.resourcemapping.js

@ -1,6 +1,7 @@
module.exports = { module.exports = {
mappings: { mappings: {
"@node_modules/jquery-validation/dist/jquery.validate.js": "@libs/jquery-validation/", "@node_modules/jquery-validation/dist/jquery.validate.js": "@libs/jquery-validation/",
"@node_modules/jquery-validation/dist/localization/*.*": "@libs/jquery-validation/localization/" "@node_modules/jquery-validation/dist/localization/*.*": "@libs/jquery-validation/localization/",
"@node_modules/@abp/jquery-validation/src/*.*": "@libs/jquery-validation/"
} }
} }

27
npm/packs/jquery-validation/src/abp.jquery.validate.js

@ -0,0 +1,27 @@
$.validator.methods.range = function (value, element, param) {
if (this.optional(element)) {
return true;
}
// Remove thousands separators and convert decimal separators
var cleanValue = value.replace(/[.,](?=.*[.,])/g, '').replace(/[.,](?!.*[.,])/g, '.');
var numericValue = parseFloat(cleanValue);
// Check if conversion was successful and value is within range
return !isNaN(numericValue) && (numericValue >= param[0] && numericValue <= param[1]);
};
$.validator.methods.number = function (value, element) {
if (this.optional(element)) {
return true;
}
// 1. Period as decimal separator, comma as thousands separator (e.g., 1,234.56 or -1,234.56)
// 2. Comma as decimal separator, period as thousands separator (e.g., 1.234,56 or -1.234,56)
// 3. Pure numbers (integers)
var pattern1 = /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:-?\.\d+)?$/;
var pattern2 = /^(?:-?\d+|-?\d{1,3}(?:\.\d{3})+)?(?:-?,\d+)?$/;
var pattern3 = /^-?\d+$/;
return pattern1.test(value) || pattern2.test(value) || pattern3.test(value);
}

5
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20250611122251_Initial.Designer.cs → templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20250707124203_Initial.Designer.cs

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations
{ {
[DbContext(typeof(MyProjectNameDbContext))] [DbContext(typeof(MyProjectNameDbContext))]
[Migration("20250611122251_Initial")] [Migration("20250707124203_Initial")]
partial class Initial partial class Initial
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -1229,6 +1229,9 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations
.HasColumnType("nvarchar(max)") .HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties"); .HasColumnName("ExtraProperties");
b.Property<string>("FrontChannelLogoutUri")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted") b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bit") .HasColumnType("bit")

1
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20250611122251_Initial.cs → templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20250707124203_Initial.cs

@ -427,6 +427,7 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations
RedirectUris = table.Column<string>(type: "nvarchar(max)", nullable: true), RedirectUris = table.Column<string>(type: "nvarchar(max)", nullable: true),
Requirements = table.Column<string>(type: "nvarchar(max)", nullable: true), Requirements = table.Column<string>(type: "nvarchar(max)", nullable: true),
Settings = table.Column<string>(type: "nvarchar(max)", nullable: true), Settings = table.Column<string>(type: "nvarchar(max)", nullable: true),
FrontChannelLogoutUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
ClientUri = table.Column<string>(type: "nvarchar(max)", nullable: true), ClientUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
LogoUri = table.Column<string>(type: "nvarchar(max)", nullable: true), LogoUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: false), ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: false),

3
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/MyProjectNameDbContextModelSnapshot.cs

@ -1226,6 +1226,9 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations
.HasColumnType("nvarchar(max)") .HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties"); .HasColumnName("ExtraProperties");
b.Property<string>("FrontChannelLogoutUri")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted") b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bit") .HasColumnType("bit")

5
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20250611122409_Initial.Designer.cs → templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20250707124326_Initial.Designer.cs

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace MyCompanyName.MyProjectName.Migrations namespace MyCompanyName.MyProjectName.Migrations
{ {
[DbContext(typeof(MyProjectNameDbContext))] [DbContext(typeof(MyProjectNameDbContext))]
[Migration("20250611122409_Initial")] [Migration("20250707124326_Initial")]
partial class Initial partial class Initial
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -1229,6 +1229,9 @@ namespace MyCompanyName.MyProjectName.Migrations
.HasColumnType("nvarchar(max)") .HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties"); .HasColumnName("ExtraProperties");
b.Property<string>("FrontChannelLogoutUri")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted") b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bit") .HasColumnType("bit")

1
templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20250611122321_Initial.cs → templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20250707124326_Initial.cs

@ -427,6 +427,7 @@ namespace MyCompanyName.MyProjectName.Migrations
RedirectUris = table.Column<string>(type: "nvarchar(max)", nullable: true), RedirectUris = table.Column<string>(type: "nvarchar(max)", nullable: true),
Requirements = table.Column<string>(type: "nvarchar(max)", nullable: true), Requirements = table.Column<string>(type: "nvarchar(max)", nullable: true),
Settings = table.Column<string>(type: "nvarchar(max)", nullable: true), Settings = table.Column<string>(type: "nvarchar(max)", nullable: true),
FrontChannelLogoutUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
ClientUri = table.Column<string>(type: "nvarchar(max)", nullable: true), ClientUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
LogoUri = table.Column<string>(type: "nvarchar(max)", nullable: true), LogoUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: false), ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: false),

3
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/MyProjectNameDbContextModelSnapshot.cs

@ -1226,6 +1226,9 @@ namespace MyCompanyName.MyProjectName.Migrations
.HasColumnType("nvarchar(max)") .HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties"); .HasColumnName("ExtraProperties");
b.Property<string>("FrontChannelLogoutUri")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted") b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bit") .HasColumnType("bit")

5
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20250611122225_Initial.Designer.cs → templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20250707124118_Initial.Designer.cs

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace MyCompanyName.MyProjectName.Host.Migrations namespace MyCompanyName.MyProjectName.Host.Migrations
{ {
[DbContext(typeof(MyProjectNameDbContext))] [DbContext(typeof(MyProjectNameDbContext))]
[Migration("20250611122225_Initial")] [Migration("20250707124118_Initial")]
partial class Initial partial class Initial
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -1229,6 +1229,9 @@ namespace MyCompanyName.MyProjectName.Host.Migrations
.HasColumnType("nvarchar(max)") .HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties"); .HasColumnName("ExtraProperties");
b.Property<string>("FrontChannelLogoutUri")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted") b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bit") .HasColumnType("bit")

1
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20250611122225_Initial.cs → templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20250707124118_Initial.cs

@ -427,6 +427,7 @@ namespace MyCompanyName.MyProjectName.Host.Migrations
RedirectUris = table.Column<string>(type: "nvarchar(max)", nullable: true), RedirectUris = table.Column<string>(type: "nvarchar(max)", nullable: true),
Requirements = table.Column<string>(type: "nvarchar(max)", nullable: true), Requirements = table.Column<string>(type: "nvarchar(max)", nullable: true),
Settings = table.Column<string>(type: "nvarchar(max)", nullable: true), Settings = table.Column<string>(type: "nvarchar(max)", nullable: true),
FrontChannelLogoutUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
ClientUri = table.Column<string>(type: "nvarchar(max)", nullable: true), ClientUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
LogoUri = table.Column<string>(type: "nvarchar(max)", nullable: true), LogoUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: false), ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: false),

3
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/MyProjectNameDbContextModelSnapshot.cs

@ -1226,6 +1226,9 @@ namespace MyCompanyName.MyProjectName.Host.Migrations
.HasColumnType("nvarchar(max)") .HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties"); .HasColumnName("ExtraProperties");
b.Property<string>("FrontChannelLogoutUri")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted") b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bit") .HasColumnType("bit")

5
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20250611122237_Initial.Designer.cs → templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20250707124141_Initial.Designer.cs

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace MyCompanyName.MyProjectName.Mvc.Migrations namespace MyCompanyName.MyProjectName.Mvc.Migrations
{ {
[DbContext(typeof(MyProjectNameDbContext))] [DbContext(typeof(MyProjectNameDbContext))]
[Migration("20250611122237_Initial")] [Migration("20250707124141_Initial")]
partial class Initial partial class Initial
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -1229,6 +1229,9 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations
.HasColumnType("nvarchar(max)") .HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties"); .HasColumnName("ExtraProperties");
b.Property<string>("FrontChannelLogoutUri")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted") b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bit") .HasColumnType("bit")

1
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20250611122237_Initial.cs → templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20250707124141_Initial.cs

@ -427,6 +427,7 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations
RedirectUris = table.Column<string>(type: "nvarchar(max)", nullable: true), RedirectUris = table.Column<string>(type: "nvarchar(max)", nullable: true),
Requirements = table.Column<string>(type: "nvarchar(max)", nullable: true), Requirements = table.Column<string>(type: "nvarchar(max)", nullable: true),
Settings = table.Column<string>(type: "nvarchar(max)", nullable: true), Settings = table.Column<string>(type: "nvarchar(max)", nullable: true),
FrontChannelLogoutUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
ClientUri = table.Column<string>(type: "nvarchar(max)", nullable: true), ClientUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
LogoUri = table.Column<string>(type: "nvarchar(max)", nullable: true), LogoUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: false), ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: false),

3
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/MyProjectNameDbContextModelSnapshot.cs

@ -1226,6 +1226,9 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations
.HasColumnType("nvarchar(max)") .HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties"); .HasColumnName("ExtraProperties");
b.Property<string>("FrontChannelLogoutUri")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted") b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bit") .HasColumnType("bit")

5
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20250611122258_Initial.Designer.cs → templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20250707124211_Initial.Designer.cs

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace MyCompanyName.MyProjectName.Migrations namespace MyCompanyName.MyProjectName.Migrations
{ {
[DbContext(typeof(MyProjectNameDbContext))] [DbContext(typeof(MyProjectNameDbContext))]
[Migration("20250611122258_Initial")] [Migration("20250707124211_Initial")]
partial class Initial partial class Initial
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -1285,6 +1285,9 @@ namespace MyCompanyName.MyProjectName.Migrations
.HasColumnType("nvarchar(max)") .HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties"); .HasColumnName("ExtraProperties");
b.Property<string>("FrontChannelLogoutUri")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted") b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bit") .HasColumnType("bit")

1
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20250611122258_Initial.cs → templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20250707124211_Initial.cs

@ -449,6 +449,7 @@ namespace MyCompanyName.MyProjectName.Migrations
RedirectUris = table.Column<string>(type: "nvarchar(max)", nullable: true), RedirectUris = table.Column<string>(type: "nvarchar(max)", nullable: true),
Requirements = table.Column<string>(type: "nvarchar(max)", nullable: true), Requirements = table.Column<string>(type: "nvarchar(max)", nullable: true),
Settings = table.Column<string>(type: "nvarchar(max)", nullable: true), Settings = table.Column<string>(type: "nvarchar(max)", nullable: true),
FrontChannelLogoutUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
ClientUri = table.Column<string>(type: "nvarchar(max)", nullable: true), ClientUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
LogoUri = table.Column<string>(type: "nvarchar(max)", nullable: true), LogoUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: false), ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: false),

3
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/MyProjectNameDbContextModelSnapshot.cs

@ -1282,6 +1282,9 @@ namespace MyCompanyName.MyProjectName.Migrations
.HasColumnType("nvarchar(max)") .HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties"); .HasColumnName("ExtraProperties");
b.Property<string>("FrontChannelLogoutUri")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted") b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bit") .HasColumnType("bit")

5
templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20250611122321_Initial.Designer.cs → templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20250707124242_Initial.Designer.cs

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace MyCompanyName.MyProjectName.Migrations namespace MyCompanyName.MyProjectName.Migrations
{ {
[DbContext(typeof(AuthServerDbContext))] [DbContext(typeof(AuthServerDbContext))]
[Migration("20250611122321_Initial")] [Migration("20250707124242_Initial")]
partial class Initial partial class Initial
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -1229,6 +1229,9 @@ namespace MyCompanyName.MyProjectName.Migrations
.HasColumnType("nvarchar(max)") .HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties"); .HasColumnName("ExtraProperties");
b.Property<string>("FrontChannelLogoutUri")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted") b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bit") .HasColumnType("bit")

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save