Browse Source

feat: cap支持多租户

feat/cap
WangJunZzz 2 years ago
parent
commit
f5a61e9b15
  1. 12
      aspnet-core/Directory.Build.targets
  2. 2
      aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/EfCoreAbpProCapTransactionApiFactory.cs
  3. 35
      aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCAPMessageExtensions.cs
  4. 165
      aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCAPSubscribeInvoker.cs
  5. 9
      aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapConsts.cs
  6. 6
      aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapDistributedEventBus.cs
  7. 6
      aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapPublisherExtension.cs
  8. 2
      aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapUnitOfWork.cs
  9. 41
      aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/AbpProCapHeader.cs
  10. 118
      aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/AwaitableInfo.cs
  11. 55
      aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/CoercedAwaitableInfo.cs
  12. 329
      aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutor.cs
  13. 119
      aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs
  14. 140
      aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs
  15. 2
      aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/readme.md
  16. 10
      aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/appsettings.json

12
aspnet-core/Directory.Build.targets

@ -46,12 +46,12 @@
<PackageReference Update="Hangfire.Redis.StackExchange" Version="1.9.3"/>
<!-- CAP 分布式事务-->
<PackageReference Update="DotNetCore.CAP" Version="8.0.0"/>
<PackageReference Update="DotNetCore.CAP.Dashboard" Version="8.0.0"/>
<PackageReference Update="DotNetCore.CAP.Mysql" Version="8.0.0"/>
<PackageReference Update="DotNetCore.CAP.RabbitMQ" Version="8.0.0"/>
<PackageReference Update="DotNetCore.CAP.InMemoryStorage" Version="8.0.0"/>
<PackageReference Update="Savorboard.CAP.InMemoryMessageQueue" Version="7.0.0"/>
<PackageReference Update="DotNetCore.CAP" Version="8.3.3"/>
<PackageReference Update="DotNetCore.CAP.Dashboard" Version="8.3.3"/>
<PackageReference Update="DotNetCore.CAP.Mysql" Version="8.3.3"/>
<PackageReference Update="DotNetCore.CAP.RabbitMQ" Version="8.3.3"/>
<PackageReference Update="DotNetCore.CAP.InMemoryStorage" Version="8.3.3"/>
<PackageReference Update="Savorboard.CAP.InMemoryMessageQueue" Version="8.2.1"/>
<!-- Swagger-->
<PackageReference Update="Swashbuckle.AspNetCore.SwaggerUI" Version="6.8.1"/>

2
aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/EfCoreAbpProCapTransactionApiFactory.cs

@ -57,7 +57,7 @@ public class EfCoreAbpProCapTransactionApiFactory : IAbpProCapTransactionApiFact
capTransaction.DbTransaction = originalApi.DbContextTransaction;
capTransaction.AutoCommit = false;
Publisher.Transaction.Value = capTransaction;
Publisher.Transaction = capTransaction;
return (IDbContextTransaction)Activator.CreateInstance(dbProviderInfo.CapEfDbTransactionType, capTransaction);
}

35
aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCAPMessageExtensions.cs

@ -0,0 +1,35 @@
namespace Lion.AbpPro.CAP;
public static class AbpProCAPMessageExtensions
{
/// <summary>
/// 尝试获取消息标头中的租户标识
/// </summary>
public static bool TryGetTenantId(this Message message, out Guid? tenantId)
{
if (message.Headers.TryGetValue(AbpProCapConsts.Tenant, out string tenantStr))
{
if (Guid.TryParse(tenantStr, out Guid id))
{
tenantId = id;
return true;
}
}
tenantId = null;
return false;
}
/// <summary>
/// 获取消息标头中的租户标识
/// </summary>
public static Guid? GetTenantIdOrNull(this Message message)
{
if (message.TryGetTenantId(out Guid? tenantId))
{
return tenantId;
}
return null;
}
}

165
aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCAPSubscribeInvoker.cs

