Browse Source

Merge branch 'dev' into auto-merge/rel-10-1/4250

pull/24556/head
selman koc 9 months ago
committed by GitHub
parent
commit
46d40686e6
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      common.props
  2. 2
      docs/en/Community-Articles/2025-09-02-training-campaign/post.md
  3. 76
      framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs
  4. 31
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs
  5. 6
      framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/ObjectExtending/MvcUiObjectExtensionPropertyInfoExtensions.cs
  6. 38
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs
  7. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinderProvider.cs
  8. 54
      framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs
  9. 8
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQOptions.cs
  10. 11
      framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiObjectExtensionPropertyInfoExtensions.cs
  11. 7
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs
  12. 4
      framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLoggerFactory.cs
  13. 9
      framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs
  14. 98
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs
  15. 33
      framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/PostConfigureAbpRabbitMqEventBusOptions.cs
  16. 6
      framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs
  17. 49
      framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs
  18. 69
      framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs
  19. 116
      framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs
  20. 67
      framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs
  21. 2
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceDictionary.cs
  22. 2
      framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseCollection.cs
  23. 2
      framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs
  24. 2
      framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ParameterRebinder.cs
  25. 39
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs
  26. 104
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs
  27. 2
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs
  28. 27
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithJsonProperty.cs
  29. 21
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs
  30. 98
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs
  31. 165
      framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs
  32. 5
      modules/docs/app/VoloDocs.Web/Pages/Error.cshtml.cs

4
common.props

@ -1,8 +1,8 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Version>10.1.0-rc.1</Version> <Version>10.2.0-preview</Version>
<LeptonXVersion>5.1.0-rc.1</LeptonXVersion> <LeptonXVersion>5.2.0-preview</LeptonXVersion>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn> <NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl> <PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io/</PackageProjectUrl> <PackageProjectUrl>https://abp.io/</PackageProjectUrl>

2
docs/en/Community-Articles/2025-09-02-training-campaign/post.md

@ -1,6 +1,6 @@
# IMPROVE YOUR ABP SKILLS WITH 33% OFF LIVE TRAININGS! # 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? #### Why Join ABP.IO Training?

76
framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs

