diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Http/IObjectToQueryString.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Http/IObjectToQueryString.cs new file mode 100644 index 0000000000..19696e546c --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Http/IObjectToQueryString.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Http +{ + public interface IObjectToQueryString + { + Task ConvertAsync(TValue value); + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs index 132c198aa9..e0b999ac3d 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs @@ -134,7 +134,7 @@ namespace Volo.Abp.Reflection var currentType = objectType; var objectPath = currentType.FullName; var absolutePropertyPath = propertyPath; - if (objectPath != null && absolutePropertyPath.StartsWith(objectPath)) + if (objectPath != null && absolutePropertyPath.StartsWith(objectPath)) { absolutePropertyPath = absolutePropertyPath.Replace(objectPath + ".", ""); } @@ -144,7 +144,10 @@ namespace Volo.Abp.Reflection var property = currentType.GetProperty(propertyName); if (property != null) { - value = property.GetValue(value, null); + if (value != null) + { + value = property.GetValue(value, null); + } currentType = property.PropertyType; } else diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs index 5254b10238..68ebc87543 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs @@ -156,9 +156,9 @@ namespace Volo.Abp.Http.Client.ClientProxying return new ApiVersionInfo(versionParam?.BindingSourceId, apiVersion); } - protected virtual Task GetUrlWithParametersAsync(ClientProxyRequestContext requestContext, ApiVersionInfo apiVersion) + protected virtual async Task GetUrlWithParametersAsync(ClientProxyRequestContext requestContext, ApiVersionInfo apiVersion) { - return Task.FromResult(ClientProxyUrlBuilder.GenerateUrlWithParameters(requestContext.Action, requestContext.Arguments, apiVersion)); + return await ClientProxyUrlBuilder.GenerateUrlWithParametersAsync(requestContext.Action, requestContext.Arguments, apiVersion); } protected virtual Task GetHttpContentAsync(ClientProxyRequestContext requestContext, ApiVersionInfo apiVersion) 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 e6a9b8ff71..1eb07e88cf 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 @@ -3,8 +3,11 @@ using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Reflection; using System.Text; +using System.Threading.Tasks; using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client.Proxying; using Volo.Abp.Http.Modeling; @@ -15,7 +18,23 @@ namespace Volo.Abp.Http.Client.ClientProxying { public class ClientProxyUrlBuilder : ITransientDependency { - public string GenerateUrlWithParameters(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) + protected static MethodInfo CallObjectToQueryStringAsyncMethod { get; } + + static ClientProxyUrlBuilder() + { + CallObjectToQueryStringAsyncMethod = typeof(ClientProxyUrlBuilder) + .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) + .First(m => m.Name == nameof(ObjectToQueryStringAsync) && m.IsGenericMethodDefinition); + } + + protected IServiceScopeFactory ServiceScopeFactory { get; } + + public ClientProxyUrlBuilder(IServiceScopeFactory serviceScopeFactory) + { + ServiceScopeFactory = serviceScopeFactory; + } + + public async Task GenerateUrlWithParametersAsync(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) { // The ASP.NET Core route value provider and query string value provider: // Treat values as invariant culture. @@ -24,14 +43,14 @@ namespace Volo.Abp.Http.Client.ClientProxying { var urlBuilder = new StringBuilder(action.Url); - ReplacePathVariables(urlBuilder, action.Parameters, methodArguments, apiVersion); - AddQueryStringParameters(urlBuilder, action.Parameters, methodArguments, apiVersion); + await ReplacePathVariablesAsync(urlBuilder, action.Parameters, methodArguments, apiVersion); + await AddQueryStringParametersAsync(urlBuilder, action.Parameters, methodArguments, apiVersion); return urlBuilder.ToString(); } } - protected virtual void ReplacePathVariables(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) + protected virtual Task ReplacePathVariablesAsync(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) { var pathParameters = actionParameters .Where(p => p.BindingSourceId == ParameterBindingSources.Path) @@ -39,7 +58,7 @@ namespace Volo.Abp.Http.Client.ClientProxying if (!pathParameters.Any()) { - return; + return Task.CompletedTask; } if (pathParameters.Any(p => p.Name == "apiVersion")) @@ -71,9 +90,11 @@ namespace Volo.Abp.Http.Client.ClientProxying urlBuilder = urlBuilder.Replace($"{{{pathParameter.Name}}}", value.ToString()); } } + + return Task.CompletedTask; } - protected virtual void AddQueryStringParameters(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) + protected virtual async Task AddQueryStringParametersAsync(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) { var queryStringParameters = actionParameters .Where(p => p.BindingSourceId.IsIn(ParameterBindingSources.ModelBinding, ParameterBindingSources.Query)) @@ -89,7 +110,26 @@ namespace Volo.Abp.Http.Client.ClientProxying continue; } - if (AddQueryStringParameter(urlBuilder, isFirstParam, queryStringParameter.Name, value)) + using (var scope = ServiceScopeFactory.CreateScope()) + { + var objectToQuery = scope.ServiceProvider.GetService(typeof(IObjectToQueryString<>).MakeGenericType(value.GetType())); + if (objectToQuery != null) + { + var queryString = await (Task)CallObjectToQueryStringAsyncMethod + .MakeGenericMethod(value.GetType()) + .Invoke(this, new object[] { value }); + + if (!queryString.IsNullOrWhiteSpace()) + { + urlBuilder.Append(isFirstParam ? "?" : "&"); + urlBuilder.Append(queryString); + isFirstParam = false; + continue; + } + } + } + + if (await AddQueryStringParameterAsync(urlBuilder, isFirstParam, queryStringParameter.Name, value)) { isFirstParam = false; } @@ -97,11 +137,24 @@ namespace Volo.Abp.Http.Client.ClientProxying if (apiVersion.ShouldSendInQueryString()) { - AddQueryStringParameter(urlBuilder, isFirstParam, "api-version", apiVersion.Version); //TODO: Constant! + await AddQueryStringParameterAsync(urlBuilder, isFirstParam, "api-version", apiVersion.Version); //TODO: Constant! + } + } + + protected virtual async Task ObjectToQueryStringAsync(T value) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var objectToQueryString = scope.ServiceProvider.GetService>(); + if (objectToQueryString != null) + { + return await objectToQueryString.ConvertAsync(value); + } } + return null; } - protected virtual bool AddQueryStringParameter( + protected virtual async Task AddQueryStringParameterAsync( StringBuilder urlBuilder, bool isFirstParam, string name, @@ -116,7 +169,7 @@ namespace Volo.Abp.Http.Client.ClientProxying { urlBuilder.Append(isFirstParam ? "?" : "&"); } - urlBuilder.Append(name + $"[{index++}]=" + System.Net.WebUtility.UrlEncode(ConvertValueToString(item)) + "&"); + urlBuilder.Append(name + $"[{index++}]=" + System.Net.WebUtility.UrlEncode(await ConvertValueToStringAsync(item)) + "&"); } if (index > 0) @@ -130,18 +183,18 @@ namespace Volo.Abp.Http.Client.ClientProxying } urlBuilder.Append(isFirstParam ? "?" : "&"); - urlBuilder.Append(name + "=" + System.Net.WebUtility.UrlEncode(ConvertValueToString(value))); + urlBuilder.Append(name + "=" + System.Net.WebUtility.UrlEncode(await ConvertValueToStringAsync(value))); return true; } - protected virtual string ConvertValueToString([CanBeNull] object value) + protected virtual Task ConvertValueToStringAsync([CanBeNull] object value) { if (value is DateTime dateTimeValue) { - return dateTimeValue.ToUniversalTime().ToString("O"); + return Task.FromResult(dateTimeValue.ToUniversalTime().ToString("O")); } - return value?.ToString(); + return Task.FromResult(value?.ToString()); } } } diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs index b040c3e3d4..98225cf424 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs @@ -276,5 +276,32 @@ namespace Volo.Abp.Http.DynamicProxying }); result.ShouldBe("123.rtf:File1:application/rtf:1-1.rtf123.rtf:File2:application/rtf2:1-2.rtf789.rtf:File3:application/rtf3:i-789.rtf"); } + + [Fact] + public async Task GetParamsFromQueryAsync() + { + var result = await _peopleAppService.GetParamsFromQueryAsync(new GetParamsFromQueryInput() + { + NameValues = new List() + { + new GetParamsFromQueryInputNameValue() + { + Name = "name1", + Value = "value1" + }, + new GetParamsFromQueryInputNameValue() + { + Name = "name2", + Value = "value2" + } + }, + NameValue = new GetParamsFromQueryInputNameValue() + { + Name = "name3", + Value = "value3" + } + }); + result.ShouldBe("name1-value1:name2-value2:name3-value3"); + } } } diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/GetParamsFromQueryInput.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/GetParamsFromQueryInput.cs new file mode 100644 index 0000000000..764a6390f0 --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/GetParamsFromQueryInput.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http; + +namespace Volo.Abp.TestApp.Application.Dto +{ + public class GetParamsFromQueryInput + { + public List NameValues { get; set; } + + public GetParamsFromQueryInputNameValue NameValue { get; set; } + } + + public class GetParamsFromQueryInputNameValue + { + public string Name { get; set; } + + public string Value { get; set; } + } + + [ExposeServices(typeof(IObjectToQueryString>))] + public class TestInputToQueryString : IObjectToQueryString>, ITransientDependency + { + public Task ConvertAsync(List values) + { + if (values.IsNullOrEmpty()) + { + return null; + } + + var sb = new StringBuilder(); + + for (var i = 0; i < values.Count; i++) + { + sb.Append($"NameValues[{i}].Name={values[i].Name}&NameValues[{i}].Value={values[i].Value}&"); + } + + sb.Remove(sb.Length - 1, 1); + return Task.FromResult(sb.ToString()); + } + } +} diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs index 844e7fd383..ec63c279f2 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs @@ -32,5 +32,6 @@ namespace Volo.Abp.TestApp.Application Task CreateMultipleFileAsync(CreateMultipleFileInput input); + Task GetParamsFromQueryAsync(GetParamsFromQueryInput input); } } diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs index a3d8764423..f211a9198f 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs @@ -125,5 +125,13 @@ namespace Volo.Abp.TestApp.Application return str; } + + public Task GetParamsFromQueryAsync(GetParamsFromQueryInput input) + { + return Task.FromResult(input.NameValues?.FirstOrDefault()?.Name + "-" + + input.NameValues?.FirstOrDefault()?.Value + ":" + + input.NameValues?.LastOrDefault()?.Name + "-" + input.NameValues?.LastOrDefault()?.Value + ":" + + input.NameValue?.Name + "-" + input.NameValue?.Value); + } } }