From 171f7971c4e21e894ba93c2f06a44ae575a7b2cb Mon Sep 17 00:00:00 2001 From: Super Date: Fri, 24 Oct 2025 22:56:18 +0800 Subject: [PATCH 01/30] Redo `AbpDateTimeModelBinder` to align with the current TZ For https://github.com/abpframework/abp/issues/24048. --- .../ModelBinding/AbpDateTimeModelBinder.cs | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs index 35a06c9c1d..35e1777f4c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs @@ -20,9 +20,35 @@ public class AbpDateTimeModelBinder : IModelBinder public async Task BindModelAsync(ModelBindingContext bindingContext) { await _dateTimeModelBinder.BindModelAsync(bindingContext); - if (bindingContext.Result.IsModelSet && bindingContext.Result.Model is DateTime dateTime) + + if (!bindingContext.Result.IsModelSet || bindingContext.Result.Model is not DateTime dateTime) { - bindingContext.Result = ModelBindingResult.Success(_clock.Normalize(dateTime)); + return; } + + // If the DateTime has no timezone info (most cases from input) + if (dateTime.Kind == DateTimeKind.Unspecified) + { + // Try to get user's timezone + var userTz = _currentTimezoneProvider.TimeZone; + if (!userTz.IsNullOrWhiteSpace()) + { + try + { + var tzInfo = _timezoneProvider.GetTimeZoneInfo(userTz); + // Treat the input as user's local time and convert to UTC + var utc = TimeZoneInfo.ConvertTimeToUtc(dateTime, tzInfo); + bindingContext.Result = ModelBindingResult.Success(utc); + return; + } + catch + { + // fallback to default clock normalization if invalid TZ + } + } + } + + // fallback: original behavior + bindingContext.Result = ModelBindingResult.Success(_clock.Normalize(dateTime)); } } From fcf835a8af7f3ef67281080206b2f4526a870622 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Mon, 8 Dec 2025 20:02:26 +0100 Subject: [PATCH 02/30] Use frozen collections where suitable --- .../AbpDatePickerBaseTagHelperService.cs | 31 ++++++++++--------- ...UiObjectExtensionPropertyInfoExtensions.cs | 5 +-- .../AbpBackgroundWorkersTickerQOptions.cs | 8 ++--- ...UiObjectExtensionPropertyInfoExtensions.cs | 9 +++--- .../ProjectModification/NpmPackagesUpdater.cs | 7 +---- .../Abp/Logging/DefaultInitLoggerFactory.cs | 4 +-- .../Volo/Abp/Reflection/TypeHelper.cs | 9 +++--- ...PostConfigureAbpRabbitMqEventBusOptions.cs | 13 ++++---- .../Generators/ProxyScriptingJsFuncHelper.cs | 5 +-- .../LocalizationResourceDictionary.cs | 2 +- .../MemoryDb/MemoryDatabaseCollection.cs | 2 +- .../Volo/Abp/MongoDB/MongoModelBuilder.cs | 2 +- .../Abp/Specifications/ParameterRebinder.cs | 2 +- .../MultiLingualObjectManager_Tests.cs | 5 +-- .../app/VoloDocs.Web/Pages/Error.cshtml.cs | 5 +-- 15 files changed, 57 insertions(+), 52 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs index 5088c08293..3f7b6b0afa 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; @@ -23,7 +24,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; public abstract class AbpDatePickerBaseTagHelperService : AbpTagHelperService where TTagHelper : AbpDatePickerBaseTagHelper { - protected readonly Dictionary> SupportedInputTypes; + protected readonly FrozenDictionary> SupportedInputTypes; protected readonly IJsonSerializer JsonSerializer; protected readonly IHtmlGenerator Generator; @@ -103,7 +104,7 @@ public abstract class AbpDatePickerBaseTagHelperService : AbpTagHelp return string.Empty; } } - }; + }.ToFrozenDictionary(); } protected virtual T? GetAttribute() where T : Attribute @@ -136,7 +137,7 @@ public abstract class AbpDatePickerBaseTagHelperService : AbpTagHelp ? await ProcessButtonAndGetContentAsync(context, output, "calendar", "open") : ""; var clearButtonContent = TagHelper.ClearButton == true || (!TagHelper.ClearButton.HasValue && TagHelper.AutoUpdateInput != true) - ? await ProcessButtonAndGetContentAsync(context, output, "times", "clear", visible:!TagHelper.SingleOpenAndClearButton) + ? await ProcessButtonAndGetContentAsync(context, output, "times", "clear", visible: !TagHelper.SingleOpenAndClearButton) : ""; var labelContent = await GetLabelAsHtmlAsync(context, output, TagHelperOutput); @@ -269,7 +270,7 @@ public abstract class AbpDatePickerBaseTagHelperService : AbpTagHelp { var attrList = new TagHelperAttributeList(); - if(options == null) + if (options == null) { return attrList; } @@ -401,29 +402,29 @@ public abstract class AbpDatePickerBaseTagHelperService : AbpTagHelp attrList.Add("data-visible-date-format", options.VisibleDateFormat); } - if(!options.InputDateFormat.IsNullOrEmpty()) + if (!options.InputDateFormat.IsNullOrEmpty()) { attrList.Add("data-input-date-format", options.InputDateFormat); } - if(options.Ranges != null && options.Ranges.Any()) + if (options.Ranges != null && options.Ranges.Any()) { var ranges = options.Ranges.ToDictionary(r => r.Label, r => r.Dates); attrList.Add("data-ranges", JsonSerializer.Serialize(ranges)); } - if(options.AlwaysShowCalendars != null) + if (options.AlwaysShowCalendars != null) { attrList.Add("data-always-show-calendars", options.AlwaysShowCalendars.ToString()!.ToLowerInvariant()); } - if(options.ShowCustomRangeLabel == false) + if (options.ShowCustomRangeLabel == false) { attrList.Add("data-show-custom-range-label", options.ShowCustomRangeLabel.ToString()!.ToLowerInvariant()); } - if(options.Options != null) + if (options.Options != null) { attrList.Add("data-options", JsonSerializer.Serialize(options.Options)); } @@ -443,7 +444,7 @@ public abstract class AbpDatePickerBaseTagHelperService : AbpTagHelp attrList.Add("id", options.PickerId); } - if(!options.SingleOpenAndClearButton) + if (!options.SingleOpenAndClearButton) { attrList.Add("data-single-open-and-clear-button", options.SingleOpenAndClearButton.ToString().ToLowerInvariant()); } @@ -614,7 +615,8 @@ public abstract class AbpDatePickerBaseTagHelperService : AbpTagHelp { return string.Empty; } - var labelTagHelper = new LabelTagHelper(Generator) { + var labelTagHelper = new LabelTagHelper(Generator) + { ViewContext = TagHelper.ViewContext, For = modelExpression }; @@ -764,7 +766,8 @@ public abstract class AbpDatePickerBaseTagHelperService : AbpTagHelp TagHelper.Size = attribute.Size; } - return TagHelper.Size switch { + return TagHelper.Size switch + { AbpFormControlSize.Small => "form-control-sm", AbpFormControlSize.Medium => "form-control-md", AbpFormControlSize.Large => "form-control-lg", @@ -785,14 +788,14 @@ public abstract class AbpDatePickerBaseTagHelperService : AbpTagHelp protected virtual async Task GetValidationAsHtmlByInputAsync(TagHelperContext context, TagHelperOutput output, - [NotNull]ModelExpression @for) + [NotNull] ModelExpression @for) { var validationMessageTagHelper = new ValidationMessageTagHelper(Generator) { For = @for, ViewContext = TagHelper.ViewContext }; var attributeList = new TagHelperAttributeList { { "class", "text-danger" } }; - if(!output.Attributes.TryGetAttribute("name", out var nameAttribute) || nameAttribute == null || nameAttribute.Value == null) + if (!output.Attributes.TryGetAttribute("name", out var nameAttribute) || nameAttribute == null || nameAttribute.Value == null) { if (nameAttribute != null) { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/ObjectExtending/MvcUiObjectExtensionPropertyInfoExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/ObjectExtending/MvcUiObjectExtensionPropertyInfoExtensions.cs index 62b86bc1c5..ae5d30a2ac 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/ObjectExtending/MvcUiObjectExtensionPropertyInfoExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/ObjectExtending/MvcUiObjectExtensionPropertyInfoExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; @@ -8,7 +9,7 @@ namespace Volo.Abp.ObjectExtending; public static class MvcUiObjectExtensionPropertyInfoExtensions { - private static readonly HashSet NumberTypes = new HashSet { + private static readonly FrozenSet NumberTypes = new HashSet { typeof(int), typeof(long), typeof(byte), @@ -33,7 +34,7 @@ public static class MvcUiObjectExtensionPropertyInfoExtensions typeof(float?), typeof(double?), typeof(decimal?) - }; + }.ToFrozenSet(); public static string? GetInputFormatOrNull(this IBasicObjectExtensionPropertyInfo property) { diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQOptions.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQOptions.cs index 6d48a21262..438686d29f 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQOptions.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQOptions.cs @@ -5,11 +5,11 @@ namespace Volo.Abp.BackgroundWorkers.TickerQ; public class AbpBackgroundWorkersTickerQOptions { - private readonly Dictionary _onfigurations; + private readonly Dictionary _configurations; public AbpBackgroundWorkersTickerQOptions() { - _onfigurations = new Dictionary(); + _configurations = new Dictionary(); } public void AddConfiguration(AbpBackgroundWorkersCronTickerConfiguration configuration) @@ -19,7 +19,7 @@ public class AbpBackgroundWorkersTickerQOptions public void AddConfiguration(Type workerType, AbpBackgroundWorkersCronTickerConfiguration configuration) { - _onfigurations[workerType] = configuration; + _configurations[workerType] = configuration; } public AbpBackgroundWorkersCronTickerConfiguration? GetConfigurationOrNull() @@ -29,6 +29,6 @@ public class AbpBackgroundWorkersTickerQOptions public AbpBackgroundWorkersCronTickerConfiguration? GetConfigurationOrNull(Type workerType) { - return _onfigurations.GetValueOrDefault(workerType); + return _configurations.GetValueOrDefault(workerType); } } diff --git a/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiObjectExtensionPropertyInfoExtensions.cs b/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiObjectExtensionPropertyInfoExtensions.cs index 3026682f62..d535c87c00 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiObjectExtensionPropertyInfoExtensions.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiObjectExtensionPropertyInfoExtensions.cs @@ -1,5 +1,6 @@ using Blazorise; using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; @@ -11,7 +12,7 @@ namespace Volo.Abp.BlazoriseUI; public static class BlazoriseUiObjectExtensionPropertyInfoExtensions { - private static readonly HashSet NumberTypes = new HashSet { + private static readonly FrozenSet NumberTypes = new HashSet { typeof(int), typeof(long), typeof(byte), @@ -36,13 +37,13 @@ public static class BlazoriseUiObjectExtensionPropertyInfoExtensions typeof(float?), typeof(double?), typeof(decimal?) - }; + }.ToFrozenSet(); - private static readonly HashSet TextEditSupportedAttributeTypes = new HashSet { + private static readonly FrozenSet TextEditSupportedAttributeTypes = new HashSet { typeof(EmailAddressAttribute), typeof(UrlAttribute), typeof(PhoneAttribute) - }; + }.ToFrozenSet(); public static string? GetDateEditInputFormatOrNull(this IBasicObjectExtensionPropertyInfo property) { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs index b8ba1ea113..9f2cb4d221 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs @@ -3,14 +3,12 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NuGet.Versioning; -using Volo.Abp.Cli.Http; using Volo.Abp.Cli.LIbs; using Volo.Abp.Cli.Utils; using Volo.Abp.DependencyInjection; @@ -28,14 +26,12 @@ public class NpmPackagesUpdater : ITransientDependency private readonly PackageJsonFileFinder _packageJsonFileFinder; private readonly NpmGlobalPackagesChecker _npmGlobalPackagesChecker; - private readonly Dictionary _fileVersionStorage = new Dictionary(); - private readonly CliHttpClientFactory _cliHttpClientFactory; + private readonly Dictionary _fileVersionStorage = []; public NpmPackagesUpdater( PackageJsonFileFinder packageJsonFileFinder, NpmGlobalPackagesChecker npmGlobalPackagesChecker, ICancellationTokenProvider cancellationTokenProvider, - CliHttpClientFactory cliHttpClientFactory, IInstallLibsService installLibsService, ICmdHelper cmdHelper) { @@ -44,7 +40,6 @@ public class NpmPackagesUpdater : ITransientDependency CancellationTokenProvider = cancellationTokenProvider; InstallLibsService = installLibsService; CmdHelper = cmdHelper; - _cliHttpClientFactory = cliHttpClientFactory; Logger = NullLogger.Instance; } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLoggerFactory.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLoggerFactory.cs index 1fa5d45090..638888493d 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLoggerFactory.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLoggerFactory.cs @@ -5,10 +5,10 @@ namespace Volo.Abp.Logging; public class DefaultInitLoggerFactory : IInitLoggerFactory { - private readonly Dictionary _cache = new Dictionary(); + private readonly Dictionary _cache = []; public virtual IInitLogger Create() { - return (IInitLogger)_cache.GetOrAdd(typeof(T), () => new DefaultInitLogger()); ; + return (IInitLogger)_cache.GetOrAdd(typeof(T), () => new DefaultInitLogger()); } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs index 0118fd9874..87c9419cfa 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Frozen; using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel; @@ -13,14 +14,14 @@ namespace Volo.Abp.Reflection; public static class TypeHelper { - private static readonly HashSet FloatingTypes = new HashSet + private static readonly FrozenSet FloatingTypes = new HashSet { typeof(float), typeof(double), typeof(decimal) - }; + }.ToFrozenSet(); - private static readonly HashSet NonNullablePrimitiveTypes = new HashSet + private static readonly FrozenSet NonNullablePrimitiveTypes = new HashSet { typeof(byte), typeof(short), @@ -37,7 +38,7 @@ public static class TypeHelper typeof(DateTimeOffset), typeof(TimeSpan), typeof(Guid) - }; + }.ToFrozenSet(); public static bool IsNonNullablePrimitiveType(Type type) { diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/PostConfigureAbpRabbitMqEventBusOptions.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/PostConfigureAbpRabbitMqEventBusOptions.cs index f4e69c46c6..2c400e01da 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/PostConfigureAbpRabbitMqEventBusOptions.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/PostConfigureAbpRabbitMqEventBusOptions.cs @@ -1,3 +1,4 @@ +using System.Collections.Frozen; using System.Collections.Generic; using Microsoft.Extensions.Options; @@ -5,8 +6,8 @@ namespace Volo.Abp.EventBus.RabbitMq; public class PostConfigureAbpRabbitMqEventBusOptions : IPostConfigureOptions { - private readonly HashSet _uint64QueueArguments = - [ + private readonly FrozenSet _uint64QueueArguments = new HashSet() + { "x-delivery-limit", "x-expires", "x-message-ttl", @@ -16,12 +17,12 @@ public class PostConfigureAbpRabbitMqEventBusOptions : IPostConfigureOptions _boolQueueArguments = - [ + private readonly FrozenSet _boolQueueArguments = new HashSet() + { "x-single-active-consumer" - ]; + }.ToFrozenSet(); public virtual void PostConfigure(string? name, AbpRabbitMqEventBusOptions options) { diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs index 33c5b0d05f..21547bd05d 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Linq; using System.Text; @@ -10,7 +11,7 @@ internal static class ProxyScriptingJsFuncHelper { private const string ValidJsVariableNameChars = "abcdefghijklmnopqrstuxwvyzABCDEFGHIJKLMNOPQRSTUXWVYZ0123456789_"; - private static readonly HashSet ReservedWords = new HashSet { + private static readonly FrozenSet ReservedWords = new HashSet { "abstract", "else", "instanceof", @@ -71,7 +72,7 @@ internal static class ProxyScriptingJsFuncHelper "in", "static", "with" - }; + }.ToFrozenSet(); public static string NormalizeJsVariableName(string name, string additionalChars = "") { diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceDictionary.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceDictionary.cs index f6487a85b7..0b8d3d7f63 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceDictionary.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceDictionary.cs @@ -6,7 +6,7 @@ namespace Volo.Abp.Localization; public class LocalizationResourceDictionary : Dictionary { - private readonly Dictionary _resourcesByTypes = new(); + private readonly Dictionary _resourcesByTypes = []; public LocalizationResource Add(string? defaultCultureName = null) { diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseCollection.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseCollection.cs index 3e08c63417..7f4f9dbcdf 100644 --- a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseCollection.cs +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseCollection.cs @@ -8,7 +8,7 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb; public class MemoryDatabaseCollection : IMemoryDatabaseCollection where TEntity : class, IEntity { - private readonly Dictionary _dictionary = new Dictionary(); + private readonly Dictionary _dictionary = []; private readonly IMemoryDbSerializer _memoryDbSerializer; diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs index d7ad79f7d0..a68f1584f8 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs @@ -17,7 +17,7 @@ public class MongoModelBuilder : IMongoModelBuilder { private readonly Dictionary _entityModelBuilders; - private static readonly object SyncObj = new object(); + private static readonly object SyncObj = new(); public MongoModelBuilder() { diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ParameterRebinder.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ParameterRebinder.cs index d3553369b8..be0d8ab3e9 100644 --- a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ParameterRebinder.cs +++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ParameterRebinder.cs @@ -15,7 +15,7 @@ internal class ParameterRebinder : ExpressionVisitor internal ParameterRebinder(Dictionary map) { - _map = map ?? new Dictionary(); + _map = map ?? []; } internal static Expression ReplaceParameters(Dictionary map, diff --git a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs index e442d80953..6b18822df3 100644 --- a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs +++ b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -18,12 +19,12 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest _books; private readonly IMapperAccessor _mapperAccessor; - private readonly Dictionary _testTranslations = new() + private readonly FrozenDictionary _testTranslations = new Dictionary() { ["ar"] = "C# التعمق في", ["zh-Hans"] = "深入理解C#", ["en"] = "C# in Depth" - }; + }.ToFrozenDictionary(); public MultiLingualObjectManager_Tests() { diff --git a/modules/docs/app/VoloDocs.Web/Pages/Error.cshtml.cs b/modules/docs/app/VoloDocs.Web/Pages/Error.cshtml.cs index 5e5c4f639b..88ed671381 100644 --- a/modules/docs/app/VoloDocs.Web/Pages/Error.cshtml.cs +++ b/modules/docs/app/VoloDocs.Web/Pages/Error.cshtml.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Net; using Microsoft.AspNetCore.Diagnostics; @@ -48,7 +49,7 @@ namespace VoloDocs.Web.Pages #region Error Messages /*For more ASCII arts http://patorjk.com/software/taag/#p=display&h=0&f=Big&t=400*/ - private readonly Dictionary _errorMessages = new Dictionary + private readonly FrozenDictionary _errorMessages = new Dictionary { { 400, @" @@ -131,7 +132,7 @@ Ooops! Our server is experiencing a mild case of the hiccups." Looks like we're having some server issues." } - }; + }.ToFrozenDictionary(); #endregion } } \ No newline at end of file From 9db10bbd578390b459a3c6557bdc4b47e1d0db80 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Tue, 9 Dec 2025 08:21:21 +0100 Subject: [PATCH 03/30] Cleaning --- ...UiObjectExtensionPropertyInfoExtensions.cs | 3 +- ...UiObjectExtensionPropertyInfoExtensions.cs | 6 +- ...PostConfigureAbpRabbitMqEventBusOptions.cs | 32 ++-- .../Generators/ProxyScriptingJsFuncHelper.cs | 3 +- .../MultiLingualObjectManager_Tests.cs | 166 +++++++++--------- 5 files changed, 107 insertions(+), 103 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/ObjectExtending/MvcUiObjectExtensionPropertyInfoExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/ObjectExtending/MvcUiObjectExtensionPropertyInfoExtensions.cs index ae5d30a2ac..49ff15f9f5 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/ObjectExtending/MvcUiObjectExtensionPropertyInfoExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/ObjectExtending/MvcUiObjectExtensionPropertyInfoExtensions.cs @@ -9,7 +9,8 @@ namespace Volo.Abp.ObjectExtending; public static class MvcUiObjectExtensionPropertyInfoExtensions { - private static readonly FrozenSet NumberTypes = new HashSet { + private static readonly FrozenSet NumberTypes = new HashSet + { typeof(int), typeof(long), typeof(byte), diff --git a/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiObjectExtensionPropertyInfoExtensions.cs b/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiObjectExtensionPropertyInfoExtensions.cs index d535c87c00..5794021488 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiObjectExtensionPropertyInfoExtensions.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiObjectExtensionPropertyInfoExtensions.cs @@ -12,7 +12,8 @@ namespace Volo.Abp.BlazoriseUI; public static class BlazoriseUiObjectExtensionPropertyInfoExtensions { - private static readonly FrozenSet NumberTypes = new HashSet { + private static readonly FrozenSet NumberTypes = new HashSet + { typeof(int), typeof(long), typeof(byte), @@ -39,7 +40,8 @@ public static class BlazoriseUiObjectExtensionPropertyInfoExtensions typeof(decimal?) }.ToFrozenSet(); - private static readonly FrozenSet TextEditSupportedAttributeTypes = new HashSet { + private static readonly FrozenSet TextEditSupportedAttributeTypes = new HashSet + { typeof(EmailAddressAttribute), typeof(UrlAttribute), typeof(PhoneAttribute) diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/PostConfigureAbpRabbitMqEventBusOptions.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/PostConfigureAbpRabbitMqEventBusOptions.cs index 2c400e01da..76a74e2b3e 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/PostConfigureAbpRabbitMqEventBusOptions.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/PostConfigureAbpRabbitMqEventBusOptions.cs @@ -6,23 +6,23 @@ namespace Volo.Abp.EventBus.RabbitMq; public class PostConfigureAbpRabbitMqEventBusOptions : IPostConfigureOptions { - private readonly FrozenSet _uint64QueueArguments = new HashSet() - { - "x-delivery-limit", - "x-expires", - "x-message-ttl", - "x-max-length", - "x-max-length-bytes", - "x-quorum-initial-group-size", - "x-quorum-target-group-size", - "x-stream-filter-size-bytes", - "x-stream-max-segment-size-bytes", - }.ToFrozenSet(); + private readonly FrozenSet _uint64QueueArguments = new HashSet + { + "x-delivery-limit", + "x-expires", + "x-message-ttl", + "x-max-length", + "x-max-length-bytes", + "x-quorum-initial-group-size", + "x-quorum-target-group-size", + "x-stream-filter-size-bytes", + "x-stream-max-segment-size-bytes", + }.ToFrozenSet(); - private readonly FrozenSet _boolQueueArguments = new HashSet() - { - "x-single-active-consumer" - }.ToFrozenSet(); + private readonly FrozenSet _boolQueueArguments = new HashSet + { + "x-single-active-consumer" + }.ToFrozenSet(); public virtual void PostConfigure(string? name, AbpRabbitMqEventBusOptions options) { diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs index 21547bd05d..ebc296e697 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs @@ -11,7 +11,8 @@ internal static class ProxyScriptingJsFuncHelper { private const string ValidJsVariableNameChars = "abcdefghijklmnopqrstuxwvyzABCDEFGHIJKLMNOPQRSTUXWVYZ0123456789_"; - private static readonly FrozenSet ReservedWords = new HashSet { + private static readonly FrozenSet ReservedWords = new HashSet + { "abstract", "else", "instanceof", diff --git a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs index 6b18822df3..bb527d2fcd 100644 --- a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs +++ b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs @@ -1,38 +1,38 @@ -using System; -using System.Collections.Frozen; -using System.Collections.Generic; +using System; +using System.Collections.Frozen; +using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Shouldly; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; using Volo.Abp.AutoMapper; -using Volo.Abp.Localization; -using Volo.Abp.MultiLingualObjects.TestObjects; -using Volo.Abp.Testing; -using Xunit; - -namespace Volo.Abp.MultiLingualObjects; - -public class MultiLingualObjectManager_Tests : AbpIntegratedTest -{ - private readonly IMultiLingualObjectManager _multiLingualObjectManager; - private readonly MultiLingualBook _book; - private readonly List _books; - private readonly IMapperAccessor _mapperAccessor; - private readonly FrozenDictionary _testTranslations = new Dictionary() - { - ["ar"] = "C# التعمق في", - ["zh-Hans"] = "深入理解C#", - ["en"] = "C# in Depth" - }.ToFrozenDictionary(); +using Volo.Abp.Localization; +using Volo.Abp.MultiLingualObjects.TestObjects; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.MultiLingualObjects; + +public class MultiLingualObjectManager_Tests : AbpIntegratedTest +{ + private readonly IMultiLingualObjectManager _multiLingualObjectManager; + private readonly MultiLingualBook _book; + private readonly List _books; + private readonly IMapperAccessor _mapperAccessor; + private readonly FrozenDictionary _testTranslations = new Dictionary + { + ["ar"] = "C# التعمق في", + ["zh-Hans"] = "深入理解C#", + ["en"] = "C# in Depth" + }.ToFrozenDictionary(); - public MultiLingualObjectManager_Tests() - { + public MultiLingualObjectManager_Tests() + { _multiLingualObjectManager = ServiceProvider.GetRequiredService(); //Single Lookup - _book = GetTestBook("en", "zh-Hans"); - //Bulk lookup + _book = GetTestBook("en", "zh-Hans"); + //Bulk lookup _books = new List { //has no translations @@ -46,14 +46,14 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest(); + _mapperAccessor = ServiceProvider.GetRequiredService(); } MultiLingualBook GetTestBook(params string[] included) { - var id = Guid.NewGuid(); - //Single book - var res = new MultiLingualBook(id, 100); - + var id = Guid.NewGuid(); + //Single book + var res = new MultiLingualBook(id, 100); + foreach (var language in included) { res.Translations.Add(new MultiLingualBookTranslation @@ -66,45 +66,45 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest(_book); - translation.ShouldNotBeNull(); - translation.Name.ShouldBe(_testTranslations["en"]); - } - } - - [Fact] - public async Task GetTranslationFromListAsync() - { - using (CultureHelper.Use("en-us")) - { - var translation = await _multiLingualObjectManager.GetTranslationAsync(_book.Translations); - translation.ShouldNotBeNull(); - translation.Name.ShouldBe(_testTranslations["en"]); - } - } - - [Fact] - public async Task Should_Get_Specified_Language() - { - using (CultureHelper.Use("zh-Hans")) - { - var translation = await _multiLingualObjectManager.GetTranslationAsync(_book, culture: "en"); - translation.ShouldNotBeNull(); - translation.Name.ShouldBe(_testTranslations["en"]); - } + [Fact] + public async Task GetTranslationAsync() + { + using (CultureHelper.Use("en-us")) + { + var translation = await _multiLingualObjectManager.GetTranslationAsync(_book); + translation.ShouldNotBeNull(); + translation.Name.ShouldBe(_testTranslations["en"]); + } + } + + [Fact] + public async Task GetTranslationFromListAsync() + { + using (CultureHelper.Use("en-us")) + { + var translation = await _multiLingualObjectManager.GetTranslationAsync(_book.Translations); + translation.ShouldNotBeNull(); + translation.Name.ShouldBe(_testTranslations["en"]); + } + } + + [Fact] + public async Task Should_Get_Specified_Language() + { + using (CultureHelper.Use("zh-Hans")) + { + var translation = await _multiLingualObjectManager.GetTranslationAsync(_book, culture: "en"); + translation.ShouldNotBeNull(); + translation.Name.ShouldBe(_testTranslations["en"]); + } } - [Fact] - public async Task GetBulkTranslationsAsync() - { - using (CultureHelper.Use("en-us")) - { + [Fact] + public async Task GetBulkTranslationsAsync() + { + using (CultureHelper.Use("en-us")) + { var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync(_books); foreach (var (entity, translation) in translations) { @@ -118,26 +118,26 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest x.Translations)); foreach (var translation in translations) { translation?.Name.ShouldBe(_testTranslations["en"]); } - } - } - - [Fact] + } + } + + [Fact] public async Task TestBulkMapping() { - using (CultureHelper.Use("en-us")) + using (CultureHelper.Use("en-us")) { var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync(_books); var translationsDict = translations.ToDictionary(x => x.entity.Id, x => x.translation); @@ -153,5 +153,5 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest x.Language == "en")?.Name, m.Name); } } - } -} + } +} From 12f4797179a927e906f9160d57e39e4d8d2b47a7 Mon Sep 17 00:00:00 2001 From: selman koc <64414348+skoc10@users.noreply.github.com> Date: Tue, 30 Dec 2025 11:12:31 +0300 Subject: [PATCH 04/30] Update version for nightly build 10.2.0preview --- common.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common.props b/common.props index 42e230791c..2b274e5d75 100644 --- a/common.props +++ b/common.props @@ -1,8 +1,8 @@ latest - 10.1.0-preview - 5.1.0-preview + 10.2.0-preview + 5.2.0-preview $(NoWarn);CS1591;CS0436 https://abp.io/assets/abp_nupkg.png https://abp.io/ From f253f9945e4be1b1c076aaefd5a5cbfb9fa2ab06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Tue, 30 Dec 2025 15:19:43 +0300 Subject: [PATCH 05/30] Improve entity history handling for shared types and JSON Enhanced EntityHistoryHelper to correctly handle entities with shared CLR types and properties mapped to JSON. Added logic to determine property type from entry values when the type is object, and improved property change tracking for JSON-mapped navigation properties. --- .../EntityHistory/EntityHistoryHelper.cs | 55 ++++++++++++++++--- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs index f87a05e4a9..c23eee6450 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; @@ -108,12 +109,17 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency } var entityType = entity.GetType(); + var entityFullName = entityType.FullName!; + if (entityEntry.Metadata.HasSharedClrType) + { + entityFullName = entityEntry.Metadata.Name; + } var entityChange = new EntityChangeInfo { ChangeType = changeType, EntityEntry = entityEntry, EntityId = entityId, - EntityTypeFullName = entityType.FullName, + EntityTypeFullName = entityFullName, PropertyChanges = GetPropertyChanges(entityEntry), EntityTenantId = GetTenantId(entity) }; @@ -184,12 +190,14 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency var propertyEntry = entityEntry.Property(property.Name); if (ShouldSavePropertyHistory(propertyEntry, isCreated || isDeleted) && !IsSoftDeleted(entityEntry)) { + var propertyType = DeterminePropertyTypeFromEntry(property, propertyEntry); + propertyChanges.Add(new EntityPropertyChangeInfo { NewValue = isDeleted ? null : JsonSerializer.Serialize(propertyEntry.CurrentValue!).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength), OriginalValue = isCreated ? null : JsonSerializer.Serialize(propertyEntry.OriginalValue!).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength), PropertyName = property.Name, - PropertyTypeFullName = property.ClrType.GetFirstGenericArgumentIfNullable().FullName! + PropertyTypeFullName = propertyType.FullName! }); } } @@ -208,14 +216,22 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency if (AbpEfCoreNavigationHelper.IsNavigationEntryModified(entityEntry, index)) { var abpNavigationEntry = AbpEfCoreNavigationHelper.GetNavigationEntry(entityEntry, index); - var isCollection = navigationEntry.Metadata.IsCollection; - propertyChanges.Add(new EntityPropertyChangeInfo + if (navigationEntry.Metadata.TargetEntityType.IsMappedToJson() && navigationEntry is ReferenceEntry referenceEntry && referenceEntry.TargetEntry != null) + { + var jsonPropertyChanges = GetPropertyChanges(referenceEntry.TargetEntry); + propertyChanges.AddRange(jsonPropertyChanges); + } + else { - PropertyName = navigationEntry.Metadata.Name, - PropertyTypeFullName = navigationEntry.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName!, - OriginalValue = GetNavigationPropertyValue(abpNavigationEntry?.OriginalValue, isCollection), - NewValue = GetNavigationPropertyValue(abpNavigationEntry?.CurrentValue, isCollection) - }); + var isCollection = navigationEntry.Metadata.IsCollection; + propertyChanges.Add(new EntityPropertyChangeInfo + { + PropertyName = navigationEntry.Metadata.Name, + PropertyTypeFullName = navigationEntry.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName!, + OriginalValue = GetNavigationPropertyValue(abpNavigationEntry?.OriginalValue, isCollection), + NewValue = GetNavigationPropertyValue(abpNavigationEntry?.CurrentValue, isCollection) + }); + } } } } @@ -223,6 +239,27 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency return propertyChanges; } + protected virtual Type DeterminePropertyTypeFromEntry(IProperty property, PropertyEntry propertyEntry) + { + var propertyType = property.ClrType.GetFirstGenericArgumentIfNullable(); + + if (propertyType != typeof(object)) + { + return propertyType; + } + + if (propertyEntry.CurrentValue != null) + { + propertyType = propertyEntry.CurrentValue.GetType().GetFirstGenericArgumentIfNullable(); + } + else if (propertyEntry.OriginalValue != null) + { + propertyType = propertyEntry.OriginalValue.GetType().GetFirstGenericArgumentIfNullable(); + } + + return propertyType; + } + protected virtual string? GetNavigationPropertyValue(object? entity, bool isCollection) { switch (entity) From bdd0901dbad4618476f13642117a277c31317788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Tue, 30 Dec 2025 15:41:38 +0300 Subject: [PATCH 06/30] Update framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../EntityHistory/EntityHistoryHelper.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs index c23eee6450..7554c1d281 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs @@ -239,6 +239,18 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency return propertyChanges; } + /// + /// Determines the CLR type of a property based on its EF Core metadata and the values in the given . + /// + /// The EF Core property metadata that provides the declared CLR type. + /// The property entry that contains the current and original values for the property. + /// + /// The most specific CLR type inferred for the property. This is normally the property's declared CLR type (with + /// nullable wrappers removed). If the declared type is , the type is inferred from the + /// runtime type of or, if that is null, from + /// . If both values are null, the declared CLR type + /// (which may remain ) is returned. + /// protected virtual Type DeterminePropertyTypeFromEntry(IProperty property, PropertyEntry propertyEntry) { var propertyType = property.ClrType.GetFirstGenericArgumentIfNullable(); From d041aeb1fa2fc1b5dd231d32ab7d5ecd0c0e77d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Tue, 30 Dec 2025 15:58:32 +0300 Subject: [PATCH 07/30] Improve JSON navigation property change tracking Refactored EntityHistoryHelper to better handle property changes for navigation properties mapped to JSON. Now, nested property changes are prefixed with the navigation property name, and duplicate property names are handled to avoid conflicts. --- .../EntityHistory/EntityHistoryHelper.cs | 60 +++++++++++-------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs index 7554c1d281..66e632dc8c 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs @@ -202,37 +202,47 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency } } - if (AbpEfCoreNavigationHelper != null) + if (AbpEfCoreNavigationHelper == null) { - foreach (var (navigationEntry, index) in entityEntry.Navigations.Select((value, i) => ( value, i ))) - { - var propertyInfo = navigationEntry.Metadata.PropertyInfo; - if (propertyInfo != null && - propertyInfo.IsDefined(typeof(DisableAuditingAttribute), true)) - { - continue; - } + return propertyChanges; + } - if (AbpEfCoreNavigationHelper.IsNavigationEntryModified(entityEntry, index)) + foreach (var (navigationEntry, index) in entityEntry.Navigations.Select((value, i) => ( value, i ))) + { + var propertyInfo = navigationEntry.Metadata.PropertyInfo; + if (propertyInfo != null && + propertyInfo.IsDefined(typeof(DisableAuditingAttribute), true)) + { + continue; + } + + if (navigationEntry.Metadata.TargetEntityType.IsMappedToJson() && navigationEntry is ReferenceEntry referenceEntry && referenceEntry.TargetEntry != null) + { + foreach (var propertyChange in GetPropertyChanges(referenceEntry.TargetEntry)) { - var abpNavigationEntry = AbpEfCoreNavigationHelper.GetNavigationEntry(entityEntry, index); - if (navigationEntry.Metadata.TargetEntityType.IsMappedToJson() && navigationEntry is ReferenceEntry referenceEntry && referenceEntry.TargetEntry != null) - { - var jsonPropertyChanges = GetPropertyChanges(referenceEntry.TargetEntry); - propertyChanges.AddRange(jsonPropertyChanges); - } - else + if (propertyChanges.Any(pc => pc.PropertyName == propertyChange.PropertyName)) { - var isCollection = navigationEntry.Metadata.IsCollection; - propertyChanges.Add(new EntityPropertyChangeInfo - { - PropertyName = navigationEntry.Metadata.Name, - PropertyTypeFullName = navigationEntry.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName!, - OriginalValue = GetNavigationPropertyValue(abpNavigationEntry?.OriginalValue, isCollection), - NewValue = GetNavigationPropertyValue(abpNavigationEntry?.CurrentValue, isCollection) - }); + propertyChange.PropertyName = $"{referenceEntry.Metadata.Name}.{propertyChange.PropertyName}"; } + + propertyChanges.Add(propertyChange); } + + continue; + } + + if (AbpEfCoreNavigationHelper.IsNavigationEntryModified(entityEntry, index)) + { + var abpNavigationEntry = AbpEfCoreNavigationHelper.GetNavigationEntry(entityEntry, index); + + var isCollection = navigationEntry.Metadata.IsCollection; + propertyChanges.Add(new EntityPropertyChangeInfo + { + PropertyName = navigationEntry.Metadata.Name, + PropertyTypeFullName = navigationEntry.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName!, + OriginalValue = GetNavigationPropertyValue(abpNavigationEntry?.OriginalValue, isCollection), + NewValue = GetNavigationPropertyValue(abpNavigationEntry?.CurrentValue, isCollection) + }); } } From fb5dbaee99ccd1b7a81c7952559e70102ee9cc26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Tue, 30 Dec 2025 19:02:53 +0300 Subject: [PATCH 08/30] Fix entity type name for shared CLR types Update EntityHistoryHelper to set the entity type name only for shared CLR types that are not owned entities. This prevents incorrect entity name assignment for owned types. --- .../EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs index 66e632dc8c..2fcdb35480 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs @@ -110,7 +110,7 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency var entityType = entity.GetType(); var entityFullName = entityType.FullName!; - if (entityEntry.Metadata.HasSharedClrType) + if (entityEntry.Metadata.HasSharedClrType && !entityEntry.Metadata.IsOwned()) { entityFullName = entityEntry.Metadata.Name; } From 4d00ee9365dd8fd09716caaf86c6371c660ce61b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Wed, 31 Dec 2025 11:40:14 +0300 Subject: [PATCH 09/30] Add support and tests for entity history with JSON properties Introduces AppEntityWithJsonProperty and related DbSet to test contexts, configures model to handle owned JSON properties, and adds tests to verify entity history tracking for nested JSON property changes and shared entities. Refactors EntityHistoryHelper to improve navigation property change handling. --- .../EntityHistory/EntityHistoryHelper.cs | 10 +- .../AbpEntityFrameworkCoreTestModule.cs | 7 + .../Auditing/EntityHistoryHelper_Tests.cs | 130 ++++++++++++++++++ .../TestMigrationsDbContext.cs | 23 ++++ .../EntityFrameworkCore/TestAppDbContext.cs | 25 +++- .../Domain/AppEntityWithJsonProperty.cs | 28 ++++ 6 files changed, 217 insertions(+), 6 deletions(-) create mode 100644 framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Auditing/EntityHistoryHelper_Tests.cs create mode 100644 framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithJsonProperty.cs diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs index 2fcdb35480..e659689da1 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs @@ -202,11 +202,6 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency } } - if (AbpEfCoreNavigationHelper == null) - { - return propertyChanges; - } - foreach (var (navigationEntry, index) in entityEntry.Navigations.Select((value, i) => ( value, i ))) { var propertyInfo = navigationEntry.Metadata.PropertyInfo; @@ -230,6 +225,11 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency continue; } + + if (AbpEfCoreNavigationHelper == null) + { + return propertyChanges; + } if (AbpEfCoreNavigationHelper.IsNavigationEntryModified(entityEntry, index)) { diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs index a7b5c1e602..e4183a5a19 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Auditing; using Volo.Abp.Autofac; using Volo.Abp.Domain.Repositories; using Volo.Abp.EntityFrameworkCore.Domain; @@ -86,6 +87,12 @@ public class AbpEntityFrameworkCoreTestModule : AbpModule abpDbContextConfigurationContext.DbContextOptions.UseSqlite(sqliteConnection).AddAbpDbContextOptionsExtension(); }); }); + + Configure(options => + { + options.EntityHistorySelectors.Add(new NamedTypeSelector(nameof(AppEntityWithJsonProperty), type => type == typeof(AppEntityWithJsonProperty))); + options.EntityHistorySelectors.Add(new NamedTypeSelector(nameof(TestSharedEntity), type => type == typeof(TestSharedEntity))); + }); } public override void OnPreApplicationInitialization(ApplicationInitializationContext context) diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Auditing/EntityHistoryHelper_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Auditing/EntityHistoryHelper_Tests.cs new file mode 100644 index 0000000000..65873dd514 --- /dev/null +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Auditing/EntityHistoryHelper_Tests.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Auditing; +using Volo.Abp.Data; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.EntityFrameworkCore.EntityHistory; +using Volo.Abp.TestApp.Domain; +using Volo.Abp.TestApp.EntityFrameworkCore; +using Volo.Abp.Uow; +using Xunit; + +namespace Volo.Abp.EntityFrameworkCore.Auditing; + +public class EntityHistoryHelper_Tests : EntityFrameworkCoreTestBase +{ + private readonly IEntityHistoryHelper _entityHistoryHelper; + private readonly IRepository _appEntityWithJsonRepository; + private readonly IRepository _testSharedEntityRepository; + private readonly IUnitOfWorkManager _unitOfWorkManager; + + public EntityHistoryHelper_Tests() + { + _entityHistoryHelper = GetRequiredService(); + _appEntityWithJsonRepository = GetRequiredService>(); + _testSharedEntityRepository = GetRequiredService>(); + _unitOfWorkManager = GetRequiredService(); + } + + [Fact] + public async Task CreateChangeList_Should_Track_Nested_Json_Property_Changes_As_Separate_Property_Changes() + { + // Arrange & Act + EntityChangeInfo entityChange = null; + + await WithUnitOfWorkAsync(async () => + { + var entity = new AppEntityWithJsonProperty(Guid.NewGuid(), "Test Entity") + { + Data = new JsonPropertyObject() + { + { "Name", "String Name" }, + { "Value", "String Value"} + }, + Count = 10 + }; + + await _appEntityWithJsonRepository.InsertAsync(entity); + + var dbContext = await GetDbContextAsync(); + + var entries = dbContext.ChangeTracker.Entries().ToList(); + var entityChanges = _entityHistoryHelper.CreateChangeList(entries); + + entityChange = entityChanges.FirstOrDefault(x => x.EntityTypeFullName.Contains(nameof(AppEntityWithJsonProperty))); + }); + + // Assert + entityChange.ShouldNotBeNull(); + var dataPropertyChange = entityChange.PropertyChanges.FirstOrDefault(x => x.PropertyName == nameof(AppEntityWithJsonProperty.Data)); + dataPropertyChange.ShouldBeNull(); + var jsonNamePropertyChange = entityChange.PropertyChanges.FirstOrDefault(x => x.PropertyName == nameof(AppEntityWithJsonProperty.Data) + "." + "Name"); + jsonNamePropertyChange.ShouldNotBeNull(); + jsonNamePropertyChange.PropertyTypeFullName.ShouldBe(typeof(string).FullName); + jsonNamePropertyChange.NewValue.ShouldBe("\"String Name\""); + + var jsonValuePropertyChange = entityChange.PropertyChanges.FirstOrDefault(x => x.PropertyName == "Value"); + jsonValuePropertyChange.ShouldNotBeNull(); + jsonValuePropertyChange.PropertyTypeFullName.ShouldBe(typeof(string).FullName); + jsonValuePropertyChange.NewValue.ShouldBe("\"String Value\""); + } + + [Fact] + public async Task CreateChangeList_Should_Track_Shared_Entities_With_Their_Respective_Entity_Names() + { + // Arrange & Act + List entityChanges = null; + + await WithUnitOfWorkAsync(async () => + { + var entity = new TestSharedEntity(Guid.NewGuid()) + { + TenantId = null, + IsDeleted = false, + Name = "Test Person1", + Age = 10, + Birthday = DateTime.Now + }.SetProperty("testProperty", "Test Value1"); + + _testSharedEntityRepository.SetEntityName("TestSharedEntity1"); + await _testSharedEntityRepository.InsertAsync(entity); + + var entity2 = new TestSharedEntity(Guid.NewGuid()) + { + TenantId = null, + IsDeleted = false, + Name = "Test Person2", + Age = 20, + Birthday = DateTime.Now + }.SetProperty("testProperty", "Test Value2"); + + _testSharedEntityRepository.SetEntityName("TestSharedEntity2"); + await _testSharedEntityRepository.InsertAsync(entity2); + + var dbContext = await GetDbContextAsync(); + + var entries = dbContext.ChangeTracker.Entries().ToList(); + entityChanges = _entityHistoryHelper.CreateChangeList(entries); + }); + + entityChanges.ShouldContain(x => x.EntityTypeFullName == "TestSharedEntity1"); + entityChanges.ShouldContain(x => x.EntityTypeFullName == "TestSharedEntity2"); + } + + private async Task GetDbContextAsync() + { + var uow = _unitOfWorkManager.Current; + if (uow == null) + { + throw new InvalidOperationException("No active unit of work found"); + } + + var dbContextProvider = uow.ServiceProvider.GetRequiredService>(); + return await dbContextProvider.GetDbContextAsync(); + } +} + diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs index 4c6efa5a02..6e102706fc 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs @@ -37,6 +37,8 @@ public class TestMigrationsDbContext : AbpDbContext public DbSet TestSharedEntity => Set("TestSharedEntity1"); public DbSet TestSharedEntity2 => Set("TestSharedEntity2"); + + public DbSet EntitiesWithObjectProperty { get; set; } public TestMigrationsDbContext(DbContextOptions options) : base(options) @@ -140,5 +142,26 @@ public class TestMigrationsDbContext : AbpDbContext { b.ConfigureByConvention(); }); + + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + b.OwnsOne(x => x.Data, b2 => + { + b2.ToJson(); + + b2.Property("Name") + .HasConversion( + v => v.ToString(), + v => v + ); + + b2.Property("Value") + .HasConversion( + v => v.ToString(), + v => v + ); + }); + }); } } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs index a8e68dfca8..b0b0a55c03 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs @@ -40,9 +40,11 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, public DbSet Blogs { get; set; } public DbSet BlogPosts { get; set; } - + public DbSet TestSharedEntity => Set("TestSharedEntity1"); public DbSet TestSharedEntity2 => Set("TestSharedEntity2"); + + public DbSet EntitiesWithObjectProperty { get; set; } public TestAppDbContext(DbContextOptions options) : base(options) @@ -166,6 +168,27 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, b.ConfigureByConvention(); }); + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + b.OwnsOne(x => x.Data, b2 => + { + b2.ToJson(); + + b2.Property("Name") + .HasConversion( + v => v.ToString(), + v => v + ); + + b2.Property("Value") + .HasConversion( + v => v.ToString(), + v => v + ); + }); + }); + modelBuilder.TryConfigureObjectExtensions(); } } diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithJsonProperty.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithJsonProperty.cs new file mode 100644 index 0000000000..ab830e3e85 --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithJsonProperty.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Domain.Entities.Auditing; + +namespace Volo.Abp.TestApp.Domain; + +public class AppEntityWithJsonProperty : FullAuditedAggregateRoot +{ + public string Name { get; set; } + + public JsonPropertyObject Data { get; set; } + + public int Count { get; set; } + + public AppEntityWithJsonProperty() + { + } + + public AppEntityWithJsonProperty(Guid id, string name) : base(id) + { + Name = name; + } +} + +public class JsonPropertyObject : Dictionary +{ +} + From 96a7303fc6a73eea9eb81f1e01040c68ad5c628d Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 31 Dec 2025 18:17:36 +0800 Subject: [PATCH 10/30] Revert "Add support and tests for entity history with JSON properties" This reverts commit 4d00ee9365dd8fd09716caaf86c6371c660ce61b. --- .../EntityHistory/EntityHistoryHelper.cs | 10 +- .../AbpEntityFrameworkCoreTestModule.cs | 7 - .../Auditing/EntityHistoryHelper_Tests.cs | 130 ------------------ .../TestMigrationsDbContext.cs | 23 ---- .../EntityFrameworkCore/TestAppDbContext.cs | 25 +--- .../Domain/AppEntityWithJsonProperty.cs | 28 ---- 6 files changed, 6 insertions(+), 217 deletions(-) delete mode 100644 framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Auditing/EntityHistoryHelper_Tests.cs delete mode 100644 framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithJsonProperty.cs diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs index e659689da1..2fcdb35480 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs @@ -202,6 +202,11 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency } } + if (AbpEfCoreNavigationHelper == null) + { + return propertyChanges; + } + foreach (var (navigationEntry, index) in entityEntry.Navigations.Select((value, i) => ( value, i ))) { var propertyInfo = navigationEntry.Metadata.PropertyInfo; @@ -225,11 +230,6 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency continue; } - - if (AbpEfCoreNavigationHelper == null) - { - return propertyChanges; - } if (AbpEfCoreNavigationHelper.IsNavigationEntryModified(entityEntry, index)) { diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs index e4183a5a19..a7b5c1e602 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs @@ -5,7 +5,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Auditing; using Volo.Abp.Autofac; using Volo.Abp.Domain.Repositories; using Volo.Abp.EntityFrameworkCore.Domain; @@ -87,12 +86,6 @@ public class AbpEntityFrameworkCoreTestModule : AbpModule abpDbContextConfigurationContext.DbContextOptions.UseSqlite(sqliteConnection).AddAbpDbContextOptionsExtension(); }); }); - - Configure(options => - { - options.EntityHistorySelectors.Add(new NamedTypeSelector(nameof(AppEntityWithJsonProperty), type => type == typeof(AppEntityWithJsonProperty))); - options.EntityHistorySelectors.Add(new NamedTypeSelector(nameof(TestSharedEntity), type => type == typeof(TestSharedEntity))); - }); } public override void OnPreApplicationInitialization(ApplicationInitializationContext context) diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Auditing/EntityHistoryHelper_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Auditing/EntityHistoryHelper_Tests.cs deleted file mode 100644 index 65873dd514..0000000000 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Auditing/EntityHistoryHelper_Tests.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Shouldly; -using Volo.Abp.Auditing; -using Volo.Abp.Data; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.EntityFrameworkCore.EntityHistory; -using Volo.Abp.TestApp.Domain; -using Volo.Abp.TestApp.EntityFrameworkCore; -using Volo.Abp.Uow; -using Xunit; - -namespace Volo.Abp.EntityFrameworkCore.Auditing; - -public class EntityHistoryHelper_Tests : EntityFrameworkCoreTestBase -{ - private readonly IEntityHistoryHelper _entityHistoryHelper; - private readonly IRepository _appEntityWithJsonRepository; - private readonly IRepository _testSharedEntityRepository; - private readonly IUnitOfWorkManager _unitOfWorkManager; - - public EntityHistoryHelper_Tests() - { - _entityHistoryHelper = GetRequiredService(); - _appEntityWithJsonRepository = GetRequiredService>(); - _testSharedEntityRepository = GetRequiredService>(); - _unitOfWorkManager = GetRequiredService(); - } - - [Fact] - public async Task CreateChangeList_Should_Track_Nested_Json_Property_Changes_As_Separate_Property_Changes() - { - // Arrange & Act - EntityChangeInfo entityChange = null; - - await WithUnitOfWorkAsync(async () => - { - var entity = new AppEntityWithJsonProperty(Guid.NewGuid(), "Test Entity") - { - Data = new JsonPropertyObject() - { - { "Name", "String Name" }, - { "Value", "String Value"} - }, - Count = 10 - }; - - await _appEntityWithJsonRepository.InsertAsync(entity); - - var dbContext = await GetDbContextAsync(); - - var entries = dbContext.ChangeTracker.Entries().ToList(); - var entityChanges = _entityHistoryHelper.CreateChangeList(entries); - - entityChange = entityChanges.FirstOrDefault(x => x.EntityTypeFullName.Contains(nameof(AppEntityWithJsonProperty))); - }); - - // Assert - entityChange.ShouldNotBeNull(); - var dataPropertyChange = entityChange.PropertyChanges.FirstOrDefault(x => x.PropertyName == nameof(AppEntityWithJsonProperty.Data)); - dataPropertyChange.ShouldBeNull(); - var jsonNamePropertyChange = entityChange.PropertyChanges.FirstOrDefault(x => x.PropertyName == nameof(AppEntityWithJsonProperty.Data) + "." + "Name"); - jsonNamePropertyChange.ShouldNotBeNull(); - jsonNamePropertyChange.PropertyTypeFullName.ShouldBe(typeof(string).FullName); - jsonNamePropertyChange.NewValue.ShouldBe("\"String Name\""); - - var jsonValuePropertyChange = entityChange.PropertyChanges.FirstOrDefault(x => x.PropertyName == "Value"); - jsonValuePropertyChange.ShouldNotBeNull(); - jsonValuePropertyChange.PropertyTypeFullName.ShouldBe(typeof(string).FullName); - jsonValuePropertyChange.NewValue.ShouldBe("\"String Value\""); - } - - [Fact] - public async Task CreateChangeList_Should_Track_Shared_Entities_With_Their_Respective_Entity_Names() - { - // Arrange & Act - List entityChanges = null; - - await WithUnitOfWorkAsync(async () => - { - var entity = new TestSharedEntity(Guid.NewGuid()) - { - TenantId = null, - IsDeleted = false, - Name = "Test Person1", - Age = 10, - Birthday = DateTime.Now - }.SetProperty("testProperty", "Test Value1"); - - _testSharedEntityRepository.SetEntityName("TestSharedEntity1"); - await _testSharedEntityRepository.InsertAsync(entity); - - var entity2 = new TestSharedEntity(Guid.NewGuid()) - { - TenantId = null, - IsDeleted = false, - Name = "Test Person2", - Age = 20, - Birthday = DateTime.Now - }.SetProperty("testProperty", "Test Value2"); - - _testSharedEntityRepository.SetEntityName("TestSharedEntity2"); - await _testSharedEntityRepository.InsertAsync(entity2); - - var dbContext = await GetDbContextAsync(); - - var entries = dbContext.ChangeTracker.Entries().ToList(); - entityChanges = _entityHistoryHelper.CreateChangeList(entries); - }); - - entityChanges.ShouldContain(x => x.EntityTypeFullName == "TestSharedEntity1"); - entityChanges.ShouldContain(x => x.EntityTypeFullName == "TestSharedEntity2"); - } - - private async Task GetDbContextAsync() - { - var uow = _unitOfWorkManager.Current; - if (uow == null) - { - throw new InvalidOperationException("No active unit of work found"); - } - - var dbContextProvider = uow.ServiceProvider.GetRequiredService>(); - return await dbContextProvider.GetDbContextAsync(); - } -} - diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs index 6e102706fc..4c6efa5a02 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs @@ -37,8 +37,6 @@ public class TestMigrationsDbContext : AbpDbContext public DbSet TestSharedEntity => Set("TestSharedEntity1"); public DbSet TestSharedEntity2 => Set("TestSharedEntity2"); - - public DbSet EntitiesWithObjectProperty { get; set; } public TestMigrationsDbContext(DbContextOptions options) : base(options) @@ -142,26 +140,5 @@ public class TestMigrationsDbContext : AbpDbContext { b.ConfigureByConvention(); }); - - modelBuilder.Entity(b => - { - b.ConfigureByConvention(); - b.OwnsOne(x => x.Data, b2 => - { - b2.ToJson(); - - b2.Property("Name") - .HasConversion( - v => v.ToString(), - v => v - ); - - b2.Property("Value") - .HasConversion( - v => v.ToString(), - v => v - ); - }); - }); } } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs index b0b0a55c03..a8e68dfca8 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs @@ -40,11 +40,9 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, public DbSet Blogs { get; set; } public DbSet BlogPosts { get; set; } - + public DbSet TestSharedEntity => Set("TestSharedEntity1"); public DbSet TestSharedEntity2 => Set("TestSharedEntity2"); - - public DbSet EntitiesWithObjectProperty { get; set; } public TestAppDbContext(DbContextOptions options) : base(options) @@ -168,27 +166,6 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, b.ConfigureByConvention(); }); - modelBuilder.Entity(b => - { - b.ConfigureByConvention(); - b.OwnsOne(x => x.Data, b2 => - { - b2.ToJson(); - - b2.Property("Name") - .HasConversion( - v => v.ToString(), - v => v - ); - - b2.Property("Value") - .HasConversion( - v => v.ToString(), - v => v - ); - }); - }); - modelBuilder.TryConfigureObjectExtensions(); } } diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithJsonProperty.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithJsonProperty.cs deleted file mode 100644 index ab830e3e85..0000000000 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithJsonProperty.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using Volo.Abp.Domain.Entities.Auditing; - -namespace Volo.Abp.TestApp.Domain; - -public class AppEntityWithJsonProperty : FullAuditedAggregateRoot -{ - public string Name { get; set; } - - public JsonPropertyObject Data { get; set; } - - public int Count { get; set; } - - public AppEntityWithJsonProperty() - { - } - - public AppEntityWithJsonProperty(Guid id, string name) : base(id) - { - Name = name; - } -} - -public class JsonPropertyObject : Dictionary -{ -} - From 6f57ec45680bef671f13d0416c789f8b8def6f65 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 31 Dec 2025 19:11:25 +0800 Subject: [PATCH 11/30] Add audit support for JSON property changes. --- .../EntityHistory/EntityHistoryHelper.cs | 17 ++-- .../Abp/Auditing/AbpAuditingTestModule.cs | 2 + .../App/Entities/AppEntityWithJsonProperty.cs | 27 +++++ .../AbpAuditingTestDbContext.cs | 21 ++++ .../Volo/Abp/Auditing/Auditing_Tests.cs | 98 +++++++++++++++++++ 5 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithJsonProperty.cs diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs index 2fcdb35480..75e82976b4 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs @@ -187,6 +187,11 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency foreach (var property in properties) { + if (entityEntry.Metadata.IsMappedToJson() && property.GetJsonPropertyName() == null) + { + continue; + } + var propertyEntry = entityEntry.Property(property.Name); if (ShouldSavePropertyHistory(propertyEntry, isCreated || isDeleted) && !IsSoftDeleted(entityEntry)) { @@ -215,26 +220,22 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency { continue; } - + if (navigationEntry.Metadata.TargetEntityType.IsMappedToJson() && navigationEntry is ReferenceEntry referenceEntry && referenceEntry.TargetEntry != null) { foreach (var propertyChange in GetPropertyChanges(referenceEntry.TargetEntry)) { - if (propertyChanges.Any(pc => pc.PropertyName == propertyChange.PropertyName)) - { - propertyChange.PropertyName = $"{referenceEntry.Metadata.Name}.{propertyChange.PropertyName}"; - } - + propertyChange.PropertyName = $"{referenceEntry.Metadata.Name}.{propertyChange.PropertyName}"; propertyChanges.Add(propertyChange); } - + continue; } if (AbpEfCoreNavigationHelper.IsNavigationEntryModified(entityEntry, index)) { var abpNavigationEntry = AbpEfCoreNavigationHelper.GetNavigationEntry(entityEntry, index); - + var isCollection = navigationEntry.Metadata.IsCollection; propertyChanges.Add(new EntityPropertyChangeInfo { diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs index 4e14dbc262..800b73544d 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs @@ -59,6 +59,8 @@ public class AbpAuditingTestModule : AbpModule "AppEntityWithValueObject", type => type == typeof(AppEntityWithValueObject) || type == typeof(AppEntityWithValueObjectAddress)) ); + + options.EntityHistorySelectors.Add(new NamedTypeSelector(nameof(AppEntityWithJsonProperty), type => type == typeof(AppEntityWithJsonProperty))); }); context.Services.AddType(); diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithJsonProperty.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithJsonProperty.cs new file mode 100644 index 0000000000..de07544629 --- /dev/null +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithJsonProperty.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Domain.Entities.Auditing; + +namespace Volo.Abp.Auditing.App.Entities; + +public class AppEntityWithJsonProperty : FullAuditedAggregateRoot +{ + public string Name { get; set; } + + public JsonPropertyObject Data { get; set; } + + public int Count { get; set; } + + public AppEntityWithJsonProperty() + { + } + + public AppEntityWithJsonProperty(Guid id, string name) : base(id) + { + Name = name; + } +} + +public class JsonPropertyObject : Dictionary +{ +} diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs index 91698ea5ec..e8950880d7 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs @@ -30,6 +30,7 @@ public class AbpAuditingTestDbContext : AbpDbContext public DbSet AppEntityWithNavigations { get; set; } public DbSet AppEntityWithNavigationChildOneToMany { get; set; } public DbSet AppEntityWithNavigationsAndDisableAuditing { get; set; } + public DbSet EntitiesWithObjectProperty { get; set; } public AbpAuditingTestDbContext(DbContextOptions options) : base(options) @@ -56,5 +57,25 @@ public class AbpAuditingTestDbContext : AbpDbContext b.HasMany(x => x.ManyToMany).WithMany(x => x.ManyToMany).UsingEntity(); }); + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + b.OwnsOne(x => x.Data, b2 => + { + b2.ToJson(); + + b2.Property("Name") + .HasConversion( + v => v.ToString(), + v => v + ); + + b2.Property("Value") + .HasConversion( + v => v.ToString(), + v => v + ); + }); + }); } } diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs index a88cf70292..637b9b4d97 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs @@ -720,6 +720,104 @@ public class Auditing_Tests : AbpAuditingTestBase x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigationChildManyToMany.ManyToMany) && x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List).FullName)); +#pragma warning restore 4014 + } + + [Fact] + public async Task Should_Write_AuditLog_For_Json_Property_Changes() + { + var entityId = Guid.NewGuid(); + var repository = ServiceProvider.GetRequiredService>(); + + using (var scope = _auditingManager.BeginScope()) + { + using (var uow = _unitOfWorkManager.Begin()) + { + var entity = new AppEntityWithJsonProperty(entityId, "Test Entity") + { + Data = new JsonPropertyObject() + { + { "Name", "String Name" }, + { "Value", "String Value"} + }, + Count = 10 + }; + + await repository.InsertAsync(entity); + + await uow.CompleteAsync(); + await scope.SaveAsync(); + } + } + +#pragma warning disable 4014 + AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 1 && + x.EntityChanges[0].ChangeType == EntityChangeType.Created && + x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithJsonProperty).FullName && + x.EntityChanges[0].PropertyChanges.Count == 4 && + + x.EntityChanges[0].PropertyChanges[0].OriginalValue == null && + x.EntityChanges[0].PropertyChanges[0].NewValue == "10" && + x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithJsonProperty.Count) && + x.EntityChanges[0].PropertyChanges[0].PropertyTypeFullName == typeof(int).FullName && + + x.EntityChanges[0].PropertyChanges[1].OriginalValue == null && + x.EntityChanges[0].PropertyChanges[1].NewValue == "\"Test Entity\"" && + x.EntityChanges[0].PropertyChanges[1].PropertyName == nameof(AppEntityWithJsonProperty.Name) && + x.EntityChanges[0].PropertyChanges[1].PropertyTypeFullName == typeof(string).FullName && + + x.EntityChanges[0].PropertyChanges[2].OriginalValue == null && + x.EntityChanges[0].PropertyChanges[2].NewValue == "\"String Name\"" && + x.EntityChanges[0].PropertyChanges[2].PropertyName == "Data.Name" && + x.EntityChanges[0].PropertyChanges[2].PropertyTypeFullName == typeof(string).FullName && + + x.EntityChanges[0].PropertyChanges[3].OriginalValue == null && + x.EntityChanges[0].PropertyChanges[3].NewValue == "\"String Value\"" && + x.EntityChanges[0].PropertyChanges[3].PropertyName == "Data.Value" && + x.EntityChanges[0].PropertyChanges[3].PropertyTypeFullName == typeof(string).FullName)); + AuditingStore.ClearReceivedCalls(); +#pragma warning restore 4014 + + + using (var scope = _auditingManager.BeginScope()) + { + using (var uow = _unitOfWorkManager.Begin()) + { + var entity = await repository.GetAsync(entityId); + + entity.Name = "Updated Test Entity"; + + entity.Data["Name"] = "Updated String Name"; + entity.Data["Value"] = "Updated String Value"; + + await repository.UpdateAsync(entity); + + await uow.CompleteAsync(); + await scope.SaveAsync(); + } + } + +#pragma warning disable 4014 + AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 1 && + x.EntityChanges[0].ChangeType == EntityChangeType.Updated && + x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithJsonProperty).FullName && + x.EntityChanges[0].PropertyChanges.Count == 3 && + + x.EntityChanges[0].PropertyChanges[0].OriginalValue == "\"Test Entity\"" && + x.EntityChanges[0].PropertyChanges[0].NewValue == "\"Updated Test Entity\"" && + x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithJsonProperty.Name) && + x.EntityChanges[0].PropertyChanges[0].PropertyTypeFullName == typeof(string).FullName && + + x.EntityChanges[0].PropertyChanges[1].OriginalValue == "\"String Name\"" && + x.EntityChanges[0].PropertyChanges[1].NewValue == "\"Updated String Name\"" && + x.EntityChanges[0].PropertyChanges[1].PropertyName == "Data.Name" && + x.EntityChanges[0].PropertyChanges[1].PropertyTypeFullName == typeof(string).FullName && + + x.EntityChanges[0].PropertyChanges[2].OriginalValue == "\"String Value\"" && + x.EntityChanges[0].PropertyChanges[2].NewValue == "\"Updated String Value\"" && + x.EntityChanges[0].PropertyChanges[2].PropertyName == "Data.Value" && + x.EntityChanges[0].PropertyChanges[2].PropertyTypeFullName == typeof(string).FullName)); + AuditingStore.ClearReceivedCalls(); #pragma warning restore 4014 } } From 5d84359a97fc779084a03022ddb10b605800c73b Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 2 Jan 2026 16:03:25 +0800 Subject: [PATCH 12/30] Refactor token validation in CookieAuthenticationOptionsExtensions Resolve #24468 --- .../CookieAuthenticationOptionsExtensions.cs | 76 +------------------ .../CookieAuthenticationOptionsExtensions.cs | 36 ++++++--- 2 files changed, 30 insertions(+), 82 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs index 0dd2c33cb8..2aead07ce7 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs @@ -1,86 +1,16 @@ using System; -using System.Threading.Tasks; -using Duende.IdentityModel.Client; -using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Volo.Abp.Threading; namespace Microsoft.AspNetCore.Authentication.Cookies; public static class CookieAuthenticationOptionsExtensions { /// - /// Introspect access token on validating the principal. + /// Check the access_token is expired or inactive. /// - /// - /// - /// + [Obsolete("Use CheckTokenExpiration method instead.")] public static CookieAuthenticationOptions IntrospectAccessToken(this CookieAuthenticationOptions options, string oidcAuthenticationScheme = "oidc") { - options.Events.OnValidatePrincipal = async principalContext => - { - if (principalContext.Principal == null || principalContext.Principal.Identity == null || !principalContext.Principal.Identity.IsAuthenticated) - { - return; - } - - var logger = principalContext.HttpContext.RequestServices.GetRequiredService>(); - - var accessToken = principalContext.Properties.GetTokenValue("access_token"); - if (!accessToken.IsNullOrWhiteSpace()) - { - var openIdConnectOptions = await GetOpenIdConnectOptions(principalContext, oidcAuthenticationScheme); - var response = await openIdConnectOptions.Backchannel.IntrospectTokenAsync(new TokenIntrospectionRequest - { - Address = openIdConnectOptions.Configuration?.IntrospectionEndpoint ?? openIdConnectOptions.Authority!.EnsureEndsWith('/') + "connect/introspect", - ClientId = openIdConnectOptions.ClientId!, - ClientSecret = openIdConnectOptions.ClientSecret, - Token = accessToken - }); - - if (response.IsError) - { - logger.LogError(response.Error); - await SignOutAsync(principalContext); - return; - } - - if (!response.IsActive) - { - logger.LogError("The access_token is not active."); - await SignOutAsync(principalContext); - return; - } - - logger.LogInformation("The access_token is active."); - } - else - { - logger.LogError("The access_token is not found in the cookie properties, Please make sure SaveTokens of OpenIdConnectOptions is set as true."); - await SignOutAsync(principalContext); - } - }; - - return options; - } - - private async static Task GetOpenIdConnectOptions(CookieValidatePrincipalContext principalContext, string oidcAuthenticationScheme) - { - var openIdConnectOptions = principalContext.HttpContext.RequestServices.GetRequiredService>().Get(oidcAuthenticationScheme); - if (openIdConnectOptions.Configuration == null && openIdConnectOptions.ConfigurationManager != null) - { - var cancellationTokenProvider = principalContext.HttpContext.RequestServices.GetRequiredService(); - openIdConnectOptions.Configuration = await openIdConnectOptions.ConfigurationManager.GetConfigurationAsync(cancellationTokenProvider.Token); - } - - return openIdConnectOptions; - } - - private async static Task SignOutAsync(CookieValidatePrincipalContext principalContext) - { - principalContext.RejectPrincipal(); - await principalContext.HttpContext.SignOutAsync(principalContext.Scheme.Name); + return options.CheckTokenExpiration(oidcAuthenticationScheme, null, TimeSpan.FromMinutes(1)); } } diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs index c37e75ef32..e7732ea45b 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs @@ -20,6 +20,7 @@ public static class CookieAuthenticationOptionsExtensions { advance ??= TimeSpan.FromMinutes(3); validationInterval ??= TimeSpan.FromMinutes(1); + var previousHandler = options.Events.OnValidatePrincipal; options.Events.OnValidatePrincipal = async principalContext => { if (principalContext.Principal == null || principalContext.Principal.Identity == null || !principalContext.Principal.Identity.IsAuthenticated) @@ -30,25 +31,37 @@ public static class CookieAuthenticationOptionsExtensions var logger = principalContext.HttpContext.RequestServices.GetRequiredService>(); var tokenExpiresAt = principalContext.Properties.GetString(".Token.expires_at"); - if (!tokenExpiresAt.IsNullOrWhiteSpace() && DateTimeOffset.TryParseExact(tokenExpiresAt, "o", null, DateTimeStyles.RoundtripKind, out var expiresAt) && - expiresAt < DateTimeOffset.UtcNow.Subtract(advance.Value)) + if (!tokenExpiresAt.IsNullOrWhiteSpace() && DateTimeOffset.TryParseExact(tokenExpiresAt, "o", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var expiresAt) && + expiresAt <= DateTimeOffset.UtcNow.Add(advance.Value)) { - logger.LogInformation("The access_token is expired."); + logger.LogInformation("The access_token expires within {AdvanceSeconds}s; signing out.", advance.Value.TotalSeconds); await SignOutAsync(principalContext); return; } if (principalContext.Properties.IssuedUtc != null && DateTimeOffset.UtcNow.Subtract(principalContext.Properties.IssuedUtc.Value) > validationInterval) { - logger.LogInformation($"Check the access_token is active every {validationInterval.Value.TotalSeconds} seconds."); + logger.LogInformation("Checking access_token activity every {Seconds} seconds.", validationInterval.Value.TotalSeconds); var accessToken = principalContext.Properties.GetTokenValue("access_token"); if (!accessToken.IsNullOrWhiteSpace()) { var openIdConnectOptions = await GetOpenIdConnectOptions(principalContext, oidcAuthenticationScheme); + var introspectionEndpoint = openIdConnectOptions.Configuration?.IntrospectionEndpoint; + if (introspectionEndpoint.IsNullOrWhiteSpace() && !openIdConnectOptions.Authority.IsNullOrWhiteSpace()) + { + introspectionEndpoint = openIdConnectOptions.Authority.EnsureEndsWith('/') + "connect/introspect"; + } + + if (introspectionEndpoint.IsNullOrWhiteSpace()) + { + logger.LogWarning("No introspection endpoint configured. Skipping token activity check."); + return; + } + var response = await openIdConnectOptions.Backchannel.IntrospectTokenAsync(new TokenIntrospectionRequest { - Address = openIdConnectOptions.Configuration?.IntrospectionEndpoint ?? openIdConnectOptions.Authority!.EnsureEndsWith('/') + "connect/introspect", + Address = introspectionEndpoint, ClientId = openIdConnectOptions.ClientId!, ClientSecret = openIdConnectOptions.ClientSecret, Token = accessToken @@ -56,7 +69,7 @@ public static class CookieAuthenticationOptionsExtensions if (response.IsError) { - logger.LogError(response.Error); + logger.LogError("Token introspection error: {Error}", response.Error); await SignOutAsync(principalContext); return; } @@ -73,16 +86,21 @@ public static class CookieAuthenticationOptionsExtensions } else { - logger.LogError("The access_token is not found in the cookie properties, Please make sure SaveTokens of OpenIdConnectOptions is set as true."); + logger.LogError("The access_token is not found in the cookie properties. Ensure SaveTokens of OpenIdConnectOptions is true."); await SignOutAsync(principalContext); } } + + if (previousHandler != null) + { + await previousHandler(principalContext); + } }; return options; } - private async static Task GetOpenIdConnectOptions(CookieValidatePrincipalContext principalContext, string oidcAuthenticationScheme) + private static async Task GetOpenIdConnectOptions(CookieValidatePrincipalContext principalContext, string oidcAuthenticationScheme) { var openIdConnectOptions = principalContext.HttpContext.RequestServices.GetRequiredService>().Get(oidcAuthenticationScheme); var cancellationTokenProvider = principalContext.HttpContext.RequestServices.GetRequiredService(); @@ -94,7 +112,7 @@ public static class CookieAuthenticationOptionsExtensions return openIdConnectOptions; } - private async static Task SignOutAsync(CookieValidatePrincipalContext principalContext) + private static async Task SignOutAsync(CookieValidatePrincipalContext principalContext) { principalContext.RejectPrincipal(); await principalContext.HttpContext.SignOutAsync(principalContext.Scheme.Name); From 71bc239f8830587e9e32297595c83848fc00d208 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 2 Jan 2026 16:19:56 +0800 Subject: [PATCH 13/30] Refactor token expiration handling in cookie auth options --- .../CookieAuthenticationOptionsExtensions.cs | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs index e7732ea45b..7d6955f08b 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs @@ -14,7 +14,7 @@ namespace Microsoft.Extensions.DependencyInjection; public static class CookieAuthenticationOptionsExtensions { /// - /// Check the access_token is expired or inactive. + /// Check if the access_token is expired or inactive. /// public static CookieAuthenticationOptions CheckTokenExpiration(this CookieAuthenticationOptions options, string oidcAuthenticationScheme = "oidc", TimeSpan? advance = null, TimeSpan? validationInterval = null) { @@ -25,6 +25,7 @@ public static class CookieAuthenticationOptionsExtensions { if (principalContext.Principal == null || principalContext.Principal.Identity == null || !principalContext.Principal.Identity.IsAuthenticated) { + await InvokePreviousHandlerAsync(principalContext, previousHandler); return; } @@ -35,7 +36,7 @@ public static class CookieAuthenticationOptionsExtensions expiresAt <= DateTimeOffset.UtcNow.Add(advance.Value)) { logger.LogInformation("The access_token expires within {AdvanceSeconds}s; signing out.", advance.Value.TotalSeconds); - await SignOutAsync(principalContext); + await SignOutAndInvokePreviousHandlerAsync(principalContext, previousHandler); return; } @@ -56,6 +57,7 @@ public static class CookieAuthenticationOptionsExtensions if (introspectionEndpoint.IsNullOrWhiteSpace()) { logger.LogWarning("No introspection endpoint configured. Skipping token activity check."); + await InvokePreviousHandlerAsync(principalContext, previousHandler); return; } @@ -70,14 +72,14 @@ public static class CookieAuthenticationOptionsExtensions if (response.IsError) { logger.LogError("Token introspection error: {Error}", response.Error); - await SignOutAsync(principalContext); + await SignOutAndInvokePreviousHandlerAsync(principalContext, previousHandler); return; } if (!response.IsActive) { logger.LogError("The access_token is not active."); - await SignOutAsync(principalContext); + await SignOutAndInvokePreviousHandlerAsync(principalContext, previousHandler); return; } @@ -91,10 +93,7 @@ public static class CookieAuthenticationOptionsExtensions } } - if (previousHandler != null) - { - await previousHandler(principalContext); - } + await InvokePreviousHandlerAsync(principalContext, previousHandler); }; return options; @@ -117,4 +116,15 @@ public static class CookieAuthenticationOptionsExtensions principalContext.RejectPrincipal(); await principalContext.HttpContext.SignOutAsync(principalContext.Scheme.Name); } + + private static Task InvokePreviousHandlerAsync(CookieValidatePrincipalContext principalContext, Func? previousHandler) + { + return previousHandler != null ? previousHandler(principalContext) : Task.CompletedTask; + } + + private static async Task SignOutAndInvokePreviousHandlerAsync(CookieValidatePrincipalContext principalContext, Func? previousHandler) + { + await SignOutAsync(principalContext); + await InvokePreviousHandlerAsync(principalContext, previousHandler); + } } From 8d40a1c935ebcd8db475b3eeaa62352e51086d38 Mon Sep 17 00:00:00 2001 From: irem1demirci Date: Fri, 2 Jan 2026 16:34:04 +0300 Subject: [PATCH 14/30] Update post.md --- docs/en/Community-Articles/2025-09-02-training-campaign/post.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Community-Articles/2025-09-02-training-campaign/post.md b/docs/en/Community-Articles/2025-09-02-training-campaign/post.md index 20f2bcf4bd..40314695aa 100644 --- a/docs/en/Community-Articles/2025-09-02-training-campaign/post.md +++ b/docs/en/Community-Articles/2025-09-02-training-campaign/post.md @@ -1,6 +1,6 @@ # IMPROVE YOUR ABP SKILLS WITH 33% OFF LIVE TRAININGS! -We have exciting news to share\! As you know, we offer live training packages to help you improve your skills and knowledge of ABP. From September 8th to 19th, we are giving you 33% OFF our live trainings, so you can learn more about the product at a discounted price\! +We have exciting news to share\! As you know, we offer live training packages to help you improve your skills and knowledge of ABP. For a limited time, we are giving you 33% OFF our live trainings, so you can learn more about the product at a discounted price\! #### Why Join ABP.IO Training? From 275b181fce9e908750eb9de7382726bf4ba264d6 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 3 Jan 2026 16:51:37 +0800 Subject: [PATCH 15/30] Enhance DateTime handling with timezone support in model binding and JSON converters --- .../ModelBinding/AbpDateTimeModelBinder.cs | 42 +++---- .../AbpDateTimeModelBinderProvider.cs | 5 +- .../Json/Newtonsoft/AbpDateTimeConverter.cs | 42 ++++--- .../JsonConverters/AbpDateTimeConverter.cs | 69 +++-------- .../AbpDateTimeConverterBase.cs | 110 ++++++++++++++++++ .../AbpNullableDateTimeConverter.cs | 67 +++-------- .../ModelBinding/ModelBindingController.cs | 40 +++++++ .../ModelBindingController_Tests.cs | 80 +++++++++++++ 8 files changed, 310 insertions(+), 145 deletions(-) create mode 100644 framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs index 35e1777f4c..c63c2c582f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs @@ -10,45 +10,41 @@ public class AbpDateTimeModelBinder : IModelBinder { private readonly DateTimeModelBinder _dateTimeModelBinder; private readonly IClock _clock; + private readonly ICurrentTimezoneProvider _currentTimezoneProvider; + private readonly ITimezoneProvider _timezoneProvider; - public AbpDateTimeModelBinder(IClock clock, DateTimeModelBinder dateTimeModelBinder) + public AbpDateTimeModelBinder(DateTimeModelBinder dateTimeModelBinder, IClock clock, ICurrentTimezoneProvider currentTimezoneProvider, ITimezoneProvider timezoneProvider) { - _clock = clock; _dateTimeModelBinder = dateTimeModelBinder; + _clock = clock; + _currentTimezoneProvider = currentTimezoneProvider; + _timezoneProvider = timezoneProvider; } public async Task BindModelAsync(ModelBindingContext bindingContext) { await _dateTimeModelBinder.BindModelAsync(bindingContext); - + if (!bindingContext.Result.IsModelSet || bindingContext.Result.Model is not DateTime dateTime) { return; } - - // If the DateTime has no timezone info (most cases from input) - if (dateTime.Kind == DateTimeKind.Unspecified) + + if (dateTime.Kind == DateTimeKind.Unspecified && + _clock.SupportsMultipleTimezone && + !_currentTimezoneProvider.TimeZone.IsNullOrWhiteSpace()) { - // Try to get user's timezone - var userTz = _currentTimezoneProvider.TimeZone; - if (!userTz.IsNullOrWhiteSpace()) + try { - try - { - var tzInfo = _timezoneProvider.GetTimeZoneInfo(userTz); - // Treat the input as user's local time and convert to UTC - var utc = TimeZoneInfo.ConvertTimeToUtc(dateTime, tzInfo); - bindingContext.Result = ModelBindingResult.Success(utc); - return; - } - catch - { - // fallback to default clock normalization if invalid TZ - } + var timezoneInfo = _timezoneProvider.GetTimeZoneInfo(_currentTimezoneProvider.TimeZone); + dateTime = new DateTimeOffset(dateTime, timezoneInfo.BaseUtcOffset).UtcDateTime; + } + catch + { + // ignored } } - - // fallback: original behavior + bindingContext.Result = ModelBindingResult.Success(_clock.Normalize(dateTime)); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinderProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinderProvider.cs index a4c3ee2288..3be76251c3 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinderProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinderProvider.cs @@ -50,6 +50,9 @@ public class AbpDateTimeModelBinderProvider : IModelBinderProvider { const DateTimeStyles supportedStyles = DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AdjustToUniversal; var dateTimeModelBinder = new DateTimeModelBinder(supportedStyles, context.Services.GetRequiredService()); - return new AbpDateTimeModelBinder(context.Services.GetRequiredService(), dateTimeModelBinder); + return new AbpDateTimeModelBinder(dateTimeModelBinder, + context.Services.GetRequiredService(), + context.Services.GetRequiredService(), + context.Services.GetRequiredService()); } } diff --git a/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs b/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs index c95660e484..257a90e84c 100644 --- a/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs +++ b/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs @@ -19,11 +19,15 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency private readonly CultureInfo _culture = CultureInfo.InvariantCulture; private readonly IClock _clock; private readonly AbpJsonOptions _options; + private readonly ICurrentTimezoneProvider _currentTimezoneProvider; + private readonly ITimezoneProvider _timezoneProvider; private bool _skipDateTimeNormalization; - public AbpDateTimeConverter(IClock clock, IOptions options) + public AbpDateTimeConverter(IClock clock, IOptions options, ICurrentTimezoneProvider currentTimezoneProvider, ITimezoneProvider timezoneProvider) { _clock = clock; + _currentTimezoneProvider = currentTimezoneProvider; + _timezoneProvider = timezoneProvider; _options = options.Value; } @@ -41,19 +45,14 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { var nullable = Nullable.GetUnderlyingType(objectType) != null; - if (reader.TokenType == JsonToken.Null) + switch (reader.TokenType) { - if (!nullable) - { + case JsonToken.Null when !nullable: throw new JsonSerializationException($"Cannot convert null value to {objectType.FullName}."); - } - - return null; - } - - if (reader.TokenType == JsonToken.Date) - { - return Normalize(reader.Value!.To()); + case JsonToken.Null: + return null; + case JsonToken.Date: + return Normalize(reader.Value!.To()); } if (reader.TokenType != JsonToken.String) @@ -108,7 +107,7 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency } } - static internal bool ShouldNormalize(MemberInfo member, JsonProperty property) + internal static bool ShouldNormalize(MemberInfo member, JsonProperty property) { if (property.PropertyType != typeof(DateTime) && property.PropertyType != typeof(DateTime?)) @@ -121,6 +120,23 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency protected virtual DateTime Normalize(DateTime dateTime) { + if (dateTime.Kind != DateTimeKind.Unspecified || + !_clock.SupportsMultipleTimezone || + _currentTimezoneProvider.TimeZone.IsNullOrWhiteSpace()) + { + return _skipDateTimeNormalization ? dateTime : _clock.Normalize(dateTime); + } + + try + { + var timezoneInfo = _timezoneProvider.GetTimeZoneInfo(_currentTimezoneProvider.TimeZone); + dateTime = new DateTimeOffset(dateTime, timezoneInfo.BaseUtcOffset).UtcDateTime; + } + catch + { + // ignored + } + return _skipDateTimeNormalization ? dateTime : _clock.Normalize(dateTime); diff --git a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs index 3feea8d299..d796c3183f 100644 --- a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs +++ b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs @@ -1,84 +1,43 @@ using System; -using System.Globalization; using System.Linq; using System.Text.Json; -using System.Text.Json.Serialization; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.Timing; namespace Volo.Abp.Json.SystemTextJson.JsonConverters; -public class AbpDateTimeConverter : JsonConverter, ITransientDependency +public class AbpDateTimeConverter : AbpDateTimeConverterBase, ITransientDependency { - private readonly IClock _clock; - private readonly AbpJsonOptions _options; - private bool _skipDateTimeNormalization; - - public AbpDateTimeConverter(IClock clock, IOptions abpJsonOptions) + public AbpDateTimeConverter( + IClock clock, + IOptions abpJsonOptions, + ICurrentTimezoneProvider currentTimezoneProvider, + ITimezoneProvider timezoneProvider) + : base(clock, abpJsonOptions, currentTimezoneProvider, timezoneProvider) { - _clock = clock; - _options = abpJsonOptions.Value; } public virtual AbpDateTimeConverter SkipDateTimeNormalization() { - _skipDateTimeNormalization = true; + IsSkipDateTimeNormalization = true; return this; } public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (_options.InputDateTimeFormats.Any()) - { - if (reader.TokenType == JsonTokenType.String) - { - foreach (var format in _options.InputDateTimeFormats) - { - var s = reader.GetString(); - if (DateTime.TryParseExact(s, format, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d1)) - { - return Normalize(d1); - } - } - } - else - { - throw new JsonException("Reader's TokenType is not String!"); - } - } - - if (reader.TryGetDateTime(out var d3)) - { - return Normalize(d3); - } - - var dateText = reader.GetString(); - if (!dateText.IsNullOrWhiteSpace()) + if (Options.InputDateTimeFormats.Any() && reader.TokenType != JsonTokenType.String) { - if (DateTime.TryParse(dateText, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d4)) - { - return Normalize(d4); - } + throw new JsonException("Reader's TokenType is not String!"); } - throw new JsonException("Can't get datetime from the reader!"); + return TryReadDateTime(ref reader, out var result) + ? result + : throw new JsonException("Can't get datetime from the reader!"); } public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { - if (_options.OutputDateTimeFormat.IsNullOrWhiteSpace()) - { - writer.WriteStringValue(Normalize(value)); - } - else - { - writer.WriteStringValue(Normalize(value).ToString(_options.OutputDateTimeFormat, CultureInfo.CurrentUICulture)); - } - } - - protected virtual DateTime Normalize(DateTime dateTime) - { - return _skipDateTimeNormalization ? dateTime : _clock.Normalize(dateTime); + WriteDateTime(writer, value); } } diff --git a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs new file mode 100644 index 0000000000..2f3557fcbd --- /dev/null +++ b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs @@ -0,0 +1,110 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.Options; +using Volo.Abp.Timing; + +namespace Volo.Abp.Json.SystemTextJson.JsonConverters; + +public abstract class AbpDateTimeConverterBase : JsonConverter +{ + protected IClock Clock { get; } + protected AbpJsonOptions Options { get; } + protected ICurrentTimezoneProvider CurrentTimezoneProvider { get; } + protected ITimezoneProvider TimezoneProvider { get; } + protected bool IsSkipDateTimeNormalization { get; set; } + + protected AbpDateTimeConverterBase( + IClock clock, + IOptions abpJsonOptions, + ICurrentTimezoneProvider currentTimezoneProvider, + ITimezoneProvider timezoneProvider) + { + Clock = clock; + CurrentTimezoneProvider = currentTimezoneProvider; + TimezoneProvider = timezoneProvider; + Options = abpJsonOptions.Value; + } + + protected bool TryReadDateTime(ref Utf8JsonReader reader, out DateTime value) + { + value = default; + + if (Options.InputDateTimeFormats.Any()) + { + if (reader.TokenType != JsonTokenType.String) + { + return false; + } + + var s = reader.GetString(); + foreach (var format in Options.InputDateTimeFormats) + { + if (!DateTime.TryParseExact(s, format, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d1)) + { + continue; + } + + value = Normalize(d1); + return true; + } + } + + if (reader.TryGetDateTime(out var d2)) + { + value = Normalize(d2); + return true; + } + + var dateText = reader.GetString(); + if (dateText.IsNullOrWhiteSpace()) + { + return false; + } + + if (!DateTime.TryParse(dateText, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d3)) + { + return false; + } + + value = Normalize(d3); + return true; + + } + + protected void WriteDateTime(Utf8JsonWriter writer, DateTime value) + { + if (Options.OutputDateTimeFormat.IsNullOrWhiteSpace()) + { + writer.WriteStringValue(Normalize(value)); + } + else + { + writer.WriteStringValue(Normalize(value).ToString(Options.OutputDateTimeFormat, CultureInfo.CurrentUICulture)); + } + } + + protected virtual DateTime Normalize(DateTime dateTime) + { + if (dateTime.Kind != DateTimeKind.Unspecified || + !Clock.SupportsMultipleTimezone || + CurrentTimezoneProvider.TimeZone.IsNullOrWhiteSpace()) + { + return IsSkipDateTimeNormalization ? dateTime : Clock.Normalize(dateTime); + } + + try + { + var timezoneInfo = TimezoneProvider.GetTimeZoneInfo(CurrentTimezoneProvider.TimeZone); + dateTime = new DateTimeOffset(dateTime, timezoneInfo.BaseUtcOffset).UtcDateTime; + } + catch + { + // ignored + } + + return IsSkipDateTimeNormalization ? dateTime : Clock.Normalize(dateTime); + } +} diff --git a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs index e73f39d097..e1125b5040 100644 --- a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs +++ b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs @@ -1,65 +1,39 @@ using System; -using System.Globalization; using System.Linq; using System.Text.Json; -using System.Text.Json.Serialization; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.Timing; namespace Volo.Abp.Json.SystemTextJson.JsonConverters; -public class AbpNullableDateTimeConverter : JsonConverter, ITransientDependency +public class AbpNullableDateTimeConverter : AbpDateTimeConverterBase, ITransientDependency { - private readonly IClock _clock; - private readonly AbpJsonOptions _options; - private bool _skipDateTimeNormalization; - - public AbpNullableDateTimeConverter(IClock clock, IOptions abpJsonOptions) + public AbpNullableDateTimeConverter( + IClock clock, + IOptions abpJsonOptions, + ICurrentTimezoneProvider currentTimezoneProvider, + ITimezoneProvider timezoneProvider) + : base(clock, abpJsonOptions, currentTimezoneProvider, timezoneProvider) { - _clock = clock; - _options = abpJsonOptions.Value; } public virtual AbpNullableDateTimeConverter SkipDateTimeNormalization() { - _skipDateTimeNormalization = true; + IsSkipDateTimeNormalization = true; return this; } public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (_options.InputDateTimeFormats.Any()) - { - if (reader.TokenType == JsonTokenType.String) - { - foreach (var format in _options.InputDateTimeFormats) - { - var s = reader.GetString(); - if (DateTime.TryParseExact(s, format, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d1)) - { - return Normalize(d1); - } - } - } - else - { - throw new JsonException("Reader's TokenType is not String!"); - } - } - - if (reader.TryGetDateTime(out var d2)) + if (Options.InputDateTimeFormats.Any() && reader.TokenType != JsonTokenType.String) { - return Normalize(d2); + throw new JsonException("Reader's TokenType is not String!"); } - var dateText = reader.GetString(); - if (!dateText.IsNullOrWhiteSpace()) + if (TryReadDateTime(ref reader, out var result)) { - if (DateTime.TryParse(dateText, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d3)) - { - return Normalize(d3); - } + return result; } return null; @@ -70,22 +44,9 @@ public class AbpNullableDateTimeConverter : JsonConverter, ITransient if (value == null) { writer.WriteNullValue(); + return; } - else - { - if (_options.OutputDateTimeFormat.IsNullOrWhiteSpace()) - { - writer.WriteStringValue(Normalize(value.Value)); - } - else - { - writer.WriteStringValue(Normalize(value.Value).ToString(_options.OutputDateTimeFormat, CultureInfo.CurrentUICulture)); - } - } - } - protected virtual DateTime Normalize(DateTime dateTime) - { - return _skipDateTimeNormalization ? dateTime : _clock.Normalize(dateTime); + WriteDateTime(writer, value.Value); } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs index 6dfe3a7281..4093e47011 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs @@ -13,12 +13,24 @@ public class ModelBindingController : AbpController return input.Kind.ToString().ToLower(); } + [HttpGet("DateTimeKind_WithResult")] + public string DateTimeKind_WithResult(DateTime input) + { + return input.Kind.ToString().ToLower() + "_" + input.ToString("O").ToLower(); + } + [HttpGet("NullableDateTimeKind")] public string NullableDateTimeKind(DateTime? input) { return input.Value.Kind.ToString().ToLower(); } + [HttpGet("NullableDateTimeKind_WithResult")] + public string NullableDateTimeKind_WithResult(DateTime? input) + { + return input.Value.Kind.ToString().ToLower() + "_" + input.Value.ToString("O").ToLower(); + } + [HttpGet("DisableDateTimeNormalizationDateTimeKind")] public string DisableDateTimeNormalizationDateTimeKind([DisableDateTimeNormalization] DateTime input) { @@ -40,6 +52,19 @@ public class ModelBindingController : AbpController input.InnerModel.Time4.Kind.ToString().ToLower(); } + [HttpGet("ComplexTypeDateTimeKind_WithResult")] + public string ComplexTypeDateTimeKind_WithResult(GetDateTimeKindModel input) + { + return input.Time1.Kind.ToString().ToLower() + "_" + + input.Time1.ToString("O").ToLower() + "_" + + input.Time2.Kind.ToString().ToLower() + "_" + + input.Time2.ToString("O").ToLower() + "_" + + input.Time3.Value.Kind.ToString().ToLower() + "_" + + input.Time3.Value.ToString("O").ToLower() + "_" + + input.InnerModel.Time4.Kind.ToString().ToLower() + "_" + + input.InnerModel.Time4.ToString("O").ToLower(); + } + //JSON input and output. [HttpPost("ComplexTypeDateTimeKind_JSON")] public string ComplexTypeDateTimeKind_JSON([FromBody] GetDateTimeKindModel input) @@ -50,6 +75,21 @@ public class ModelBindingController : AbpController input.InnerModel.Time4.Kind.ToString().ToLower(); } + //JSON input and output. + [HttpPost("ComplexTypeDateTimeKind_JSON_WithResult")] + public string ComplexTypeDateTimeKind_JSON_WithResult([FromBody] GetDateTimeKindModel input) + { + return input.Time1.Kind.ToString().ToLower() + "_" + + input.Time1.ToString("O").ToLower() + "_" + + input.Time2.Kind.ToString().ToLower() + "_" + + input.Time2.ToString("O").ToLower() + "_" + + input.Time3.Value.Kind.ToString().ToLower() + "_" + + input.Time3.Value.ToString("O").ToLower() + "_" + + input.InnerModel.Time4.Kind.ToString().ToLower() + "_" + + input.InnerModel.Time4.ToString("O").ToLower(); + } + + [HttpPost("Guid_Json_Test")] public GuidJsonModel Guid_Json_Test([FromBody] GuidJsonModel input) { diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs index edfdc4ff73..0d2820c59c 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs @@ -26,6 +26,28 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase resultAsString.ShouldBe(Kind.ToString().ToLower()); } + [Fact] + public async Task DateTimeKind_WithTimezone_Test() + { + var response = await Client.GetAsync("/api/model-Binding-test/DateTimeKind_WithResult?input=2010-01-01T00:00:00&__timezone=Europe/Istanbul"); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + var resultAsString = await response.Content.ReadAsStringAsync(); + + var dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); + switch(Kind) + { + case DateTimeKind.Utc: + dateTime = new DateTime(2009, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 + break; + case DateTimeKind.Local: + dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Local); + break; + } + + resultAsString.ShouldBe($"{Kind.ToString().ToLower()}_{dateTime.ToString("O").ToLower()}"); + } + [Fact] public async Task NullableDateTimeKind_Test() { @@ -37,6 +59,29 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase resultAsString.ShouldBe(Kind.ToString().ToLower()); } + [Fact] + public async Task NullableDateTimeKind_WithTimezone_Test() + { + var response = + await Client.GetAsync("/api/model-Binding-test/NullableDateTimeKind_WithResult?input=2010-01-01T00:00:00&__timezone=Europe/Istanbul"); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + var resultAsString = await response.Content.ReadAsStringAsync(); + + var dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); + switch(Kind) + { + case DateTimeKind.Utc: + dateTime = new DateTime(2009, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 + break; + case DateTimeKind.Local: + dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Local); + break; + } + + resultAsString.ShouldBe($"{Kind.ToString().ToLower()}_{dateTime.ToString("O").ToLower()}"); + } + [Fact] public async Task DisableDateTimeNormalizationDateTimeKind_Test() { @@ -104,6 +149,41 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase resultAsString.ShouldBe($"local_{Kind.ToString().ToLower()}_{Kind.ToString().ToLower()}_local"); } + [Fact] + public async Task ComplexTypeDateTimeKind_JSON_WithTimezone_Test() + { + var time = DateTime.Parse("2010-01-01T00:00:00"); + var response = await Client.PostAsync("/api/model-Binding-test/ComplexTypeDateTimeKind_JSON_WithResult?__timezone=Europe/Istanbul", + new StringContent(JsonSerializer.Serialize( + new GetDateTimeKindModel + { + Time1 = time, + Time2 = time, + Time3 = time, + InnerModel = new GetDateTimeKindModel.GetDateTimeKindInnerModel + { + Time4 = time + } + } + ), Encoding.UTF8, MimeTypes.Application.Json)); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + var resultAsString = await response.Content.ReadAsStringAsync(); + + var dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); + switch(Kind) + { + case DateTimeKind.Utc: + dateTime = new DateTime(2009, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 + break; + case DateTimeKind.Local: + dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Local); + break; + } + + resultAsString.ShouldBe($"unspecified_{time.ToString("O").ToLower()}_{Kind.ToString().ToLower()}_{dateTime.ToString("O").ToLower()}_{Kind.ToString().ToLower()}_{dateTime.ToString("O").ToLower()}_unspecified_{time.ToString("O").ToLower()}"); + } + [Fact] public async Task Guid_Json_Test() { From 84cf25f17b1eb068b4071a1f4fe94ecf3d69a1c7 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 3 Jan 2026 18:02:38 +0800 Subject: [PATCH 16/30] Refactor DateTime handling to use GetUtcOffset for accurate timezone adjustments in model binding and JSON converters --- .../ModelBinding/AbpDateTimeModelBinder.cs | 2 +- .../Json/Newtonsoft/AbpDateTimeConverter.cs | 2 +- .../AbpDateTimeConverterBase.cs | 2 +- .../ModelBinding/ModelBindingController.cs | 1 - .../ModelBindingController_Tests.cs | 54 +++++++++---------- 5 files changed, 30 insertions(+), 31 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs index c63c2c582f..109b85fe9e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs @@ -37,7 +37,7 @@ public class AbpDateTimeModelBinder : IModelBinder try { var timezoneInfo = _timezoneProvider.GetTimeZoneInfo(_currentTimezoneProvider.TimeZone); - dateTime = new DateTimeOffset(dateTime, timezoneInfo.BaseUtcOffset).UtcDateTime; + dateTime = new DateTimeOffset(dateTime, timezoneInfo.GetUtcOffset(dateTime)).UtcDateTime; } catch { diff --git a/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs b/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs index 257a90e84c..7b9a2cd4d3 100644 --- a/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs +++ b/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs @@ -130,7 +130,7 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency try { var timezoneInfo = _timezoneProvider.GetTimeZoneInfo(_currentTimezoneProvider.TimeZone); - dateTime = new DateTimeOffset(dateTime, timezoneInfo.BaseUtcOffset).UtcDateTime; + dateTime = new DateTimeOffset(dateTime, timezoneInfo.GetUtcOffset(dateTime)).UtcDateTime; } catch { diff --git a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs index 2f3557fcbd..b603353d0c 100644 --- a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs +++ b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs @@ -98,7 +98,7 @@ public abstract class AbpDateTimeConverterBase : JsonConverter try { var timezoneInfo = TimezoneProvider.GetTimeZoneInfo(CurrentTimezoneProvider.TimeZone); - dateTime = new DateTimeOffset(dateTime, timezoneInfo.BaseUtcOffset).UtcDateTime; + dateTime = new DateTimeOffset(dateTime, timezoneInfo.GetUtcOffset(dateTime)).UtcDateTime; } catch { diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs index 4093e47011..ceddc0083e 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs @@ -89,7 +89,6 @@ public class ModelBindingController : AbpController input.InnerModel.Time4.ToString("O").ToLower(); } - [HttpPost("Guid_Json_Test")] public GuidJsonModel Guid_Json_Test([FromBody] GuidJsonModel input) { diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs index 0d2820c59c..2fd1130474 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs @@ -19,7 +19,7 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase [Fact] public async Task DateTimeKind_Test() { - var response = await Client.GetAsync("/api/model-Binding-test/DateTimeKind?input=2010-01-01T00:00:00Z"); + var response = await Client.GetAsync("/api/model-Binding-test/DateTimeKind?input=2020-01-01T00:00:00Z"); response.StatusCode.ShouldBe(HttpStatusCode.OK); var resultAsString = await response.Content.ReadAsStringAsync(); @@ -29,19 +29,19 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase [Fact] public async Task DateTimeKind_WithTimezone_Test() { - var response = await Client.GetAsync("/api/model-Binding-test/DateTimeKind_WithResult?input=2010-01-01T00:00:00&__timezone=Europe/Istanbul"); + var response = await Client.GetAsync("/api/model-Binding-test/DateTimeKind_WithResult?input=2020-01-01T00:00:00&__timezone=Europe/Istanbul"); response.StatusCode.ShouldBe(HttpStatusCode.OK); var resultAsString = await response.Content.ReadAsStringAsync(); - var dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); - switch(Kind) + var dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); + switch (Kind) { case DateTimeKind.Utc: - dateTime = new DateTime(2009, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 + dateTime = new DateTime(2019, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 break; case DateTimeKind.Local: - dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Local); + dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Local); break; } @@ -52,7 +52,7 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase public async Task NullableDateTimeKind_Test() { var response = - await Client.GetAsync("/api/model-Binding-test/NullableDateTimeKind?input=2010-01-01T00:00:00Z"); + await Client.GetAsync("/api/model-Binding-test/NullableDateTimeKind?input=2020-01-01T00:00:00Z"); response.StatusCode.ShouldBe(HttpStatusCode.OK); var resultAsString = await response.Content.ReadAsStringAsync(); @@ -63,19 +63,19 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase public async Task NullableDateTimeKind_WithTimezone_Test() { var response = - await Client.GetAsync("/api/model-Binding-test/NullableDateTimeKind_WithResult?input=2010-01-01T00:00:00&__timezone=Europe/Istanbul"); + await Client.GetAsync("/api/model-Binding-test/NullableDateTimeKind_WithResult?input=2020-01-01T00:00:00&__timezone=Europe/Istanbul"); response.StatusCode.ShouldBe(HttpStatusCode.OK); var resultAsString = await response.Content.ReadAsStringAsync(); - var dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); - switch(Kind) + var dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); + switch (Kind) { case DateTimeKind.Utc: - dateTime = new DateTime(2009, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 + dateTime = new DateTime(2019, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 break; case DateTimeKind.Local: - dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Local); + dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Local); break; } @@ -87,11 +87,11 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase { var response = await Client.GetAsync( - "/api/model-Binding-test/DisableDateTimeNormalizationDateTimeKind?input=2010-01-01T00:00:00Z"); + "/api/model-Binding-test/DisableDateTimeNormalizationDateTimeKind?input=2020-01-01T00:00:00Z"); response.StatusCode.ShouldBe(HttpStatusCode.OK); var resultAsString = await response.Content.ReadAsStringAsync(); - //Time parameter(2010-01-01T00:00:00Z) with time zone information, so the default Kind is UTC + //Time parameter(2020-01-01T00:00:00Z) with time zone information, so the default Kind is UTC //https://docs.microsoft.com/en-us/aspnet/core/migration/31-to-50?view=aspnetcore-3.1&tabs=visual-studio#datetime-values-are-model-bound-as-utc-times resultAsString.ShouldBe(DateTimeKind.Utc.ToString().ToLower()); } @@ -101,11 +101,11 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase { var response = await Client.GetAsync( - "/api/model-Binding-test/DisableDateTimeNormalizationNullableDateTimeKind?input=2010-01-01T00:00:00Z"); + "/api/model-Binding-test/DisableDateTimeNormalizationNullableDateTimeKind?input=2020-01-01T00:00:00Z"); response.StatusCode.ShouldBe(HttpStatusCode.OK); var resultAsString = await response.Content.ReadAsStringAsync(); - //Time parameter(2010-01-01T00:00:00Z) with time zone information, so the default Kind is UTC + //Time parameter(2020-01-01T00:00:00Z) with time zone information, so the default Kind is UTC //https://docs.microsoft.com/en-us/aspnet/core/migration/31-to-50?view=aspnetcore-3.1&tabs=visual-studio#datetime-values-are-model-bound-as-utc-times resultAsString.ShouldBe(DateTimeKind.Utc.ToString().ToLower()); } @@ -114,14 +114,14 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase public async Task ComplexTypeDateTimeKind_Test() { var response = await Client.GetAsync("/api/model-Binding-test/ComplexTypeDateTimeKind?" + - "Time1=2010-01-01T00:00:00Z&" + - "Time2=2010-01-01T00:00:00Z&" + - "Time3=2010-01-01T00:00:00Z&" + - "InnerModel.Time4=2010-01-01T00:00:00Z"); + "Time1=2020-01-01T00:00:00Z&" + + "Time2=2020-01-01T00:00:00Z&" + + "Time3=2020-01-01T00:00:00Z&" + + "InnerModel.Time4=2020-01-01T00:00:00Z"); response.StatusCode.ShouldBe(HttpStatusCode.OK); var resultAsString = await response.Content.ReadAsStringAsync(); - //Time parameter(2010-01-01T00:00:00Z) with time zone information, so the default Kind is UTC + //Time parameter(2020-01-01T00:00:00Z) with time zone information, so the default Kind is UTC //https://docs.microsoft.com/en-us/aspnet/core/migration/31-to-50?view=aspnetcore-3.1&tabs=visual-studio#datetime-values-are-model-bound-as-utc-times resultAsString.ShouldBe($"utc_{Kind.ToString().ToLower()}_{Kind.ToString().ToLower()}_utc"); } @@ -129,7 +129,7 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase [Fact] public async Task ComplexTypeDateTimeKind_JSON_Test() { - var time = DateTime.Parse("2010-01-01T00:00:00Z"); + var time = DateTime.Parse("2020-01-01T00:00:00Z"); var response = await Client.PostAsync("/api/model-Binding-test/ComplexTypeDateTimeKind_JSON", new StringContent(JsonSerializer.Serialize( new GetDateTimeKindModel @@ -152,7 +152,7 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase [Fact] public async Task ComplexTypeDateTimeKind_JSON_WithTimezone_Test() { - var time = DateTime.Parse("2010-01-01T00:00:00"); + var time = DateTime.Parse("2020-01-01T00:00:00"); var response = await Client.PostAsync("/api/model-Binding-test/ComplexTypeDateTimeKind_JSON_WithResult?__timezone=Europe/Istanbul", new StringContent(JsonSerializer.Serialize( new GetDateTimeKindModel @@ -170,14 +170,14 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase response.StatusCode.ShouldBe(HttpStatusCode.OK); var resultAsString = await response.Content.ReadAsStringAsync(); - var dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); - switch(Kind) + var dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); + switch (Kind) { case DateTimeKind.Utc: - dateTime = new DateTime(2009, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 + dateTime = new DateTime(2019, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3 break; case DateTimeKind.Local: - dateTime = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Local); + dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Local); break; } From 028362c6728f47331aa1211b4f122225d93ac318 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 3 Jan 2026 18:35:17 +0800 Subject: [PATCH 17/30] Add logging for DateTime conversion failures --- .../Mvc/ModelBinding/AbpDateTimeModelBinder.cs | 12 ++++++++++-- .../ModelBinding/AbpDateTimeModelBinderProvider.cs | 5 +---- .../Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs | 9 ++++++++- .../JsonConverters/AbpDateTimeConverterBase.cs | 8 +++++++- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs index 109b85fe9e..ac6e44ed6c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs @@ -2,19 +2,27 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; +using Microsoft.Extensions.Logging; using Volo.Abp.Timing; namespace Volo.Abp.AspNetCore.Mvc.ModelBinding; public class AbpDateTimeModelBinder : IModelBinder { + private readonly ILogger _logger; private readonly DateTimeModelBinder _dateTimeModelBinder; private readonly IClock _clock; private readonly ICurrentTimezoneProvider _currentTimezoneProvider; private readonly ITimezoneProvider _timezoneProvider; - public AbpDateTimeModelBinder(DateTimeModelBinder dateTimeModelBinder, IClock clock, ICurrentTimezoneProvider currentTimezoneProvider, ITimezoneProvider timezoneProvider) + public AbpDateTimeModelBinder( + ILogger logger, + DateTimeModelBinder dateTimeModelBinder, + IClock clock, + ICurrentTimezoneProvider currentTimezoneProvider, + ITimezoneProvider timezoneProvider) { + _logger = logger; _dateTimeModelBinder = dateTimeModelBinder; _clock = clock; _currentTimezoneProvider = currentTimezoneProvider; @@ -41,7 +49,7 @@ public class AbpDateTimeModelBinder : IModelBinder } catch { - // ignored + _logger.LogWarning("Could not convert DateTime with unspecified Kind using timezone '{TimeZone}'.", _currentTimezoneProvider.TimeZone); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinderProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinderProvider.cs index 3be76251c3..a836838cd1 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinderProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinderProvider.cs @@ -50,9 +50,6 @@ public class AbpDateTimeModelBinderProvider : IModelBinderProvider { const DateTimeStyles supportedStyles = DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AdjustToUniversal; var dateTimeModelBinder = new DateTimeModelBinder(supportedStyles, context.Services.GetRequiredService()); - return new AbpDateTimeModelBinder(dateTimeModelBinder, - context.Services.GetRequiredService(), - context.Services.GetRequiredService(), - context.Services.GetRequiredService()); + return ActivatorUtilities.CreateInstance(context.Services, dateTimeModelBinder); } } diff --git a/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs b/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs index 7b9a2cd4d3..55c270d14e 100644 --- a/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs +++ b/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs @@ -2,6 +2,8 @@ using System.Globalization; using System.Linq; using System.Reflection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -15,6 +17,9 @@ namespace Volo.Abp.Json.Newtonsoft; public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency { private const string DefaultDateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK"; + + public ILogger Logger { get; set; } + private readonly DateTimeStyles _dateTimeStyles = DateTimeStyles.RoundtripKind; private readonly CultureInfo _culture = CultureInfo.InvariantCulture; private readonly IClock _clock; @@ -25,6 +30,8 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency public AbpDateTimeConverter(IClock clock, IOptions options, ICurrentTimezoneProvider currentTimezoneProvider, ITimezoneProvider timezoneProvider) { + Logger = NullLogger.Instance; + _clock = clock; _currentTimezoneProvider = currentTimezoneProvider; _timezoneProvider = timezoneProvider; @@ -134,7 +141,7 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency } catch { - // ignored + Logger.LogWarning("Could not convert DateTime with unspecified Kind using timezone '{TimeZone}'.", _currentTimezoneProvider.TimeZone); } return _skipDateTimeNormalization diff --git a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs index b603353d0c..ee39a66678 100644 --- a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs +++ b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs @@ -3,6 +3,8 @@ using System.Globalization; using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Volo.Abp.Timing; @@ -10,6 +12,8 @@ namespace Volo.Abp.Json.SystemTextJson.JsonConverters; public abstract class AbpDateTimeConverterBase : JsonConverter { + public ILogger> Logger { get; set; } + protected IClock Clock { get; } protected AbpJsonOptions Options { get; } protected ICurrentTimezoneProvider CurrentTimezoneProvider { get; } @@ -22,6 +26,8 @@ public abstract class AbpDateTimeConverterBase : JsonConverter ICurrentTimezoneProvider currentTimezoneProvider, ITimezoneProvider timezoneProvider) { + Logger = NullLogger>.Instance; + Clock = clock; CurrentTimezoneProvider = currentTimezoneProvider; TimezoneProvider = timezoneProvider; @@ -102,7 +108,7 @@ public abstract class AbpDateTimeConverterBase : JsonConverter } catch { - // ignored + Logger.LogWarning("Could not convert DateTime with unspecified Kind using timezone '{TimeZone}'.", CurrentTimezoneProvider.TimeZone); } return IsSkipDateTimeNormalization ? dateTime : Clock.Normalize(dateTime); From 01ac33d7b5aaabdbb451fc464cd53086c7839a85 Mon Sep 17 00:00:00 2001 From: selman koc <64414348+skoc10@users.noreply.github.com> Date: Mon, 5 Jan 2026 13:37:11 +0300 Subject: [PATCH 18/30] Add new version for 10.0.2 --- latest-versions.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/latest-versions.json b/latest-versions.json index 59f9c65cf3..007d19f046 100644 --- a/latest-versions.json +++ b/latest-versions.json @@ -1,4 +1,13 @@ [ + { + "version": "10.0.2", + "releaseDate": "", + "type": "stable", + "message": "", + "leptonx": { + "version": "5.0.2" + } + }, { "version": "9.3.7", "releaseDate": "", From fe694c3e59f96892ace825dce1afdd2fc1343ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0smail=20=C3=87A=C4=9EDA=C5=9E?= Date: Wed, 7 Jan 2026 08:39:10 +0300 Subject: [PATCH 19/30] First version of .cursorrules file added --- .cursorrules | 206 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 .cursorrules diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 0000000000..5f583f4e01 --- /dev/null +++ b/.cursorrules @@ -0,0 +1,206 @@ +# ABP Framework – Cursor Rules +# Scope: ABP Framework repository (abpframework/abp). Apply to framework + module development. +# Goal: Enforce ABP module architecture best practices (DDD, layering, DB/ORM independence) +# and align contributions with the ABP contribution guide. + +## Global Defaults +- Follow existing patterns in this repository first. Before generating new code, search for similar implementations and mirror their structure, naming, and conventions. +- Prefer minimal, focused diffs. Avoid drive-by refactors and formatting churn. +- Preserve public APIs. Avoid breaking changes unless explicitly requested and justified. +- Keep layers clean. Do not introduce forbidden dependencies between packages. + +## Module / Package Architecture (Layering) +- Use a layered module structure with explicit dependencies: + - *.Domain.Shared: constants, enums, shared types safe for all layers and 3rd-party clients. MUST NOT contain entities, repositories, domain services, or business objects. + - *.Domain: entities/aggregate roots, repository interfaces, domain services. + - *.Application.Contracts: application service interfaces and DTOs. + - *.Application: application service implementations. + - *.EntityFrameworkCore / *.MongoDb: ORM integration packages depend on *.Domain only. MUST NOT depend on other layers. + - *.HttpApi: REST controllers. MUST depend ONLY on *.Application.Contracts (NOT *.Application). + - *.HttpApi.Client: remote client proxies. MUST depend ONLY on *.Application.Contracts. + - *.Web: UI. MUST depend ONLY on *.HttpApi. +- Enforce dependency direction: + - Web -> HttpApi -> Application.Contracts + - Application -> Domain + Application.Contracts + - Domain -> Domain.Shared + - ORM integration -> Domain +- Do not leak web concerns into application/domain. + +## Domain Layer – Entities & Aggregate Roots +- Define entities in the domain layer. +- Entities must be valid at creation: + - Provide a primary constructor that enforces invariants. + - Always include a protected parameterless constructor for ORMs. + - Always initialize sub-collections in the primary constructor. + - Do NOT generate Guid keys inside constructors; accept `id` and generate using `IGuidGenerator` from the calling code. +- Make members `virtual` where appropriate (ORM/proxy compatibility). +- Protect consistency: + - Use non-public setters (private/protected/internal) when needed. + - Provide meaningful domain methods for state transitions; prefer returning `this` from setters when applicable. +- Aggregate roots: + - Always use a single `Id` property. Do NOT use composite keys. + - Prefer `Guid` keys for aggregate roots. + - Inherit from `AggregateRoot` or audited base classes as required. +- Aggregate boundaries: + - Keep aggregates small. Avoid large sub-collections unless necessary. +- References: + - Reference other aggregate roots by Id only. + - Do NOT add navigation properties to other aggregate roots. + +## Repositories +- Define repository interfaces in the domain layer. +- Create one dedicated repository interface per aggregate root (e.g., `IProductRepository`). +- Application code MUST NOT use generic repositories (e.g., `IRepository`). +- Repository interfaces MUST NOT expose `IQueryable` to application/domain layers: + - Prefer inheriting from `IBasicRepository` (or `IReadOnlyRepository<...>` when suitable). + - Do NOT inherit from `IRepository`. +- Do NOT define repositories for non-aggregate-root entities. +- Repository method conventions: + - All methods async. + - Include optional `CancellationToken cancellationToken = default` in every method. + - For single-entity returning methods: include `bool includeDetails = true`. + - For list returning methods: include `bool includeDetails = false`. + - Do NOT return composite projection classes like `UserWithRoles`. Use `includeDetails` for eager-loading. + - Avoid projection-only view models from repositories by default; only allow when performance is critical. + +## Domain Services +- Define domain services in the domain layer. +- Default: do NOT create interfaces for domain services unless necessary (mocking/multiple implementations). +- Naming: use `*Manager` suffix. +- Domain service methods: + - Do NOT define read-only “GET” methods. Querying belongs to repositories and application services. + - Define methods that mutate state and enforce domain rules. + - Use specific, intention-revealing names (avoid generic `UpdateXAsync`). + - Accept valid domain objects as parameters; do NOT accept/return DTOs. + - On rule violations, throw `BusinessException` (or custom business exceptions). + - Use unique, namespaced error codes suitable for localization (e.g., `IssueTracking:ConcurrentOpenIssueLimit`). + - Do NOT depend on authenticated user logic; pass required values from application layer. + +## Application Services (Contracts + Implementation) +### Contracts +- Define one interface per application service in *.Application.Contracts. +- Interfaces must inherit from `IApplicationService`. +- Naming: `I*AppService`. +- Do NOT accept/return entities. Use DTOs and primitive parameters. + +### Method Naming & Shapes +- All service methods async and end with `Async`. +- Do not repeat entity names in method names (use `GetAsync`, not `GetProductAsync`). +- Standard CRUD: + - `GetAsync(Guid id)` returns a detailed DTO. + - `GetListAsync(QueryDto queryDto)` returns a list of detailed DTOs. + - `CreateAsync(CreateDto dto)` returns detailed DTO. + - `UpdateAsync(Guid id, UpdateDto dto)` returns detailed DTO (id MUST NOT be inside update DTO). + - `DeleteAsync(Guid id)` returns void/Task. +- `GetListAsync` query DTO: + - Filtering/sorting/paging fields optional with defaults. + - Enforce a maximum page size for performance. + +### DTO Usage +- Inputs: + - Do not include unused properties. + - Do NOT share input DTOs between methods. + - Do NOT use inheritance between input DTOs (except rare abstract base DTO cases; be very cautious). + +### Implementation +- Application layer must be independent of web. +- Implement interfaces in *.Application, name `ProductAppService` for `IProductAppService`. +- Inherit from `ApplicationService`. +- Make all public methods `virtual`. +- Avoid private helper methods; prefer `protected virtual` helpers for extensibility. +- Data access: + - Use dedicated repositories (e.g., `IProductRepository`). + - Do NOT use generic repositories. + - Do NOT put LINQ/SQL queries inside application service methods; repositories perform queries. +- Entity mutation: + - Load required entities from repositories. + - Mutate using domain methods. + - Call repository `UpdateAsync` after updates (do not assume change tracking). +- Extra properties: + - Use `MapExtraPropertiesTo` or configure object mapper for `MapExtraProperties`. +- Files: + - Do NOT use web types like `IFormFile` or `Stream` in application services. + - Controllers handle upload; pass `byte[]` (or similar) to application services. +- Cross-application-service calls: + - Do NOT call other application services within the same module. + - For reuse, push logic into domain layer or extract shared helpers carefully. + - You MAY call other modules’ application services only via their Application.Contracts. + +## DTO Conventions +- Define DTOs in *.Application.Contracts. +- Prefer ABP base DTO types (`EntityDto`, audited DTOs). +- For aggregate roots, prefer extensible DTO base types so extra properties can map. +- DTO properties: public getters/setters. +- Input DTO validation: + - Use data annotations. + - Reuse constants from Domain.Shared wherever possible. +- Avoid logic in DTOs; only implement `IValidatableObject` when necessary. +- Mark DTOs `[Serializable]`. +- Output DTO strategy: + - Prefer a Basic DTO and a Detailed DTO; avoid many variants. + - Detailed DTOs: include reference details as nested basic DTOs; avoid duplicating raw FK ids unnecessarily. + +## EF Core Integration +- Define a separate DbContext interface + class per module. +- Do NOT rely on lazy loading; do NOT enable lazy loading. +- DbContext interface: + - Inherit from `IEfCoreDbContext`. + - Add `[ConnectionStringName("...")]`. + - Expose `DbSet` ONLY for aggregate roots. + - Do NOT include setters in the interface. +- DbContext class: + - Inherit `AbpDbContext`. + - Add `[ConnectionStringName("...")]` and implement the interface. +- Table prefix/schema: + - Provide static `TablePrefix` and `Schema` defaulted from constants. + - Use short prefixes; `Abp` prefix reserved for ABP core modules. + - Default schema should be `null`. +- Model mapping: + - Do NOT configure entities directly inside `OnModelCreating`. + - Create `ModelBuilder` extension method `ConfigureX()` and call it. + - Call `b.ConfigureByConvention()` for each entity. +- Repository implementations: + - Inherit from `EfCoreRepository`. + - Use DbContext interface as generic parameter. + - Pass cancellation tokens using `GetCancellationToken(cancellationToken)`. + - Implement `IncludeDetails(include)` extension per aggregate root with sub-collections. + - Override `WithDetailsAsync()` where needed. + +## MongoDB Integration +- Define a separate MongoDbContext interface + class per module. +- MongoDbContext interface: + - Inherit from `IAbpMongoDbContext`. + - Add `[ConnectionStringName("...")]`. + - Expose `IMongoCollection` ONLY for aggregate roots. +- MongoDbContext class: + - Inherit `AbpMongoDbContext` and implement the interface. +- Collection prefix: + - Provide static `CollectionPrefix` defaulted from constants. + - Use short prefixes; `Abp` prefix reserved for ABP core modules. +- Mapping: + - Do NOT configure directly inside `CreateModel`. + - Create `IMongoModelBuilder` extension method `ConfigureX()` and call it. +- Repository implementations: + - Inherit from `MongoDbRepository`. + - Pass cancellation tokens using `GetCancellationToken(cancellationToken)`. + - Ignore `includeDetails` for MongoDB in most cases (documents load sub-collections). + - Prefer `GetQueryableAsync()` to ensure ABP data filters are applied. + +## Contribution Discipline (PR / Issues / Tests) +- Before significant changes, align via GitHub issue/discussion. +- PRs: + - Keep changes scoped and reviewable. + - Add/update unit/integration tests relevant to the change. + - Build and run tests for the impacted area when possible. +- Localization: + - Prefer the `abp translate` workflow for adding missing translations (generate `abp-translation.json`, fill, apply, then PR). + +## Review Checklist +- Layer dependencies respected (no forbidden references). +- No `IQueryable` or generic repository usage leaking into application/domain. +- Entities maintain invariants; Guid id generation not inside constructors. +- Repositories follow async + CancellationToken + includeDetails conventions. +- No web types in application services. +- DTOs in contracts, serializable, validated, minimal, no logic. +- EF/Mongo integration follows context + mapping + repository patterns. +- Minimal diff; no unnecessary API surface expansion. From adab0ae387d2c3545729bc025043a1fc3d17e93e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0smail=20=C3=87A=C4=9EDA=C5=9E?= Date: Wed, 7 Jan 2026 09:45:01 +0300 Subject: [PATCH 20/30] added AI guidelines for ABP Framework itself for Cursor and Copilot --- .cursorrules | 82 ++++++- .github/copilot-instructions.md | 372 ++++++++++++++++++++++++++++++++ 2 files changed, 445 insertions(+), 9 deletions(-) create mode 100644 .github/copilot-instructions.md diff --git a/.cursorrules b/.cursorrules index 5f583f4e01..b88c4e1588 100644 --- a/.cursorrules +++ b/.cursorrules @@ -1,7 +1,7 @@ # ABP Framework – Cursor Rules -# Scope: ABP Framework repository (abpframework/abp). Apply to framework + module development. -# Goal: Enforce ABP module architecture best practices (DDD, layering, DB/ORM independence) -# and align contributions with the ABP contribution guide. +# Scope: ABP Framework repository (abpframework/abp) — for developing ABP itself, not ABP-based applications. +# Goal: Enforce ABP module architecture best practices (DDD, layering, DB/ORM independence), +# maintain backward compatibility, ensure extensibility, and align with ABP contribution guidelines. ## Global Defaults - Follow existing patterns in this repository first. Before generating new code, search for similar implementations and mirror their structure, naming, and conventions. @@ -50,10 +50,10 @@ ## Repositories - Define repository interfaces in the domain layer. - Create one dedicated repository interface per aggregate root (e.g., `IProductRepository`). -- Application code MUST NOT use generic repositories (e.g., `IRepository`). -- Repository interfaces MUST NOT expose `IQueryable` to application/domain layers: - - Prefer inheriting from `IBasicRepository` (or `IReadOnlyRepository<...>` when suitable). - - Do NOT inherit from `IRepository`. +- Public repository interfaces exposed by modules: + - SHOULD inherit from `IBasicRepository` (or `IReadOnlyRepository<...>` when suitable). + - SHOULD NOT expose `IQueryable` in the public contract. + - Internal implementations MAY use `IRepository` and `IQueryable` as needed. - Do NOT define repositories for non-aggregate-root entities. - Repository method conventions: - All methods async. @@ -68,7 +68,8 @@ - Default: do NOT create interfaces for domain services unless necessary (mocking/multiple implementations). - Naming: use `*Manager` suffix. - Domain service methods: - - Do NOT define read-only “GET” methods. Querying belongs to repositories and application services. + - Focus on operations that enforce domain invariants and business rules. + - Query methods are acceptable when they encapsulate domain-specific lookup logic (e.g., normalized lookups, caching, complex resolution). Simple queries belong in repositories. - Define methods that mutate state and enforce domain rules. - Use specific, intention-revealing names (avoid generic `UpdateXAsync`). - Accept valid domain objects as parameters; do NOT accept/return DTOs. @@ -135,7 +136,7 @@ - Use data annotations. - Reuse constants from Domain.Shared wherever possible. - Avoid logic in DTOs; only implement `IValidatableObject` when necessary. -- Mark DTOs `[Serializable]`. +- Do NOT use `[Serializable]` attribute (BinaryFormatter is obsolete); ABP uses JSON serialization. - Output DTO strategy: - Prefer a Basic DTO and a Detailed DTO; avoid many variants. - Detailed DTOs: include reference details as nested basic DTOs; avoid duplicating raw FK ids unnecessarily. @@ -186,6 +187,69 @@ - Ignore `includeDetails` for MongoDB in most cases (documents load sub-collections). - Prefer `GetQueryableAsync()` to ensure ABP data filters are applied. +## ABP Module Classes +- Every package must have exactly one `AbpModule` class. +- Naming: `Abp[ModuleName][Layer]Module` (e.g., `AbpIdentityDomainModule`, `AbpIdentityApplicationModule`). +- Use `[DependsOn(typeof(...))]` to declare module dependencies explicitly. +- Override `ConfigureServices` for DI registration and configuration. +- Override `OnApplicationInitialization` sparingly; prefer `ConfigureServices` when possible. +- Each module must be usable standalone; avoid hidden cross-module coupling. + +## Framework Extensibility +- All public and protected members should be `virtual` for inheritance-based extensibility. +- Prefer `protected virtual` over `private` for helper methods to allow overriding. +- Use `[Dependency(ReplaceServices = true)]` patterns for services intended to be replaceable. +- Provide extension points via interfaces and virtual methods. +- Document extension points with XML comments explaining intended usage. +- Consider providing `*Options` classes for configuration-based extensibility. + +## Backward Compatibility +- Do NOT remove or rename public API members without a deprecation cycle. +- Use `[Obsolete("Message. Use X instead.")]` with clear migration guidance before removal. +- Maintain binary and source compatibility within major versions. +- Add new optional parameters with defaults; do not change existing method signatures. +- When adding new abstract members to base classes, provide default implementations if possible. +- Prefer adding new interfaces over modifying existing ones. + +## Localization Resources +- Define localization resources in Domain.Shared. +- Resource class naming: `[ModuleName]Resource` (e.g., `IdentityResource`, `PermissionManagementResource`). +- JSON files under `/Localization/[ModuleName]/` directory. +- Use `LocalizableString.Create("Key")` for localizable exceptions and messages. +- All user-facing strings must be localized; no hardcoded English text in code. +- Error codes should be namespaced: `ModuleName:ErrorCode` (e.g., `Identity:UserNameAlreadyExists`). + +## Settings & Features +- Define settings in `*SettingDefinitionProvider` in Domain.Shared or Domain. +- Setting names must follow `Abp.[ModuleName].[SettingName]` convention. +- Define features in `*FeatureDefinitionProvider` in Domain.Shared. +- Feature names must follow `[ModuleName].[FeatureName]` convention. +- Use constants for setting/feature names; never hardcode strings. + +## Permissions +- Define permissions in `*PermissionDefinitionProvider` in Application.Contracts. +- Permission names must follow `[ModuleName].[Permission]` convention. +- Use constants for permission names (e.g., `IdentityPermissions.Users.Create`). +- Group related permissions logically. + +## Event Bus & Distributed Events +- Use `ILocalEventBus` for intra-module communication within the same process. +- Use `IDistributedEventBus` for cross-module or cross-service communication. +- Define Event Transfer Objects (ETOs) in Domain.Shared for distributed events. +- ETO naming: `[EntityName][Action]Eto` (e.g., `UserCreatedEto`, `OrderCompletedEto`). +- Event handlers belong in the Application layer. +- ETOs should be simple, serializable, and contain only primitive types or nested ETOs. + +## Testing +- Unit tests: `*.Tests` projects for isolated logic testing with mocked dependencies. +- Integration tests: `*.EntityFrameworkCore.Tests` / `*.MongoDB.Tests` for repository and DB tests. +- Use `AbpIntegratedTest` or `AbpApplicationTestBase` base classes. +- Test modules should use `[DependsOn]` on the module under test. +- Use `Shouldly` assertions (ABP convention). +- Test both EF Core and MongoDB implementations when the module supports both. +- Include tests for permission checks, validation, and edge cases. +- Name test methods: `MethodName_Scenario_ExpectedResult` or `Should_ExpectedBehavior_When_Condition`. + ## Contribution Discipline (PR / Issues / Tests) - Before significant changes, align via GitHub issue/discussion. - PRs: diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..a754a2b5ea --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,372 @@ +# ABP Framework – GitHub Copilot Instructions + +> **Scope**: ABP Framework repository (abpframework/abp) — for developing ABP itself, not ABP-based applications. +> +> **Goal**: Enforce ABP module architecture best practices (DDD, layering, DB/ORM independence), maintain backward compatibility, ensure extensibility, and align with ABP contribution guidelines. + +--- + +## Global Defaults + +- Follow existing patterns in this repository first. Before generating new code, search for similar implementations and mirror their structure, naming, and conventions. +- Prefer minimal, focused diffs. Avoid drive-by refactors and formatting churn. +- Preserve public APIs. Avoid breaking changes unless explicitly requested and justified. +- Keep layers clean. Do not introduce forbidden dependencies between packages. + +--- + +## Module / Package Architecture (Layering) + +Use a layered module structure with explicit dependencies: + +| Layer | Purpose | Allowed Dependencies | +|-------|---------|---------------------| +| `*.Domain.Shared` | Constants, enums, shared types safe for all layers and 3rd-party clients. MUST NOT contain entities, repositories, domain services, or business objects. | None | +| `*.Domain` | Entities/aggregate roots, repository interfaces, domain services. | Domain.Shared | +| `*.Application.Contracts` | Application service interfaces and DTOs. | Domain.Shared | +| `*.Application` | Application service implementations. | Domain, Application.Contracts | +| `*.EntityFrameworkCore` / `*.MongoDb` | ORM integration packages. MUST NOT depend on other layers. | Domain only | +| `*.HttpApi` | REST controllers. MUST depend ONLY on Application.Contracts (NOT Application). | Application.Contracts | +| `*.HttpApi.Client` | Remote client proxies. MUST depend ONLY on Application.Contracts. | Application.Contracts | +| `*.Web` | UI layer. MUST depend ONLY on HttpApi. | HttpApi | + +### Dependency Direction +``` +Web -> HttpApi -> Application.Contracts +Application -> Domain + Application.Contracts +Domain -> Domain.Shared +ORM integration -> Domain +``` + +Do not leak web concerns into application/domain. + +--- + +## Domain Layer – Entities & Aggregate Roots + +- Define entities in the domain layer. +- Entities must be valid at creation: + - Provide a primary constructor that enforces invariants. + - Always include a `protected` parameterless constructor for ORMs. + - Always initialize sub-collections in the primary constructor. + - Do NOT generate Guid keys inside constructors; accept `id` and generate using `IGuidGenerator` from the calling code. +- Make members `virtual` where appropriate (ORM/proxy compatibility). +- Protect consistency: + - Use non-public setters (`private`/`protected`/`internal`) when needed. + - Provide meaningful domain methods for state transitions. + +### Aggregate Roots +- Always use a single `Id` property. Do NOT use composite keys. +- Prefer `Guid` keys for aggregate roots. +- Inherit from `AggregateRoot` or audited base classes as required. +- Keep aggregates small. Avoid large sub-collections unless necessary. + +### References +- Reference other aggregate roots by Id only. +- Do NOT add navigation properties to other aggregate roots. + +--- + +## Repositories + +- Define repository interfaces in the domain layer. +- Create one dedicated repository interface per aggregate root (e.g., `IProductRepository`). +- Public repository interfaces exposed by modules: + - SHOULD inherit from `IBasicRepository` (or `IReadOnlyRepository<...>` when suitable). + - SHOULD NOT expose `IQueryable` in the public contract. + - Internal implementations MAY use `IRepository` and `IQueryable` as needed. +- Do NOT define repositories for non-aggregate-root entities. + +### Method Conventions +- All methods async. +- Include optional `CancellationToken cancellationToken = default` in every method. +- For single-entity returning methods: include `bool includeDetails = true`. +- For list returning methods: include `bool includeDetails = false`. +- Do NOT return composite projection classes like `UserWithRoles`. Use `includeDetails` for eager-loading. +- Avoid projection-only view models from repositories by default; only allow when performance is critical. + +--- + +## Domain Services + +- Define domain services in the domain layer. +- Default: do NOT create interfaces for domain services unless necessary (mocking/multiple implementations). +- Naming: use `*Manager` suffix. + +### Method Guidelines +- Focus on operations that enforce domain invariants and business rules. +- Query methods are acceptable when they encapsulate domain-specific lookup logic (e.g., normalized lookups, caching, complex resolution). Simple queries belong in repositories. +- Define methods that mutate state and enforce domain rules. +- Use specific, intention-revealing names (avoid generic `UpdateXAsync`). +- Accept valid domain objects as parameters; do NOT accept/return DTOs. +- On rule violations, throw `BusinessException` (or custom business exceptions). +- Use unique, namespaced error codes suitable for localization (e.g., `IssueTracking:ConcurrentOpenIssueLimit`). +- Do NOT depend on authenticated user logic; pass required values from application layer. + +--- + +## Application Services + +### Contracts +- Define one interface per application service in `*.Application.Contracts`. +- Interfaces must inherit from `IApplicationService`. +- Naming: `I*AppService`. +- Do NOT accept/return entities. Use DTOs and primitive parameters. + +### Method Naming & Shapes +- All service methods async and end with `Async`. +- Do not repeat entity names in method names (use `GetAsync`, not `GetProductAsync`). + +**Standard CRUD:** +```csharp +Task GetAsync(Guid id); +Task> GetListAsync(GetProductListInput input); +Task CreateAsync(CreateProductInput input); +Task UpdateAsync(Guid id, UpdateProductInput input); // id NOT inside DTO +Task DeleteAsync(Guid id); +``` + +### DTO Usage (Inputs) +- Do not include unused properties. +- Do NOT share input DTOs between methods. +- Do NOT use inheritance between input DTOs (except rare abstract base DTO cases; be very cautious). + +### Implementation +- Application layer must be independent of web. +- Implement interfaces in `*.Application`, name `ProductAppService` for `IProductAppService`. +- Inherit from `ApplicationService`. +- Make all public methods `virtual`. +- Avoid private helper methods; prefer `protected virtual` helpers for extensibility. + +### Data Access +- Use dedicated repositories (e.g., `IProductRepository`). +- Do NOT put LINQ/SQL queries inside application service methods; repositories perform queries. + +### Entity Mutation +- Load required entities from repositories. +- Mutate using domain methods. +- Call repository `UpdateAsync` after updates (do not assume change tracking). + +### Files +- Do NOT use web types like `IFormFile` or `Stream` in application services. +- Controllers handle upload; pass `byte[]` (or similar) to application services. + +### Cross-Service Calls +- Do NOT call other application services within the same module. +- For reuse, push logic into domain layer or extract shared helpers carefully. +- You MAY call other modules' application services only via their Application.Contracts. + +--- + +## DTO Conventions + +- Define DTOs in `*.Application.Contracts`. +- Prefer ABP base DTO types (`EntityDto`, audited DTOs). +- For aggregate roots, prefer extensible DTO base types so extra properties can map. +- DTO properties: public getters/setters. + +### Input DTO Validation +- Use data annotations. +- Reuse constants from Domain.Shared wherever possible. + +### General Rules +- Avoid logic in DTOs; only implement `IValidatableObject` when necessary. +- Do NOT use `[Serializable]` attribute (BinaryFormatter is obsolete); ABP uses JSON serialization. + +### Output DTO Strategy +- Prefer a Basic DTO and a Detailed DTO; avoid many variants. +- Detailed DTOs: include reference details as nested basic DTOs; avoid duplicating raw FK ids unnecessarily. + +--- + +## EF Core Integration + +- Define a separate DbContext interface + class per module. +- Do NOT rely on lazy loading; do NOT enable lazy loading. + +### DbContext Interface +```csharp +[ConnectionStringName("ModuleName")] +public interface IModuleNameDbContext : IEfCoreDbContext +{ + DbSet Products { get; } // No setters, aggregate roots only +} +``` + +### DbContext Class +```csharp +[ConnectionStringName("ModuleName")] +public class ModuleNameDbContext : AbpDbContext, IModuleNameDbContext +{ + public static string TablePrefix { get; set; } = ModuleNameConsts.DefaultDbTablePrefix; + public static string? Schema { get; set; } = ModuleNameConsts.DefaultDbSchema; + + public DbSet Products { get; set; } +} +``` + +### Table Prefix/Schema +- Provide static `TablePrefix` and `Schema` defaulted from constants. +- Use short prefixes; `Abp` prefix reserved for ABP core modules. +- Default schema should be `null`. + +### Model Mapping +- Do NOT configure entities directly inside `OnModelCreating`. +- Create `ModelBuilder` extension method `ConfigureX()` and call it. +- Call `b.ConfigureByConvention()` for each entity. + +### Repository Implementations +- Inherit from `EfCoreRepository`. +- Use DbContext interface as generic parameter. +- Pass cancellation tokens using `GetCancellationToken(cancellationToken)`. +- Implement `IncludeDetails(include)` extension per aggregate root with sub-collections. +- Override `WithDetailsAsync()` where needed. + +--- + +## MongoDB Integration + +- Define a separate MongoDbContext interface + class per module. + +### MongoDbContext Interface +```csharp +[ConnectionStringName("ModuleName")] +public interface IModuleNameMongoDbContext : IAbpMongoDbContext +{ + IMongoCollection Products { get; } // Aggregate roots only +} +``` + +### MongoDbContext Class +```csharp +public class ModuleNameMongoDbContext : AbpMongoDbContext, IModuleNameMongoDbContext +{ + public static string CollectionPrefix { get; set; } = ModuleNameConsts.DefaultDbTablePrefix; +} +``` + +### Mapping +- Do NOT configure directly inside `CreateModel`. +- Create `IMongoModelBuilder` extension method `ConfigureX()` and call it. + +### Repository Implementations +- Inherit from `MongoDbRepository`. +- Pass cancellation tokens using `GetCancellationToken(cancellationToken)`. +- Ignore `includeDetails` for MongoDB in most cases (documents load sub-collections). +- Prefer `GetQueryableAsync()` to ensure ABP data filters are applied. + +--- + +## ABP Module Classes + +- Every package must have exactly one `AbpModule` class. +- Naming: `Abp[ModuleName][Layer]Module` (e.g., `AbpIdentityDomainModule`, `AbpIdentityApplicationModule`). +- Use `[DependsOn(typeof(...))]` to declare module dependencies explicitly. +- Override `ConfigureServices` for DI registration and configuration. +- Override `OnApplicationInitialization` sparingly; prefer `ConfigureServices` when possible. +- Each module must be usable standalone; avoid hidden cross-module coupling. + +--- + +## Framework Extensibility + +- All public and protected members should be `virtual` for inheritance-based extensibility. +- Prefer `protected virtual` over `private` for helper methods to allow overriding. +- Use `[Dependency(ReplaceServices = true)]` patterns for services intended to be replaceable. +- Provide extension points via interfaces and virtual methods. +- Document extension points with XML comments explaining intended usage. +- Consider providing `*Options` classes for configuration-based extensibility. + +--- + +## Backward Compatibility + +- Do NOT remove or rename public API members without a deprecation cycle. +- Use `[Obsolete("Message. Use X instead.")]` with clear migration guidance before removal. +- Maintain binary and source compatibility within major versions. +- Add new optional parameters with defaults; do not change existing method signatures. +- When adding new abstract members to base classes, provide default implementations if possible. +- Prefer adding new interfaces over modifying existing ones. + +--- + +## Localization Resources + +- Define localization resources in Domain.Shared. +- Resource class naming: `[ModuleName]Resource` (e.g., `IdentityResource`, `PermissionManagementResource`). +- JSON files under `/Localization/[ModuleName]/` directory. +- Use `LocalizableString.Create("Key")` for localizable exceptions and messages. +- All user-facing strings must be localized; no hardcoded English text in code. +- Error codes should be namespaced: `ModuleName:ErrorCode` (e.g., `Identity:UserNameAlreadyExists`). + +--- + +## Settings & Features + +- Define settings in `*SettingDefinitionProvider` in Domain.Shared or Domain. +- Setting names must follow `Abp.[ModuleName].[SettingName]` convention. +- Define features in `*FeatureDefinitionProvider` in Domain.Shared. +- Feature names must follow `[ModuleName].[FeatureName]` convention. +- Use constants for setting/feature names; never hardcode strings. + +--- + +## Permissions + +- Define permissions in `*PermissionDefinitionProvider` in Application.Contracts. +- Permission names must follow `[ModuleName].[Permission]` convention. +- Use constants for permission names (e.g., `IdentityPermissions.Users.Create`). +- Group related permissions logically. + +--- + +## Event Bus & Distributed Events + +- Use `ILocalEventBus` for intra-module communication within the same process. +- Use `IDistributedEventBus` for cross-module or cross-service communication. +- Define Event Transfer Objects (ETOs) in Domain.Shared for distributed events. +- ETO naming: `[EntityName][Action]Eto` (e.g., `UserCreatedEto`, `OrderCompletedEto`). +- Event handlers belong in the Application layer. +- ETOs should be simple, serializable, and contain only primitive types or nested ETOs. + +--- + +## Testing + +- Unit tests: `*.Tests` projects for isolated logic testing with mocked dependencies. +- Integration tests: `*.EntityFrameworkCore.Tests` / `*.MongoDB.Tests` for repository and DB tests. +- Use `AbpIntegratedTest` or `AbpApplicationTestBase` base classes. +- Test modules should use `[DependsOn]` on the module under test. +- Use `Shouldly` assertions (ABP convention). +- Test both EF Core and MongoDB implementations when the module supports both. +- Include tests for permission checks, validation, and edge cases. +- Name test methods: `MethodName_Scenario_ExpectedResult` or `Should_ExpectedBehavior_When_Condition`. + +--- + +## Contribution Discipline (PR / Issues / Tests) + +- Before significant changes, align via GitHub issue/discussion. + +### PRs +- Keep changes scoped and reviewable. +- Add/update unit/integration tests relevant to the change. +- Build and run tests for the impacted area when possible. + +### Localization +- Prefer the `abp translate` workflow for adding missing translations (generate `abp-translation.json`, fill, apply, then PR). + +--- + +## Review Checklist + +- [ ] Layer dependencies respected (no forbidden references). +- [ ] No `IQueryable` leaking into public repository contracts. +- [ ] Entities maintain invariants; Guid id generation not inside constructors. +- [ ] Repositories follow async + CancellationToken + includeDetails conventions. +- [ ] No web types in application services. +- [ ] DTOs in contracts, validated, minimal, no logic. +- [ ] EF/Mongo integration follows context + mapping + repository patterns. +- [ ] Public members are `virtual` for extensibility. +- [ ] Backward compatibility maintained; no breaking changes without deprecation. +- [ ] Minimal diff; no unnecessary API surface expansion. From 75ec207f889ac0dd4137f0c531bdad5e83cf9bbc Mon Sep 17 00:00:00 2001 From: Ma Liming Date: Wed, 7 Jan 2026 14:50:55 +0800 Subject: [PATCH 21/30] Add instructions for reviewing pull requests --- .github/pr-review.instructions.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/pr-review.instructions.md diff --git a/.github/pr-review.instructions.md b/.github/pr-review.instructions.md new file mode 100644 index 0000000000..6f3045f27d --- /dev/null +++ b/.github/pr-review.instructions.md @@ -0,0 +1,2 @@ +When reviewing pull requests, skip and ignore any pull request whose source branch name starts with "auto-merge/". +Please don't make any comments, suggestions, summaries, or reviews for these pull requests. From ed39030ddb620e22061ba7100339a81be73cef37 Mon Sep 17 00:00:00 2001 From: Ma Liming Date: Wed, 7 Jan 2026 14:54:26 +0800 Subject: [PATCH 22/30] Revise PR review instructions for clarity and scope Updated instructions for reviewing pull requests to clarify the scope and expectations for human-authored changes. --- .github/pr-review.instructions.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/pr-review.instructions.md b/.github/pr-review.instructions.md index 6f3045f27d..49abc514c4 100644 --- a/.github/pr-review.instructions.md +++ b/.github/pr-review.instructions.md @@ -1,2 +1,18 @@ -When reviewing pull requests, skip and ignore any pull request whose source branch name starts with "auto-merge/". -Please don't make any comments, suggestions, summaries, or reviews for these pull requests. +# Pull Request Review – GitHub Copilot Instructions + +> **Scope**: Pull request reviews in this repository. +> +> **Goal**: Avoid generating noise on machine-generated or automatically merged pull requests, and focus reviews on human-authored changes that require design or code feedback. + +--- + +## Review Scope Filtering + +- When reviewing pull requests, **skip and ignore any pull request whose source branch name starts with `auto-merge/`**. +- Do **not** generate review comments, suggestions, summaries, or approvals for these pull requests. +- Treat these pull requests as machine-generated and out of scope for AI review. + +## Review Expectations (Applicable PRs Only) + +- Only review pull requests created from human-authored feature, bugfix, or maintenance branches. +- Provide feedback that focuses on correctness, design quality, maintainability, and alignment with existing repository conventions. From 2cad3cd18684d392c74a6aaf4d5132efaf59d7b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E8=8D=A3=E5=8D=8E?= Date: Thu, 8 Jan 2026 15:13:47 +0800 Subject: [PATCH 23/30] Fix Quartz Cron expression for Excel file cleanup options --- docs/en/modules/audit-logging-pro.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/modules/audit-logging-pro.md b/docs/en/modules/audit-logging-pro.md index e64b39dc66..d86e0b7de2 100644 --- a/docs/en/modules/audit-logging-pro.md +++ b/docs/en/modules/audit-logging-pro.md @@ -143,7 +143,7 @@ Configure(options => // The Hangfire Cron expression is different from the Quartz Cron expression, Please refer to the following links: // https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/crontriggers.html#cron-expressions // https://docs.hangfire.io/en/latest/background-methods/performing-recurrent-tasks.html - options.ExcelFileCleanupOptions.CronExpression = "0 23 * * *"; // Quartz Cron expression is "0 23 * * * ?" + options.ExcelFileCleanupOptions.CronExpression = "0 23 * * *"; // Quartz Cron expression is "0 0 23 * * ?" }); ``` @@ -164,7 +164,7 @@ Configure(options => // The Hangfire Cron expression is different from the Quartz Cron expression, Please refer to the following links: // https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/crontriggers.html#cron-expressions // https://docs.hangfire.io/en/latest/background-methods/performing-recurrent-tasks.html - options.ExcelFileCleanupOptions.CronExpression = "0 23 * * *"; // Quartz Cron expression is "0 23 * * * ?" + options.ExcelFileCleanupOptions.CronExpression = "0 23 * * *"; // Quartz Cron expression is "0 0 23 * * ?" }); ``` From 4646c9f551ed940447401bac9edc7cc1a01f76ca Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Fri, 9 Jan 2026 10:03:17 +0300 Subject: [PATCH 24/30] Update version header to include a checkmark --- .../2025-12-18-Announcement-AIMAnagement/post.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/Community-Articles/2025-12-18-Announcement-AIMAnagement/post.md b/docs/en/Community-Articles/2025-12-18-Announcement-AIMAnagement/post.md index 80c4a8fbeb..4eb82e0f9e 100644 --- a/docs/en/Community-Articles/2025-12-18-Announcement-AIMAnagement/post.md +++ b/docs/en/Community-Articles/2025-12-18-Announcement-AIMAnagement/post.md @@ -76,7 +76,7 @@ Installation is straightforward using the [ABP Studio](https://abp.io/studio). Y - Client Components - Integration to Startup Templates -### v10.1 +### v10.1 ✅ - Blazor UI - Angular UI - Resource based authorization on Workspaces @@ -103,4 +103,4 @@ The AI Management Module is available now for ABP Team and higher license holder --- -*The AI Management Module is currently in preview. We're excited to hear your feedback as we continue to improve and add new features!* \ No newline at end of file +*The AI Management Module is currently in preview. We're excited to hear your feedback as we continue to improve and add new features!* From 9e1b56451375554459d6a7c57d9678b4eacc65fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0smail=20=C3=87A=C4=9EDA=C5=9E?= Date: Fri, 9 Jan 2026 12:48:28 +0300 Subject: [PATCH 25/30] I also need to update the Introduction paragraph to mention the AI Assistant feature: Done! I've updated `docs\en\studio\overview.md` with the AI Assistant documentation. Here's a summary of changes: **Changes made:** 1. **SEO description** - Added "AI Assistant" to the feature list 2. **Introduction** - Added "AI Assistant" to the key components list 3. **Sidebar section** - Added AI Assistant link to the sidebar description 4. **New AI Assistant section** - Added comprehensive documentation including: - Description of the chat interface - Key features (Chat, File Attachments, Recent Chats, Credits System) - Privacy Notice reminder - Screenshot reference 5. **Screenshot** - Copied the provided image to `docs\en\studio\images\overview\ai-assistant.png` --- .../studio/images/overview/ai-assistant.png | Bin 0 -> 79051 bytes docs/en/studio/overview.md | 21 +++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 docs/en/studio/images/overview/ai-assistant.png diff --git a/docs/en/studio/images/overview/ai-assistant.png b/docs/en/studio/images/overview/ai-assistant.png new file mode 100644 index 0000000000000000000000000000000000000000..8289997ce145bb0af8be84322aef4d271246c9a2 GIT binary patch literal 79051 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�Re$~vpvt>u+^n(gLsm5aL5 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 ThPd-3pa_GftDnm{r-UW|Hl^ly literal 0 HcmV?d00001 diff --git a/docs/en/studio/overview.md b/docs/en/studio/overview.md index 3f5835c998..e04e3b6b5e 100644 --- a/docs/en/studio/overview.md +++ b/docs/en/studio/overview.md @@ -1,7 +1,7 @@ ```json //[doc-seo] { - "Description": "Explore ABP Studio's key features like Solution Explorer and Kubernetes Integration to optimize your ABP application development and management." + "Description": "Explore ABP Studio's key features like Solution Explorer, Kubernetes Integration, and AI Assistant to optimize your ABP application development and management." } ``` @@ -19,7 +19,7 @@ ## Introduction -ABP Studio, a comprehensive desktop application, offers a wide range of features and functionalities tailored to streamline the development and management of ABP-based applications. This article provides an overview of the key components of ABP Studio, including the Solution Explorer, Solution Runner, Kubernetes Integration, Application Monitoring Area, Background Tasks, Notifications and Logs. Understanding these components is essential for efficiently utilizing ABP Studio to its full potential. +ABP Studio, a comprehensive desktop application, offers a wide range of features and functionalities tailored to streamline the development and management of ABP-based applications. This article provides an overview of the key components of ABP Studio, including the Solution Explorer, Solution Runner, Kubernetes Integration, AI Assistant, Application Monitoring Area, Background Tasks, Notifications and Logs. Understanding these components is essential for efficiently utilizing ABP Studio to its full potential. ![overview-intro](./images/overview/overview-intro.png) @@ -69,7 +69,7 @@ The Welcome Screen is the initial user interface users encounter upon starting A ## Sidebar -Located on the left side of the interface, the Left Area is a panel that provides quick access to various functionalities like [Solution Explorer](./solution-explorer.md), [Solution Runner](./running-applications.md), and Kubernetes Integration. Now let's examine each item. +Located on the left side of the interface, the Left Area is a panel that provides quick access to various functionalities like [Solution Explorer](./solution-explorer.md), [Solution Runner](./running-applications.md), Kubernetes Integration, and [AI Assistant](#ai-assistant). Now let's examine each item. ### Solution Explorer @@ -99,6 +99,21 @@ This pane is dedicated to managing Kubernetes services. It simplifies the proces ![kubernetes-integration-kubernetes-pane](./images/overview/kubernetes-integration-kubernetes.png) +### AI Assistant + +The AI Assistant is an integrated chat interface within ABP Studio that provides intelligent assistance for ABP-related questions and development tasks. You can access it from the left sidebar by clicking the AI icon. + +![ai-assistant](./images/overview/ai-assistant.png) + +Key features of the AI Assistant include: + +- **Chat Interface**: Ask anything about ABP Framework, and get instant, context-aware responses to help with your development workflow. +- **File Attachments**: Attach files to your conversations to get more specific assistance with your code or configuration. +- **Recent Chats**: Access your conversation history to continue previous discussions or reference past solutions. +- **Credits System**: The AI Assistant uses a credits-based system, with usage tracked and displayed in the interface. + +> **Note**: Review the Privacy Notice available in the AI Assistant panel to understand how your data is handled. + ## Application Monitoring Area The [Application Monitoring Area](./monitoring-applications.md) is a functional component within ABP Studio that displays real-time operational status and metrics for applications. It indicates the active state of applications, the count of running instances, and so on. The user interface is organized into several tabs: Overall, Browse, HTTP Requests, Events, Exceptions, and Logs. Now let's examine the **Overall** tab. From 4a1587bbb0c7b7529aa47f0ca475c752fc9aae00 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 09:51:00 +0000 Subject: [PATCH 26/30] Optimised images with calibre/image-actions --- .../studio/images/overview/ai-assistant.png | Bin 79051 -> 19658 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/en/studio/images/overview/ai-assistant.png b/docs/en/studio/images/overview/ai-assistant.png index 8289997ce145bb0af8be84322aef4d271246c9a2..92c33d9aaaa285727b1d4626ad2415caa7673923 100644 GIT binary patch literal 19658 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 literal 79051 zcmY&wJfo8_s3K3 z`+onOYp>s#bI+WaIdkUR8>*rtgN;doiGqTHE&EAQ4Fv@q9|h%U4+iSvFVpvGXecPJ zP-G>=HNHMUEG+QCAeGcR-sjjn%T)wo_3M74rC;=)8y8tl;7s{qJ22qk z?qWGdCfa^+?!t6AmIXH*aesFt;SX!0gsA!@G~eE?f^y8BUk_l8q|Zx({AazC;LB8X zdba^(8uHVp^8_K6-0t3v@cQF5kUrBxCm$rCb=KZo=K4bWAT!E_4s>r`=C|RM?F=qz zzJ^fV)a0mtd2rCCNJtv@ka}URu`}^n7q7qXO%pV2)5R*t=RJ}Z-&VjwsZnA78wt@_ zNse+k3ygNe`2mOh0rG%5-@ppQDStcVKTX)5HdK@*l7F6O_nVONSL5)USMWVBed=Hy z{-Clsm@-szJ=R9qu*RdYSc8u1bl+Rn~BtfY@F_$W~$XROwJc z(okB7w1_JB{qFOtg-f^!F&!+s@VTg}J~N1}%du94I%X)vJ)vY%-${Q*OS)sPn$&&R znbJKItOj6SbcW<4auncPalPL>!EI8y$7s_W4A_lIFSBuQ6}()WxNU1oirz@=tLNcA zc|N;G2o7s*UM^*`d#&$JpJaNWl(`wqAnzBwXrBVBSnf5x|59^HG%q=ZWj?z(S))h3 zvy~KEO*N}!$Djck9~?@cY`k@VoSs_jr1o!wQfljdi1Q1SK2I&;Zkd>Sny!@^U20j} z?Jh@NOs1JaH54E+yxMzQ3vixRfIC(PSK~Zn!BwzDRqGc{UFHi*O*UCI!zOGDtH_Jl zVm7uIzNIEtd+Xx)8SJEtFh;8@HDd0lndiAO*1vusr)>@^o&w&Cb;`mKE4&`OGkq?> z$90YXy<6o)Yj$P4y%}Y{gmi>8$XT$^_Y;>Uj-&u@+4r4+ILxS0pJ_rq8d5Oj&&BHW zN)W8C()6_*z&z6q``RRl)A?#Ag;#wlQ$p}Jn=y_X_^vNO@!Lc9`QY?g>*O$w5Fwl9 zIbK&(?{Hu$g^+SLcYkY9N;yA65OIKH0aZG>6zN`>;VxM)s|HI^mD!{W70(Oz*6Dd#DZ}4%` z_|1o4K6=XJ-pK7vL;BM%=2EpVlADRVu(+CjueVV{FhThT)P&_U=OGTln87eb&#|l3 zB5`&N22hdU8eNUFITu5n!%`|wR#Qh|u_N(%D(GFsFyiX71`b_v!Qp+s0mIeNhjpY9 zK<`=$ij2K0s8~n+(S*6e@0#E+QIT2N`o?ZrOOUzggTOCj47e2$xnZ8NXS!_`bZF6GQ%Gj3Gx9`|JjJ&|0~)L=y>Y%tXALYiH1 zPv+|E_?WLurW`TAZx14X@{=ln3eFvNmlR^5xVr4Ntxi`Lz{}%%tPN}rjw?u0wR6Ei zlH*h00`$u#|9+0Gt{DVWlUMb?m@7t3I?rFXB;^N8wa|bo!~*C&v>{#ak)%9c`UG2| zFc7C`vVg)&(X1w$9=YQTR!()`@0GSB%F~h8_keTj5|?cXUT;9x@+V7yIh%Wob>wwl z)=N7QR@I`j20WB+sOz1}N%X}tRQbNhi>6eX?T)nQ*E%e$rCQJPz1&quIgf0+^_+l+ zq+0k+=xDWC&LtUmhM%`s?!jt(OoSlKh)TG3;ar@mNTZEG&@f(-iUc3S1dC5_1OS=MQulr^xqvE;0{!?ciTa8Vzs`8K)ww7uid&Y!9XA00|} z`rHnNF0v11LrYt(hw1!BTkZMmcuB{L{q+-V8~u$rzJ4m(S0Pl#*H|}>GBCqvqfRX( ztk%TD+q^Gkd;Xl2d@)?8-z`r7Z9dIQt7LsF>uzhA??|H3uqyp9Z@c#f>(Fd>y>X~mpPf$uL>d_C)2^ikx4ALmiN~wBvYDJ znWkzY6DWe>*1vL(+#v>ODm;jTzQ?P&c3Dgk&ISshFZ@X$@0TuskBUw5WM(yUGii6Q zOP={0A-5{*!pw^Nxu8?6Su$^P``L9RYE#g2Sf!P4Uq=3%rUTx{)E8YX zabk+HJJ;2q5C^Wm4-ziWugp!J9Gwf zXh^9G{du7EcKsZ(qD7nfVxC6`Q(JcrS!v3ojM;?gPQP%fA^2sWsLcHZ!DiZHZt~}x z?HjnZUZYBV3J~=oXSP?8psho&G zSZgQFf~)&f7>W&QOXe8n(^B7R_nr@GTMQ7CsH=EbeH^;NycMe*c%Lv70|bpM%@S94 zj^X{<;dy$20UrPX?2aFU0KzEn0mN7`mXLs_GaI7Umk(~klQ}a7WD6psgW9l36#*r; zs01Gj5IF=7?-_^D^@nR1{ZO}?S^PA3Er;0*S_M%pQ)}GJ)YXv3ITKoZukPL*D;H1#fxPMYrTj2`C zHDNb^q<_aFpN0K*?x~BzU+HSQ2pQ8Bm!)-^XDGT;qfOp#Gg*WD7|v$N@wFkv%F>lr zHC5Ua^8GPgg(%fFv60i^+rK0DfkL#QmR9nq(@)URq(Zoia#=z(>i|{vYR;hzS%17T zOaHwc?n-ivvj*i--o!(eou~Rv2 z$#=01G!@eS|0A&|)cgiSS`Idj*ss7BpP~QXqTgE46wao&u7e(dsC<6H`+os_3&4E$ zm2VX9hqX8gDxEq?P4NGsio$0!i;?>t;QXeIzp+`lSOAbystVknM>+ z@L%Q{r9gevE8Y$pW4=g`5Ng3!zV;gg+<5FF!Tk)%Z%a;MhCiZ>2I68;oCU zXrjqtgS&;Awws1cetaT^ai@ZH@;C087s%NXnI85`$V09Az_k+GZg6m@T5%*(0Akan zta0^25&x+~dm(zZsk)0?SM&Zfa84~y!Ku2tLE|I5Os{^7am2hnrsJffu&~Zn%a>K>9b;|5#4N8I5z|4b$PZ;$$Kqu`R;K-qyjE>W!Xm^80fA&aSSp zxqh|JHWt=Q&ShI>=2VyzK^(dE`Lp5Md3hu4vLquiqG>;hC$F+h_z5GXO>_8J!Mp`6)<;l<3yKhkf(O*dZ3 z8d}sSYqBt(-o&3M%~VQ^nkV~>+UzeR`N=8Miu(QPFF+Ia_v!&Z5K@4Vf|W+!kSKi! zutKxxyrs#5$!%-a`r!VlF)o=T)oFGR)-k}x{(X(Jkk0pL;$5iRAydaB)xQD5sSxme zXlJYHE}Bx54-Z2DO*c`CI%{-L;j;zvkl!6IE3@!1?4)u&I#UE2%W^PTp=fgQPhgZq zidh>5wuqORafkFrpa_PUES4WAt?WJS-Ww^90S?nn!iK{B0atzwou`u&r^mm@l@ki$ zzMtz?u3Twq;4gaSb20i+2s^gJ<%!Z+v~p|xmnr7z>ezVvJ3b250(UKn)ngJ)H+3Bg zsk>;ooM!HCt!w<>oV{MHIr2gCol%bK4i0WL35oTn;EwN|2@9#@W!KBP*D$sv zG@NJ3kS5Ts+;PK%$jm5JheLnzJB96ze?Jqio1&M#fyVRF@rjpGv|7-1(d8Er71e?y zMP7t4gTrB`g&{NSi(Z67=ZSP6i{c3C0Xem$uI?iyE;@O_X9ZFsmou5R39p=Jr~`_b z<*j5y;Le{~OmMt)t(~0ukZ5jXBcJ3xBM6h?verLW5r|;C91NIR0jM>(vM(A-vmH<9 z=7lOW!L6Gy zql?XygwC#OVEWFP0iRL8sTuGP8#PaVW&a21c*M)@NewVn->_MA9_&rkpVrbJMc?)T zmOSI92_8;P4yHWYOF>1tkdz`?LuSQzz2sC*dfgOVUB6aQ&ns(V{^I(dA6m8?x>`c0 z8^7n-GQbF&57{aJTBX8k6<&0CqF(IYHh2S78ATti?Z2`<08osXn2!Cd)DoFG53GST zHMHBsQ+3Q6O=8FF%-cEi8Fhw%FKI$O9nXU1KzuWb{Uy(-RF53i8Cx(SrNr9f}y)a4t=)Jl>%mE<&5T5g`K$|4zUG^^iy_w~(emK}+ql|frN z^vqDUcC15O zJc+|N_a!-rhy`-)eMxr|sq)o862}-yG-&PvpgBxe`$Vx zVE#spk!Yei=-}jx{t#y!Ha#_Cyf+Q;q4%ZtnP)!k;DP1T`?N$YoL>8$Dasr*rmFTk z+E#B|NWD)KVt9gMmU$4DO({vorBM1nH-~+a z1xM?4_BHPcx*i*EZmw8TZ2}CCvyOIWQ82)o1=X`uWfCVB0~|76c_EmE0!IwJ=r_V= z%Pu3^I7)WlHyd@`9Aw*-ms8}F=UaIzI$bt}4L{?3@HvbH8k|v_3OQuQ`w;XME^+<+ zz2d(#F#asGkZf)wc)*Zl)CtVs3`E!P?-H)MHMSLet3 zcbkj;Mn~(lLGnq_?{>to(ll0yT+5W*?MPQP%TgE?(r1U}|NUY)EUMmkN@-9#463h0_K zeU%8Pjp!Xm3Vlj)lSdI6Z;8|M8&XOQdDyPIQZ=CG{`bOykt01aDkc12et$B9lBr_p zBCYUlN;;`FQ``au8QX=IT8X(mt+`p5A<}f8-Ug>ft*OkVuJ9MdHT14nQYlj#byca> z)3P&DbAI@6NjJl$ufNW6rij13qqS)sVevAKfaq>K%f5z$AU%CyA+@22do2N%P{ekS zxM>F!pG`S~(|yrFf7)R|?y-Ie^#V0tD4dj2SBQ^sXO`(ynM_y=DWwZ@`FH#V;SmB; z>NB|U`8<%9vTADMd-L^RUTd$s_70xqdXrRqCnkftKAK_w5w(gLrAR$jpqBbdo5%%e zbC{p=fF!yR9`6xy_80V^Q_N8L=~^yrQCY8=3{jC9=o(c%>)nnyzA<(2cvW~4miH70 z-)}qg_=@p)6BkGYI|_i&lslpeuWcLeXG_u|35LW@P&93L|!yv~Nl zzem4kG>F~H?YqkoG^l?$*_ozA zu;uDt^jHzbF~mly+FIT+&s=w?2`ilrcRpP`aetwCG;d<$KKOlMqR8v zZ6%(9#|!UpJorm!p)bEvfub#mHs18F@2--w-Jw#uTyj9|>cC1_Twq*X^#L$;sztM+ zS;Yr@v(Tz9_T@^q>giHLOB0EHh9ilghf5m-?UDx9-UB;|Gfa}W0wNx}hWmcpGwkR4 z`vL_;$?nzTABP{fE%(J^OY{0-mD4KJa}9S@H-7Pa*bBN&$1uJc>PiU@=nnr+xyc349)BnneSc7iOND3MTmYYF|(X6 z4eOrx8@5;vaGVs)_r5IWWZJmh5db`VOfS_hMFxr_>}u=%oPl1s_-D!5>l_Z)XId4N zLVOx>dyIsdX_A|aco&wV<5PUL#!&gwMeDbdK$C&KQ|(bg&5%+{pC4k@cwg_X;i-siR$JP?s2<{%v<_xD_vpVCT$w zcmI`}$sPdLd?uiy>edBcQ`Y+~u!+?y`O)hrQZT=({iwAvj^4&`;GDhD<3suFAb65&d3;fb*JOzM}dSeCu))OAwj z7|?VzGY+Bg#B<$SB~|YUvsXILuXtK65~0c(P?DPd%4wdTc4)3B&ek(LAx5A7cSBP} zZDBjwZWv`FXvA;Z1MnS~Qv&+?l4P=$YnfUWa$v?`w$-fbbV%c~h1}~`#*CE}YD0Yxlp_MS znB_pP13ED$Zm9u$o|B5{$C|S!upevE;fa!c??i#m3;_#N#ZH0BwFjJ$72c9L1ek}l zu#sb8dX4AGPHb%ChOx8P>bK*2#oxeF>g%uNvk6)x4TPw-M4H3BJ%wT=K#;rZ>5InK zpGL^T*3pN}q=Z{Evvl_`vjvC1OFp@8$`=Kyo!|}$)8$8QWK&HPK@huT*|FRCjDxR~ zhZayzXq9<2N4>K2lz*cOjKlWf=p5TOzD-uQNy7zVPPR}yQ>eKFmi5}&JGfT_MDO5C zf=|8*r;5}K5?Sf zdU+-ixcZq*)jhA<_IaT){rQ)@-uwpw=esIF99pn$$>8MqV&j+(nm`gKuZt9Ku22I< zt1s5p9h{m_<%j+sMN7VewVC-I*Ius#>aFoF)XRuPNETeB78KHM3`HU21bd0!tk49`>0 zvXIU%DtbrrUILt$!ZtJ*XJHYR7wQDA$DcZ_c81!s?!qi}NH2D9sGsxC9KSSNnJR>D zwuktFyf3zQHhYa4&i7SE04h!cI%TF^Q4%E;tu`|XOIoU>8BT>NZ0~O0$K!v!63=;p zJWK5z&eKZjSgal%s93IjMqs6^(4q_ynXe{@&%T=FVp-kW4I^NwWRbyE1t?NH=G@qQIlp?QmS`hw6wOj-kq!T zd@oePD(#`@qW_#UD=6}Zgs%RRBumDx?7LRL^0xsak|K@1>x6X1%Sl>_HXzG_ZpzUr zMP<2H0<$nZOxAQ;J{t}3KRuBCuqeuz5)%iuN>|4+-Oh9KQnxd`D6PbgEJ=EGX{Jlf zIS)1onoLRQuSl)oYQuUe!+C21Gfq3{FioHu)z~ru>acQ>Ig_gqi|tA=W_Wj@MdRWZ|3XLCf1nKeXysZS806C)c0fk0#zEZG_w8iajG z6zI@p*(3GbfCKqn)iZBP_?5iyKu5Q?l=u_J+kc*J>eAkDTb?5orD6$*Ym zSvpr^vHi&<1_9uelyD?qz%ntvFel(_!CQNH<+G6YR zr6Sm)7%X1jXJPV zM+tu2*Z{q3i!ghW{d>KnDIux;ec6hd6d6TcT3eyfLC5!SmL&E??{5C_+Ui^WqT_4y z`|=M1$VU5({yOEacyuq?YU6K$(Agd?XmFE9`AG`S*S)4n8~bay+)Ad!$W9We{pZBL zi*4ieVq!IzwmhYNB7JS%mE` z&C?j>D2>MpMT<=CoL)3|?}Z+VLQ3z~?;cE-X@$*qCdy= z72+^1Q`24iRsRdmQr_^_lcZJd8l;z8=JEga0`!CR9d2;U6&r18mlDZ`9G+SI>3E5E zelencRcCi%dhm<^ec5**-{0ekmd*Bje@XbqF7=4wzE>1b*aq`bwEw-84*zC)BD4Q1 zeT;->Ph8Y_^q=cz<&FM{aALMy&T{HhI@{H(u8gClvow>-F_M(*VF<6Osj22xMEh;W zL}P}Q_;^1l;$#W$OR$i4@xQT;nu%m5*fLS8ckq0EH5D#q)%SD4*?(5sankce>5`jP z@TAeZ&qgxnvJMon#zMf`PDI`ZHFvm9rTCP$WimIrJu!#z1W)0q+hhqQ0TRX_B&+s) zc4>G{(rBiC*eER~_gEt{?s7US0$U$cVq#;8coiS>8>3&l!ji;qulV53vLY^-+x??D zeF)f~rkS6>hRYw?-Vva(oP>-sc+>4g;8rC!I|1w4?#Zg}ySrQUI})j|#`ktJ!xJrGzFMyB?;o~! zTVF$lqqGF_Ou8HQT_XRA-1psiZAdw-O_Y2HisFkzmLn|+D8~alcH66qTBu{=O60E8)P!L;V zF|t+cb)#)rl7^L+g?rGOb5v)EO8gKKy)l;TL+@^%0W3B?0WG4j8CRGlvA4|gx_PiT zl#G>Wuv$=>eGEB%KX<-6!8i-h5!we?6afY&n<{HP3THQ2_)HMm{1(Hv9AImloTc1v z-Vhv6T<#llnkw49uBF5(`srF;_++m2o9+%Rb2If2FW>y(RGt!V^6Qp%@W6q0o3{Nx zhE*(`e=$s8a+f>dF-+@ki+B*VPMS)fp%%ZSgf2fsYuwseGB<>pJsKNLE>q0x8|Aw0 z1&#Zv`EFg&Hqqh7t5@fjVEYF(aLZcH^ntrw<84%#lri>AM7n6kcmf@ZMTC`3qTHl< z3Yem$NBP0CYg`t{Gh8<3#Up*^$nJe$B0=ne1!{VV%1~fKa+Z@L!Mmda=7}9U?qjl< zA!qa-IeCkzeq|yL!$WT?uuVPPr^9pddwyplmYA3s<;ZLK6<^;4Yl^R2>72O~lV8D}{MKRdA;(XGF5{X|`7KC|on^F|R3uwVq}*8AlqUg|S)Vzr;|fo%FD z4a9IM2veFg6jj^zBEcGz@h?PuvQFSwhEzMEk-6c{-gFb)Uuaf!)7LMlJ++gS{ypyz zkuv&wX>({k*`qI%nq|OFeWH}L$ePeJ(b|Tgwu%$)#o*vTBIvGIDM^KWCK6hVXp8VK z@AvR^`ZMuU5X8diYKRzxWFF61FHBw;zgKDW8}JCxBPb(h;a06;8lTmE0Wu_S5EB=R z^{4kPVkuGZ*ddm$WAKYL_sT#180kk4Yj4|O1P@KKeZMa>&CGku+}#=C~}A7-JRctN8g-e>vw zdqS?cz}NKRb=MiV4$s1+sKc6AQ+fwj_->@D?uho~Nyxs|JGUL=_pMEZ8rS*mP_lZb zWM<1aY3O{_wG0u?sV$V3_ZMRDUi2_P+PcW-pWg?$z%9PuUg8FPrp;;*S=0O|3?p%Nu@c|~*_%{l1IC!FZ%`#KckBN50#`h$>oC^DsvkgG!J0dkhz z9cf8Fbun(Jn{s)TC(j|W+t}Lk(+V>nj`fxMG6UcFI5Sj4ejCvlJ#la?GJYg)e=Z74 z_WvZ@rg7auki=*O$I^g|UslFc>fB!|sz79Clr(>vz&KU&Ff&2iw|E#$NePVM73t}= z+*L=+BQLjOV@F^l%*J|dk6m`|_l76L!o{w+JhuA7Qwv4w{HH8WJ;B26UJt>79MqZ3 z35uz^+lC51a~kCqMhJ=>SXn$zDHve~JvwsQNZ8@UU^ zyR<6&swCyL`G?Y51|2w)vhG!Me@=b_k2 z3Ie((_D8vEffHihRaa>kRoZBW;yT0&1`iSdpoR^0#;HI)Vx6-!$n+^iy{SUT~R{Txy$ zWNMpTXaBNR>0#nI@pSnie@0h8t4FFApYukB6C+7|IbwhZVFS3Y3ToTi+1A>f z`*1|M(nowDo5$IwX=t{&uOQXFhnl!J)~Vi+ZZ#jP^Kn=WXI*(bB`d#!T8%l_A~A+FmFZG*<1>}BiqEsJRVtw2)$DaExejy*BC`h6)87JFLp zGv%Hs??zj$C-yqE(?U7(hx_a#fMiYRSIidwo1pZKYIRwX_zl61XQ+7+VkC|iUa)&W zAE57QrcOR&y7}gv*i9T0Q#(g{RdGqbzNHK5n@^oe573?@{|Y;;!S#?>xAcvR{CGTd zb0W@RO>notvFGo$Y@zLpdj1;|vz>9sm)(42r8{4a;BuO+Irhz^Sa~Wize9r{A_}ld z<7pkSeBa5H>0W3nJG4y0w7EH*Utd(8bZhUa*So42juYZIF}5RebV_{iiGt;RY1QAy z#vN!VTN0Q3j_q?#sj+fT%(tROQvt*y--!5S6JeXFQMGoEW!L4NLFHN0w1}$|(QyRu zM-(uF4B*2M+~#qzzqE|V+&1#pBLM(BE>BjrYAqoU8F6}j zw&?P^_YA6<7i*idpTER{7=*bcwdHCE(2j)doKkGKBKXg}?8Y{4zUzTGrebH*{gRB6 z*bSX%N3(7=oc57tr0b(T8XQ;$AlEyH(>6D>ob*mpq@m>mt%O$7}c$ z-*Q`RcReD;Rn8{MG!@_^^wAGye%#w4{mUc?bDuRaM2(B-J$}dZbKW+d)Qe+NG~Ye$DOlH=*ouvybN36fCZ38>lpY ztOSbBUe1?Y7HNCf0$}#<74x|$`fs`1b+av(cq|L@mKiN2$cFiyzC)5<>^_6w`VdEd zla7d+|IG|-@DXtwg_P;DfMLp+9>Y7;5}4`!4fTZs(fQg(15c48 zrIV2Nc|8Q!^&0mMV-2Di$n~@@cBxV8QT?{IK19&3ej@%$(L4+a=>TslZK5n>+=>bQ}NyRYspB$m+Q)=@8&ZJ6^Vu1`lC;_FHlj7 zuQQ6|4)QuhKvWGyy=9Amo`4Zbey6eDls$D$czsa*vXv!|&EEnw-9vi32Tc_VS0zS5 zBVRfjA^A9G*TZT2wmS`dCzk$;5Fe$9xHHfVf3>F2fE;F)Jda<(0b=BSzr@RxqVSP+ z#yFC!?MNxS+9R-T!K(McsnRaaPN4O#c5^P(6Hx+@-9+{Bx#}p(iRT`fqYq1VDI-v( zTYaEq8Pt5~p=k+pd_rbi-r0LlB6WgwV#Z+RF~K_pS7vONUDkb5w~X>#jc+bU)th@vY&?dOEFT4A^QX!LmLb=kC=v zDF7Hrd7o-H&^xVBx}*jddA#RcIOy{tdGeaB;8Q#T?QP3m=i8-={Z-|b6I*vK`NbTW zq+AXnk;I>gQ1euN$xbA`-$P1T%QD5PZigOJtHtLMn^x85H5W-K+&a{QUols zSKs!EU?*WX-CF`CAgfh8Rde}X>;zt~k1M;h?oCW9I(@OU(Qlb}bde!qe)nPIql*;% zF`iNN1zj;d(@agCLxH*nDxCqgCkG>c8^cxQlLXlWA-1cqeBU06Is;b41{%Wh{r&Qf z>@1ZfOAWvtz9GFj+vVZKA@O$YR|4uKd_%$_`t>CUlc9&`+?xDK6P}B(<0ovhTIqtm zYMmLodJw0=v28r&jWF9E*pdao~?f%azfy;Up_x@GN#IkdIzWLjGElf$c79_>uppP6VsE+ zQZ0J~4?GqX$Z3dwecMs~xHOUDJuKpNu2<{WF~yREcxdj;%a*^vA!jTzsIxdp1TdE8 zOV+0YfdtXW<{MT=?0|Cu?SLE3g=g2s>HDu0(7zk_5jq3(G{9GHr7kwY%}}?{Q0c_K ztrga(3npkvoW2qLuNRPBY##66N4^|*e*tmexz_F?-zLD4@zo58u7Vxk-&P9sq8pm|{yu6!` zCn7Jt=jZmoOu0dw``$yoN0IJ?gkPcDLEWv8Uy%`vep%sud9JQRtJJX7wblef@SU}9+P_DoUxWymdUliKCz-n8qZU}K)%C3_y@L8v7LRBwPR%|fSt?J~j9fvRQ z3QpB-XxZ#?d9t-hmq$6zOhPPXw&nc|tE`s72ms3ahq1`tQN{z~nS|w|ErcXV#_08+ zHh+DUsv&J2txD9k>RwH~{%4vvJ);$jAL#5;(BhIPuxX!vvD`0=vAj4$4;W|oOjTW7 zGR-;go4WHOEfg7+XB^vetvlH zkJ9JyI$K;8+phHKI5X|iBdf8EVfC^ z*;^rgS=GxHM7c~miSGiMDW2-d>mHtDkC}GwFV|nhN9{3&DJd!Wo$X95-&~9hA(}kw zC$a*5JhOfO@d?_yU;a-bPy$R|alRkfCIX_0iv}04O!-VQn}O00`LL{==ZjVp6IC24 z3$ZF9It_LB=-s zioc-+WaC!?&!=XjZeZ(D0z#*n38-6G|1cL1{ZO-??Xi-vU?BJGM4gL9e_u zN#l}NmJRn#I;Cbj-7A^dq>kkkitis%;9kx%_gjF>-$aH^2Q$5^bSvIJS8eGYD%RQc z{a#%K=5-|4@nwB^NQI7xCp_W90&P%9YxdlwB;qj6ktBw$Dg?P=yg-#={mlF-6isS@ z0s7-x2HI#@2j=YL_HlYae1{S)-VYSCuutuS5IY!s`oQd*Bw~iE;rTxD zH(z~g4iF{d9BzCHLC%vC}PQnWmkF)@; zqCFz|CH5!->rYVszqHXitj}nVzcQjJ1W~I_U7va3*zJBd*P{a9mx@dmx}$lH#I|4d z-5l}%n#UogOmv#tfpD1{pTH5h+Hl2(|GSMCVq(UqTiPS2u8}g@UY@qU=GzHVi2T6b*aD5{23f{#p0eCp71_odFuYe4!aB?iV6%*)CqF%@+E~3j!YDG_N>Go}??k6U?-Uu;|ml z_#dc`+Y)o5X-x6>5G2(%2Tmy6Hh~0FebW{DM%0dnaqx}pf4}?|{O$F#5fYX^>}YbK zD2Xrr7xSaqTgqZ>9WEv&YpXOp7Q$HUR$Dch627ALGZ!`n9VC8GPO<=>+`k4a?> zjq}U@P?jI3zAgPm@dH831rmZod3M&4_wLtEuzUT%?0XC3g+{Gg#UPn%7-Vl|D_ug)azG_{VP}Sf!+Xo0f66c<;l(*0rb8CWr|d8&yU3V`UhA< z+vnVL{iEyz<5b1JFtU7o9aLR*0v+rJ=WBazHY4VVW9$ZP|IGdEJHIMXt^P^lk1qBE z6W{ufjJU{a_wvBE$&f`;@6nF?k@}}wQy$%el>dmepFhu3d1qL!1jNij-0`LstlAq8 z9o!)T!%Jj*dF+Od82z5zJ`!~Da$0r0w#nNH8mC~AHEQKI92MlP*LFQ+F$T<1hKrT( zt-A}Hig-%$$DxfR`_o4P@IQ!p(MPHN8Ma@dh0%Z2icBGp#%EBEY4+`5PnX%o;nB33 zZ#~KCl#z*gD_#E!2m}_ffuis1QO&~`=$1uY7#Xj(r^peSG`dB!e2jv8n%OQm(A2F` zu=x&G&sFg0LoEW4}*p*&b9cU(E4cW)?If_goYg)) zUK1^U2o3jG@Fn;4?Begdl(_XhcDXp%PZf&YQBH3IzE=kC zX7EY>^H8}4lziO3wU%O0LF?dIWXb=a>!ug}P#^J72n>sP+~s-1PS@4R=46?APas#8 zq!ahvGvNji&p0Fw&!Ue}?N{p=_H+We-D!v12E97vmJMSgq16G`Jzn3#s#Ga1LR zz8?8k@ID)4NF=<@HS2`PI!*n`FDQLM~`T&{irWOo9FW@olC=7 zjWy)nHpA_i(QPhgR&ZVZtI3@&6S&e`3cA|S(T2p`OJ!LFG9}<)!k08J7C4c+dHVZU zMY*k@Wn@P@WFgA$8oU>=>Lpb5bsyD#_nKGG#lLeh7K>fgjGa}x#)-{4*r`mm#s+R9 zf%ox$Xq;V)c!|z9ED3Jv2^Uo^36SofXS@q0hWl@8F2nSzT!D{ z*2YppS9^zq@14D3%Ag-6zh=&+ko&#VRAHdK`UJbw@SI0YQL~l$HJC$)F10;58UpgN7zcWRUPj21Cs6mD*}b3wk4#ogK9FJ413Kde~x z#QncB2K`(8G1)Lo^DkCY%e{iK2-RGI&m&P^cj z$9WVnz+-@^jj_kxRK`~~bFOIWOR~p>0~$Bw#h&`In!DKS#(;R8x#zup&D?t{a=WF$ zGD7sF8Jfu-w&xhF|3|%dD({|XJpM%pAN7D#m{QP(mx)PsES*|{KaI)x(W#jpu7S)4 z%LgZY@$@P?<8ZvWyxPYUBxl&(wh^Bv>^u5?urTmh?%y{$em)opDQoC$>r4TbUdB~7Iww|dB`fX&0l4h*f8n`)PU?aUzb;OEq&z24tifBv1&Y!W`Iu-?#k&Opf+$0b$aDr zwQeWfY^nY^AN8It0`rqS!*1QX;RFjT@JD zPs@+Y5A0^WX>{w9Wi@^+cX|*2$m`5@V<(ovMKZj~0I#$40tU1TVZW$d*5fj{%l-CV z%VwYb*9-8tqVT}@pG@`l-aXzfDnC4Z{q-vvltZh z;myj$;_7^Sv{X9o{#@QkLSMBqR!-_jc{xZ}7_4sYz^<3ypHOhBqm9roLSs6)Z*1xN zEzaU_Z4gti$7912GJIXw1u3S1&q*db+HOy#03KF}Krv)zr@C)DHJJ1h^#EsJq1BKn zhVR+&rkUsfMGMyS0bR8!>KE}29I2e{!-?~+`(C{6Q2tHaJv0_2#Ej3~`jFAE@4?Th z_j_{heR27yM`xh<^MhcSz(vsp*PUQegR*A6k61jxjjo%2fNf19%WzHNp8r#K{G~d6 z%QvUf%cYK!^>;uFZEbDljRVq|BK7%+9CY--j#PWv;O0==2pj{a?ZuYfSeoD?{>&>3 z*hJ?Kuu3-R7{0uEu$qYpHSjE!_F|Y!u7-H5s7elDuLy9lU{7m|{iT*4HNF78LG%3Hr`zOD z9C3zSU2|){`&+IC&HRz^A{U0Vyk6mooezH%-Tz!G{*6~XRE^Ja#s~4Pt*fw!8uYWi z#}l_Z@z}wa#;W9#gF{V|+irEHCjl{W9%I4aK<}p+IS^yc46U6gB5dUUk#&_}Rjo}M z5JW;iMM4Brq`Re21f(0J1*E&XL_oSbHr*lJ-MQ(8O?P+mt*v;D=ly=^#a{8mJTv#) z6RT)p`@Gvc$Ej#71KeF;wN&u|G|G)Fe`Y36pcTy-OXn}L#-=QxT4`}Q5kZ+*A-=b) z*eE(VPFY*m@(B5qddMb4OVB{aXvlnlbO!64M$fh8a}4ZG#v^! zkaK=HnP`-A{Q0LJ`$Deu>3YLvv)FC0fvJ&L#;sj-0(TL26!sWF(dDCYu-zyG)2!p? zUl9IR9i#g-QmyHJe1jAYieOn#MKM{=?vR*{K|jPb!~YHWE+?h1ML*8WljMMaVDvb- zOdXQaBH7vTZW#o;QSgJ|oL96MtA(+(?Dc&ASvx>2Jb8qvxZf+-%+}XgX3K>lXn30? z#@r#ywaM*r{Wi2_O{D_O@$PJ;C+b>C0{ygr8AOY(ocHuM{=D}wfB^#OaLyjOw;uI+ z5wt`^mOhU8JjJGEOSzXkHU{T1QIX0Gq7fN=*BWRoH(OI>jLv5#HT=`U5hJ(A7WB0) zaS!<&Oku75ecbQk+=(#9YIhp$Ixv25w5=Yq`Q{F<|AkRQ!PU-CfZ=lv*X@zyOF%U} zKtxaF0Lyx7?iHxA%Lu)-#g09c&H7}$H72zj;_k+cFu?k+=K%GL+-Kl{2N(1>uVV+y zJ1?kD1-_*BFQ4if2b=az?B&WAis^4xA~`LqWQqehldRG!{C-hGk)Z;X61CssB!gcC z)56OB3k#rH_LywH9tw|Ec@DN#dOoQ)C}Zvp^(|lbM&;bp?G-X;clSnL5V=lQxi*01 zcqsCuiKD73kKzf1N8H^)9(}?6TT|1ixSu^v)YtZ%Zi50X8-@JybpL#6ZQ0>#(5h%6 zlRKP_a5$Uw;yUEY<`=Xi{eNkSO1q;I|Db+ryFR3h#PG_9X+T596{r*9%f}))#r4eF2$KT~{B! zIb~o76<#m59Dg~-EhZT9YkPQk;MSl2D+jz4yief4nA9?yy|xmpAh2Tz=%qjn=F{Ow z39WW#9 zxW-1~&gHpw@V$~q7lrA$ufgSQ#6SR?hl~j0+1euUgsJf4clN+`Z;jmv*rwghRXC3N zs%QN)!7t-s=YM}v3(`@qIo!*M*p{kR&ws%t}zCWKu`fD)8d= z|2*~8zB@b})xCcqlhpZ5cQZN&RMW|`4oFrffUc}eXTI%HII5qIgZEB>EhLlj`es0} zj4s7&AmZbS#A^NFuZO|7pJDLI{2DQtrh5u%qP_vm-cC-I9`Go>o6%C365jX2n8 z+zNLIoceJ5ZB9bs)9ls;GHE-OCyUrOSF5s!|I#W@VRVO!W&rTBESv*UqRKgDel{l$v`djBDb+XnEDFsw5I*!Rii zSU%sv!B`(2ux-jAtKc|3Pr#C6jt2ja_LLlfTStfECrLV+uj(+{-I$_ooD$ zYsCyN|EnsXkH-vw`L_1v8{u2K1QDSaQB>jGPOZA~#{7VZR$YxB7K%Yw8UhnX-6Z2pmrlZdL zAhtD4ToS##_3T`lhLoizQFjw&7sSB1m2^Nzqop-cEHxYTTJekAttZS}44$vJ@wR8I zxFVKP92#~--<_}>+AI5RgR>#h0HioOzAWyuyhCpI`StF>sQt1UzM#l`!Tz7!3KrbY z1HFHoJieS+({U1Qp|W_}mR2;&9mvI9y_fdPXq^(IobkO5)Vnvhv+{L!GZC^<9{P3H zi!n-<^@PMheP#v{QCx&>OW=AQ$Z!}gAh%-gnTqVM9n$cTu%K;veiK8P1No3wFSScLx3VuCtIN>G((?T6qiJV zqV2J+HR*N)46Nus3eW?SEWhdK$OEPPk?wVoy`64QMjyvXY^A|;c&wB=czx<>Fr)t& zt5Ni9%~d4tY1;%VOThLN&|1RVWv(7F(*mxV*4%Sq@n4_2Edftg>~Wi~?4f;lyfxxHL3oyJSx-}rL1*D!`9n8?6Yrf_ zu9eAD^R@lg7f}k3&q{QQZx6P{3wx%J)gYWthKrPqlN}yci}gWLb^Ml0j3Ew`?u!eR zSB{Z|m(IhchLNUKr-axq{D=yTl*)8&BB>4Kjc4v8Z9HPARgQZuAuj4iTm2|t=?R*B z1mKW_!Fb)DW1Li8^2dp(1Q(ts`1|_&ZRBMal^v}`35NSoyS7eY4#sROI@)MqioDW8U zYB=jKJ{MZvT+iABR@Q^-_xyc^Geq%ZNzZpnm;_!}Xu3+Tkuzq9#k@0ae^oKs z(^FZEDPf;T`P~X-MX6+)(JwU>j1DV%KTSQP@S%CdH$pn!SDmu*w3{oPPVFfb@RDQR z&L@KM<+ju7@XD`?76ww&EEe3G27~t0$T-&h!~=CuwRDC{_oDhrrzwTw4Xu7~y)*6~ zY8mf2g_Ne{GRP~8@B%48pBrUmle;^ZRj5I#hvZj^Tgd(e3^$_cbDbl>#^;W3TuJ|c$dPJbmBKOmF@ z0IC$Md0J|}Y6XuyaJq%+!YHDI9M4M*YQ=-GWeV*c3rY;7N4V47C$Uu(Uu3qLEXM-% zlvp}~;|6aPlBXF8xf&ju#JgCI?$@KxlIo4~BEkDhH6}YHd|zgo>`oiP6zPd~k>R&a zp#k--4@~avHl@0t)0%Rf|8TT~k5~);KI$MIKav}0+?&vVwR7y)(Qw4(=Xk*~-CBOC zn_&34gspb=UPZfyQo6-JAut`CyYvfAlsNx*sh#t0&vlzCQLdMh>V_TBKbxsuWD8vD z=g2{bHf`W#I#Ts5gM16?#t5BBit@&jI%-bh&;+FB6L6iEKpwW|Ih)Et>@}e68i$Em z*BFP{hkY>cr2MryaR=&S<=W~39}huT7-#2Fbjq<;iff~GZNVU&7sk;JAn`nN6N)Q$jg3CUpr1V4{s!eB$J9Q5wKC>hO+q*U3dwr@ zI|mI8tp00_xfB_F(5aq57o8A#IT+G$<4F<0oXN#*-=G6&xA8X?J1dRr=vTAblpg1| zSCOokT^lNCpt`VbiB$);%VqTw6y;6Uk8Y*rj^$rikB%viaL%9UfECf{$Jg1Z`d|+luFtD#8C0e}tL9OOBT4hxUXE_&fhA zAXxm!Hwn)Qh;iOK*Z#sVe)Kp%xbvYU1>KWBf4#>V?WA9*C_n5QQRzP2`sJPea~p7h z^CL6oH+HKy|IdYmeY8v^S?sYEKkiBN2YTJV4NxI(fcUA(*Kp6RNBMta9w_{2+(!Vg z*)1ExF|MY||J>=X2H=-LkR}zKDeTDqj3u}RfLSxnK**1n52ob5VCtB{ZC*r>;_ z2y{TRn|q`;5aw3@Z7o6=1+a&?GT|8t^TF{*RzKE7+Y-AM6WHbIrR_|-xXS9Dd0T``tHOwl2Z?ljwZTC5? z&L<*`*<~&vLlIqFT{$xEfu>ARn9026h~U@Y4;PalcISc~U62f>ST-aI zvhVqKRQQ+ByM2JcZFvGLaC~T}#oc>1gKquFLN)$1t4IIwnV>I`v_SCJSp{SZkJaG( z&vxjI{KWXP9e+fJj2P~k;p91I;kovpgr$xPheejbqIc_kwD4ZOem$`m{`fg3&@<_H z{Wg1(&_086aQ!;0pB*LJLJjGE$wgqeZLgCG=h-r=KwQ?#&1|#*A3fPdSx+tU{$JZK zVK?Xs=;-K-jmf|8K^B2|o{^;tlm4Hz0)$^ZsnSDt3A+thBoRjEKCbp>IbWdzTE`w{ zfev2y8_eo|{iAgSpiD3S|@ZqPRj6R;c7g&dh0v)7dtBK z&8v@|xG|+f)dduE4mS*$w-*UN3de$VkR0ZVBbYy?qm5+N`g_%p~@zOKLlIh8Yhv$4+GMB zOyY)EKc_cH4=;-ld@sH@nNT#$SmJ+Vr6~t6crj#pX?t_=$C`|0Pew4Ac=>UG*wni z`y^I9Oh%e}Z}=*%=>fSIh}|<%BIF32r#IQLU%C9Y=*!#;@^cD*JdW6I>&#Y;D=eD$ z>T6l6jO}w*U98*6Bk?D-{Z~5y#zdE{^p>MQV~L7<5QkG^mOAYreeK%|F;Ttu-xexM zcwFhh9O5kRoqhr7dpioWL!e7Md1J?tXsK8%e|UZ2_wezijL~)9{QK}~tBdbJ@#HBP zo{}zSbIz_i*Ch^R2BmQ#q3|XUJKrFqQA6Te+p9*em9RCF8T-vHW%lsH<@MDT*7MlV z0hu`41mwlNM}q1zwOiYk+!80zy-7F2k+TJ?`giUPEVl<8?RDO^XqJ_g2CD@y_Rob> zvd(q`c%;E|$v#FXx0L;Rjpd$U*oE)RIIbH=y;(qyK4~-;Zr7X=KC`xm=$J$mdjCZs zK$TZ0{8vlOla{Y&00{?8?lX7JkSgAYG&JSQ|DXJ5xR^QetPYwkMd-ysS**q?5VGigLA_esu`-EbF{i(yU#RIg-aJvX6;=n=G<{0E#a5_V(WPM} zgr^^rxXzD5-K^fzmruP6z#MVow_)Kcw_3DVa-w=efVAej+qAx z76mx}jTa^@1DqL6)mr~29haq5F&E;;FGkjgbf*IB`&^0@L}^?zeJQdtrifhE@hz1J z4#qYHTKY`Sd`O=rmoY!HHne$3|>Ze! zu#CCN+Vw*BJBQuzHPz9?NMVDTOXQ8Uo6+|-cV1|JnG?W(a-qDk8)Kxa1}nZ|CXPUp zZk#zg;uaTwPFDD-t7|(zv#ns)UnZz6Q^Mbbibqf0abBIYzhI+@zssMmB&6XbxB2PB zLP^K%?Sh{{aZS7I+TJr=?6L|CJT~X*)z(R#hm}S40knn{bBtrR&ga`3&vYdS)oQ+c zB2xU^&wNpzTx-)Z=!^7ue7E7ywD6fXd42-ZmkCo+tTP+UT(&Y$vB!>Jc@NftNEA%> zA0P#_VP7WTE*~e}P24H~IR37}O51|r)+k8h?iJ?MwvvN`7q{!}&G)Vnm-y6#K>V}f z^74$hp*;@TWGVv}h|A&RoG95>$Xz^mDoaNN?(^e+�Re$~vpvt>u+^n(gLsm5aL5 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 ThPd-3pa_GftDnm{r-UW|Hl^ly From a34f1987874d6099861a8a32248316019c6cf22a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0smail=20=C3=87A=C4=9EDA=C5=9E?= Date: Fri, 9 Jan 2026 12:53:06 +0300 Subject: [PATCH 27/30] Update sidebar description in overview.md Removed the hyperlink from 'AI Assistant' in the sidebar description. --- docs/en/studio/overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/studio/overview.md b/docs/en/studio/overview.md index e04e3b6b5e..f90bd78403 100644 --- a/docs/en/studio/overview.md +++ b/docs/en/studio/overview.md @@ -69,7 +69,7 @@ The Welcome Screen is the initial user interface users encounter upon starting A ## Sidebar -Located on the left side of the interface, the Left Area is a panel that provides quick access to various functionalities like [Solution Explorer](./solution-explorer.md), [Solution Runner](./running-applications.md), Kubernetes Integration, and [AI Assistant](#ai-assistant). Now let's examine each item. +Located on the left side of the interface, the Left Area is a panel that provides quick access to various functionalities like [Solution Explorer](./solution-explorer.md), [Solution Runner](./running-applications.md), Kubernetes Integration, and AI Assistant. Now let's examine each item. ### Solution Explorer From e3e3e6e6c0b7347bcbd81ccd433756b5979ebd43 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 09:53:51 +0000 Subject: [PATCH 28/30] Optimised images with calibre/image-actions --- .../studio/images/overview/ai-assistant.png | Bin 19658 -> 18192 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/en/studio/images/overview/ai-assistant.png b/docs/en/studio/images/overview/ai-assistant.png index 92c33d9aaaa285727b1d4626ad2415caa7673923..156c354680deeeb5522ea0ca228942ec6d046433 100644 GIT binary patch literal 18192 zcmd74cUV)~x;`2e#qL5v6A-0`D!qdWp@$NBUq-3Yd&h=AsG(yh8WMVuE?tp`NR!@! z^xmZR-wf`x*WSOq_Bs39=icYsKk_6Q8DowzzwiCtugrvKs3}~#eCsj@1iGfACE-DzjoEDKB;0Y~kBGky&4c2;yVOr9zzYeIoCe z-+5UwqIJJ1;8KnFVBk2TlCcnXv|N2$z@rba=rRNCZ`C@O@=IIgA*K!*i@mS$hRbkD z@06N_o#OlTEBmy=jkb&yDWi6Fl!D#wM-ePO&8&gqOm6POW#aY6Ud_5TOg=9{q})*h zEI5ME33s6Mn1xE5d)arMdgK^;#kzd`HEhD$ZyH(p! z)+{35%nmYNkgN7}KDMy09a~}=eyAr!U4QifwYu&GFV<+8FpM!Rr}djxtlucszmdZV z?ZmVyHz(642vp}A7p%i+!YCN4RVq63e3mbF%~>J_dD8?oAR&mYA%81*cQi~Myk@`n zpves@N^|Wf8MQ4Z!qxKKPm(!OA@gQz(C0^xy{!oFOH5c>UqP!jKlc`Co$igI#p!ul zcx{aV;>n3@Vt_nyzfo5O>&cNNXAZ-rvC*LC?JfGh+Dd1eZNx0He>1#JIj^woMs?zv zHh%tUQvr%&w9WtLnW*rd=Mj-4F&akq43y9lHf8)BA62h6P3P7AHjB+_8J`zv|1ks= z-$>szf#h6k`+|`{#a#tQH?j2;d6!j3ovIiCx%eVuX4b&!ls1Z!(N_?L<_F zRNj77`Q}o?=kZoEs<;K&f{OUU6Pto~!Q$xecP_(b`kb_5NmIHiefg=>?B0HJ0N;+6 zcjc$5=f=Aa-?_TEltK;;;h;i&d%GZ{*ZunzFtSzoUACE}z(&aRC68+7xILtUY%{|C z<<%17)$T7eHsU3;i-T{CATb$L-{Yr}j&%h|-&~6TyQ0VVgoRaYthKppx5Oao-wfCz zLE7gbp4*#jwGpry-6rvzZga+aMz>qHZw|4iZ-3MHt$iu<+DT0TvcGUGWK?p?!9wn> z6o-S&kqzel-fUZsbnROWcfO{sS;{e8G{%f|qT%H%{cUJSQ(KiTvLk1o4qR=Z9noST zRA}1P_k34LrSq!|AxTa7vke5_g+9;ERmL;90gwIc$vOBo-umveS3=87=iKdla4!Eg z7SvL%Isa{Gv6kw6)~0sE{FQgb>grp15&qXK%D^8gDdXMW+4QHigxO5$C(RYdAU5+1 ztEZmA?3k5QHa}>SDe*_9%94BN7UIQqziFLOv+cU~$cG79wz0SXmA+(y=3k+Qu$!d( zd}U85WovYcf|hN+SxGF$d(>9P+exGDOO9d=BBNkD=jvp+YENO+VVK^cbzFemCm2?<+)r$c3lu|!(@;erY)^CO zb;{msK&@h{Gu)kK;j#CN*mXJJrOHcj&E?7X+JzEuvo3PO^z9w0tb5a$gSG>kWUn%A z+A@AI#%2j+aJuGLe6M*rW!)e{4|0dM>U?k@yeY@KtJHQaO}5$FCS&3e{95p+PS926n(m`*&Mtrnld%wz^^z3c#^d63wk zBRR(%*4&s-Ri>|`DZ+>rY*il35UpryhMFOlQHCPq5Z10YAEL>`@YdLYxRU4K4P9hQ z9T=DK_R5fK$zTzg4g(C}i_tH)50No{sF!$kfjv+-L2{Ih)gPM4sT+4)C(D{bqA)kbo3IQ-r z5BUP4Fp!K_cnohvYjh#s&whOleoCR`UYj;Pm9gR%f8BCx<7s4Mos|>3%Apjp@TUFH zxU}!pc6mCE)>X510Zta14|bD1Njm%cA*5HLtn*vXp!SxRhXskkaK?aL?zaPM-A+y- z&Hlmnh1RdRi&mu+CXbnlqT|h;FyYICEVPksN$3JHZu}HGyl2KIC5hUxFIRf8%Eu85 z8DhE%rTqjhi=W`gQIjy-pSHaN_0x=g2i7^Gfn)WK=uqt3477?<`kZF3m7KJzsh5L( zzjh6hbIbFfoEM)#rO=xn?)lOv8ncsySxO>lZGJpgS+W7|XkocTj{Go;G&zLLI3KxJQSe6p!^5U|b zkfq=_j*vh`O~|w@&w4gEyffhvVNvTrA;|1>hn;_8KmKk8Yv(O$i}XoFWZy=mKg65; zynhk9TZp@u5W1#U1uNphy!{QLyf_glZL)f<;ot|ikId5D68OowpBH1J!`|;smL%|+ z!OO1sL6cx}eFt1_B7!}4<&hx@}ltbK%UQ^a&#izm7neK;p+m;l>pM}MF z)67ib=NW(~id}yOeojZas|0WDakwudy{|+AmqRgr997Z0RP;%mAIT0U`S2KGk+dQD z(F1}}B>Q2Hn`22;)bs}#o^5P(ar^Fhi|cfRpCaEHgOozeoaej6*ETt&i6m597_@UU z4q&AsjL=n0>>LShOglwj_HD)>DblKx+eONYnn#piyWZ8VH@@pQMM025f@JO^lTet& z9Ye5eQ%prO&~f^pD4yW^;YT7t3m&?a1r`~mDxidu?D|8h43XY~YBb)aS?9hqUYg!o zvnLz1?H9jbiyPo_#PAz%N)$vtNqD1J96dM!q4fXBp@PJQh4u>$wVRV)y4mYo8Vo*D zXN3He9*q(0q&j~gb>XQKJ_zSculu$MSfD3+pO-pv^hH^7_Y=6hZyW#^aHEire48|Zz z-bO2LT2QF`NEF}V(OZn;N|!Cj?I<2e)WYs!#xk_ifKykG!Z={r)og4X=o$Yv4?0ZSq4xQ(!QW3xGO-}Ml*D1fsj0MD5IV~RiRLdVz zq|ScY2`^OZ-N)CM`NiA$B8cl~gZm1?dv>HHFpVctGD@*?oDlC%K5$>OMa_1pj7AM< zz0?cv8UGL9G;Qph5DH@;PX;}Cb$d{c#CVb|e+(CrL#VqRILH)PL>CDBJr z68GpB0C$Dym7p-3GIDS2pLTjkCnzIHPt~E~({P<2$1TW^kKxo_u}vOkvPQy_Hj44% zYIR$OtCf19d-U`E+}x*-5R6wuVo#3j>BRM8R!Wu~Izq*Bb8u@RKS zRoq7s*ZvGl1OpI+*dGKOA@Dhh5l#Za0xAW9FhT)e^q-rYm%lmzfH@`lu|95Msc=`0 ztPS3z(tOY%0NjxZdWbx1U9BJySJcDYA}_)g~y-_2{$+moM(NAm&9wEWp7 zl@fRH{TjI!gPosHIHlM4!Qy>F?T8)JPBg9( zC|3yq@yyp>x>3ge%&&aUFeSkD1acRp?&3w^OMKgX} zR&mM?Ob`omt_contpz_#`ta1Yofc%b(2VfF76g}ww%AQ+r~1qI03eWW;Cl3kh&n$w}3c4h?&>KjGzQz*`HKdceYU9 zX^5ixV`3+Ed`Gwz3HIVgQ7B-K!nBC1dUjCNw(9VOxbWS4cxyvFbtbTp9~Ckzq(lkE z4luO|LWp{E2l}sqZwf_PiwA~AWdI|#altovvx%Uw2$y@3a2^o6a&QQF;Vn*J651F> zwVDEoKh)%hJ1+Qh!Eg?sC%+CtkqqDuaA0ZAz=f|9V#xtg;lT^tyA+u8CWm3s5Y@+r#n zo#~g@53pBzuJuyU2saB-;`JS%NLKNlbwpP;p&^YHav}E#m1XXj%wv5uzx%{0po<}PnftH5(iFS79Zh3Tsp(z8MZS_N^ye+ zVbo06FMNlDa0Yn%7EV05T~4g$gdA$(CAH?U9>qkSX9%TJsVNsz{^#;&E)+soCzsNt ztR;Ct00cjzfJ-`Iq7m;?IC&QsSdB@CgBEa!wDVhlRTSwh7;zvT9MCD5hVAun9xvWH zmYG98Ia@20l6Tm=jAzDTy319=18$rXFHhWJKp^-AtD594;XbmvL61A+h5PQGCtKVF z?t-pN?A_Dduyc%+L z;rl6eXM&GjrHQ8eB+mo_dZ)s;8@C{!Ta!Z$n1_6~((gH*ZIncTbCGoprYNLg0|X6C zTa%w3Ov#ID?0En{svnx+F!ZT{G-#yt++9FB*XHg4lK+Pa{yVWI@`C%H9{yk8{TF!t z2XnlSydQDy4)jjb8UJ)}W(B48lt&uuWkCdp24%$*8Gf_lDe)kl z9iT8eh-iIo;zAue;8)SqMIltOr@i!%Ks1Q|-7G}rw!AF#vZGwqO42b~UDcr~ zJ)nWC)Eb+n@0MJq9!&Qx%B4(G;x^=(Sg!KZn>iAEkymf6+mt4%H|SB4qP`mU%a&NHr4qm0ytDG#?0o@Dc$*aUm+nDUqX`rKp5Tpg&pnMQ z?@4xtj&D|F`awoM7XkLbiOZU3{0=#KIZ1cebxB9BWlV{Wqs1#Vf67iMO>MDKxFv_H zci)gT7sqAHVm^|9St`{==Vo_BF1^nU2lwXf3Z>m$JQ6PkK@3(c**EH%CRK|!AYe7+rwLVhLWnGP-;5{vkd&?$ndR9YKZP}`|$pv z`z`K1g3Y7!nyq5PU5m#t{7q-ut~Ln+$E##MdM$d{c$_7?-Gp;>$PY^-3`MB3hG~ z+a|QB^Fx))hjUjLn-Z+ZxPLAK@xxM$+}qP1ym+s<<(b;pVcFdf*>@azdFq_=vcK;T zZYybW4K)T0_a{bBJ{$8}EvcIo+idCT*L*x78ldIZr$rG(u>pTrg|L6cVl$$?RHX&6 z*S-iDvd&h@ofdmXp?U<$ObyAJ01Y`(|7|j^Sco%8jFDTUuPtfa~6|VFe zp6&MW;ezhHoY`J&1JvIkz%_Aa;IqrI8ge40NkNE#TTT~NZB)C)0FD2&-DwsnhqPe} z;2t77n9tt)uv_=^D(k_1^#g1c)x0}2De^F=4y<$e817NG*_AaFyL3wcBX8kmSa%XV zf6crrYNT_9{taf<7}li5gs^XY*f+i07g;+Wk^>jC6mw^kfV* z^(98UTE~66RQk<6DJkHuJ{wz~vMVh0l=M1iwG#kT;73qy6vAUz_4TRYo_hYEti7(Lz5!uNp6aHbQIDvWz%@p4IOflRiK_Gf5{$xqZLaPf9?rg+on=GLRS+Hc z9}LPxE)!{*`?{xmW{d~FlPvn4kqGXG&X5as8}H^EZXfQE%bcLnrnO?s-%$w7uQYO!jBZ=H? zMGkpFtL#J*40g$uK53r3v+dmdeOKo6lIoD-5Zr~7>^;$qneHDqPW>tf-jWG_qEahk zE$(-`@k8=N3AVgPL&Wjw*ojI$u~TE00*0OU9tif<7wNrNZgMdMyShe^`3SV;~l*Ed1Ir+4|@g>B;_O-WdHc7bo^U4gv=`m&cv+2 zU0#k)s|Hsf#~{e@4l~t#DQ#hm?oTf*0q1Bv=qC5Ab=)k@YVzxXTzsVq!9^-9nL>5c z-gl2+iQPSiWoLAiq~Ml5>d@K%_I~V(e7DoC*cqVIgtelK?6V>595eXjN~_L$oNyP< zzz9|7r;!ARDSkLhir4T{TO{^_V<6)^q+6L!Gr3&uc5mvz)~w5mkmG|@<0G+P%#RP2 z77S2R_da;B@Z}5#6({D-2WjfB!G(A5+nj5+;3K4BdgmbG39A+~eSzuzenYRLTihq9 z(v$H{DpMh|;_s5lRi(;{vZ;r0-6-=xMGRUPqV;a2vXbI)%!`Df7*(GITZxix|9)Xa zjVgA|D=h@fwAVFS`>x6+vrti+St-#TX&%t;G8*s|`d%0i{*BttMT$=4(PDYQ;Cfvje?EKTsmJhALaPibkQTKX z@Nt$$svm_9m&tw(H|Ss<8Mez(o$e=x^qr(cNAT`MO`ONEGm&sH)TOg1E3Xe{gt6S* zY;^G9{l4L3GBXr!7{uA_nx4~9oD;qNh#z$#g13){RX$W)=N%mKF3G+k6Q;;FntlL)qOQMd0)kX&$K3O^E4= zaJ@?n1;iW2Kgx4eR-M$(!k-;43>SpQddMT&O&Jv!p=cigEzsX;0kFGeLoS3l9w`y# zro4G2r^g3ox7))W)3_f+s1%uqXUWhqY)!}(#!a}&1&${7C;W6;bs2U0ytK#%xlrpV z_;zOxjic5xuY=53*S3Uu>8@^D)S)nSPwx+r+o3UK$^6vnk7a*pTfDJR?P2E!|wJtybV*s zQE%#bDy~=K*yXirTW+pzndO6VnwmyM%YJ>~QA%D>%^WDtpKZXj$oA@rUA&#yPhVqX zGrtrQKB)7u({~X9pj`yIzBKgkJM|_y<&B=X`wbZdhhVJb1qX-MOl;~BhFwhROn8S_ z00mYHr5N1Ma+Zt@!czSnfNs_Tr1XLAr|#n08Fgxf!f zLb0BKX`+5t{3pN=aT)ihiI&cf=<;-^2MuMYT0-x@wa#>k38rh|muFljvt1;y)x8K; zenv|TYk%?EnW8-SB`HXs;`&**?|v{i4G6@nH}$0^EX)TV_#b(RGF$oxdJYp7^uEj| z86BQI3U164n9Pf`>F}PMyuad#-F6)8R>Ew}f0xJZn!vA0Gvc2C@ST$88#?y>jbs#} zu%q%8eV~AM=$NBuOXBZezBFvw+lcuUG-ll9k+@CRb#?t-55)TLDvIk&?za^=Q6z9J~6E^f2k@HIBD{uRlvln5b~4rCk2{85PvJz?0~KwHoblF5fG> zvMVnf@uo| zi1?0(qLY8uIzIzyg3+B2H!;)du$wY6B6&;adVCL4WR{p$*wUTOwy$5m@X(jEp>HJ7 zqYxg7fCGe@km^fC@0V<5UBxBFw#1NyGGt3tjQ{S8uQu!`$1(6JLFkOm#+dY!f)FrU=nnpN@9Ldo732 z*Zyr1EO_C2*ILVGJf2MTVy$3Dz5~5!`0$ty85J2b<6%{TV+r&taa;FP}wR*Po1Siduu#S zEVVJHGUBE{4C3*^4>5b$mT$_M?G#3*CsW7?MMZ8$mYbBd;O!6M+<@c&rn*WSt{rN_ zr-id%^y2Ha)M>fK+yj_Ucj%x3<3n=^z8n-{hlj(X$ZL$5z5?Fr(`#9jM)ubIowy7w zgs12{AjpdARq(tx(pgxqD`rV|`Hd&c2&Ox0VfXfhl!$OXaAzf+3Nk@yM+Sr2Ytps} zgDl0hNm>+>fLV3^EyY!vqf`->jzbCkS|SU+*5+YYSb%$u+dZSoeDAQI8E#C_tsHQs zY>TOaiXCp(27ShEcx=AWz4U7%D>n~a48}0`2BVQ0>$lHU>G;W}OTbCWA@$-BQ#nOE ztk14Iv|et}B^9}?T(qE~NmHu(*@2LNxEF=FE}-&wFcn-v23gU2hrBWiLu0Tf?)AUc z!OM)dVI$LLe|dF><7&6gnE9x_re0j{Z;f@}X>$tou~>}F&7Nx!u&qk3{=TM?xk2{< z>Wv%@Z#Mi_9X1MOXI-QO5vUm9hivdt&15;piA=8UYhf*4EHF7H$>%ve?%=KM7W#YJ zuQt=5FxZM@)pS`P{XwWSLY$~XeO3I2z)j6Zmrz zda%Ft7=^hsP#S>s(cikZ=!A^x9hBA{rQ6a#KBv*+9&U+Bc7kx6kFS1f#iVw?<6WkZ zl#@DJeAT5|SQ~{=D-tMkPG7NdN-*Gr_EvJ!KcFz-ITh^Ov4iArs9B5)qn}I>L4VP- zYt&Yx9R2=A6ywOsxw|<#tIh?mi@U0#q>5zzwO|?NLKBTwB}&@g*N6@E;Pka~>uL63 zjUhk^HmLP)NcpF>o`UD!)%3sgHsyd&{OLyj!?%BflYfTs{|=p}O@Df|zhLqo(BmJM z1q88A@8tjB?Jsi(|2+9wA~>Hm!(i2y$fTO^JU9Wnty+8uSTu3vJT= z4eud&pdu+9Z`@kho(oN;Sfqv!Lc!s?u}eL+JG{CWGmB%-j}(}H-){aH z1R)r#ZWN2yVY5CfU@wZW;H~G`{MZ&|KBbmyc03WDxUv#AlG{uQ`n6{1K3KNM>0 zk3xij$I)2PBwzIh`vsE?)6Io<@pO;|5v6tXL+DxPLwv`1fHNH@0ZN9+q#NsO+sBGVvvSa+Rl zX49}*9QloJNG)P5z{nf0mgPorVS|Y&9##A}(Lpyzb~jm~%tv&pxa;!!%jQN&F}?PQ z@&&Xrk5=T0QF*oFTD)SjaS6!cCqAju- zlxhuI;7U%`IejV}8xLtYIOx5#?10S!3pK@N+0@7*(T*p{3`&|4_a94mF$6j&buL1Y z=8ktxuWW+svj5;3k<+0@|HiIozv}S*mRJu8qksmSBF7DQn6cMj-pI>L2}9#vuVN?I zSN(aNoOHZf!oVjjBb{e~M#Jq#?X?}i zee=9S@)y?oU!9u&yFBB6!?F5PeEz*JcPhMJ|DW3i5bi(pC;vZF z*Z+qp+<)u8pR!V7j^U4jioaEI{NoKE;?pl?_?HDhf98<;Bj`x~8lX<4fS5t~d*I+d znfO;d67kgE{(kqr3>=&a8&K}}*R%gCJf70RU%`ogyLlohWB=a;=l@>2&H)uB{{!tJ zBFukd8veJAo0w|)&w@4oQ|0=9^=1S05Pwsqzm>iHaRdJD&HOWp|7%U%UkDsPJN_jW z_V*@yb*(#u=8WS5IM#C!o*YdEqI!Y^JD4D8h>iSM&kYzuPuiIRO&@ur2q*L*^x0Dq zfWo;;G+XJAIPwrM!C(ikDUdG7hZ=I!#5EPC=+1(RYi?aYfl)vR7#gZob8(G@Ni9fO z4k^L|1$_|ww|wM@;&sqH=Aci*ppQT7u%hEYIZV*-A;1tpYxghy=zIja9(n!TZT>4i z)}jo4%w7cTik$_Ls~|f650`gCv_L?>oQDni^UM$N$D_mwjen{sT<7X^Ii5FZ*vy@; zBq-d_yaKDEodLY+6t2pxYPux;Iz34^?Zk#SkHZ7xNxvmK6s1t63zdnvJLcU}@rLha zxB3u@N+@L^F3D@Ry{AsK?TbZb7T+Vjik2?nK%UyArLR9fatE%-A?-(olw_{LuxvqU zDOL%Y+p2lrGWKKNY%L?RM~hODNwUmU?(yKq$$DZRsRQSCd;!8UxjU|nd|o3L%50#A z5t2$L%pNR@kJ%nEUo!X8Xy+5zdJza>f zR;rm&Dl;C=cD`YsXs7OUj{7g<>c#I91l*@mS=AC<7{p z_+xmUkHP?SL%Y4v?Xk2SaVhPJe8YOY^A-|%Hsq^=-!++agKK;0+jO&Ga@Hb92}n+O zD1jga5%$UXjc=g~?eTod=`H(1S9MwFI=rmMJqIt}ZeyL(#3*IXmZVo^6>_Uv(VD|~ zcGP>i$i`l8$(j3mM{iI#82w6^b2JJ9S%Lex5?u&g9Ce4p#5*uKIrYLC3?W5^Em)He zjVpd*Z*MDUCE`7g%@U3`bRq8By~B!61QyOAwcID*S$@_>biHdvOt>&(#g_}3ax5s+ zofCt(Oxe4#U#oQI=zi`*HhB1zyUmH=Q%WX)!tP4=q?J`OBLyDD^iSZ6=$ZU&U9E)S zm_=b5oYlVG5Jd{OS}Gm!E_br7?@^he`@9&WY9U}pP(QSx)@?W#%t`C{#(GrQsiezN zqcI$e)B7UTZ9wlDj$8==SHB7)Obh9!s9XXBwq{FFa|>ackd8t~-hxmH!@0yEbrcXA zEpF$SROu=AntflZ(%6)~-#OLKN-eV*T{}iAc3RssWMUb@z%JyFg?ej+_dw`k!eQBu z(#@`%H``9j*rs-Vj<27-yR%PO(=}e)4JfsKX9gU}swo;#Vu|%}LZAtI!cY^R+w%d! zpA0o~gz?4-b_(#3Icae31e@DXj4-~J-wsQ?Ot8{YZBy|shiTsIIvEni|8^S8orDX1 z21oj&^}c<`@+E(@et|HXU#}r;G*xxxeN?Y$cLhecYdzJlY5_a2JW3ngy=1_Bh_6vn z&y1=U;Ia^(U=h(v`G~V`&28}-QtAxREhuP}&n-4q-l{amOSSVlZr7xE4=#*iAFxeTq;@0jo9z z02G*uXx0B48grWJAbeXtIT+uRwmnG=rB9BeKlLhqIh8=E(0@UP2>IY{Tawh<*Pek_ zwUE}U{SpUn0juj730Hkt@;mWU0KBzbg^#HOs4%QmS`WXE(g2EV329)vx1!ci(fL{y zZNLzEaNwB)-~kr^RYJ)Fqk_RDoxgSR5NizAQ<o#md zj=#uS)?&NG7a*b6-9cYuYPuX5GEp&B@4||{H+ybf`ruxjd~x@D#i39qKNPt}20@bL zQ&ej>X(LfSjvnizGJ5FQLlZ5(r5IxlwVTKphx&le?|?dJVZx}2la^xL?RK-faj=7@ zPh$4zvRAP>zOJP$x6OBxG)Y4d*ozEtn6imsc$oXo2ZX|&3cDfs@EM8v9saVk>N&Br zg=?7>XbZd<$)%w}_kLjr_a|#AFKWn85Bg=I^+KY6(z24=hOM~h@VrML&lo3uHfC|~ ziAv6~$Y89gBwDy*BGU#E6aiakQc&T&sZwygg%I?SF7E^dOQ}DRW5oMJVXXChwUGNI zT;zM-@)5LedKV;QgdW1pwmKIaMF-kOWD~!=?(}6DeiBELRC!H!qdpp=ETK)By~#I~ zh@uriHQmjt?j9UaY^=*~=rvAJD${1W>r=JzD7$M!5y^4M51A&NJ9Zc9XMkRW(A*u= zP;&R~*y}BBrt`?*QTOx~r0<5#eDi~7_+EFt5^~fy|FVq1U@tvY_CU|?NJDWwL0Pj2 z>7ZPcJ@(bZS1%YoYS^#nl)lK$TF}kl$O6>~DC+_Yhy$ERE1|DVK0z)v@kS(@QhAY41x#_{}d2hbwB%s=7$E3|PoboARM`$FcdH zsdO-2-KV-{PrXqc&y_zF=rV?aKo=my{{`?BbYj&Z!L`2a!;>@J&DK%blm6QRR@yn;@sTdB!7%y{h8eZw zxmDfSuy+DMNgTXyFftX_-%s;)oq6Hz?HwqbZ-hcfG59toU4F1yJ$ZRhyIO*`muhax zYNkqOJRwL%k)v#HU(n5*6MB^S&8hV{uM0bNmo{jsRM+>Zq8d0=4w+NzE4m3hYjIuy zZd=2ml0&!JWj$$GJI)a#Z?>TlT^OaYdPE~>jx2rxMzB0)Q!V|1%W=4smGH~`Iu^(g=ggq8eB+9F`Ro=iZ!TyrWchVK|3OVc`XXU=sMqhS zi&CpTU$jsQVP4Sk(O5Z9i{0JF$Ue&fOF^uR=F8aNxG9>s@E04A^X~ntrcOLc1-o;BqO-!{7A8zZ)H8G@b zKAE92+XZDXs4>!4o>6G#s~u@R2Z% z8OID0@Z7>#hF~xollqV2@Efm)>4fJD1q=O$d+*P{U)?xvoX1kJ{QO}DfNmOGbuUm0 zg#B8!IPK}3aZdbm2!N!waapCJXO#iG-QoEAqb;ZSf6DG%OM!&-hoLCC8=x-_kXH#8 zK#X{a%C^#M>|{V(uKkU5a6#?v2>rj%b$vjscbnnvpr}g^6bTReqQP7=~XzGM{=# zPJ%#x;^^(Fv|pyk3YWzMJ$-OPv4{$=QABIub%%s_-Sitt%iO1ldPNk`4JK5A**+x& zaS%Isj>#Ac%^yty6g`Sj#z`4?&`=C=tfP-&#IIJm&)?HdUcZtFGP_G3GqLFaf8+lk zYc23;ze)b4YF?s~CP+HB_#OBpr#Q~<*AMlE*22J1?$&&J4L?LX@*Ww!WIpt3A?-T% zL4Mim4u0v1JB#*V{{!h-c2NICFe&`7W-!Cd76tgPKP)No-hpL|+B3}fIeeG9P?Mm3 zc70ihnsm2hnnlbd^UVXE(b)n@kguw_0zCGG@R5d+O6K6yD@fqx8$EmPHGfXu94GX7?o=;e(ADaABiU!Xhi|}pa^`9HN-|QF9z@&35_m}tU7s{}9^YgxYf>e}bZrz}~0f9hn$s(kX5XcoM z1ahhA+9hzrUXsrky!5CjsKe)1cKL;$^bZcj#KZ-cAFLn}=jIo-c8^a^&qii8<{w5R zB_>pVt|ld?IygBiD=kt}SAX?7XmfMBc4U8G^H?}wckX-U!BEcY($No1YvWbG(Na^> z($V+ycPSrGC&$|-hhup|`%5ErOEG0dfZJ0ei^avhfTX(b%^klEPS;M)oSa-%w~id8 z7IwGix?+`5o+*^R`}E;c3nIS9TMWMHP-^doPO$Stm_+j1f3gg&TP+*0%U_H&&i?($ zmY?6F*x}k{@1_TjpSFnnC66z)l9=X2%3yGJ*PkPFoFNc=65QVfNMbSt1acQ5EA?32 zV`L=-&vXP)AweVWBHAGUo4vdrL=*1Vxmqsa)oG_)=E0W`Dc%2+wis=zU*C3vhWdWE;@t^!ASKjf>0W z^NK8P8}uE|_oOt>qwkBfC(1h`ocr26g)3vd<)Td3XBc(#mJQYXV_U3F_7=o{C^U!B zqy$taCFdOk_jg;Igvp`@%4&n^h6kvh_y_!|9REC`o7UDY;y~`}Q}(Mp*o2JtyNcIIcLEjrOf;IHVExq7yN+&wr>)q)iub|$)LB`z>f_OhKrC&L+aO8e_ez~d+sHT^U)p z?0EsAvKA)2J7p|W})F4g1C9984(4-~>4-N)Wi zBfKpHu+@rt&@8uit2Q#7U6>dTa&q3uGRWQ@DI?PKF@RO^`@AM0aUE?PY7OT9d~>}Z zgxkRLw^a3tAYUY^J)}8$FdDTxWjg)c-r`|Ips&=`bOsK{7jOD5MAXPu7Q0+cC53s+ zJ;G=AeMDgZ5l?uwNE>-?Bp;=YjGHgJW(dJ+{n5^yI!!ty#yR_Z)JbYhHI1dHzg+$r z^hr0Kci-1V4;ul?ilc)cyMg-iC&b~yWMuuXI^ypocD}L3lB4xj&z|9D{&LtxG5B`2 zq3C!AL~XUaShEnqDD8 z+&XhDmcDK2XZaWw8qE{dn0DEuOQbSM&i3NThb$YV&86LiJDZddBlXGED=^$rd}D3! z?ls@74YqJ;gBIpF;d(b3xDEybR~ud_E~}IrB>Y{-GwBkFZu_vb!b}GlM$lO8Aa2Xq z;9FS7%2ofCFRIh+JjO4=s4qHM75dMx8B~{d9=k1tBh{%AL*f3YQBO%xv4$3QTGXe7 zXE(ahdLiB|>vvO*9`w&O!2yOy6El~XqA>b$KJD>|UtYWkS|r_OwRN+FHfgzppC@$d zPTSyR-=kw+UtRg!qR%|Ink-M*?c!SpFaG^?$Lx}Z-{^u@%R8kECqnt|$#Q|Ug%(lf zkvrpAZ0dK^>pFAytX4+!^OK>;s9&!1#$w&7d({S?TYa!cAAgn3U%g}Td7>$tr_cNA zxVEx1IqPgkE{A?8*@Ya0>cE~b=6g>hD!5~h!W;c)h(;(D=>4o>WLo^iklnJ|Opqon zpl|Wy+LCfrFZbiR=w8(n4iZrV`W@?GLrE$f`s&MhvCoaZR`F+|Dfps`?^AF7{%(J1 z$N%d)iOzeO0aP;e(mT7{L`qq{s9-N+htyGu{E2%Y;)aT1%Zm{wdWF?cPYp48F zSj?Q?giu!*r!JvvkxweV{EmLP-DqUoa{1boJYO*tBZWgr!8whsf_kTI8{R2|fTb$F zeDx=$Z!-Y*J+i)Ddj%8p(5d5tZj4WUwy)$$aQA#Mnm)?D=!C&;x^vTYt~e)V@J^^S zd-U7uqoOe3;^`_kmbBMaGO2G<@u{=jKlX6;bf8iM<+PGV-mF@xZrRK(e@$h!o_nRg zHZ7b9pjj*yDF>E4J2^=@%Q(c#mTIbX)K`f2eSRrip1(u0CrVmX$}surTR z)8;~9z7Q7TL+jupDcXpf~!xk4yqB1;(+295h#MM<-${A5= zX>URmNvZ;j>Lcv>1$x4hqo%|49|{kTj%Ad1x4L@A%U|=-IS7B7Wwp)MsP}3#>{px;83XFex?8%)VmPs;J795&PT& zZQZ?lso%0cF=l9NHJweMYPUf3?zND&WLgXqFnh(8P^g}8%yv#=qZ6T4S|~M1)nOjl zithzARjz-M%eFKl^#;#FOl*{mpsk=lH7}q42dpvpT8~nenhV?1$jZG7i#Od?SFxZ%7joB98H5o_Vponj@r;({;km zAb8A=#g^yP*f1~il@&~X8BB;AM5TA<&s$Rk#iFWe(^sScuR|I8U}(x#&FNPjBz3+N zFyb?K-Pc!93t)vCxQ7==0jrS_sGz6PG&1rv3?zbwp9-H(o)sujmvD5m(4 zu*2uI&k@;Tx#hWd=%K9&yS-jNxMR3Y7rV2$f*xwLd)m)2*Y9?BWK@z%T6TkolOl(6 zPPkMBOs+z+dEeRq@S@wwpOI7T@8BF8DFQBamGRS@bZFf~sJeQzS$)9p!`Bc9)#x~{ zk{ls-VWm+abyd_mu3F7bg*B&&`C+BlFu%LV7$?C+E!Tr047F!cwn2c5d}k z9c4cuKFhdY$Dhk}P~RkXSaV-VeL74|W#&h8)2v0?bZvE`N0<`ZS2VM!$i#t}GFsoK-YueH4r_8O}AK+~<2j}eRC-_PCK;J3#> z-r97td|~CO54UhJiSekCd=|y^dvFgE`&@pK;;AwxwP`7KtWvd3QuaFsq6dEMMvAn* zj7*aV1M_Jkd7@M{q95J92s^cdeO-d@^NnXbKrXvV0mR&X%L5e+X>Z7Bm!}I^^gC{b_ySi>XFAbU~CoA2VdGFi|HuL$1#b@d#;^}s?bpNS$?Por#F{d?&LU$^Kf2JGUiTdXX<+9OjuaN7|NoMu? zDwjRBRJe8nas*fOHtNUrABSD4@ys<@yz&y?^>aPV@8}tbL$5yFPexc|y8qEgBq0Pr z)a(dBU;8EaMJCFGpIv;}-Nnp&`92&9ySQ8nm65^EQ;nkspci(vfnnQYtLv(;Zb^a+v|F#u8n%!!&c>Q$x+bPU6^@L?Bg>Y)s}K+e{1 zarCRlNW74A52WFeKO3rJcy{=i3NR7iF@^F@P}?x77!~i1G)sT9xVyEnP}^TC7NjLS zzO}f0h{X)!BMx?ve-`;3Oyx)_ht>U>h_ zXD|TrZ3DgFMmt~S%+0|WDNfowT5Ua52aP~gd|->6x5)2LHo#0P`9D~q>TNTwQR!M* zEV&%hn!G0mWWo|^g+CsKKre^|K-n4Kjwo@TQB7A%)>KVSLUL6i9l4V=`EDZ>q3R`O zG>xORL1&2I)6K z^6sy`TuG!K50KmB-9M;XR{a<;gF9(r#;;H1w9^0W?F8UkhDjwE8fQNNAe7L~zkdqO%GY&CpSwx&Pw z=VpM%!9;OEAa~qovD9m8-x7vsIA4vP{!qk@JND_0PI=KgQjjAe@*B+v)yX63vmCSs zn<@%}7JPRq3lPqP4cfE$nWBrfsT7WPuOTn*|3p60L>{lg0gF55ijn|LuY>x-V2xkx zNld9>^zawH%T}tAF6m!~_SQCR8$-s*W1uonRrIw_TF8*Bg5L$K*muXbUsmNarb|1F z$^ovx1LWl)R@kDq*3|+i&+jlY&$>Ly+$=Oj#yi|>)g4`rjNR_R9jBYslg~Vo=08dZ-NnY~%I9=DM^Jmrsbo(w@L~mF%u2~61rTHo?P1=3L5pWWX zqI{o48%WZxOeoUYm%fpgC<)-y-M>Rwult5ePL?KihtBVu*pmp8N2C3>g3^E8rKUy8 z9J-%KpYOa788Z?0g)o^m|9j~9fIBk8@>Ts&a<-Zj$bCO~)oX**cN*ft$Z*V%0C4xb zfa0tQ1cn6ffPiubevY6=UWVXA2mjmp-tiY_OYgs~@#hYu z09m|(C>meb-Llam^Q*Ywo^Qckv6f);`BPW!VXc3ULRa8M>o*-F$36H9gE*6!U3;pN@_Oor-kYb&}>#h z@mm8!ow)c!#aa0>G^KyD^0ZNoSKO%IQfsSPY#DoI?QjWuAX}r_mIPy-E7e~}nC+Kl zH;G4v7Sja%npw;@dICU1*s*$D?!9gLi@V%(@n@NX%%KY@Q78b4AAit_oftsj3pg^0 zr#DD*sz%;$kpSMHzDFHMa*-DL(2(6n*}6s0e+iTCPI;f&2wD&X+F3x*CV?N$fNdSd zum;q|qkwGZjErTuqTvLbm7paEB+Q*9vjf-V#BO48DCN|q)#c8%z3y(uVJI9*Mcn&q z3&i|8ypefO)-7F|dsvSet)(<8*jr_AA?Ao_GdIx?@=p6wr~_y2#^~16wzc3!P8Z(B z=D{Z+))kux){Sgy$M)V2y*ceOIhUB|3Xdbg$kl@47Ps`Z(pbqsQK|_1#VfKmV8B&? z8LF(U_#Ujdpc^!gC~~whP()YEdigJP#dad3*@R9=7~zgjF{uN$^1@tlsO4mW?a0+` zHMx`ux|Bkm%e0+F(l=d*pnsl32SHxOiO>bu#k+6`0CaT`pb()!d4`}7I4_2X#7TVw zJx+)3!v8FFk)V*hXp$?Ua-3vu>lCYc*C@H3vu@)Dp@Rbh&AtL=w0pno*mg5CK~uQ) z09)W?zxyQ`wetX`6giap3e>W9Py=#$(1*N$#zJ{qvAt--PQoJ3m()_2u1o2U?u##= zZ}RMQsm<=({Tf02cXAwc0A;GoYY3tjjsQqDMdmq`-i+98=SJ8?(C*|R+cVO*l)!y_ zvGkWCz!_o$mjp|$tm?Ub{QK6XlMr07KtpbX94OM`@$_;&itL~|8^qJodmDhW*vm%> zZYY|9gnlwbeh+f6IVTyn`ym6^^rs3t4crn$I88%?bU|=bNB|Df9%g|cFTns|mKjRC zD9RvS`38F7X%~i~GYfAzN6;~%NP42D9M2iB6q1!xcrW$A}3Xorio?UF?X9-rjp!ExDhs{6YE*2Y2CF-&xm8ayCEEjy$Ieg4p0eIF>F-F=8i{rO|@@ z;tC$5DKulLN)D~MUUD%7P>f=clbec4L2^4z_>Q4S1q$Yjd?^*6NV0+zStFOF7N)3d ziP`MmmM*N}Tp(CX=ERmnSwF+^eM1dV#No?E%si0a5Ct_=Te8-!6sHhDa&fr$Yq8c> zvx}}c(1~o7tNdaT?d$*WU5`5ON2}4n^blXu8s7<)+*O_208()$b$0ViZ z&^dr6+N@dBN;A)Imq!l`qzs%N$|q>`g!=uvSPao{xGa+h=z>7@%Z(S8R5M{C`)Tgv z-6VVsd7%}HTFlB{dk=lW(1xLs1|aK;$e8L`4;Rq5N`O&6J8KjiXOdxu$kIICa{+a{ z2fwoy2!Ti zAR^$Qr$@wI7e}n-2!1nKanLYX>Gtc=mrgLy2U!|UaOq#&#J{V-dB_4~`%AC!Pe&3l z^UsJFG#_Am%_GM}CijJv2*5docoT#icMWk=0?uGzC?talbFsHUWif{}5_>GW%6;ES%Sk3>PGTW;pZviZ0HrJ@rS=vlNZG zmc7HpW@7SOE7*62HSf>A3dcO)$YUjW`>i#6C-md_Uc4FLOY_Zg77Q%N5VL4&0^Dq( zai|=w%#ww4FR6*BKZZ_XrzM^}^ya;0Su*uf;o2*jXp?KOi(hhOxt!2BUBt8lOT@fP zJls^C0F?RF$C~n7WsNUyMeUV4yr=fmkh#g@8vdtq9i*LG4ZNtCpBXwb?6z~X@t>AQ zk3C6}*hDH)*g&RqvBAw76a?96IC>hQZss})C1fIsP%B$oKeKNPeQN5{(3@I?A=%CZ zLUxX6Zp0r*P)5(DzaL&$Nbl*Vs@OW!xs$KN*;|r(4>WK!NOeaOZhO5Qj~e^o4fLY5 zXDW2G+<@A`hJ8bwi_94qVdERse3@W6ny)KMHXuFM{{d>A88xC`%X-pG;M;^yReW2& z*y5DUzc^R>>&A(39&vUfleM|Io26Dtl(z>v7EWotgmkl!S@!dj1+4L-_3Lxg_LK%v zH1K+l;`H8>h*YCbv+|PbPi>Mc*})@Bz+}B;8j&#`C7>IwN}u&a*3`0=TWWc#Ds3&Z zIG_a%JYyeB(tXcx-`ZWC=j9h=d21={DGiwC(xvL09jwKcdrgz%&{pb4&}rKl@4Qz#m}}TOe@Z08Gf#$)H^i9PKOpdeaC@9(vNZ3{28N=i=M>$R@B zZ1y5)e);nsY!1`*?2pa(2agg-y-X95X?4CD?Ch3qR-cH7v$CVLh17Dx?4^L0ynRK- zrI#X6eLVT>LZC?20NS&Quk z{nveC7IG4|x6ltdspP&H@@aPJ6MzOf|2@^bqF<_oKcLzwvxJOktgU%%2TH{o@3L29 zy6~Lg=mk|3z}^X|*omOB*yAmaK%wu}vbnQscpITVhGTm9ooZucv)hy{1XzR^l{BK= za1_Lv%)0+>q=|~gNx@0C@l-lwY9CxWLs+=V2A1|N&+pW7nq_W6m{@1mLS~@7Q2!6FR~K*nOTF zJbzQ);9)m;tHsW&#@Kr6c)FA}>5u_W0JKkC8^2awu5kZs8BKCXIv59?)bVpt+XQf?@rym$&~OYv8Ql-+ja%VH7SVIS>1PI>>TCznT=bS)YQFrbL;9sBDy7!s3E^ zV;C_@-Lns1Zs4W_Y^YphuS+E+`6!6I?BM`A6+YeUBCDf5J2WMVjk;MV^?(dy-x@pC zwCly$(Z(93_^G=Bk?)?3j2Pq?4)fbeu6!J%8$9^{hidqvE8mfvjZ5Cu#QByN${bju z$IKJQhkKJ_gZ{boe%Td0oV5~p`@!c~loZg^(7L5~d{lq1b{00bZ0*-kJ#+TZ*{P`2@!BMbn)m5ToG+P> zbzFK!z2Ga2^D=Ab%5gya^fCC^Fr{@P-eFSh*EhVl>2eX>(>+4aU9TT19i#T&l8P5Y zE5?qF&xaOy!^)ub$MmdX#n7{ZIqhbP{-sSPFkC+V39q>2x(U>58)8zeKY zaG>@+^c1MS1M{0B_OpV~I(R5t24|*A)RTACGGJrTme(g!j^lR~>atV%!Bu=n{KLXn zPjL``FIDyWsVUm3b7ueZ0rG>}=?L->NVFrlRi}Q_xNR)TimT!=5a%j77V}~0`_pR` z8V^YwW-QeM&O~l>9YOxq3(%MG4l4cvTJIwgS6FDY_^~8(f1`pE3}O9zZtN9I6)2U< zcZV`K44NfOGDVxG*^-8kYlyWO_|Jn3qLTnZXJ+=@N!e-7wnY7W#=z`yV}@s^cF;=# z7&cNT$>493$65vd-M&3Ng%?R4WKkyWq+n9`AOxYrd7;(3{&ueBbZP zo*vtzkp^NnS!XX6TYXQuZQZ5Q4`fT!ZmfD4&W_y0l25U8efP~*L~fh3Xuzs=R{IZ~ zXZG9P&N~D{Vy=c+49=ZA6CUe-dvwdJ6ajh7lX4cU z1|B_jk2)&-t7y>Gwz&l*|16{NXO?}+Mlt%ZMUNsP8#l9l&pM}W<_Q7;Q*c1hIU!;_c9y^B=bWZ=WiM(WN-1Z~s1vJV2tgRvL zLVueO*O^Ni^iU*!wv$`}P%acbx)s>p zfvjQ03QTkti+PPY$m5YN0dIs!X9EyU=a3N$l&MTHmuA`I{{!E>kV@E6)N;Mm4AYRCCogU}R zwMh6`62WyEpm(h6$GP2M0dtdQ)xZduL zWi8TsfU5Aa(~5%)Lm9b1={^BI)5!CIKz>kBqNWR*5F6y+RE^@B?0#+izZhXx50f1P zLcb3C1W#^0k5dd7en@7))mhbVnD2US0?qrN_!39A_X4b^$Qk(aH~g9W(n!Jy}hyCOW-)329bO3PouP zwe4ixo2aBG?qUU^r_)USw))uQJ?(`@Ernu?nLcOQF#D?L&i9tDp7O(E8z9d&)bSXx z@aPYtnZ-~TZ6QDJRd0v7CAh_cTF5CP>B!!ehb;Yj=Wh4f>WSN*~{GZ;nQCT(cA@}MjnSFUHH$q zgSq<>p;S$|z7IOfEhnfm2XcQ;A_+x>Z4bl}XS;vt(bAMZ23~@|5a@h2YVV*uN(MHZ zN*)9Dj}AUSlWgjzv<`|le*acfo$ZOjJPvHi-U+)i{@_?yNd;)e(i$T zFZa%;@OBP1TF-!1oU)0sv?1G*$iL^f*ZB90vOX zkNyup!yiy0E-C{9+P_-u9|T0&>}d*#5immBUi_edeKl&{aHNM)HbKN5&8R%!k_Ofr zg=poUN)0h`GMONVCb*lhK~UZyM#FJ1euHs{L_Q>PW7qaEX9b0X;+=UOh>d`h7!V5hn)FMs%<{u&2sF|}1&HAo zQvA~%&!N8mx$XZj^v~!V2gT!XRB-b~@rJ1ZHxWc)Z=l}t$_DKNa^XjP7X5}wj#j^! zQl=SF$A{`V&tUL)D{XN)cVe=>gM{n)xM&sW;|x<0p6j1O6zSiT+i;AW{)X&>dG(fy zh3o1g1|!46+O>$6SjzxS~w5o)03pT8HF3TG}uiF7|R#s>+HoF0|q z(qk1K5f7sYN}el)GN;r9&3o}3J>(|eQWS1!{-|qL!@Ce;U2aDns3cA!6ysj2cl3xR zd(=xycKIRk>aTXL$<* z%ZGX!Id67_er-MZIAn)(PI+)IalW%C7TQlQ)2^<0)vhfWY$4FKSC&*{hwF`yn$rbc zTS?~6iJUemu-nVEuA60K=W|yEzDexpEL&dAGRW%e3WdrKT?FwU^nN+gB?d3pu%8#b ztm&5W;G-LR(v{%cUYb1%Sxo;4QX2hh5 zeOh!MfK_dzd)tFuJHk#rFkvSzOpzLjDfC>_FToODC?3gF1!_!wqRrHH%NLZ*wI{ID z0cwMzMD%6e{<4ijJy0PI_Il!klc_#x#th*yJMUEY!A=cymYV*1-OsA-rBZUHMnDf= zbS%NTQ=0MEADU6OCsYkPypb>P#4lfrqbP_Oi$Rk|2N#XM_I&|0cBi|hdb(xM*{MGn zw)x6ULIPM{!0f;GUhzutr0-k(v5p6-Zas8#OXjjpR|^S{0PFRjl1G(`Vg2Uios`HKHY7OwttPR4(K`CGx`zoRD$ zEdHHK;$Ly$Pt-|)1)g)a`d?noJ@fy3I@MeBb z1FNd_Y$Sb6F7!#z#w5pM)4cr_E1DV>5XA&bR6nYDc}%d%@P(hTFtDFS9Cmsl*!>-N z|5|Y4f)nUl}SWC{n`lNeMmjbv+~2oyz#7 zf|aU|TrPO>=xZWh8^A2$!ui4CuJmA`?fmqIV6Xw1$cNxP;OU^4`pT;A*sQ-ss-_ZZ z#X(mI`PtxrHv<;W3+>#tBqxNvMyC%SfpXcMCGe^v$B+Ts{8?Lz4$+lEX`Hm9b635kCP6*MH z3K8n^$sd!uIow(%_qj(JbjkY^NM5C7rmC0x*ah>6LZPMjuO~atxnqTQ)P1xBqurMz z>W?B(%-F9dinjDelM`jdYJ-W0jb3@x?9OOYdFrf3Lp^5|F%RTl3Kn5Tp#nNYu&+%K z)6LE%BSw8m6zvX67x*43G3st7-qv7)@$({~L~kWKeOo@)EB(l*6sKWl zqJ4B)OeKUSR-kf&El>g=s`)_Yt!Tc4?kyb*iPPMQV~7GJ;i{0 zZ2&-dxJFwqr%G_FaXrE7$75lmGq{%4^*d9uzl;kx{of1UrrUua&2>X$=!a? zld~U#D~b6l118vw$#4=!`l9LRX(@0-6&(3t)ITlwnnc{F+GMF8g+;Q`JptAWkm|a} zxgVrFq=WH6oqh1%TH*~^B(+-AQ}R2Y?k7>t3EljZRAVg!>a$}57@XS6Ifg)|?3|Jh zd6s=w?tq#u3BRiXT6IU8taqfwd&%$LfzRbqE33YIJD&TfDT2PnAZ+V}*3f4`E)Z&6 ze=9_L)YheZ*<)%;z!WUyIV8<&{<5jLR&6Zh$$d;kimT4Wi+m)3h)7r+TU^1%c@Uf} z`Rj&{<}!x=kSaJ)gGO73;M^$vsqBC5t^V9h@_!nHe69I`0(NtK@)KztO;lxz7FyD_@cRJtC3PT8Iyap~iiunXGKs$Kr-Bf@ zBpWLae_y#5%cpLW(f3X!20C1CaW@)7Ps`RExN6JqZO!V#mxmB@No8K52d+cU36^$h z@}L)T-hE0=&WyQXzB6@1wR%=YdK@h>Ml(?Gr{frnHqAXSocfIJ08^nbD!3Hv5p7kwER+YZy zKxH{&>6?itbLOn(fw0R`({y!!y&d#fhK69amW@uU{hO$4-T1Fl&qr>N#uDIh*@2Sy zOK{l`O%d*mwC!Xz3S>pbtalG7{bt7(iAjL8< zDy*x%=3Hx-xp$}8asT^-&y5f@@GQi*l$KhmpUdD!%&b34xF0xn1ZEpA@Fc;Ktpu)h z;!kPJf03#^(8h%(f1Mqk=WG8dEj^dDe_!%EI{EkZrT-vt|4oW0^H)jnU#uRLj!Rnq z|GDiy#fATxg8w5aK0k_IBa#1FiT|5gpQq~o^N9bi@%#T?AHZK56)^bupUp%6Ze{)y zpn|dIf78Bp-h1%p9iKxZ|7c=5w`Tvv!hf_mopb#L+5cMD{@x4xZ>#?YexDEhd*{@j zH;)VXUj)1XUF5$3F8_1r|Hae(k8saFR*%G$`~EZJ^cULoU&z}3SM2`7zMRY2|8C{_ z-+9=e`TjS-J~yuaK-bQB{11`)U$pA|A#k{~@2@ewu~D0NdIpGLBosO33Tte>_Co9a zTd2DMV9I^*C+QsqY>LC-htr$ujb)bTNU9KZHEqYgFbnXn;iB)=fTq#F&lbYH1Yjz>p z91d;W(MKh0O9f@ADpT&K*-ae!Fc_ISFwoz^)6>SZ;^pFMT#~*$aGYqgYa_zE*6+b1 zG`4J{bLN4lLI8_$+F;tN_rjDa1m>fG(YJe=Vl>M zFKdZ)Wcz#agLqdwpv}91XKGuz{S8Qe65jQW_~?+!#fAEpD`1;EG&7{1B6GO8cAZ0 z*o5H@zBNFoXjYyl_JwrtIWQkM8ig8b{a$)&ZZR3iIdPZBw zEt~$D#H-&(VPrrK6P0|zUNI+DTFBC^@IlGKWv6VJQkDW)$4C{U6UOP^a`$C$i2}B3 z_$UvoIb-j9l)d8NWvz+u4>Ig^5?3k$+i|O~61z#8Q zUz3mtuCSa^+8I$-^a@?E1FCjwKAsILjO7y?%P2{q{CRE3}F!WL{{ zhfDZ9NAz|PFlB%=L15}P6MJHO-*0|+bi|<%_`(=|hEyRAa>3HXqwZu>_8jF&lNtgP zVAFZkKy4S#lA#Y1u?=EQ&Xgq=&wfY80nI-kZ-Wox*ac5`v_!S8Vihp-5`J?`_ZIt| z-vbtwlT%7hp?!KmT)^wqWl~G< z2~QUa$bXYif;kCjB?Fy~&%^|i#BKe9v_u5yM8FBokvfpaNZ+e}fSvy_R~XZSWfW8W zT<8Y7Z^be1ZybAB-Yx~f$URjcdtUPs*w8d44zs9`=aRR$a2M+vJ$>mV{l~wJ(nXzr z#6?VhLJ7fSW*zKhp~Ig9BNi}x17=2woY}x`7Ti56QafZ~Vqgm{NZ4XkNzwG4I4G{( zBSa}ZNhe&fW8b#?1*0vvyYQyF6ngqA5d37`@%0%7U?;do2o(FSfRy~>v@}eUU@7oq zj6qzhqwvWA^tYkkz!kl2T|Ipt55KZUmpjqOM?nI3-+a#mc}1cMcUH6z=DlX?S0`yU zg@E=z&?;3qfp1klr&Tbd%S!>V2W?oDrjIu827HxCRi@B3!_Qt%*LVQnQ@3MirGR;N_2j$q(X2+}F z9gTr9e6AG2o<8TqWO3%Yi!w4Cev=kL76#V;9=C0+_u^j=mSV`@=z3J|#!J(i9o}+m z9%(vj;~=)3BjD-$a7QTiI%x)fJ=1KVHP&cG1+ft=r`iM%%5~>XHoG6AZl}ESlB=||=R%ZhkppU{j!m$PqzdO5u2oJv0Htq7xZ1JOjo zv52m2r`f{=LB^DTQx?u`|jq~z4$PMMs7(RJ4Dm7*ag9= z9|=0yT(R=@-Gg}X*R=prn!8q+nG+urZM4wy&`U;6unNgf`>W0Me*ApN-%fu@`4bF; zS)jF3!;Newat_Jd#Ho>5{7Bn*!c1xzx_wuIH&F4}2>iaRwXmt?;lOmc72NoY@_>i;h_{^es1h1T{i*qqAOh z?$gS1QNYN^W37$U9HJD#9wu{ir4w&D5BO6l<|*`~25{OcI)c3CD%IAhq6%IdyTUf_+Y8+iE`r>iHWazRC`_=(JO7U;4%- zU<8KZ`|Ws=+t!=2x!>5E$~D{_YQ}@4ViHD(swXwSjaWxEKU(9b9>zv%z;cupn$?bU z#v&}}Wd>`zl9s>BH6Ng2k1WqFPgak2m2Fps-bGhbJM{~ z&k}viGdNEg?D=Pn#FP4VxCiGR)w}S1MmaHp&PczZN1mW>ATv96_B3!xe^0DJr2J5Y zO}NU)>jAoQPq`(bl3q9eLgoNd`9{EQQeS%X^9pUsml?m_d(}oC=%Z=dWBZImwn+*$ zcnH^X(h#5*=&x`Pu1NRy`;3)=RHk3?NCi`Q^tI#X{f&v@bo>&)7v*FS3Ggrr%iMpX z20=HM;s5fmmd|^S4FVkk9VV-^@6G_Y3cE>@@D>t&$NV1ZF)iGlqV=q&X7)xY_jKdN>DxL#4*-Crc& zxOZHLU{%PzNcxI{3*gX6wfpqxO=_J;XuL*;-K&Cr7m)3;qiM0%)3X`4GO|eGnJg z&teC)O0#R1I^XIp?|NQxjH!;o4t3m8bBSyJqw;{{qwZUE|>pmsNBjO;_my@8e z`__zHPpb@(k)~B*gk4*Q2I%=;W(jKaX^^LdXziP^UCvWVsu!F5hKjm!C%g}IuS5%N zUWe>T1D~JwbUxc?DUEvC%*Cy~X8qY&k^jOM+A^4-KC|Qsz8*!{eTzQ3po!`m^fv6p z;3HXwV{uk00pDNWW~t`ho_Mmd^>CaW^2x{!$SKH^e3H#kv7-4vSFa`zmGVTVH(i4W zvMU4BD#7}4L#5|XVwSv=81sZZS|<+kX9*T?@Ylq|ECgqKi6{k&l`t8 zwC_D8gFx62QCGmIdhwYF_(a8OG67@n&SMJBi;(+YUwH}hQrz!ds5<7ruenb2V(_iW i4*xs6-i2R3&n`Uj%HS3WNhrt7DGOJUDv*2{@P7aYXMHpP From 1ee1eb07ea37147ece8a2f5dc20b6e46ab99296d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0smail=20=C3=87A=C4=9EDA=C5=9E?= Date: Fri, 9 Jan 2026 12:54:37 +0300 Subject: [PATCH 29/30] Update AI Assistant description in overview.md Removed mention of development tasks from AI Assistant description. --- docs/en/studio/overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/studio/overview.md b/docs/en/studio/overview.md index f90bd78403..228f10afce 100644 --- a/docs/en/studio/overview.md +++ b/docs/en/studio/overview.md @@ -101,7 +101,7 @@ This pane is dedicated to managing Kubernetes services. It simplifies the proces ### AI Assistant -The AI Assistant is an integrated chat interface within ABP Studio that provides intelligent assistance for ABP-related questions and development tasks. You can access it from the left sidebar by clicking the AI icon. +The AI Assistant is an integrated chat interface within ABP Studio that provides intelligent assistance for ABP-related questions. You can access it from the left sidebar by clicking the AI icon. ![ai-assistant](./images/overview/ai-assistant.png) From f58ef680ad0ae6f33e9fa290686858cb19b9bb58 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 10 Jan 2026 19:31:06 +0000 Subject: [PATCH 30/30] docs: Add SEO descriptions to modified documentation files Related to PR #24602 --- .../artificial-intelligence/microsoft-extensions-ai.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/en/framework/infrastructure/artificial-intelligence/microsoft-extensions-ai.md b/docs/en/framework/infrastructure/artificial-intelligence/microsoft-extensions-ai.md index eac8d3ad69..b0247b6b18 100644 --- a/docs/en/framework/infrastructure/artificial-intelligence/microsoft-extensions-ai.md +++ b/docs/en/framework/infrastructure/artificial-intelligence/microsoft-extensions-ai.md @@ -1,3 +1,10 @@ +```json +//[doc-seo] +{ + "Description": "Explore the Microsoft.Extensions.AI library documentation to seamlessly integrate AI services with the ABP Framework in your applications." +} +``` + # Microsoft.Extensions.AI [Microsoft.Extensions.AI](https://learn.microsoft.com/en-us/dotnet/ai/microsoft-extensions-ai) is a library that provides a unified API for integrating AI services. It is a part of the Microsoft AI Extensions Library. It is used to integrate AI services into your application. This documentation is about the usage of this library with ABP Framework. Make sure you have read the [Artificial Intelligence](./index.md) documentation before reading this documentation.