From 70444944c77878e1b87ea835c15522c720c9b478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 3 Apr 2020 11:44:13 +0300 Subject: [PATCH] Resolve #3339: Dynamic C# API Clients may not handle DateTime on QueryString when client and server cultures are different --- .../DynamicHttpProxyInterceptor.cs | 15 ++++-- .../Http/Client/DynamicProxying/UrlBuilder.cs | 22 ++++++++- .../DynamicProxying/IRegularTestController.cs | 5 +- .../DynamicProxying/RegularTestController.cs | 22 +++++++-- .../RegularTestControllerClientProxy_Tests.cs | 46 ++++++++++++++++--- 5 files changed, 92 insertions(+), 18 deletions(-) diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs index 3e0124237f..d5d90ed182 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs @@ -56,7 +56,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying IJsonSerializer jsonSerializer, IRemoteServiceHttpClientAuthenticator clientAuthenticator, ICancellationTokenProvider cancellationTokenProvider, - ICorrelationIdProvider correlationIdProvider, + ICorrelationIdProvider correlationIdProvider, IOptions correlationIdOptions, ICurrentTenant currentTenant) { @@ -110,7 +110,14 @@ namespace Volo.Abp.Http.Client.DynamicProxying //TODO: Think on that if (TypeHelper.IsPrimitiveExtended(typeof(T), true)) { - return (T)Convert.ChangeType(responseAsString, typeof(T)); + if (typeof(DateTime).IsAssignableFrom(typeof(T))) + { + return (T)(object)DateTime.Parse(responseAsString.Trim('\"'), CultureInfo.InvariantCulture); + } + else + { + return (T)Convert.ChangeType(responseAsString, typeof(T)); + } } return JsonSerializer.Deserialize(responseAsString); @@ -151,8 +158,8 @@ namespace Volo.Abp.Http.Client.DynamicProxying } return await response.Content.ReadAsStringAsync(); - } - + } + private ApiVersionInfo GetApiVersionInfo(ActionApiDescriptionModel action) { var apiVersion = FindBestApiVersion(action); diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs index dcb9868b7f..dae7853e08 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; +using JetBrains.Annotations; using Volo.Abp.Http.Modeling; using Volo.Abp.Http.ProxyScripting.Generators; +using Volo.Abp.Reflection; namespace Volo.Abp.Http.Client.DynamicProxying { @@ -88,10 +91,25 @@ namespace Volo.Abp.Http.Client.DynamicProxying } } - private static void AddQueryStringParameter(StringBuilder urlBuilder, bool isFirstParam, string name, object value) + private static void AddQueryStringParameter( + StringBuilder urlBuilder, + bool isFirstParam, + string name, + [NotNull] object value) { urlBuilder.Append(isFirstParam ? "?" : "&"); - urlBuilder.Append(name + "=" + System.Net.WebUtility.UrlEncode(value.ToString())); + + urlBuilder.Append(name + "=" + System.Net.WebUtility.UrlEncode(ConvertValueToString(value))); + } + + private static string ConvertValueToString([NotNull] object value) + { + if (value is DateTime dateTimeValue) + { + return dateTimeValue.ToUniversalTime().ToString("u"); + } + + return value.ToString(); } } } diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/IRegularTestController.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/IRegularTestController.cs index 0e7b9d9163..546968d402 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/IRegularTestController.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/IRegularTestController.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; namespace Volo.Abp.Http.DynamicProxying { @@ -8,6 +9,8 @@ namespace Volo.Abp.Http.DynamicProxying Task GetException1Async(); + Task GetWithDateTimeParameterAsync(DateTime dateTime1); + Task PostValueWithHeaderAndQueryStringAsync(string headerValue, string qsValue); Task PostValueWithBodyAsync(string bodyValue); diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestController.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestController.cs index 717b62701e..ef2a8bface 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestController.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestController.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +using System; +using System.Globalization; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Volo.Abp.Application.Services; using Volo.Abp.AspNetCore.Mvc; @@ -25,6 +27,14 @@ namespace Volo.Abp.Http.DynamicProxying throw new UserFriendlyException("This is an error message!"); } + [HttpGet] + [Route("get-with-datetime-parameter")] + public Task GetWithDateTimeParameterAsync(DateTime dateTime1) + { + var culture = CultureInfo.CurrentCulture; + return Task.FromResult(dateTime1); + } + [HttpPost] [Route("post-with-header-and-qs")] public Task PostValueWithHeaderAndQueryStringAsync([FromHeader] string headerValue, [FromQuery] string qsValue) @@ -48,7 +58,7 @@ namespace Volo.Abp.Http.DynamicProxying [HttpPost] [Route("post-object-with-query")] - public Task PostObjectWithQueryAsync( Car bodyValue) + public Task PostObjectWithQueryAsync(Car bodyValue) { return Task.FromResult(bodyValue); } @@ -59,7 +69,7 @@ namespace Volo.Abp.Http.DynamicProxying { return Task.FromResult(bodyValue); } - + [HttpGet] [Route("post-object-and-id-with-url/{id}")] public Task GetObjectandIdAsync(int id, [FromBody] Car bodyValue) @@ -67,7 +77,7 @@ namespace Volo.Abp.Http.DynamicProxying bodyValue.Year = id; return Task.FromResult(bodyValue); } - + [HttpGet] [Route("post-object-and-id-with-url-and-query/{id}")] public Task GetObjectAndIdWithQueryAsync(int id, Car bodyValue) @@ -116,7 +126,11 @@ namespace Volo.Abp.Http.DynamicProxying { [FromQuery] public int Year { get; set; } + [FromQuery] public string Model { get; set; } + + [FromQuery] + public DateTime FirstReleaseDate { get; set; } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestControllerClientProxy_Tests.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestControllerClientProxy_Tests.cs index 28a024f68e..85825263bd 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestControllerClientProxy_Tests.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/RegularTestControllerClientProxy_Tests.cs @@ -1,7 +1,9 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Shouldly; using Volo.Abp.Http.Client; +using Volo.Abp.Localization; using Xunit; namespace Volo.Abp.Http.DynamicProxying @@ -30,6 +32,25 @@ namespace Volo.Abp.Http.DynamicProxying exception.Error.Message.ShouldBe("This is an error message!"); } + [Fact] + public async Task GetWithDateTimeParameterAsync() + { + var dateTime1 = new DateTime(2020, 04, 19, 19, 05, 01); + var result = await _controller.GetWithDateTimeParameterAsync(dateTime1); + result.ShouldBe(dateTime1); + } + + [Fact] + public async Task GetWithDateTimeParameterAsync_With_Different_Culture() + { + using (CultureHelper.Use("es")) + { + var dateTime1 = new DateTime(2020, 04, 19, 19, 05, 01); + var result = await _controller.GetWithDateTimeParameterAsync(dateTime1); + result.ShouldBe(dateTime1); + } + } + [Fact] public async Task PostValueWithHeaderAndQueryStringAsync() { @@ -54,7 +75,7 @@ namespace Volo.Abp.Http.DynamicProxying [Fact] public async Task PostObjectWithBodyAsync() { - var result = await _controller.PostObjectWithBodyAsync(new Car { Year = 1976, Model = "Ford" }); + var result = await _controller.PostObjectWithBodyAsync(new Car { Year = 1976, Model = "Ford", FirstReleaseDate = new DateTime(1976, 02, 22, 15, 0, 6, 22) }); result.Year.ShouldBe(1976); result.Model.ShouldBe("Ford"); } @@ -62,15 +83,26 @@ namespace Volo.Abp.Http.DynamicProxying [Fact] public async Task PostObjectWithQueryAsync() { - var result = await _controller.PostObjectWithQueryAsync(new Car { Year = 1976, Model = "Ford" }); + var result = await _controller.PostObjectWithQueryAsync(new Car { Year = 1976, Model = "Ford", FirstReleaseDate = new DateTime(1976, 02, 22, 15, 0, 6, 22) }); result.Year.ShouldBe(1976); result.Model.ShouldBe("Ford"); } + [Fact] + public async Task PostObjectWithQueryAsync_With_Different_Culture() + { + using (CultureHelper.Use("tr")) + { + var result = await _controller.PostObjectWithQueryAsync(new Car { Year = 1976, Model = "Ford", FirstReleaseDate = new DateTime(1976, 02, 22, 15, 0, 6, 22) }); + result.Year.ShouldBe(1976); + result.Model.ShouldBe("Ford"); + } + } + [Fact] public async Task GetObjectWithUrlAsync() { - var result = await _controller.GetObjectWithUrlAsync(new Car { Year = 1976, Model = "Ford" }); + var result = await _controller.GetObjectWithUrlAsync(new Car { Year = 1976, Model = "Ford", FirstReleaseDate = new DateTime(1976, 02, 22, 15, 0, 6, 22) }); result.Year.ShouldBe(1976); result.Model.ShouldBe("Ford"); } @@ -78,7 +110,7 @@ namespace Volo.Abp.Http.DynamicProxying [Fact] public async Task GetObjectandIdAsync() { - var result = await _controller.GetObjectandIdAsync(42, new Car { Year = 1976, Model = "Ford" }); + var result = await _controller.GetObjectandIdAsync(42, new Car { Year = 1976, Model = "Ford", FirstReleaseDate = new DateTime(1976, 02, 22, 15, 0, 6, 22) }); result.Year.ShouldBe(42); result.Model.ShouldBe("Ford"); } @@ -86,7 +118,7 @@ namespace Volo.Abp.Http.DynamicProxying [Fact] public async Task GetObjectAndIdWithQueryAsync() { - var result = await _controller.GetObjectAndIdWithQueryAsync(42, new Car { Year = 1976, Model = "Ford" }); + var result = await _controller.GetObjectAndIdWithQueryAsync(42, new Car { Year = 1976, Model = "Ford", FirstReleaseDate = new DateTime(1976, 02, 22, 15, 0, 6, 22) }); result.Year.ShouldBe(42); result.Model.ShouldBe("Ford"); } @@ -97,7 +129,7 @@ namespace Volo.Abp.Http.DynamicProxying var result = await _controller.PatchValueWithBodyAsync("mybody"); result.ShouldBe("mybody"); } - + [Fact] public async Task PutValueWithHeaderAndQueryStringAsync() {