@ -0,0 +1,165 @@
using System.ComponentModel;
using DotNetCore.CAP.Filter;
using Lion.AbpPro.CAP.Internal;
using Microsoft.Extensions.Internal;
namespace Lion.AbpPro.CAP;
public class AbpProCAPSubscribeInvoker
{
private readonly ConcurrentDictionary<string, ObjectMethodExecutor> _executors;
private readonly ISerializer _serializer;
private readonly IServiceProvider _serviceProvider;
private readonly ICurrentTenant _currentTenant;
public AbpProCAPSubscribeInvoker(IServiceProvider serviceProvider, ISerializer serializer, ICurrentTenant currentTenant)
{
_serviceProvider = serviceProvider;
_serializer = serializer;
_currentTenant = currentTenant;
_executors = new ConcurrentDictionary<string, ObjectMethodExecutor>();
}
public async Task<ConsumerExecutedResult> InvokeAsync(ConsumerContext context,
CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var methodInfo = context.ConsumerDescriptor.MethodInfo;
var reflectedTypeHandle = methodInfo.ReflectedType!.TypeHandle.Value;
var methodHandle = methodInfo.MethodHandle.Value;
var key = $"{reflectedTypeHandle}_{methodHandle}";
var executor = _executors.GetOrAdd(key,
_ => ObjectMethodExecutor.Create(methodInfo, context.ConsumerDescriptor.ImplTypeInfo));
await using var scope = _serviceProvider.CreateAsyncScope();
var provider = scope.ServiceProvider;
var obj = GetInstance(provider, context);
var message = context.DeliverMessage;
// 租户数据可能在消息标头中
var tenantId = message.GetTenantIdOrNull();
var parameterDescriptors = context.ConsumerDescriptor.Parameters;
var executeParameters = new object?[parameterDescriptors.Count];
for (var i = 0; i < parameterDescriptors.Count; i++)
{
var parameterDescriptor = parameterDescriptors[i];
if (parameterDescriptor.IsFromCap)
{
executeParameters[i] = GetCapProvidedParameter(parameterDescriptor, message, cancellationToken);
}
else
{
if (message.Value != null)
{
// use ISerializer when reading from storage, skip other objects if not Json
if (_serializer.IsJsonType(message.Value))
{
executeParameters[i] =
_serializer.Deserialize(message.Value, parameterDescriptor.ParameterType);
}
else
{
var converter = TypeDescriptor.GetConverter(parameterDescriptor.ParameterType);
if (converter.CanConvertFrom(message.Value.GetType()))
{
executeParameters[i] = converter.ConvertFrom(message.Value);
}
else
{
if (parameterDescriptor.ParameterType.IsInstanceOfType(message.Value))
executeParameters[i] = message.Value;
else
executeParameters[i] =
Convert.ChangeType(message.Value, parameterDescriptor.ParameterType);
}
}
}
}
}
var filter = provider.GetService<ISubscribeFilter>();
object? resultObj = null;
try
{
if (filter != null)
{
var etContext = new ExecutingContext(context, executeParameters);
await filter.OnSubscribeExecutingAsync(etContext).ConfigureAwait(false);
executeParameters = etContext.Arguments;
}
using (_currentTenant.Change(tenantId))
{
resultObj = await ExecuteWithParameterAsync(executor, obj, executeParameters).ConfigureAwait(false);
}
if (filter != null)
{
var edContext = new ExecutedContext(context, resultObj);
await filter.OnSubscribeExecutedAsync(edContext).ConfigureAwait(false);
resultObj = edContext.Result;
}
}
catch (Exception e)
{
if (filter != null)
{
var exContext = new ExceptionContext(context, e);
await filter.OnSubscribeExceptionAsync(exContext).ConfigureAwait(false);
if (!exContext.ExceptionHandled) exContext.Exception.ReThrow();
if (exContext.Result != null) resultObj = exContext.Result;
}
else
{
throw;
}
}
var callbackName = message.GetCallbackName();
if (string.IsNullOrEmpty(callbackName))
{
return new ConsumerExecutedResult(resultObj, message.GetId(), null, null);
}
else
{
var capHeader = executeParameters.FirstOrDefault(x => x is CapHeader) as AbpProCapHeader;
return new ConsumerExecutedResult(resultObj, message.GetId(), callbackName, capHeader?.ResponseHeader);
}
}
private static object GetCapProvidedParameter(ParameterDescriptor parameterDescriptor, Message message,
CancellationToken cancellationToken)
{
if (typeof(CancellationToken).IsAssignableFrom(parameterDescriptor.ParameterType)) return cancellationToken;
if (parameterDescriptor.ParameterType.IsAssignableFrom(typeof(CapHeader)))
return new CapHeader(message.Headers);
throw new ArgumentException(parameterDescriptor.Name);
}
protected virtual object GetInstance(IServiceProvider provider, ConsumerContext context)
{
var srvType = context.ConsumerDescriptor.ServiceTypeInfo?.AsType();
var implType = context.ConsumerDescriptor.ImplTypeInfo.AsType();
object? obj = null;
if (srvType != null) obj = provider.GetServices(srvType).FirstOrDefault(o => o?.GetType() == implType);
if (obj == null) obj = ActivatorUtilities.GetServiceOrCreateInstance(provider, implType);
return obj;
}
private async Task<object?> ExecuteWithParameterAsync(ObjectMethodExecutor executor, object @class,
object?[] parameter)
{
if (executor.IsMethodAsync) return await executor.ExecuteAsync(@class, parameter);
return executor.Execute(@class, parameter);
}
}

9
aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapConsts.cs

@ -0,0 +1,9 @@
namespace Lion.AbpPro.CAP;
public class AbpProCapConsts
{
/// <summary>
/// 租户字符串
/// </summary>
public const string Tenant = "__tenant";
}

6
aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapDistributedEventBus.cs

@ -129,7 +129,11 @@ public class AbpProCapDistributedEventBus : EventBusBase, IDistributedEventBus,
protected override async Task PublishToEventBusAsync(Type eventType, object eventData)
{
var eventName = EventNameAttribute.GetNameOrDefault(eventType);
await CapPublisher.PublishAsync(eventName, eventData);
var header = new Dictionary<string, string>
{
{ AbpProCapConsts.Tenant, CurrentTenant.Id?.ToString() ?? string.Empty },
};
await CapPublisher.PublishAsync(eventName, eventData, header);
}
protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord)

6
aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapPublisherExtension.cs

@ -4,8 +4,8 @@ public static class AbpProCapPublisherExtension
{
public static IDisposable UseTransaction(this ICapPublisher capPublisher, ICapTransaction capTransaction)
{
var previousValue = capPublisher.Transaction.Value;
capPublisher.Transaction.Value = capTransaction;
return new DisposeAction(() => capPublisher.Transaction.Value = previousValue);
var previousValue = capPublisher.Transaction;
capPublisher.Transaction = capTransaction;
return new DisposeAction(() => capPublisher.Transaction = previousValue);
}
}

2
aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapUnitOfWork.cs

