From 994d43bbfd12d208e04ab8a413743b093fc2de85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 15 Sep 2017 20:31:48 +0300 Subject: [PATCH] Added ReflectionHelper.GetValueByPath and used in HynamicHttpProxyInterceptor. --- .../CastleAbpMethodInvocationAdapter.cs | 59 +++++++++++------ .../DynamicHttpProxyInterceptor.cs | 64 ++++++++++++------- .../Modeling/ParameterApiDescriptionModel.cs | 3 - src/Volo.Abp/Properties/AssemblyInfo.cs | 1 + .../Abp/DynamicProxy/IAbpMethodInvocation.cs | 3 + .../Volo/Abp/Reflection/ReflectionHelper.cs | 58 +++++++++++++++++ .../PersonAppServiceClientProxy_Tests.cs | 23 +++++++ .../TestApp/Application/IPeopleAppService.cs | 20 ++++++ .../TestApp/Application/PeopleAppService.cs | 5 ++ 9 files changed, 190 insertions(+), 46 deletions(-) diff --git a/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpMethodInvocationAdapter.cs b/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpMethodInvocationAdapter.cs index 6758f138b0..4e9d819851 100644 --- a/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpMethodInvocationAdapter.cs +++ b/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpMethodInvocationAdapter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; using Castle.DynamicProxy; @@ -11,6 +12,9 @@ namespace Volo.Abp.Castle.DynamicProxy { public object[] Arguments => Invocation.Arguments; + public IReadOnlyDictionary ArgumentsDictionary => _lazyArgumentsDictionary.Value; + private readonly Lazy> _lazyArgumentsDictionary; + public Type[] GenericArguments => Invocation.GenericArguments; public object TargetObject => Invocation.InvocationTarget; @@ -23,32 +27,47 @@ namespace Volo.Abp.Castle.DynamicProxy set => Invocation.ReturnValue = value; } - private object _actualReturnValue; + private object _actualReturnValue; protected IInvocation Invocation { get; } public CastleAbpMethodInvocationAdapter(IInvocation invocation) { Invocation = invocation; + + _lazyArgumentsDictionary = new Lazy>(GetArgumentsDictionary); } - public void Proceed() - { - Invocation.Proceed(); - - if (Invocation.Method.IsAsync()) - { - AsyncHelper.RunSync(() => (Task) Invocation.ReturnValue); - } - } - - public Task ProceedAsync() - { - Invocation.Proceed(); - _actualReturnValue = Invocation.ReturnValue; - return Invocation.Method.IsAsync() - ? (Task)_actualReturnValue - : Task.FromResult(_actualReturnValue); - } - } + public void Proceed() + { + Invocation.Proceed(); + + if (Invocation.Method.IsAsync()) + { + AsyncHelper.RunSync(() => (Task)Invocation.ReturnValue); + } + } + + public Task ProceedAsync() + { + Invocation.Proceed(); + _actualReturnValue = Invocation.ReturnValue; + return Invocation.Method.IsAsync() + ? (Task)_actualReturnValue + : Task.FromResult(_actualReturnValue); + } + + private IReadOnlyDictionary GetArgumentsDictionary() + { + var dict = new Dictionary(); + + var methodParameters = Method.GetParameters(); + for (int i = 0; i < methodParameters.Length; i++) + { + dict[methodParameters[i].Name] = Invocation.Arguments[i]; + } + + return dict; + } + } } \ No newline at end of file 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 c0bcfe6ecd..0de38c0eea 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 @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Options; using Newtonsoft.Json; @@ -9,6 +10,7 @@ using Newtonsoft.Json.Serialization; using Volo.Abp.DependencyInjection; using Volo.Abp.DynamicProxy; using Volo.Abp.Http.Modeling; +using Volo.Abp.Reflection; using Volo.Abp.Threading; namespace Volo.Abp.Http.Client.DynamicProxying @@ -91,7 +93,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying { //TODO: Can be optimized using StringBuilder? var url = ReplacePathVariables(action.Url, action.Parameters, invocation); - //url = AddQueryStringParameters(url, action.Parameters); + url = AddQueryStringParameters(url, action.Parameters, invocation); return url; } @@ -108,42 +110,58 @@ namespace Volo.Abp.Http.Client.DynamicProxying foreach (var pathParameter in pathParameters) { - url = url.Replace($"{{{pathParameter.Name}}}", FindParameterValue(invocation.Method, invocation.Arguments, pathParameter.Name)); + url = url.Replace($"{{{pathParameter.Name}}}", FindParameterValue(invocation, pathParameter)); } return url; } - //private static string AddQueryStringParameters(string url, IList actionParameters) - //{ - // var queryStringParameters = actionParameters - // .Where(p => p.BindingSourceId.IsIn("ModelBinding", "Query")) - // .ToArray(); + private static string AddQueryStringParameters(string url, IList actionParameters, IAbpMethodInvocation invocation) + { + var queryStringParameters = actionParameters + .Where(p => p.BindingSourceId.IsIn("ModelBinding", "Query")) + .ToArray(); - // if (!queryStringParameters.Any()) - // { - // return url; - // } + if (!queryStringParameters.Any()) + { + return url; + } - // var qsBuilderParams = queryStringParameters - // .Select(p => $"{{ name: '{p.Name.ToCamelCase()}', value: {ProxyScriptingJsFuncHelper.GetParamNameInJsFunc(p)} }}") - // .JoinAsString(", "); + var qsBuilder = new StringBuilder(); - // return url + $"' + abp.utils.buildQueryString([{qsBuilderParams}]) + '"; - //} + foreach (var queryStringParameter in queryStringParameters) + { + var value = FindParameterValue(invocation, queryStringParameter); + if (value == null) + { + continue; + } - private static string FindParameterValue(MethodInfo method, object[] arguments, string parameterName) + qsBuilder.Append(qsBuilder.Length == 0 ? "?" : "&"); + qsBuilder.Append(queryStringParameter.Name + "=" + value); //TODO: URL Encode! + } + + return url + qsBuilder; + } + + private static string FindParameterValue(IAbpMethodInvocation invocation, ParameterApiDescriptionModel parameter) { - var methodParameters = method.GetParameters(); - for (int i = 0; i < methodParameters.Length; i++) + //TODO: Handle null values + + if (parameter.Name == parameter.NameOnMethod) { - if (methodParameters[i].Name == parameterName) + return invocation.ArgumentsDictionary[parameter.Name]?.ToString(); + } + else + { + var obj = invocation.ArgumentsDictionary[parameter.NameOnMethod]; + if (obj == null) { - return arguments[i].ToString(); + return null; } - } - throw new AbpException("Could not find parameter in the invocation: " + parameterName); + return ReflectionHelper.GetValueByPath(obj, obj.GetType(), parameter.Name)?.ToString(); + } } } } \ No newline at end of file diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs index d3e9a5fbab..4158282967 100644 --- a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs +++ b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs @@ -9,8 +9,6 @@ namespace Volo.Abp.Http.Modeling public string Name { get; set; } - public Type Type { get; set; } - public string TypeAsString { get; set; } public bool IsOptional { get; set; } @@ -32,7 +30,6 @@ namespace Volo.Abp.Http.Modeling { Name = name, NameOnMethod = nameOnMethod, - Type = type, TypeAsString = type.FullName, IsOptional = isOptional, DefaultValue = defaultValue, diff --git a/src/Volo.Abp/Properties/AssemblyInfo.cs b/src/Volo.Abp/Properties/AssemblyInfo.cs index ceda416b42..7e48f188de 100644 --- a/src/Volo.Abp/Properties/AssemblyInfo.cs +++ b/src/Volo.Abp/Properties/AssemblyInfo.cs @@ -12,6 +12,7 @@ using System.Runtime.InteropServices; [assembly: InternalsVisibleTo("Volo.Abp.Tests")] [assembly: InternalsVisibleTo("Volo.Abp.AspNetCore.Mvc")] +[assembly: InternalsVisibleTo("Volo.Abp.Http.Client")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from diff --git a/src/Volo.Abp/Volo/Abp/DynamicProxy/IAbpMethodInvocation.cs b/src/Volo.Abp/Volo/Abp/DynamicProxy/IAbpMethodInvocation.cs index 92bd440363..37a36ac05f 100644 --- a/src/Volo.Abp/Volo/Abp/DynamicProxy/IAbpMethodInvocation.cs +++ b/src/Volo.Abp/Volo/Abp/DynamicProxy/IAbpMethodInvocation.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; @@ -8,6 +9,8 @@ namespace Volo.Abp.DynamicProxy { object[] Arguments { get; } + IReadOnlyDictionary ArgumentsDictionary { get; } + Type[] GenericArguments { get; } object TargetObject { get; } diff --git a/src/Volo.Abp/Volo/Abp/Reflection/ReflectionHelper.cs b/src/Volo.Abp/Volo/Abp/Reflection/ReflectionHelper.cs index ce0eeae715..c4b3f0e450 100644 --- a/src/Volo.Abp/Volo/Abp/Reflection/ReflectionHelper.cs +++ b/src/Volo.Abp/Volo/Abp/Reflection/ReflectionHelper.cs @@ -91,5 +91,63 @@ namespace Volo.Abp.Reflection return defaultValue; } + + /// + /// Gets value of a property by it's full path from given object + /// + internal static object GetValueByPath(object obj, Type objectType, string propertyPath) + { + var value = obj; + var currentType = objectType; + var objectPath = currentType.FullName; + var absolutePropertyPath = propertyPath; + if (absolutePropertyPath.StartsWith(objectPath)) + { + absolutePropertyPath = absolutePropertyPath.Replace(objectPath + ".", ""); + } + + foreach (var propertyName in absolutePropertyPath.Split('.')) + { + var property = currentType.GetProperty(propertyName); + value = property.GetValue(value, null); + currentType = property.PropertyType; + } + + return value; + } + + /// + /// Sets value of a property by it's full path on given object + /// + internal static void SetValueByPath(object obj, Type objectType, string propertyPath, object value) + { + var currentType = objectType; + PropertyInfo property; + var objectPath = currentType.FullName; + var absolutePropertyPath = propertyPath; + if (absolutePropertyPath.StartsWith(objectPath)) + { + absolutePropertyPath = absolutePropertyPath.Replace(objectPath + ".", ""); + } + + var properties = absolutePropertyPath.Split('.'); + + if (properties.Length == 1) + { + property = objectType.GetProperty(properties.First()); + property.SetValue(obj, value); + return; + } + + for (int i = 0; i < properties.Length - 1; i++) + { + property = currentType.GetProperty(properties[i]); + obj = property.GetValue(obj, null); + currentType = property.PropertyType; + } + + property = currentType.GetProperty(properties.Last()); + property.SetValue(obj, value); + } } } 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 9faca4d701..ee269a6000 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 @@ -39,5 +39,28 @@ namespace Volo.Abp.Http.DynamicProxying person.Id.ShouldBe(firstPerson.Id); person.Name.ShouldBe(firstPerson.Name); } + + [Fact] + public async Task GetWithComplexType() + { + var result = await _peopleAppService.GetWithComplexType( + new GetWithComplexTypeInput + { + Value1 = "value one", + Inner1 = new GetWithComplexTypeInner + { + Value2 = "value two", + Inner2 = new GetWithComplexTypeInnerInner + { + Value3 = "value three" + } + } + } + ); + + result.Value1.ShouldBe("value one"); + result.Inner1.Value2.ShouldBe("value two"); + result.Inner1.Inner2.Value3.ShouldBe("value three"); + } } } diff --git a/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs b/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs index cd1a3932a6..58089ca49e 100644 --- a/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs +++ b/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs @@ -12,5 +12,25 @@ namespace Volo.Abp.TestApp.Application Task AddPhone(Guid id, PhoneDto phoneDto); Task RemovePhone(Guid id, long phoneId); + + Task GetWithComplexType(GetWithComplexTypeInput input); + } + + public class GetWithComplexTypeInput + { + public string Value1 { get; set; } + + public GetWithComplexTypeInner Inner1 { get; set; } + } + + public class GetWithComplexTypeInner + { + public string Value2 { get; set; } + public GetWithComplexTypeInnerInner Inner2 { get; set; } + } + + public class GetWithComplexTypeInnerInner + { + public string Value3 { get; set; } } } \ No newline at end of file diff --git a/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs b/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs index 2b41185905..61f49deb73 100644 --- a/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs +++ b/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs @@ -43,5 +43,10 @@ namespace Volo.Abp.TestApp.Application var person = await GetEntityByIdAsync(id); person.Phones.RemoveAll(p => p.Id == phoneId); } + + public Task GetWithComplexType(GetWithComplexTypeInput input) + { + return Task.FromResult(input); + } } }