Browse Source

Merge pull request #10220 from abpframework/IObjectToQueryString

Add `IObjectToQueryString` and `IObjectToFormData`.
pull/10274/head
Halil İbrahim Kalkan 5 years ago
committed by GitHub
parent
commit
e2faf69da1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs
  2. 18
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/AbpHttpClientProxyingOptions.cs
  3. 28
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs
  4. 140
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs
  5. 79
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs
  6. 11
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToFormData.cs
  7. 9
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/IObjectToQueryString.cs
  8. 10
      framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs
  9. 54
      framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs
  10. 30
      framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToFormData.cs
  11. 30
      framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/TestObjectToQueryString.cs
  12. 2
      framework/test/Volo.Abp.TestApp/Volo.Abp.TestApp.csproj
  13. 18
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/GetParamsInput.cs
  14. 3
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs
  15. 17
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs

7
framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs

@ -134,7 +134,7 @@ namespace Volo.Abp.Reflection
var currentType = objectType; var currentType = objectType;
var objectPath = currentType.FullName; var objectPath = currentType.FullName;
var absolutePropertyPath = propertyPath; var absolutePropertyPath = propertyPath;
if (objectPath != null && absolutePropertyPath.StartsWith(objectPath)) if (objectPath != null && absolutePropertyPath.StartsWith(objectPath))
{ {
absolutePropertyPath = absolutePropertyPath.Replace(objectPath + ".", ""); absolutePropertyPath = absolutePropertyPath.Replace(objectPath + ".", "");
} }
@ -144,7 +144,10 @@ namespace Volo.Abp.Reflection
var property = currentType.GetProperty(propertyName); var property = currentType.GetProperty(propertyName);
if (property != null) if (property != null)
{ {
value = property.GetValue(value, null); if (value != null)
{
value = property.GetValue(value, null);
}
currentType = property.PropertyType; currentType = property.PropertyType;
} }
else else

18
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<Type, Type> QueryStringConverts { get; set; }
public Dictionary<Type, Type> FormDataConverts { get; set; }
public AbpHttpClientProxyingOptions()
{
QueryStringConverts = new Dictionary<Type, Type>();
FormDataConverts = new Dictionary<Type, Type>();
}
}
}