@ -26,7 +26,7 @@ public class AbpProCapUnitOfWork : UnitOfWork
if (factory is not null)
{
api = factory.Create(api);
CapTransaction = CapPublisher.Transaction.Value;
CapTransaction = CapPublisher.Transaction;
}
base.AddTransactionApi(key, api);

41
aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/AbpProCapHeader.cs

@ -0,0 +1,41 @@
using System.Collections.ObjectModel;
namespace Lion.AbpPro.CAP.Internal;
public class AbpProCapHeader : ReadOnlyDictionary<string, string>
{
internal IDictionary<string, string?>? ResponseHeader { get; set; }
public AbpProCapHeader(IDictionary<string, string?> dictionary) : base(dictionary)
{
}
/// <summary>
/// When a callbackName is specified from publish message, use this method to add an additional header.
/// </summary>
/// <param name="key">The response header key.</param>
/// <param name="value">The response header value.</param>
public void AddResponseHeader(string key, string? value)
{
ResponseHeader ??= new Dictionary<string, string?>();
ResponseHeader[key] = value;
}
/// <summary>
/// When a callbackName is specified from publish message, use this method to abort the callback.
/// </summary>
public void RemoveCallback()
{
Dictionary.Remove(Headers.CallbackName);
}
/// <summary>
/// When a callbackName is specified from Publish message, use this method to rewrite the callback name.
/// </summary>
/// <param name="callbackName">The new callback name.</param>
public void RewriteCallback(string callbackName)
{
Dictionary[Headers.CallbackName] = callbackName;
}
}

118
aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/AwaitableInfo.cs

@ -0,0 +1,118 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace Microsoft.Extensions.Internal;
internal readonly struct AwaitableInfo
{
public Type AwaiterType { get; }
public PropertyInfo AwaiterIsCompletedProperty { get; }
public MethodInfo AwaiterGetResultMethod { get; }
public MethodInfo AwaiterOnCompletedMethod { get; }
public MethodInfo AwaiterUnsafeOnCompletedMethod { get; }
public Type ResultType { get; }
public MethodInfo GetAwaiterMethod { get; }
public AwaitableInfo(
Type awaiterType,
PropertyInfo awaiterIsCompletedProperty,
MethodInfo awaiterGetResultMethod,
MethodInfo awaiterOnCompletedMethod,
MethodInfo awaiterUnsafeOnCompletedMethod,
Type resultType,
MethodInfo getAwaiterMethod)
{
AwaiterType = awaiterType;
AwaiterIsCompletedProperty = awaiterIsCompletedProperty;
AwaiterGetResultMethod = awaiterGetResultMethod;
AwaiterOnCompletedMethod = awaiterOnCompletedMethod;
AwaiterUnsafeOnCompletedMethod = awaiterUnsafeOnCompletedMethod;
ResultType = resultType;
GetAwaiterMethod = getAwaiterMethod;
}
public static bool IsTypeAwaitable(Type type, out AwaitableInfo awaitableInfo)
{
// Based on Roslyn code: http://source.roslyn.io/#Microsoft.CodeAnalysis.Workspaces/Shared/Extensions/ISymbolExtensions.cs,db4d48ba694b9347
// Awaitable must have method matching "object GetAwaiter()"
var getAwaiterMethod = type.GetRuntimeMethods().FirstOrDefault(m =>
m.Name.Equals("GetAwaiter", StringComparison.OrdinalIgnoreCase)
&& m.GetParameters().Length == 0
&& m.ReturnType != null);
if (getAwaiterMethod == null)
{
awaitableInfo = default;
return false;
}
var awaiterType = getAwaiterMethod.ReturnType;
// Awaiter must have property matching "bool IsCompleted { get; }"
var isCompletedProperty = awaiterType.GetRuntimeProperties().FirstOrDefault(p =>
p.Name.Equals("IsCompleted", StringComparison.OrdinalIgnoreCase)
&& p.PropertyType == typeof(bool)
&& p.GetMethod != null);
if (isCompletedProperty == null)
{
awaitableInfo = default;
return false;
}
// Awaiter must implement INotifyCompletion
var awaiterInterfaces = awaiterType.GetInterfaces();
var implementsINotifyCompletion = awaiterInterfaces.Any(t => t == typeof(INotifyCompletion));
if (!implementsINotifyCompletion)
{
awaitableInfo = default;
return false;
}
// INotifyCompletion supplies a method matching "void OnCompleted(Action action)"
var onCompletedMethod = typeof(INotifyCompletion).GetRuntimeMethods().Single(m =>
m.Name.Equals("OnCompleted", StringComparison.OrdinalIgnoreCase)
&& m.ReturnType == typeof(void)
&& m.GetParameters().Length == 1
&& m.GetParameters()[0].ParameterType == typeof(Action));
// Awaiter optionally implements ICriticalNotifyCompletion
var implementsICriticalNotifyCompletion = awaiterInterfaces.Any(t => t == typeof(ICriticalNotifyCompletion));
MethodInfo unsafeOnCompletedMethod;
if (implementsICriticalNotifyCompletion)
// ICriticalNotifyCompletion supplies a method matching "void UnsafeOnCompleted(Action action)"
unsafeOnCompletedMethod = typeof(ICriticalNotifyCompletion).GetRuntimeMethods().Single(m =>
m.Name.Equals("UnsafeOnCompleted", StringComparison.OrdinalIgnoreCase)
&& m.ReturnType == typeof(void)
&& m.GetParameters().Length == 1
&& m.GetParameters()[0].ParameterType == typeof(Action));
else
unsafeOnCompletedMethod = null;
// Awaiter must have method matching "void GetResult" or "T GetResult()"
var getResultMethod = awaiterType.GetRuntimeMethods().FirstOrDefault(m =>
m.Name.Equals("GetResult")
&& m.GetParameters().Length == 0);
if (getResultMethod == null)
{
awaitableInfo = default;
return false;
}
awaitableInfo = new AwaitableInfo(
awaiterType,
isCompletedProperty,
getResultMethod,
onCompletedMethod,
unsafeOnCompletedMethod,
getResultMethod.ReturnType,
getAwaiterMethod);
return true;
}
}

