Browse Source

add support dapr service-invocation

pull/243/head
cKey 5 years ago
parent
commit
ad59c91968
  1. 7
      aspnet-core/LINGYUN.MicroService.Common.sln
  2. 51
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Actors/LINGYUN/Abp/Dapr/Actors/DynamicProxying/DynamicDaprActorProxyInterceptor.cs
  3. 13
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN.Abp.Dapr.Client.csproj
  4. 21
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/AbpDaprClientModule.cs
  5. 14
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/AbpDaprClientOptions.cs
  6. 12
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/AbpDaprRemoteServiceOptions.cs
  7. 9
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/Authentication/IRemoteServiceDaprClientAuthenticator.cs
  8. 14
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/Authentication/NullRemoteServiceDaprClientAuthenticator.cs
  9. 21
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/Authentication/RemoteServiceDaprClientAuthenticateContext.cs
  10. 45
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DaprClientFactory.cs
  11. 37
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DaprRemoteServiceConfiguration.cs
  12. 23
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DaprRemoteServiceConfigurationDictionary.cs
  13. 15
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/AbpDaprClientProxyOptions.cs
  14. 160
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DaprApiDescriptionFinder.cs
  15. 17
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DynamicDaprClientProxyConfig.cs
  16. 299
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DynamicDaprClientProxyInterceptor.cs
  17. 25
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/HttpActionParameterHelper.cs
  18. 14
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/IDaprApiDescriptionFinder.cs
  19. 144
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/UrlBuilder.cs
  20. 9
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/IDaprClientFactory.cs
  21. 114
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicDaprClientProxyExtensions.cs
  22. 51
      aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/README.md

7
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}

51
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<object>
throw new AbpException("The return type of Actor method must be Task or Task<object>");
}
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<RemoteServiceAttribute>();
var actorType = remoteServiceAttr != null
? remoteServiceAttr.Name
: invokeType.Name;
var isAsyncMethod = invocation.Method.IsAsync();
try
{
// 创建强类型代理
var actorProxy = proxyFactory.CreateActorProxy<TService>(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<object>.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<object>.Result), BindingFlags.Public | BindingFlags.Instance)
.GetValue(task);
}
}
catch (ActorMethodInvocationException amie) // 其他异常忽略交给框架处理

13
aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN.Abp.Dapr.Client.csproj

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapr.Client" Version="1.1.0" />
<PackageReference Include="Volo.Abp.Http.Client" Version="4.2.1" />
</ItemGroup>
</Project>

21
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<AbpDaprRemoteServiceOptions>(configuration);
Configure<AbpDaprClientOptions>(configuration.GetSection("Dapr:Client"));
// DaprClient应该配置为单例的实现
context.Services.AddDaprClient();
}
}
}

14
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()
{
}
}
}

12
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();
}
}
}

9
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);
}
}

14
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;
}
}
}

21
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;
}
}
}

45
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<DaprClient> _daprClientLazy;
public DaprClientFactory(
IOptions<AbpDaprClientOptions> daprClientOptions,
IOptions<AbpSystemTextJsonSerializerOptions> jsonSerializarOptions)
{
DaprClientOptions = daprClientOptions.Value;
JsonSerializerOptions = jsonSerializarOptions.Value;
_daprClientLazy = new Lazy<DaprClient>(() => 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();
}
}
}

37
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<string, string>
{
/// <summary>
/// Base AppId.
/// </summary>
public string AppId
{
get => this.GetOrDefault(nameof(AppId));
set => this[nameof(AppId)] = value;
}
/// <summary>
/// Version.
/// </summary>
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;
}
}
}

23
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<string, DaprRemoteServiceConfiguration>
{
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.");
}
}
}

15
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<Type, DynamicDaprClientProxyConfig> DaprClientProxies { get; set; }
public AbpDaprClientProxyOptions()
{
DaprClientProxies = new Dictionary<Type, DynamicDaprClientProxyConfig>();
}
}
}

160
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> abpCorrelationIdOptions,
ICorrelationIdProvider correlationIdProvider,
ICurrentTenant currentTenant)
{
DaprClient = daprClient;
Cache = cache;
AbpCorrelationIdOptions = abpCorrelationIdOptions.Value;
CorrelationIdProvider = correlationIdProvider;
CurrentTenant = currentTenant;
CancellationTokenProvider = NullCancellationTokenProvider.Instance;
}
public virtual async Task<ActionApiDescriptionModel> 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<ApplicationApiDescriptionModel> GetApiDescriptionAsync(string appId)
{
return await Cache.GetAsync(appId, () => GetApiDescriptionFromServerAsync(appId));
}
protected virtual async Task<ApplicationApiDescriptionModel> 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<ApplicationApiDescriptionModel>(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);
}
}
}

17
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;
}
}
}

299
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<TService> : 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<DynamicDaprClientProxyInterceptor<TService>> Logger { get; set; }
static DynamicDaprClientProxyInterceptor()
{
MakeRequestAndGetResultAsyncMethod = typeof(DynamicDaprClientProxyInterceptor<TService>)
.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
.First(m => m.Name == nameof(MakeRequestAndGetResultAsync) && m.IsGenericMethodDefinition);
}
public DynamicDaprClientProxyInterceptor(
DaprClient daprClient,
IOptions<AbpDaprClientProxyOptions> clientProxyOptions,
IOptionsSnapshot<AbpDaprRemoteServiceOptions> remoteServiceOptions,
IDaprApiDescriptionFinder apiDescriptionFinder,
IJsonSerializer jsonSerializer,
IRemoteServiceDaprClientAuthenticator clientAuthenticator,
ICancellationTokenProvider cancellationTokenProvider,
ICorrelationIdProvider correlationIdProvider,
IOptions<AbpCorrelationIdOptions> 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<DynamicDaprClientProxyInterceptor<TService>>.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<object> GetResultAsync(Task task, Type resultType)
{
await task;
return typeof(Task<>)
.MakeGenericType(resultType)
.GetProperty(nameof(Task<object>.Result), BindingFlags.Instance | BindingFlags.Public)
.GetValue(task);
}
private async Task<T> MakeRequestAndGetResultAsync<T>(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<T>(stringContent);
}
private async Task<HttpContent> 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<RemoteServiceErrorResponse>(
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;
}
}
}

25
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<string, object> 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);
}
}
}

14
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<ActionApiDescriptionModel> FindActionAsync(string appId, Type serviceType, MethodInfo invocationMethod);
Task<ApplicationApiDescriptionModel> GetApiDescriptionAsync(string appId);
}
}

144
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<string, object> 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<ParameterApiDescriptionModel> actionParameters, IReadOnlyDictionary<string, object> 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<ParameterApiDescriptionModel> actionParameters, IReadOnlyDictionary<string, object> 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();
}
}
}
}

9
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();
}
}

114
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<IDaprClientFactory>().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<T>(
[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<AbpDaprClientProxyOptions>(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);
}
}
}

51
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"
}
}
}
```
## 其他
Loading…
Cancel
Save