@ -1,86 +1,16 @@
using System; using System;
using System.Threading.Tasks;
using Duende.IdentityModel.Client;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.Threading;
namespace Microsoft.AspNetCore.Authentication.Cookies; namespace Microsoft.AspNetCore.Authentication.Cookies;
public static class CookieAuthenticationOptionsExtensions public static class CookieAuthenticationOptionsExtensions
{ {
/// <summary> /// <summary>
/// Introspect access token on validating the principal. /// Check the access_token is expired or inactive.
/// </summary> /// </summary>
/// <param name="options"></param> [Obsolete("Use CheckTokenExpiration method instead.")]
/// <param name="oidcAuthenticationScheme"></param>
/// <returns></returns>
public static CookieAuthenticationOptions IntrospectAccessToken(this CookieAuthenticationOptions options, string oidcAuthenticationScheme = "oidc") public static CookieAuthenticationOptions IntrospectAccessToken(this CookieAuthenticationOptions options, string oidcAuthenticationScheme = "oidc")
{ {
options.Events.OnValidatePrincipal = async principalContext => return options.CheckTokenExpiration(oidcAuthenticationScheme, null, TimeSpan.FromMinutes(1));
{
if (principalContext.Principal == null || principalContext.Principal.Identity == null || !principalContext.Principal.Identity.IsAuthenticated)
{
return;
}
var logger = principalContext.HttpContext.RequestServices.GetRequiredService<ILogger<CookieAuthenticationOptions>>();
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<OpenIdConnectOptions> GetOpenIdConnectOptions(CookieValidatePrincipalContext principalContext, string oidcAuthenticationScheme)
{
var openIdConnectOptions = principalContext.HttpContext.RequestServices.GetRequiredService<IOptionsMonitor<OpenIdConnectOptions>>().Get(oidcAuthenticationScheme);
if (openIdConnectOptions.Configuration == null && openIdConnectOptions.ConfigurationManager != null)
{
var cancellationTokenProvider = principalContext.HttpContext.RequestServices.GetRequiredService<ICancellationTokenProvider>();
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);
} }
} }

31
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Frozen;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
@ -23,7 +24,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker;
public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelperService<TTagHelper> public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelperService<TTagHelper>
where TTagHelper : AbpDatePickerBaseTagHelper<TTagHelper> where TTagHelper : AbpDatePickerBaseTagHelper<TTagHelper>
{ {
protected readonly Dictionary<Type, Func<object, string>> SupportedInputTypes; protected readonly FrozenDictionary<Type, Func<object, string>> SupportedInputTypes;
protected readonly IJsonSerializer JsonSerializer; protected readonly IJsonSerializer JsonSerializer;
protected readonly IHtmlGenerator Generator; protected readonly IHtmlGenerator Generator;
@ -103,7 +104,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
return string.Empty; return string.Empty;
} }
} }
}; }.ToFrozenDictionary();
} }
protected virtual T? GetAttribute<T>() where T : Attribute protected virtual T? GetAttribute<T>() where T : Attribute
@ -136,7 +137,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
? await ProcessButtonAndGetContentAsync(context, output, "calendar", "open") ? await ProcessButtonAndGetContentAsync(context, output, "calendar", "open")
: ""; : "";
var clearButtonContent = TagHelper.ClearButton == true || (!TagHelper.ClearButton.HasValue && TagHelper.AutoUpdateInput != true) 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); var labelContent = await GetLabelAsHtmlAsync(context, output, TagHelperOutput);
@ -269,7 +270,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
{ {
var attrList = new TagHelperAttributeList(); var attrList = new TagHelperAttributeList();
if(options == null) if (options == null)
{ {
return attrList; return attrList;
} }
@ -401,29 +402,29 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
attrList.Add("data-visible-date-format", options.VisibleDateFormat); attrList.Add("data-visible-date-format", options.VisibleDateFormat);
} }
if(!options.InputDateFormat.IsNullOrEmpty()) if (!options.InputDateFormat.IsNullOrEmpty())
{ {
attrList.Add("data-input-date-format", options.InputDateFormat); 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); var ranges = options.Ranges.ToDictionary(r => r.Label, r => r.Dates);
attrList.Add("data-ranges", JsonSerializer.Serialize(ranges)); 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()); 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()); 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)); attrList.Add("data-options", JsonSerializer.Serialize(options.Options));
} }
@ -443,7 +444,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
attrList.Add("id", options.PickerId); attrList.Add("id", options.PickerId);
} }
if(!options.SingleOpenAndClearButton) if (!options.SingleOpenAndClearButton)
{ {
attrList.Add("data-single-open-and-clear-button", options.SingleOpenAndClearButton.ToString().ToLowerInvariant()); attrList.Add("data-single-open-and-clear-button", options.SingleOpenAndClearButton.ToString().ToLowerInvariant());
} }
@ -614,7 +615,8 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
{ {
return string.Empty; return string.Empty;
} }
var labelTagHelper = new LabelTagHelper(Generator) { var labelTagHelper = new LabelTagHelper(Generator)
{
ViewContext = TagHelper.ViewContext, ViewContext = TagHelper.ViewContext,
For = modelExpression For = modelExpression
}; };
@ -764,7 +766,8 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
TagHelper.Size = attribute.Size; TagHelper.Size = attribute.Size;
} }
return TagHelper.Size switch { return TagHelper.Size switch
{
AbpFormControlSize.Small => "form-control-sm", AbpFormControlSize.Small => "form-control-sm",
AbpFormControlSize.Medium => "form-control-md", AbpFormControlSize.Medium => "form-control-md",
AbpFormControlSize.Large => "form-control-lg", AbpFormControlSize.Large => "form-control-lg",
@ -785,14 +788,14 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
protected virtual async Task<string> GetValidationAsHtmlByInputAsync(TagHelperContext context, protected virtual async Task<string> GetValidationAsHtmlByInputAsync(TagHelperContext context,
TagHelperOutput output, TagHelperOutput output,
[NotNull]ModelExpression @for) [NotNull] ModelExpression @for)
{ {
var validationMessageTagHelper = var validationMessageTagHelper =
new ValidationMessageTagHelper(Generator) { For = @for, ViewContext = TagHelper.ViewContext }; new ValidationMessageTagHelper(Generator) { For = @for, ViewContext = TagHelper.ViewContext };
var attributeList = new TagHelperAttributeList { { "class", "text-danger" } }; 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) if (nameAttribute != null)
{ {

6
framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/ObjectExtending/MvcUiObjectExtensionPropertyInfoExtensions.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Frozen;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -8,7 +9,8 @@ namespace Volo.Abp.ObjectExtending;
public static class MvcUiObjectExtensionPropertyInfoExtensions public static class MvcUiObjectExtensionPropertyInfoExtensions
{ {
private static readonly HashSet<Type> NumberTypes = new HashSet<Type> { private static readonly FrozenSet<Type> NumberTypes = new HashSet<Type>
{
typeof(int), typeof(int),
typeof(long), typeof(long),
typeof(byte), typeof(byte),
@ -33,7 +35,7 @@ public static class MvcUiObjectExtensionPropertyInfoExtensions
typeof(float?), typeof(float?),
typeof(double?), typeof(double?),
typeof(decimal?) typeof(decimal?)
}; }.ToFrozenSet();
public static string? GetInputFormatOrNull(this IBasicObjectExtensionPropertyInfo property) public static string? GetInputFormatOrNull(this IBasicObjectExtensionPropertyInfo property)
{ {

38
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinder.cs

@ -2,27 +2,57 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.Extensions.Logging;
using Volo.Abp.Timing; using Volo.Abp.Timing;
namespace Volo.Abp.AspNetCore.Mvc.ModelBinding; namespace Volo.Abp.AspNetCore.Mvc.ModelBinding;
public class AbpDateTimeModelBinder : IModelBinder public class AbpDateTimeModelBinder : IModelBinder
{ {
private readonly ILogger<AbpDateTimeModelBinder> _logger;
private readonly DateTimeModelBinder _dateTimeModelBinder; private readonly DateTimeModelBinder _dateTimeModelBinder;
private readonly IClock _clock; private readonly IClock _clock;
private readonly ICurrentTimezoneProvider _currentTimezoneProvider;
private readonly ITimezoneProvider _timezoneProvider;
public AbpDateTimeModelBinder(IClock clock, DateTimeModelBinder dateTimeModelBinder) public AbpDateTimeModelBinder(
ILogger<AbpDateTimeModelBinder> logger,
DateTimeModelBinder dateTimeModelBinder,
IClock clock,
ICurrentTimezoneProvider currentTimezoneProvider,
ITimezoneProvider timezoneProvider)
{ {
_clock = clock; _logger = logger;
_dateTimeModelBinder = dateTimeModelBinder; _dateTimeModelBinder = dateTimeModelBinder;
_clock = clock;
_currentTimezoneProvider = currentTimezoneProvider;
_timezoneProvider = timezoneProvider;
} }
public async Task BindModelAsync(ModelBindingContext bindingContext) public async Task BindModelAsync(ModelBindingContext bindingContext)
{ {
await _dateTimeModelBinder.BindModelAsync(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 (dateTime.Kind == DateTimeKind.Unspecified &&
_clock.SupportsMultipleTimezone &&
!_currentTimezoneProvider.TimeZone.IsNullOrWhiteSpace())
{
try
{
var timezoneInfo = _timezoneProvider.GetTimeZoneInfo(_currentTimezoneProvider.TimeZone);
dateTime = new DateTimeOffset(dateTime, timezoneInfo.GetUtcOffset(dateTime)).UtcDateTime;
}
catch
{
_logger.LogWarning("Could not convert DateTime with unspecified Kind using timezone '{TimeZone}'.", _currentTimezoneProvider.TimeZone);
}
}
bindingContext.Result = ModelBindingResult.Success(_clock.Normalize(dateTime));
} }
} }

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpDateTimeModelBinderProvider.cs

@ -50,6 +50,6 @@ public class AbpDateTimeModelBinderProvider : IModelBinderProvider
{ {
const DateTimeStyles supportedStyles = DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AdjustToUniversal; const DateTimeStyles supportedStyles = DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AdjustToUniversal;
var dateTimeModelBinder = new DateTimeModelBinder(supportedStyles, context.Services.GetRequiredService<ILoggerFactory>()); var dateTimeModelBinder = new DateTimeModelBinder(supportedStyles, context.Services.GetRequiredService<ILoggerFactory>());
return new AbpDateTimeModelBinder(context.Services.GetRequiredService<IClock>(), dateTimeModelBinder); return ActivatorUtilities.CreateInstance<AbpDateTimeModelBinder>(context.Services, dateTimeModelBinder);
} }
} }

54
framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs

@ -14,41 +14,56 @@ namespace Microsoft.Extensions.DependencyInjection;
public static class CookieAuthenticationOptionsExtensions public static class CookieAuthenticationOptionsExtensions
{ {
/// <summary> /// <summary>
/// Check the access_token is expired or inactive. /// Check if the access_token is expired or inactive.
/// </summary> /// </summary>
public static CookieAuthenticationOptions CheckTokenExpiration(this CookieAuthenticationOptions options, string oidcAuthenticationScheme = "oidc", TimeSpan? advance = null, TimeSpan? validationInterval = null) public static CookieAuthenticationOptions CheckTokenExpiration(this CookieAuthenticationOptions options, string oidcAuthenticationScheme = "oidc", TimeSpan? advance = null, TimeSpan? validationInterval = null)
{ {
advance ??= TimeSpan.FromMinutes(3); advance ??= TimeSpan.FromMinutes(3);
validationInterval ??= TimeSpan.FromMinutes(1); validationInterval ??= TimeSpan.FromMinutes(1);
var previousHandler = options.Events.OnValidatePrincipal;
options.Events.OnValidatePrincipal = async principalContext => options.Events.OnValidatePrincipal = async principalContext =>
{ {
if (principalContext.Principal == null || principalContext.Principal.Identity == null || !principalContext.Principal.Identity.IsAuthenticated) if (principalContext.Principal == null || principalContext.Principal.Identity == null || !principalContext.Principal.Identity.IsAuthenticated)
{ {
await InvokePreviousHandlerAsync(principalContext, previousHandler);
return; return;
} }
var logger = principalContext.HttpContext.RequestServices.GetRequiredService<ILogger<CookieAuthenticationOptions>>(); var logger = principalContext.HttpContext.RequestServices.GetRequiredService<ILogger<CookieAuthenticationOptions>>();
var tokenExpiresAt = principalContext.Properties.GetString(".Token.expires_at"); var tokenExpiresAt = principalContext.Properties.GetString(".Token.expires_at");
if (!tokenExpiresAt.IsNullOrWhiteSpace() && DateTimeOffset.TryParseExact(tokenExpiresAt, "o", null, DateTimeStyles.RoundtripKind, out var expiresAt) && if (!tokenExpiresAt.IsNullOrWhiteSpace() && DateTimeOffset.TryParseExact(tokenExpiresAt, "o", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var expiresAt) &&
expiresAt < DateTimeOffset.UtcNow.Subtract(advance.Value)) 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); await SignOutAndInvokePreviousHandlerAsync(principalContext, previousHandler);
return; return;
} }
if (principalContext.Properties.IssuedUtc != null && DateTimeOffset.UtcNow.Subtract(principalContext.Properties.IssuedUtc.Value) > validationInterval) 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"); var accessToken = principalContext.Properties.GetTokenValue("access_token");
if (!accessToken.IsNullOrWhiteSpace()) if (!accessToken.IsNullOrWhiteSpace())
{ {
var openIdConnectOptions = await GetOpenIdConnectOptions(principalContext, oidcAuthenticationScheme); 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.");
await InvokePreviousHandlerAsync(principalContext, previousHandler);
return;
}
var response = await openIdConnectOptions.Backchannel.IntrospectTokenAsync(new TokenIntrospectionRequest var response = await openIdConnectOptions.Backchannel.IntrospectTokenAsync(new TokenIntrospectionRequest
{ {
Address = openIdConnectOptions.Configuration?.IntrospectionEndpoint ?? openIdConnectOptions.Authority!.EnsureEndsWith('/') + "connect/introspect", Address = introspectionEndpoint,
ClientId = openIdConnectOptions.ClientId!, ClientId = openIdConnectOptions.ClientId!,
ClientSecret = openIdConnectOptions.ClientSecret, ClientSecret = openIdConnectOptions.ClientSecret,
Token = accessToken Token = accessToken
@ -56,15 +71,15 @@ public static class CookieAuthenticationOptionsExtensions
if (response.IsError) if (response.IsError)
{ {
logger.LogError(response.Error); logger.LogError("Token introspection error: {Error}", response.Error);
await SignOutAsync(principalContext); await SignOutAndInvokePreviousHandlerAsync(principalContext, previousHandler);
return; return;
} }
if (!response.IsActive) if (!response.IsActive)
{ {
logger.LogError("The access_token is not active."); logger.LogError("The access_token is not active.");
await SignOutAsync(principalContext); await SignOutAndInvokePreviousHandlerAsync(principalContext, previousHandler);
return; return;
} }
@ -73,16 +88,18 @@ public static class CookieAuthenticationOptionsExtensions
} }
else 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); await SignOutAsync(principalContext);
} }
} }
await InvokePreviousHandlerAsync(principalContext, previousHandler);
}; };
return options; return options;
} }
private async static Task<OpenIdConnectOptions> GetOpenIdConnectOptions(CookieValidatePrincipalContext principalContext, string oidcAuthenticationScheme) private static async Task<OpenIdConnectOptions> GetOpenIdConnectOptions(CookieValidatePrincipalContext principalContext, string oidcAuthenticationScheme)
{ {
var openIdConnectOptions = principalContext.HttpContext.RequestServices.GetRequiredService<IOptionsMonitor<OpenIdConnectOptions>>().Get(oidcAuthenticationScheme); var openIdConnectOptions = principalContext.HttpContext.RequestServices.GetRequiredService<IOptionsMonitor<OpenIdConnectOptions>>().Get(oidcAuthenticationScheme);
var cancellationTokenProvider = principalContext.HttpContext.RequestServices.GetRequiredService<ICancellationTokenProvider>(); var cancellationTokenProvider = principalContext.HttpContext.RequestServices.GetRequiredService<ICancellationTokenProvider>();
@ -94,9 +111,20 @@ public static class CookieAuthenticationOptionsExtensions
return openIdConnectOptions; return openIdConnectOptions;
} }
private async static Task SignOutAsync(CookieValidatePrincipalContext principalContext) private static async Task SignOutAsync(CookieValidatePrincipalContext principalContext)
{ {
principalContext.RejectPrincipal(); principalContext.RejectPrincipal();
await principalContext.HttpContext.SignOutAsync(principalContext.Scheme.Name); await principalContext.HttpContext.SignOutAsync(principalContext.Scheme.Name);
} }
private static Task InvokePreviousHandlerAsync(CookieValidatePrincipalContext principalContext, Func<CookieValidatePrincipalContext, Task>? previousHandler)
{
return previousHandler != null ? previousHandler(principalContext) : Task.CompletedTask;
}
private static async Task SignOutAndInvokePreviousHandlerAsync(CookieValidatePrincipalContext principalContext, Func<CookieValidatePrincipalContext, Task>? previousHandler)
{
await SignOutAsync(principalContext);
await InvokePreviousHandlerAsync(principalContext, previousHandler);
}
} }

8
framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQOptions.cs

@ -5,11 +5,11 @@ namespace Volo.Abp.BackgroundWorkers.TickerQ;
public class AbpBackgroundWorkersTickerQOptions public class AbpBackgroundWorkersTickerQOptions
{ {
private readonly Dictionary<Type, AbpBackgroundWorkersCronTickerConfiguration> _onfigurations; private readonly Dictionary<Type, AbpBackgroundWorkersCronTickerConfiguration> _configurations;
public AbpBackgroundWorkersTickerQOptions() public AbpBackgroundWorkersTickerQOptions()
{ {
_onfigurations = new Dictionary<Type, AbpBackgroundWorkersCronTickerConfiguration>(); _configurations = new Dictionary<Type, AbpBackgroundWorkersCronTickerConfiguration>();
} }
public void AddConfiguration<TWorker>(AbpBackgroundWorkersCronTickerConfiguration configuration) public void AddConfiguration<TWorker>(AbpBackgroundWorkersCronTickerConfiguration configuration)
@ -19,7 +19,7 @@ public class AbpBackgroundWorkersTickerQOptions
public void AddConfiguration(Type workerType, AbpBackgroundWorkersCronTickerConfiguration configuration) public void AddConfiguration(Type workerType, AbpBackgroundWorkersCronTickerConfiguration configuration)
{ {
_onfigurations[workerType] = configuration; _configurations[workerType] = configuration;
} }
public AbpBackgroundWorkersCronTickerConfiguration? GetConfigurationOrNull<TJob>() public AbpBackgroundWorkersCronTickerConfiguration? GetConfigurationOrNull<TJob>()
@ -29,6 +29,6 @@ public class AbpBackgroundWorkersTickerQOptions
public AbpBackgroundWorkersCronTickerConfiguration? GetConfigurationOrNull(Type workerType) public AbpBackgroundWorkersCronTickerConfiguration? GetConfigurationOrNull(Type workerType)
{ {
return _onfigurations.GetValueOrDefault(workerType); return _configurations.GetValueOrDefault(workerType);
} }
} }

11
framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiObjectExtensionPropertyInfoExtensions.cs

@ -1,5 +1,6 @@
using Blazorise; using Blazorise;
using System; using System;
using System.Collections.Frozen;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
@ -11,7 +12,8 @@ namespace Volo.Abp.BlazoriseUI;
public static class BlazoriseUiObjectExtensionPropertyInfoExtensions public static class BlazoriseUiObjectExtensionPropertyInfoExtensions
{ {
private static readonly HashSet<Type> NumberTypes = new HashSet<Type> { private static readonly FrozenSet<Type> NumberTypes = new HashSet<Type>
{
typeof(int), typeof(int),
typeof(long), typeof(long),
typeof(byte), typeof(byte),
@ -36,13 +38,14 @@ public static class BlazoriseUiObjectExtensionPropertyInfoExtensions
typeof(float?), typeof(float?),
typeof(double?), typeof(double?),
typeof(decimal?) typeof(decimal?)
}; }.ToFrozenSet();
private static readonly HashSet<Type> TextEditSupportedAttributeTypes = new HashSet<Type> { private static readonly FrozenSet<Type> TextEditSupportedAttributeTypes = new HashSet<Type>
{
typeof(EmailAddressAttribute), typeof(EmailAddressAttribute),
typeof(UrlAttribute), typeof(UrlAttribute),
typeof(PhoneAttribute) typeof(PhoneAttribute)
}; }.ToFrozenSet();
public static string? GetDateEditInputFormatOrNull(this IBasicObjectExtensionPropertyInfo property) public static string? GetDateEditInputFormatOrNull(this IBasicObjectExtensionPropertyInfo property)
{ {

7
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.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NuGet.Versioning; using NuGet.Versioning;
using Volo.Abp.Cli.Http;
using Volo.Abp.Cli.LIbs; using Volo.Abp.Cli.LIbs;
using Volo.Abp.Cli.Utils; using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -28,14 +26,12 @@ public class NpmPackagesUpdater : ITransientDependency
private readonly PackageJsonFileFinder _packageJsonFileFinder; private readonly PackageJsonFileFinder _packageJsonFileFinder;
private readonly NpmGlobalPackagesChecker _npmGlobalPackagesChecker; private readonly NpmGlobalPackagesChecker _npmGlobalPackagesChecker;
private readonly Dictionary<string, string> _fileVersionStorage = new Dictionary<string, string>(); private readonly Dictionary<string, string> _fileVersionStorage = [];
private readonly CliHttpClientFactory _cliHttpClientFactory;
public NpmPackagesUpdater( public NpmPackagesUpdater(
PackageJsonFileFinder packageJsonFileFinder, PackageJsonFileFinder packageJsonFileFinder,
NpmGlobalPackagesChecker npmGlobalPackagesChecker, NpmGlobalPackagesChecker npmGlobalPackagesChecker,
ICancellationTokenProvider cancellationTokenProvider, ICancellationTokenProvider cancellationTokenProvider,
CliHttpClientFactory cliHttpClientFactory,
IInstallLibsService installLibsService, IInstallLibsService installLibsService,
ICmdHelper cmdHelper) ICmdHelper cmdHelper)
{ {
@ -44,7 +40,6 @@ public class NpmPackagesUpdater : ITransientDependency
CancellationTokenProvider = cancellationTokenProvider; CancellationTokenProvider = cancellationTokenProvider;
InstallLibsService = installLibsService; InstallLibsService = installLibsService;
CmdHelper = cmdHelper; CmdHelper = cmdHelper;
_cliHttpClientFactory = cliHttpClientFactory;
Logger = NullLogger<NpmPackagesUpdater>.Instance; Logger = NullLogger<NpmPackagesUpdater>.Instance;
} }

4
framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLoggerFactory.cs

@ -5,10 +5,10 @@ namespace Volo.Abp.Logging;
public class DefaultInitLoggerFactory : IInitLoggerFactory public class DefaultInitLoggerFactory : IInitLoggerFactory
{ {
private readonly Dictionary<Type, object> _cache = new Dictionary<Type, object>(); private readonly Dictionary<Type, object> _cache = [];
public virtual IInitLogger<T> Create<T>() public virtual IInitLogger<T> Create<T>()
{ {
return (IInitLogger<T>)_cache.GetOrAdd(typeof(T), () => new DefaultInitLogger<T>()); ; return (IInitLogger<T>)_cache.GetOrAdd(typeof(T), () => new DefaultInitLogger<T>());
} }
} }

9
framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Frozen;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.ComponentModel; using System.ComponentModel;
@ -13,14 +14,14 @@ namespace Volo.Abp.Reflection;
public static class TypeHelper public static class TypeHelper
{ {
private static readonly HashSet<Type> FloatingTypes = new HashSet<Type> private static readonly FrozenSet<Type> FloatingTypes = new HashSet<Type>
{ {
typeof(float), typeof(float),
typeof(double), typeof(double),
typeof(decimal) typeof(decimal)
}; }.ToFrozenSet();
private static readonly HashSet<Type> NonNullablePrimitiveTypes = new HashSet<Type> private static readonly FrozenSet<Type> NonNullablePrimitiveTypes = new HashSet<Type>
{ {
typeof(byte), typeof(byte),
typeof(short), typeof(short),
@ -37,7 +38,7 @@ public static class TypeHelper
typeof(DateTimeOffset), typeof(DateTimeOffset),
typeof(TimeSpan), typeof(TimeSpan),
typeof(Guid) typeof(Guid)
}; }.ToFrozenSet();
public static bool IsNonNullablePrimitiveType(Type type) public static bool IsNonNullablePrimitiveType(Type type)
{ {

98
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs

@ -5,6 +5,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -108,12 +109,17 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
} }
var entityType = entity.GetType(); var entityType = entity.GetType();
var entityFullName = entityType.FullName!;
if (entityEntry.Metadata.HasSharedClrType && !entityEntry.Metadata.IsOwned())
{
entityFullName = entityEntry.Metadata.Name;
}
var entityChange = new EntityChangeInfo var entityChange = new EntityChangeInfo
{ {
ChangeType = changeType, ChangeType = changeType,
EntityEntry = entityEntry, EntityEntry = entityEntry,
EntityId = entityId, EntityId = entityId,
EntityTypeFullName = entityType.FullName, EntityTypeFullName = entityFullName,
PropertyChanges = GetPropertyChanges(entityEntry), PropertyChanges = GetPropertyChanges(entityEntry),
EntityTenantId = GetTenantId(entity) EntityTenantId = GetTenantId(entity)
}; };
@ -181,48 +187,102 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
foreach (var property in properties) foreach (var property in properties)
{ {
if (entityEntry.Metadata.IsMappedToJson() && property.GetJsonPropertyName() == null)
{
continue;
}
var propertyEntry = entityEntry.Property(property.Name); var propertyEntry = entityEntry.Property(property.Name);
if (ShouldSavePropertyHistory(propertyEntry, isCreated || isDeleted) && !IsSoftDeleted(entityEntry)) if (ShouldSavePropertyHistory(propertyEntry, isCreated || isDeleted) && !IsSoftDeleted(entityEntry))
{ {
var propertyType = DeterminePropertyTypeFromEntry(property, propertyEntry);
propertyChanges.Add(new EntityPropertyChangeInfo propertyChanges.Add(new EntityPropertyChangeInfo
{ {
NewValue = isDeleted ? null : JsonSerializer.Serialize(propertyEntry.CurrentValue!).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength), NewValue = isDeleted ? null : JsonSerializer.Serialize(propertyEntry.CurrentValue!).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength),
OriginalValue = isCreated ? null : JsonSerializer.Serialize(propertyEntry.OriginalValue!).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength), OriginalValue = isCreated ? null : JsonSerializer.Serialize(propertyEntry.OriginalValue!).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength),
PropertyName = property.Name, PropertyName = property.Name,
PropertyTypeFullName = property.ClrType.GetFirstGenericArgumentIfNullable().FullName! PropertyTypeFullName = propertyType.FullName!
}); });
} }
} }
if (AbpEfCoreNavigationHelper != null) if (AbpEfCoreNavigationHelper == null)
{
return propertyChanges;
}
foreach (var (navigationEntry, index) in entityEntry.Navigations.Select((value, i) => ( value, i )))
{ {
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))
{ {
var propertyInfo = navigationEntry.Metadata.PropertyInfo; continue;
if (propertyInfo != null && }
propertyInfo.IsDefined(typeof(DisableAuditingAttribute), true))
if (navigationEntry.Metadata.TargetEntityType.IsMappedToJson() && navigationEntry is ReferenceEntry referenceEntry && referenceEntry.TargetEntry != null)
{
foreach (var propertyChange in GetPropertyChanges(referenceEntry.TargetEntry))
{ {
continue; propertyChange.PropertyName = $"{referenceEntry.Metadata.Name}.{propertyChange.PropertyName}";
propertyChanges.Add(propertyChange);
} }
if (AbpEfCoreNavigationHelper.IsNavigationEntryModified(entityEntry, index)) continue;
}
if (AbpEfCoreNavigationHelper.IsNavigationEntryModified(entityEntry, index))
{
var abpNavigationEntry = AbpEfCoreNavigationHelper.GetNavigationEntry(entityEntry, index);
var isCollection = navigationEntry.Metadata.IsCollection;
propertyChanges.Add(new EntityPropertyChangeInfo
{ {
var abpNavigationEntry = AbpEfCoreNavigationHelper.GetNavigationEntry(entityEntry, index); PropertyName = navigationEntry.Metadata.Name,
var isCollection = navigationEntry.Metadata.IsCollection; PropertyTypeFullName = navigationEntry.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName!,
propertyChanges.Add(new EntityPropertyChangeInfo OriginalValue = GetNavigationPropertyValue(abpNavigationEntry?.OriginalValue, isCollection),
{ NewValue = GetNavigationPropertyValue(abpNavigationEntry?.CurrentValue, isCollection)
PropertyName = navigationEntry.Metadata.Name, });
PropertyTypeFullName = navigationEntry.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName!,
OriginalValue = GetNavigationPropertyValue(abpNavigationEntry?.OriginalValue, isCollection),
NewValue = GetNavigationPropertyValue(abpNavigationEntry?.CurrentValue, isCollection)
});
}
} }
} }
return propertyChanges; return propertyChanges;
} }
/// <summary>
/// Determines the CLR type of a property based on its EF Core metadata and the values in the given <see cref="PropertyEntry"/>.
/// </summary>
/// <param name="property">The EF Core property metadata that provides the declared CLR type.</param>
/// <param name="propertyEntry">The property entry that contains the current and original values for the property.</param>
/// <returns>
/// 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 <see cref="object"/>, the type is inferred from the
/// runtime type of <see cref="PropertyEntry.CurrentValue"/> or, if that is <c>null</c>, from
/// <see cref="PropertyEntry.OriginalValue"/>. If both values are <c>null</c>, the declared CLR type
/// (which may remain <see cref="object"/>) is returned.
/// </returns>
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) protected virtual string? GetNavigationPropertyValue(object? entity, bool isCollection)
{ {
switch (entity) switch (entity)

33
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 System.Collections.Generic;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -5,23 +6,23 @@ namespace Volo.Abp.EventBus.RabbitMq;
public class PostConfigureAbpRabbitMqEventBusOptions : IPostConfigureOptions<AbpRabbitMqEventBusOptions> public class PostConfigureAbpRabbitMqEventBusOptions : IPostConfigureOptions<AbpRabbitMqEventBusOptions>
{ {
private readonly HashSet<string> _uint64QueueArguments = private readonly FrozenSet<string> _uint64QueueArguments = new HashSet<string>
[ {
"x-delivery-limit", "x-delivery-limit",
"x-expires", "x-expires",
"x-message-ttl", "x-message-ttl",
"x-max-length", "x-max-length",
"x-max-length-bytes", "x-max-length-bytes",
"x-quorum-initial-group-size", "x-quorum-initial-group-size",
"x-quorum-target-group-size", "x-quorum-target-group-size",
"x-stream-filter-size-bytes", "x-stream-filter-size-bytes",
"x-stream-max-segment-size-bytes", "x-stream-max-segment-size-bytes",
]; }.ToFrozenSet();
private readonly HashSet<string> _boolQueueArguments = private readonly FrozenSet<string> _boolQueueArguments = new HashSet<string>
[ {
"x-single-active-consumer" "x-single-active-consumer"
]; }.ToFrozenSet();
public virtual void PostConfigure(string? name, AbpRabbitMqEventBusOptions options) public virtual void PostConfigure(string? name, AbpRabbitMqEventBusOptions options)
{ {

6
framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Frozen;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -10,7 +11,8 @@ internal static class ProxyScriptingJsFuncHelper
{ {
private const string ValidJsVariableNameChars = "abcdefghijklmnopqrstuxwvyzABCDEFGHIJKLMNOPQRSTUXWVYZ0123456789_"; private const string ValidJsVariableNameChars = "abcdefghijklmnopqrstuxwvyzABCDEFGHIJKLMNOPQRSTUXWVYZ0123456789_";
private static readonly HashSet<string> ReservedWords = new HashSet<string> { private static readonly FrozenSet<string> ReservedWords = new HashSet<string>
{
"abstract", "abstract",
"else", "else",
"instanceof", "instanceof",
@ -71,7 +73,7 @@ internal static class ProxyScriptingJsFuncHelper
"in", "in",
"static", "static",
"with" "with"
}; }.ToFrozenSet();
public static string NormalizeJsVariableName(string name, string additionalChars = "") public static string NormalizeJsVariableName(string name, string additionalChars = "")
{ {

49
framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs

@ -2,6 +2,8 @@
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
@ -15,15 +17,24 @@ namespace Volo.Abp.Json.Newtonsoft;
public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency
{ {
private const string DefaultDateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK"; private const string DefaultDateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK";
public ILogger<AbpDateTimeConverter> Logger { get; set; }
private readonly DateTimeStyles _dateTimeStyles = DateTimeStyles.RoundtripKind; private readonly DateTimeStyles _dateTimeStyles = DateTimeStyles.RoundtripKind;
private readonly CultureInfo _culture = CultureInfo.InvariantCulture; private readonly CultureInfo _culture = CultureInfo.InvariantCulture;
private readonly IClock _clock; private readonly IClock _clock;
private readonly AbpJsonOptions _options; private readonly AbpJsonOptions _options;
private readonly ICurrentTimezoneProvider _currentTimezoneProvider;
private readonly ITimezoneProvider _timezoneProvider;
private bool _skipDateTimeNormalization; private bool _skipDateTimeNormalization;
public AbpDateTimeConverter(IClock clock, IOptions<AbpJsonOptions> options) public AbpDateTimeConverter(IClock clock, IOptions<AbpJsonOptions> options, ICurrentTimezoneProvider currentTimezoneProvider, ITimezoneProvider timezoneProvider)
{ {
Logger = NullLogger<AbpDateTimeConverter>.Instance;
_clock = clock; _clock = clock;
_currentTimezoneProvider = currentTimezoneProvider;
_timezoneProvider = timezoneProvider;
_options = options.Value; _options = options.Value;
} }
@ -41,19 +52,14 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{ {
var nullable = Nullable.GetUnderlyingType(objectType) != null; 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}."); throw new JsonSerializationException($"Cannot convert null value to {objectType.FullName}.");
} case JsonToken.Null:
return null;
return null; case JsonToken.Date:
} return Normalize(reader.Value!.To<DateTime>());
if (reader.TokenType == JsonToken.Date)
{
return Normalize(reader.Value!.To<DateTime>());
} }
if (reader.TokenType != JsonToken.String) if (reader.TokenType != JsonToken.String)
@ -108,7 +114,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) && if (property.PropertyType != typeof(DateTime) &&
property.PropertyType != typeof(DateTime?)) property.PropertyType != typeof(DateTime?))
@ -121,6 +127,23 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency
protected virtual DateTime Normalize(DateTime dateTime) 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.GetUtcOffset(dateTime)).UtcDateTime;
}
catch
{
Logger.LogWarning("Could not convert DateTime with unspecified Kind using timezone '{TimeZone}'.", _currentTimezoneProvider.TimeZone);
}
return _skipDateTimeNormalization return _skipDateTimeNormalization
? dateTime ? dateTime
: _clock.Normalize(dateTime); : _clock.Normalize(dateTime);

69
framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs

@ -1,84 +1,43 @@
using System; using System;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Timing; using Volo.Abp.Timing;
namespace Volo.Abp.Json.SystemTextJson.JsonConverters; namespace Volo.Abp.Json.SystemTextJson.JsonConverters;
public class AbpDateTimeConverter : JsonConverter<DateTime>, ITransientDependency public class AbpDateTimeConverter : AbpDateTimeConverterBase<DateTime>, ITransientDependency
{ {
private readonly IClock _clock; public AbpDateTimeConverter(
private readonly AbpJsonOptions _options; IClock clock,
private bool _skipDateTimeNormalization; IOptions<AbpJsonOptions> abpJsonOptions,
ICurrentTimezoneProvider currentTimezoneProvider,
public AbpDateTimeConverter(IClock clock, IOptions<AbpJsonOptions> abpJsonOptions) ITimezoneProvider timezoneProvider)
: base(clock, abpJsonOptions, currentTimezoneProvider, timezoneProvider)
{ {
_clock = clock;
_options = abpJsonOptions.Value;
} }
public virtual AbpDateTimeConverter SkipDateTimeNormalization() public virtual AbpDateTimeConverter SkipDateTimeNormalization()
{ {
_skipDateTimeNormalization = true; IsSkipDateTimeNormalization = true;
return this; return this;
} }
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
if (_options.InputDateTimeFormats.Any()) if (Options.InputDateTimeFormats.Any() && reader.TokenType != JsonTokenType.String)
{
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 (DateTime.TryParse(dateText, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d4)) throw new JsonException("Reader's TokenType is not String!");
{
return Normalize(d4);
}
} }
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) public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{ {
if (_options.OutputDateTimeFormat.IsNullOrWhiteSpace()) WriteDateTime(writer, value);
{
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);
} }
} }

116
framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverterBase.cs

@ -0,0 +1,116 @@
using System;
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;
namespace Volo.Abp.Json.SystemTextJson.JsonConverters;
public abstract class AbpDateTimeConverterBase<T> : JsonConverter<T>
{
public ILogger<AbpDateTimeConverterBase<T>> Logger { get; set; }
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> abpJsonOptions,
ICurrentTimezoneProvider currentTimezoneProvider,
ITimezoneProvider timezoneProvider)
{
Logger = NullLogger<AbpDateTimeConverterBase<T>>.Instance;
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.GetUtcOffset(dateTime)).UtcDateTime;
}
catch
{
Logger.LogWarning("Could not convert DateTime with unspecified Kind using timezone '{TimeZone}'.", CurrentTimezoneProvider.TimeZone);
}
return IsSkipDateTimeNormalization ? dateTime : Clock.Normalize(dateTime);
}
}

67
framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs

@ -1,65 +1,39 @@
using System; using System;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Timing; using Volo.Abp.Timing;
namespace Volo.Abp.Json.SystemTextJson.JsonConverters; namespace Volo.Abp.Json.SystemTextJson.JsonConverters;
public class AbpNullableDateTimeConverter : JsonConverter<DateTime?>, ITransientDependency public class AbpNullableDateTimeConverter : AbpDateTimeConverterBase<DateTime?>, ITransientDependency
{ {
private readonly IClock _clock; public AbpNullableDateTimeConverter(
private readonly AbpJsonOptions _options; IClock clock,
private bool _skipDateTimeNormalization; IOptions<AbpJsonOptions> abpJsonOptions,
ICurrentTimezoneProvider currentTimezoneProvider,
public AbpNullableDateTimeConverter(IClock clock, IOptions<AbpJsonOptions> abpJsonOptions) ITimezoneProvider timezoneProvider)
: base(clock, abpJsonOptions, currentTimezoneProvider, timezoneProvider)
{ {
_clock = clock;
_options = abpJsonOptions.Value;
} }
public virtual AbpNullableDateTimeConverter SkipDateTimeNormalization() public virtual AbpNullableDateTimeConverter SkipDateTimeNormalization()
{ {
_skipDateTimeNormalization = true; IsSkipDateTimeNormalization = true;
return this; return this;
} }
public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
if (_options.InputDateTimeFormats.Any()) if (Options.InputDateTimeFormats.Any() && reader.TokenType != JsonTokenType.String)
{
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))
{ {
return Normalize(d2); throw new JsonException("Reader's TokenType is not String!");
} }
var dateText = reader.GetString(); if (TryReadDateTime(ref reader, out var result))
if (!dateText.IsNullOrWhiteSpace())
{ {
if (DateTime.TryParse(dateText, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d3)) return result;
{
return Normalize(d3);
}
} }
return null; return null;
@ -70,22 +44,9 @@ public class AbpNullableDateTimeConverter : JsonConverter<DateTime?>, ITransient
if (value == null) if (value == null)
{ {
writer.WriteNullValue(); 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) WriteDateTime(writer, value.Value);
{
return _skipDateTimeNormalization ? dateTime : _clock.Normalize(dateTime);
} }
} }

2
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceDictionary.cs

@ -6,7 +6,7 @@ namespace Volo.Abp.Localization;
public class LocalizationResourceDictionary : Dictionary<string, LocalizationResourceBase> public class LocalizationResourceDictionary : Dictionary<string, LocalizationResourceBase>
{ {
private readonly Dictionary<Type, LocalizationResourceBase> _resourcesByTypes = new(); private readonly Dictionary<Type, LocalizationResourceBase> _resourcesByTypes = [];
public LocalizationResource Add<TResouce>(string? defaultCultureName = null) public LocalizationResource Add<TResouce>(string? defaultCultureName = null)
{ {

2
framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseCollection.cs

@ -9,7 +9,7 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb;
public class MemoryDatabaseCollection<TEntity> : IMemoryDatabaseCollection<TEntity> public class MemoryDatabaseCollection<TEntity> : IMemoryDatabaseCollection<TEntity>
where TEntity : class, IEntity where TEntity : class, IEntity
{ {
private readonly Dictionary<string, byte[]> _dictionary = new Dictionary<string, byte[]>(); private readonly Dictionary<string, byte[]> _dictionary = [];
private readonly IMemoryDbSerializer _memoryDbSerializer; private readonly IMemoryDbSerializer _memoryDbSerializer;

2
framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs

@ -17,7 +17,7 @@ public class MongoModelBuilder : IMongoModelBuilder
{ {
private readonly Dictionary<Type, object> _entityModelBuilders; private readonly Dictionary<Type, object> _entityModelBuilders;
private static readonly object SyncObj = new object(); private static readonly object SyncObj = new();
public MongoModelBuilder() public MongoModelBuilder()
{ {

2
framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ParameterRebinder.cs

@ -15,7 +15,7 @@ internal class ParameterRebinder : ExpressionVisitor
internal ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map) internal ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
{ {
_map = map ?? new Dictionary<ParameterExpression, ParameterExpression>(); _map = map ?? [];
} }
internal static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, internal static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map,

39
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(); 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")] [HttpGet("NullableDateTimeKind")]
public string NullableDateTimeKind(DateTime? input) public string NullableDateTimeKind(DateTime? input)
{ {
return input.Value.Kind.ToString().ToLower(); 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")] [HttpGet("DisableDateTimeNormalizationDateTimeKind")]
public string DisableDateTimeNormalizationDateTimeKind([DisableDateTimeNormalization] DateTime input) public string DisableDateTimeNormalizationDateTimeKind([DisableDateTimeNormalization] DateTime input)
{ {
@ -40,6 +52,19 @@ public class ModelBindingController : AbpController
input.InnerModel.Time4.Kind.ToString().ToLower(); 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. //JSON input and output.
[HttpPost("ComplexTypeDateTimeKind_JSON")] [HttpPost("ComplexTypeDateTimeKind_JSON")]
public string ComplexTypeDateTimeKind_JSON([FromBody] GetDateTimeKindModel input) public string ComplexTypeDateTimeKind_JSON([FromBody] GetDateTimeKindModel input)
@ -50,6 +75,20 @@ public class ModelBindingController : AbpController
input.InnerModel.Time4.Kind.ToString().ToLower(); 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")] [HttpPost("Guid_Json_Test")]
public GuidJsonModel Guid_Json_Test([FromBody] GuidJsonModel input) public GuidJsonModel Guid_Json_Test([FromBody] GuidJsonModel input)
{ {

104
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs

@ -19,34 +19,79 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase
[Fact] [Fact]
public async Task DateTimeKind_Test() 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); response.StatusCode.ShouldBe(HttpStatusCode.OK);
var resultAsString = await response.Content.ReadAsStringAsync(); var resultAsString = await response.Content.ReadAsStringAsync();
resultAsString.ShouldBe(Kind.ToString().ToLower()); resultAsString.ShouldBe(Kind.ToString().ToLower());
} }
[Fact]
public async Task DateTimeKind_WithTimezone_Test()
{
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(2020, 1, 1, 0, 0, 0, DateTimeKind.Unspecified);
switch (Kind)
{
case DateTimeKind.Utc:
dateTime = new DateTime(2019, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3
break;
case DateTimeKind.Local:
dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Local);
break;
}
resultAsString.ShouldBe($"{Kind.ToString().ToLower()}_{dateTime.ToString("O").ToLower()}");
}
[Fact] [Fact]
public async Task NullableDateTimeKind_Test() public async Task NullableDateTimeKind_Test()
{ {
var response = 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); response.StatusCode.ShouldBe(HttpStatusCode.OK);
var resultAsString = await response.Content.ReadAsStringAsync(); var resultAsString = await response.Content.ReadAsStringAsync();
resultAsString.ShouldBe(Kind.ToString().ToLower()); resultAsString.ShouldBe(Kind.ToString().ToLower());
} }
[Fact]
public async Task NullableDateTimeKind_WithTimezone_Test()
{
var response =
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(2020, 1, 1, 0, 0, 0, DateTimeKind.Unspecified);
switch (Kind)
{
case DateTimeKind.Utc:
dateTime = new DateTime(2019, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3
break;
case DateTimeKind.Local:
dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Local);
break;
}
resultAsString.ShouldBe($"{Kind.ToString().ToLower()}_{dateTime.ToString("O").ToLower()}");
}
[Fact] [Fact]
public async Task DisableDateTimeNormalizationDateTimeKind_Test() public async Task DisableDateTimeNormalizationDateTimeKind_Test()
{ {
var response = var response =
await Client.GetAsync( 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); response.StatusCode.ShouldBe(HttpStatusCode.OK);
var resultAsString = await response.Content.ReadAsStringAsync(); 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 //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()); resultAsString.ShouldBe(DateTimeKind.Utc.ToString().ToLower());
} }
@ -56,11 +101,11 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase
{ {
var response = var response =
await Client.GetAsync( 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); response.StatusCode.ShouldBe(HttpStatusCode.OK);
var resultAsString = await response.Content.ReadAsStringAsync(); 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 //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()); resultAsString.ShouldBe(DateTimeKind.Utc.ToString().ToLower());
} }
@ -69,14 +114,14 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase
public async Task ComplexTypeDateTimeKind_Test() public async Task ComplexTypeDateTimeKind_Test()
{ {
var response = await Client.GetAsync("/api/model-Binding-test/ComplexTypeDateTimeKind?" + var response = await Client.GetAsync("/api/model-Binding-test/ComplexTypeDateTimeKind?" +
"Time1=2010-01-01T00:00:00Z&" + "Time1=2020-01-01T00:00:00Z&" +
"Time2=2010-01-01T00:00:00Z&" + "Time2=2020-01-01T00:00:00Z&" +
"Time3=2010-01-01T00:00:00Z&" + "Time3=2020-01-01T00:00:00Z&" +
"InnerModel.Time4=2010-01-01T00:00:00Z"); "InnerModel.Time4=2020-01-01T00:00:00Z");
response.StatusCode.ShouldBe(HttpStatusCode.OK); response.StatusCode.ShouldBe(HttpStatusCode.OK);
var resultAsString = await response.Content.ReadAsStringAsync(); 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 //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"); resultAsString.ShouldBe($"utc_{Kind.ToString().ToLower()}_{Kind.ToString().ToLower()}_utc");
} }
@ -84,7 +129,7 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase
[Fact] [Fact]
public async Task ComplexTypeDateTimeKind_JSON_Test() 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", var response = await Client.PostAsync("/api/model-Binding-test/ComplexTypeDateTimeKind_JSON",
new StringContent(JsonSerializer.Serialize( new StringContent(JsonSerializer.Serialize(
new GetDateTimeKindModel new GetDateTimeKindModel
@ -104,6 +149,41 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase
resultAsString.ShouldBe($"local_{Kind.ToString().ToLower()}_{Kind.ToString().ToLower()}_local"); resultAsString.ShouldBe($"local_{Kind.ToString().ToLower()}_{Kind.ToString().ToLower()}_local");
} }
[Fact]
public async Task ComplexTypeDateTimeKind_JSON_WithTimezone_Test()
{
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
{
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(2020, 1, 1, 0, 0, 0, DateTimeKind.Unspecified);
switch (Kind)
{
case DateTimeKind.Utc:
dateTime = new DateTime(2019, 12, 31, 21, 0, 0, DateTimeKind.Utc); //Turkey is UTC+3
break;
case DateTimeKind.Local:
dateTime = new DateTime(2020, 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] [Fact]
public async Task Guid_Json_Test() public async Task Guid_Json_Test()
{ {

2
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs

@ -59,6 +59,8 @@ public class AbpAuditingTestModule : AbpModule
"AppEntityWithValueObject", "AppEntityWithValueObject",
type => type == typeof(AppEntityWithValueObject) || type == typeof(AppEntityWithValueObjectAddress)) type => type == typeof(AppEntityWithValueObject) || type == typeof(AppEntityWithValueObjectAddress))
); );
options.EntityHistorySelectors.Add(new NamedTypeSelector(nameof(AppEntityWithJsonProperty), type => type == typeof(AppEntityWithJsonProperty)));
}); });
context.Services.AddType<Auditing_Tests.MyAuditedObject1>(); context.Services.AddType<Auditing_Tests.MyAuditedObject1>();

27
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<Guid>
{
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<string, object>
{
}

21
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs

@ -30,6 +30,7 @@ public class AbpAuditingTestDbContext : AbpDbContext<AbpAuditingTestDbContext>
public DbSet<AppEntityWithNavigations> AppEntityWithNavigations { get; set; } public DbSet<AppEntityWithNavigations> AppEntityWithNavigations { get; set; }
public DbSet<AppEntityWithNavigationChildOneToMany> AppEntityWithNavigationChildOneToMany { get; set; } public DbSet<AppEntityWithNavigationChildOneToMany> AppEntityWithNavigationChildOneToMany { get; set; }
public DbSet<AppEntityWithNavigationsAndDisableAuditing> AppEntityWithNavigationsAndDisableAuditing { get; set; } public DbSet<AppEntityWithNavigationsAndDisableAuditing> AppEntityWithNavigationsAndDisableAuditing { get; set; }
public DbSet<AppEntityWithJsonProperty> EntitiesWithObjectProperty { get; set; }
public AbpAuditingTestDbContext(DbContextOptions<AbpAuditingTestDbContext> options) public AbpAuditingTestDbContext(DbContextOptions<AbpAuditingTestDbContext> options)
: base(options) : base(options)
@ -56,5 +57,25 @@ public class AbpAuditingTestDbContext : AbpDbContext<AbpAuditingTestDbContext>
b.HasMany(x => x.ManyToMany).WithMany(x => x.ManyToMany).UsingEntity<AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany>(); b.HasMany(x => x.ManyToMany).WithMany(x => x.ManyToMany).UsingEntity<AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany>();
}); });
modelBuilder.Entity<AppEntityWithJsonProperty>(b =>
{
b.ConfigureByConvention();
b.OwnsOne(x => x.Data, b2 =>
{
b2.ToJson();
b2.Property<object>("Name")
.HasConversion<string>(
v => v.ToString(),
v => v
);
b2.Property<object>("Value")
.HasConversion<string>(
v => v.ToString(),
v => v
);
});
});
} }
} }

98
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].PropertyName == nameof(AppEntityWithNavigationChildManyToMany.ManyToMany) &&
x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigations>).FullName)); x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigations>).FullName));
#pragma warning restore 4014
}
[Fact]
public async Task Should_Write_AuditLog_For_Json_Property_Changes()
{
var entityId = Guid.NewGuid();
var repository = ServiceProvider.GetRequiredService<IBasicRepository<AppEntityWithJsonProperty, Guid>>();
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<AuditLogInfo>(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<AuditLogInfo>(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 #pragma warning restore 4014
} }
} }