55
aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/CoercedAwaitableInfo.cs

@ -0,0 +1,55 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Linq.Expressions;
namespace Microsoft.Extensions.Internal;
internal readonly struct CoercedAwaitableInfo
{
public AwaitableInfo AwaitableInfo { get; }
public Expression CoercerExpression { get; }
public Type CoercerResultType { get; }
public bool RequiresCoercion => CoercerExpression != null;
public CoercedAwaitableInfo(AwaitableInfo awaitableInfo)
{
AwaitableInfo = awaitableInfo;
CoercerExpression = null;
CoercerResultType = null;
}
public CoercedAwaitableInfo(Expression coercerExpression, Type coercerResultType,
AwaitableInfo coercedAwaitableInfo)
{
CoercerExpression = coercerExpression;
CoercerResultType = coercerResultType;
AwaitableInfo = coercedAwaitableInfo;
}
public static bool IsTypeAwaitable(Type type, out CoercedAwaitableInfo info)
{
if (AwaitableInfo.IsTypeAwaitable(type, out var directlyAwaitableInfo))
{
info = new CoercedAwaitableInfo(directlyAwaitableInfo);
return true;
}
// It's not directly awaitable, but maybe we can coerce it.
// Currently we support coercing FSharpAsync<T>.
if (ObjectMethodExecutorFSharpSupport.TryBuildCoercerFromFSharpAsyncToAwaitable(type,
out var coercerExpression,
out var coercerResultType))
if (AwaitableInfo.IsTypeAwaitable(coercerResultType, out var coercedAwaitableInfo))
{
info = new CoercedAwaitableInfo(coercerExpression, coercerResultType, coercedAwaitableInfo);
return true;
}
info = default;
return false;
}
}

329
aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutor.cs

