using System; using System.Linq; using System.Reflection; using Castle.DynamicProxy; using JetBrains.Annotations; using Volo.Abp; using Volo.Abp.Castle.DynamicProxy; using Volo.Abp.Http.Client; using Volo.Abp.Http.Client.DynamicProxying; using Volo.Abp.Http.Client.Proxying; using Volo.Abp.Validation; namespace Microsoft.Extensions.DependencyInjection { public static class ServiceCollectionHttpClientProxyExtensions { private static readonly ProxyGenerator ProxyGeneratorInstance = new ProxyGenerator(); /// /// Registers Static HTTP Client Proxies for all public interfaces /// extend the interface in the /// given . /// /// Service collection /// The assembly containing the service interfaces /// /// The name of the remote service configuration to be used by the Static HTTP Client proxies. /// See . /// public static IServiceCollection AddStaticHttpClientProxies( [NotNull] this IServiceCollection services, [NotNull] Assembly assembly, [NotNull] string remoteServiceConfigurationName = RemoteServiceConfigurationDictionary.DefaultName) { Check.NotNull(services, nameof(assembly)); var serviceTypes = assembly.GetTypes().Where(IsSuitableForClientProxying).ToArray(); foreach (var serviceType in serviceTypes) { AddHttpClientFactory(services, remoteServiceConfigurationName); services.Configure(options => { options.HttpClientProxies[serviceType] = new HttpClientProxyConfig(serviceType, remoteServiceConfigurationName); }); } return services; } /// /// Registers HTTP Client Proxies for all public interfaces /// extend the interface in the /// given . /// /// Service collection /// The assembly containing the service interfaces /// /// The name of the remote service configuration to be used by the HTTP Client proxies. /// See . /// /// /// True, to register the HTTP client proxy as the default implementation for the services. /// public static IServiceCollection AddHttpClientProxies( [NotNull] this IServiceCollection services, [NotNull] Assembly assembly, [NotNull] string remoteServiceConfigurationName = RemoteServiceConfigurationDictionary.DefaultName, bool asDefaultServices = true) { Check.NotNull(services, nameof(assembly)); var serviceTypes = assembly.GetTypes().Where(IsSuitableForClientProxying).ToArray(); foreach (var serviceType in serviceTypes) { services.AddHttpClientProxy( serviceType, remoteServiceConfigurationName, asDefaultServices ); } return services; } /// /// Registers HTTP Client Proxy for given service type . /// /// Type of the service /// Service collection /// /// The name of the remote service configuration to be used by the HTTP Client proxy. /// See . /// /// /// True, to register the HTTP client proxy as the default implementation for the service . /// public static IServiceCollection AddHttpClientProxy( [NotNull] this IServiceCollection services, [NotNull] string remoteServiceConfigurationName = RemoteServiceConfigurationDictionary.DefaultName, bool asDefaultService = true) { return services.AddHttpClientProxy( typeof(T), remoteServiceConfigurationName, asDefaultService ); } /// /// Registers HTTP Client Proxy for given service . /// /// Service collection /// Type of the service /// /// The name of the remote service configuration to be used by the HTTP Client proxy. /// See . /// /// /// True, to register the HTTP client proxy as the default implementation for the service . /// public static IServiceCollection AddHttpClientProxy( [NotNull] this IServiceCollection services, [NotNull] Type type, [NotNull] string remoteServiceConfigurationName = RemoteServiceConfigurationDictionary.DefaultName, bool asDefaultService = true) { Check.NotNull(services, nameof(services)); Check.NotNull(type, nameof(type)); Check.NotNullOrWhiteSpace(remoteServiceConfigurationName, nameof(remoteServiceConfigurationName)); AddHttpClientFactory(services, remoteServiceConfigurationName); services.Configure(options => { options.HttpClientProxies[type] = new HttpClientProxyConfig(type, remoteServiceConfigurationName); }); var interceptorType = typeof(DynamicHttpProxyInterceptor<>).MakeGenericType(type); services.AddTransient(interceptorType); var interceptorAdapterType = typeof(AbpAsyncDeterminationInterceptor<>).MakeGenericType(interceptorType); var validationInterceptorAdapterType = typeof(AbpAsyncDeterminationInterceptor<>).MakeGenericType(typeof(ValidationInterceptor)); if (asDefaultService) { services.AddTransient( type, serviceProvider => ProxyGeneratorInstance .CreateInterfaceProxyWithoutTarget( type, (IInterceptor)serviceProvider.GetRequiredService(validationInterceptorAdapterType), (IInterceptor)serviceProvider.GetRequiredService(interceptorAdapterType) ) ); } services.AddTransient( typeof(IHttpClientProxy<>).MakeGenericType(type), serviceProvider => { var service = ProxyGeneratorInstance .CreateInterfaceProxyWithoutTarget( type, (IInterceptor)serviceProvider.GetRequiredService(validationInterceptorAdapterType), (IInterceptor)serviceProvider.GetRequiredService(interceptorAdapterType) ); return Activator.CreateInstance( typeof(HttpClientProxy<>).MakeGenericType(type), service ); }); return services; } private static IServiceCollection AddHttpClientFactory( [NotNull] this IServiceCollection services, [NotNull] string remoteServiceConfigurationName = RemoteServiceConfigurationDictionary.DefaultName) { var preOptions = services.ExecutePreConfiguredActions(); if (preOptions.ConfiguredProxyClients.Contains(remoteServiceConfigurationName)) { return services; } var clientBuilder = services.AddHttpClient(remoteServiceConfigurationName, (provider, client) => { foreach (var clientBuildAction in preOptions.ProxyClientActions) { clientBuildAction(remoteServiceConfigurationName, provider, client); } }); foreach (var clientBuildAction in preOptions.ProxyClientBuildActions) { clientBuildAction(remoteServiceConfigurationName, clientBuilder); } services.PreConfigure(options => { options.ConfiguredProxyClients.Add(remoteServiceConfigurationName); }); return services; } /// /// Checks wether the type is suitable to use with the proxying. /// Currently the type is checked statically against some fixed conditions. /// /// Type to check /// True, if the type is suitable for proxying. Otherwise false. private static bool IsSuitableForClientProxying(Type type) { //TODO: Add option to change type filter return type.IsInterface && type.IsPublic && !type.IsGenericType && typeof(IRemoteService).IsAssignableFrom(type); } } }