165
framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs

@ -1,37 +1,38 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Frozen;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Shouldly; using Shouldly;
using Volo.Abp.AutoMapper; using Volo.Abp.AutoMapper;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.MultiLingualObjects.TestObjects; using Volo.Abp.MultiLingualObjects.TestObjects;
using Volo.Abp.Testing; using Volo.Abp.Testing;
using Xunit; using Xunit;
namespace Volo.Abp.MultiLingualObjects; namespace Volo.Abp.MultiLingualObjects;
public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingualObjectsTestModule> public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingualObjectsTestModule>
{ {
private readonly IMultiLingualObjectManager _multiLingualObjectManager; private readonly IMultiLingualObjectManager _multiLingualObjectManager;
private readonly MultiLingualBook _book; private readonly MultiLingualBook _book;
private readonly List<MultiLingualBook> _books; private readonly List<MultiLingualBook> _books;
private readonly IMapperAccessor _mapperAccessor; private readonly IMapperAccessor _mapperAccessor;
private readonly Dictionary<string, string> _testTranslations = new() private readonly FrozenDictionary<string, string> _testTranslations = new Dictionary<string, string>
{ {
["ar"] = "C# التعمق في", ["ar"] = "C# التعمق في",
["zh-Hans"] = "深入理解C#", ["zh-Hans"] = "深入理解C#",
["en"] = "C# in Depth" ["en"] = "C# in Depth"
}; }.ToFrozenDictionary();
public MultiLingualObjectManager_Tests() public MultiLingualObjectManager_Tests()
{ {
_multiLingualObjectManager = ServiceProvider.GetRequiredService<IMultiLingualObjectManager>(); _multiLingualObjectManager = ServiceProvider.GetRequiredService<IMultiLingualObjectManager>();
//Single Lookup //Single Lookup
_book = GetTestBook("en", "zh-Hans"); _book = GetTestBook("en", "zh-Hans");
//Bulk lookup //Bulk lookup
_books = new List<MultiLingualBook> _books = new List<MultiLingualBook>
{ {
//has no translations //has no translations
@ -45,14 +46,14 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingual
//arabic + english + chineese //arabic + english + chineese
GetTestBook("en", "ar", "zh-Hans") GetTestBook("en", "ar", "zh-Hans")
}; };
_mapperAccessor = ServiceProvider.GetRequiredService<IMapperAccessor>(); _mapperAccessor = ServiceProvider.GetRequiredService<IMapperAccessor>();
} }
MultiLingualBook GetTestBook(params string[] included) MultiLingualBook GetTestBook(params string[] included)
{ {
var id = Guid.NewGuid(); var id = Guid.NewGuid();
//Single book //Single book
var res = new MultiLingualBook(id, 100); var res = new MultiLingualBook(id, 100);
foreach (var language in included) foreach (var language in included)
{ {
res.Translations.Add(new MultiLingualBookTranslation res.Translations.Add(new MultiLingualBookTranslation
@ -65,45 +66,45 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingual
return res; return res;
} }
[Fact] [Fact]
public async Task GetTranslationAsync() public async Task GetTranslationAsync()
{ {
using (CultureHelper.Use("en-us")) using (CultureHelper.Use("en-us"))
{ {
var translation = await _multiLingualObjectManager.GetTranslationAsync<MultiLingualBook, MultiLingualBookTranslation>(_book); var translation = await _multiLingualObjectManager.GetTranslationAsync<MultiLingualBook, MultiLingualBookTranslation>(_book);
translation.ShouldNotBeNull(); translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]); translation.Name.ShouldBe(_testTranslations["en"]);
} }
} }
[Fact] [Fact]
public async Task GetTranslationFromListAsync() public async Task GetTranslationFromListAsync()
{ {
using (CultureHelper.Use("en-us")) using (CultureHelper.Use("en-us"))
{ {
var translation = await _multiLingualObjectManager.GetTranslationAsync(_book.Translations); var translation = await _multiLingualObjectManager.GetTranslationAsync(_book.Translations);
translation.ShouldNotBeNull(); translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]); translation.Name.ShouldBe(_testTranslations["en"]);
} }
} }
[Fact] [Fact]
public async Task Should_Get_Specified_Language() public async Task Should_Get_Specified_Language()
{ {
using (CultureHelper.Use("zh-Hans")) using (CultureHelper.Use("zh-Hans"))
{ {
var translation = await _multiLingualObjectManager.GetTranslationAsync<MultiLingualBook, MultiLingualBookTranslation>(_book, culture: "en"); var translation = await _multiLingualObjectManager.GetTranslationAsync<MultiLingualBook, MultiLingualBookTranslation>(_book, culture: "en");
translation.ShouldNotBeNull(); translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]); translation.Name.ShouldBe(_testTranslations["en"]);
} }
} }
[Fact] [Fact]
public async Task GetBulkTranslationsAsync() public async Task GetBulkTranslationsAsync()
{ {
using (CultureHelper.Use("en-us")) using (CultureHelper.Use("en-us"))
{ {
var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync<MultiLingualBook, MultiLingualBookTranslation>(_books); var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync<MultiLingualBook, MultiLingualBookTranslation>(_books);
foreach (var (entity, translation) in translations) foreach (var (entity, translation) in translations)
{ {
@ -117,26 +118,26 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingual
translation.ShouldBeNull(); translation.ShouldBeNull();
} }
} }
} }
} }
[Fact] [Fact]
public async Task GetBulkTranslationsFromListAsync() public async Task GetBulkTranslationsFromListAsync()
{ {
using (CultureHelper.Use("en-us")) using (CultureHelper.Use("en-us"))
{ {
var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync(_books.Select(x => x.Translations)); var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync(_books.Select(x => x.Translations));
foreach (var translation in translations) foreach (var translation in translations)
{ {
translation?.Name.ShouldBe(_testTranslations["en"]); translation?.Name.ShouldBe(_testTranslations["en"]);
} }
} }
} }
[Fact] [Fact]
public async Task TestBulkMapping() public async Task TestBulkMapping()
{ {
using (CultureHelper.Use("en-us")) using (CultureHelper.Use("en-us"))
{ {
var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync<MultiLingualBook, MultiLingualBookTranslation>(_books); var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync<MultiLingualBook, MultiLingualBookTranslation>(_books);
var translationsDict = translations.ToDictionary(x => x.entity.Id, x => x.translation); var translationsDict = translations.ToDictionary(x => x.entity.Id, x => x.translation);
@ -152,5 +153,5 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingual
Assert.Equal(og.Translations.FirstOrDefault(x => x.Language == "en")?.Name, m.Name); Assert.Equal(og.Translations.FirstOrDefault(x => x.Language == "en")?.Name, m.Name);
} }
} }
} }
} }

5
modules/docs/app/VoloDocs.Web/Pages/Error.cshtml.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Frozen;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Diagnostics;
@ -48,7 +49,7 @@ namespace VoloDocs.Web.Pages
#region Error Messages #region Error Messages
/*For more ASCII arts http://patorjk.com/software/taag/#p=display&h=0&f=Big&t=400*/ /*For more ASCII arts http://patorjk.com/software/taag/#p=display&h=0&f=Big&t=400*/
private readonly Dictionary<int, string> _errorMessages = new Dictionary<int, string> private readonly FrozenDictionary<int, string> _errorMessages = new Dictionary<int, string>
{ {
{ {
400, @" 400, @"
@ -131,7 +132,7 @@ Ooops! Our server is experiencing a mild case of the hiccups."
Looks like we're having some server issues." Looks like we're having some server issues."
} }
}; }.ToFrozenDictionary();
#endregion #endregion
} }
} }
Loading…
Cancel
Save