@ -0,0 +1,329 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
namespace Microsoft.Extensions.Internal;
internal class ObjectMethodExecutor
{
private static readonly ConstructorInfo _objectMethodExecutorAwaitableConstructor =
typeof(ObjectMethodExecutorAwaitable).GetConstructor(new[]
{
typeof(object), // customAwaitable
typeof(Func<object, object>), // getAwaiterMethod
typeof(Func<object, bool>), // isCompletedMethod
typeof(Func<object, object>), // getResultMethod
typeof(Action<object, Action>), // onCompletedMethod
typeof(Action<object, Action>) // unsafeOnCompletedMethod
})!;
private readonly MethodExecutor? _executor;
private readonly MethodExecutorAsync? _executorAsync;
private readonly object?[]? _parameterDefaultValues;
private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo, object?[]? parameterDefaultValues)
{
if (methodInfo == null) throw new ArgumentNullException(nameof(methodInfo));
MethodInfo = methodInfo;
MethodParameters = methodInfo.GetParameters();
TargetTypeInfo = targetTypeInfo;
MethodReturnType = methodInfo.ReturnType;
var isAwaitable = CoercedAwaitableInfo.IsTypeAwaitable(MethodReturnType, out var coercedAwaitableInfo);
IsMethodAsync = isAwaitable;
AsyncResultType = isAwaitable ? coercedAwaitableInfo.AwaitableInfo.ResultType : null;
// Upstream code may prefer to use the sync-executor even for async methods, because if it knows
// that the result is a specific Task<T> where T is known, then it can directly cast to that type
// and await it without the extra heap allocations involved in the _executorAsync code path.
_executor = GetExecutor(methodInfo, targetTypeInfo);
if (IsMethodAsync) _executorAsync = GetExecutorAsync(methodInfo, targetTypeInfo, coercedAwaitableInfo);
_parameterDefaultValues = parameterDefaultValues;
}
public MethodInfo MethodInfo { get; }
public ParameterInfo[] MethodParameters { get; }
public TypeInfo TargetTypeInfo { get; }
public Type? AsyncResultType { get; }
// This field is made internal set because it is set in unit tests.
public Type MethodReturnType { get; internal set; }
public bool IsMethodAsync { get; }
public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo)
{
return new ObjectMethodExecutor(methodInfo, targetTypeInfo, null);
}
public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo,
object?[] parameterDefaultValues)
{
if (parameterDefaultValues == null) throw new ArgumentNullException(nameof(parameterDefaultValues));
return new ObjectMethodExecutor(methodInfo, targetTypeInfo, parameterDefaultValues);
}
/// <summary>
/// Executes the configured method on <paramref name="target" />. This can be used whether or not
/// the configured method is asynchronous.
/// </summary>
/// <remarks>
/// Even if the target method is asynchronous, it's desirable to invoke it using Execute rather than
/// ExecuteAsync if you know at compile time what the return type is, because then you can directly
/// "await" that value (via a cast), and then the generated code will be able to reference the
/// resulting awaitable as a value-typed variable. If you use ExecuteAsync instead, the generated
/// code will have to treat the resulting awaitable as a boxed object, because it doesn't know at
/// compile time what type it would be.
/// </remarks>
/// <param name="target">The object whose method is to be executed.</param>
/// <param name="parameters">Parameters to pass to the method.</param>
/// <returns>The method return value.</returns>
public object? Execute(object target, object?[]? parameters)
{
Debug.Assert(_executor != null, "Sync execution is not supported.");
return _executor(target, parameters);
}
/// <summary>
/// Executes the configured method on <paramref name="target" />. This can only be used if the configured
/// method is asynchronous.
/// </summary>
/// <remarks>
/// If you don't know at compile time the type of the method's returned awaitable, you can use ExecuteAsync,
/// which supplies an awaitable-of-object. This always works, but can incur several extra heap allocations
/// as compared with using Execute and then using "await" on the result value typecasted to the known
/// awaitable type. The possible extra heap allocations are for:
/// 1. The custom awaitable (though usually there's a heap allocation for this anyway, since normally
/// it's a reference type, and you normally create a new instance per call).
/// 2. The custom awaiter (whether or not it's a value type, since if it's not, you need a new instance
/// of it, and if it is, it will have to be boxed so the calling code can reference it as an object).
/// 3. The async result value, if it's a value type (it has to be boxed as an object, since the calling
/// code doesn't know what type it's going to be).
/// </remarks>
/// <param name="target">The object whose method is to be executed.</param>
/// <param name="parameters">Parameters to pass to the method.</param>
/// <returns>An object that you can "await" to get the method return value.</returns>
public ObjectMethodExecutorAwaitable ExecuteAsync(object target, object?[]? parameters)
{
Debug.Assert(_executorAsync != null, "Async execution is not supported.");
return _executorAsync(target, parameters);
}
public object? GetDefaultValueForParameter(int index)
{
if (_parameterDefaultValues == null)
throw new InvalidOperationException(
$"Cannot call {nameof(GetDefaultValueForParameter)}, because no parameter default values were supplied.");
if (index < 0 || index > MethodParameters.Length - 1) throw new ArgumentOutOfRangeException(nameof(index));
return _parameterDefaultValues[index];
}
private static MethodExecutor GetExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo)
{
// Parameters to executor
var targetParameter = Expression.Parameter(typeof(object), "target");
var parametersParameter = Expression.Parameter(typeof(object?[]), "parameters");
// Build parameter list
var paramInfos = methodInfo.GetParameters();
var parameters = new List<Expression>(paramInfos.Length);
for (var i = 0; i < paramInfos.Length; i++)
{
var paramInfo = paramInfos[i];
var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType);
// valueCast is "(Ti) parameters[i]"
parameters.Add(valueCast);
}
// Call method
var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType());
var methodCall = Expression.Call(instanceCast, methodInfo, parameters);
// methodCall is "((Ttarget) target) method((T0) parameters[0], (T1) parameters[1], ...)"
// Create function
if (methodCall.Type == typeof(void))
{
var lambda = Expression.Lambda<VoidMethodExecutor>(methodCall, targetParameter, parametersParameter);
var voidExecutor = lambda.Compile();
return WrapVoidMethod(voidExecutor);
}
else
{
// must coerce methodCall to match ActionExecutor signature
var castMethodCall = Expression.Convert(methodCall, typeof(object));
var lambda = Expression.Lambda<MethodExecutor>(castMethodCall, targetParameter, parametersParameter);
return lambda.Compile();
}
}
private static MethodExecutor WrapVoidMethod(VoidMethodExecutor executor)
{
return delegate(object target, object?[]? parameters)
{
executor(target, parameters);
return null;
};
}
private static MethodExecutorAsync GetExecutorAsync(
MethodInfo methodInfo,
TypeInfo targetTypeInfo,
CoercedAwaitableInfo coercedAwaitableInfo)
{
// Parameters to executor
var targetParameter = Expression.Parameter(typeof(object), "target");
var parametersParameter = Expression.Parameter(typeof(object[]), "parameters");
// Build parameter list
var paramInfos = methodInfo.GetParameters();
var parameters = new List<Expression>(paramInfos.Length);
for (var i = 0; i < paramInfos.Length; i++)
{
var paramInfo = paramInfos[i];
var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType);
// valueCast is "(Ti) parameters[i]"
parameters.Add(valueCast);
}
// Call method
var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType());
var methodCall = Expression.Call(instanceCast, methodInfo, parameters);
// Using the method return value, construct an ObjectMethodExecutorAwaitable based on
// the info we have about its implementation of the awaitable pattern. Note that all
// the funcs/actions we construct here are precompiled, so that only one instance of
// each is preserved throughout the lifetime of the ObjectMethodExecutor.
// var getAwaiterFunc = (object awaitable) =>
// (object)((CustomAwaitableType)awaitable).GetAwaiter();
var customAwaitableParam = Expression.Parameter(typeof(object), "awaitable");
var awaitableInfo = coercedAwaitableInfo.AwaitableInfo;
var postCoercionMethodReturnType = coercedAwaitableInfo.CoercerResultType ?? methodInfo.ReturnType;
var getAwaiterFunc = Expression.Lambda<Func<object, object>>(
Expression.Convert(
Expression.Call(
Expression.Convert(customAwaitableParam, postCoercionMethodReturnType),
awaitableInfo.GetAwaiterMethod),
typeof(object)),
customAwaitableParam).Compile();
// var isCompletedFunc = (object awaiter) =>
// ((CustomAwaiterType)awaiter).IsCompleted;
var isCompletedParam = Expression.Parameter(typeof(object), "awaiter");
var isCompletedFunc = Expression.Lambda<Func<object, bool>>(
Expression.MakeMemberAccess(
Expression.Convert(isCompletedParam, awaitableInfo.AwaiterType),
awaitableInfo.AwaiterIsCompletedProperty),
isCompletedParam).Compile();
var getResultParam = Expression.Parameter(typeof(object), "awaiter");
Func<object, object> getResultFunc;
if (awaitableInfo.ResultType == typeof(void))
// var getResultFunc = (object awaiter) =>
// {
// ((CustomAwaiterType)awaiter).GetResult(); // We need to invoke this to surface any exceptions
// return (object)null;
// };
getResultFunc = Expression.Lambda<Func<object, object>>(
Expression.Block(
Expression.Call(
Expression.Convert(getResultParam, awaitableInfo.AwaiterType),
awaitableInfo.AwaiterGetResultMethod),
Expression.Constant(null)
),
getResultParam).Compile();
else
// var getResultFunc = (object awaiter) =>
// (object)((CustomAwaiterType)awaiter).GetResult();
getResultFunc = Expression.Lambda<Func<object, object>>(
Expression.Convert(
Expression.Call(
Expression.Convert(getResultParam, awaitableInfo.AwaiterType),
awaitableInfo.AwaiterGetResultMethod),
typeof(object)),
getResultParam).Compile();
// var onCompletedFunc = (object awaiter, Action continuation) => {
// ((CustomAwaiterType)awaiter).OnCompleted(continuation);
// };
var onCompletedParam1 = Expression.Parameter(typeof(object), "awaiter");
var onCompletedParam2 = Expression.Parameter(typeof(Action), "continuation");
var onCompletedFunc = Expression.Lambda<Action<object, Action>>(
Expression.Call(
Expression.Convert(onCompletedParam1, awaitableInfo.AwaiterType),
awaitableInfo.AwaiterOnCompletedMethod,
onCompletedParam2),
onCompletedParam1,
onCompletedParam2).Compile();
Action<object, Action>? unsafeOnCompletedFunc = null;
if (awaitableInfo.AwaiterUnsafeOnCompletedMethod != null)
{
// var unsafeOnCompletedFunc = (object awaiter, Action continuation) => {
// ((CustomAwaiterType)awaiter).UnsafeOnCompleted(continuation);
// };
var unsafeOnCompletedParam1 = Expression.Parameter(typeof(object), "awaiter");
var unsafeOnCompletedParam2 = Expression.Parameter(typeof(Action), "continuation");
unsafeOnCompletedFunc = Expression.Lambda<Action<object, Action>>(
Expression.Call(
Expression.Convert(unsafeOnCompletedParam1, awaitableInfo.AwaiterType),
awaitableInfo.AwaiterUnsafeOnCompletedMethod,
unsafeOnCompletedParam2),
unsafeOnCompletedParam1,
unsafeOnCompletedParam2).Compile();
}
// If we need to pass the method call result through a coercer function to get an
// awaitable, then do so.
var coercedMethodCall = coercedAwaitableInfo.RequiresCoercion
? Expression.Invoke(coercedAwaitableInfo.CoercerExpression, methodCall)
: (Expression)methodCall;
// return new ObjectMethodExecutorAwaitable(
// (object)coercedMethodCall,
// getAwaiterFunc,
// isCompletedFunc,
// getResultFunc,
// onCompletedFunc,
// unsafeOnCompletedFunc);
var returnValueExpression = Expression.New(
_objectMethodExecutorAwaitableConstructor,
Expression.Convert(coercedMethodCall, typeof(object)),
Expression.Constant(getAwaiterFunc),
Expression.Constant(isCompletedFunc),
Expression.Constant(getResultFunc),
Expression.Constant(onCompletedFunc),
Expression.Constant(unsafeOnCompletedFunc, typeof(Action<object, Action>)));
var lambda =
Expression.Lambda<MethodExecutorAsync>(returnValueExpression, targetParameter, parametersParameter);
return lambda.Compile();
}
private delegate ObjectMethodExecutorAwaitable MethodExecutorAsync(object target, object?[]? parameters);
private delegate object? MethodExecutor(object target, object?[]? parameters);
private delegate void VoidMethodExecutor(object target, object?[]? parameters);
}

