diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs index 01f90d474c..e5063765d6 100644 --- a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs +++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs @@ -1,13 +1,16 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Net.Mime; using System.Reflection; +using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Volo.Abp.DependencyInjection; using Volo.Abp.DynamicProxy; +using Volo.Abp.Json; using Volo.Abp.Threading; namespace Volo.Abp.Http.Client.DynamicProxying @@ -21,6 +24,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying private readonly IDynamicProxyHttpClientFactory _httpClientFactory; private readonly IApiDescriptionFinder _apiDescriptionFinder; private readonly AbpHttpClientOptions _options; + private readonly IJsonSerializer _jsonSerializer; static DynamicHttpProxyInterceptor() { @@ -32,10 +36,12 @@ namespace Volo.Abp.Http.Client.DynamicProxying public DynamicHttpProxyInterceptor( IDynamicProxyHttpClientFactory httpClientFactory, IOptions options, - IApiDescriptionFinder apiDescriptionFinder) + IApiDescriptionFinder apiDescriptionFinder, + IJsonSerializer jsonSerializer) { _httpClientFactory = httpClientFactory; _apiDescriptionFinder = apiDescriptionFinder; + _jsonSerializer = jsonSerializer; _options = options.Value; } @@ -81,10 +87,16 @@ namespace Volo.Abp.Http.Client.DynamicProxying { var proxyConfig = GetProxyConfig(); var actionApiDescription = await _apiDescriptionFinder.FindActionAsync(proxyConfig, invocation.Method); - var url = proxyConfig.BaseUrl + UrlBuilder.GenerateUrlWithParameters(actionApiDescription, invocation.ArgumentsDictionary); + var requestMessage = new HttpRequestMessage(actionApiDescription.GetHttpMethod(), url); + var body = RequestPayloadBuilder.GenerateBody(actionApiDescription, invocation.ArgumentsDictionary, _jsonSerializer); + if (body != null) + { + requestMessage.Content = new StringContent(body, Encoding.UTF8, "application/json"); //TODO: application/json to a constant + } + var response = await client.SendAsync(requestMessage); if (!response.IsSuccessStatusCode) diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/HttpActionParameterHelper.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/HttpActionParameterHelper.cs new file mode 100644 index 0000000000..f5910f63d9 --- /dev/null +++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/HttpActionParameterHelper.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Volo.Abp.Http.Modeling; +using Volo.Abp.Reflection; + +namespace Volo.Abp.Http.Client.DynamicProxying +{ + internal static class HttpActionParameterHelper + { + public static object FindParameterValue(IReadOnlyDictionary methodArguments, ParameterApiDescriptionModel apiParameter) + { + var value = methodArguments[apiParameter.NameOnMethod]; + if (value == null) + { + return null; + } + + if (apiParameter.Name == apiParameter.NameOnMethod) + { + return value; + } + + return ReflectionHelper.GetValueByPath(value, value.GetType(), apiParameter.Name); + } + } +} diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs new file mode 100644 index 0000000000..fd0a4d123c --- /dev/null +++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using Volo.Abp.Http.Modeling; +using Volo.Abp.Json; + +namespace Volo.Abp.Http.Client.DynamicProxying +{ + public static class RequestPayloadBuilder + { + public static string GenerateBody(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, IJsonSerializer jsonSerializer) + { + var parameters = action + .Parameters + .Where(p => p.BindingSourceId == "Body") + .ToArray(); + + if (parameters.Length <= 0) + { + return null; + } + + if (parameters.Length > 1) + { + throw new AbpException( + $"Only one complex type allowed as argument to a controller action that's binding source is 'Body'. But action on URL: {action.Url} contains more than one!" + ); + } + + var value = HttpActionParameterHelper.FindParameterValue(methodArguments, parameters[0]); + if (value == null) + { + return null; + } + + return jsonSerializer.Serialize(value); + } + } +} diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs index c7bb265580..1210e9a813 100644 --- a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs +++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; using Volo.Abp.Http.Modeling; -using Volo.Abp.Reflection; namespace Volo.Abp.Http.Client.DynamicProxying { @@ -32,7 +31,26 @@ namespace Volo.Abp.Http.Client.DynamicProxying foreach (var pathParameter in pathParameters) { - urlBuilder = urlBuilder.Replace($"{{{pathParameter.Name}}}", FindParameterValue(methodArguments, pathParameter)); + var value = HttpActionParameterHelper.FindParameterValue(methodArguments, pathParameter); + if (value == null) + { + if (pathParameter.IsOptional) + { + urlBuilder = urlBuilder.Replace($"{{{pathParameter.Name}}}", ""); + } + else if (pathParameter.DefaultValue != null) + { + urlBuilder = urlBuilder.Replace($"{{{pathParameter.Name}}}", pathParameter.DefaultValue.ToString()); + } + else + { + throw new AbpException($"Missing path parameter value for {pathParameter.Name} ({pathParameter.NameOnMethod})"); + } + } + else + { + urlBuilder = urlBuilder.Replace($"{{{pathParameter.Name}}}", value.ToString()); + } } } @@ -50,33 +68,17 @@ namespace Volo.Abp.Http.Client.DynamicProxying var isFirstParam = true; foreach (var queryStringParameter in queryStringParameters) { - var value = FindParameterValue(methodArguments, queryStringParameter); + var value = HttpActionParameterHelper.FindParameterValue(methodArguments, queryStringParameter); if (value == null) { continue; } urlBuilder.Append(isFirstParam ? "?" : "&"); - urlBuilder.Append(queryStringParameter.Name + "=" + System.Net.WebUtility.UrlEncode(value)); + urlBuilder.Append(queryStringParameter.Name + "=" + System.Net.WebUtility.UrlEncode(value.ToString())); isFirstParam = false; } } - - private static string FindParameterValue(IReadOnlyDictionary methodArguments, ParameterApiDescriptionModel apiParameter) - { - var value = methodArguments[apiParameter.NameOnMethod]; - if (value == null) - { - return null; - } - - if (apiParameter.Name == apiParameter.NameOnMethod) - { - return value.ToString(); - } - - return ReflectionHelper.GetValueByPath(value, value.GetType(), apiParameter.Name)?.ToString(); - } } } diff --git a/src/Volo.Abp/Volo/Abp/Json/IJsonSerializer.cs b/src/Volo.Abp/Volo/Abp/Json/IJsonSerializer.cs index 38cfd6d0be..481e191348 100644 --- a/src/Volo.Abp/Volo/Abp/Json/IJsonSerializer.cs +++ b/src/Volo.Abp/Volo/Abp/Json/IJsonSerializer.cs @@ -2,7 +2,7 @@ { public interface IJsonSerializer { - string Serialize(object obj, bool camelCase = false, bool indented = false); + string Serialize(object obj, bool camelCase = true, bool indented = false); T Deserialize(string jsonString); } diff --git a/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/App/AppModule.cs b/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/App/AppModule.cs index 85b1c31efe..06fa19658c 100644 --- a/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/App/AppModule.cs +++ b/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/App/AppModule.cs @@ -39,7 +39,7 @@ namespace Volo.Abp.AspNetCore.App ["TenantId"] = manager.CurrentTenant == null ? "" : manager.CurrentTenant.Id.ToString() }; - var result = jsonSerializer.Serialize(dictionary); + var result = jsonSerializer.Serialize(dictionary, camelCase: false); await ctx.Response.WriteAsync(result); }); } diff --git a/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs b/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs index c025496870..f4866beb3c 100644 --- a/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs +++ b/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Shouldly; @@ -52,6 +53,27 @@ namespace Volo.Abp.Http.DynamicProxying firstPerson.ShouldBeNull(); } + [Fact] + public async Task Create() + { + var uniquePersonName = Guid.NewGuid().ToString(); + + var person = await _peopleAppService.Create(new PersonDto + { + Name = uniquePersonName, + Age = 42 + } + ); + + person.ShouldNotBeNull(); + person.Id.ShouldNotBe(Guid.Empty); + person.Name.ShouldBe(uniquePersonName); + + var personInDb = _personRepository.GetList().FirstOrDefault(p => p.Name == uniquePersonName); + personInDb.ShouldNotBeNull(); + personInDb.Id.ShouldBe(person.Id); + } + [Fact] public async Task GetWithComplexType() {