28
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<IClientProxyApiDescriptionFinder>(); protected IClientProxyApiDescriptionFinder ClientProxyApiDescriptionFinder => LazyServiceProvider.LazyGetRequiredService<IClientProxyApiDescriptionFinder>();
protected ICancellationTokenProvider CancellationTokenProvider => LazyServiceProvider.LazyGetRequiredService<ICancellationTokenProvider>(); protected ICancellationTokenProvider CancellationTokenProvider => LazyServiceProvider.LazyGetRequiredService<ICancellationTokenProvider>();
protected ICorrelationIdProvider CorrelationIdProvider => LazyServiceProvider.LazyGetRequiredService<ICorrelationIdProvider>(); protected ICorrelationIdProvider CorrelationIdProvider => LazyServiceProvider.LazyGetRequiredService<ICorrelationIdProvider>();
protected ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>(); protected ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
protected IOptions<AbpCorrelationIdOptions> AbpCorrelationIdOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpCorrelationIdOptions>>(); protected IOptions<AbpCorrelationIdOptions> AbpCorrelationIdOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpCorrelationIdOptions>>();
protected IProxyHttpClientFactory HttpClientFactory => LazyServiceProvider.LazyGetRequiredService<IProxyHttpClientFactory>(); protected IProxyHttpClientFactory HttpClientFactory => LazyServiceProvider.LazyGetRequiredService<IProxyHttpClientFactory>();
protected IRemoteServiceConfigurationProvider RemoteServiceConfigurationProvider => LazyServiceProvider.LazyGetRequiredService<IRemoteServiceConfigurationProvider>(); protected IRemoteServiceConfigurationProvider RemoteServiceConfigurationProvider => LazyServiceProvider.LazyGetRequiredService<IRemoteServiceConfigurationProvider>();
protected IOptions<AbpHttpClientOptions> ClientOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpHttpClientOptions>>(); protected IOptions<AbpHttpClientOptions> ClientOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpHttpClientOptions>>();
protected IJsonSerializer JsonSerializer => LazyServiceProvider.LazyGetRequiredService<IJsonSerializer>(); protected IJsonSerializer JsonSerializer => LazyServiceProvider.LazyGetRequiredService<IJsonSerializer>();
protected IRemoteServiceHttpClientAuthenticator ClientAuthenticator => LazyServiceProvider.LazyGetRequiredService<IRemoteServiceHttpClientAuthenticator>(); protected IRemoteServiceHttpClientAuthenticator ClientAuthenticator => LazyServiceProvider.LazyGetRequiredService<IRemoteServiceHttpClientAuthenticator>();
protected ClientProxyRequestPayloadBuilder ClientProxyRequestPayloadBuilder => LazyServiceProvider.LazyGetRequiredService<ClientProxyRequestPayloadBuilder>(); protected ClientProxyRequestPayloadBuilder ClientProxyRequestPayloadBuilder => LazyServiceProvider.LazyGetRequiredService<ClientProxyRequestPayloadBuilder>();
protected ClientProxyUrlBuilder ClientProxyUrlBuilder => LazyServiceProvider.LazyGetRequiredService<ClientProxyUrlBuilder>(); protected ClientProxyUrlBuilder ClientProxyUrlBuilder => LazyServiceProvider.LazyGetRequiredService<ClientProxyUrlBuilder>();
protected virtual async Task RequestAsync(string methodName, ClientProxyRequestTypeValue arguments = null) 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) 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); AddHeaders(requestContext.Arguments, requestContext.Action, requestMessage, apiVersion);
@ -156,14 +156,14 @@ namespace Volo.Abp.Http.Client.ClientProxying
return new ApiVersionInfo(versionParam?.BindingSourceId, apiVersion); return new ApiVersionInfo(versionParam?.BindingSourceId, apiVersion);
} }
protected virtual Task<string> GetUrlWithParametersAsync(ClientProxyRequestContext requestContext, ApiVersionInfo apiVersion) protected virtual async Task<string> 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<HttpContent> GetHttpContentAsync(ClientProxyRequestContext requestContext, ApiVersionInfo apiVersion) protected virtual async Task<HttpContent> 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<string> FindBestApiVersionAsync(ClientProxyRequestContext requestContext) protected virtual async Task<string> FindBestApiVersionAsync(ClientProxyRequestContext requestContext)

