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/AbpHttpClientProxyingOptions.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/AbpHttpClientProxyingOptions.cs new file mode 100644 index 0000000000..87ee7de37c --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/AbpHttpClientProxyingOptions.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Http.Client.ClientProxying +{ + public class AbpHttpClientProxyingOptions + { + public Dictionary QueryStringConverts { get; set; } + + public Dictionary FormDataConverts { get; set; } + + public AbpHttpClientProxyingOptions() + { + QueryStringConverts = new Dictionary(); + FormDataConverts = new Dictionary(); + } + } +} 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..9b47208141 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 @@ -28,15 +28,15 @@ namespace Volo.Abp.Http.Client.ClientProxying protected IClientProxyApiDescriptionFinder ClientProxyApiDescriptionFinder => LazyServiceProvider.LazyGetRequiredService(); protected ICancellationTokenProvider CancellationTokenProvider => LazyServiceProvider.LazyGetRequiredService(); protected ICorrelationIdProvider CorrelationIdProvider => LazyServiceProvider.LazyGetRequiredService(); - protected ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService(); - protected IOptions AbpCorrelationIdOptions => LazyServiceProvider.LazyGetRequiredService>(); - protected IProxyHttpClientFactory HttpClientFactory => LazyServiceProvider.LazyGetRequiredService(); - protected IRemoteServiceConfigurationProvider RemoteServiceConfigurationProvider => LazyServiceProvider.LazyGetRequiredService(); - protected IOptions ClientOptions => LazyServiceProvider.LazyGetRequiredService>(); - protected IJsonSerializer JsonSerializer => LazyServiceProvider.LazyGetRequiredService(); - protected IRemoteServiceHttpClientAuthenticator ClientAuthenticator => LazyServiceProvider.LazyGetRequiredService(); - protected ClientProxyRequestPayloadBuilder ClientProxyRequestPayloadBuilder => LazyServiceProvider.LazyGetRequiredService(); - protected ClientProxyUrlBuilder ClientProxyUrlBuilder => LazyServiceProvider.LazyGetRequiredService(); + protected ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService(); + protected IOptions AbpCorrelationIdOptions => LazyServiceProvider.LazyGetRequiredService>(); + protected IProxyHttpClientFactory HttpClientFactory => LazyServiceProvider.LazyGetRequiredService(); + protected IRemoteServiceConfigurationProvider RemoteServiceConfigurationProvider => LazyServiceProvider.LazyGetRequiredService(); + protected IOptions ClientOptions => LazyServiceProvider.LazyGetRequiredService>(); + protected IJsonSerializer JsonSerializer => LazyServiceProvider.LazyGetRequiredService(); + protected IRemoteServiceHttpClientAuthenticator ClientAuthenticator => LazyServiceProvider.LazyGetRequiredService(); + protected ClientProxyRequestPayloadBuilder ClientProxyRequestPayloadBuilder => LazyServiceProvider.LazyGetRequiredService(); + protected ClientProxyUrlBuilder ClientProxyUrlBuilder => LazyServiceProvider.LazyGetRequiredService(); protected virtual async Task RequestAsync(string methodName, ClientProxyRequestTypeValue arguments = null) { @@ -114,7 +114,7 @@ namespace Volo.Abp.Http.Client.ClientProxying var requestMessage = new HttpRequestMessage(requestContext.Action.GetHttpMethod(), url) { - Content = ClientProxyRequestPayloadBuilder.BuildContent(requestContext.Action, requestContext.Arguments, JsonSerializer, apiVersion) + Content = await ClientProxyRequestPayloadBuilder.BuildContentAsync(requestContext.Action, requestContext.Arguments, JsonSerializer, apiVersion) }; AddHeaders(requestContext.Arguments, requestContext.Action, requestMessage, apiVersion); @@ -156,14 +156,14 @@ 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) + protected virtual async Task GetHttpContentAsync(ClientProxyRequestContext requestContext, ApiVersionInfo apiVersion) { - return Task.FromResult(ClientProxyRequestPayloadBuilder.BuildContent(requestContext.Action, requestContext.Arguments, JsonSerializer, apiVersion)); + return await ClientProxyRequestPayloadBuilder.BuildContentAsync(requestContext.Action, requestContext.Arguments, JsonSerializer, apiVersion); } protected virtual async Task FindBestApiVersionAsync(ClientProxyRequestContext requestContext) 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 3ba7026c8e..a91a9904de 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 @@ -3,8 +3,12 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; +using System.Reflection; using System.Text; +using System.Threading.Tasks; using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Volo.Abp.Content; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client.Proxying; @@ -16,21 +20,40 @@ namespace Volo.Abp.Http.Client.ClientProxying { public class ClientProxyRequestPayloadBuilder : ITransientDependency { + protected static MethodInfo CallObjectToFormDataAsyncMethod { get; } + + static ClientProxyRequestPayloadBuilder() + { + CallObjectToFormDataAsyncMethod = typeof(ClientProxyRequestPayloadBuilder) + .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) + .First(m => m.Name == nameof(ObjectToFormDataAsync) && m.IsGenericMethodDefinition); + } + + protected IServiceScopeFactory ServiceScopeFactory { get; } + + protected AbpHttpClientProxyingOptions HttpClientProxyingOptions { get; } + + public ClientProxyRequestPayloadBuilder(IServiceScopeFactory serviceScopeFactory, IOptions httpClientProxyingOptions) + { + ServiceScopeFactory = serviceScopeFactory; + HttpClientProxyingOptions = httpClientProxyingOptions.Value; + } + [CanBeNull] - public virtual HttpContent BuildContent(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, IJsonSerializer jsonSerializer, ApiVersionInfo apiVersion) + public virtual async Task BuildContentAsync(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, IJsonSerializer jsonSerializer, ApiVersionInfo apiVersion) { - var body = GenerateBody(action, methodArguments, jsonSerializer); + var body = await GenerateBodyAsync(action, methodArguments, jsonSerializer); if (body != null) { return body; } - body = GenerateFormPostData(action, methodArguments); + body = await GenerateFormPostDataAsync(action, methodArguments); return body; } - protected virtual HttpContent GenerateBody(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, IJsonSerializer jsonSerializer) + protected virtual Task GenerateBodyAsync(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, IJsonSerializer jsonSerializer) { var parameters = action .Parameters @@ -39,7 +62,7 @@ namespace Volo.Abp.Http.Client.ClientProxying if (parameters.Length <= 0) { - return null; + return Task.FromResult(null); } if (parameters.Length > 1) @@ -52,13 +75,13 @@ namespace Volo.Abp.Http.Client.ClientProxying var value = HttpActionParameterHelper.FindParameterValue(methodArguments, parameters[0]); if (value == null) { - return null; + return Task.FromResult(null); } - return new StringContent(jsonSerializer.Serialize(value), Encoding.UTF8, MimeTypes.Application.Json); + return Task.FromResult(new StringContent(jsonSerializer.Serialize(value), Encoding.UTF8, MimeTypes.Application.Json)); } - protected virtual HttpContent GenerateFormPostData(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments) + protected virtual async Task GenerateFormPostDataAsync(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments) { var parameters = action .Parameters @@ -70,71 +93,76 @@ namespace Volo.Abp.Http.Client.ClientProxying return null; } - if (parameters.Any(x => x.BindingSourceId == ParameterBindingSources.FormFile)) + var formData = new MultipartFormDataContent(); + + foreach (var parameter in parameters) { - var formData = new MultipartFormDataContent(); - foreach (var parameter in parameters) + var value = HttpActionParameterHelper.FindParameterValue(methodArguments, parameter); + if (value == null) { - var value = HttpActionParameterHelper.FindParameterValue(methodArguments, parameter); - if (value == null) - { - continue; - } + continue; + } - if (value is IRemoteStreamContent remoteStreamContent) - { - var stream = remoteStreamContent.GetStream(); - var streamContent = new StreamContent(stream); - if (!remoteStreamContent.ContentType.IsNullOrWhiteSpace()) - { - streamContent.Headers.ContentType = new MediaTypeHeaderValue(remoteStreamContent.ContentType); - } - streamContent.Headers.ContentLength = remoteStreamContent.ContentLength; - formData.Add(streamContent, parameter.Name, remoteStreamContent.FileName ?? parameter.Name); - } - else if (value is IEnumerable remoteStreamContents) + if (HttpClientProxyingOptions.FormDataConverts.ContainsKey(value.GetType())) + { + using (var scope = ServiceScopeFactory.CreateScope()) { - foreach (var content in remoteStreamContents) + var formDataContents = await (Task>>)CallObjectToFormDataAsyncMethod + .MakeGenericMethod(value.GetType()) + .Invoke(this, new object[] + { + scope.ServiceProvider.GetRequiredService(HttpClientProxyingOptions.FormDataConverts[value.GetType()]), + value + }); + + if (formDataContents != null) { - var stream = content.GetStream(); - var streamContent = new StreamContent(stream); - if (!content.ContentType.IsNullOrWhiteSpace()) + foreach (var content in formDataContents) { - streamContent.Headers.ContentType = new MediaTypeHeaderValue(content.ContentType); + formData.Add(content.Value, content.Key); } - streamContent.Headers.ContentLength = content.ContentLength; - formData.Add(streamContent, parameter.Name, content.FileName ?? parameter.Name); + continue; } } - else + } + + if (value is IRemoteStreamContent remoteStreamContent) + { + var stream = remoteStreamContent.GetStream(); + var streamContent = new StreamContent(stream); + if (!remoteStreamContent.ContentType.IsNullOrWhiteSpace()) { - formData.Add(new StringContent(value.ToString(), Encoding.UTF8), parameter.Name); + streamContent.Headers.ContentType = new MediaTypeHeaderValue(remoteStreamContent.ContentType); } + streamContent.Headers.ContentLength = remoteStreamContent.ContentLength; + formData.Add(streamContent, parameter.Name, remoteStreamContent.FileName ?? parameter.Name); } - - return formData; - } - else - { - var postDataBuilder = new StringBuilder(); - - var isFirstParam = true; - foreach (var parameter in parameters.Where(p => p.BindingSourceId == ParameterBindingSources.Form)) + else if (value is IEnumerable remoteStreamContents) { - var value = HttpActionParameterHelper.FindParameterValue(methodArguments, parameter); - if (value == null) + foreach (var content in remoteStreamContents) { - continue; + var stream = content.GetStream(); + var streamContent = new StreamContent(stream); + if (!content.ContentType.IsNullOrWhiteSpace()) + { + streamContent.Headers.ContentType = new MediaTypeHeaderValue(content.ContentType); + } + streamContent.Headers.ContentLength = content.ContentLength; + formData.Add(streamContent, parameter.Name, content.FileName ?? parameter.Name); } - - postDataBuilder.Append(isFirstParam ? "?" : "&"); - postDataBuilder.Append(parameter.Name + "=" + System.Net.WebUtility.UrlEncode(value.ToString())); - - isFirstParam = false; } - - return new StringContent(postDataBuilder.ToString(), Encoding.UTF8, MimeTypes.Application.XWwwFormUrlencoded); + else + { + formData.Add(new StringContent(value.ToString(), Encoding.UTF8), parameter.Name); + } } + + return formData; + } + + protected virtual async Task>> ObjectToFormDataAsync(IObjectToFormData converter, T value) + { + return await converter.ConvertAsync(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 e6a9b8ff71..12f7bc1ac8 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,12 @@ 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 Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client.Proxying; using Volo.Abp.Http.Modeling; @@ -15,7 +19,25 @@ 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; } + protected AbpHttpClientProxyingOptions HttpClientProxyingOptions { get; } + + public ClientProxyUrlBuilder(IServiceScopeFactory serviceScopeFactory, IOptions httpClientProxyingOptions) + { + ServiceScopeFactory = serviceScopeFactory; + HttpClientProxyingOptions = httpClientProxyingOptions.Value; + } + + 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 +46,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 +61,7 @@ namespace Volo.Abp.Http.Client.ClientProxying if (!pathParameters.Any()) { - return; + return Task.CompletedTask; } if (pathParameters.Any(p => p.Name == "apiVersion")) @@ -71,9 +93,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 +113,29 @@ namespace Volo.Abp.Http.Client.ClientProxying continue; } - if (AddQueryStringParameter(urlBuilder, isFirstParam, queryStringParameter.Name, value)) + if (HttpClientProxyingOptions.QueryStringConverts.ContainsKey(value.GetType())) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var queryString = await (Task)CallObjectToQueryStringAsyncMethod + .MakeGenericMethod(value.GetType()) + .Invoke(this, new object[] + { + scope.ServiceProvider.GetRequiredService(HttpClientProxyingOptions.QueryStringConverts[value.GetType()]), + value + }); + + if (queryString != null) + { + urlBuilder.Append(isFirstParam ? "?" : "&"); + urlBuilder.Append(queryString); + isFirstParam = false; + continue; + } + } + } + + if (await AddQueryStringParameterAsync(urlBuilder, isFirstParam, queryStringParameter.Name, value)) { isFirstParam = false; } @@ -97,11 +143,16 @@ 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 bool AddQueryStringParameter( + protected virtual async Task ObjectToQueryStringAsync(IObjectToQueryString converter, T value) + { + return await converter.ConvertAsync(value); + } + + protected virtual async Task AddQueryStringParameterAsync( StringBuilder urlBuilder, bool isFirstParam, string name, @@ -116,7 +167,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 +181,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/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 new file mode 100644 index 0000000000..5aec4252e3 --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToFormData.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Volo.Abp.Http.Client.ClientProxying +{ + public interface IObjectToFormData + { + Task>> ConvertAsync(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 new file mode 100644 index 0000000000..ef223a903d --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToQueryString.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Http.Client.ClientProxying +{ + public interface IObjectToQueryString + { + Task ConvertAsync(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 966ab01840..d90ef50006 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 @@ -1,7 +1,9 @@ -using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.Http.Client; +using Volo.Abp.Http.Client.ClientProxying; using Volo.Abp.Http.DynamicProxying; using Volo.Abp.Http.Localization; using Volo.Abp.Localization; @@ -52,6 +54,12 @@ namespace Volo.Abp.Http options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(CreateFileInput)); options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(CreateMultipleFileInput)); }); + + Configure(options => + { + options.QueryStringConverts.Add(typeof(List), typeof(TestObjectToQueryString)); + options.FormDataConverts.Add(typeof(List), typeof(TestObjectToFormData)); + }); } } } 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..789c552861 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,59 @@ 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 GetParamsInput() + { + NameValues = new List() + { + new GetParamsNameValue() + { + Name = "name1", + Value = "value1" + }, + new GetParamsNameValue() + { + Name = "name2", + Value = "value2" + } + }, + NameValue = new GetParamsNameValue() + { + Name = "name3", + Value = "value3" + } + }); + result.ShouldBe("name1-value1:name2-value2:name3-value3"); + } + + [Fact] + public async Task GetParamsFromFormAsync() + { + var result = await _peopleAppService.GetParamsFromFormAsync(new GetParamsInput() + { + NameValues = new List() + { + new GetParamsNameValue() + { + Name = "name1", + Value = "value1" + }, + new GetParamsNameValue() + { + Name = "name2", + Value = "value2" + } + }, + NameValue = new GetParamsNameValue() + { + Name = "name3", + Value = "value3" + } + }); + result.ShouldBe("name1-value1:name2-value2:name3-value3"); + } } } 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 new file mode 100644 index 0000000000..65b65fc54b --- /dev/null +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToFormData.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.ClientProxying; +using Volo.Abp.TestApp.Application.Dto; + +namespace Volo.Abp.Http.DynamicProxying +{ + public class TestObjectToFormData : IObjectToFormData>, ITransientDependency + { + public Task>> ConvertAsync(List values) + { + if (values.IsNullOrEmpty()) + { + return null; + } + + var formDataContents = new List>(); + for (var i = 0; i < values.Count; i++) + { + formDataContents.Add(new KeyValuePair($"NameValues[{i}].Name", new StringContent(values[i].Name, Encoding.UTF8))); + formDataContents.Add(new KeyValuePair($"NameValues[{i}].Value", new StringContent(values[i].Value, Encoding.UTF8))); + } + + return Task.FromResult(formDataContents); + } + } +} 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 new file mode 100644 index 0000000000..ae11a7bf2f --- /dev/null +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToQueryString.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.ClientProxying; +using Volo.Abp.TestApp.Application.Dto; + +namespace Volo.Abp.Http.DynamicProxying +{ + public class TestObjectToQueryString : 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.csproj b/framework/test/Volo.Abp.TestApp/Volo.Abp.TestApp.csproj index 6dc99c23c3..0664ad2b7b 100644 --- a/framework/test/Volo.Abp.TestApp/Volo.Abp.TestApp.csproj +++ b/framework/test/Volo.Abp.TestApp/Volo.Abp.TestApp.csproj @@ -1,4 +1,4 @@ - + diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/GetParamsInput.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/GetParamsInput.cs new file mode 100644 index 0000000000..93a643491c --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/GetParamsInput.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Volo.Abp.TestApp.Application.Dto +{ + public class GetParamsInput + { + public List NameValues { get; set; } + + public GetParamsNameValue NameValue { get; set; } + } + + public class GetParamsNameValue + { + public string Name { get; set; } + + public string Value { get; set; } + } +} 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..b9cb2e0887 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,8 @@ namespace Volo.Abp.TestApp.Application Task CreateMultipleFileAsync(CreateMultipleFileInput input); + Task GetParamsFromQueryAsync(GetParamsInput input); + + Task GetParamsFromFormAsync(GetParamsInput 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..4baa2a4ec2 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 @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; using Volo.Abp.Application.Dtos; using Volo.Abp.TestApp.Domain; using Volo.Abp.Domain.Repositories; @@ -125,5 +126,21 @@ namespace Volo.Abp.TestApp.Application return str; } + + public Task GetParamsFromQueryAsync([FromQuery]GetParamsInput 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); + } + + public Task GetParamsFromFormAsync([FromForm]GetParamsInput 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); + } } }