diff --git a/aspnet-core/Directory.Build.targets b/aspnet-core/Directory.Build.targets index a0540f2e..7b6f751f 100644 --- a/aspnet-core/Directory.Build.targets +++ b/aspnet-core/Directory.Build.targets @@ -46,12 +46,12 @@ - - - - - - + + + + + + diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/EfCoreAbpProCapTransactionApiFactory.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/EfCoreAbpProCapTransactionApiFactory.cs index 71482e3a..695fa474 100644 --- a/aspnet-core/frameworks/src/Lion.AbpPro.CAP.EntityFrameworkCore/Lion/AbpPro/CAP/EntityFrameworkCore/EfCoreAbpProCapTransactionApiFactory.cs +++ b/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); } diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCAPMessageExtensions.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCAPMessageExtensions.cs new file mode 100644 index 00000000..fc407abb --- /dev/null +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCAPMessageExtensions.cs @@ -0,0 +1,35 @@ +namespace Lion.AbpPro.CAP; + +public static class AbpProCAPMessageExtensions +{ + /// + /// 尝试获取消息标头中的租户标识 + /// + 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; + } + + /// + /// 获取消息标头中的租户标识 + /// + public static Guid? GetTenantIdOrNull(this Message message) + { + if (message.TryGetTenantId(out Guid? tenantId)) + { + return tenantId; + } + + return null; + } +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCAPSubscribeInvoker.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCAPSubscribeInvoker.cs new file mode 100644 index 00000000..c05487fa --- /dev/null +++ b/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 _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(); + } + + public async Task 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(); + 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 ExecuteWithParameterAsync(ObjectMethodExecutor executor, object @class, + object?[] parameter) + { + if (executor.IsMethodAsync) return await executor.ExecuteAsync(@class, parameter); + + return executor.Execute(@class, parameter); + } +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapConsts.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapConsts.cs new file mode 100644 index 00000000..cd768b98 --- /dev/null +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapConsts.cs @@ -0,0 +1,9 @@ +namespace Lion.AbpPro.CAP; + +public class AbpProCapConsts +{ + /// + /// 租户字符串 + /// + public const string Tenant = "__tenant"; +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapDistributedEventBus.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapDistributedEventBus.cs index a270004b..2d179c21 100644 --- a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapDistributedEventBus.cs +++ b/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 + { + { AbpProCapConsts.Tenant, CurrentTenant.Id?.ToString() ?? string.Empty }, + }; + await CapPublisher.PublishAsync(eventName, eventData, header); } protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord) diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapPublisherExtension.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapPublisherExtension.cs index 2e1d66fa..9be67890 100644 --- a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapPublisherExtension.cs +++ b/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); } } \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapUnitOfWork.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapUnitOfWork.cs index 8e16efbd..31789746 100644 --- a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/AbpProCapUnitOfWork.cs +++ b/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); diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/AbpProCapHeader.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/AbpProCapHeader.cs new file mode 100644 index 00000000..df19b311 --- /dev/null +++ b/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 +{ + internal IDictionary? ResponseHeader { get; set; } + + public AbpProCapHeader(IDictionary dictionary) : base(dictionary) + { + } + + /// + /// When a callbackName is specified from publish message, use this method to add an additional header. + /// + /// The response header key. + /// The response header value. + public void AddResponseHeader(string key, string? value) + { + ResponseHeader ??= new Dictionary(); + ResponseHeader[key] = value; + } + + /// + /// When a callbackName is specified from publish message, use this method to abort the callback. + /// + public void RemoveCallback() + { + Dictionary.Remove(Headers.CallbackName); + } + + /// + /// When a callbackName is specified from Publish message, use this method to rewrite the callback name. + /// + /// The new callback name. + public void RewriteCallback(string callbackName) + { + Dictionary[Headers.CallbackName] = callbackName; + } +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/AwaitableInfo.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/AwaitableInfo.cs new file mode 100644 index 00000000..51f6cbb8 --- /dev/null +++ b/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; + } +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/CoercedAwaitableInfo.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/CoercedAwaitableInfo.cs new file mode 100644 index 00000000..622ffa39 --- /dev/null +++ b/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. + 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; + } +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutor.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutor.cs new file mode 100644 index 00000000..e5227862 --- /dev/null +++ b/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), // getAwaiterMethod + typeof(Func), // isCompletedMethod + typeof(Func), // getResultMethod + typeof(Action), // onCompletedMethod + typeof(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 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); + } + + /// + /// Executes the configured method on . This can be used whether or not + /// the configured method is asynchronous. + /// + /// + /// 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. + /// + /// The object whose method is to be executed. + /// Parameters to pass to the method. + /// The method return value. + public object? Execute(object target, object?[]? parameters) + { + Debug.Assert(_executor != null, "Sync execution is not supported."); + return _executor(target, parameters); + } + + /// + /// Executes the configured method on . This can only be used if the configured + /// method is asynchronous. + /// + /// + /// 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). + /// + /// The object whose method is to be executed. + /// Parameters to pass to the method. + /// An object that you can "await" to get the method return value. + 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(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(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(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(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>( + 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>( + Expression.MakeMemberAccess( + Expression.Convert(isCompletedParam, awaitableInfo.AwaiterType), + awaitableInfo.AwaiterIsCompletedProperty), + isCompletedParam).Compile(); + + var getResultParam = Expression.Parameter(typeof(object), "awaiter"); + Func 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>( + 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>( + 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>( + Expression.Call( + Expression.Convert(onCompletedParam1, awaitableInfo.AwaiterType), + awaitableInfo.AwaiterOnCompletedMethod, + onCompletedParam2), + onCompletedParam1, + onCompletedParam2).Compile(); + + 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>( + 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))); + + var lambda = + Expression.Lambda(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); +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs new file mode 100644 index 00000000..d8897933 --- /dev/null +++ b/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; + +/// +/// Provides a common awaitable structure that can +/// return, regardless of whether the underlying value is a System.Task, an FSharpAsync, or an +/// application-defined custom awaitable. +/// +internal readonly struct ObjectMethodExecutorAwaitable +{ + private readonly object _customAwaitable; + private readonly Func _getAwaiterMethod; + private readonly Func _isCompletedMethod; + private readonly Func _getResultMethod; + private readonly Action _onCompletedMethod; + private readonly 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). + // [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 + // or other value-typed awaitables. + + public ObjectMethodExecutorAwaitable( + object customAwaitable, + Func getAwaiterMethod, + Func isCompletedMethod, + Func getResultMethod, + Action onCompletedMethod, + 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 _isCompletedMethod; + private readonly Func _getResultMethod; + private readonly Action _onCompletedMethod; + private readonly Action _unsafeOnCompletedMethod; + + public Awaiter( + object customAwaiter, + Func isCompletedMethod, + Func getResultMethod, + Action onCompletedMethod, + 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); + } + } +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs new file mode 100644 index 00000000..bde049b4 --- /dev/null +++ b/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; + +/// +/// Helper for detecting whether a given type is FSharpAsync`1, and if so, supplying +/// an for mapping instances of that type to a C# awaitable. +/// +/// +/// 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. +/// +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( + // (Microsoft.FSharp.Control.FSharpAsync)fsharpAsync, + // FSharpOption.None, + // FSharpOption.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.None + var fsharpOptionOfTaskCreationOptionsType = fsharpOptionType + .MakeGenericType(typeof(TaskCreationOptions)); + _fsharpOptionOfTaskCreationOptionsNoneProperty = fsharpOptionOfTaskCreationOptionsType + .GetRuntimeProperty("None"); + + // Get a reference to FSharpOption.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); + } +} \ No newline at end of file diff --git a/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/readme.md b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/readme.md new file mode 100644 index 00000000..a85d4b16 --- /dev/null +++ b/aspnet-core/frameworks/src/Lion.AbpPro.CAP/Lion/AbpPro/CAP/Internal/readme.md @@ -0,0 +1,2 @@ +这个目录下是cap的源码 +因为cap的源码的访问级别是internal,扩展的时候无法访问,所以直接把源码拷贝过来。 \ No newline at end of file diff --git a/aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/appsettings.json b/aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/appsettings.json index 9df02e23..0e8987ba 100644 --- a/aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/appsettings.json +++ b/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",