diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Controllers/ApiDefinitionController.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Controllers/AbpApiDefinitionController.cs similarity index 76% rename from src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Controllers/ApiDefinitionController.cs rename to src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Controllers/AbpApiDefinitionController.cs index f2bb05ba74..0a8c6fb8ef 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Controllers/ApiDefinitionController.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Controllers/AbpApiDefinitionController.cs @@ -4,11 +4,11 @@ using Volo.Abp.Http.Modeling; namespace Volo.Abp.AspNetCore.Mvc.Controllers { [Area("abp")] - public class ApiDefinitionController : AbpController + public class AbpApiDefinitionController : AbpController { private readonly IApiDescriptionModelProvider _modelProvider; - public ApiDefinitionController(IApiDescriptionModelProvider modelProvider) + public AbpApiDefinitionController(IApiDescriptionModelProvider modelProvider) { _modelProvider = modelProvider; } diff --git a/src/Volo.Abp.Http/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs b/src/Volo.Abp.Http/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs new file mode 100644 index 0000000000..91ac82c21a --- /dev/null +++ b/src/Volo.Abp.Http/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs @@ -0,0 +1,41 @@ +using System; +using Castle.DynamicProxy; +using Volo.Abp.Castle.DynamicProxy; +using Volo.Abp.Http; +using Volo.Abp.Http.DynamicProxying; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class ServiceCollectionDynamicHttpClientProxyExtensions + { + private static readonly ProxyGenerator ProxyGeneratorInstance = new ProxyGenerator(); + + //TODO: AddHttpClientProxies for adding all services from single assembly! + + public static IServiceCollection AddHttpClientProxy(this IServiceCollection services, string baseUrl) + { + return services.AddHttpClientProxy(typeof(T), baseUrl); + } + + public static IServiceCollection AddHttpClientProxy(this IServiceCollection services, Type type, string baseUrl) + { + services.Configure(options => + { + options.HttpClientProxies[type] = new DynamicHttpClientProxyConfig(type, baseUrl); + }); + + var interceptorType = typeof(DynamicHttpProxyInterceptor<>).MakeGenericType(type); + services.AddTransient(interceptorType); + + var interceptorAdapterType = typeof(CastleAbpInterceptorAdapter<>).MakeGenericType(interceptorType); + return services.AddTransient( + type, + serviceProvider => ProxyGeneratorInstance + .CreateInterfaceProxyWithoutTarget( + type, + (IInterceptor) serviceProvider.GetRequiredService(interceptorAdapterType) + ) + ); + } + } +} diff --git a/src/Volo.Abp.Http/Microsoft/Extensions/DependencyInjection/ServiceCollectionHttpProxyExtensions.cs b/src/Volo.Abp.Http/Microsoft/Extensions/DependencyInjection/ServiceCollectionHttpProxyExtensions.cs deleted file mode 100644 index 0470a4bffd..0000000000 --- a/src/Volo.Abp.Http/Microsoft/Extensions/DependencyInjection/ServiceCollectionHttpProxyExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using Castle.DynamicProxy; -using Volo.Abp.Castle.DynamicProxy; -using Volo.Abp.Http; -using Volo.Abp.Http.DynamicProxying; - -namespace Microsoft.Extensions.DependencyInjection -{ - public static class ServiceCollectionHttpProxyExtensions - { - private static readonly ProxyGenerator ProxyGeneratorInstance = new ProxyGenerator(); - - public static IServiceCollection AddHttpClientProxy(this IServiceCollection services, string baseUrl) - { - return services.AddHttpClientProxy(typeof(T), baseUrl); - } - - public static IServiceCollection AddHttpClientProxy(this IServiceCollection services, Type type, string baseUrl) - { - services.AddTransient(type, serviceProvider => - { - return ProxyGeneratorInstance - .CreateInterfaceProxyWithoutTarget( - type, - serviceProvider.GetRequiredService>() - ); - }); - - return services; - } - } -} diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/AbpHttpOptions.cs b/src/Volo.Abp.Http/Volo/Abp/Http/AbpHttpOptions.cs new file mode 100644 index 0000000000..692702c405 --- /dev/null +++ b/src/Volo.Abp.Http/Volo/Abp/Http/AbpHttpOptions.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Http.DynamicProxying; + +namespace Volo.Abp.Http +{ + public class AbpHttpOptions + { + public Dictionary HttpClientProxies { get; set; } + + public AbpHttpOptions() + { + HttpClientProxies = new Dictionary(); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/DynamicProxying/ApplicationApiDescriptionModelCache.cs b/src/Volo.Abp.Http/Volo/Abp/Http/DynamicProxying/ApplicationApiDescriptionModelCache.cs new file mode 100644 index 0000000000..d1f5b48985 --- /dev/null +++ b/src/Volo.Abp.Http/Volo/Abp/Http/DynamicProxying/ApplicationApiDescriptionModelCache.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Nito.AsyncEx; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Modeling; + +namespace Volo.Abp.Http.DynamicProxying +{ + public class ApplicationApiDescriptionModelCache : IApplicationApiDescriptionModelCache, ISingletonDependency + { + private readonly IDynamicProxyHttpClientFactory _httpClientFactory; + + private readonly Dictionary _cache; + private readonly AsyncLock _asyncLock; + + public ApplicationApiDescriptionModelCache(IDynamicProxyHttpClientFactory httpClientFactory) + { + _httpClientFactory = httpClientFactory; + + _cache = new Dictionary(); + _asyncLock = new AsyncLock(); + } + + public async Task GetAsync(string baseUrl, CancellationToken cancellationToken = default(CancellationToken)) + { + using (await _asyncLock.LockAsync(cancellationToken)) + { + var model = _cache.GetOrDefault(baseUrl); + if (model == null) + { + _cache[baseUrl] = model = await GetModelFromRemoteServer(baseUrl); + } + + return model; + } + } + + private async Task GetModelFromRemoteServer(string baseUrl) + { + using (var client = _httpClientFactory.Create()) + { + var response = await client.GetAsync(baseUrl + "api/abp/api-description"); + if (!response.IsSuccessStatusCode) + { + throw new AbpException("Remote service returns error!"); + } + + var content = await response.Content.ReadAsStringAsync(); + + var result = JsonConvert.DeserializeObject( + content, + typeof(ApplicationApiDescriptionModel), + new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }); + + return (ApplicationApiDescriptionModel)result; + } + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/DynamicProxying/DynamicHttpClientProxyConfig.cs b/src/Volo.Abp.Http/Volo/Abp/Http/DynamicProxying/DynamicHttpClientProxyConfig.cs new file mode 100644 index 0000000000..e818d46f0c --- /dev/null +++ b/src/Volo.Abp.Http/Volo/Abp/Http/DynamicProxying/DynamicHttpClientProxyConfig.cs @@ -0,0 +1,17 @@ +using System; + +namespace Volo.Abp.Http.DynamicProxying +{ + public class DynamicHttpClientProxyConfig + { + public Type Type { get; } + + public string BaseUrl { get; } + + public DynamicHttpClientProxyConfig(Type type, string baseUrl) + { + Type = type; + BaseUrl = baseUrl; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/DynamicProxying/DynamicHttpProxyInterceptor.cs b/src/Volo.Abp.Http/Volo/Abp/Http/DynamicProxying/DynamicHttpProxyInterceptor.cs index 933c58c9e7..e0fdd8dd09 100644 --- a/src/Volo.Abp.Http/Volo/Abp/Http/DynamicProxying/DynamicHttpProxyInterceptor.cs +++ b/src/Volo.Abp.Http/Volo/Abp/Http/DynamicProxying/DynamicHttpProxyInterceptor.cs @@ -1,36 +1,46 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Reflection; 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.Threading; namespace Volo.Abp.Http.DynamicProxying { - public class DynamicHttpProxyInterceptor : AbpInterceptor, ITransientDependency + public class DynamicHttpProxyInterceptor : AbpInterceptor, ITransientDependency { - private readonly IDynamicProxyHttpClientFactory _httpClientFactory; - private static readonly MethodInfo GenericInterceptAsyncMethod; + private readonly IDynamicProxyHttpClientFactory _httpClientFactory; + private readonly IApplicationApiDescriptionModelCache _discoverManager; + private readonly AbpHttpOptions _options; + static DynamicHttpProxyInterceptor() { - GenericInterceptAsyncMethod = typeof(DynamicHttpProxyInterceptor) + GenericInterceptAsyncMethod = typeof(DynamicHttpProxyInterceptor) .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) .First(m => m.Name == nameof(InterceptAsync) && m.IsGenericMethodDefinition); } - public DynamicHttpProxyInterceptor(IDynamicProxyHttpClientFactory httpClientFactory) + public DynamicHttpProxyInterceptor( + IDynamicProxyHttpClientFactory httpClientFactory, + IOptions options, + IApplicationApiDescriptionModelCache discoverManager) { _httpClientFactory = httpClientFactory; + _discoverManager = discoverManager; + _options = options.Value; } public override void Intercept(IAbpMethodInvocation invocation) { - throw new System.NotImplementedException(); + AsyncHelper.RunSync(() => InterceptAsync(invocation)); } public override Task InterceptAsync(IAbpMethodInvocation invocation) @@ -44,6 +54,14 @@ namespace Volo.Abp.Http.DynamicProxying private async Task InterceptAsync(IAbpMethodInvocation invocation) { + var config = _options.HttpClientProxies.GetOrDefault(typeof(TService)); + if (config == null) + { + throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(T).FullName}."); + } + + var apiDescriptionModel = await _discoverManager.GetAsync(config.BaseUrl); + using (var client = _httpClientFactory.Create()) { var response = await client.GetAsync("/api/app/people"); diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/DynamicProxying/IApplicationApiDescriptionModelCache.cs b/src/Volo.Abp.Http/Volo/Abp/Http/DynamicProxying/IApplicationApiDescriptionModelCache.cs new file mode 100644 index 0000000000..2d6ccc5d5b --- /dev/null +++ b/src/Volo.Abp.Http/Volo/Abp/Http/DynamicProxying/IApplicationApiDescriptionModelCache.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Http.Modeling; + +namespace Volo.Abp.Http.DynamicProxying +{ + public interface IApplicationApiDescriptionModelCache + { + Task GetAsync(string baseUrl, CancellationToken cancellationToken = default(CancellationToken)); + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs index b261c62416..d89a093988 100644 --- a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs +++ b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs @@ -43,7 +43,7 @@ namespace Volo.Abp.Http.Modeling public ControllerApiDescriptionModel CreateSubModel(string[] actions) { - var subModel = new ControllerApiDescriptionModel(Name); + var subModel = new ControllerApiDescriptionModel(Name, Type); foreach (var action in Actions.Values) { diff --git a/src/Volo.Abp/Volo.Abp.csproj b/src/Volo.Abp/Volo.Abp.csproj index d02cb6b1b3..be558fc887 100644 --- a/src/Volo.Abp/Volo.Abp.csproj +++ b/src/Volo.Abp/Volo.Abp.csproj @@ -16,6 +16,7 @@ +