119
aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs

@ -0,0 +1,119 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Runtime.CompilerServices;
namespace Microsoft.Extensions.Internal;
/// <summary>
/// Provides a common awaitable structure that <see cref="ObjectMethodExecutor.ExecuteAsync" /> can
/// return, regardless of whether the underlying value is a System.Task, an FSharpAsync, or an
/// application-defined custom awaitable.
/// </summary>
internal readonly struct ObjectMethodExecutorAwaitable
{
private readonly object _customAwaitable;
private readonly Func<object, object> _getAwaiterMethod;
private readonly Func<object, bool> _isCompletedMethod;
private readonly Func<object, object> _getResultMethod;
private readonly Action<object, Action> _onCompletedMethod;
private readonly Action<object, Action> _unsafeOnCompletedMethod;
// Perf note: since we're requiring the customAwaitable to be supplied here as an object,
// this will trigger a further allocation if it was a value type (i.e., to box it). We can't
// fix this by making the customAwaitable type generic, because the calling code typically
// does not know the type of the awaitable/awaiter at compile-time anyway.
//
// However, we could fix it by not passing the customAwaitable here at all, and instead
// passing a func that maps directly from the target object (e.g., controller instance),
// target method (e.g., action method info), and params array to the custom awaiter in the
// GetAwaiter() method below. In effect, by delaying the actual method call until the
// upstream code calls GetAwaiter on this ObjectMethodExecutorAwaitable instance.
// This optimization is not currently implemented because:
// [1] It would make no difference when the awaitable was an object type, which is
// by far the most common scenario (e.g., System.Task<T>).
// [2] It would be complex - we'd need some kind of object pool to track all the parameter
// arrays until we needed to use them in GetAwaiter().
// We can reconsider this in the future if there's a need to optimize for ValueTask<T>
// or other value-typed awaitables.
public ObjectMethodExecutorAwaitable(
object customAwaitable,
Func<object, object> getAwaiterMethod,
Func<object, bool> isCompletedMethod,
Func<object, object> getResultMethod,
Action<object, Action> onCompletedMethod,
Action<object, Action> unsafeOnCompletedMethod)
{
_customAwaitable = customAwaitable;
_getAwaiterMethod = getAwaiterMethod;
_isCompletedMethod = isCompletedMethod;
_getResultMethod = getResultMethod;
_onCompletedMethod = onCompletedMethod;
_unsafeOnCompletedMethod = unsafeOnCompletedMethod;
}
public Awaiter GetAwaiter()
{
var customAwaiter = _getAwaiterMethod(_customAwaitable);
return new Awaiter(customAwaiter, _isCompletedMethod, _getResultMethod, _onCompletedMethod,
_unsafeOnCompletedMethod);
}
public readonly struct Awaiter : ICriticalNotifyCompletion
{
private readonly object _customAwaiter;
private readonly Func<object, bool> _isCompletedMethod;
private readonly Func<object, object> _getResultMethod;
private readonly Action<object, Action> _onCompletedMethod;
private readonly Action<object, Action> _unsafeOnCompletedMethod;
public Awaiter(
object customAwaiter,
Func<object, bool> isCompletedMethod,
Func<object, object> getResultMethod,
Action<object, Action> onCompletedMethod,
Action<object, Action> unsafeOnCompletedMethod)
{
_customAwaiter = customAwaiter;
_isCompletedMethod = isCompletedMethod;
_getResultMethod = getResultMethod;
_onCompletedMethod = onCompletedMethod;
_unsafeOnCompletedMethod = unsafeOnCompletedMethod;
}
public bool IsCompleted => _isCompletedMethod(_customAwaiter);
public object GetResult()
{
return _getResultMethod(_customAwaiter);
}
public void OnCompleted(Action continuation)
{
_onCompletedMethod(_customAwaiter, continuation);
}
public void UnsafeOnCompleted(Action continuation)
{
// If the underlying awaitable implements ICriticalNotifyCompletion, use its UnsafeOnCompleted.
// If not, fall back on using its OnCompleted.
//
// Why this is safe:
// - Implementing ICriticalNotifyCompletion is a way of saying the caller can choose whether it
// needs the execution context to be preserved (which it signals by calling OnCompleted), or
// that it doesn't (which it signals by calling UnsafeOnCompleted). Obviously it's faster *not*
// to preserve and restore the context, so we prefer that where possible.
// - If a caller doesn't need the execution context to be preserved and hence calls UnsafeOnCompleted,
// there's no harm in preserving it anyway - it's just a bit of wasted cost. That's what will happen
// if a caller sees that the proxy implements ICriticalNotifyCompletion but the proxy chooses to
// pass the call on to the underlying awaitable's OnCompleted method.
var underlyingMethodToUse = _unsafeOnCompletedMethod ?? _onCompletedMethod;
underlyingMethodToUse(_customAwaiter, continuation);
}
}
}

