diff --git a/Directory.Packages.props b/Directory.Packages.props index d0ab5e52a1..439f651454 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -183,10 +183,10 @@ - - - - + + + + diff --git a/docs/en/Blog-Posts/2026-02-23 v10_1_Release_Stable/POST.md b/docs/en/Blog-Posts/2026-02-23 v10_1_Release_Stable/POST.md new file mode 100644 index 0000000000..9b3da381f4 --- /dev/null +++ b/docs/en/Blog-Posts/2026-02-23 v10_1_Release_Stable/POST.md @@ -0,0 +1,82 @@ +# ABP.IO Platform 10.1 Final Has Been Released! + +We are glad to announce that [ABP](https://abp.io/) 10.1 stable version has been released. + +## What's New With Version 10.1? + +All the new features were explained in detail in the [10.1 RC Announcement Post](https://abp.io/community/announcements/announcing-abp-10-1-release-candidate-cyqui19d), so there is no need to review them again. You can check it out for more details. + +## Getting Started with 10.1 + +### How to Upgrade an Existing Solution + +You can upgrade your existing solutions with either ABP Studio or ABP CLI. In the following sections, both approaches are explained: + +### Upgrading via ABP Studio + +If you are already using the ABP Studio, you can upgrade it to the latest version. ABP Studio periodically checks for updates in the background, and when a new version of ABP Studio is available, you will be notified through a modal. Then, you can update it by confirming the opened modal. See [the documentation](https://abp.io/docs/latest/studio/installation#upgrading) for more info. + +After upgrading the ABP Studio, then you can open your solution in the application, and simply click the **Upgrade ABP Packages** action button to instantly upgrade your solution: + +![](upgrade-abp-packages.png) + +### Upgrading via ABP CLI + +Alternatively, you can upgrade your existing solution via ABP CLI. First, you need to install the ABP CLI or upgrade it to the latest version. + +If you haven't installed it yet, you can run the following command: + +```bash +dotnet tool install -g Volo.Abp.Studio.Cli +``` + +Or to update the existing CLI, you can run the following command: + +```bash +dotnet tool update -g Volo.Abp.Studio.Cli +``` + +After installing/updating the ABP CLI, you can use the [`update` command](https://abp.io/docs/latest/CLI#update) to update all the ABP related NuGet and NPM packages in your solution as follows: + +```bash +abp update +``` + +You can run this command in the root folder of your solution to update all ABP related packages. + +## Migration Guides + +There are a few breaking changes in this version that may affect your application. Please read the migration guide carefully, if you are upgrading from v10.0 or earlier versions: [ABP Version 10.1 Migration Guide](https://abp.io/docs/latest/release-info/migration-guides/abp-10-1) + +## Community News + +### New ABP Community Articles + +As always, exciting articles have been contributed by the ABP community. I will highlight some of them here: + +* [Enis Necipoğlu](https://abp.io/community/members/enisn): + * [ABP Framework's Hidden Magic: Things That Just Work Without You Knowing](https://abp.io/community/articles/hidden-magic-things-that-just-work-without-you-knowing-vw6osmyt) + * [Implementing Multiple Global Query Filters with Entity Framework Core](https://abp.io/community/articles/implementing-multiple-global-query-filters-with-entity-ugnsmf6i) +* [Suhaib Mousa](https://abp.io/community/members/suhaib-mousa): + * [.NET 11 Preview 1 Highlights: Faster Runtime, Smarter JIT, and AI-Ready Improvements](https://abp.io/community/articles/dotnet-11-preview-1-highlights-hspp3o5x) + * [TOON vs JSON for LLM Prompts in ABP: Token-Efficient Structured Context](https://abp.io/community/articles/toon-vs-json-b4rn2avd) +* [Fahri Gedik](https://abp.io/community/members/fahrigedik): + * [Building a Multi-Agent AI System with A2A, MCP, and ADK in .NET](https://abp.io/community/articles/building-a-multiagent-ai-system-with-a2a-mcp-iefdehyx) + * [Async Chain of Persistence Pattern: Designing for Failure in Event-Driven Systems](https://abp.io/community/articles/async-chain-of-persistence-pattern-wzjuy4gl) +* [Alper Ebiçoğlu](https://abp.io/community/members/alper): + * [NDC London 2026: From a Developer's Perspective and My Personal Notes about AI](https://abp.io/community/articles/ndc-london-2026-a-.net-conf-from-a-developers-perspective-07wp50yl) + * [Which Open-Source PDF Libraries Are Recently Popular? A Data-Driven Look At PDF Topic](https://abp.io/community/articles/which-opensource-pdf-libraries-are-recently-popular-a-g68q78it) +* [Engincan Veske](https://abp.io/community/members/EngincanV): + * [Stop Spam and Toxic Users in Your App with AI](https://abp.io/community/articles/stop-spam-and-toxic-users-in-your-app-with-ai-3i0xxh0y) +* [Liming Ma](https://abp.io/community/members/maliming): + * [How AI Is Changing Developers](https://abp.io/community/articles/how-ai-is-changing-developers-e8y4a85f) +* [Tarık Özdemir](https://abp.io/community/members/mtozdemir): + * [JetBrains State of Developer Ecosystem Report 2025 — Key Insights](https://abp.io/community/articles/jetbrains-state-of-developer-ecosystem-report-2025-key-z0638q5e) +* [Adnan Ali](https://abp.io/community/members/adnanaldaim): + * [Integrating AI into ABP.IO Applications: The Complete Guide to Volo.Abp.AI and AI Management Module](https://abp.io/community/articles/integrating-ai-into-abp.io-applications-the-complete-guide-jc9fbjq0) + +Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://abp.io/community/posts/create) to the ABP Community. + +## About the Next Version + +The next feature version will be 10.2. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version. diff --git a/docs/en/Blog-Posts/2026-02-23 v10_1_Release_Stable/cover-image.png b/docs/en/Blog-Posts/2026-02-23 v10_1_Release_Stable/cover-image.png new file mode 100644 index 0000000000..3e2e01b18c Binary files /dev/null and b/docs/en/Blog-Posts/2026-02-23 v10_1_Release_Stable/cover-image.png differ diff --git a/docs/en/Blog-Posts/2026-02-23 v10_1_Release_Stable/upgrade-abp-packages.png b/docs/en/Blog-Posts/2026-02-23 v10_1_Release_Stable/upgrade-abp-packages.png new file mode 100644 index 0000000000..4ec1d19589 Binary files /dev/null and b/docs/en/Blog-Posts/2026-02-23 v10_1_Release_Stable/upgrade-abp-packages.png differ diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 3f1abf424f..780ad88523 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -1627,6 +1627,10 @@ "text": "Localization", "path": "framework/ui/angular/localization.md" }, + { + "text": "Hybrid Localization", + "path": "framework/ui/angular/hybrid-localization.md" + }, { "text": "Form Validation", "path": "framework/ui/angular/form-validation.md" diff --git a/docs/en/framework/infrastructure/background-jobs/tickerq.md b/docs/en/framework/infrastructure/background-jobs/tickerq.md index 013a8fde74..de7b3631f2 100644 --- a/docs/en/framework/infrastructure/background-jobs/tickerq.md +++ b/docs/en/framework/infrastructure/background-jobs/tickerq.md @@ -38,16 +38,19 @@ public override void ConfigureServices(ServiceConfigurationContext context) ### UseAbpTickerQ -You need to call the `UseAbpTickerQ` extension method instead of `AddTickerQ` in the `OnApplicationInitialization` method of your module: +You need to call the `UseAbpTickerQ` extension method in the `OnApplicationInitialization` method of your module: ```csharp -// (default: TickerQStartMode.Immediate) -app.UseAbpTickerQ(startMode: ...); +public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) +{ + // (default: TickerQStartMode.Immediate) + context.GetHost().UseAbpTickerQ(qStartMode: ...); +} ``` ### AbpBackgroundJobsTickerQOptions -You can configure the `TimeTicker` properties for specific jobs. For example, you can change `Priority`, `Retries` and `RetryIntervals` properties as shown below: +You can configure the `TimeTickerEntity` properties for specific jobs. For example, you can change `Priority`, `Retries` and `RetryIntervals` properties as shown below: ```csharp Configure(options => @@ -56,11 +59,10 @@ Configure(options => { Retries = 3, RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min - Priority = TickerTaskPriority.High + Priority = TickerTaskPriority.High, - // Optional batching - //BatchParent = Guid.Parse("...."), - //BatchRunCondition = BatchRunCondition.OnSuccess + // Optional: run condition for chained jobs + //RunCondition = RunCondition.OnSuccess }); options.AddJobConfiguration(new AbpBackgroundJobsTimeTickerConfiguration() @@ -74,7 +76,7 @@ Configure(options => ### Add your own TickerQ Background Jobs Definitions -ABP will handle the TickerQ job definitions by `AbpTickerQFunctionProvider` service. You shouldn't use `TickerFunction` to add your own job definitions. You can inject and use the `AbpTickerQFunctionProvider` to add your own definitions and use `ITimeTickerManager` or `ICronTickerManager` to manage the jobs. +ABP will handle the TickerQ job definitions by `AbpTickerQFunctionProvider` service. You shouldn't use `TickerFunction` to add your own job definitions. You can inject and use the `AbpTickerQFunctionProvider` to add your own definitions and use `ITimeTickerManager` or `ICronTickerManager` to manage the jobs. For example, you can add a `CleanupJobs` job definition in the `OnPreApplicationInitializationAsync` method of your module: @@ -96,7 +98,7 @@ public override Task OnPreApplicationInitializationAsync(ApplicationInitializati abpTickerQFunctionProvider.Functions.TryAdd(nameof(CleanupJobs), (string.Empty, TickerTaskPriority.Normal, new TickerFunctionDelegate(async (cancellationToken, serviceProvider, tickerFunctionContext) => { var service = new CleanupJobs(); // Or get it from the serviceProvider - var request = await TickerRequestProvider.GetRequestAsync(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type); + var request = await TickerRequestProvider.GetRequestAsync(tickerFunctionContext, cancellationToken); var genericContext = new TickerFunctionContext(tickerFunctionContext, request); await service.CleanupLogsAsync(genericContext, cancellationToken); }))); @@ -105,11 +107,11 @@ public override Task OnPreApplicationInitializationAsync(ApplicationInitializati } ``` -And then you can add a job by using the `ITimeTickerManager`: +And then you can add a job by using the `ITimeTickerManager`: ```csharp -var timeTickerManager = context.ServiceProvider.GetRequiredService>(); -await timeTickerManager.AddAsync(new TimeTicker +var timeTickerManager = context.ServiceProvider.GetRequiredService>(); +await timeTickerManager.AddAsync(new TimeTickerEntity { Function = nameof(CleanupJobs), ExecutionTime = DateTime.UtcNow.AddSeconds(5), diff --git a/docs/en/framework/infrastructure/background-workers/tickerq.md b/docs/en/framework/infrastructure/background-workers/tickerq.md index 840b5137cb..d4ddde3cd9 100644 --- a/docs/en/framework/infrastructure/background-workers/tickerq.md +++ b/docs/en/framework/infrastructure/background-workers/tickerq.md @@ -36,11 +36,14 @@ public override void ConfigureServices(ServiceConfigurationContext context) ### UseAbpTickerQ -You need to call the `UseAbpTickerQ` extension method instead of `AddTickerQ` in the `OnApplicationInitialization` method of your module: +You need to call the `UseAbpTickerQ` extension method in the `OnApplicationInitialization` method of your module: ```csharp -// (default: TickerQStartMode.Immediate) -app.UseAbpTickerQ(startMode: ...); +public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) +{ + // (default: TickerQStartMode.Immediate) + context.GetHost().UseAbpTickerQ(qStartMode: ...); +} ``` ### AbpBackgroundWorkersTickerQOptions @@ -61,7 +64,7 @@ Configure(options => ### Add your own TickerQ Background Worker Definitions -ABP will handle the TickerQ job definitions by `AbpTickerQFunctionProvider` service. You shouldn't use `TickerFunction` to add your own job definitions. You can inject and use the `AbpTickerQFunctionProvider` to add your own definitions and use `ITimeTickerManager` or `ICronTickerManager` to manage the jobs. +ABP will handle the TickerQ job definitions by `AbpTickerQFunctionProvider` service. You shouldn't use `TickerFunction` to add your own job definitions. You can inject and use the `AbpTickerQFunctionProvider` to add your own definitions and use `ITimeTickerManager` or `ICronTickerManager` to manage the jobs. For example, you can add a `CleanupJobs` job definition in the `OnPreApplicationInitializationAsync` method of your module: @@ -83,7 +86,7 @@ public override Task OnPreApplicationInitializationAsync(ApplicationInitializati abpTickerQFunctionProvider.Functions.TryAdd(nameof(CleanupJobs), (string.Empty, TickerTaskPriority.Normal, new TickerFunctionDelegate(async (cancellationToken, serviceProvider, tickerFunctionContext) => { var service = new CleanupJobs(); // Or get it from the serviceProvider - var request = await TickerRequestProvider.GetRequestAsync(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type); + var request = await TickerRequestProvider.GetRequestAsync(tickerFunctionContext, cancellationToken); var genericContext = new TickerFunctionContext(tickerFunctionContext, request); await service.CleanupLogsAsync(genericContext, cancellationToken); }))); @@ -92,11 +95,11 @@ public override Task OnPreApplicationInitializationAsync(ApplicationInitializati } ``` -And then you can add a job by using the `ICronTickerManager`: +And then you can add a job by using the `ICronTickerManager`: ```csharp -var cronTickerManager = context.ServiceProvider.GetRequiredService>(); -await cronTickerManager.AddAsync(new CronTicker +var cronTickerManager = context.ServiceProvider.GetRequiredService>(); +await cronTickerManager.AddAsync(new CronTickerEntity { Function = nameof(CleanupJobs), Expression = "0 */6 * * *", // Every 6 hours @@ -106,13 +109,13 @@ await cronTickerManager.AddAsync(new CronTicker }); ``` -You can specify a cron expression instead of use `ICronTickerManager` to add a worker: +You can specify a cron expression instead of using `ICronTickerManager` to add a worker: ```csharp abpTickerQFunctionProvider.Functions.TryAdd(nameof(CleanupJobs), (string.Empty, TickerTaskPriority.Normal, new TickerFunctionDelegate(async (cancellationToken, serviceProvider, tickerFunctionContext) => { var service = new CleanupJobs(); - var request = await TickerRequestProvider.GetRequestAsync(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type); + var request = await TickerRequestProvider.GetRequestAsync(tickerFunctionContext, cancellationToken); var genericContext = new TickerFunctionContext(tickerFunctionContext, request); await service.CleanupLogsAsync(genericContext, cancellationToken); }))); diff --git a/docs/en/framework/ui/angular/hybrid-localization.md b/docs/en/framework/ui/angular/hybrid-localization.md new file mode 100644 index 0000000000..f71025237c --- /dev/null +++ b/docs/en/framework/ui/angular/hybrid-localization.md @@ -0,0 +1,162 @@ +```json +//[doc-seo] +{ + "Description": "Combine backend and UI localizations in Angular: use JSON files to override or extend server-sidtexts with the same key format and abpLocalization pipe." +} +``` + +# Hybrid Localization + +Hybrid localization lets you combine **backend localizations** (from the ABP server) with **UI localizations** (JSON files in your Angular app). UI values take priority over backend values for the same key, so you can override or extend server-side texts without changing the backend. + +## How It Works + +- **Backend localizations**: Loaded from the server (e.g. `ApplicationLocalizationResourceDto`). Keys use the format `ResourceName::Key`. +- **UI localizations**: Loaded from static JSON files under your app's assets (e.g. `/assets/localization/en.json`). The same key format `ResourceName::Key` is used. +- **Priority**: When a key exists in both backend and UI, the **UI value is used** (UI overrides backend). + +The existing `abpLocalization` pipe and localization APIs work unchanged; they resolve keys from the merged set (backend + UI), with UI winning on conflicts. + +## Configuration + +Enable hybrid localization in your app config via `provideAbpCore` and `withOptions`: + +```typescript +// app.config.ts +import { provideAbpCore, withOptions } from "@abp/ng.core"; + +export const appConfig: ApplicationConfig = { + providers: [ + provideAbpCore( + withOptions({ + // ...other options + uiLocalization: { + enabled: true, + basePath: "/assets/localization", // optional; default is '/assets/localization' + }, + }), + ), + // ... + ], +}; +``` + +| Option | Description | Default | +| ---------- | ---------------------------------------------------------------------------- | ------------------------ | +| `enabled` | Turn on UI localization loading from `{basePath}/{culture}.json`. | — | +| `basePath` | Base path for JSON files. Files are loaded from `{basePath}/{culture}.json`. | `'/assets/localization'` | + +When `enabled` is `true`, the app loads a JSON file for the current language (e.g. `en`, `tr`) whenever the user changes language. Loaded data is merged with backend localizations (UI overrides backend for the same key). + +## UI Localization File Format + +Place one JSON file per culture under your `basePath`. File name must be `{culture}.json` (e.g. `en.json`, `tr.json`). + +Structure: **resource name → key → value**. + +```json +{ + "MyProjectName": { + "Welcome": "Welcome from UI (en.json)", + "CustomKey": "This is a UI-only localization", + "TestMessage": "UI localization is working!" + }, + "AbpAccount": { + "Login": "Sign In (UI Override)" + } +} +``` + +- Top-level keys are **resource names** (e.g. `MyProjectName`, `AbpAccount`). +- Nested keys are **localization keys**; values are the display strings for that culture. + +In templates you keep using the same key format: `ResourceName::Key`. + +## Using in Templates + +Use the `abpLocalization` pipe as usual. Keys can come from backend only, UI only, or both (UI wins): + +```html + +