140
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.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Reflection;
using System.Text; using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.Content; using Volo.Abp.Content;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.Proxying; using Volo.Abp.Http.Client.Proxying;
@ -16,21 +20,40 @@ namespace Volo.Abp.Http.Client.ClientProxying
{ {
public class ClientProxyRequestPayloadBuilder : ITransientDependency 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<AbpHttpClientProxyingOptions> httpClientProxyingOptions)
{
ServiceScopeFactory = serviceScopeFactory;
HttpClientProxyingOptions = httpClientProxyingOptions.Value;
}
[CanBeNull] [CanBeNull]
public virtual HttpContent BuildContent(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments, IJsonSerializer jsonSerializer, ApiVersionInfo apiVersion) public virtual async Task<HttpContent> BuildContentAsync(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments, IJsonSerializer jsonSerializer, ApiVersionInfo apiVersion)
{ {
var body = GenerateBody(action, methodArguments, jsonSerializer); var body = await GenerateBodyAsync(action, methodArguments, jsonSerializer);
if (body != null) if (body != null)
{ {
return body; return body;
} }
body = GenerateFormPostData(action, methodArguments); body = await GenerateFormPostDataAsync(action, methodArguments);
return body; return body;
} }
protected virtual HttpContent GenerateBody(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments, IJsonSerializer jsonSerializer) protected virtual Task<HttpContent> GenerateBodyAsync(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments, IJsonSerializer jsonSerializer)
{ {
var parameters = action var parameters = action
.Parameters .Parameters
@ -39,7 +62,7 @@ namespace Volo.Abp.Http.Client.ClientProxying
if (parameters.Length <= 0) if (parameters.Length <= 0)
{ {
return null; return Task.FromResult<HttpContent>(null);
} }
if (parameters.Length > 1) if (parameters.Length > 1)
@ -52,13 +75,13 @@ namespace Volo.Abp.Http.Client.ClientProxying
var value = HttpActionParameterHelper.FindParameterValue(methodArguments, parameters[0]); var value = HttpActionParameterHelper.FindParameterValue(methodArguments, parameters[0]);
if (value == null) if (value == null)
{ {
return null; return Task.FromResult<HttpContent>(null);
} }
return new StringContent(jsonSerializer.Serialize(value), Encoding.UTF8, MimeTypes.Application.Json); return Task.FromResult<HttpContent>(new StringContent(jsonSerializer.Serialize(value), Encoding.UTF8, MimeTypes.Application.Json));
} }
protected virtual HttpContent GenerateFormPostData(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments) protected virtual async Task<HttpContent> GenerateFormPostDataAsync(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments)
{ {
var parameters = action var parameters = action
.Parameters .Parameters
@ -70,71 +93,76 @@ namespace Volo.Abp.Http.Client.ClientProxying
return null; return null;
} }
if (parameters.Any(x => x.BindingSourceId == ParameterBindingSources.FormFile)) var formData = new MultipartFormDataContent();
foreach (var parameter in parameters)
{ {
var formData = new MultipartFormDataContent(); var value = HttpActionParameterHelper.FindParameterValue(methodArguments, parameter);
foreach (var parameter in parameters) if (value == null)
{ {
var value = HttpActionParameterHelper.FindParameterValue(methodArguments, parameter); continue;
if (value == null) }
{
continue;
}
if (value is IRemoteStreamContent remoteStreamContent) if (HttpClientProxyingOptions.FormDataConverts.ContainsKey(value.GetType()))
{ {
var stream = remoteStreamContent.GetStream(); using (var scope = ServiceScopeFactory.CreateScope())
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<IRemoteStreamContent> remoteStreamContents)
{ {
foreach (var content in remoteStreamContents) var formDataContents = await (Task<List<KeyValuePair<string, HttpContent>>>)CallObjectToFormDataAsyncMethod
.MakeGenericMethod(value.GetType())
.Invoke(this, new object[]
{
scope.ServiceProvider.GetRequiredService(HttpClientProxyingOptions.FormDataConverts[value.GetType()]),
value
});
if (formDataContents != null)
{ {
var stream = content.GetStream(); foreach (var content in formDataContents)
var streamContent = new StreamContent(stream);
if (!content.ContentType.IsNullOrWhiteSpace())
{ {
streamContent.Headers.ContentType = new MediaTypeHeaderValue(content.ContentType); formData.Add(content.Value, content.Key);
} }
streamContent.Headers.ContentLength = content.ContentLength; continue;
formData.Add(streamContent, parameter.Name, content.FileName ?? parameter.Name);
} }
} }
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);
} }
else if (value is IEnumerable<IRemoteStreamContent> remoteStreamContents)
return formData;
}
else
{
var postDataBuilder = new StringBuilder();
var isFirstParam = true;
foreach (var parameter in parameters.Where(p => p.BindingSourceId == ParameterBindingSources.Form))
{ {
var value = HttpActionParameterHelper.FindParameterValue(methodArguments, parameter); foreach (var content in remoteStreamContents)
if (value == null)
{ {
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;
} }
else
return new StringContent(postDataBuilder.ToString(), Encoding.UTF8, MimeTypes.Application.XWwwFormUrlencoded); {
formData.Add(new StringContent(value.ToString(), Encoding.UTF8), parameter.Name);
}
} }
return formData;
}
protected virtual async Task<List<KeyValuePair<string, HttpContent>>> ObjectToFormDataAsync<T>(IObjectToFormData<T> converter, T value)
{
return await converter.ConvertAsync(value);
} }
} }
} }

