diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/AbpHttpClientProxyingOptions.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/AbpHttpClientProxyingOptions.cs index fbd57e40c3..d1cecaeb1a 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/AbpHttpClientProxyingOptions.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/AbpHttpClientProxyingOptions.cs @@ -9,9 +9,12 @@ public class AbpHttpClientProxyingOptions public Dictionary FormDataConverts { get; set; } + public Dictionary PathConverts { get; set; } + public AbpHttpClientProxyingOptions() { QueryStringConverts = new Dictionary(); FormDataConverts = new Dictionary(); + PathConverts = new Dictionary(); } } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs index ab6cdb269e..abbbb8b6f1 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs @@ -112,8 +112,10 @@ public class ClientProxyRequestPayloadBuilder : ITransientDependency .MakeGenericMethod(value.GetType()) .Invoke(this, new object[] { - scope.ServiceProvider.GetRequiredService(HttpClientProxyingOptions.FormDataConverts[value.GetType()]), - value + scope.ServiceProvider.GetRequiredService(HttpClientProxyingOptions.FormDataConverts[value.GetType()]), + action, + parameter, + value }); if (formDataContents != null) @@ -154,7 +156,7 @@ public class ClientProxyRequestPayloadBuilder : ITransientDependency } else if (value.GetType().IsArray || (value.GetType().IsGenericType && value is IEnumerable)) { - foreach (var item in (IEnumerable)value) + foreach (var item in (IEnumerable) value) { formData.Add(new StringContent(item.ToString(), Encoding.UTF8), parameter.Name); } @@ -168,8 +170,8 @@ public class ClientProxyRequestPayloadBuilder : ITransientDependency return formData; } - protected virtual async Task>> ObjectToFormDataAsync(IObjectToFormData converter, T value) + protected virtual async Task>> ObjectToFormDataAsync(IObjectToFormData converter, ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, T value) { - return await converter.ConvertAsync(value); + return await converter.ConvertAsync(actionApiDescription, parameterApiDescription, value); } } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs index ce962bee14..63ddd75510 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs @@ -21,11 +21,17 @@ public class ClientProxyUrlBuilder : ITransientDependency { protected static MethodInfo CallObjectToQueryStringAsyncMethod { get; } + protected static MethodInfo CallObjectToPathAsyncMethod { get; } + static ClientProxyUrlBuilder() { CallObjectToQueryStringAsyncMethod = typeof(ClientProxyUrlBuilder) .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) .First(m => m.Name == nameof(ObjectToQueryStringAsync) && m.IsGenericMethodDefinition); + + CallObjectToPathAsyncMethod = typeof(ClientProxyUrlBuilder) + .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) + .First(m => m.Name == nameof(ObjectToPathAsync) && m.IsGenericMethodDefinition); } protected IServiceScopeFactory ServiceScopeFactory { get; } @@ -46,22 +52,22 @@ public class ClientProxyUrlBuilder : ITransientDependency { var urlBuilder = new StringBuilder(action.Url); - await ReplacePathVariablesAsync(urlBuilder, action.Parameters, methodArguments, apiVersion); - await AddQueryStringParametersAsync(urlBuilder, action.Parameters, methodArguments, apiVersion); + await ReplacePathVariablesAsync(urlBuilder, action, methodArguments, apiVersion); + await AddQueryStringParametersAsync(urlBuilder, action, methodArguments, apiVersion); return urlBuilder.ToString(); } } - protected virtual Task ReplacePathVariablesAsync(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) + protected virtual async Task ReplacePathVariablesAsync(StringBuilder urlBuilder, ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) { - var pathParameters = actionParameters + var pathParameters = action.Parameters .Where(p => p.BindingSourceId == ParameterBindingSources.Path) .ToArray(); if (!pathParameters.Any()) { - return Task.CompletedTask; + return; } if (pathParameters.Any(p => p.Name == "apiVersion")) @@ -81,7 +87,7 @@ public class ClientProxyUrlBuilder : ITransientDependency } else if (pathParameter.DefaultValue != null) { - urlBuilder = urlBuilder.Replace($"{{{pathParameter.Name}}}", pathParameter.DefaultValue.ToString()); + urlBuilder = urlBuilder.Replace($"{{{pathParameter.Name}}}", await ConvertValueToStringAsync(pathParameter.DefaultValue)); } else { @@ -90,16 +96,36 @@ public class ClientProxyUrlBuilder : ITransientDependency } else { - urlBuilder = urlBuilder.Replace($"{{{pathParameter.Name}}}", value.ToString()); + if (HttpClientProxyingOptions.PathConverts.ContainsKey(value.GetType())) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var path = await (Task)CallObjectToPathAsyncMethod + .MakeGenericMethod(value.GetType()) + .Invoke(this, new object[] + { + scope.ServiceProvider.GetRequiredService(HttpClientProxyingOptions.PathConverts[value.GetType()]), + action, + pathParameter, + value + }); + + if (path != null) + { + urlBuilder = urlBuilder.Replace($"{{{pathParameter.Name}}}", path); + continue; + } + } + } + + urlBuilder = urlBuilder.Replace($"{{{pathParameter.Name}}}", await ConvertValueToStringAsync(value)); } } - - return Task.CompletedTask; } - protected virtual async Task AddQueryStringParametersAsync(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) + protected virtual async Task AddQueryStringParametersAsync(StringBuilder urlBuilder, ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) { - var queryStringParameters = actionParameters + var queryStringParameters = action.Parameters .Where(p => p.BindingSourceId.IsIn(ParameterBindingSources.ModelBinding, ParameterBindingSources.Query)) .ToArray(); @@ -121,8 +147,10 @@ public class ClientProxyUrlBuilder : ITransientDependency .MakeGenericMethod(value.GetType()) .Invoke(this, new object[] { - scope.ServiceProvider.GetRequiredService(HttpClientProxyingOptions.QueryStringConverts[value.GetType()]), - value + scope.ServiceProvider.GetRequiredService(HttpClientProxyingOptions.QueryStringConverts[value.GetType()]), + action, + queryStringParameter, + value }); if (queryString != null) @@ -147,9 +175,14 @@ public class ClientProxyUrlBuilder : ITransientDependency } } - protected virtual async Task ObjectToQueryStringAsync(IObjectToQueryString converter, T value) + protected virtual async Task ObjectToQueryStringAsync(IObjectToQueryString converter, ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, T value) + { + return await converter.ConvertAsync(actionApiDescription, parameterApiDescription, value); + } + + protected virtual async Task ObjectToPathAsync(IObjectToPath converter, ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, T value) { - return await converter.ConvertAsync(value); + return await converter.ConvertAsync(actionApiDescription, parameterApiDescription, value); } protected virtual async Task AddQueryStringParameterAsync( @@ -161,7 +194,7 @@ public class ClientProxyUrlBuilder : ITransientDependency if (value.GetType().IsArray || (value.GetType().IsGenericType && value is IEnumerable)) { var index = 0; - foreach (var item in (IEnumerable)value) + foreach (var item in (IEnumerable) value) { if (index == 0) { @@ -194,4 +227,4 @@ public class ClientProxyUrlBuilder : ITransientDependency return Task.FromResult(value?.ToString()); } -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToFormData.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToFormData.cs index b98b8c8ba7..695ca888ba 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToFormData.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToFormData.cs @@ -1,10 +1,11 @@ using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; +using Volo.Abp.Http.Modeling; namespace Volo.Abp.Http.Client.ClientProxying; public interface IObjectToFormData { - Task>> ConvertAsync(TValue value); + Task>> ConvertAsync(ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, TValue value); } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToPath.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToPath.cs new file mode 100644 index 0000000000..2da0a3b90e --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToPath.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Volo.Abp.Http.Modeling; + +namespace Volo.Abp.Http.Client.ClientProxying +{ + public interface IObjectToPath + { + Task ConvertAsync(ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, TValue value); + } +} diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToQueryString.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToQueryString.cs index 7d1a84ed80..0e9008b1ed 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToQueryString.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToQueryString.cs @@ -1,8 +1,9 @@ using System.Threading.Tasks; +using Volo.Abp.Http.Modeling; namespace Volo.Abp.Http.Client.ClientProxying; public interface IObjectToQueryString { - Task ConvertAsync(TValue value); + Task ConvertAsync(ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, TValue value); } diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs index 66962962c7..6540b717d3 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs @@ -59,6 +59,7 @@ public class AbpHttpClientTestModule : AbpModule { options.QueryStringConverts.Add(typeof(List), typeof(TestObjectToQueryString)); options.FormDataConverts.Add(typeof(List), typeof(TestObjectToFormData)); + options.PathConverts.Add(typeof(int), typeof(TestObjectToPath)); }); } } 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 18ea2d3271..547f1bf5a7 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 @@ -26,6 +26,10 @@ public interface IRegularTestController Task GetObjectandIdAsync(int id, Car bodyValue); + Task GetObjectandFirstReleaseDateAsync(DateTime time, Car bodyValue); + + Task GetObjectandCountAsync(int count, Car bodyValue); + Task GetObjectAndIdWithQueryAsync(int id, Car bodyValue); Task PutValueWithBodyAsync(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 e9443f80ca..b092e67bc8 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 @@ -10,7 +10,7 @@ namespace Volo.Abp.Http.DynamicProxying; [Route("api/regular-test-controller")] [RemoteService] //Automatically enables API explorer and apply ABP conventions. - //[ApiExplorerSettings(IgnoreApi = false)] //alternative +//[ApiExplorerSettings(IgnoreApi = false)] //alternative public class RegularTestController : AbpController, IRegularTestController { [HttpGet] @@ -32,7 +32,7 @@ public class RegularTestController : AbpController, IRegularTestController public Task GetException2Async() { throw new BusinessException("Volo.Abp.Http.DynamicProxying:10001") - .WithData("0", "TEST"); + .WithData("0","TEST"); } [HttpGet] @@ -86,6 +86,22 @@ public class RegularTestController : AbpController, IRegularTestController return Task.FromResult(bodyValue); } + [HttpGet] + [Route("post-object-and-first-release-date-with-url/{time:datetime}")] + public Task GetObjectandFirstReleaseDateAsync(DateTime time, Car bodyValue) + { + bodyValue.FirstReleaseDate = time; + return Task.FromResult(bodyValue); + } + + [HttpGet] + [Route("post-object-and-count-with-url/{count}")] + public Task GetObjectandCountAsync(int count, Car bodyValue) + { + bodyValue.Year = count; + return Task.FromResult(bodyValue); + } + [HttpGet] [Route("post-object-and-id-with-url-and-query/{id}")] public Task GetObjectAndIdWithQueryAsync(int id, Car bodyValue) 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 338654a7bc..519e319f26 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 @@ -124,6 +124,23 @@ public class RegularTestControllerClientProxy_Tests : AbpHttpClientTestBase result.Model.ShouldBe("Ford"); } + [Fact] + public async Task GetObjectandFirstReleaseDateAsync() + { + var time = DateTime.Now; + var result = await _controller.GetObjectandFirstReleaseDateAsync(time, new Car { Year = 1976, Model = "Ford", FirstReleaseDate = new DateTime(1976, 02, 22, 15, 0, 6, 22) }); + result.FirstReleaseDate.ToUniversalTime().ShouldBe(time.ToUniversalTime()); + result.Model.ShouldBe("Ford"); + } + + [Fact] + public async Task GetObjectandCountAsync() + { + var result = await _controller.GetObjectandCountAsync(-1, new Car { Year = 1976, Model = "Ford", FirstReleaseDate = new DateTime(1976, 02, 22, 15, 0, 6, 22) }); + result.Year.ShouldBe(888); + result.Model.ShouldBe("Ford"); + } + [Fact] public async Task GetObjectAndIdWithQueryAsync() { diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToFormData.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToFormData.cs index f67337b601..2c64773c88 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToFormData.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToFormData.cs @@ -4,13 +4,14 @@ using System.Text; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client.ClientProxying; +using Volo.Abp.Http.Modeling; using Volo.Abp.TestApp.Application.Dto; namespace Volo.Abp.Http.DynamicProxying; public class TestObjectToFormData : IObjectToFormData>, ITransientDependency { - public Task>> ConvertAsync(List values) + public Task>> ConvertAsync(ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, List values) { if (values.IsNullOrEmpty()) { diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToPath.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToPath.cs new file mode 100644 index 0000000000..a9b7936578 --- /dev/null +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToPath.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.ClientProxying; +using Volo.Abp.Http.Modeling; + +namespace Volo.Abp.Http.DynamicProxying; + +public class TestObjectToPath : IObjectToPath, ITransientDependency +{ + public Task ConvertAsync(ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, int value) + { + if (actionApiDescription.Name == nameof(IRegularTestController.GetObjectandCountAsync)) + { + if (value <= 0) + { + value = 888; + } + return Task.FromResult(value.ToString()); + } + + return Task.FromResult(null); + } +} diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToQueryString.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToQueryString.cs index 7537dd8701..d4ca6f7285 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToQueryString.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToQueryString.cs @@ -3,13 +3,14 @@ using System.Text; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client.ClientProxying; +using Volo.Abp.Http.Modeling; using Volo.Abp.TestApp.Application.Dto; namespace Volo.Abp.Http.DynamicProxying; public class TestObjectToQueryString : IObjectToQueryString>, ITransientDependency { - public Task ConvertAsync(List values) + public Task ConvertAsync(ActionApiDescriptionModel actionApiDescription, ParameterApiDescriptionModel parameterApiDescription, List values) { if (values.IsNullOrEmpty()) {