{%{{ 'MyProjectName::Welcome' | abpLocalization }}%}

+ + +

{%{{ 'MyProjectName::CustomKey' | abpLocalization }}%}

+ + +

{%{{ 'AbpAccount::Login' | abpLocalization }}%}

+``` + +No template changes are needed; only the configuration and the JSON files. + +## UILocalizationService + +The `UILocalizationService` (`@abp/ng.core`) manages UI localizations and merges them with backend data. + +### Get loaded UI data + +To inspect what was loaded from the UI JSON files (e.g. for debugging or display): + +```typescript +import { UILocalizationService, SessionStateService } from "@abp/ng.core"; + +export class MyComponent { + private uiLocalizationService = inject(UILocalizationService); + private sessionState = inject(SessionStateService); + + currentLanguage$ = this.sessionState.getLanguage$(); + + ngOnInit() { + // All loaded UI resources for current language + const loaded = this.uiLocalizationService.getLoadedLocalizations(); + // Or for a specific culture + const loadedEn = this.uiLocalizationService.getLoadedLocalizations("en"); + } +} +``` + +`getLoadedLocalizations(culture?: string)` returns an object of the form `{ [resourceName: string]: Record }` for the given culture (or current language if omitted). + +### Add translations at runtime + +You can also add or merge UI translations programmatically (e.g. from another source or lazy-loaded module): + +```typescript +this.uiLocalizationService.addAngularLocalizeLocalization( + 'en', // culture + 'MyProjectName', // resource name + { MyKey: 'My value' }, // key-value map +); +``` + +This merges into the existing UI localizations and is taken into account by the `abpLocalization` pipe with the same UI-over-backend priority. + +## Example: Dev App + +The ABP dev app demonstrates hybrid localization: + +1. **Config** (`app.config.ts`): + +```typescript +uiLocalization: { + enabled: true, + basePath: '/assets/localization', +}, +``` + +2. **Files**: `src/assets/localization/en.json` and `src/assets/localization/tr.json` with the structure shown above. + +3. **Component** (`localization-test.component.ts`): Uses `abpLocalization` for backend keys, UI-only keys, and overrides; and uses `UILocalizationService.getLoadedLocalizations()` to show loaded UI data. + +See `apps/dev-app/src/app/localization-test/localization-test.component.ts` and `apps/dev-app/src/assets/localization/*.json` in the repository for the full example. + +## Summary + +| Topic | Description | +|------------------|-------------| +| **Purpose** | Combine backend and UI localizations; UI overrides backend for the same key. | +| **Config** | `provideAbpCore(withOptions({ uiLocalization: { enabled: true, basePath?: string } }))`. | +| **File location**| `{basePath}/{culture}.json` (e.g. `/assets/localization/en.json`). | +| **JSON format** | `{ "ResourceName": { "Key": "Value", ... }, ... }`. | +| **Template usage** | Same as before: `{%{{ 'ResourceName::Key' \| abpLocalization }}%}`. | +| **API** | `UILocalizationService`: `getLoadedLocalizations(culture?)`, `addAngularLocalizeLocalization(culture, resourceName, translations)`. | diff --git a/docs/en/package-version-changes.md b/docs/en/package-version-changes.md index c4d8b0722f..bb31239a3c 100644 --- a/docs/en/package-version-changes.md +++ b/docs/en/package-version-changes.md @@ -8,6 +8,10 @@ | Blazorise.Components | 1.8.8 | 2.0.0 | #24906 | | Blazorise.DataGrid | 1.8.8 | 2.0.0 | #24906 | | Blazorise.Snackbar | 1.8.8 | 2.0.0 | #24906 | +| TickerQ | 2.5.3 | 10.1.1 | #24916 | +| TickerQ.Dashboard | 2.5.3 | 10.1.1 | #24916 | +| TickerQ.EntityFrameworkCore | 2.5.3 | 10.1.1 | #24916 | +| TickerQ.Utilities | 2.5.3 | 10.1.1 | #24916 | ## 10.1.0 @@ -15,4 +19,3 @@ |---------|-------------|-------------|-----| | Microsoft.SemanticKernel | 1.67.1 | 1.71.0 | #24891 | | Microsoft.SemanticKernel.Abstractions | 1.67.1 | 1.71.0 | #24891 | - diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationConfigurationClientProxy.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationConfigurationClientProxy.cs index 62b582b01b..8b2001a17f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationConfigurationClientProxy.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationConfigurationClientProxy.cs @@ -1,12 +1,9 @@ // This file is part of AbpApplicationConfigurationClientProxy, you can customize it here // ReSharper disable once CheckNamespace -using Volo.Abp.DependencyInjection; - namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies; [RemoteService(false)] -[DisableConventionalRegistration] public partial class AbpApplicationConfigurationClientProxy { } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpTenantClientProxy.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpTenantClientProxy.cs index 8ca24d1317..f8bf2389e0 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpTenantClientProxy.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpTenantClientProxy.cs @@ -2,13 +2,10 @@ // ReSharper disable once CheckNamespace using Volo.Abp; -using Volo.Abp.DependencyInjection; namespace Pages.Abp.MultiTenancy.ClientProxies; [RemoteService(false)] -[DisableConventionalRegistration] public partial class AbpTenantClientProxy { } - diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientCommonModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientCommonModule.cs index b7cd9d3a30..a9ab95cbc0 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientCommonModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientCommonModule.cs @@ -1,6 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using Pages.Abp.MultiTenancy.ClientProxies; -using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies; using Volo.Abp.Authorization; using Volo.Abp.Caching; using Volo.Abp.Features; @@ -39,9 +37,6 @@ public class AbpAspNetCoreMvcClientCommonModule : AbpModule options.GlobalContributors.Add(); }); - context.Services.AddTransient(); - context.Services.AddTransient(); - var abpClaimsPrincipalFactoryOptions = context.Services.ExecutePreConfiguredActions(); if (abpClaimsPrincipalFactoryOptions.IsRemoteRefreshEnabled) { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs index de38e89104..8910ce1ce7 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs @@ -17,14 +17,14 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; [ApiExplorerSettings(IgnoreApi = true)] public class AbpApplicationConfigurationScriptController : AbpController { - protected readonly AbpApplicationConfigurationAppService ConfigurationAppService; + protected readonly IAbpApplicationConfigurationAppService ConfigurationAppService; protected readonly IJsonSerializer JsonSerializer; protected readonly AbpAspNetCoreMvcOptions Options; protected readonly IJavascriptMinifier JavascriptMinifier; protected readonly IAbpAntiForgeryManager AntiForgeryManager; public AbpApplicationConfigurationScriptController( - AbpApplicationConfigurationAppService configurationAppService, + IAbpApplicationConfigurationAppService configurationAppService, IJsonSerializer jsonSerializer, IOptions options, IJavascriptMinifier javascriptMinifier, diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs index 3a283cbe36..aab5b5cc2b 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs @@ -38,6 +38,18 @@ public static class AbpApplicationBuilderExtensions Check.NotNull(app, nameof(app)); app.ApplicationServices.GetRequiredService>().Value = app; + if (app is WebApplication webApplication) + { + app.ApplicationServices.GetRequiredService>().Value = webApplication; + } + if (app is IHost host) + { + app.ApplicationServices.GetRequiredService>().Value = host; + } + if (app is IEndpointRouteBuilder endpointRouteBuilder) + { + app.ApplicationServices.GetRequiredService>().Value = endpointRouteBuilder; + } var application = app.ApplicationServices.GetRequiredService(); var applicationLifetime = app.ApplicationServices.GetRequiredService(); @@ -59,6 +71,18 @@ public static class AbpApplicationBuilderExtensions Check.NotNull(app, nameof(app)); app.ApplicationServices.GetRequiredService>().Value = app; + if (app is WebApplication webApplication) + { + app.ApplicationServices.GetRequiredService>().Value = webApplication; + } + if (app is IHost host) + { + app.ApplicationServices.GetRequiredService>().Value = host; + } + if (app is IEndpointRouteBuilder endpointRouteBuilder) + { + app.ApplicationServices.GetRequiredService>().Value = endpointRouteBuilder; + } var application = app.ApplicationServices.GetRequiredService(); var applicationLifetime = app.ApplicationServices.GetRequiredService(); diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/ApplicationInitializationContextExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/ApplicationInitializationContextExtensions.cs index 1e361d1587..930b081df0 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/ApplicationInitializationContextExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/ApplicationInitializationContextExtensions.cs @@ -1,7 +1,9 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Volo.Abp.DependencyInjection; @@ -21,6 +23,42 @@ public static class ApplicationInitializationContextExtensions return context.ServiceProvider.GetRequiredService>().Value; } + public static IHost GetHost(this ApplicationInitializationContext context) + { + var host = context.ServiceProvider.GetRequiredService>().Value; + Check.NotNull(host, nameof(host)); + return host; + } + + public static IHost? GetHostOrNull(this ApplicationInitializationContext context) + { + return context.ServiceProvider.GetRequiredService>().Value; + } + + public static IEndpointRouteBuilder GetEndpointRouteBuilder(this ApplicationInitializationContext context) + { + var endpointRouteBuilder = context.ServiceProvider.GetRequiredService>().Value; + Check.NotNull(endpointRouteBuilder, nameof(endpointRouteBuilder)); + return endpointRouteBuilder; + } + + public static IEndpointRouteBuilder? GetEndpointRouteBuilderOrNull(this ApplicationInitializationContext context) + { + return context.ServiceProvider.GetRequiredService>().Value; + } + + public static WebApplication GetWebApplication(this ApplicationInitializationContext context) + { + var webApplication = context.ServiceProvider.GetRequiredService>().Value; + Check.NotNull(webApplication, nameof(webApplication)); + return webApplication; + } + + public static WebApplication? GetWebApplicationOrNull(this ApplicationInitializationContext context) + { + return context.ServiceProvider.GetRequiredService>().Value; + } + public static IWebHostEnvironment GetEnvironment(this ApplicationInitializationContext context) { return context.ServiceProvider.GetRequiredService(); diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreModule.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreModule.cs index 405e49753c..a457d3b1eb 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreModule.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreModule.cs @@ -2,7 +2,9 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting.StaticWebAssets; using Microsoft.AspNetCore.RequestLocalization; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.FileProviders; using MyCSharp.HttpUserAgentParser.DependencyInjection; using Volo.Abp.AspNetCore.Auditing; @@ -57,6 +59,9 @@ public class AbpAspNetCoreModule : AbpModule AddAspNetServices(context.Services); context.Services.AddObjectAccessor(); + context.Services.AddObjectAccessor(); + context.Services.AddObjectAccessor(); + context.Services.AddObjectAccessor(); context.Services.AddAbpDynamicOptions(); StaticWebAssetsLoader.UseStaticWebAssets(context.Services.GetHostingEnvironment(), context.Services.GetConfiguration()); diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo.Abp.BackgroundJobs.TickerQ.csproj b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo.Abp.BackgroundJobs.TickerQ.csproj index df450f5ff6..41d70914a4 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo.Abp.BackgroundJobs.TickerQ.csproj +++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo.Abp.BackgroundJobs.TickerQ.csproj @@ -4,7 +4,7 @@ - netstandard2.1;net8.0;net9.0;net10.0 + net10.0 enable Nullable Volo.Abp.BackgroundJobs.TickerQ diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs index b5ed598932..3d93fc68a1 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs @@ -65,7 +65,7 @@ public class AbpBackgroundJobsTickerQModule : AbpModule using (var scope = serviceProvider.CreateScope()) { var jobExecuter = serviceProvider.GetRequiredService(); - var args = await TickerRequestProvider.GetRequestAsync(serviceProvider, context.Id, context.Type); + var args = await TickerRequestProvider.GetRequestAsync(context, cancellationToken); var jobType = options.GetJob(typeof(TArgs)).JobType; var jobExecutionContext = new JobExecutionContext(scope.ServiceProvider, jobType, args!, cancellationToken: cancellationToken); await jobExecuter.ExecuteAsync(jobExecutionContext); diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTimeTickerConfiguration.cs b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTimeTickerConfiguration.cs index 65b150ea48..ecceaeb28a 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTimeTickerConfiguration.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTimeTickerConfiguration.cs @@ -1,4 +1,3 @@ -using System; using TickerQ.Utilities.Enums; namespace Volo.Abp.BackgroundJobs.TickerQ; @@ -11,7 +10,5 @@ public class AbpBackgroundJobsTimeTickerConfiguration public TickerTaskPriority? Priority { get; set; } - public Guid? BatchParent { get; set; } - - public BatchRunCondition? BatchRunCondition { get; set; } + public RunCondition? RunCondition { get; set; } } diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpTickerQBackgroundJobManager.cs b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpTickerQBackgroundJobManager.cs index b1a2dbe36d..638c1845f2 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpTickerQBackgroundJobManager.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpTickerQBackgroundJobManager.cs @@ -2,8 +2,8 @@ using System.Threading.Tasks; using Microsoft.Extensions.Options; using TickerQ.Utilities; +using TickerQ.Utilities.Entities; using TickerQ.Utilities.Interfaces.Managers; -using TickerQ.Utilities.Models.Ticker; using Volo.Abp.DependencyInjection; namespace Volo.Abp.BackgroundJobs.TickerQ; @@ -11,12 +11,12 @@ namespace Volo.Abp.BackgroundJobs.TickerQ; [Dependency(ReplaceServices = true)] public class AbpTickerQBackgroundJobManager : IBackgroundJobManager, ITransientDependency { - protected ITimeTickerManager TimeTickerManager { get; } + protected ITimeTickerManager TimeTickerManager { get; } protected AbpBackgroundJobOptions Options { get; } protected AbpBackgroundJobsTickerQOptions TickerQOptions { get; } public AbpTickerQBackgroundJobManager( - ITimeTickerManager timeTickerManager, + ITimeTickerManager timeTickerManager, IOptions options, IOptions tickerQOptions) { @@ -28,7 +28,7 @@ public class AbpTickerQBackgroundJobManager : IBackgroundJobManager, ITransientD public virtual async Task EnqueueAsync(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) { var job = Options.GetJob(typeof(TArgs)); - var timeTicker = new TimeTicker + var timeTicker = new TimeTickerEntity { Id = Guid.NewGuid(), Function = job.JobName, @@ -41,11 +41,10 @@ public class AbpTickerQBackgroundJobManager : IBackgroundJobManager, ITransientD { timeTicker.Retries = config.Retries ?? timeTicker.Retries; timeTicker.RetryIntervals = config.RetryIntervals ?? timeTicker.RetryIntervals; - timeTicker.BatchParent = config.BatchParent ?? timeTicker.BatchParent; - timeTicker.BatchRunCondition = config.BatchRunCondition ?? timeTicker.BatchRunCondition; + timeTicker.RunCondition = config.RunCondition ?? timeTicker.RunCondition; } var result = await TimeTickerManager.AddAsync(timeTicker); - return !result.IsSucceded ? timeTicker.Id.ToString() : result.Result.Id.ToString(); + return !result.IsSucceeded ? timeTicker.Id.ToString() : result.Result.Id.ToString(); } } diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.TickerQ.csproj b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.TickerQ.csproj index 2c4ab29c97..b62024131d 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.TickerQ.csproj +++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.TickerQ.csproj @@ -4,7 +4,7 @@ - netstandard2.1;net8.0;net9.0;net10.0 + net10.0 enable Nullable Volo.Abp.BackgroundWorkers.TickerQ diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQModule.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQModule.cs index 3fb15a50b8..788eee1759 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQModule.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQModule.cs @@ -1,8 +1,8 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using TickerQ.Utilities.Entities; using TickerQ.Utilities.Interfaces.Managers; -using TickerQ.Utilities.Models.Ticker; using Volo.Abp.Modularity; using Volo.Abp.TickerQ; @@ -14,11 +14,11 @@ public class AbpBackgroundWorkersTickerQModule : AbpModule public override async Task OnPostApplicationInitializationAsync(ApplicationInitializationContext context) { var abpTickerQBackgroundWorkersProvider = context.ServiceProvider.GetRequiredService(); - var cronTickerManager = context.ServiceProvider.GetRequiredService>(); + var cronTickerManager = context.ServiceProvider.GetRequiredService>(); var abpBackgroundWorkersTickerQOptions = context.ServiceProvider.GetRequiredService>().Value; foreach (var backgroundWorker in abpTickerQBackgroundWorkersProvider.BackgroundWorkers) { - var cronTicker = new CronTicker + var cronTicker = new CronTickerEntity { Function = backgroundWorker.Value.Function, Expression = backgroundWorker.Value.CronExpression diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQPeriodicBackgroundWorkerInvoker.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQPeriodicBackgroundWorkerInvoker.cs index 17cf7cdc87..f09a271017 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQPeriodicBackgroundWorkerInvoker.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQPeriodicBackgroundWorkerInvoker.cs @@ -3,7 +3,7 @@ using System.Linq.Expressions; using System.Reflection; using System.Threading; using System.Threading.Tasks; -using TickerQ.Utilities.Models; +using TickerQ.Utilities.Base; namespace Volo.Abp.BackgroundWorkers.TickerQ; diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EfCoreRepositoryExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EfCoreRepositoryExtensions.cs index 614ba135ff..d054733785 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EfCoreRepositoryExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EfCoreRepositoryExtensions.cs @@ -49,6 +49,6 @@ public static class EfCoreRepositoryExtensions public static IQueryable AsNoTrackingIf(this IQueryable queryable, bool condition) where TEntity : class, IEntity { - return condition ? queryable.AsNoTracking() : queryable; + return condition ? queryable.AsNoTracking() : queryable.AsTracking(); } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs index ff4aeabc44..cf8f994d42 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs @@ -478,9 +478,7 @@ public class EfCoreRepository : EfCoreRepository e.Id).FirstOrDefaultAsync(e => e.Id!.Equals(id), GetCancellationToken(cancellationToken)) - : !ShouldTrackingEntityChange() - ? await (await GetQueryableAsync()).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id!.Equals(id), GetCancellationToken(cancellationToken)) - : await (await GetDbSetAsync()).FindAsync(new object[] { id! }, GetCancellationToken(cancellationToken)); + : await (await GetQueryableAsync()).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id!.Equals(id), GetCancellationToken(cancellationToken)); } public virtual async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default) diff --git a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/ObjectMappingHelper.cs b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/ObjectMappingHelper.cs index 5949edaba9..3bc1a1389b 100644 --- a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/ObjectMappingHelper.cs +++ b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/ObjectMappingHelper.cs @@ -55,10 +55,17 @@ public static class ObjectMappingHelper return true; } - if (type.IsGenericType && - supportedCollectionTypes.Contains(type.GetGenericTypeDefinition()) || + if ((type.IsGenericType && + supportedCollectionTypes.Contains(type.GetGenericTypeDefinition())) || type.GetInterfaces().Any(i => i.IsGenericType && supportedCollectionTypes.Contains(i.GetGenericTypeDefinition()))) { + if (!type.IsGenericType) + { + elementType = null!; + definitionGenericType = null!; + return false; + } + elementType = type.GetGenericArguments()[0]; definitionGenericType = type.GetGenericTypeDefinition(); if (definitionGenericType == typeof(IEnumerable<>) || diff --git a/framework/src/Volo.Abp.Swashbuckle/Microsoft/Extensions/DependencyInjection/AbpSwaggerGenServiceCollectionExtensions.cs b/framework/src/Volo.Abp.Swashbuckle/Microsoft/Extensions/DependencyInjection/AbpSwaggerGenServiceCollectionExtensions.cs index d63ca833eb..b81aa8bf77 100644 --- a/framework/src/Volo.Abp.Swashbuckle/Microsoft/Extensions/DependencyInjection/AbpSwaggerGenServiceCollectionExtensions.cs +++ b/framework/src/Volo.Abp.Swashbuckle/Microsoft/Extensions/DependencyInjection/AbpSwaggerGenServiceCollectionExtensions.cs @@ -77,7 +77,8 @@ public static class AbpSwaggerGenServiceCollectionExtensions string[]? scopes = null, string[]? flows = null, string? discoveryEndpoint = null, - Action? setupAction = null) + Action? setupAction = null, + string oidcAuthenticationScheme = "oidc") { var discoveryUrl = discoveryEndpoint != null ? $"{discoveryEndpoint.TrimEnd('/')}/.well-known/openid-configuration": @@ -96,7 +97,7 @@ public static class AbpSwaggerGenServiceCollectionExtensions .AddSwaggerGen( options => { - options.AddSecurityDefinition("oidc", new OpenApiSecurityScheme + options.AddSecurityDefinition(oidcAuthenticationScheme, new OpenApiSecurityScheme { Type = SecuritySchemeType.OpenIdConnect, OpenIdConnectUrl = new Uri(RemoveTenantPlaceholders(discoveryUrl)) @@ -104,7 +105,7 @@ public static class AbpSwaggerGenServiceCollectionExtensions options.AddSecurityRequirement(document => new OpenApiSecurityRequirement() { - [new OpenApiSecuritySchemeReference("oauth2", document)] = [] + [new OpenApiSecuritySchemeReference(oidcAuthenticationScheme, document)] = [] }); setupAction?.Invoke(options); diff --git a/framework/src/Volo.Abp.TickerQ/Microsoft/AspNetCore/Builder/AbpTickerQApplicationBuilderExtensions.cs b/framework/src/Volo.Abp.TickerQ/Microsoft/Extensions/Hosting/AbpTickerQApplicationBuilderExtensions.cs similarity index 61% rename from framework/src/Volo.Abp.TickerQ/Microsoft/AspNetCore/Builder/AbpTickerQApplicationBuilderExtensions.cs rename to framework/src/Volo.Abp.TickerQ/Microsoft/Extensions/Hosting/AbpTickerQApplicationBuilderExtensions.cs index 4e81565e99..4d1897b200 100644 --- a/framework/src/Volo.Abp.TickerQ/Microsoft/AspNetCore/Builder/AbpTickerQApplicationBuilderExtensions.cs +++ b/framework/src/Volo.Abp.TickerQ/Microsoft/Extensions/Hosting/AbpTickerQApplicationBuilderExtensions.cs @@ -1,16 +1,17 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using TickerQ.DependencyInjection; using TickerQ.Utilities; using TickerQ.Utilities.Enums; using Volo.Abp.TickerQ; -namespace Microsoft.AspNetCore.Builder; +namespace Microsoft.Extensions.Hosting; public static class AbpTickerQApplicationBuilderExtensions { - public static IApplicationBuilder UseAbpTickerQ(this IApplicationBuilder app, TickerQStartMode qStartMode = TickerQStartMode.Immediate) + public static IHost UseAbpTickerQ(this IHost app, TickerQStartMode qStartMode = TickerQStartMode.Immediate) { - var abpTickerQFunctionProvider = app.ApplicationServices.GetRequiredService(); + var abpTickerQFunctionProvider = app.Services.GetRequiredService(); TickerFunctionProvider.RegisterFunctions(abpTickerQFunctionProvider.Functions); TickerFunctionProvider.RegisterRequestType(abpTickerQFunctionProvider.RequestTypes); diff --git a/framework/src/Volo.Abp.TickerQ/Volo.Abp.TickerQ.csproj b/framework/src/Volo.Abp.TickerQ/Volo.Abp.TickerQ.csproj index 89a037bab7..51649a9a86 100644 --- a/framework/src/Volo.Abp.TickerQ/Volo.Abp.TickerQ.csproj +++ b/framework/src/Volo.Abp.TickerQ/Volo.Abp.TickerQ.csproj @@ -4,7 +4,7 @@ - netstandard2.1;net8.0;net9.0;net10.0 + net10.0 enable Nullable Volo.Abp.TickerQ diff --git a/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQModule.cs b/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQModule.cs index b6c140e962..82b47294e1 100644 --- a/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQModule.cs +++ b/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQModule.cs @@ -10,7 +10,10 @@ public class AbpTickerQModule : AbpModule { context.Services.AddTickerQ(options => { - options.SetInstanceIdentifier(context.Services.GetApplicationName()); + options.ConfigureScheduler(scheduler => + { + scheduler.NodeIdentifier = context.Services.GetApplicationName(); + }); }); } } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ChangeTracking/ChangeTrackingInterceptor_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ChangeTracking/ChangeTrackingInterceptor_Tests.cs index 87b487b68a..9581df3a19 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ChangeTracking/ChangeTrackingInterceptor_Tests.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ChangeTracking/ChangeTrackingInterceptor_Tests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; using Shouldly; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.ChangeTracking; @@ -83,6 +84,96 @@ public class ChangeTrackingInterceptor_Tests : TestAppTestBase>(); + + Guid personId = default; + await WithUnitOfWorkAsync(async () => + { + var p = await repository.FindAsync(x => x.Name == "people1"); + p.ShouldNotBeNull(); + personId = p.Id; + }); + + // Simulate global NoTracking configured on DbContext (e.g. optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)) + await WithUnitOfWorkAsync(async () => + { + var db = await repository.GetDbContextAsync(); + db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + db.ChangeTracker.Entries().Count().ShouldBe(0); + + // FindAsync(id): ShouldTrackingEntityChange()=true, GetQueryableAsync() uses AsTracking() to override global NoTracking + var person = await repository.FindAsync(personId, includeDetails: false); + person.ShouldNotBeNull(); + db.ChangeTracker.Entries().Count().ShouldBe(1); + db.Entry(person).State.ShouldBe(EntityState.Unchanged); + }); + + await WithUnitOfWorkAsync(async () => + { + var db = await repository.GetDbContextAsync(); + db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + db.ChangeTracker.Entries().Count().ShouldBe(0); + + // FindAsync(predicate): same - AsTracking() overrides global NoTracking + var person = await repository.FindAsync(x => x.Name == "people1"); + person.ShouldNotBeNull(); + db.ChangeTracker.Entries().Count().ShouldBe(1); + db.Entry(person).State.ShouldBe(EntityState.Unchanged); + }); + + await WithUnitOfWorkAsync(async () => + { + var db = await repository.GetDbContextAsync(); + db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + db.ChangeTracker.Entries().Count().ShouldBe(0); + + // GetListAsync: same - AsTracking() overrides global NoTracking + var list = await repository.GetListAsync(); + list.Count.ShouldBeGreaterThan(0); + db.ChangeTracker.Entries().Count().ShouldBe(list.Count); + }); + } + + [Fact] + public async Task Repository_Should_Respect_NoTracking_When_EntityChangeTracking_Is_Disabled_With_Global_NoTracking() + { + await AddSomePeopleAsync(); + + var repository = GetRequiredService>(); + + Guid personId = default; + await WithUnitOfWorkAsync(async () => + { + var p = await repository.FindAsync(x => x.Name == "people1"); + p.ShouldNotBeNull(); + personId = p.Id; + }); + + await WithUnitOfWorkAsync(async () => + { + var db = await repository.GetDbContextAsync(); + db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + db.ChangeTracker.Entries().Count().ShouldBe(0); + + // When tracking is explicitly disabled, entity should NOT be tracked regardless of global setting + using (repository.DisableTracking()) + { + var person = await repository.FindAsync(personId, includeDetails: false); + person.ShouldNotBeNull(); + db.ChangeTracker.Entries().Count().ShouldBe(0); + + var list = await repository.GetListAsync(); + list.Count.ShouldBeGreaterThan(0); + db.ChangeTracker.Entries().Count().ShouldBe(0); + } + }); + } + private async Task AddSomePeopleAsync() { var repository = GetRequiredService>(); diff --git a/framework/test/Volo.Abp.ObjectMapping.Tests/Volo/Abp/ObjectMapping/ObjectMappingHelper_Tests.cs b/framework/test/Volo.Abp.ObjectMapping.Tests/Volo/Abp/ObjectMapping/ObjectMappingHelper_Tests.cs new file mode 100644 index 0000000000..c262dad81a --- /dev/null +++ b/framework/test/Volo.Abp.ObjectMapping.Tests/Volo/Abp/ObjectMapping/ObjectMappingHelper_Tests.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Shouldly; +using Xunit; + +namespace Volo.Abp.ObjectMapping; + +public class ObjectMappingHelper_Tests +{ + [Fact] + public void IsCollectionGenericType_Should_Return_True_For_Standard_GenericCollection() + { + var result = ObjectMappingHelper.IsCollectionGenericType, List>( + out var sourceArg, out var destArg, out var defGenericType); + + result.ShouldBeTrue(); + sourceArg.ShouldBe(typeof(MappingTestSource)); + destArg.ShouldBe(typeof(MappingTestDestination)); + defGenericType.ShouldBe(typeof(List<>)); + } + + [Fact] + public void IsCollectionGenericType_Should_Return_True_For_Array() + { + var result = ObjectMappingHelper.IsCollectionGenericType( + out var sourceArg, out var destArg, out _); + + result.ShouldBeTrue(); + sourceArg.ShouldBe(typeof(MappingTestSource)); + destArg.ShouldBe(typeof(MappingTestDestination)); + } + + [Fact] + public void IsCollectionGenericType_Should_Normalize_IEnumerable_To_List() + { + var result = ObjectMappingHelper.IsCollectionGenericType, IEnumerable>( + out _, out _, out var defGenericType); + + result.ShouldBeTrue(); + defGenericType.ShouldBe(typeof(List<>)); + } + + [Fact] + public void IsCollectionGenericType_Should_Normalize_ICollection_To_Collection() + { + var result = ObjectMappingHelper.IsCollectionGenericType, ICollection>( + out _, out _, out var defGenericType); + + result.ShouldBeTrue(); + defGenericType.ShouldBe(typeof(Collection<>)); + } + + [Fact] + public void IsCollectionGenericType_Should_Return_False_For_NonCollection() + { + var result = ObjectMappingHelper.IsCollectionGenericType( + out _, out _, out _); + + result.ShouldBeFalse(); + } + + [Fact] + public void IsCollectionGenericType_Should_Return_False_For_NonGeneric_DerivedCollection() + { + var result = ObjectMappingHelper.IsCollectionGenericType, MappingTestDestinationList>( + out _, out _, out _); + + result.ShouldBeFalse(); + } +} + +public class MappingTestSource +{ + public string Value { get; set; } = ""; +} + +public class MappingTestDestination +{ + public string Value { get; set; } = ""; +} + +public class MappingTestDestinationList : List +{ +} diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/CleanupJobs.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/CleanupJobs.cs index 6eca55e09f..3b9da774ae 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/CleanupJobs.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/CleanupJobs.cs @@ -1,7 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; -using TickerQ.Utilities.Models; +using TickerQ.Utilities.Base; namespace Volo.Abp.BackgroundJobs.DemoApp.TickerQ; diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/DemoAppTickerQModule.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/DemoAppTickerQModule.cs index c9a3b957de..de3ef3c05d 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/DemoAppTickerQModule.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/DemoAppTickerQModule.cs @@ -3,13 +3,14 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using TickerQ.Dashboard.DependencyInjection; using TickerQ.DependencyInjection; using TickerQ.Utilities; using TickerQ.Utilities.Enums; using TickerQ.Utilities.Interfaces.Managers; -using TickerQ.Utilities.Models; -using TickerQ.Utilities.Models.Ticker; +using TickerQ.Utilities.Base; +using TickerQ.Utilities.Entities; using Volo.Abp.AspNetCore; using Volo.Abp.Autofac; using Volo.Abp.BackgroundJobs.DemoApp.Shared; @@ -35,13 +36,14 @@ public class DemoAppTickerQModule : AbpModule { context.Services.AddTickerQ(options => { - options.UpdateMissedJobCheckDelay(TimeSpan.FromSeconds(30)); + options.ConfigureScheduler(scheduler => + { + scheduler.FallbackIntervalChecker = TimeSpan.FromSeconds(30); + }); options.AddDashboard(x => { - x.BasePath = "/tickerq-dashboard"; - - x.UseHostAuthentication = true; + x.SetBasePath("/tickerq-dashboard"); }); }); @@ -78,7 +80,7 @@ public class DemoAppTickerQModule : AbpModule abpTickerQFunctionProvider.Functions.TryAdd(nameof(CleanupJobs), (string.Empty, TickerTaskPriority.Normal, new TickerFunctionDelegate(async (cancellationToken, serviceProvider, tickerFunctionContext) => { var service = new CleanupJobs(); - var request = await TickerRequestProvider.GetRequestAsync(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type); + var request = await TickerRequestProvider.GetRequestAsync(tickerFunctionContext, cancellationToken); var genericContext = new TickerFunctionContext(tickerFunctionContext, request); await service.CleanupLogsAsync(genericContext, cancellationToken); }))); @@ -92,10 +94,11 @@ public class DemoAppTickerQModule : AbpModule await backgroundWorkerManager.AddAsync(context.ServiceProvider.GetRequiredService()); var app = context.GetApplicationBuilder(); - app.UseAbpTickerQ(); - var timeTickerManager = context.ServiceProvider.GetRequiredService>(); - await timeTickerManager.AddAsync(new TimeTicker + context.GetHost().UseAbpTickerQ(); + + var timeTickerManager = context.ServiceProvider.GetRequiredService>(); + await timeTickerManager.AddAsync(new TimeTickerEntity { Function = nameof(CleanupJobs), ExecutionTime = DateTime.UtcNow.AddSeconds(5), @@ -104,8 +107,8 @@ public class DemoAppTickerQModule : AbpModule RetryIntervals = new[] { 30, 60, 120 }, // Retry after 30s, 60s, then 2min }); - var cronTickerManager = context.ServiceProvider.GetRequiredService>(); - await cronTickerManager.AddAsync(new CronTicker + var cronTickerManager = context.ServiceProvider.GetRequiredService>(); + await cronTickerManager.AddAsync(new CronTickerEntity { Function = nameof(CleanupJobs), Expression = "* * * * *", // Every minute @@ -134,7 +137,7 @@ public class DemoAppTickerQModule : AbpModule await Task.Delay(1000); - var timeTickerManager = serviceProvider.GetRequiredService>(); + var timeTickerManager = serviceProvider.GetRequiredService>(); var result = await timeTickerManager.DeleteAsync(Guid.Parse(jobId)); } } diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts index 26b9a49349..3ed4794990 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts +++ b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts @@ -1,3 +1,4 @@ +import { NgStyle } from '@angular/common'; import { ConfigStateService, CurrentUserDto, LocalizationPipe } from '@abp/ng.core'; import { ButtonComponent, @@ -99,6 +100,7 @@ type PermissionWithGroupName = PermissionGrantInfoDto & { ], imports: [ FormsModule, + NgStyle, ModalComponent, LocalizationPipe, ButtonComponent,