140
aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs

@ -0,0 +1,140 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Extensions.Internal;
/// <summary>
/// Helper for detecting whether a given type is FSharpAsync`1, and if so, supplying
/// an <see cref="Expression" /> for mapping instances of that type to a C# awaitable.
/// </summary>
/// <remarks>
/// The main design goal here is to avoid taking a compile-time dependency on
/// FSharp.Core.dll, because non-F# applications wouldn't use it. So all the references
/// to FSharp types have to be constructed dynamically at runtime.
/// </remarks>
internal static class ObjectMethodExecutorFSharpSupport
{
private static readonly object _fsharpValuesCacheLock = new();
private static Assembly _fsharpCoreAssembly;
private static MethodInfo _fsharpAsyncStartAsTaskGenericMethod;
private static PropertyInfo _fsharpOptionOfTaskCreationOptionsNoneProperty;
private static PropertyInfo _fsharpOptionOfCancellationTokenNoneProperty;
public static bool TryBuildCoercerFromFSharpAsyncToAwaitable(
Type possibleFSharpAsyncType,
out Expression coerceToAwaitableExpression,
out Type awaitableType)
{
var methodReturnGenericType = possibleFSharpAsyncType.IsGenericType
? possibleFSharpAsyncType.GetGenericTypeDefinition()
: null;
if (!IsFSharpAsyncOpenGenericType(methodReturnGenericType))
{
coerceToAwaitableExpression = null;
awaitableType = null;
return false;
}
var awaiterResultType = possibleFSharpAsyncType.GetGenericArguments().Single();
awaitableType = typeof(Task<>).MakeGenericType(awaiterResultType);
// coerceToAwaitableExpression = (object fsharpAsync) =>
// {
// return (object)FSharpAsync.StartAsTask<TResult>(
// (Microsoft.FSharp.Control.FSharpAsync<TResult>)fsharpAsync,
// FSharpOption<TaskCreationOptions>.None,
// FSharpOption<CancellationToken>.None);
// };
var startAsTaskClosedMethod = _fsharpAsyncStartAsTaskGenericMethod
.MakeGenericMethod(awaiterResultType);
var coerceToAwaitableParam = Expression.Parameter(typeof(object));
coerceToAwaitableExpression = Expression.Lambda(
Expression.Convert(
Expression.Call(
startAsTaskClosedMethod,
Expression.Convert(coerceToAwaitableParam, possibleFSharpAsyncType),
Expression.MakeMemberAccess(null, _fsharpOptionOfTaskCreationOptionsNoneProperty),
Expression.MakeMemberAccess(null, _fsharpOptionOfCancellationTokenNoneProperty)),
typeof(object)),
coerceToAwaitableParam);
return true;
}
private static bool IsFSharpAsyncOpenGenericType(Type possibleFSharpAsyncGenericType)
{
var typeFullName = possibleFSharpAsyncGenericType?.FullName;
if (!string.Equals(typeFullName, "Microsoft.FSharp.Control.FSharpAsync`1", StringComparison.Ordinal))
return false;
lock (_fsharpValuesCacheLock)
{
if (_fsharpCoreAssembly != null)
// Since we've already found the real FSharpAsync.Core assembly, we just have
// to check that the supplied FSharpAsync`1 type is the one from that assembly.
return possibleFSharpAsyncGenericType.Assembly == _fsharpCoreAssembly;
// We'll keep trying to find the FSharp types/values each time any type called
// FSharpAsync`1 is supplied.
return TryPopulateFSharpValueCaches(possibleFSharpAsyncGenericType);
}
}
private static bool TryPopulateFSharpValueCaches(Type possibleFSharpAsyncGenericType)
{
var assembly = possibleFSharpAsyncGenericType.Assembly;
var fsharpOptionType = assembly.GetType("Microsoft.FSharp.Core.FSharpOption`1");
var fsharpAsyncType = assembly.GetType("Microsoft.FSharp.Control.FSharpAsync");
if (fsharpOptionType == null || fsharpAsyncType == null) return false;
// Get a reference to FSharpOption<TaskCreationOptions>.None
var fsharpOptionOfTaskCreationOptionsType = fsharpOptionType
.MakeGenericType(typeof(TaskCreationOptions));
_fsharpOptionOfTaskCreationOptionsNoneProperty = fsharpOptionOfTaskCreationOptionsType
.GetRuntimeProperty("None");
// Get a reference to FSharpOption<CancellationToken>.None
var fsharpOptionOfCancellationTokenType = fsharpOptionType
.MakeGenericType(typeof(CancellationToken));
_fsharpOptionOfCancellationTokenNoneProperty = fsharpOptionOfCancellationTokenType
.GetRuntimeProperty("None");
// Get a reference to FSharpAsync.StartAsTask<>
var fsharpAsyncMethods = fsharpAsyncType
.GetRuntimeMethods()
.Where(m => m.Name.Equals("StartAsTask", StringComparison.Ordinal));
foreach (var candidateMethodInfo in fsharpAsyncMethods)
{
var parameters = candidateMethodInfo.GetParameters();
if (parameters.Length == 3
&& TypesHaveSameIdentity(parameters[0].ParameterType, possibleFSharpAsyncGenericType)
&& parameters[1].ParameterType == fsharpOptionOfTaskCreationOptionsType
&& parameters[2].ParameterType == fsharpOptionOfCancellationTokenType)
{
// This really does look like the correct method (and hence assembly).
_fsharpAsyncStartAsTaskGenericMethod = candidateMethodInfo;
_fsharpCoreAssembly = assembly;
break;
}
}
return _fsharpCoreAssembly != null;
}
private static bool TypesHaveSameIdentity(Type type1, Type type2)
{
return type1.Assembly == type2.Assembly
&& string.Equals(type1.Namespace, type2.Namespace, StringComparison.Ordinal)
&& string.Equals(type1.Name, type2.Name, StringComparison.Ordinal);
}
}