79
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.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Text; using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.Proxying; using Volo.Abp.Http.Client.Proxying;
using Volo.Abp.Http.Modeling; using Volo.Abp.Http.Modeling;
@ -15,7 +19,25 @@ namespace Volo.Abp.Http.Client.ClientProxying
{ {
public class ClientProxyUrlBuilder : ITransientDependency public class ClientProxyUrlBuilder : ITransientDependency
{ {
public string GenerateUrlWithParameters(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> 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<AbpHttpClientProxyingOptions> httpClientProxyingOptions)
{
ServiceScopeFactory = serviceScopeFactory;
HttpClientProxyingOptions = httpClientProxyingOptions.Value;
}
public async Task<string> GenerateUrlWithParametersAsync(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments, ApiVersionInfo apiVersion)
{ {
// The ASP.NET Core route value provider and query string value provider: // The ASP.NET Core route value provider and query string value provider:
// Treat values as invariant culture. // Treat values as invariant culture.
@ -24,14 +46,14 @@ namespace Volo.Abp.Http.Client.ClientProxying
{ {
var urlBuilder = new StringBuilder(action.Url); var urlBuilder = new StringBuilder(action.Url);
ReplacePathVariables(urlBuilder, action.Parameters, methodArguments, apiVersion); await ReplacePathVariablesAsync(urlBuilder, action.Parameters, methodArguments, apiVersion);
AddQueryStringParameters(urlBuilder, action.Parameters, methodArguments, apiVersion); await AddQueryStringParametersAsync(urlBuilder, action.Parameters, methodArguments, apiVersion);
return urlBuilder.ToString(); return urlBuilder.ToString();
} }
} }
protected virtual void ReplacePathVariables(StringBuilder urlBuilder, IList<ParameterApiDescriptionModel> actionParameters, IReadOnlyDictionary<string, object> methodArguments, ApiVersionInfo apiVersion) protected virtual Task ReplacePathVariablesAsync(StringBuilder urlBuilder, IList<ParameterApiDescriptionModel> actionParameters, IReadOnlyDictionary<string, object> methodArguments, ApiVersionInfo apiVersion)
{ {
var pathParameters = actionParameters var pathParameters = actionParameters
.Where(p => p.BindingSourceId == ParameterBindingSources.Path) .Where(p => p.BindingSourceId == ParameterBindingSources.Path)
@ -39,7 +61,7 @@ namespace Volo.Abp.Http.Client.ClientProxying
if (!pathParameters.Any()) if (!pathParameters.Any())
{ {
return; return Task.CompletedTask;
} }
if (pathParameters.Any(p => p.Name == "apiVersion")) if (pathParameters.Any(p => p.Name == "apiVersion"))
@ -71,9 +93,11 @@ namespace Volo.Abp.Http.Client.ClientProxying
urlBuilder = urlBuilder.Replace($"{{{pathParameter.Name}}}", value.ToString()); urlBuilder = urlBuilder.Replace($"{{{pathParameter.Name}}}", value.ToString());
} }
} }
return Task.CompletedTask;
} }
protected virtual void AddQueryStringParameters(StringBuilder urlBuilder, IList<ParameterApiDescriptionModel> actionParameters, IReadOnlyDictionary<string, object> methodArguments, ApiVersionInfo apiVersion) protected virtual async Task AddQueryStringParametersAsync(StringBuilder urlBuilder, IList<ParameterApiDescriptionModel> actionParameters, IReadOnlyDictionary<string, object> methodArguments, ApiVersionInfo apiVersion)
{ {
var queryStringParameters = actionParameters var queryStringParameters = actionParameters
.Where(p => p.BindingSourceId.IsIn(ParameterBindingSources.ModelBinding, ParameterBindingSources.Query)) .Where(p => p.BindingSourceId.IsIn(ParameterBindingSources.ModelBinding, ParameterBindingSources.Query))
@ -89,7 +113,29 @@ namespace Volo.Abp.Http.Client.ClientProxying
continue; continue;
} }
if (AddQueryStringParameter(urlBuilder, isFirstParam, queryStringParameter.Name, value)) if (HttpClientProxyingOptions.QueryStringConverts.ContainsKey(value.GetType()))
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var queryString = await (Task<string>)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; isFirstParam = false;
} }
@ -97,11 +143,16 @@ namespace Volo.Abp.Http.Client.ClientProxying
if (apiVersion.ShouldSendInQueryString()) 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<string> ObjectToQueryStringAsync<T>(IObjectToQueryString<T> converter, T value)
{
return await converter.ConvertAsync(value);
}
protected virtual async Task<bool> AddQueryStringParameterAsync(
StringBuilder urlBuilder, StringBuilder urlBuilder,
bool isFirstParam, bool isFirstParam,
string name, string name,
@ -116,7 +167,7 @@ namespace Volo.Abp.Http.Client.ClientProxying
{ {
urlBuilder.Append(isFirstParam ? "?" : "&"); 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) if (index > 0)
@ -130,18 +181,18 @@ namespace Volo.Abp.Http.Client.ClientProxying
} }
urlBuilder.Append(isFirstParam ? "?" : "&"); urlBuilder.Append(isFirstParam ? "?" : "&");
urlBuilder.Append(name + "=" + System.Net.WebUtility.UrlEncode(ConvertValueToString(value))); urlBuilder.Append(name + "=" + System.Net.WebUtility.UrlEncode(await ConvertValueToStringAsync(value)));
return true; return true;
} }
protected virtual string ConvertValueToString([CanBeNull] object value) protected virtual Task<string> ConvertValueToStringAsync([CanBeNull] object value)
{ {
if (value is DateTime dateTimeValue) 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());
} }
} }
} }

