From a080ebc69ea82fb397fbdb1a19c5e1b4f3290cdb Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Fri, 25 Jul 2025 15:33:21 +0300 Subject: [PATCH 01/19] Add async auth guard support for OAuth routes Introduced asyncAuthGuard and asyncAbpOAuthGuard to enable asynchronous authentication checks, particularly for OAuth flows involving code exchange. Updated app routes and OAuth module provider to use the new async guards where appropriate. --- .../core/src/lib/abstracts/auth.guard.ts | 6 ++++ .../oauth/src/lib/guards/oauth.guard.ts | 32 ++++++++++++++++++- .../providers/oauth-module-config.provider.ts | 7 +++- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/abstracts/auth.guard.ts b/npm/ng-packs/packages/core/src/lib/abstracts/auth.guard.ts index 50473e4df1..020f750555 100644 --- a/npm/ng-packs/packages/core/src/lib/abstracts/auth.guard.ts +++ b/npm/ng-packs/packages/core/src/lib/abstracts/auth.guard.ts @@ -21,3 +21,9 @@ export const authGuard: CanActivateFn = () => { console.error('You should add @abp/ng-oauth packages or create your own auth packages.'); return false; }; + + +export const asyncAuthGuard: CanActivateFn = () => { + console.error('You should add @abp/ng-oauth packages or create your own auth packages.'); + return false; +}; \ No newline at end of file diff --git a/npm/ng-packs/packages/oauth/src/lib/guards/oauth.guard.ts b/npm/ng-packs/packages/oauth/src/lib/guards/oauth.guard.ts index 6f751804cd..e7f31643aa 100644 --- a/npm/ng-packs/packages/oauth/src/lib/guards/oauth.guard.ts +++ b/npm/ng-packs/packages/oauth/src/lib/guards/oauth.guard.ts @@ -6,7 +6,7 @@ import { CanActivateFn, } from '@angular/router'; -import { Observable } from 'rxjs'; +import { Observable, interval, filter, take, map, firstValueFrom, timeout, catchError, of } from 'rxjs'; import { OAuthService } from 'angular-oauth2-oidc'; import { AuthService, IAbpGuard } from '@abp/ng.core'; @@ -53,3 +53,33 @@ export const abpOAuthGuard: CanActivateFn = ( authService.navigateToLogin(params); return false; }; + +export const asyncAbpOAuthGuard: CanActivateFn = ( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot, +) => { + const authService = inject(AuthService); + const code = new URLSearchParams(window.location.search).get('code'); + + if (code) { + return firstValueFrom( + interval(100).pipe( + map(() => authService.isAuthenticated), + filter(isAuthenticated => isAuthenticated), + take(1), + timeout(3000), + catchError(() => { + authService.navigateToLogin({ returnUrl: state.url }); + return of(false); + }) + ) + ); + } + + if (authService.isAuthenticated) { + return true; + } + + authService.navigateToLogin({ returnUrl: state.url }); + return false; +}; diff --git a/npm/ng-packs/packages/oauth/src/lib/providers/oauth-module-config.provider.ts b/npm/ng-packs/packages/oauth/src/lib/providers/oauth-module-config.provider.ts index 92ecc86012..534882dd13 100644 --- a/npm/ng-packs/packages/oauth/src/lib/providers/oauth-module-config.provider.ts +++ b/npm/ng-packs/packages/oauth/src/lib/providers/oauth-module-config.provider.ts @@ -2,6 +2,7 @@ import { AuthService, AuthGuard, authGuard, + asyncAuthGuard, ApiInterceptor, PIPE_TO_LOGIN_FN_KEY, CHECK_AUTHENTICATION_STATE_FN_KEY, @@ -11,7 +12,7 @@ import { import { Provider, makeEnvironmentProviders, inject, provideAppInitializer } from '@angular/core'; import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { OAuthModule, OAuthStorage } from 'angular-oauth2-oidc'; -import { AbpOAuthGuard, abpOAuthGuard } from '../guards'; +import { AbpOAuthGuard, abpOAuthGuard, asyncAbpOAuthGuard, } from '../guards'; import { OAuthConfigurationHandler } from '../handlers'; import { OAuthApiInterceptor } from '../interceptors'; import { AbpOAuthService, OAuthErrorFilterService } from '../services'; @@ -31,6 +32,10 @@ export function provideAbpOAuth() { { provide: authGuard, useValue: abpOAuthGuard, + }, + { + provide: asyncAuthGuard, + useValue: asyncAbpOAuthGuard, }, { provide: ApiInterceptor, From d436f9020bf3487af03efaea7a8f356aa06b791c Mon Sep 17 00:00:00 2001 From: Engincan VESKE Date: Mon, 28 Jul 2025 16:14:13 +0300 Subject: [PATCH 02/19] Update the separate multi-tenancy article --- .../2025-07-26-Separate-Tenant-Schema/POST.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/POST.md b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/POST.md index 5567b42aad..e63db935a5 100644 --- a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/POST.md +++ b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/POST.md @@ -22,7 +22,7 @@ In the shared database model, all the application data stored in a single physic ![single-shared-database](single-shared-database.png) -This is the default behavior when you [create a new ABP application](https://abp.io/docs/latest/get-started), because it is simple to begin with and proper for must applications. +This is the default behavior when you [create a new ABP application](https://abp.io/docs/latest/get-started), because it is simple to begin with and proper for most applications. In this model, a single database table may contain data of multiple tenants. Each row in these tables have a `TenantId` field which is used to distinguish the tenant data and isolate a tenant's data from other tenant users. To make your entities multi-tenant aware, all you have to do is to implement the `IMultiTenant` interface provided by the ABP Framework. From 341707264bcc1c9b5c44397883f9bfa4ccde68d9 Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Mon, 28 Jul 2025 23:31:07 +0300 Subject: [PATCH 03/19] Refactor locale initialization to support async and injector Updated provideAppInitializer to use an async function and pass the Injector to localeInitializer. Modified localeInitializer to accept an optional Injector parameter for improved flexibility and testability. --- .../src/lib/providers/core-module-config.provider.ts | 11 +++++++---- .../packages/core/src/lib/utils/initial-utils.ts | 8 ++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/providers/core-module-config.provider.ts b/npm/ng-packs/packages/core/src/lib/providers/core-module-config.provider.ts index fafb51ff6d..3ebe64360f 100644 --- a/npm/ng-packs/packages/core/src/lib/providers/core-module-config.provider.ts +++ b/npm/ng-packs/packages/core/src/lib/providers/core-module-config.provider.ts @@ -1,4 +1,4 @@ -import { makeEnvironmentProviders, Provider, inject, provideAppInitializer } from '@angular/core'; +import { makeEnvironmentProviders, Provider, provideAppInitializer, inject, Injector } from '@angular/core'; import { TitleStrategy } from '@angular/router'; import { HTTP_INTERCEPTORS, @@ -107,13 +107,16 @@ export function provideAbpCore(...features: CoreFeature[]) { }), ), provideAppInitializer(() => { - getInitialData(); - localeInitializer(); + const injector = inject(Injector); inject(LocalizationService); inject(LocalStorageListenerService); inject(RoutesHandler); - }), + return (async (): Promise => { + await getInitialData(); + await localeInitializer(injector); + })(); + }), LocaleProvider, CookieLanguageProvider, { diff --git a/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts index 3bc9b6f2b5..254d61896d 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts @@ -52,10 +52,10 @@ export async function getInitialData() { await lastValueFrom(result$); } -export function localeInitializer() { - const injector = inject(Injector); - const sessionState = injector.get(SessionStateService); - const { registerLocaleFn }: ABP.Root = injector.get(CORE_OPTIONS); +export function localeInitializer(injector?: Injector) { + const currentInjector = injector || inject(Injector); + const sessionState = currentInjector.get(SessionStateService); + const { registerLocaleFn }: ABP.Root = currentInjector.get(CORE_OPTIONS); const lang = sessionState.getLanguage() || 'en'; From a9fae7ff2973811eb19c2dd6b26f4f66b32071fe Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Tue, 29 Jul 2025 17:22:18 +0300 Subject: [PATCH 04/19] Move localeInitializer call to getInitialData The localeInitializer invocation was moved from the app initializer in core-module-config.provider.ts to the getInitialData function in initial-utils.ts. This change centralizes initialization logic and ensures locale setup occurs after initial data is loaded. --- .../core/src/lib/providers/core-module-config.provider.ts | 2 -- npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/providers/core-module-config.provider.ts b/npm/ng-packs/packages/core/src/lib/providers/core-module-config.provider.ts index 3ebe64360f..fa24f66e45 100644 --- a/npm/ng-packs/packages/core/src/lib/providers/core-module-config.provider.ts +++ b/npm/ng-packs/packages/core/src/lib/providers/core-module-config.provider.ts @@ -107,14 +107,12 @@ export function provideAbpCore(...features: CoreFeature[]) { }), ), provideAppInitializer(() => { - const injector = inject(Injector); inject(LocalizationService); inject(LocalStorageListenerService); inject(RoutesHandler); return (async (): Promise => { await getInitialData(); - await localeInitializer(injector); })(); }), LocaleProvider, diff --git a/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts index 254d61896d..0c65073ef6 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts @@ -50,6 +50,7 @@ export async function getInitialData() { }), ); await lastValueFrom(result$); + await localeInitializer(injector); } export function localeInitializer(injector?: Injector) { From 4e462b577d3b6368d1c90217fe702df47ed9fb49 Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Wed, 30 Jul 2025 09:27:56 +0300 Subject: [PATCH 05/19] Refactor app initializer to use async function Simplifies the app initializer by making it an async function directly instead of returning an async IIFE. This improves readability and maintains the same initialization behavior. --- .../core/src/lib/providers/core-module-config.provider.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/providers/core-module-config.provider.ts b/npm/ng-packs/packages/core/src/lib/providers/core-module-config.provider.ts index fa24f66e45..631f25217c 100644 --- a/npm/ng-packs/packages/core/src/lib/providers/core-module-config.provider.ts +++ b/npm/ng-packs/packages/core/src/lib/providers/core-module-config.provider.ts @@ -106,14 +106,11 @@ export function provideAbpCore(...features: CoreFeature[]) { headerName: 'RequestVerificationToken', }), ), - provideAppInitializer(() => { + provideAppInitializer(async () => { inject(LocalizationService); inject(LocalStorageListenerService); inject(RoutesHandler); - - return (async (): Promise => { - await getInitialData(); - })(); + await getInitialData(); }), LocaleProvider, CookieLanguageProvider, From de1db9761a909d3757bd4459919c55808f767691 Mon Sep 17 00:00:00 2001 From: sumeyye Date: Wed, 30 Jul 2025 09:32:16 +0300 Subject: [PATCH 06/19] update: remove unused imports --- .../core/src/lib/providers/core-module-config.provider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/providers/core-module-config.provider.ts b/npm/ng-packs/packages/core/src/lib/providers/core-module-config.provider.ts index 631f25217c..e8ca5ea711 100644 --- a/npm/ng-packs/packages/core/src/lib/providers/core-module-config.provider.ts +++ b/npm/ng-packs/packages/core/src/lib/providers/core-module-config.provider.ts @@ -1,4 +1,4 @@ -import { makeEnvironmentProviders, Provider, provideAppInitializer, inject, Injector } from '@angular/core'; +import { makeEnvironmentProviders, Provider, provideAppInitializer, inject } from '@angular/core'; import { TitleStrategy } from '@angular/router'; import { HTTP_INTERCEPTORS, @@ -23,7 +23,7 @@ import { ABP, SortableItem } from '../models'; import { AuthErrorFilterService } from '../abstracts'; import { DEFAULT_DYNAMIC_LAYOUTS } from '../constants'; import { LocalizationService, LocalStorageListenerService, AbpTitleStrategy } from '../services'; -import { DefaultQueueManager, getInitialData, localeInitializer } from '../utils'; +import { DefaultQueueManager, getInitialData } from '../utils'; import { CookieLanguageProvider, IncludeLocalizationResourcesProvider, LocaleProvider } from './'; import { TimezoneInterceptor } from '../interceptors'; From f5d1731793d0f15828ebefb383130a1cd90a9a88 Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Wed, 30 Jul 2025 12:06:26 +0300 Subject: [PATCH 07/19] Refactor OAuth guard to use OAuthService and timer Replaces usage of AuthService.isAuthenticated and interval with OAuthService.hasValidAccessToken() and timer for checking authentication status. Also injects EnvironmentService to access OAuth configuration and updates logic to check responseType before waiting for authentication. --- .../oauth/src/lib/guards/oauth.guard.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/npm/ng-packs/packages/oauth/src/lib/guards/oauth.guard.ts b/npm/ng-packs/packages/oauth/src/lib/guards/oauth.guard.ts index e7f31643aa..1311a8d7a8 100644 --- a/npm/ng-packs/packages/oauth/src/lib/guards/oauth.guard.ts +++ b/npm/ng-packs/packages/oauth/src/lib/guards/oauth.guard.ts @@ -6,10 +6,10 @@ import { CanActivateFn, } from '@angular/router'; -import { Observable, interval, filter, take, map, firstValueFrom, timeout, catchError, of } from 'rxjs'; +import { Observable, timer, filter, take, map, firstValueFrom, timeout, catchError, of } from 'rxjs'; import { OAuthService } from 'angular-oauth2-oidc'; -import { AuthService, IAbpGuard } from '@abp/ng.core'; +import { AuthService, IAbpGuard, EnvironmentService } from '@abp/ng.core'; /** * @deprecated Use `abpOAuthGuard` *function* instead. @@ -58,14 +58,17 @@ export const asyncAbpOAuthGuard: CanActivateFn = ( route: ActivatedRouteSnapshot, state: RouterStateSnapshot, ) => { + const oAuthService = inject(OAuthService); const authService = inject(AuthService); - const code = new URLSearchParams(window.location.search).get('code'); + const environmentService = inject(EnvironmentService); + + const { oAuthConfig } = environmentService.getEnvironment(); - if (code) { + if (oAuthConfig?.responseType === 'code') { return firstValueFrom( - interval(100).pipe( - map(() => authService.isAuthenticated), - filter(isAuthenticated => isAuthenticated), + timer(0, 100).pipe( + map(() => oAuthService.hasValidAccessToken()), + filter(Boolean), take(1), timeout(3000), catchError(() => { @@ -76,7 +79,7 @@ export const asyncAbpOAuthGuard: CanActivateFn = ( ); } - if (authService.isAuthenticated) { + if (oAuthService.hasValidAccessToken()) { return true; } From 00a1950d09158e6c37904bd230096770983d484e Mon Sep 17 00:00:00 2001 From: berkansasmaz Date: Fri, 1 Aug 2025 09:53:36 +0300 Subject: [PATCH 08/19] docs: Update version mapping with new ABP Studio and ABP versions --- docs/en/studio/version-mapping.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/en/studio/version-mapping.md b/docs/en/studio/version-mapping.md index 51e0de5bbd..6fe3ba95be 100644 --- a/docs/en/studio/version-mapping.md +++ b/docs/en/studio/version-mapping.md @@ -4,6 +4,8 @@ This document provides a general overview of the relationship between various ve | **ABP Studio Version** | **ABP Version of Startup Template** | |------------------------|---------------------------| +| 1.1.2 | 9.2.3 | +| 1.1.0 - 1.1.1 | 9.2.2 | | 1.0.2 | 9.2.1 | | 1.0.1 | 9.2.0 | | 1.0.0 | 9.2.0 | From 6ddae0f82da817ab807ce23e118e7ec0a71f178a Mon Sep 17 00:00:00 2001 From: berkansasmaz Date: Fri, 1 Aug 2025 09:53:50 +0300 Subject: [PATCH 09/19] docs: Update release notes for versions 1.1.0 to 1.1.2 with new features and enhancements --- docs/en/studio/release-notes.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/en/studio/release-notes.md b/docs/en/studio/release-notes.md index d2db328fbf..5601fe11d0 100644 --- a/docs/en/studio/release-notes.md +++ b/docs/en/studio/release-notes.md @@ -2,6 +2,33 @@ This document contains **brief release notes** for each ABP Studio release. Release notes only include **major features** and **visible enhancements**. Therefore, they don't include all the development done in the related version. +## 1.1.2 (2025-07-31) + +* Upgraded template dependencies for ABP Framework and LeptonX. (targeting ABP `9.2.3`) +* Configured LeptonX Lite logos in the templates. +* Added browser tab memory feature to remember previously selected tabs. +* Enhanced tools section with default credentials display for first-time tool usage. +* Improved module and package loading with better error handling. + +## 1.1.1 (2025-07-22) + +* Enhanced tools section with clear cookies option. +* Fixed language management module name display for imported modules. +* Improved update window messaging with "Skip this version" option. +* Fixed Docker Compose file issues in microservice template. +* Resolved RabbitMQ tool cookie problems. + +## 1.1.0 (2025-07-16) + +* Upgraded template dependencies for ABP Framework and LeptonX. (targeting ABP `9.2.2`) +* Enhanced UI scaling for all windows and improved user experience. +* Added tools section in solution runner main area with basic Grafana dashboard for microservice template. +* Improved container management during application building. +* Enhanced background task exception handling. +* Added public account module package reference to Blazor WebApp client. +* Fixed tenant database context updating errors. +* Improved optional module selection UI with better documentation integration. + ## 1.0.2 (2025-06-24) * Enhanced the ABP NuGet package installation experience. From 6df1a02c15896ce0fcd32eab3298a1e81740e4d7 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 1 Aug 2025 20:47:09 +0800 Subject: [PATCH 10/19] Enhance object mapping capabilities by adding collection mapping support and optimizing method invocation caching --- .../MapperlyAutoObjectMappingProvider.cs | 136 ++++++++++++++++++ .../Abp/ObjectMapping/DefaultObjectMapper.cs | 122 +++++++++------- .../Mapperly/AbpMapperlyModule_Basic_Tests.cs | 31 ++++ 3 files changed, 240 insertions(+), 49 deletions(-) diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs index 7685aa03ac..a3a9abc234 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs @@ -1,5 +1,9 @@ using System; +using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Data; @@ -19,6 +23,8 @@ public class MapperlyAutoObjectMappingProvider : MapperlyAutoObjectMap public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider { + protected static readonly ConcurrentDictionary> MapCache = new(); + protected IServiceProvider ServiceProvider { get; } public MapperlyAutoObjectMappingProvider(IServiceProvider serviceProvider) @@ -28,6 +34,11 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider public virtual TDestination Map(object source) { + if (TryToMapCollection((TSource)source, default, out var collectionResult)) + { + return collectionResult; + } + var mapper = ServiceProvider.GetService>(); if (mapper != null) { @@ -54,6 +65,11 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider public virtual TDestination Map(TSource source, TDestination destination) { + if (TryToMapCollection(source, destination, out var collectionResult)) + { + return collectionResult; + } + var mapper = ServiceProvider.GetService>(); if (mapper != null) { @@ -80,6 +96,126 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider $" {TypeHelper.GetFullNameHandlingNullableAndGenerics(typeof(IAbpReverseMapperlyMapper))} was found"); } + protected virtual bool TryToMapCollection(TSource source, TDestination? destination, out TDestination collectionResult) + { + if (!DefaultObjectMapper.IsCollectionGenericType(out var sourceArgumentType, out var destinationArgumentType, out var definitionGenericType)) + { + collectionResult = default!; + return false; + } + + var mapperType = typeof(IAbpMapperlyMapper<,>).MakeGenericType(sourceArgumentType, destinationArgumentType); + var mapper = ServiceProvider.GetService(mapperType); + if (mapper == null) + { + mapperType = typeof(IAbpReverseMapperlyMapper<,>).MakeGenericType(destinationArgumentType, sourceArgumentType); + mapper = ServiceProvider.GetService(mapperType); + if (mapper == null) + { + //skip, no specific mapper + collectionResult = default!; + return false; + } + } + + var invoker = MapCache.GetOrAdd( + $"{mapperType.FullName}_{(destination == null ? "MapMethodWithSingleParameter" : "MapMethodWithDoubleParameters")}", + _ => CreateMapDelegate(mapperType, sourceArgumentType, destinationArgumentType, destination != null)); + + var sourceList = source!.As(); + var result = definitionGenericType.IsGenericType + ? Activator.CreateInstance(definitionGenericType.MakeGenericType(destinationArgumentType))!.As() + : Array.CreateInstance(destinationArgumentType, sourceList.Count); + + if (destination != null && !destination.GetType().IsArray) + { + //Clear destination collection if destination not an array, We won't change array just same behavior as AutoMapper. + destination.As().Clear(); + } + + for (var i = 0; i < sourceList.Count; i++) + { + var invokeResult = destination == null + ? invoker(this, sourceList[i]!, null!) + : invoker(this, sourceList[i]!, Activator.CreateInstance(destinationArgumentType)!); + + if (definitionGenericType.IsGenericType) + { + result.Add(invokeResult); + destination?.As().Add(invokeResult); + } + else + { + result[i] = invokeResult; + } + } + + if (destination != null && destination.GetType().IsArray) + { + //Return the new collection if destination is an array, We won't change array just same behavior as AutoMapper. + collectionResult = (TDestination)result; + return true; + } + + //Return the destination if destination exists. The parameter reference equals with return object. + collectionResult = destination ?? (TDestination)result; + return true; + } + + protected virtual Func CreateMapDelegate( + Type mapperType, + Type sourceArgumentType, + Type destinationArgumentType, + bool hasDestination) + { + var methods = typeof(MapperlyAutoObjectMappingProvider) + .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Where(x => x.Name == nameof(Map)) + .Where(x => + { + var parameters = x.GetParameters(); + return (hasDestination || parameters.Length == 1) && + (!hasDestination || parameters.Length == 2); + }) + .ToList(); + + if (methods.Count == 0) + { + throw new AbpException($"Could not find a method named '{nameof(Map)}'" + + $" with parameters({(hasDestination ? sourceArgumentType + ", " + destinationArgumentType : sourceArgumentType.ToString())})" + + $" in the type '{mapperType}'."); + } + + if (methods.Count > 1) + { + throw new AbpException($"Found more than one method named '{nameof(Map)}'" + + $" with parameters({(hasDestination ? sourceArgumentType + ", " + destinationArgumentType : sourceArgumentType.ToString())})" + + $" in the type '{mapperType}'."); + } + + var method = methods[0].MakeGenericMethod(sourceArgumentType, destinationArgumentType); + + var instanceParam = Expression.Parameter(typeof(object), "mapper"); + var sourceParam = Expression.Parameter(typeof(object), "source"); + var destinationParam = Expression.Parameter(typeof(object), "destination"); + + var instanceCast = Expression.Convert(instanceParam, method.DeclaringType!); + var callParams = new List + { + Expression.Convert(sourceParam, sourceArgumentType) + }; + + if (hasDestination) + { + callParams.Add(Expression.Convert(destinationParam, destinationArgumentType)); + } + + var call = Expression.Call(instanceCast, method, callParams); + var callConvert = Expression.Convert(call, typeof(object)); + + return Expression.Lambda>(callConvert, instanceParam, sourceParam, destinationParam).Compile(); + } + protected virtual ExtraPropertyDictionary GetExtraProperties(TDestination destination) { var extraProperties = new ExtraPropertyDictionary(); diff --git a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs index 30733b1e24..6b0e723fbb 100644 --- a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs +++ b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs @@ -5,6 +5,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using Volo.Abp.DependencyInjection; @@ -25,7 +26,7 @@ public class DefaultObjectMapper : DefaultObjectMapper, IObjectMapper< public class DefaultObjectMapper : IObjectMapper, ITransientDependency { - protected static ConcurrentDictionary MethodInfoCache { get; } = new ConcurrentDictionary(); + protected static readonly ConcurrentDictionary> MapCache = new(); public IAutoObjectMappingProvider AutoObjectMappingProvider { get; } protected IServiceProvider ServiceProvider { get; } @@ -137,46 +138,9 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency return false; } - var cacheKey = $"{mapperType.FullName}_{(destination == null ? "MapMethodWithSingleParameter" : "MapMethodWithDoubleParameters")}"; - var method = MethodInfoCache.GetOrAdd( - cacheKey, - _ => - { - var methods = specificMapper - .GetType() - .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) - .Where(x => x.Name == nameof(IObjectMapper.Map)) - .Where(x => - { - var parameters = x.GetParameters(); - if (destination == null && parameters.Length != 1 || - destination != null && parameters.Length != 2 || - parameters[0].ParameterType != sourceArgumentType) - { - return false; - } - - return destination == null || parameters[1].ParameterType == destinationArgumentType; - }) - .ToList(); - - if (methods.IsNullOrEmpty()) - { - throw new AbpException($"Could not find a method named '{nameof(IObjectMapper.Map)}'" + - $" with parameters({(destination == null ? sourceArgumentType.ToString() : sourceArgumentType + "," + destinationArgumentType)})" + - $" in the type '{mapperType}'."); - } - - if (methods.Count > 1) - { - throw new AbpException($"Found more than one method named '{nameof(IObjectMapper.Map)}'" + - $" with parameters({(destination == null ? sourceArgumentType.ToString() : sourceArgumentType + "," + destinationArgumentType)})" + - $" in the type '{mapperType}'."); - } - - return methods.First(); - } - ); + var invoker = MapCache.GetOrAdd( + $"{mapperType.FullName}_{(destination == null ? "MapMethodWithSingleParameter" : "MapMethodWithDoubleParameters")}", + _ => CreateMapDelegate(mapperType, sourceArgumentType, destinationArgumentType, destination != null)); var sourceList = source!.As(); var result = definitionGenericType.IsGenericType @@ -192,8 +156,8 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency for (var i = 0; i < sourceList.Count; i++) { var invokeResult = destination == null - ? method.Invoke(specificMapper, new [] { sourceList[i] })! - : method.Invoke(specificMapper, new [] { sourceList[i], Activator.CreateInstance(destinationArgumentType)! })!; + ? invoker(specificMapper, sourceList[i]!, null!) + : invoker(specificMapper, sourceList[i]!, Activator.CreateInstance(destinationArgumentType)!); if (definitionGenericType.IsGenericType) { @@ -218,11 +182,71 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency return true; } - protected virtual bool IsCollectionGenericType(out Type sourceArgumentType, out Type destinationArgumentType, out Type definitionGenericType) + protected virtual Func CreateMapDelegate( + Type mapperType, + Type sourceArgumentType, + Type destinationArgumentType, + bool hasDestination) + { + var methods = mapperType + .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Where(x => x.Name == nameof(IObjectMapper.Map)) + .Where(x => + { + var parameters = x.GetParameters(); + if (!hasDestination && parameters.Length != 1 || + hasDestination && parameters.Length != 2 || + parameters[0].ParameterType != sourceArgumentType) + { + return false; + } + + return !hasDestination || parameters[1].ParameterType == destinationArgumentType; + }) + .ToList(); + + if (methods.Count == 0) + { + throw new AbpException($"Could not find a method named '{nameof(IObjectMapper.Map)}'" + + $" with parameters({(hasDestination ? sourceArgumentType + ", " + destinationArgumentType : sourceArgumentType.ToString())})" + + $" in the type '{mapperType}'."); + } + + if (methods.Count > 1) + { + throw new AbpException($"Found more than one method named '{nameof(IObjectMapper.Map)}'" + + $" with parameters({(hasDestination ? sourceArgumentType + ", " + destinationArgumentType : sourceArgumentType.ToString())})" + + $" in the type '{mapperType}'."); + } + + var method = methods[0]; + + var instanceParam = Expression.Parameter(typeof(object), "mapper"); + var sourceParam = Expression.Parameter(typeof(object), "source"); + var destinationParam = Expression.Parameter(typeof(object), "destination"); + + var instanceCast = Expression.Convert(instanceParam, method.DeclaringType!); + var callParams = new List + { + Expression.Convert(sourceParam, sourceArgumentType) + }; + + if (hasDestination) + { + callParams.Add(Expression.Convert(destinationParam, destinationArgumentType)); + } + + var call = Expression.Call(instanceCast, method, callParams); + var callConvert = Expression.Convert(call, typeof(object)); + + return Expression.Lambda>(callConvert, instanceParam, sourceParam, destinationParam).Compile(); + } + + public static bool IsCollectionGenericType(out Type sourceArgumentType, out Type destinationArgumentType, out Type definitionGenericType) { - sourceArgumentType = default!; - destinationArgumentType = default!; - definitionGenericType = default!; + sourceArgumentType = null!; + destinationArgumentType = null!; + definitionGenericType = null!; if ((!typeof(TSource).IsGenericType && !typeof(TSource).IsArray) || (!typeof(TDestination).IsGenericType && !typeof(TDestination).IsArray)) @@ -249,7 +273,7 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency sourceArgumentType = typeof(TSource).GetElementType()!; } - if (sourceArgumentType == default!) + if (sourceArgumentType == null!) { return false; } @@ -272,7 +296,7 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency definitionGenericType = typeof(Array); } - return destinationArgumentType != default!; + return destinationArgumentType != null!; } protected virtual TDestination AutoMap(object source) diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs index 89861f6947..50a799e5a3 100644 --- a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Shouldly; using Volo.Abp.Mapperly.SampleClasses; @@ -41,6 +42,36 @@ public class AbpMapperlyModule_Basic_Tests : AbpIntegratedTest, List>(new List + { + new MyEntity { Number = 42 }, + new MyEntity { Number = 43 } + }); + + dto.Count.ShouldBe(2); + dto[0].Number.ShouldBe(42); + dto[1].Number.ShouldBe(43); + + var dtoList = new List(); + { + new MyEntityDto() { Number = 44 }; + new MyEntityDto() { Number = 45 }; + } + + dto = _objectMapper.Map, List>(new List + { + new MyEntity { Number = 42 }, + new MyEntity { Number = 43 } + }, dtoList); + + dtoList.Count.ShouldBe(2); + dtoList[0].Number.ShouldBe(42); + dtoList[1].Number.ShouldBe(43); + } + [Fact] public void Should_Map_Enum() { From b89031b671450d2f9b3f9ced48179922bcfa29a6 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 2 Aug 2025 10:35:47 +0800 Subject: [PATCH 11/19] Add `ObjectMappingHelper` to enhance performance. --- .../MapperlyAutoObjectMappingProvider.cs | 2 +- .../Abp/ObjectMapping/DefaultObjectMapper.cs | 59 +------------ .../Abp/ObjectMapping/ObjectMappingHelper.cs | 85 +++++++++++++++++++ 3 files changed, 87 insertions(+), 59 deletions(-) create mode 100644 framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/ObjectMappingHelper.cs diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs index a3a9abc234..677649b79a 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs @@ -98,7 +98,7 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider protected virtual bool TryToMapCollection(TSource source, TDestination? destination, out TDestination collectionResult) { - if (!DefaultObjectMapper.IsCollectionGenericType(out var sourceArgumentType, out var destinationArgumentType, out var definitionGenericType)) + if (!ObjectMappingHelper.IsCollectionGenericType(out var sourceArgumentType, out var destinationArgumentType, out var definitionGenericType)) { collectionResult = default!; return false; diff --git a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs index 6b0e723fbb..faf8c4e258 100644 --- a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs +++ b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs @@ -123,7 +123,7 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency protected virtual bool TryToMapCollection(IServiceScope serviceScope, TSource source, TDestination? destination, out TDestination collectionResult) { - if (!IsCollectionGenericType(out var sourceArgumentType, out var destinationArgumentType, out var definitionGenericType)) + if (!ObjectMappingHelper.IsCollectionGenericType(out var sourceArgumentType, out var destinationArgumentType, out var definitionGenericType)) { collectionResult = default!; return false; @@ -242,63 +242,6 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency return Expression.Lambda>(callConvert, instanceParam, sourceParam, destinationParam).Compile(); } - public static bool IsCollectionGenericType(out Type sourceArgumentType, out Type destinationArgumentType, out Type definitionGenericType) - { - sourceArgumentType = null!; - destinationArgumentType = null!; - definitionGenericType = null!; - - if ((!typeof(TSource).IsGenericType && !typeof(TSource).IsArray) || - (!typeof(TDestination).IsGenericType && !typeof(TDestination).IsArray)) - { - return false; - } - - var supportedCollectionTypes = new[] - { - typeof(IEnumerable<>), - typeof(ICollection<>), - typeof(Collection<>), - typeof(IList<>), - typeof(List<>) - }; - - if (typeof(TSource).IsGenericType && supportedCollectionTypes.Any(x => x == typeof(TSource).GetGenericTypeDefinition())) - { - sourceArgumentType = typeof(TSource).GenericTypeArguments[0]; - } - - if (typeof(TSource).IsArray) - { - sourceArgumentType = typeof(TSource).GetElementType()!; - } - - if (sourceArgumentType == null!) - { - return false; - } - - definitionGenericType = typeof(List<>); - if (typeof(TDestination).IsGenericType && supportedCollectionTypes.Any(x => x == typeof(TDestination).GetGenericTypeDefinition())) - { - destinationArgumentType = typeof(TDestination).GenericTypeArguments[0]; - - if (typeof(TDestination).GetGenericTypeDefinition() == typeof(ICollection<>) || - typeof(TDestination).GetGenericTypeDefinition() == typeof(Collection<>)) - { - definitionGenericType = typeof(Collection<>); - } - } - - if (typeof(TDestination).IsArray) - { - destinationArgumentType = typeof(TDestination).GetElementType()!; - definitionGenericType = typeof(Array); - } - - return destinationArgumentType != null!; - } - protected virtual TDestination AutoMap(object source) { return AutoObjectMappingProvider.Map(source); diff --git a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/ObjectMappingHelper.cs b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/ObjectMappingHelper.cs new file mode 100644 index 0000000000..cbfd33988b --- /dev/null +++ b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/ObjectMappingHelper.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Volo.Abp.ObjectMapping; + +public static class ObjectMappingHelper +{ + private static readonly ConcurrentDictionary<(Type, Type), (Type sourceArgumentType, Type destinationArgumentType, Type definitionGenericType)?> Cache = new(); + + public static bool IsCollectionGenericType( + out Type sourceArgumentType, + out Type destinationArgumentType, + out Type definitionGenericType) + { + var cached = Cache.GetOrAdd((typeof(TSource), typeof(TDestination)), _ => IsCollectionGenericTypeInternal()); + if (cached == null) + { + sourceArgumentType = destinationArgumentType = definitionGenericType = null!; + return false; + } + + (sourceArgumentType, destinationArgumentType, definitionGenericType) = cached.Value; + return true; + } + + private static (Type, Type, Type)? IsCollectionGenericTypeInternal() + { + Type sourceArgumentType = null!; + Type destinationArgumentType = null!; + Type definitionGenericType = null!; + + if ((!typeof(TSource).IsGenericType && !typeof(TSource).IsArray) || + (!typeof(TDestination).IsGenericType && !typeof(TDestination).IsArray)) + { + return null; + } + + var supportedCollectionTypes = new[] + { + typeof(IEnumerable<>), + typeof(ICollection<>), + typeof(Collection<>), + typeof(IList<>), + typeof(List<>) + }; + + if (typeof(TSource).IsGenericType && supportedCollectionTypes.Any(x => x == typeof(TSource).GetGenericTypeDefinition())) + { + sourceArgumentType = typeof(TSource).GenericTypeArguments[0]; + } + + if (typeof(TSource).IsArray) + { + sourceArgumentType = typeof(TSource).GetElementType()!; + } + + if (sourceArgumentType == null) + { + return null; + } + + definitionGenericType = typeof(List<>); + if (typeof(TDestination).IsGenericType && supportedCollectionTypes.Any(x => x == typeof(TDestination).GetGenericTypeDefinition())) + { + destinationArgumentType = typeof(TDestination).GenericTypeArguments[0]; + + if (typeof(TDestination).GetGenericTypeDefinition() == typeof(ICollection<>) || + typeof(TDestination).GetGenericTypeDefinition() == typeof(Collection<>)) + { + definitionGenericType = typeof(Collection<>); + } + } + + if (typeof(TDestination).IsArray) + { + destinationArgumentType = typeof(TDestination).GetElementType()!; + definitionGenericType = typeof(Array); + } + + return (sourceArgumentType, destinationArgumentType, definitionGenericType); + } +} From 5c37d7b20bede457916031606eacfe9429308409 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 2 Aug 2025 10:46:52 +0800 Subject: [PATCH 12/19] [Identity] Replace AutoMapper with Mapperly. --- .../Volo.Abp.Identity.Application.csproj | 2 +- .../Identity/AbpIdentityApplicationMappers.cs | 19 ++++ .../Identity/AbpIdentityApplicationModule.cs | 11 +-- ...ntityApplicationModuleAutoMapperProfile.cs | 15 --- .../AbpIdentityBlazorAutoMapperProfile.cs | 18 ---- .../AbpIdentityBlazorMappers.cs | 25 +++++ .../AbpIdentityBlazorModule.cs | 13 +-- .../Volo.Abp.Identity.Blazor.csproj | 2 +- .../Volo.Abp.Identity.Domain.csproj | 2 +- .../Abp/Identity/AbpIdentityDomainModule.cs | 11 +-- .../Abp/Identity/IdentityDomainMappers.cs | 33 +++++++ .../Identity/IdentityDomainMappingProfile.cs | 15 --- .../AbpIdentityWebAutoMapperProfile.cs | 57 ----------- .../AbpIdentityWebMappers.cs | 94 +++++++++++++++++++ .../AbpIdentityWebModule.cs | 11 +-- .../Volo.Abp.Identity.Web.csproj | 2 +- 16 files changed, 188 insertions(+), 142 deletions(-) create mode 100644 modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationMappers.cs delete mode 100644 modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs delete mode 100644 modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorAutoMapperProfile.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMappers.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDomainMappers.cs delete mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDomainMappingProfile.cs delete mode 100644 modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebAutoMapperProfile.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebMappers.cs diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo.Abp.Identity.Application.csproj b/modules/identity/src/Volo.Abp.Identity.Application/Volo.Abp.Identity.Application.csproj index 0b3693b5d1..7125af8060 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application/Volo.Abp.Identity.Application.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo.Abp.Identity.Application.csproj @@ -18,7 +18,7 @@ - + diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationMappers.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationMappers.cs new file mode 100644 index 0000000000..164da27aae --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationMappers.cs @@ -0,0 +1,19 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Identity; +using Volo.Abp.Mapperly; + +[Mapper] +[MapExtraProperties] +public partial class IdentityUserToIdentityUserDtoMapper : MapperBase +{ + public override partial IdentityUserDto Map(IdentityUser source); + public override partial void Map(IdentityUser source, IdentityUserDto destination); +} + +[Mapper] +[MapExtraProperties] +public partial class IdentityRoleToIdentityRoleDtoMapper : MapperBase +{ + public override partial IdentityRoleDto Map(IdentityRole source); + public override partial void Map(IdentityRole source, IdentityRoleDto destination); +} diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModule.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModule.cs index 673a0605c7..07865ab8f5 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModule.cs @@ -1,5 +1,5 @@ using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement; @@ -8,18 +8,13 @@ namespace Volo.Abp.Identity; [DependsOn( typeof(AbpIdentityDomainModule), typeof(AbpIdentityApplicationContractsModule), - typeof(AbpAutoMapperModule), + typeof(AbpMapperlyModule), typeof(AbpPermissionManagementApplicationModule) )] public class AbpIdentityApplicationModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddAutoMapperObjectMapper(); - - Configure(options => - { - options.AddProfile(validate: true); - }); + context.Services.AddMapperlyObjectMapper(); } } diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs deleted file mode 100644 index a546a9bca1..0000000000 --- a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs +++ /dev/null @@ -1,15 +0,0 @@ -using AutoMapper; - -namespace Volo.Abp.Identity; - -public class AbpIdentityApplicationModuleAutoMapperProfile : Profile -{ - public AbpIdentityApplicationModuleAutoMapperProfile() - { - CreateMap() - .MapExtraProperties(); - - CreateMap() - .MapExtraProperties(); - } -} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorAutoMapperProfile.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorAutoMapperProfile.cs deleted file mode 100644 index 52a450785e..0000000000 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorAutoMapperProfile.cs +++ /dev/null @@ -1,18 +0,0 @@ -using AutoMapper; -using Volo.Abp.AutoMapper; - -namespace Volo.Abp.Identity.Blazor; - -public class AbpIdentityBlazorAutoMapperProfile : Profile -{ - public AbpIdentityBlazorAutoMapperProfile() - { - CreateMap() - .MapExtraProperties() - .Ignore(x => x.Password) - .Ignore(x => x.RoleNames); - - CreateMap() - .MapExtraProperties(); - } -} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMappers.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMappers.cs new file mode 100644 index 0000000000..614ca6ae32 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMappers.cs @@ -0,0 +1,25 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace Volo.Abp.Identity.Blazor; + +[Mapper] +[MapExtraProperties] +public partial class IdentityUserDtoToIdentityUserUpdateDtoMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(IdentityUserUpdateDto.Password))] + [MapperIgnoreTarget(nameof(IdentityUserUpdateDto.RoleNames))] + public override partial IdentityUserUpdateDto Map(IdentityUserDto source); + + [MapperIgnoreTarget(nameof(IdentityUserUpdateDto.Password))] + [MapperIgnoreTarget(nameof(IdentityUserUpdateDto.RoleNames))] + public override partial void Map(IdentityUserDto source, IdentityUserUpdateDto destination); +} + +[Mapper] +[MapExtraProperties] +public partial class IdentityRoleDtoToIdentityRoleUpdateDtoMapper : MapperBase +{ + public override partial IdentityRoleUpdateDto Map(IdentityRoleDto source); + public override partial void Map(IdentityRoleDto source, IdentityRoleUpdateDto destination); +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs index 3367b42a1b..89aba05f89 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs @@ -1,7 +1,7 @@ using Localization.Resources.AbpUi; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.BlazoriseUI; using Volo.Abp.Identity.Localization; using Volo.Abp.Localization; @@ -16,7 +16,7 @@ namespace Volo.Abp.Identity.Blazor; [DependsOn( typeof(AbpIdentityApplicationContractsModule), - typeof(AbpAutoMapperModule), + typeof(AbpMapperlyModule), typeof(AbpPermissionManagementBlazorModule), typeof(AbpBlazoriseUIModule) )] @@ -26,12 +26,7 @@ public class AbpIdentityBlazorModule : AbpModule public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddAutoMapperObjectMapper(); - - Configure(options => - { - options.AddProfile(validate: true); - }); + context.Services.AddMapperlyObjectMapper(); Configure(options => { @@ -42,7 +37,7 @@ public class AbpIdentityBlazorModule : AbpModule { options.AdditionalAssemblies.Add(typeof(AbpIdentityBlazorModule).Assembly); }); - + Configure(options => { options.Resources diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Volo.Abp.Identity.Blazor.csproj b/modules/identity/src/Volo.Abp.Identity.Blazor/Volo.Abp.Identity.Blazor.csproj index 355cb4294a..777fb1c31e 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Volo.Abp.Identity.Blazor.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Volo.Abp.Identity.Blazor.csproj @@ -8,7 +8,7 @@ - + diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj b/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj index fadd6e4109..a1bde79665 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj @@ -23,7 +23,7 @@ - + diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs index baf0871c58..590beb8e09 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Domain; using Volo.Abp.Domain.Entities.Events.Distributed; using Volo.Abp.Modularity; @@ -19,7 +19,7 @@ namespace Volo.Abp.Identity; typeof(AbpDddDomainModule), typeof(AbpIdentityDomainSharedModule), typeof(AbpUsersDomainModule), - typeof(AbpAutoMapperModule) + typeof(AbpMapperlyModule) )] public class AbpIdentityDomainModule : AbpModule { @@ -35,12 +35,7 @@ public class AbpIdentityDomainModule : AbpModule public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddAutoMapperObjectMapper(); - - Configure(options => - { - options.AddProfile(validate: true); - }); + context.Services.AddMapperlyObjectMapper(); Configure(options => { diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDomainMappers.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDomainMappers.cs new file mode 100644 index 0000000000..f7d41c9891 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDomainMappers.cs @@ -0,0 +1,33 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; +using Volo.Abp.Users; + +namespace Volo.Abp.Identity; + +[Mapper] +public partial class IdentityUserToUserEtoMapper : MapperBase +{ + public override partial UserEto Map(IdentityUser source); + public override partial void Map(IdentityUser source, UserEto destination); +} + +[Mapper] +public partial class IdentityClaimTypeToIdentityClaimTypeEtoMapper : MapperBase +{ + public override partial IdentityClaimTypeEto Map(IdentityClaimType source); + public override partial void Map(IdentityClaimType source, IdentityClaimTypeEto destination); +} + +[Mapper] +public partial class IdentityRoleToIdentityRoleEtoMapper : MapperBase +{ + public override partial IdentityRoleEto Map(IdentityRole source); + public override partial void Map(IdentityRole source, IdentityRoleEto destination); +} + +[Mapper] +public partial class OrganizationUnitToOrganizationUnitEtoMapper : MapperBase +{ + public override partial OrganizationUnitEto Map(OrganizationUnit source); + public override partial void Map(OrganizationUnit source, OrganizationUnitEto destination); +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDomainMappingProfile.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDomainMappingProfile.cs deleted file mode 100644 index ae9e5b5065..0000000000 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDomainMappingProfile.cs +++ /dev/null @@ -1,15 +0,0 @@ -using AutoMapper; -using Volo.Abp.Users; - -namespace Volo.Abp.Identity; - -public class IdentityDomainMappingProfile : Profile -{ - public IdentityDomainMappingProfile() - { - CreateMap(); - CreateMap(); - CreateMap(); - CreateMap(); - } -} diff --git a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebAutoMapperProfile.cs b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebAutoMapperProfile.cs deleted file mode 100644 index e032b3352f..0000000000 --- a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebAutoMapperProfile.cs +++ /dev/null @@ -1,57 +0,0 @@ -using AutoMapper; -using Volo.Abp.AutoMapper; -using Volo.Abp.Identity.Web.Pages.Identity.Roles; -using CreateUserModalModel = Volo.Abp.Identity.Web.Pages.Identity.Users.CreateModalModel; -using EditUserModalModel = Volo.Abp.Identity.Web.Pages.Identity.Users.EditModalModel; - -namespace Volo.Abp.Identity.Web; - -public class AbpIdentityWebAutoMapperProfile : Profile -{ - public AbpIdentityWebAutoMapperProfile() - { - CreateUserMappings(); - CreateRoleMappings(); - } - - protected virtual void CreateUserMappings() - { - //List - CreateMap() - .Ignore(x => x.Password); - - //CreateModal - CreateMap() - .MapExtraProperties() - .ForMember(dest => dest.RoleNames, opt => opt.Ignore()); - - CreateMap() - .ForMember(dest => dest.IsAssigned, opt => opt.Ignore()); - - //EditModal - CreateMap() - .MapExtraProperties() - .ForMember(dest => dest.RoleNames, opt => opt.Ignore()); - - CreateMap() - .ForMember(dest => dest.IsAssigned, opt => opt.Ignore()); - - CreateMap() - .ForMember(dest => dest.CreatedBy, opt => opt.Ignore()) - .ForMember(dest => dest.ModifiedBy, opt => opt.Ignore()); - } - - protected virtual void CreateRoleMappings() - { - //List - CreateMap(); - - //CreateModal - CreateMap() - .MapExtraProperties(); - - //EditModal - CreateMap() - .MapExtraProperties(); - } -} diff --git a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebMappers.cs b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebMappers.cs new file mode 100644 index 0000000000..1877dac1d8 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebMappers.cs @@ -0,0 +1,94 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; +using Volo.Abp.Identity.Web.Pages.Identity.Roles; +using CreateUserModalModel = Volo.Abp.Identity.Web.Pages.Identity.Users.CreateModalModel; +using EditUserModalModel = Volo.Abp.Identity.Web.Pages.Identity.Users.EditModalModel; + +namespace Volo.Abp.Identity.Web; + +[Mapper] +public partial class IdentityUserDtoToEditUserModalModelUserInfoViewModelMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(EditUserModalModel.UserInfoViewModel.Password))] + public override partial EditUserModalModel.UserInfoViewModel Map(IdentityUserDto source); + + [MapperIgnoreTarget(nameof(EditUserModalModel.UserInfoViewModel.Password))] + public override partial void Map(IdentityUserDto source, EditUserModalModel.UserInfoViewModel destination); +} + +[Mapper] +[MapExtraProperties] +public partial class CreateUserModalModelUserInfoViewModelToIdentityUserCreateDtoMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(IdentityUserCreateDto.RoleNames))] + public override partial IdentityUserCreateDto Map(CreateUserModalModel.UserInfoViewModel source); + + [MapperIgnoreTarget(nameof(IdentityUserCreateDto.RoleNames))] + public override partial void Map(CreateUserModalModel.UserInfoViewModel source, IdentityUserCreateDto destination); +} + +[Mapper] +[MapExtraProperties] +public partial class IdentityRoleDtoToCreateUserModalModelAssignedRoleViewModelMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(CreateUserModalModel.AssignedRoleViewModel.IsAssigned))] + public override partial CreateUserModalModel.AssignedRoleViewModel Map(IdentityRoleDto source); + + [MapperIgnoreTarget(nameof(CreateUserModalModel.AssignedRoleViewModel.IsAssigned))] + public override partial void Map(IdentityRoleDto source, CreateUserModalModel.AssignedRoleViewModel destination); +} + +[Mapper] +public partial class EditUserModalModelUserInfoViewModelToIdentityUserUpdateDtoMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(IdentityUserUpdateDto.RoleNames))] + public override partial IdentityUserUpdateDto Map(EditUserModalModel.UserInfoViewModel source); + + [MapperIgnoreTarget(nameof(IdentityUserUpdateDto.RoleNames))] + public override partial void Map(EditUserModalModel.UserInfoViewModel source, IdentityUserUpdateDto destination); +} + +[Mapper] +public partial class IdentityRoleDtoToEditUserModalModelAssignedRoleViewModelMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(EditUserModalModel.AssignedRoleViewModel.IsAssigned))] + public override partial EditUserModalModel.AssignedRoleViewModel Map(IdentityRoleDto source); + + [MapperIgnoreTarget(nameof(EditUserModalModel.AssignedRoleViewModel.IsAssigned))] + public override partial void Map(IdentityRoleDto source, EditUserModalModel.AssignedRoleViewModel destination); +} + +[Mapper] +public partial class IdentityUserDtoToEditUserModalModelDetailViewModelMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(EditUserModalModel.DetailViewModel.CreatedBy))] + [MapperIgnoreTarget(nameof(EditUserModalModel.DetailViewModel.ModifiedBy))] + public override partial EditUserModalModel.DetailViewModel Map(IdentityUserDto source); + + [MapperIgnoreTarget(nameof(EditUserModalModel.DetailViewModel.CreatedBy))] + [MapperIgnoreTarget(nameof(EditUserModalModel.DetailViewModel.ModifiedBy))] + public override partial void Map(IdentityUserDto source, EditUserModalModel.DetailViewModel destination); +} + +[Mapper] +public partial class IdentityRoleDtoToEditModalModelRoleInfoModelMapper : MapperBase +{ + public override partial EditModalModel.RoleInfoModel Map(IdentityRoleDto source); + public override partial void Map(IdentityRoleDto source, EditModalModel.RoleInfoModel destination); +} + +[Mapper] +[MapExtraProperties] +public partial class CreateModalModelRoleInfoModelToIdentityRoleCreateDtoMapper : MapperBase +{ + public override partial IdentityRoleCreateDto Map(CreateModalModel.RoleInfoModel source); + public override partial void Map(CreateModalModel.RoleInfoModel source, IdentityRoleCreateDto destination); +} + +[Mapper] +[MapExtraProperties] +public partial class EditModalModelRoleInfoModelToIdentityRoleUpdateDtoMapper : MapperBase +{ + public override partial IdentityRoleUpdateDto Map(EditModalModel.RoleInfoModel source); + public override partial void Map(EditModalModel.RoleInfoModel source, IdentityRoleUpdateDto destination); +} diff --git a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs index ae3e8ebc72..6d2faf6467 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.PageToolbars; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Http.ProxyScripting.Generators.JQuery; using Volo.Abp.Identity.Localization; using Volo.Abp.Identity.Web.Navigation; @@ -19,7 +19,7 @@ using Volo.Abp.Threading; namespace Volo.Abp.Identity.Web; [DependsOn(typeof(AbpIdentityApplicationContractsModule))] -[DependsOn(typeof(AbpAutoMapperModule))] +[DependsOn(typeof(AbpMapperlyModule))] [DependsOn(typeof(AbpPermissionManagementWebModule))] [DependsOn(typeof(AbpAspNetCoreMvcUiThemeSharedModule))] public class AbpIdentityWebModule : AbpModule @@ -51,12 +51,7 @@ public class AbpIdentityWebModule : AbpModule options.FileSets.AddEmbedded(); }); - context.Services.AddAutoMapperObjectMapper(); - - Configure(options => - { - options.AddProfile(validate: true); - }); + context.Services.AddMapperlyObjectMapper(); Configure(options => { diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj b/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj index 8a77024b52..54bd5fd28b 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj @@ -40,7 +40,7 @@ - + From e1eeb866175b51db3ddaabec32034bc9316ea3de Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 2 Aug 2025 14:37:44 +0800 Subject: [PATCH 13/19] Add `AutoMapper` reference in `Account` to fix the build problem. --- .../Volo.Abp.Account.Application.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj b/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj index d8c149f8b5..dd23b42528 100644 --- a/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj +++ b/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj @@ -21,6 +21,7 @@ + From 7c75110d6d250c5a1ceb49e726874de958bc724f Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Sun, 3 Aug 2025 00:57:07 +0300 Subject: [PATCH 14/19] Add Czech translations for Marked Items and comments Added new Czech localization strings for Marked Items, favorites, and comment approval features in cs.json. This update supports new UI elements and system messages related to marking items, filtering favorites, and comment moderation. --- .../CmsKit/Localization/Resources/cs.json | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/cs.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/cs.json index c530dd064b..17e549f7a7 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/cs.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/cs.json @@ -15,6 +15,7 @@ "CmsKit.Ratings": "Hodnocení", "CmsKit.Reactions": "Reakce", "CmsKit.Tags": "Tagy", + "CmsKit.MarkedItems": "Označené položky", "CmsKit:0002": "Obsah již existuje!", "CmsKit:0003": "Entitu {0} nelze označit.", "CmsKit:Blog:0001": "Daný slimák ({Slug}) již existuje!", @@ -26,8 +27,19 @@ "CmsKit:Rating:0001": "Entitu {EntityType} nelze hodnotit.", "CmsKit:Reaction:0001": "Entita {EntityType} nemůže mít reakce.", "CmsKit:Tag:0002": "Entitu nelze označit!", + "CmsKit:MarkedItem:ToggleConfirmation": "Opravdu chcete přepnout označenou položku?", + "ToggleFavorite": "Přidat/Odebrat z oblíbených", + "FavoritesFilterMessage": "Pro filtrování oblíbených se přihlaste", + "FilterOnFavorites": "Filtrovat podle oblíbených", "CommentAuthorizationExceptionMessage": "Tyto komentáře nejsou povoleny pro veřejné zobrazení.", + "CmsKit:Modals:Login": "Přihlášení", + "CmsKit:Modals:LoginModalDefaultMessage": "Pro pokračování se přihlaste!", + "CmsKit:Modals:YouAreNotAuthenticated": "Tato operace pro vás není autorizována.", "CommentDeletionConfirmationMessage": "Tento komentář a všechny odpovědi budou smazány!", + "CmsKit:MarkedItem:0001": "Entitu {EntityType} nelze označit.", + "CmsKit:MarkedItem:0002": "Pro typ entity '{EntityType}' nebyla nalezena žádná definice.", + "CmsKit:MarkedItem:0003": "Definice pro typ entity '{EntityType}' již existuje. Každý typ entity by měl mít pouze jednu definici.", + "CmsKit:MarkedItem:LoginMessage": "Pro označení této položky se přihlaste.", "Comments": "Komentáře", "Content": "Obsah", "ContentDeletionConfirmationMessage": "Opravdu chcete tento obsah smazat?", @@ -134,6 +146,7 @@ "SamplePageMessage": "Ukázková stránka pro modul Pro", "SaveChanges": "Uložit změny", "Script": "Skript", + "SelectLayout": "Vybrat rozložení", "SelectAll": "Vybrat vše", "Send": "Poslat", "SendMessage": "Poslat zprávu", @@ -216,6 +229,8 @@ "Feature:ReactionEnableDescription": "Systém reakcí CMS Kit, který umožňuje uživatelům posílat reakce entitám, jako jsou BlogPost, Komentáře atd.", "Feature:TagEnable": "Označování", "Feature:TagEnableDescription": "Systém značek CMS Kit, který umožňuje označování entit, jako je BlogPost.", + "Feature:MarkedItemEnable": "Označená položka", + "Feature:MarkedItemEnableDescription": "Systém označování CMS Kit, který umožňuje uživatelům označovat entity jako oblíbené.", "DeleteBlogPostMessage": "Blog bude smazán. Jsi si jistá?", "CaptchaCode": "Captcha kód", "CommentTextRequired": "Komentář je povinný", @@ -230,6 +245,37 @@ "TagsHelpText": "Značky by měly být odděleny čárkami (např.: tag1, tag2, tag3)", "ThisPartOfContentCouldntBeLoaded": "Tato část obsahu nemohla být načtena.", "DuplicateCommentAttemptMessage": "Byl zjištěn duplicitní pokus o vložení komentáře. Váš komentář již byl odeslán.", + "NoBlogPostYet": "Zatím žádný blogový příspěvek!", + "CmsKit:Comment": "Komentář", + "CmsKitCommentOptions:RequireApprovement": "Vyžadovat schválení komentářů", + "CmsKitCommentOptions:RequireApprovementDescription": "Když je povoleno, komentáře budou vyžadovat schválení před zveřejněním.", + "CommentFilter:ApproveState": "Stav schválení", + "ApproveState": "Stav schválení", + "CommentFilter:0": "Vše", + "CommentFilter:1": "Schváleno", + "CommentFilter:2": "Zamítnuto", + "CommentFilter:4": "Čeká", + "ApprovedSuccessfully": "Úspěšně schváleno", + "ApprovalRevokedSuccessfully": "Schválení úspěšně odvoláno", + "Approve": "Schválit", + "Disapproved": "Zamítnuto", + "CommentAlertMessage": "Čeká {0} komentářů na schválení", + "Settings:Menu:CmsKit": "CMS", + "CommentsAwaitingApproval": "Komentáře čekající na schválení", + "JustNow": "Právě teď", + "MinuteAgo": "před 1 minutou", + "MinutesAgo": "před {0} minutami", + "HourAgo": "před 1 hodinou", + "HoursAgo": "před {0} hodinami", + "YesterdayAt": "Včera v {0}", + "DayAt": "{0} v {1}", + "MonthDayAt": "{0} {1} v {2}", + "FullDate": "{0} {1} {2}", + "Minute": "m", + "Hour": "h", + "Day": "d", + "Week": "t", + "CommentSubmittedForApproval": "Váš komentář byl odeslán ke schválení.", "ChooseAnActionForBlog": "Vyberte akci pro blog", "AssignBlogPostsToOtherBlog": "Přiřaďte blogové příspěvky k jinému blogu", "SelectAnBlogToAssign": "Vyberte blog, ke kterému chcete přiřadit blogové příspěvky", From 25f8983c2bc394c060f0560429f3cef0c5dbf67b Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 3 Aug 2025 11:58:57 +0800 Subject: [PATCH 15/19] Replace AutoMapper with Mapperly in the account module. --- .../Volo.Abp.Account.Application.csproj | 2 +- .../Account/AbpAccountApplicationMappers.cs | 21 +++++++++++++++ .../Account/AbpAccountApplicationModule.cs | 10 +++---- ...countApplicationModuleAutoMapperProfile.cs | 15 ----------- .../AbpAccountBlazorAutoMapperProfile.cs | 20 -------------- .../AbpAccountBlazorMappers.cs | 27 +++++++++++++++++++ .../AbpAccountBlazorModule.cs | 15 ++++------- .../Volo.Abp.Account.Blazor.csproj | 2 +- .../AbpAccountWebAutomapperProfile.cs | 15 ----------- .../AbpAccountWebMappers.cs | 13 +++++++++ .../AbpAccountWebModule.cs | 14 ++++------ .../Volo.Abp.Account.Web.csproj | 2 +- 12 files changed, 77 insertions(+), 79 deletions(-) create mode 100644 modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationMappers.cs delete mode 100644 modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationModuleAutoMapperProfile.cs delete mode 100644 modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorAutoMapperProfile.cs create mode 100644 modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorMappers.cs delete mode 100644 modules/account/src/Volo.Abp.Account.Web/AbpAccountWebAutomapperProfile.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/AbpAccountWebMappers.cs diff --git a/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj b/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj index dd23b42528..b4530a5dd7 100644 --- a/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj +++ b/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj @@ -21,7 +21,7 @@ - + diff --git a/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationMappers.cs b/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationMappers.cs new file mode 100644 index 0000000000..dcfaf275dd --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationMappers.cs @@ -0,0 +1,21 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Identity; +using Volo.Abp.Mapperly; + +namespace Volo.Abp.Account; + +[Mapper] +[MapExtraProperties] +public partial class IdentityUserToProfileDtoMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(ProfileDto.HasPassword))] + public override partial ProfileDto Map(IdentityUser source); + + [MapperIgnoreTarget(nameof(ProfileDto.HasPassword))] + public override partial void Map(IdentityUser source, ProfileDto destination); + + public override void AfterMap(IdentityUser source, ProfileDto destination) + { + destination.HasPassword = source.PasswordHash != null; + } +} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationModule.cs b/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationModule.cs index 283e06a934..04f25d33d8 100644 --- a/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationModule.cs +++ b/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationModule.cs @@ -1,4 +1,4 @@ -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Emailing; using Volo.Abp.Identity; using Volo.Abp.Modularity; @@ -12,7 +12,8 @@ namespace Volo.Abp.Account; typeof(AbpAccountApplicationContractsModule), typeof(AbpIdentityApplicationModule), typeof(AbpUiNavigationModule), - typeof(AbpEmailingModule) + typeof(AbpEmailingModule), + typeof(AbpMapperlyModule) )] public class AbpAccountApplicationModule : AbpModule { @@ -23,11 +24,6 @@ public class AbpAccountApplicationModule : AbpModule options.FileSets.AddEmbedded(); }); - Configure(options => - { - options.AddProfile(validate: true); - }); - Configure(options => { options.Applications["MVC"].Urls[AccountUrlNames.PasswordReset] = "Account/ResetPassword"; diff --git a/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationModuleAutoMapperProfile.cs b/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationModuleAutoMapperProfile.cs deleted file mode 100644 index 0c117d98d0..0000000000 --- a/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationModuleAutoMapperProfile.cs +++ /dev/null @@ -1,15 +0,0 @@ -using AutoMapper; -using Volo.Abp.Identity; - -namespace Volo.Abp.Account; - -public class AbpAccountApplicationModuleAutoMapperProfile : Profile -{ - public AbpAccountApplicationModuleAutoMapperProfile() - { - CreateMap() - .ForMember(dest => dest.HasPassword, - op => op.MapFrom(src => src.PasswordHash != null)) - .MapExtraProperties(); - } -} diff --git a/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorAutoMapperProfile.cs b/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorAutoMapperProfile.cs deleted file mode 100644 index af59d75321..0000000000 --- a/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorAutoMapperProfile.cs +++ /dev/null @@ -1,20 +0,0 @@ -using AutoMapper; -using Volo.Abp.Account.Blazor.Pages.Account; -using Volo.Abp.AutoMapper; -using Volo.Abp.Identity; - -namespace Volo.Abp.Account.Blazor; - -public class AbpAccountBlazorAutoMapperProfile : Profile -{ - public AbpAccountBlazorAutoMapperProfile() - { - CreateMap() - .MapExtraProperties() - .Ignore(x => x.PhoneNumberConfirmed) - .Ignore(x => x.EmailConfirmed); - - CreateMap() - .MapExtraProperties(); - } -} diff --git a/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorMappers.cs b/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorMappers.cs new file mode 100644 index 0000000000..98ef9f9ab6 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorMappers.cs @@ -0,0 +1,27 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Account.Blazor.Pages.Account; +using Volo.Abp.Mapperly; +using Volo.Abp.Identity; + +namespace Volo.Abp.Account.Blazor; + +[Mapper] +[MapExtraProperties] +public partial class ProfileDtoToPersonalInfoModelMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(PersonalInfoModel.PhoneNumberConfirmed))] + [MapperIgnoreTarget(nameof(PersonalInfoModel.EmailConfirmed))] + public override partial PersonalInfoModel Map(ProfileDto source); + + [MapperIgnoreTarget(nameof(PersonalInfoModel.PhoneNumberConfirmed))] + [MapperIgnoreTarget(nameof(PersonalInfoModel.EmailConfirmed))] + public override partial void Map(ProfileDto source, PersonalInfoModel destination); +} + +[Mapper] +[MapExtraProperties] +public partial class PersonalInfoModelToUpdateProfileDtoMapper : MapperBase +{ + public override partial UpdateProfileDto Map(PersonalInfoModel source); + public override partial void Map(PersonalInfoModel source, UpdateProfileDto destination); +} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorModule.cs b/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorModule.cs index 61b42e1807..6a44f156ee 100644 --- a/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorModule.cs +++ b/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorModule.cs @@ -2,7 +2,7 @@ using Volo.Abp.Account.Blazor.Pages.Account; using Volo.Abp.AspNetCore.Components.Web.Theming; using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Modularity; using Volo.Abp.ObjectExtending; using Volo.Abp.ObjectExtending.Modularity; @@ -13,21 +13,16 @@ namespace Volo.Abp.Account.Blazor; [DependsOn( typeof(AbpAspNetCoreComponentsWebThemingModule), - typeof(AbpAutoMapperModule), + typeof(AbpMapperlyModule), typeof(AbpAccountApplicationContractsModule) )] public class AbpAccountBlazorModule : AbpModule { private readonly static OneTimeRunner OneTimeRunner = new OneTimeRunner(); - + public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddAutoMapperObjectMapper(); - - Configure(options => - { - options.AddProfile(validate: true); - }); + context.Services.AddMapperlyObjectMapper(); Configure(options => { @@ -39,7 +34,7 @@ public class AbpAccountBlazorModule : AbpModule options.AdditionalAssemblies.Add(typeof(AbpAccountBlazorModule).Assembly); }); } - + public override void PostConfigureServices(ServiceConfigurationContext context) { OneTimeRunner.Run(() => diff --git a/modules/account/src/Volo.Abp.Account.Blazor/Volo.Abp.Account.Blazor.csproj b/modules/account/src/Volo.Abp.Account.Blazor/Volo.Abp.Account.Blazor.csproj index 0eaa3cd978..7590216d06 100644 --- a/modules/account/src/Volo.Abp.Account.Blazor/Volo.Abp.Account.Blazor.csproj +++ b/modules/account/src/Volo.Abp.Account.Blazor/Volo.Abp.Account.Blazor.csproj @@ -10,7 +10,7 @@ - + diff --git a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebAutomapperProfile.cs b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebAutomapperProfile.cs deleted file mode 100644 index cdc92e8b19..0000000000 --- a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebAutomapperProfile.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Volo.Abp.Account.Web.Pages.Account; -using Volo.Abp.Identity; -using AutoMapper; -using Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo; - -namespace Volo.Abp.Account.Web; - -public class AbpAccountWebAutoMapperProfile : Profile -{ - public AbpAccountWebAutoMapperProfile() - { - CreateMap() - .MapExtraProperties(); - } -} diff --git a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebMappers.cs b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebMappers.cs new file mode 100644 index 0000000000..09a2e8373c --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebMappers.cs @@ -0,0 +1,13 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo; +using Volo.Abp.Mapperly; + +namespace Volo.Abp.Account.Web; + +[Mapper] +[MapExtraProperties] +public partial class ProfileDtoToPersonalInfoModelMapper : MapperBase +{ + public override partial AccountProfilePersonalInfoManagementGroupViewComponent.PersonalInfoModel Map(ProfileDto source); + public override partial void Map(ProfileDto source, AccountProfilePersonalInfoManagementGroupViewComponent.PersonalInfoModel destination); +} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs index 1b41deaff7..f246beed84 100644 --- a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs +++ b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs @@ -8,7 +8,7 @@ using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.ExceptionHandling; using Volo.Abp.Http.ProxyScripting.Generators.JQuery; using Volo.Abp.Identity.AspNetCore; @@ -24,14 +24,14 @@ namespace Volo.Abp.Account.Web; [DependsOn( typeof(AbpAccountApplicationContractsModule), typeof(AbpIdentityAspNetCoreModule), - typeof(AbpAutoMapperModule), + typeof(AbpMapperlyModule), typeof(AbpAspNetCoreMvcUiThemeSharedModule), typeof(AbpExceptionHandlingModule) )] public class AbpAccountWebModule : AbpModule { private readonly static OneTimeRunner OneTimeRunner = new OneTimeRunner(); - + public override void PreConfigureServices(ServiceConfigurationContext context) { context.Services.PreConfigure(options => @@ -59,11 +59,7 @@ public class AbpAccountWebModule : AbpModule ConfigureProfileManagementPage(); - context.Services.AddAutoMapperObjectMapper(); - Configure(options => - { - options.AddProfile(validate: true); - }); + context.Services.AddMapperlyObjectMapper(); Configure(options => { @@ -96,7 +92,7 @@ public class AbpAccountWebModule : AbpModule }); } - + public override void PostConfigureServices(ServiceConfigurationContext context) { OneTimeRunner.Run(() => diff --git a/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj b/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj index efedb773fe..3f52eae0a4 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj +++ b/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj @@ -39,7 +39,7 @@ - + From bf7349ba3cd9cddd10d060006b30e2fb5636898e Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 4 Aug 2025 20:54:42 +0800 Subject: [PATCH 16/19] Refactor collection type handling in ObjectMappingHelper and enhance tests for mapping collections --- .../Abp/ObjectMapping/ObjectMappingHelper.cs | 56 +++++++++---------- .../Mapperly/AbpMapperlyModule_Basic_Tests.cs | 25 ++++++++- 2 files changed, 50 insertions(+), 31 deletions(-) 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 cbfd33988b..5949edaba9 100644 --- a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/ObjectMappingHelper.cs +++ b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/ObjectMappingHelper.cs @@ -28,16 +28,17 @@ public static class ObjectMappingHelper private static (Type, Type, Type)? IsCollectionGenericTypeInternal() { - Type sourceArgumentType = null!; - Type destinationArgumentType = null!; - Type definitionGenericType = null!; - - if ((!typeof(TSource).IsGenericType && !typeof(TSource).IsArray) || - (!typeof(TDestination).IsGenericType && !typeof(TDestination).IsArray)) + if (!IsCollectionGenericTypeInternal(typeof(TSource), out var sourceArgumentType, out _) || + !IsCollectionGenericTypeInternal(typeof(TDestination), out var destinationArgumentType, out var definitionGenericType)) { return null; } + return (sourceArgumentType, destinationArgumentType, definitionGenericType); + } + + private static bool IsCollectionGenericTypeInternal(Type type, out Type elementType, out Type definitionGenericType) + { var supportedCollectionTypes = new[] { typeof(IEnumerable<>), @@ -47,39 +48,34 @@ public static class ObjectMappingHelper typeof(List<>) }; - if (typeof(TSource).IsGenericType && supportedCollectionTypes.Any(x => x == typeof(TSource).GetGenericTypeDefinition())) - { - sourceArgumentType = typeof(TSource).GenericTypeArguments[0]; - } - - if (typeof(TSource).IsArray) - { - sourceArgumentType = typeof(TSource).GetElementType()!; - } - - if (sourceArgumentType == null) + if (type.IsArray) { - return null; + elementType = type.GetElementType()!; + definitionGenericType = type; + return true; } - definitionGenericType = typeof(List<>); - if (typeof(TDestination).IsGenericType && supportedCollectionTypes.Any(x => x == typeof(TDestination).GetGenericTypeDefinition())) + if (type.IsGenericType && + supportedCollectionTypes.Contains(type.GetGenericTypeDefinition()) || + type.GetInterfaces().Any(i => i.IsGenericType && supportedCollectionTypes.Contains(i.GetGenericTypeDefinition()))) { - destinationArgumentType = typeof(TDestination).GenericTypeArguments[0]; + elementType = type.GetGenericArguments()[0]; + definitionGenericType = type.GetGenericTypeDefinition(); + if (definitionGenericType == typeof(IEnumerable<>) || + definitionGenericType == typeof(IList<>)) + { + definitionGenericType = typeof(List<>); + } - if (typeof(TDestination).GetGenericTypeDefinition() == typeof(ICollection<>) || - typeof(TDestination).GetGenericTypeDefinition() == typeof(Collection<>)) + if (definitionGenericType == typeof(ICollection<>)) { definitionGenericType = typeof(Collection<>); } + return true; } - if (typeof(TDestination).IsArray) - { - destinationArgumentType = typeof(TDestination).GetElementType()!; - definitionGenericType = typeof(Array); - } - - return (sourceArgumentType, destinationArgumentType, definitionGenericType); + elementType = null!; + definitionGenericType = null!; + return false; } } diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs index 50a799e5a3..3f0773f0a1 100644 --- a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices.JavaScript; using Microsoft.Extensions.DependencyInjection; using Shouldly; using Volo.Abp.Mapperly.SampleClasses; @@ -55,13 +57,23 @@ public class AbpMapperlyModule_Basic_Tests : AbpIntegratedTest, MyEntityDto[]>(new List + { + new MyEntity { Number = 42 }, + new MyEntity { Number = 43 } + }.AsReadOnly()); + + dto2.Length.ShouldBe(2); + dto2[0].Number.ShouldBe(42); + dto2[1].Number.ShouldBe(43); + var dtoList = new List(); { new MyEntityDto() { Number = 44 }; new MyEntityDto() { Number = 45 }; } - dto = _objectMapper.Map, List>(new List + _objectMapper.Map, List>(new List { new MyEntity { Number = 42 }, new MyEntity { Number = 43 } @@ -70,6 +82,17 @@ public class AbpMapperlyModule_Basic_Tests : AbpIntegratedTest, MyEntityDto[]>(new List + { + new MyEntity { Number = 42 }, + new MyEntity { Number = 43 } + }.AsReadOnly(), dtoArray); + + dtoArray.Length.ShouldBe(2); + dtoArray[0].Number.ShouldBe(42); + dtoArray[1].Number.ShouldBe(43); } [Fact] From 76cb91ef0ee1e6611a53d27fcbfb9e2e330e3962 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 5 Aug 2025 13:49:35 +0800 Subject: [PATCH 17/19] Return a new list from `WebRequestResources`. Otherwise, the content in `Resources` may return inconsistent data. --- .../Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs index e68b671fc6..ee3c1730dc 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs @@ -30,6 +30,6 @@ public class WebRequestResources : IWebRequestResources, IScopedDependency } Resources.Add(path, resources); - return resources; + return resources.ToList(); } } From f30ae61213de4b6074c1b95c851c78f87209768d Mon Sep 17 00:00:00 2001 From: Engincan VESKE Date: Tue, 5 Aug 2025 06:10:51 +0000 Subject: [PATCH 18/19] Update part-05.md --- docs/en/tutorials/modular-crm/part-05.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/tutorials/modular-crm/part-05.md b/docs/en/tutorials/modular-crm/part-05.md index 139c4a9a8b..d2082667aa 100644 --- a/docs/en/tutorials/modular-crm/part-05.md +++ b/docs/en/tutorials/modular-crm/part-05.md @@ -167,7 +167,7 @@ The `ReplaceDbContext` attribute allows the use of the `ModularCrmDbContext` cla ````csharp public class ModularCrmDbContext : AbpDbContext, - IProductsDbContext, + ICatalogDbContext, IOrderingDbContext //NEW: IMPLEMENT THE INTERFACE { public DbSet Products { get; set; } From 1314d1a975ce35238d634b8d0cad478f970d8cdb Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 5 Aug 2025 15:09:13 +0800 Subject: [PATCH 19/19] Add comments to clarify the return value in `WebRequestResources`. --- .../Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs index ee3c1730dc..4e346b1274 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs @@ -30,6 +30,8 @@ public class WebRequestResources : IWebRequestResources, IScopedDependency } Resources.Add(path, resources); + + // Create a new list as return value to avoid any changes to the list will affect the value in Resources dictionary. return resources.ToList(); } }