diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Http/IObjectToFormData.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Http/IObjectToFormData.cs new file mode 100644 index 0000000000..166b51239e --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Http/IObjectToFormData.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Volo.Abp.Http +{ + public interface IObjectToFormData + { + Task>> ConvertAsync(TValue value); + } +} 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 68ebc87543..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); @@ -161,9 +161,9 @@ namespace Volo.Abp.Http.Client.ClientProxying 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..f76afae6a3 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,11 @@ 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 Volo.Abp.Content; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client.Proxying; @@ -16,21 +19,37 @@ 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; } + + public ClientProxyRequestPayloadBuilder(IServiceScopeFactory serviceScopeFactory) + { + ServiceScopeFactory = serviceScopeFactory; + } + [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 +58,7 @@ namespace Volo.Abp.Http.Client.ClientProxying if (parameters.Length <= 0) { - return null; + return Task.FromResult(null); } if (parameters.Length > 1) @@ -52,13 +71,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 +89,75 @@ 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) + { + continue; + } + + var formDataContents = await (Task>>)CallObjectToFormDataAsyncMethod + .MakeGenericMethod(value.GetType()) + .Invoke(this, new object[] { value }); + + if (formDataContents != null) { - var value = HttpActionParameterHelper.FindParameterValue(methodArguments, parameter); - if (value == null) + foreach (var content in formDataContents) { - continue; + formData.Add(content.Value, content.Key); } - if (value is IRemoteStreamContent remoteStreamContent) + continue; + } + + if (value is IRemoteStreamContent remoteStreamContent) + { + var stream = remoteStreamContent.GetStream(); + var streamContent = new StreamContent(stream); + if (!remoteStreamContent.ContentType.IsNullOrWhiteSpace()) { - 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); + streamContent.Headers.ContentType = new MediaTypeHeaderValue(remoteStreamContent.ContentType); } - else if (value is IEnumerable remoteStreamContents) + streamContent.Headers.ContentLength = remoteStreamContent.ContentLength; + formData.Add(streamContent, parameter.Name, remoteStreamContent.FileName ?? parameter.Name); + } + else if (value is IEnumerable remoteStreamContents) + { + foreach (var content in remoteStreamContents) { - foreach (var content in remoteStreamContents) + var stream = content.GetStream(); + var streamContent = new StreamContent(stream); + if (!content.ContentType.IsNullOrWhiteSpace()) { - 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); + streamContent.Headers.ContentType = new MediaTypeHeaderValue(content.ContentType); } - } - else - { - formData.Add(new StringContent(value.ToString(), Encoding.UTF8), parameter.Name); + streamContent.Headers.ContentLength = content.ContentLength; + formData.Add(streamContent, parameter.Name, content.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 { - var value = HttpActionParameterHelper.FindParameterValue(methodArguments, parameter); - if (value == null) - { - continue; - } + formData.Add(new StringContent(value.ToString(), Encoding.UTF8), parameter.Name); + } + } - postDataBuilder.Append(isFirstParam ? "?" : "&"); - postDataBuilder.Append(parameter.Name + "=" + System.Net.WebUtility.UrlEncode(value.ToString())); + return formData; + } - isFirstParam = false; + protected virtual async Task>> ObjectToFormDataAsync(T value) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var objectToFormData = scope.ServiceProvider.GetService>(); + if (objectToFormData != null) + { + return await objectToFormData.ConvertAsync(value); } - - return new StringContent(postDataBuilder.ToString(), Encoding.UTF8, MimeTypes.Application.XWwwFormUrlencoded); } + return null; } } } 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 0d97e6d348..97b9d747cf 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 @@ -114,7 +114,7 @@ namespace Volo.Abp.Http.Client.ClientProxying .MakeGenericMethod(value.GetType()) .Invoke(this, new object[] { value }); - if (!queryString.IsNullOrWhiteSpace()) + if (queryString != null) { urlBuilder.Append(isFirstParam ? "?" : "&"); urlBuilder.Append(queryString); 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 98225cf424..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 @@ -280,22 +280,49 @@ namespace Volo.Abp.Http.DynamicProxying [Fact] public async Task GetParamsFromQueryAsync() { - var result = await _peopleAppService.GetParamsFromQueryAsync(new GetParamsFromQueryInput() + var result = await _peopleAppService.GetParamsFromQueryAsync(new GetParamsInput() { - NameValues = new List() + NameValues = new List() { - new GetParamsFromQueryInputNameValue() + new GetParamsNameValue() { Name = "name1", Value = "value1" }, - new GetParamsFromQueryInputNameValue() + new GetParamsNameValue() { Name = "name2", Value = "value2" } }, - NameValue = new GetParamsFromQueryInputNameValue() + 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" 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/GetParamsFromQueryInput.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/GetParamsFromQueryInput.cs deleted file mode 100644 index 764a6390f0..0000000000 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/GetParamsFromQueryInput.cs +++ /dev/null @@ -1,44 +0,0 @@ -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/Dto/GetParamsInput.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/GetParamsInput.cs new file mode 100644 index 0000000000..3cbb1d4794 --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/GetParamsInput.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http; + +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; } + } + + [ExposeServices(typeof(IObjectToQueryString>))] + 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()); + } + } + + [ExposeServices(typeof(IObjectToFormData>))] + 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.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs index ec63c279f2..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,6 +32,8 @@ namespace Volo.Abp.TestApp.Application Task CreateMultipleFileAsync(CreateMultipleFileInput input); - Task GetParamsFromQueryAsync(GetParamsFromQueryInput 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 f211a9198f..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; @@ -126,7 +127,15 @@ namespace Volo.Abp.TestApp.Application return str; } - public Task GetParamsFromQueryAsync(GetParamsFromQueryInput input) + 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 + ":" +