From 058fe0fa75cf70ca4b88b875cbd28dfae61c16f2 Mon Sep 17 00:00:00 2001 From: zhifenglee-aelf Date: Wed, 9 Oct 2024 19:05:09 +0800 Subject: [PATCH] feat(package): updated package settings --- .github/workflows/publish-package-prod.yml | 60 ++++ .../.idea.AOPExceptionModule/.idea/.gitignore | 13 - ...ionModule.sln => AElf.ExceptionHandler.sln | 2 +- .../AElf.ExceptionHandler.csproj | 33 ++ AElf.ExceptionHandler/AOPExceptionModule.cs | 34 +++ AElf.ExceptionHandler/ExceptionHandler.cs | 288 ++++++++++++++++++ .../ExceptionHandlerAttribute.cs | 22 ++ AElf.ExceptionHandler/ExceptionHandlerInfo.cs | 7 + .../ExceptionHandlerInterceptor.cs | 35 +++ .../ExceptionHandlingStrategy.cs | 8 + AElf.ExceptionHandler/FlowBehavior.cs | 7 + AElf.ExceptionHandler/IInterceptor.cs | 6 + AElf.ExceptionHandler/MethodExecutionArgs.cs | 12 + AOPExceptionModule/AOPExceptionModule.csproj | 13 - AOPExceptionModule/BooClass.cs | 16 - AOPExceptionModule/ExceptionHandler.cs | 127 -------- .../ExceptionHandlingStrategy.cs | 27 -- AOPExceptionModule/FooClass.cs | 69 ----- AOPExceptionModule/Program.cs | 19 -- AOPExceptionModule/StaticClass.cs | 11 - README.md | 36 ++- 21 files changed, 542 insertions(+), 303 deletions(-) create mode 100644 .github/workflows/publish-package-prod.yml delete mode 100644 .idea/.idea.AOPExceptionModule/.idea/.gitignore rename AOPExceptionModule.sln => AElf.ExceptionHandler.sln (78%) create mode 100644 AElf.ExceptionHandler/AElf.ExceptionHandler.csproj create mode 100644 AElf.ExceptionHandler/AOPExceptionModule.cs create mode 100644 AElf.ExceptionHandler/ExceptionHandler.cs create mode 100644 AElf.ExceptionHandler/ExceptionHandlerAttribute.cs create mode 100644 AElf.ExceptionHandler/ExceptionHandlerInfo.cs create mode 100644 AElf.ExceptionHandler/ExceptionHandlerInterceptor.cs create mode 100644 AElf.ExceptionHandler/ExceptionHandlingStrategy.cs create mode 100644 AElf.ExceptionHandler/FlowBehavior.cs create mode 100644 AElf.ExceptionHandler/IInterceptor.cs create mode 100644 AElf.ExceptionHandler/MethodExecutionArgs.cs delete mode 100644 AOPExceptionModule/AOPExceptionModule.csproj delete mode 100644 AOPExceptionModule/BooClass.cs delete mode 100644 AOPExceptionModule/ExceptionHandler.cs delete mode 100644 AOPExceptionModule/ExceptionHandlingStrategy.cs delete mode 100644 AOPExceptionModule/FooClass.cs delete mode 100644 AOPExceptionModule/Program.cs delete mode 100644 AOPExceptionModule/StaticClass.cs diff --git a/.github/workflows/publish-package-prod.yml b/.github/workflows/publish-package-prod.yml new file mode 100644 index 0000000..4e2caf4 --- /dev/null +++ b/.github/workflows/publish-package-prod.yml @@ -0,0 +1,60 @@ +on: + push: + tags: + - 'v*' + +jobs: + publish: + runs-on: ubuntu-latest + env: + WORKING_DIRECTORY: AElf.ExceptionHandler/ + environment: prod + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 # Ensure the full history is fetched so we can check the commit history + + - name: Verify tag is on master branch + id: verify_tag + run: | + # Get the commit SHA of the tag + TAG_COMMIT=$(git rev-list -n 1 $GITHUB_REF) + # Check if the commit exists on the master branch + if git merge-base --is-ancestor $TAG_COMMIT origin/master; then + echo "Tag commit is on master branch." + echo "IS_ON_MASTER=true" >> $GITHUB_ENV + else + echo "Tag commit is not on master branch." + echo "IS_ON_MASTER=false" >> $GITHUB_ENV + fi + + - name: Stop if not on master + if: env.IS_ON_MASTER != 'true' + run: | + echo "This tag was not created from the master branch. Exiting." + exit 1 + + - name: Setup .NET + if: env.IS_ON_MASTER == 'true' + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '8.0.*' # Change this to the .NET version you're using + + - name: Extract version from tag + if: env.IS_ON_MASTER == 'true' + run: | + TAG_NAME=$(echo $GITHUB_REF | sed 's/refs\/tags\///') + VERSION=${TAG_NAME#v} + echo "VERSION=$VERSION" >> $GITHUB_ENV + + - name: Pack + if: env.IS_ON_MASTER == 'true' + working-directory: ${{ env.WORKING_DIRECTORY }} + run: dotnet pack --configuration Release --output nupkgs /p:Version=$VERSION + + - name: Publish NuGet packages + if: env.IS_ON_MASTER == 'true' + working-directory: ${{ env.WORKING_DIRECTORY }} + run: | + dotnet nuget push "nupkgs/*.nupkg" --api-key ${{ secrets.TEMPLATES_NUGET_API_KEY }} --source ${{ vars.TEMPLATES_NUGET_SOURCE_URL }} \ No newline at end of file diff --git a/.idea/.idea.AOPExceptionModule/.idea/.gitignore b/.idea/.idea.AOPExceptionModule/.idea/.gitignore deleted file mode 100644 index 041581c..0000000 --- a/.idea/.idea.AOPExceptionModule/.idea/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Rider ignored files -/.idea.AOPExceptionModule.iml -/contentModel.xml -/modules.xml -/projectSettingsUpdater.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/AOPExceptionModule.sln b/AElf.ExceptionHandler.sln similarity index 78% rename from AOPExceptionModule.sln rename to AElf.ExceptionHandler.sln index 6b33a69..04f804b 100644 --- a/AOPExceptionModule.sln +++ b/AElf.ExceptionHandler.sln @@ -1,6 +1,6 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOPExceptionModule", "AOPExceptionModule\AOPExceptionModule.csproj", "{0EE0548B-D1FF-4209-97EB-DAC11D0E210A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AElf.ExceptionHandler", "AElf.ExceptionHandler\AElf.ExceptionHandler.csproj", "{0EE0548B-D1FF-4209-97EB-DAC11D0E210A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/AElf.ExceptionHandler/AElf.ExceptionHandler.csproj b/AElf.ExceptionHandler/AElf.ExceptionHandler.csproj new file mode 100644 index 0000000..b8b28c5 --- /dev/null +++ b/AElf.ExceptionHandler/AElf.ExceptionHandler.csproj @@ -0,0 +1,33 @@ + + + + Contract + AElf.ExceptionHandler + AElf ExceptionHandler + AElf + An ExceptionHandler module for use in ABP and Orleans framework. + abp;module;grains;orleans + net8.0;net7.0 + enable + enable + https://github.com/AElfProject/AOPExceptionModule + git + https://github.com/AElfProject/AOPExceptionModule + AElf.ExceptionHandler + + + + + + + + + README.md + LICENSE + + + + + + + diff --git a/AElf.ExceptionHandler/AOPExceptionModule.cs b/AElf.ExceptionHandler/AOPExceptionModule.cs new file mode 100644 index 0000000..2ce47f6 --- /dev/null +++ b/AElf.ExceptionHandler/AOPExceptionModule.cs @@ -0,0 +1,34 @@ +using System.Collections.Concurrent; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.Modularity; + +namespace AElf.ExceptionHandler; + +public class AOPExceptionModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddTransient() + .AddTransient() + .AddSingleton>() + .AddSingleton>>(); + + context.Services.OnRegistered(options => + { + var methodInfos = options.ImplementationType.GetMethods(); + // Check if any of the class methods is decorated with the ExceptionHandlerAttribute + foreach (var methodInfo in methodInfos) + { + if (methodInfo.IsDefined(typeof(ExceptionHandlerAttribute), true)) + { + var result = options.Interceptors.TryAdd(); + if (!result) + { + throw new AbpException("ExceptionHandlingInterceptor is already registered."); + } + } + } + }); + } +} diff --git a/AElf.ExceptionHandler/ExceptionHandler.cs b/AElf.ExceptionHandler/ExceptionHandler.cs new file mode 100644 index 0000000..e46160c --- /dev/null +++ b/AElf.ExceptionHandler/ExceptionHandler.cs @@ -0,0 +1,288 @@ +using System.Collections.Concurrent; +using System.Linq.Expressions; +using System.Reflection; + +namespace AElf.ExceptionHandler; + +public class ExceptionHandler : IInterceptor +{ + private readonly ConcurrentDictionary MethodCache; + private readonly ConcurrentDictionary> FinallyCache; + private List? Attributes { get; set; } = null; + + public ExceptionHandler(ConcurrentDictionary methodCache, ConcurrentDictionary> finallyCache) + { + MethodCache = methodCache; + FinallyCache = finallyCache; + } + + public async Task InterceptAsync(MethodExecutionArgs args) + { + try + { + await args.Invocation(); + } + catch (Exception e) + { + OnException(e, args); + } + finally + { + OnFinally(args); + } + } + + private void OnFinally(MethodExecutionArgs args) + { + var attributes = GetAttributes(args); + + if(attributes == null) + { + return; + } + + foreach (var attribute in attributes) + { + if (HandleFinally(args, attribute)) + { + break; + } + } + } + + private void OnException(Exception ex, MethodExecutionArgs args) + { + var attributes = GetAttributes(args); + + if(attributes == null) + { + return; + } + + var handled = false; + + foreach (var attribute in attributes) + { + // If it's an AggregateException, iterate through inner exceptions + if (ex is AggregateException aggEx) + { + foreach (var innerEx in aggEx.InnerExceptions) + { + handled = HandleInnerException(innerEx, args, attribute); + } + if (handled) + { + break; + } + } + else + { + handled = HandleInnerException(ex, args, attribute); + if (handled) + { + break; + } + } + } + + if (!handled) + { + throw ex; + } + } + + private List? GetAttributes(MethodExecutionArgs args) + { + if (Attributes != null) + { + return Attributes; + } + + var attributes = args.MethodInfo.GetCustomAttributes() + .OrderBy(attr => attr.GetType().MetadataToken); + + if (attributes == null) + { + return null; + } + + var exceptionHandlerAttributes = attributes.ToList(); + + if(exceptionHandlerAttributes.Count == 0) + { + return null; + } + + Attributes = exceptionHandlerAttributes; + + return Attributes; + } + + private bool HandleFinally(MethodExecutionArgs args, ExceptionHandlerAttribute attribute) + { + if(attribute.FinallyMethodName == null || attribute.FinallyTargetType == null) + { + return false; + } + + var finallyMethod = GetFinallyMethod(attribute.FinallyTargetType, attribute.FinallyMethodName); + + var parameters = args.Arguments.ToArray(); + + var task = Task.Run(() => finallyMethod(args.TargetObject, parameters)); + task.Wait(); + + return true; + } + + private bool HandleInnerException(Exception exception, MethodExecutionArgs args, ExceptionHandlerAttribute attribute) + { + // If the exception is not of the specified type, return early + if (!attribute.Exceptions.Any(e => e.IsInstanceOfType(exception))) + { + return false; + } + + var exceptionHandlerInfo = GetExceptionHandlerInfo(attribute.TargetType, attribute.MethodName); + + var parameters = new object[] { exception }; + + if (!exceptionHandlerInfo.ContainsExceptionParamOnly) + { + parameters = parameters.Concat(args.Arguments.ToArray()).ToArray(); + } + + var task = Task.Run(() => exceptionHandlerInfo.Method(args.TargetObject, parameters)); + var flowBehavior = task.Result; + + if(task.Exception != null) + { + throw task.Exception; + } + + if(flowBehavior.ExceptionHandlingStrategy == ExceptionHandlingStrategy.Rethrow) + { + throw exception; + } + + if (flowBehavior.ReturnValue == null) + { + return true; + } + + if(flowBehavior.ExceptionHandlingStrategy == ExceptionHandlingStrategy.Throw) + { + throw (Exception)flowBehavior.ReturnValue!; + } + + args.ReturnValue = flowBehavior.ReturnValue; + return true; + } + + private ExceptionHandlerInfo GetExceptionHandlerInfo(Type targetType, string methodName) + { + var cacheKey = CacheKey(targetType, methodName); + + // Try to get the method delegate from the cache + if (MethodCache.TryGetValue(cacheKey, out var exceptionHandlerInfo)) + { + // If the delegate exists in the cache, return early + return exceptionHandlerInfo; + } + + exceptionHandlerInfo = CreateExceptionHandlerInfo(targetType, methodName); + + // Cache the compiled delegate + MethodCache.TryAdd(cacheKey, exceptionHandlerInfo); + + return exceptionHandlerInfo; + } + + private Func GetFinallyMethod(Type targetType, string methodName) + { + var cacheKey = CacheKey(targetType, methodName); + + // Try to get the method delegate from the cache + if (FinallyCache.TryGetValue(cacheKey, out var finallyMethod)) + { + // If the delegate exists in the cache, return early + return finallyMethod; + } + + finallyMethod = CreateFinallyMethod(targetType, methodName); + + // Cache the compiled delegate + FinallyCache.TryAdd(cacheKey, finallyMethod); + + return finallyMethod; + } + + private static string CacheKey(Type targetType, string methodName) + { + return $"{targetType.FullName}.{methodName}"; + } + + private static ExceptionHandlerInfo CreateExceptionHandlerInfo(Type targetType, string methodName) + { + Func> methodToCall; + var methodInfo = targetType.GetMethod(methodName); + var methodParams = methodInfo.GetParameters(); + + var instanceParameter = Expression.Parameter(typeof(object), "instance"); + var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); + + // We will prepend the exception to the parameters array directly + var convertedParams = methodParams.Select((param, index) => + Expression.Convert( + Expression.ArrayIndex(parametersParameter, Expression.Constant(index)), + param.ParameterType)).ToArray(); + + var instanceCast = methodInfo.IsStatic ? null : Expression.Convert(instanceParameter, targetType); + + var methodCall = methodInfo.IsStatic + ? Expression.Call(methodInfo, convertedParams) + : Expression.Call(instanceCast, methodInfo, convertedParams); + + var lambda = Expression.Lambda>>( + Expression.Convert(methodCall, typeof(Task)), + instanceParameter, parametersParameter); + + methodToCall = lambda.Compile(); + + return new ExceptionHandlerInfo() + { + Method = methodToCall, + ContainsExceptionParamOnly = methodParams.Length == 1 && methodParams[0].ParameterType == typeof(Exception) + }; + } + + private static Func CreateFinallyMethod(Type targetType, string methodName) + { + Func methodToCall; + var methodInfo = targetType.GetMethod(methodName); + var methodParams = methodInfo.GetParameters(); + + var instanceParameter = Expression.Parameter(typeof(object), "instance"); + var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); + + // We will prepend the exception to the parameters array directly + var convertedParams = methodParams.Select((param, index) => + Expression.Convert( + Expression.ArrayIndex(parametersParameter, Expression.Constant(index)), + param.ParameterType)).ToArray(); + + var instanceCast = methodInfo.IsStatic ? null : Expression.Convert(instanceParameter, targetType); + + var methodCall = methodInfo.IsStatic + ? Expression.Call(methodInfo, convertedParams) + : Expression.Call(instanceCast, methodInfo, convertedParams); + + var lambda = Expression.Lambda>( + Expression.Convert(methodCall, typeof(Task)), + instanceParameter, parametersParameter); + + methodToCall = lambda.Compile(); + + return methodToCall; + } +} \ No newline at end of file diff --git a/AElf.ExceptionHandler/ExceptionHandlerAttribute.cs b/AElf.ExceptionHandler/ExceptionHandlerAttribute.cs new file mode 100644 index 0000000..f3d5d16 --- /dev/null +++ b/AElf.ExceptionHandler/ExceptionHandlerAttribute.cs @@ -0,0 +1,22 @@ +namespace AElf.ExceptionHandler; + +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public class ExceptionHandlerAttribute : Attribute +{ + public Type TargetType { get; set; } + public string MethodName { get; set; } + public Type[] Exceptions { get; set; } + public Type? FinallyTargetType { get; set; } = null; + public string? FinallyMethodName { get; set; } = null; + + public ExceptionHandlerAttribute(params Type [] exceptions) + { + // loop through to check if all types are exceptions + if (exceptions.Any(exception => !typeof(Exception).IsAssignableFrom(exception))) + { + throw new ArgumentException("All types must be exceptions"); + } + + Exceptions = exceptions; + } +} \ No newline at end of file diff --git a/AElf.ExceptionHandler/ExceptionHandlerInfo.cs b/AElf.ExceptionHandler/ExceptionHandlerInfo.cs new file mode 100644 index 0000000..70e7a9e --- /dev/null +++ b/AElf.ExceptionHandler/ExceptionHandlerInfo.cs @@ -0,0 +1,7 @@ +namespace AElf.ExceptionHandler; + +public class ExceptionHandlerInfo +{ + public Func> Method { get; set; } + public bool ContainsExceptionParamOnly { get; set; } +} \ No newline at end of file diff --git a/AElf.ExceptionHandler/ExceptionHandlerInterceptor.cs b/AElf.ExceptionHandler/ExceptionHandlerInterceptor.cs new file mode 100644 index 0000000..c914f13 --- /dev/null +++ b/AElf.ExceptionHandler/ExceptionHandlerInterceptor.cs @@ -0,0 +1,35 @@ +using Volo.Abp.DynamicProxy; + +namespace AElf.ExceptionHandler; + +public class ExceptionHandlerInterceptor : AbpInterceptor +{ + private readonly IInterceptor _interceptor; + + public ExceptionHandlerInterceptor(IInterceptor interceptor) + { + _interceptor = interceptor; + } + + public override async Task InterceptAsync(IAbpMethodInvocation invocation) + { + var methodExecutionArgs = new MethodExecutionArgs + { + TargetObject = invocation.TargetObject, + MethodInfo = invocation.Method, + Arguments = invocation.Arguments, + ReturnValue = null, + Invocation = async () => + { + await invocation.ProceedAsync(); + } + }; + + await _interceptor.InterceptAsync(methodExecutionArgs); + + if (methodExecutionArgs.ReturnValue != null) + { + invocation.ReturnValue = methodExecutionArgs.ReturnValue; + } + } +} diff --git a/AElf.ExceptionHandler/ExceptionHandlingStrategy.cs b/AElf.ExceptionHandler/ExceptionHandlingStrategy.cs new file mode 100644 index 0000000..8d15683 --- /dev/null +++ b/AElf.ExceptionHandler/ExceptionHandlingStrategy.cs @@ -0,0 +1,8 @@ +namespace AElf.ExceptionHandler; + +public enum ExceptionHandlingStrategy +{ + Rethrow, + Return, + Throw, +} \ No newline at end of file diff --git a/AElf.ExceptionHandler/FlowBehavior.cs b/AElf.ExceptionHandler/FlowBehavior.cs new file mode 100644 index 0000000..26aef43 --- /dev/null +++ b/AElf.ExceptionHandler/FlowBehavior.cs @@ -0,0 +1,7 @@ +namespace AElf.ExceptionHandler; + +public class FlowBehavior +{ + public ExceptionHandlingStrategy ExceptionHandlingStrategy { get; set; } + public object? ReturnValue { get; set; } +} \ No newline at end of file diff --git a/AElf.ExceptionHandler/IInterceptor.cs b/AElf.ExceptionHandler/IInterceptor.cs new file mode 100644 index 0000000..d9df855 --- /dev/null +++ b/AElf.ExceptionHandler/IInterceptor.cs @@ -0,0 +1,6 @@ +namespace AElf.ExceptionHandler; + +public interface IInterceptor +{ + Task InterceptAsync(MethodExecutionArgs args); +} \ No newline at end of file diff --git a/AElf.ExceptionHandler/MethodExecutionArgs.cs b/AElf.ExceptionHandler/MethodExecutionArgs.cs new file mode 100644 index 0000000..b10c664 --- /dev/null +++ b/AElf.ExceptionHandler/MethodExecutionArgs.cs @@ -0,0 +1,12 @@ +using System.Reflection; + +namespace AElf.ExceptionHandler; + +public class MethodExecutionArgs +{ + public object TargetObject { get; set; } + public MethodInfo MethodInfo { get; set; } + public object[] Arguments { get; set; } + public object? ReturnValue { get; set; } + public Func Invocation { get; set; } +} \ No newline at end of file diff --git a/AOPExceptionModule/AOPExceptionModule.csproj b/AOPExceptionModule/AOPExceptionModule.csproj deleted file mode 100644 index fe70be2..0000000 --- a/AOPExceptionModule/AOPExceptionModule.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - - - - diff --git a/AOPExceptionModule/BooClass.cs b/AOPExceptionModule/BooClass.cs deleted file mode 100644 index 5d4a2df..0000000 --- a/AOPExceptionModule/BooClass.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace AOPExceptionModule; - -public class BooClass -{ - public async Task Execute(int i) - { - await Task.Delay(100); - - // Simulate an exception - if (i % 2 == 0) - { - throw new ArgumentException($"Boo Argument exception {i}!"); - } - throw new InvalidOperationException($"Boo Invalid Operation exception {i}!"); - } -} \ No newline at end of file diff --git a/AOPExceptionModule/ExceptionHandler.cs b/AOPExceptionModule/ExceptionHandler.cs deleted file mode 100644 index e73e40a..0000000 --- a/AOPExceptionModule/ExceptionHandler.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System.Collections.Concurrent; -using System.Linq.Expressions; -using PostSharp.Aspects; -using PostSharp.Serialization; - -namespace AOPExceptionModule; - -[PSerializable] -public class ExceptionHandler : OnMethodBoundaryAspect -{ - private class MethodInfo - { - public Func> Method { get; set; } - public bool ContainsExceptionParamOnly { get; set; } - } - - public Type TargetType { get; set; } - public string MethodName { get; set; } - private Type[] Exceptions { get; set; } - - private static readonly ConcurrentDictionary MethodCache = new(); - - public ExceptionHandler(params Type [] exceptions) - { - // loop through to check if all types are exceptions - if (exceptions.Any(exception => !typeof(Exception).IsAssignableFrom(exception))) - { - throw new ArgumentException("All types must be exceptions"); - } - - Exceptions = exceptions; - //SemanticallyAdvisedMethodKinds = SemanticallyAdvisedMethodKinds.None; - } - - public override void OnException(MethodExecutionArgs args) - { - // If it's an AggregateException, iterate through inner exceptions - if (args.Exception is AggregateException aggEx) - { - foreach (var innerEx in aggEx.InnerExceptions) - { - HandleInnerException(innerEx, args); - } - } - else - { - HandleInnerException(args.Exception, args); - } - } - - private void HandleInnerException(Exception exception, MethodExecutionArgs args) - { - // If the exception is not of the specified type, return early - if (!Exceptions.Any(e => e.IsInstanceOfType(exception))) - { - return; - } - - var methodInfo = GetMethodInfo(TargetType, MethodName); - - var parameters = new object[] { args.Exception }; - - if (!methodInfo.ContainsExceptionParamOnly) - { - parameters = parameters.Concat(args.Arguments.ToArray()).ToArray(); - } - - var strategyTask = Task.Run(() => methodInfo.Method(args.Instance, parameters)); - var strategy = strategyTask.Result; - - // Apply the ExceptionHandlingStrategy returned by the delegate - args.FlowBehavior = strategy.ToFlowBehavior(); - } - - private MethodInfo GetMethodInfo(Type targetType, string methodName) - { - var cacheKey = $"{targetType.FullName}.{methodName}"; - - // Try to get the method delegate from the cache - if (MethodCache.TryGetValue(cacheKey, out var methodInfo)) - { - // If the delegate exists in the cache, return early - return methodInfo; - } - - methodInfo = CreateMethodInfo(targetType, methodName); - - // Cache the compiled delegate - MethodCache.TryAdd(cacheKey, methodInfo); - - return methodInfo; - } - - private static MethodInfo CreateMethodInfo(Type targetType, string methodName) - { - Func> methodToCall; - var methodInfo = targetType.GetMethod(methodName); - var methodParams = methodInfo.GetParameters(); - - var instanceParameter = Expression.Parameter(typeof(object), "instance"); - var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); - - // We will prepend the exception to the parameters array directly - var convertedParams = methodParams.Select((param, index) => - Expression.Convert( - Expression.ArrayIndex(parametersParameter, Expression.Constant(index)), - param.ParameterType)).ToArray(); - - var instanceCast = methodInfo.IsStatic ? null : Expression.Convert(instanceParameter, targetType); - - var methodCall = methodInfo.IsStatic - ? Expression.Call(methodInfo, convertedParams) - : Expression.Call(instanceCast, methodInfo, convertedParams); - - var lambda = Expression.Lambda>>( - Expression.Convert(methodCall, typeof(Task)), - instanceParameter, parametersParameter); - - methodToCall = lambda.Compile(); - - return new MethodInfo - { - Method = methodToCall, - ContainsExceptionParamOnly = methodParams.Length == 1 && methodParams[0].ParameterType == typeof(Exception) - }; - } -} \ No newline at end of file diff --git a/AOPExceptionModule/ExceptionHandlingStrategy.cs b/AOPExceptionModule/ExceptionHandlingStrategy.cs deleted file mode 100644 index fd7b329..0000000 --- a/AOPExceptionModule/ExceptionHandlingStrategy.cs +++ /dev/null @@ -1,27 +0,0 @@ -using PostSharp.Aspects; - -namespace AOPExceptionModule; - -public enum ExceptionHandlingStrategy -{ - Rethrow, - Continue, - Return, - Default, - ThrowException -} - -public static class ExceptionHandlingStrategyExtensions -{ - public static FlowBehavior ToFlowBehavior(this ExceptionHandlingStrategy strategy) - { - return strategy switch - { - ExceptionHandlingStrategy.Rethrow => FlowBehavior.RethrowException, - ExceptionHandlingStrategy.Continue => FlowBehavior.Continue, - ExceptionHandlingStrategy.Return => FlowBehavior.Return, - ExceptionHandlingStrategy.ThrowException => FlowBehavior.ThrowException, - _ => FlowBehavior.Default - }; - } -} \ No newline at end of file diff --git a/AOPExceptionModule/FooClass.cs b/AOPExceptionModule/FooClass.cs deleted file mode 100644 index 59f3677..0000000 --- a/AOPExceptionModule/FooClass.cs +++ /dev/null @@ -1,69 +0,0 @@ -namespace AOPExceptionModule; - -public class BooData -{ - public string Message = "Boo!"; - public int Value = 100; -} - -public class FooClass -{ - private readonly int _value; - private readonly BooClass _boo = new BooClass(); - - public FooClass(int value) - { - _value = value; - } - - public async Task Execute(int index) - { - Console.WriteLine("Hello, World!"); - - await Task.Delay(100); - - for(int i = 0; i < 10; i++) - { - await BooExecute(i); - } - // Simulate an exception - //throw new Exception("Test exception"); - } - - [ExceptionHandler(typeof(Exception), - TargetType = typeof(FooClass), - MethodName = nameof(HandleException))] - [ExceptionHandler([typeof(ArgumentException), typeof(InvalidOperationException)], - TargetType = typeof(StaticClass), - MethodName = nameof(StaticClass.HandleException))] - private async Task BooExecute(int i) - { - await _boo.Execute(i); - } - - - public async Task HandleException(Exception ex, int index) - { - Console.WriteLine($"Handled exception with index: {_value - index} with exception: {ex.Message}"); - await Task.Delay(100); - return ExceptionHandlingStrategy.Continue; - } - - [ExceptionHandler(typeof(Exception), TargetType = typeof(FooClass), MethodName = nameof(HandleBooException))] - public async Task ExecuteBoo(int index, BooData data) - { - Console.WriteLine("Hello, World!"); - - await Task.Delay(100); - // Simulate an exception - throw new Exception("Test exception"); - } - - public async Task HandleBooException(Exception ex, int index, BooData boo) - { - Console.WriteLine($"Boo Handled exception with index: {_value - index} with exception: {ex.Message}"); - Console.WriteLine($"Boo data: {boo.Message}, {boo.Value}"); - await Task.Delay(100); - return ExceptionHandlingStrategy.Continue; - } -} \ No newline at end of file diff --git a/AOPExceptionModule/Program.cs b/AOPExceptionModule/Program.cs deleted file mode 100644 index cbd73e5..0000000 --- a/AOPExceptionModule/Program.cs +++ /dev/null @@ -1,19 +0,0 @@ -using AOPExceptionModule; - -public class Program -{ - public static async Task Main(string[] args) - { - var foo = new FooClass(50); - - /*await foo.ExecuteBoo(42, new BooData - { - Message = "Boo! Boo!", - Value = 200 - });*/ - - await foo.Execute(40); - - Console.WriteLine("End of program."); - } -} \ No newline at end of file diff --git a/AOPExceptionModule/StaticClass.cs b/AOPExceptionModule/StaticClass.cs deleted file mode 100644 index 60d2fdb..0000000 --- a/AOPExceptionModule/StaticClass.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace AOPExceptionModule; - -public static class StaticClass -{ - public static async Task HandleException(Exception e) - { - Console.WriteLine($"Static class handled exception: {e.Message}"); - await Task.Delay(100); - return ExceptionHandlingStrategy.Return; - } -} \ No newline at end of file diff --git a/README.md b/README.md index ccbcd3b..ba69e23 100644 --- a/README.md +++ b/README.md @@ -48,16 +48,29 @@ Cons: ## Getting Started -Simply run the program to see the demo. +### Setup + +Add the following dependency to your project's Module class: + +```cs +using AElf.ExceptionHandler; + +[DependsOn( + typeof(AOPExceptionModule) +)] +public class MyTemplateModule : AbpModule +``` + +This will automatically register the AOPException module and setup your project for instrumentation. To use the Aspect: -1. Define a Method Returning ExceptionHandlingStrategy: - Create a method in your target class that handles exceptions and returns a Task. The strategy will dictate how the flow of the program should behave (e.g., retry, rethrow, suppress). +1. Define a Method Returning `FlowBehavior`: + Create a method in your target class that handles exceptions and returns a Task. The strategy will dictate how the flow of the program should behave (e.g., return, rethrow, throw). ```csharp public class ExceptionHandlingService { - public static async Task HandleException(Exception ex, int i) + public static async Task HandleException(Exception ex, int i) { Console.WriteLine($"Handled exception: {ex.Message}"); await Task.Delay(100); @@ -70,7 +83,7 @@ public class ExceptionHandlingService Use the ExceptionHandler aspect on the methods where you want to handle exceptions. ```csharp -[ExceptionHandler(TargetType = typeof(ExceptionHandlingService), MethodName = nameof(ExceptionHandlingService.HandleException), Exception = typeof(ArgumentNullException))] +[ExceptionHandler(typeof(ArgumentNullException), TargetType = typeof(ExceptionHandlingService), MethodName = nameof(ExceptionHandlingService.HandleException))] public void SomeMethod(int i) { // Business logic that may throw exceptions @@ -81,8 +94,17 @@ public void SomeMethod(int i) Example with multiple exception handler: ```csharp -[ExceptionHandler(TargetType = typeof(ExceptionHandlingService), MethodName = nameof(ExceptionHandlingService.HandleException), Exception = typeof(InvalidOperationException))] -[ExceptionHandler(TargetType = typeof(ExceptionHandlingService), MethodName = nameof(ExceptionHandlingService.HandleException), Exception = typeof(ArgumentNullException))] +[ExceptionHandler(typeof(InvalidOperationException), TargetType = typeof(ExceptionHandlingService), MethodName = nameof(ExceptionHandlingService.HandleException))] +[ExceptionHandler(typeof(ArgumentNullException), TargetType = typeof(ExceptionHandlingService), MethodName = nameof(ExceptionHandlingService.HandleException))] +public void SomeMethod(int i) +{ + // Business logic that may throw exceptions +} +``` + +Or you can have multiple Exceptions: +```csharp +[ExceptionHandler([typeof(ArgumentNullException), typeof(InvalidOperationException)], TargetType = typeof(ExceptionHandlingService), MethodName = nameof(ExceptionHandlingService.HandleException))] public void SomeMethod(int i) { // Business logic that may throw exceptions