From f3f14a8dd98b16715318a088d08ffab9c674bd8c Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 6 Mar 2025 11:09:33 +0800 Subject: [PATCH] Implement timezone override in middleware from request sources and add unit tests --- .../Timing/AbpTimeZoneMiddleware.cs | 35 +++++++++- .../Volo/Abp/Timing/TimeZoneConsts.cs | 6 ++ .../Mvc/AbpAspNetCoreMvcTestModule.cs | 1 + .../Mvc/Timing/AbpTimeZoneMiddleware_Tests.cs | 64 +++++++++++++++++++ .../Mvc/Timing/TimingTestController.cs | 27 ++++++++ 5 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneConsts.cs create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Timing/AbpTimeZoneMiddleware_Tests.cs create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Timing/TimingTestController.cs diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Timing/AbpTimeZoneMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Timing/AbpTimeZoneMiddleware.cs index af0cb1c04c..2b8a00ec6c 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Timing/AbpTimeZoneMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Timing/AbpTimeZoneMiddleware.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; @@ -13,8 +14,14 @@ public class AbpTimeZoneMiddleware : AbpMiddlewareBase, ITransientDependency { public async override Task InvokeAsync(HttpContext context, RequestDelegate next) { - var settingProvider = context.RequestServices.GetRequiredService(); - var timezone = await settingProvider.GetOrNullAsync(TimingSettingNames.TimeZone); + var timezone = await GetTimezoneFromRequestAsync(context); + + if (timezone.IsNullOrWhiteSpace()) + { + var settingProvider = context.RequestServices.GetRequiredService(); + timezone = await settingProvider.GetOrNullAsync(TimingSettingNames.TimeZone); + } + if (timezone.IsNullOrEmpty()) { await next(context); @@ -27,4 +34,28 @@ public class AbpTimeZoneMiddleware : AbpMiddlewareBase, ITransientDependency await next(context); } } + + protected virtual async Task GetTimezoneFromRequestAsync(HttpContext context) + { + var timeZoneSources = new Func>[] + { + ctx => Task.FromResult(ctx.Request.Headers[TimeZoneConsts.DefaultTimeZoneKey].FirstOrDefault()), + ctx => Task.FromResult(ctx.Request.Query[TimeZoneConsts.DefaultTimeZoneKey].ToString()), + async ctx => ctx.Request.HasFormContentType + ? (await ctx.Request.ReadFormAsync())[TimeZoneConsts.DefaultTimeZoneKey].ToString() + : null, + ctx => Task.FromResult(ctx.Request.Cookies[TimeZoneConsts.DefaultTimeZoneKey]?.ToString()), + }; + + foreach (var source in timeZoneSources) + { + var timezone = await source(context); + if (!string.IsNullOrEmpty(timezone)) + { + return timezone; + } + } + + return null; + } } diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneConsts.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneConsts.cs new file mode 100644 index 0000000000..211247f5aa --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneConsts.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.Timing; + +public class TimeZoneConsts +{ + public const string DefaultTimeZoneKey = "__timezone"; +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs index 0e13746d66..1c705d8df3 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs @@ -159,6 +159,7 @@ public class AbpAspNetCoreMvcTestModule : AbpModule app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); + app.UseAbpTimeZone(); app.UseAuditing(); app.UseUnitOfWork(); app.UseConfiguredEndpoints(); diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Timing/AbpTimeZoneMiddleware_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Timing/AbpTimeZoneMiddleware_Tests.cs new file mode 100644 index 0000000000..fd2f107ccd --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Timing/AbpTimeZoneMiddleware_Tests.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Testing; +using Shouldly; +using Volo.Abp.Timing; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Timing; + +public class AbpTimeZoneMiddleware_Tests : AspNetCoreMvcTestBase +{ + private readonly ICurrentTimezoneProvider _currentTimezoneProvider; + + public AbpTimeZoneMiddleware_Tests() + { + _currentTimezoneProvider = GetRequiredService(); + } + + [Fact] + public async Task Should_Override_TimeZone_Setting_By_Request() + { + using (_currentTimezoneProvider.Change("UTC")) + { + var result = await Client.GetStringAsync("api/timing-test"); + result.ShouldBe("UTC"); + } + + // Query string + using (_currentTimezoneProvider.Change("UTC")) + { + var result = await Client.GetStringAsync("api/timing-test?__timezone=Europe/Istanbul"); + result.ShouldBe("Europe/Istanbul"); + } + + // Header + using (_currentTimezoneProvider.Change("UTC")) + { + Client.DefaultRequestHeaders.Add("__timezone", "Asia/Shanghai"); + var result = await Client.GetStringAsync("api/timing-test"); + result.ShouldBe("Asia/Shanghai"); + } + + // Form + using (_currentTimezoneProvider.Change("UTC")) + { + Client.DefaultRequestHeaders.Remove("__timezone"); + var result = await Client.PostAsync("api/timing-test", new FormUrlEncodedContent(new[] {new KeyValuePair("__timezone", "Europe/Germany")})); + (await result.Content.ReadAsStringAsync()).ShouldBe("Europe/Germany"); + } + + // Cookie + using (_currentTimezoneProvider.Change("UTC")) + { + Client.DefaultRequestHeaders.Remove("__timezone"); + Client.DefaultRequestHeaders.Add("Cookie", "__timezone=Europe/Istanbul"); + var result = await Client.GetStringAsync("api/timing-test"); + result.ShouldBe("Europe/Istanbul"); + } + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Timing/TimingTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Timing/TimingTestController.cs new file mode 100644 index 0000000000..a509e62ec5 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Timing/TimingTestController.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Timing; + +namespace Volo.Abp.AspNetCore.Mvc.Timing; + +[Route("api/timing-test")] +public class UnitOfWorkTestController : AbpController +{ + private readonly ICurrentTimezoneProvider _currentTimezoneProvider; + + public UnitOfWorkTestController(ICurrentTimezoneProvider currentTimezoneProvider) + { + _currentTimezoneProvider = currentTimezoneProvider; + } + + [HttpGet] + public ActionResult GetAsync() + { + return Content(_currentTimezoneProvider.TimeZone ?? "null"); + } + + [HttpPost] + public ActionResult PostAsync() + { + return Content(_currentTimezoneProvider.TimeZone ?? "null"); + } +}