diff --git a/aspnet-core/LINGYUN.MicroService.Common.sln b/aspnet-core/LINGYUN.MicroService.Common.sln index e0823510b..e940daeb2 100644 --- a/aspnet-core/LINGYUN.MicroService.Common.sln +++ b/aspnet-core/LINGYUN.MicroService.Common.sln @@ -140,6 +140,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Dapr.Actors.Ide EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Dapr.Actors.AspNetCore", "modules\dapr\LINGYUN.Abp.Dapr.Actors.AspNetCore\LINGYUN.Abp.Dapr.Actors.AspNetCore.csproj", "{E74FF671-6E5E-430C-9211-ED910634DDBE}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Dapr.Client", "modules\dapr\LINGYUN.Abp.Dapr.Client\LINGYUN.Abp.Dapr.Client.csproj", "{879791A3-BD69-42E4-A3BC-9878EFAADDD1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -362,6 +364,10 @@ Global {E74FF671-6E5E-430C-9211-ED910634DDBE}.Debug|Any CPU.Build.0 = Debug|Any CPU {E74FF671-6E5E-430C-9211-ED910634DDBE}.Release|Any CPU.ActiveCfg = Release|Any CPU {E74FF671-6E5E-430C-9211-ED910634DDBE}.Release|Any CPU.Build.0 = Release|Any CPU + {879791A3-BD69-42E4-A3BC-9878EFAADDD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {879791A3-BD69-42E4-A3BC-9878EFAADDD1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {879791A3-BD69-42E4-A3BC-9878EFAADDD1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {879791A3-BD69-42E4-A3BC-9878EFAADDD1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -432,6 +438,7 @@ Global {A5DC8C25-6504-4C35-A657-7A1BF051570F} = {7FDFB22F-1BFF-4E05-9427-78B7A8461D50} {E263A9ED-D5DB-4495-A0C7-6268ED92EB92} = {7FDFB22F-1BFF-4E05-9427-78B7A8461D50} {E74FF671-6E5E-430C-9211-ED910634DDBE} = {7FDFB22F-1BFF-4E05-9427-78B7A8461D50} + {879791A3-BD69-42E4-A3BC-9878EFAADDD1} = {7FDFB22F-1BFF-4E05-9427-78B7A8461D50} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {06C707C6-02C0-411A-AD3B-2D0E13787CB8} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Actors/LINGYUN/Abp/Dapr/Actors/DynamicProxying/DynamicDaprActorProxyInterceptor.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Actors/LINGYUN/Abp/Dapr/Actors/DynamicProxying/DynamicDaprActorProxyInterceptor.cs index 20024bc41..28116dfa5 100644 --- a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Actors/LINGYUN/Abp/Dapr/Actors/DynamicProxying/DynamicDaprActorProxyInterceptor.cs +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Actors/LINGYUN/Abp/Dapr/Actors/DynamicProxying/DynamicDaprActorProxyInterceptor.cs @@ -46,6 +46,19 @@ namespace LINGYUN.Abp.Dapr.Actors.DynamicProxying public override async Task InterceptAsync(IAbpMethodInvocation invocation) { + var isAsyncMethod = invocation.Method.IsAsync(); + if (!isAsyncMethod) + { + // see: https://docs.dapr.io/developing-applications/sdks/dotnet/dotnet-actors/dotnet-actors-howto/ + // Dapr Actor文档: Actor方法的返回类型必须为Task或Task + throw new AbpException("The return type of Actor method must be Task or Task"); + } + if (invocation.Arguments.Length > 1) + { + // see: https://docs.dapr.io/developing-applications/sdks/dotnet/dotnet-actors/dotnet-actors-howto/ + // Dapr Actor文档: Actor方法最多可以有一个参数 + throw new AbpException("Actor method can have one argument at a maximum"); + } await MakeRequestAsync(invocation); } @@ -61,7 +74,8 @@ namespace LINGYUN.Abp.Dapr.Actors.DynamicProxying }; // 自定义请求处理器 - // 可添加请求头 + // 添加请求头用于传递状态 + // TODO: Actor一次只能处理一个请求,使用状态管理来传递状态的可行性? var httpClientHandler = new DaprHttpClientHandler(); // 身份认证处理 @@ -71,7 +85,7 @@ namespace LINGYUN.Abp.Dapr.Actors.DynamicProxying AddHeaders(httpClientHandler); - // 构建代理服务 + // 代理工厂 var proxyFactory = new ActorProxyFactory(actorProxyOptions, (HttpMessageHandler)httpClientHandler); await MakeRequestAsync(invocation, proxyFactory, remoteServiceConfig); @@ -86,34 +100,29 @@ namespace LINGYUN.Abp.Dapr.Actors.DynamicProxying var actorId = new ActorId(configuration.ActorId); var invokeType = typeof(TService); + // 约定的 RemoteServiceAttribute 为Actor名称 var remoteServiceAttr = invokeType.GetTypeInfo().GetCustomAttribute(); var actorType = remoteServiceAttr != null ? remoteServiceAttr.Name : invokeType.Name; - var isAsyncMethod = invocation.Method.IsAsync(); + try { + // 创建强类型代理 var actorProxy = proxyFactory.CreateActorProxy(actorId, actorType); - if (isAsyncMethod) - { - // 调用异步Actor - var task = (Task)invocation.Method.Invoke(actorProxy, invocation.Arguments); - await task; - - if (!invocation.Method.ReturnType.GenericTypeArguments.IsNullOrEmpty()) - { - // 处理返回值 - invocation.ReturnValue = typeof(Task<>) - .MakeGenericType(invocation.Method.ReturnType.GenericTypeArguments[0]) - .GetProperty(nameof(Task.Result), BindingFlags.Public | BindingFlags.Instance) - .GetValue(task); - } - } - else + // 远程调用 + var task = (Task)invocation.Method.Invoke(actorProxy, invocation.Arguments); + await task; + + // 存在返回值 + if (!invocation.Method.ReturnType.GenericTypeArguments.IsNullOrEmpty()) { - // 调用同步Actor - invocation.ReturnValue = invocation.Method.Invoke(actorProxy, invocation.Arguments); + // 处理返回值 + invocation.ReturnValue = typeof(Task<>) + .MakeGenericType(invocation.Method.ReturnType.GenericTypeArguments[0]) + .GetProperty(nameof(Task.Result), BindingFlags.Public | BindingFlags.Instance) + .GetValue(task); } } catch (ActorMethodInvocationException amie) // 其他异常忽略交给框架处理 diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN.Abp.Dapr.Client.csproj b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN.Abp.Dapr.Client.csproj new file mode 100644 index 000000000..0f3814e78 --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN.Abp.Dapr.Client.csproj @@ -0,0 +1,13 @@ + + + + net5.0 + + + + + + + + + diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/AbpDaprClientModule.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/AbpDaprClientModule.cs new file mode 100644 index 000000000..9fb43a63d --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/AbpDaprClientModule.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Http.Client; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.Dapr.Client +{ + [DependsOn( + typeof(AbpHttpClientModule))] + public class AbpDaprClientModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + Configure(configuration); + Configure(configuration.GetSection("Dapr:Client")); + + // DaprClient应该配置为单例的实现 + context.Services.AddDaprClient(); + } + } +} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/AbpDaprClientOptions.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/AbpDaprClientOptions.cs new file mode 100644 index 000000000..e61cdb01e --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/AbpDaprClientOptions.cs @@ -0,0 +1,14 @@ +using Grpc.Net.Client; + +namespace LINGYUN.Abp.Dapr.Client +{ + public class AbpDaprClientOptions + { + public string GrpcEndpoint { get; set; } + public string HttpEndpoint { get; set; } + public GrpcChannelOptions GrpcChannelOptions { get; set; } + public AbpDaprClientOptions() + { + } + } +} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/AbpDaprRemoteServiceOptions.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/AbpDaprRemoteServiceOptions.cs new file mode 100644 index 000000000..d4ce4594c --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/AbpDaprRemoteServiceOptions.cs @@ -0,0 +1,12 @@ +namespace LINGYUN.Abp.Dapr.Client +{ + public class AbpDaprRemoteServiceOptions + { + public DaprRemoteServiceConfigurationDictionary RemoteServices { get; set; } + + public AbpDaprRemoteServiceOptions() + { + RemoteServices = new DaprRemoteServiceConfigurationDictionary(); + } + } +} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/Authentication/IRemoteServiceDaprClientAuthenticator.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/Authentication/IRemoteServiceDaprClientAuthenticator.cs new file mode 100644 index 000000000..822544406 --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/Authentication/IRemoteServiceDaprClientAuthenticator.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Dapr.Client.Authentication +{ + public interface IRemoteServiceDaprClientAuthenticator + { + Task AuthenticateAsync(RemoteServiceDaprClientAuthenticateContext context); + } +} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/Authentication/NullRemoteServiceDaprClientAuthenticator.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/Authentication/NullRemoteServiceDaprClientAuthenticator.cs new file mode 100644 index 000000000..6bdad8d7c --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/Authentication/NullRemoteServiceDaprClientAuthenticator.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.Dapr.Client.Authentication +{ + [Dependency(TryRegister = true)] + public class NullRemoteServiceDaprClientAuthenticator : IRemoteServiceDaprClientAuthenticator, ISingletonDependency + { + public Task AuthenticateAsync(RemoteServiceDaprClientAuthenticateContext context) + { + return Task.CompletedTask; + } + } +} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/Authentication/RemoteServiceDaprClientAuthenticateContext.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/Authentication/RemoteServiceDaprClientAuthenticateContext.cs new file mode 100644 index 000000000..a4225fb07 --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/Authentication/RemoteServiceDaprClientAuthenticateContext.cs @@ -0,0 +1,21 @@ +using System.Net.Http; + +namespace LINGYUN.Abp.Dapr.Client.Authentication +{ + public class RemoteServiceDaprClientAuthenticateContext + { + public HttpRequestMessage Request { get; } + public DaprRemoteServiceConfiguration RemoteService { get; } + + public string RemoteServiceName { get; } + public RemoteServiceDaprClientAuthenticateContext( + HttpRequestMessage request, + DaprRemoteServiceConfiguration remoteService, + string remoteServiceName) + { + Request = request; + RemoteService = remoteService; + RemoteServiceName = remoteServiceName; + } + } +} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DaprClientFactory.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DaprClientFactory.cs new file mode 100644 index 000000000..9e7c29b39 --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DaprClientFactory.cs @@ -0,0 +1,45 @@ +using Dapr.Client; +using Microsoft.Extensions.Options; +using System; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Json.SystemTextJson; + +namespace LINGYUN.Abp.Dapr.Client +{ + public class DaprClientFactory : IDaprClientFactory, ISingletonDependency + { + protected AbpDaprClientOptions DaprClientOptions { get; } + protected AbpSystemTextJsonSerializerOptions JsonSerializerOptions { get; } + + private readonly Lazy _daprClientLazy; + + public DaprClientFactory( + IOptions daprClientOptions, + IOptions jsonSerializarOptions) + { + DaprClientOptions = daprClientOptions.Value; + JsonSerializerOptions = jsonSerializarOptions.Value; + + _daprClientLazy = new Lazy(() => CreateDaprClient()); + } + + public DaprClient Create() => _daprClientLazy.Value; + + protected virtual DaprClient CreateDaprClient() + { + var builder = new DaprClientBuilder() + .UseHttpEndpoint(DaprClientOptions.HttpEndpoint) + .UseJsonSerializationOptions(JsonSerializerOptions.JsonSerializerOptions); + + if (!DaprClientOptions.GrpcEndpoint.IsNullOrWhiteSpace() && + DaprClientOptions.GrpcChannelOptions != null) + { + builder + .UseGrpcEndpoint(DaprClientOptions.GrpcEndpoint) + .UseGrpcChannelOptions(DaprClientOptions.GrpcChannelOptions); + } + + return builder.Build(); + } + } +} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DaprRemoteServiceConfiguration.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DaprRemoteServiceConfiguration.cs new file mode 100644 index 000000000..62658c311 --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DaprRemoteServiceConfiguration.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.Dapr.Client +{ + public class DaprRemoteServiceConfiguration : Dictionary + { + /// + /// Base AppId. + /// + public string AppId + { + get => this.GetOrDefault(nameof(AppId)); + set => this[nameof(AppId)] = value; + } + + /// + /// Version. + /// + public string Version + { + get => this.GetOrDefault(nameof(Version)); + set => this[nameof(Version)] = value; + } + + public DaprRemoteServiceConfiguration() + { + } + + public DaprRemoteServiceConfiguration( + string appId, + string version) + { + this[nameof(AppId)] = appId; + this[nameof(Version)] = version; + } + } +} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DaprRemoteServiceConfigurationDictionary.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DaprRemoteServiceConfigurationDictionary.cs new file mode 100644 index 000000000..0bb9612a2 --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DaprRemoteServiceConfigurationDictionary.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Volo.Abp; + +namespace LINGYUN.Abp.Dapr.Client +{ + public class DaprRemoteServiceConfigurationDictionary : Dictionary + { + public const string DefaultName = "Default"; + + public DaprRemoteServiceConfiguration Default + { + get => this.GetOrDefault(DefaultName); + set => this[DefaultName] = value; + } + + public DaprRemoteServiceConfiguration GetConfigurationOrDefault(string name) + { + return this.GetOrDefault(name) + ?? Default + ?? throw new AbpException($"Dapr remote service '{name}' was not found and there is no default configuration."); + } + } +} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/AbpDaprClientProxyOptions.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/AbpDaprClientProxyOptions.cs new file mode 100644 index 000000000..6466c55b7 --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/AbpDaprClientProxyOptions.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace LINGYUN.Abp.Dapr.Client.DynamicProxying +{ + public class AbpDaprClientProxyOptions + { + public Dictionary DaprClientProxies { get; set; } + + public AbpDaprClientProxyOptions() + { + DaprClientProxies = new Dictionary(); + } + } +} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DaprApiDescriptionFinder.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DaprApiDescriptionFinder.cs new file mode 100644 index 000000000..d58be33dd --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DaprApiDescriptionFinder.cs @@ -0,0 +1,160 @@ +using Dapr.Client; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Text.Json; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.DynamicProxying; +using Volo.Abp.Http.Modeling; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Threading; +using Volo.Abp.Tracing; + +namespace LINGYUN.Abp.Dapr.Client.DynamicProxying +{ + public class DaprApiDescriptionFinder : IDaprApiDescriptionFinder, ITransientDependency + { + public ICancellationTokenProvider CancellationTokenProvider { get; set; } + protected IApiDescriptionCache Cache { get; } + protected AbpCorrelationIdOptions AbpCorrelationIdOptions { get; } + protected ICorrelationIdProvider CorrelationIdProvider { get; } + protected ICurrentTenant CurrentTenant { get; } + + protected DaprClient DaprClient { get; } + public DaprApiDescriptionFinder( + DaprClient daprClient, + IApiDescriptionCache cache, + IOptions abpCorrelationIdOptions, + ICorrelationIdProvider correlationIdProvider, + ICurrentTenant currentTenant) + { + DaprClient = daprClient; + + Cache = cache; + AbpCorrelationIdOptions = abpCorrelationIdOptions.Value; + CorrelationIdProvider = correlationIdProvider; + CurrentTenant = currentTenant; + CancellationTokenProvider = NullCancellationTokenProvider.Instance; + } + + public virtual async Task FindActionAsync(string appId, Type serviceType, MethodInfo method) + { + var apiDescription = await GetApiDescriptionAsync(appId); + + //TODO: Cache finding? + + var methodParameters = method.GetParameters().ToArray(); + + foreach (var module in apiDescription.Modules.Values) + { + foreach (var controller in module.Controllers.Values) + { + if (!controller.Implements(serviceType)) + { + continue; + } + + foreach (var action in controller.Actions.Values) + { + if (action.Name == method.Name && action.ParametersOnMethod.Count == methodParameters.Length) + { + var found = true; + + for (int i = 0; i < methodParameters.Length; i++) + { + if (!TypeMatches(action.ParametersOnMethod[i], methodParameters[i])) + { + found = false; + break; + } + } + + if (found) + { + return action; + } + } + } + } + } + + throw new AbpException($"Could not found remote action for method: {method} on the appId: {appId}"); + } + + public virtual async Task GetApiDescriptionAsync(string appId) + { + return await Cache.GetAsync(appId, () => GetApiDescriptionFromServerAsync(appId)); + } + + protected virtual async Task GetApiDescriptionFromServerAsync(string appId) + { + var requestMessage = DaprClient.CreateInvokeMethodRequest(HttpMethod.Get, appId, "api/abp/api-definition"); + + AddHeaders(requestMessage); + + var response = await DaprClient.InvokeMethodWithResponseAsync( + requestMessage, + CancellationTokenProvider.Token); + + if (!response.IsSuccessStatusCode) + { + throw new AbpException("Remote service returns error! StatusCode = " + response.StatusCode); + } + + var content = await response.Content.ReadAsStringAsync(); + + var result = JsonSerializer.Deserialize(content, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + + return (ApplicationApiDescriptionModel)result; + } + + protected virtual void AddHeaders(HttpRequestMessage requestMessage) + { + //CorrelationId + requestMessage.Headers.Add(AbpCorrelationIdOptions.HttpHeaderName, CorrelationIdProvider.Get()); + + //TenantId + if (CurrentTenant.Id.HasValue) + { + //TODO: Use AbpAspNetCoreMultiTenancyOptions to get the key + requestMessage.Headers.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString()); + } + + //Culture + //TODO: Is that the way we want? Couldn't send the culture (not ui culture) + var currentCulture = CultureInfo.CurrentUICulture.Name ?? CultureInfo.CurrentCulture.Name; + if (!currentCulture.IsNullOrEmpty()) + { + requestMessage.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(currentCulture)); + } + + //X-Requested-With + requestMessage.Headers.Add("X-Requested-With", "XMLHttpRequest"); + } + + protected virtual bool TypeMatches(MethodParameterApiDescriptionModel actionParameter, ParameterInfo methodParameter) + { + return NormalizeTypeName(actionParameter.TypeAsString) == + NormalizeTypeName(methodParameter.ParameterType.GetFullNameWithAssemblyName()); + } + + protected virtual string NormalizeTypeName(string typeName) + { + const string placeholder = "%COREFX%"; + const string netCoreLib = "System.Private.CoreLib"; + const string netFxLib = "mscorlib"; + + return typeName.Replace(netCoreLib, placeholder).Replace(netFxLib, placeholder); + } + } +} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DynamicDaprClientProxyConfig.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DynamicDaprClientProxyConfig.cs new file mode 100644 index 000000000..f90cae728 --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DynamicDaprClientProxyConfig.cs @@ -0,0 +1,17 @@ +using System; + +namespace LINGYUN.Abp.Dapr.Client.DynamicProxying +{ + public class DynamicDaprClientProxyConfig + { + public Type Type { get; } + + public string RemoteServiceName { get; } + + public DynamicDaprClientProxyConfig(Type type, string remoteServiceName) + { + Type = type; + RemoteServiceName = remoteServiceName; + } + } +} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DynamicDaprClientProxyInterceptor.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DynamicDaprClientProxyInterceptor.cs new file mode 100644 index 000000000..9671108a0 --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DynamicDaprClientProxyInterceptor.cs @@ -0,0 +1,299 @@ +using Dapr.Client; +using LINGYUN.Abp.Dapr.Client.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Content; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; +using Volo.Abp.Http; +using Volo.Abp.Http.Client; +using Volo.Abp.Http.Client.DynamicProxying; +using Volo.Abp.Http.Modeling; +using Volo.Abp.Http.ProxyScripting.Generators; +using Volo.Abp.Json; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Threading; +using Volo.Abp.Tracing; + +namespace LINGYUN.Abp.Dapr.Client.DynamicProxying +{ + public class DynamicDaprClientProxyInterceptor : AbpInterceptor, ITransientDependency + { + protected static MethodInfo MakeRequestAndGetResultAsyncMethod { get; } + + protected DaprClient DaprClient { get; } + protected ICancellationTokenProvider CancellationTokenProvider { get; } + protected ICorrelationIdProvider CorrelationIdProvider { get; } + protected ICurrentTenant CurrentTenant { get; } + protected AbpCorrelationIdOptions AbpCorrelationIdOptions { get; } + protected IDaprApiDescriptionFinder ApiDescriptionFinder { get; } + protected AbpDaprRemoteServiceOptions AbpRemoteServiceOptions { get; } + protected AbpDaprClientProxyOptions ClientProxyOptions { get; } + protected IJsonSerializer JsonSerializer { get; } + protected IRemoteServiceDaprClientAuthenticator ClientAuthenticator { get; } + + public ILogger> Logger { get; set; } + + static DynamicDaprClientProxyInterceptor() + { + MakeRequestAndGetResultAsyncMethod = typeof(DynamicDaprClientProxyInterceptor) + .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) + .First(m => m.Name == nameof(MakeRequestAndGetResultAsync) && m.IsGenericMethodDefinition); + } + + public DynamicDaprClientProxyInterceptor( + DaprClient daprClient, + IOptions clientProxyOptions, + IOptionsSnapshot remoteServiceOptions, + IDaprApiDescriptionFinder apiDescriptionFinder, + IJsonSerializer jsonSerializer, + IRemoteServiceDaprClientAuthenticator clientAuthenticator, + ICancellationTokenProvider cancellationTokenProvider, + ICorrelationIdProvider correlationIdProvider, + IOptions correlationIdOptions, + ICurrentTenant currentTenant) + { + DaprClient = daprClient; + CancellationTokenProvider = cancellationTokenProvider; + CorrelationIdProvider = correlationIdProvider; + CurrentTenant = currentTenant; + AbpCorrelationIdOptions = correlationIdOptions.Value; + ApiDescriptionFinder = apiDescriptionFinder; + JsonSerializer = jsonSerializer; + ClientAuthenticator = clientAuthenticator; + ClientProxyOptions = clientProxyOptions.Value; + AbpRemoteServiceOptions = remoteServiceOptions.Value; + + Logger = NullLogger>.Instance; + } + + public override async Task InterceptAsync(IAbpMethodInvocation invocation) + { + if (invocation.Method.ReturnType.GenericTypeArguments.IsNullOrEmpty()) + { + await MakeRequestAsync(invocation); + } + else + { + var result = (Task)MakeRequestAndGetResultAsyncMethod + .MakeGenericMethod(invocation.Method.ReturnType.GenericTypeArguments[0]) + .Invoke(this, new object[] { invocation }); + + invocation.ReturnValue = await GetResultAsync( + result, + invocation.Method.ReturnType.GetGenericArguments()[0] + ); + } + } + + private async Task GetResultAsync(Task task, Type resultType) + { + await task; + return typeof(Task<>) + .MakeGenericType(resultType) + .GetProperty(nameof(Task.Result), BindingFlags.Instance | BindingFlags.Public) + .GetValue(task); + } + + private async Task MakeRequestAndGetResultAsync(IAbpMethodInvocation invocation) + { + var responseContent = await MakeRequestAsync(invocation); + + if (typeof(T) == typeof(IRemoteStreamContent)) + { + /* returning a class that holds a reference to response + * content just to be sure that GC does not dispose of + * it before we finish doing our work with the stream */ + return (T)(object)new RemoteStreamContent(await responseContent.ReadAsStreamAsync()) + { + ContentType = responseContent.Headers.ContentType?.ToString() + }; + } + + var stringContent = await responseContent.ReadAsStringAsync(); + if (typeof(T) == typeof(string)) + { + return (T)(object)stringContent; + } + + if (stringContent.IsNullOrWhiteSpace()) + { + return default; + } + + return JsonSerializer.Deserialize(stringContent); + } + + private async Task MakeRequestAsync(IAbpMethodInvocation invocation) + { + var clientConfig = ClientProxyOptions.DaprClientProxies.GetOrDefault(typeof(TService)) ?? throw new AbpException($"Could not get DynamicDaprClientProxyConfig for {typeof(TService).FullName}."); + var remoteServiceConfig = AbpRemoteServiceOptions.RemoteServices.GetConfigurationOrDefault(clientConfig.RemoteServiceName); + + // 遵循远端 api/abp/api-definition + var action = await ApiDescriptionFinder.FindActionAsync( + remoteServiceConfig.AppId, + typeof(TService), + invocation.Method + ); + + var apiVersion = GetApiVersionInfo(action); + + // See: https://docs.dapr.io/reference/api/service_invocation_api/#examples + // 需要合并端点作为dapr远程调用的方法名称 + var methodName = UrlBuilder.GenerateUrlWithParameters(action, invocation.ArgumentsDictionary, apiVersion); + + var requestMessage = DaprClient.CreateInvokeMethodRequest( + action.GetHttpMethod(), + remoteServiceConfig.AppId, + methodName); + + requestMessage.Content = RequestPayloadBuilder.BuildContent(action, invocation.ArgumentsDictionary, JsonSerializer, apiVersion); + + AddHeaders(invocation, action, requestMessage, apiVersion); + + await ClientAuthenticator.AuthenticateAsync( + new RemoteServiceDaprClientAuthenticateContext( + requestMessage, + remoteServiceConfig, + clientConfig.RemoteServiceName + ) + ); + + var response = await DaprClient.InvokeMethodWithResponseAsync(requestMessage, GetCancellationToken()); + + if (!response.IsSuccessStatusCode) + { + await ThrowExceptionForResponseAsync(response); + } + + return response.Content; + } + + private ApiVersionInfo GetApiVersionInfo(ActionApiDescriptionModel action) + { + var apiVersion = FindBestApiVersion(action); + + //TODO: Make names configurable? + var versionParam = action.Parameters.FirstOrDefault(p => p.Name == "apiVersion" && p.BindingSourceId == ParameterBindingSources.Path) ?? + action.Parameters.FirstOrDefault(p => p.Name == "api-version" && p.BindingSourceId == ParameterBindingSources.Query); + + return new ApiVersionInfo(versionParam?.BindingSourceId, apiVersion); + } + + private string FindBestApiVersion(ActionApiDescriptionModel action) + { + var configuredVersion = GetConfiguredApiVersion(); + + if (action.SupportedVersions.IsNullOrEmpty()) + { + return configuredVersion ?? "1.0"; + } + + if (action.SupportedVersions.Contains(configuredVersion)) + { + return configuredVersion; + } + + return action.SupportedVersions.Last(); //TODO: Ensure to get the latest version! + } + + protected virtual void AddHeaders( + IAbpMethodInvocation invocation, + ActionApiDescriptionModel action, + HttpRequestMessage requestMessage, + ApiVersionInfo apiVersion) + { + //API Version + if (!apiVersion.Version.IsNullOrEmpty()) + { + //TODO: What about other media types? + requestMessage.Headers.Add("accept", $"{MimeTypes.Text.Plain}; v={apiVersion.Version}"); + requestMessage.Headers.Add("accept", $"{MimeTypes.Application.Json}; v={apiVersion.Version}"); + requestMessage.Headers.Add("api-version", apiVersion.Version); + } + + //Header parameters + var headers = action.Parameters.Where(p => p.BindingSourceId == ParameterBindingSources.Header).ToArray(); + foreach (var headerParameter in headers) + { + var value = HttpActionParameterHelper.FindParameterValue(invocation.ArgumentsDictionary, headerParameter); + if (value != null) + { + requestMessage.Headers.Add(headerParameter.Name, value.ToString()); + } + } + + //CorrelationId + requestMessage.Headers.Add(AbpCorrelationIdOptions.HttpHeaderName, CorrelationIdProvider.Get()); + + //TenantId + if (CurrentTenant.Id.HasValue) + { + //TODO: Use AbpAspNetCoreMultiTenancyOptions to get the key + requestMessage.Headers.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString()); + } + + //Culture + //TODO: Is that the way we want? Couldn't send the culture (not ui culture) + var currentCulture = CultureInfo.CurrentUICulture.Name ?? CultureInfo.CurrentCulture.Name; + if (!currentCulture.IsNullOrEmpty()) + { + requestMessage.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(currentCulture)); + } + + //X-Requested-With + requestMessage.Headers.Add("X-Requested-With", "XMLHttpRequest"); + } + + private string GetConfiguredApiVersion() + { + var clientConfig = ClientProxyOptions.DaprClientProxies.GetOrDefault(typeof(TService)) + ?? throw new AbpException($"Could not get DynamicDaprClientProxyConfig for {typeof(TService).FullName}."); + + return AbpRemoteServiceOptions.RemoteServices.GetOrDefault(clientConfig.RemoteServiceName)?.Version + ?? AbpRemoteServiceOptions.RemoteServices.Default?.Version; + } + + private async Task ThrowExceptionForResponseAsync(HttpResponseMessage response) + { + if (response.Headers.Contains(AbpHttpConsts.AbpErrorFormat)) + { + var errorResponse = JsonSerializer.Deserialize( + await response.Content.ReadAsStringAsync() + ); + + throw new AbpRemoteCallException(errorResponse.Error) + { + HttpStatusCode = (int)response.StatusCode + }; + } + + throw new AbpRemoteCallException( + new RemoteServiceErrorInfo + { + Message = response.ReasonPhrase, + Code = response.StatusCode.ToString() + } + ) + { + HttpStatusCode = (int)response.StatusCode + }; + } + + protected virtual CancellationToken GetCancellationToken() + { + return CancellationTokenProvider.Token; + } + } +} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/HttpActionParameterHelper.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/HttpActionParameterHelper.cs new file mode 100644 index 000000000..032af085c --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/HttpActionParameterHelper.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Volo.Abp.Http.Modeling; +using Volo.Abp.Reflection; + +namespace LINGYUN.Abp.Dapr.Client.DynamicProxying +{ + internal static class HttpActionParameterHelper + { + public static object FindParameterValue(IReadOnlyDictionary methodArguments, ParameterApiDescriptionModel apiParameter) + { + var value = methodArguments.GetOrDefault(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/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/IDaprApiDescriptionFinder.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/IDaprApiDescriptionFinder.cs new file mode 100644 index 000000000..fd4998d7f --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/IDaprApiDescriptionFinder.cs @@ -0,0 +1,14 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; +using Volo.Abp.Http.Modeling; + +namespace LINGYUN.Abp.Dapr.Client.DynamicProxying +{ + public interface IDaprApiDescriptionFinder + { + Task FindActionAsync(string appId, Type serviceType, MethodInfo invocationMethod); + + Task GetApiDescriptionAsync(string appId); + } +} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/UrlBuilder.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/UrlBuilder.cs new file mode 100644 index 000000000..716145db8 --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/UrlBuilder.cs @@ -0,0 +1,144 @@ +using JetBrains.Annotations; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using Volo.Abp; +using Volo.Abp.Http.Client.DynamicProxying; +using Volo.Abp.Http.Modeling; +using Volo.Abp.Http.ProxyScripting.Generators; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Dapr.Client.DynamicProxying +{ + internal static class UrlBuilder + { + public static string GenerateUrlWithParameters(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) + { + var urlBuilder = new StringBuilder(action.Url); + + ReplacePathVariables(urlBuilder, action.Parameters, methodArguments, apiVersion); + AddQueryStringParameters(urlBuilder, action.Parameters, methodArguments, apiVersion); + + return urlBuilder.ToString(); + } + + private static void ReplacePathVariables(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) + { + var pathParameters = actionParameters + .Where(p => p.BindingSourceId == ParameterBindingSources.Path) + .ToArray(); + + if (!pathParameters.Any()) + { + return; + } + + if (pathParameters.Any(p => p.Name == "apiVersion")) + { + urlBuilder = urlBuilder.Replace("{apiVersion}", apiVersion.Version); + } + + foreach (var pathParameter in pathParameters.Where(p => p.Name != "apiVersion")) //TODO: Constant! + { + 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()); + } + } + } + + private static void AddQueryStringParameters(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) + { + var queryStringParameters = actionParameters + .Where(p => p.BindingSourceId.IsIn(ParameterBindingSources.ModelBinding, ParameterBindingSources.Query)) + .ToArray(); + + var isFirstParam = true; + + foreach (var queryStringParameter in queryStringParameters) + { + var value = HttpActionParameterHelper.FindParameterValue(methodArguments, queryStringParameter); + if (value == null) + { + continue; + } + + if (AddQueryStringParameter(urlBuilder, isFirstParam, queryStringParameter.Name, value)) + { + isFirstParam = false; + } + } + + if (apiVersion.ShouldSendInQueryString()) + { + AddQueryStringParameter(urlBuilder, isFirstParam, "api-version", apiVersion.Version); //TODO: Constant! + } + } + + private static bool AddQueryStringParameter( + StringBuilder urlBuilder, + bool isFirstParam, + string name, + [NotNull] object value) + { + if (value.GetType().IsArray || (value.GetType().IsGenericType && value is IEnumerable)) + { + var index = 0; + foreach (var item in (IEnumerable)value) + { + if (index == 0) + { + urlBuilder.Append(isFirstParam ? "?" : "&"); + } + urlBuilder.Append(name + $"[{index++}]=" + System.Net.WebUtility.UrlEncode(ConvertValueToString(item)) + "&"); + } + + if (index > 0) + { + //remove & at the end of the urlBuilder. + urlBuilder.Remove(urlBuilder.Length - 1, 1); + return true; + } + + return false; + } + + urlBuilder.Append(isFirstParam ? "?" : "&"); + urlBuilder.Append(name + "=" + System.Net.WebUtility.UrlEncode(ConvertValueToString(value))); + return true; + } + + private static string ConvertValueToString([NotNull] object value) + { + using (CultureHelper.Use(CultureInfo.InvariantCulture)) + { + if (value is DateTime dateTimeValue) + { + return dateTimeValue.ToUniversalTime().ToString("u"); + } + + return value.ToString(); + } + } + } +} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/IDaprClientFactory.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/IDaprClientFactory.cs new file mode 100644 index 000000000..5eb8b3177 --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/IDaprClientFactory.cs @@ -0,0 +1,9 @@ +using Dapr.Client; + +namespace LINGYUN.Abp.Dapr.Client +{ + public interface IDaprClientFactory + { + DaprClient Create(); + } +} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicDaprClientProxyExtensions.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicDaprClientProxyExtensions.cs new file mode 100644 index 000000000..d323e2c1c --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicDaprClientProxyExtensions.cs @@ -0,0 +1,114 @@ +using Castle.DynamicProxy; +using JetBrains.Annotations; +using LINGYUN.Abp.Dapr.Client; +using LINGYUN.Abp.Dapr.Client.DynamicProxying; +using Microsoft.Extensions.DependencyInjection.Extensions; +using System; +using System.Linq; +using System.Reflection; +using Volo.Abp; +using Volo.Abp.Castle.DynamicProxy; +using Volo.Abp.Validation; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class ServiceCollectionDynamicDaprClientProxyExtensions + { + private static readonly ProxyGenerator ProxyGeneratorInstance = new ProxyGenerator(); + + public static IServiceCollection AddDaprClient( + [NotNull] this IServiceCollection services) + { + Check.NotNull(services, nameof(services)); + + services.TryAddSingleton(provider => provider.GetRequiredService().Create()); + + return services; + } + + public static IServiceCollection AddDaprClientProxies( + [NotNull] this IServiceCollection services, + [NotNull] Assembly assembly, + [NotNull] string remoteServiceConfigurationName = DaprRemoteServiceConfigurationDictionary.DefaultName, + bool asDefaultServices = true) + { + Check.NotNull(services, nameof(assembly)); + + var serviceTypes = assembly.GetTypes().Where(IsSuitableForDynamicActorProxying).ToArray(); + + foreach (var serviceType in serviceTypes) + { + services.AddDaprClientProxy( + serviceType, + remoteServiceConfigurationName, + asDefaultServices + ); + } + + return services; + } + + public static IServiceCollection AddDaprClientProxy( + [NotNull] this IServiceCollection services, + [NotNull] string remoteServiceConfigurationName = DaprRemoteServiceConfigurationDictionary.DefaultName, + bool asDefaultService = true) + { + return services.AddDaprClientProxy( + typeof(T), + remoteServiceConfigurationName, + asDefaultService + ); + } + + public static IServiceCollection AddDaprClientProxy( + [NotNull] this IServiceCollection services, + [NotNull] Type type, + [NotNull] string remoteServiceConfigurationName = DaprRemoteServiceConfigurationDictionary.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.DaprClientProxies[type] = new DynamicDaprClientProxyConfig(type, remoteServiceConfigurationName); + }); + + var interceptorType = typeof(DynamicDaprClientProxyInterceptor<>).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) + ) + ); + } + + return services; + } + + private static bool IsSuitableForDynamicActorProxying(Type type) + { + //TODO: Add option to change type filter + + return type.IsInterface + && type.IsPublic + && !type.IsGenericType + && typeof(IRemoteService).IsAssignableFrom(type); + } + } +} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/README.md b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/README.md new file mode 100644 index 000000000..fdb6f1734 --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/README.md @@ -0,0 +1,51 @@ +# LINGYUN.Abp.Dapr.Client + +实现了Dapr文档中的服务间调用,项目设计与Volo.Abp.Http.Client一致,通过配置文件即可无缝替代Volo.Abp.Http.Client + +配置参考 [AbpRemoteServiceOptions](https://docs.abp.io/zh-Hans/abp/latest/API/Dynamic-CSharp-API-Clients#abpremoteserviceoptions) + +## 配置使用 + +模块按需引用 + +```csharp +[DependsOn(typeof(AbpDaprClientModule))] +public class YouProjectModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + // 注册代理类似于 Volo.Abp.Http.Client 模块 + context.Services.AddDaprClientProxies( + typeof(YouProjectActorInterfaceModule).Assembly, // 搜索 YouProjectActorInterfaceModule 模块下的远程服务定义 + RemoteServiceName + ); + } +} +``` +## 配置项说明 + +* AbpDaprClientOptions.GrpcEndpoint Dapr暴露的Grpc端点, 对应 **DaprClientBuilder.GrpcEndpoint** +* AbpDaprClientOptions.HttpEndpoint Dapr暴露的Http端点, 对应 **DaprClientBuilder.HttpEndpoint** +* AbpDaprClientOptions.GrpcChannelOptions 通过Grpc调用远程服务的配置项, 对应 **DaprClientBuilder.GrpcChannelOptions** + +* AbpDaprRemoteServiceOptions.RemoteServices 配置Dapr.AppId + +```json + +{ + "Dapr": { + "Client": { + "HttpEndpoint": "http://127.0.0.1:50000" + } + }, + "RemoteServices": { + "System": { + "AppId": "myapp" + } + } +} + +``` + + +## 其他