2
aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/readme.md

@ -0,0 +1,2 @@
这个目录下是cap的源码
因为cap的源码的访问级别是internal,扩展的时候无法访问,所以直接把源码拷贝过来。

10
aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/appsettings.json

@ -31,19 +31,19 @@
},
"App": {
"SelfUrl": "http://localhost:44315",
"CorsOrigins": "https://*.AbpPro.com,http://localhost:4200,http://localhost:3100"
"CorsOrigins": "https://*.AbpPro.com,http://localhost:4200,http://182.43.18.151:44320,http://182.43.18.151:44321,http://182.43.18.151:44322,http://182.43.18.151:44318"
},
"ConnectionStrings": {
"Default": "Data Source=localhost;Port=3306;Database=LionAbpPro91;uid=root;pwd=1q2w3E*;charset=utf8mb4;Allow User Variables=true;AllowLoadLocalInfile=true"
"Default": "Data Source=182.43.18.151;Port=3306;Database=LionAbpProDemo9;uid=root;pwd=IFKy!Cufe3V9sYcz;charset=utf8mb4;Allow User Variables=true;AllowLoadLocalInfile=true"
},
"Hangfire": {
"Redis": {
"Host": "localhost:localhost,password=1q2w3E*",
"DB": "3"
"Host": "182.43.18.151:6379,password=1q2w3E*",
"DB": "2"
}
},
"Redis": {
"Configuration": "localhost:6379,password=1q2w3E*,defaultdatabase=3"
"Configuration": "182.43.18.151:6379,password=1q2w3E*,defaultdatabase=9"
},
"Jwt": {
"Audience": "Lion.AbpPro",

Loading…
Cancel
Save