11
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<in TValue>
{
Task<List<KeyValuePair<string, HttpContent>>> ConvertAsync(TValue value);
}
}

9
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<in TValue>
{
Task<string> ConvertAsync(TValue value);
}
}

10
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;
using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.Http.Client; using Volo.Abp.Http.Client;
using Volo.Abp.Http.Client.ClientProxying;
using Volo.Abp.Http.DynamicProxying; using Volo.Abp.Http.DynamicProxying;
using Volo.Abp.Http.Localization; using Volo.Abp.Http.Localization;
using Volo.Abp.Localization; using Volo.Abp.Localization;
@ -52,6 +54,12 @@ namespace Volo.Abp.Http
options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(CreateFileInput)); options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(CreateFileInput));
options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(CreateMultipleFileInput)); options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(CreateMultipleFileInput));
}); });
Configure<AbpHttpClientProxyingOptions>(options =>
{
options.QueryStringConverts.Add(typeof(List<GetParamsNameValue>), typeof(TestObjectToQueryString));
options.FormDataConverts.Add(typeof(List<GetParamsNameValue>), typeof(TestObjectToFormData));
});
} }
} }
} }

54
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"); 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<GetParamsNameValue>()
{
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<GetParamsNameValue>()
{
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");
}
} }
} }

30
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<List<GetParamsNameValue>>, ITransientDependency
{
public Task<List<KeyValuePair<string, HttpContent>>> ConvertAsync(List<GetParamsNameValue> values)
{
if (values.IsNullOrEmpty())
{
return null;
}
var formDataContents = new List<KeyValuePair<string, HttpContent>>();
for (var i = 0; i < values.Count; i++)
{
formDataContents.Add(new KeyValuePair<string, HttpContent>($"NameValues[{i}].Name", new StringContent(values[i].Name, Encoding.UTF8)));
formDataContents.Add(new KeyValuePair<string, HttpContent>($"NameValues[{i}].Value", new StringContent(values[i].Value, Encoding.UTF8)));
}
return Task.FromResult(formDataContents);
}
}
}

30
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<List<GetParamsNameValue>>, ITransientDependency
{
public Task<string> ConvertAsync(List<GetParamsNameValue> 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());
}
}
}

2
framework/test/Volo.Abp.TestApp/Volo.Abp.TestApp.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="..\..\..\common.test.props" /> <Import Project="..\..\..\common.test.props" />

18
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<GetParamsNameValue> NameValues { get; set; }
public GetParamsNameValue NameValue { get; set; }
}
public class GetParamsNameValue
{
public string Name { get; set; }
public string Value { get; set; }
}
}

3
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs

@ -32,5 +32,8 @@ namespace Volo.Abp.TestApp.Application
Task<string> CreateMultipleFileAsync(CreateMultipleFileInput input); Task<string> CreateMultipleFileAsync(CreateMultipleFileInput input);
Task<string> GetParamsFromQueryAsync(GetParamsInput input);
Task<string> GetParamsFromFormAsync(GetParamsInput input);
} }
} }

17
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs

@ -5,6 +5,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.TestApp.Domain; using Volo.Abp.TestApp.Domain;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
@ -125,5 +126,21 @@ namespace Volo.Abp.TestApp.Application
return str; return str;
} }
public Task<string> 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<string> 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);
}
} }
} }

Loading…
Cancel
Save