Need help on creating an aspect which correctly calls and handles the result for both sync and async methods. #243
-
|
Hello @pamidur. Need help on creating an aspect which correctly calls and handles the result for both sync and async methods. I have created a sample containing the aspect which shows my use case. I need to create an aspect which executes code before and after a method. The sample aspect is similar to the LoggingAspect in the Aspects.Logging, with Console.WriteLine instead of logger for simplicity. I need to ensure that it works for both sync and async methods, so it looked like MethodWrapperAspect (from Aspects.Universal) handles both Sync and Async methods already, so I am using MethodWrapperAspect in SimpleLoggingAspect. Can you please help with this? In the sample application, when the GetSync (synchronous) action method is called the logs show the proper timing and messages are in the expected order. http://localhost:5028/WeatherForecast/GetSync
But when the GetAsync (asynchronous) action method is called, the timing are incorrect and the messages are out of order.
I tried with the LoggingAspect as well from the Aspects.Universal nuget package, and it also does not behave as per expectation with async methods. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 1 reply
-
|
Hey @apoonja-gwt , I thought it would be easy but then realized two methods were needlessly sealed for overriding. I still had to release as new version, this change basically got into the release. Do this:
make your attribute like this using System.Diagnostics;
using Aspects.Universal.Attributes;
using Aspects.Universal.Events;
namespace SampleAspectApp.Aspects;
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class SimpleLogAttribute : MethodAspectAttribute
{
public bool MeasureTime { get; } = false;
public string Prefix { get; } = string.Empty;
public SimpleLogAttribute(bool measureTime, string prefix)
{
MeasureTime = measureTime;
Prefix = prefix;
}
protected override T WrapSync<T>(Func<object[], T> target, object[] args, AspectEventArgs eventArgs)
{
T? result = default;
RunAction(() => { result = target(args); return Task.CompletedTask; }, eventArgs).GetAwaiter().GetResult();
return result!;
}
protected override async Task<T> WrapAsync<T>(Func<object[], Task<T>> target, object[] args, AspectEventArgs eventArgs)
{
T? result = default;
await RunAction(async () => { result = await target(args); }, eventArgs);
return result!;
}
private async Task RunAction(Func<Task> action, AspectEventArgs eventArgs)
{
void logMessage(string message) => Console.WriteLine($"{Prefix}::: {message}");
logMessage($"-----------------------------------------------");
logMessage($"Executing method {eventArgs.Name}");
if (MeasureTime)
{
var sw = new Stopwatch();
sw.Start();
await action();
sw.Stop();
logMessage($"Executed method {eventArgs.Name} in {sw.ElapsedMilliseconds} ms");
}
else
{
await action();
logMessage($"Executed method {eventArgs.Name}");
}
logMessage($"-----------------------------------------------");
}
}and update the lib in csproj <PackageReference Include="Aspects.Universal" Version="2.9.0" />
|
Beta Was this translation helpful? Give feedback.
-
|
1. You're keeping the same context while calling target method in
Wrap(A)Sync methods so you can use that to add state when(if) you need
globally by having a static ConcurrentDictionary in your Attribute. And
your attributes themselves can act as state holders for a single call. To
clarify trigger(attribute) object lifetime is per target method call. Not
super efficient but that's what it is
2. Honestly PerInstance aspect are hard to control, the reason is that you
can't know for sure what are of the instance boundaries at compile time,
e.g. what is the final inheritance hierarchy. So there might be pretty ugly
edge cases for PerInstance and I'd rather recommend to avoid it
…On Mon, 15 Sept 2025, 06:23 Amar, ***@***.***> wrote:
Thank you @pamidur <https://github.com/pamidur> . I really appreciate
your quick response in helping and addressing this.
Your answer does work as expected. I will try out some tests and mark it
as the answer soon.
- remove your aspect, leave only the attribute (Universal handles
aspects for you)
Have some questions regarding this:
It seems like the MethodWrapperAspect (which is injected by
MethodAspectAttribute) is marked as Global i.e. [Aspect(Scope.Global)].
Based on my understanding (which could be wrong), I thought I need to use
[Aspect(Scope.PerInstance)], which is why I thought to create a separate
aspect with PerInstance, since my project can make use of the same
Attribute/Aspect in multiple methods in multiple classes.
1. Would I not require [Aspect(Scope.PerInstance)] ? Please correct.
2. Is there any way to make my SimpleLogAttribute (in your answer
above) as PerInstance ?
—
Reply to this email directly, view it on GitHub
<#243 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AA7HZUHTFDGS5Q522TWZ4M33SWXDDAVCNFSM6AAAAACGLTDAPCVHI2DSMVQWIX3LMV43URDJONRXK43TNFXW4Q3PNVWWK3TUHMYTIMZZG43DSNY>
.
You are receiving this because you were mentioned.Message ID:
***@***.***
com>
|
Beta Was this translation helpful? Give feedback.



Hey @apoonja-gwt , I thought it would be easy but then realized two methods were needlessly sealed for overriding. I still had to release as new version, this change basically got into the release. Do this:
make your attribute like this