Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using AsyncInterceptor with Autofac #42

Open
blasferatu opened this issue May 8, 2018 · 20 comments
Open

Using AsyncInterceptor with Autofac #42

blasferatu opened this issue May 8, 2018 · 20 comments

Comments

@blasferatu
Copy link

I'm using Autofac and would like to use AsyncInterceptor. However, I cannot register a class that implements IAsyncInterceptor because Autofac expects a class that implements IInterceptor.

From the example in the Readme I don't understand how I could use AsyncInterceptor with Autofac. I thought of using a class that implements IInterceptor decorating a class that implements IAsyncInterceptor.
Then I would register the class implementing IInterceptor with Autofac which, in turn, would use the class implementing IAsyncInterceptor.

Am not sure how to do it or whether it can be done.

Any help would be appreciated.
Thank you.

@ndrwrbgs
Copy link
Collaborator

Hi @blasferatu,

You'll want to look at https://github.com/JSkimming/Castle.Core.AsyncInterceptor/blob/master/src/Castle.Core.AsyncInterceptor/ProxyGeneratorExtensions.cs which performs the bridge of accepting IAsyncInterceptor and passing it off to Castle, which expects IInterceptor.

Please check back if this isn't enough to keep you moving forward and we can try to provide an answer.

@ndrwrbgs
Copy link
Collaborator

(Specifically, I think you'll want to use AsyncDeterminationInterceptor) to bridge the two.

@blasferatu
Copy link
Author

Hi @ndrwrbgs,

I created a class named ExceptionInterceptor that implements IInterceptor which can be used with Autofac interception. This class, in turn, delegates Intercept method calls to the class that implements IAsyncInterceptor. Class ExceptionInterceptorAsync is the one actually doing the interception as I wanted, i.e. processing asynchronous metohds properly.

Finally, I registered everything with Autofac:

builder.RegisterType<ExceptionInterceptor>();

builder.RegisterType<ExceptionInterceptorAsync<().As<IAsyncInterceptor>();

Finally, the registration of a class for interception:

builder.RegisterType<SomeClass<().As<ISomeInterface>()
    .EnableClassInterceptors()
    .InterceptedBy(typeof(ExceptionInterceptor));

I think I got it right. I attached files with the code. It might be helpful for someone with the same doubts as me.

Thank you very much for your help and guidance.

AsyncInterception.zip

@tornike87
Copy link

Hi @blasferatu , @ndrwrbgs

I tried the above-mentioned method to implement an async interceptor. Interceptor has two purposes: to log execution information and to add the result to the cache.
Cache might be an external storage, so I want methods accessing cache to be async also.

Implementation behaves strangely, interceptor method is called multiple (I can not guess how many) times.

I created a simple console application. ITestService.Run is the method being intercepted. UniversalInterceptorAsync is the interceptor.

Could you please tell what I am doing wrong?
Thanks

InterceptorTestConsoleApp.zip

@blasferatu
Copy link
Author

Hi @tornike87,

I executed your code several times and the interceptor method was always called only once. Here is an example of the result in the console:

Hello World!
{
"Class": "InterceptorTestConsoleApp.Test.TestService",
"Method": "Run",
"FromCache": false,
"Arguments": [],
"ReturnValue": 380.0,
"Internal": {},
"InternalError": {},
"Exception": null,
"StartTime": "2018-05-30T16:57:09.4363968+01:00",
"EndTime": "2018-05-30T16:57:13.8954541+01:00",
"Flag": 0
}

I think this is the behaviour you want but are getting multiple results like the one above in a single execution.

Method InterceptAsynchronous<TResult>(IInvocation invocation), which in turn calls async Task<TResult> InternalInterceptAsynchronous<TResult>(IInvocation invocation) were both executed only once as expected. As a consequence, method Run in class TestService was executed only once as well.

I didn't find anything wrong with your code. I compared your implementation with mine and both seem right to me. I am sorry I have been unable to reproduce your problem and can't be more helpful.

Best regards.

@tornike87
Copy link

@blasferatu that is the behaviour I want, but it doesn't work that way always. We have the project where we need to use this interceptor and when we run it on some PC-s it does not finish writing log several minutes.

Can you vlean and debug it with visual studio? When I click F11 on invocation.proceed() it goes back to universal interceptor, makes several loops.

If you can not reproduce it I will try to play with it and make better example of this behaviour.

Thank you very much for your help.

@blasferatu
Copy link
Author

Hello @tornike87,

I cleaned the solution and executed your code repeatedly in Visual Studio 2017 Professional version 15.7.2 with breakpoints in class UniversalInterceptor's Intercept method and class UniversalInterceptorAsync's async Task<TResult> InternalInterceptAsynchronous<TResult>(IInvocation invocation) method.

When the code runs, _asyncInterceptor.ToInterceptor().Intercept(invocation); is executed only once in UniversalInterceptor's Intercept method and then method async Task<TResult> InternalInterceptAsynchronous<TResult>(IInvocation invocation) is executed in class UniversalInterceptorAsync only once also. When the code hits invocation.Proceed();, I press F11 and method Task<double> Run() in class TestService is executed normally. After that, the execution continues as expected and I get the desired result.

I have done this several times during the day and everything has worked correctly. I will keep trying and will let you know if I find anything new about this.

I do hope you can find what's wrong because I've not been successful in trying to help you.

Best regards.

@tornike87
Copy link

@blasferatu I made a simple change, inserted await Task.Delay(1000); in UniversalInterceptorAsync:158 method GetFromCacheAsync. When I debug or run the application it does not finish processing and log anything, If I move that line to line 171 in the method AddToCacheAsync it logs ExecuteInformation in every second and does not finish processing, I think it just goes to infinite loop. I uploaded source with this change.

Thank you very much for your time and effort!

InterceptorTestConsoleApp.zip

@blasferatu
Copy link
Author

@tornike87

I have exactly the same behaviour you describe when I run the latest code sample you provided.

When running again your first code sample, I found that

  • If I set a breakpoint in method async Task<object> GetObjectAsync(string key) in class InMemoryCache (line 94), wait a few seconds (e.g. 10 seconds just to make sure it's a long time) and then press F11 to continue, I get the behaviour you described when you first asked for help.

  • I also set a breakpoint in method async Task<TResult> InternalInterceptAsynchronous<TResult>(IInvocation invocation) in class UniversalInterceptorAsync in invocation.Proceed(); (line 97).
    With this set up, method Intercept(IInvocation invocation) in class UniversalInterceptor gets called more than once.

The only difference I see between your code and mine in the interceptor methods (e.g. async Task<TResult> InternalInterceptAsynchronous<TResult>(IInvocation invocation)) is that in those methods I don't have any asynchronous code before invocation.Proceed();. I wonder if this might be related to the problem.

Best regards.

@tornike87
Copy link

tornike87 commented Jun 1, 2018

@blasferatu You are absolutely right if I change Async methods when accessing the cache and use synchronous ones, the problem does not happen. I just want methods accessing the cache to be async and I wonder if it is possible. Also for some interceptors, I want to have some logic accessing some web services or other external resources and I want to know if it is possible to call async methods. Could it be related to the autofac registration or is it a general problem with this library?

Thank you very much!

@blasferatu
Copy link
Author

@tornike87,

I am afraid I don't know the answer to your question.

I can only add that in my interception methods I do have asynchronous code after invocation.Proceed();.

I wish someone more knowledgeable than me could provide more insight into this matter.

Best regards.

@ndrwrbgs
Copy link
Collaborator

ndrwrbgs commented Jun 1, 2018 via email

@ndrwrbgs
Copy link
Collaborator

ndrwrbgs commented Jun 1, 2018

Here are some suggestions I’ve used successfully with this limitation while we push for changes in Castle.

  • If your Before action can continue asynchronously and does not return a value used for proceed (eg logging) you can kick it off before await proceed and await it after
  • If your code is not production code or is a singleton style setup you can synchronously wait for the Before operation (Castle is wanting a synchronous return so while this would eat a Thread it would permit you run Before to completion and get its result)

@blasferatu
Copy link
Author

@ndrwrbgs,
Thank you for the information and suggestions.

@tornike87
Copy link

@ndrwrbgs, @blasferatu Thank you very much.

@ndrwrbgs Is there any thread regarding this problem in Castle?

Best regards.

@ndrwrbgs
Copy link
Collaborator

ndrwrbgs commented Jun 5, 2018

Relevant threads:
#25 - Original report
#26 - A PR that tried (but failed) to fix it locally
castleproject/Core#145 - A full discussion in the Castle project

Last update was we are really really hoping the Castle experts can make Async first-class support, though I believe they're hoping we can do it :(

@wswind
Copy link

wswind commented Feb 27, 2020

see this sample for autofac async interceptor with the help of AsyncDeterminationInterceptor and AsyncInterceptorBase:
https://github.com/wswind/Learn-AOP/tree/master/AutofacAsyncInterceptor

  1. create an adapter
public class AsyncInterceptorAdaper<TAsyncInterceptor> : AsyncDeterminationInterceptor
  where TAsyncInterceptor : IAsyncInterceptor
{
    public AsyncInterceptorAdaper(TAsyncInterceptor asyncInterceptor)
        : base(asyncInterceptor)
    { }
}
  1. create your async interceptor
public class CallLoggerAsyncInterceptor : AsyncInterceptorBase  
{
  ....
}
  1. relate the interceptor to interface
[Intercept(typeof(AsyncInterceptorAdaper<CallLoggerAsyncInterceptor>))]
public interface ISomeType
  1. register to IoC container
//register adapter
builder.RegisterGeneric(typeof(AsyncInterceptorAdaper<>));
//register async interceptor
builder.Register(c => new CallLoggerAsyncInterceptor(Console.Out));   

@yuzd
Copy link

yuzd commented Apr 28, 2020

CallLoggerAsyncInterceptor

how to asyncinterceptor for class ?

@wswind
Copy link

wswind commented Apr 28, 2020

CallLoggerAsyncInterceptor

how to asyncinterceptor for class ?

here is the demo code:
https://github.com/wswind/Learn-AOP/blob/master/AutofacAsyncInterceptor/CallLoggerAsyncInterceptor.cs

@newbe36524
Copy link

Hi @ndrwrbgs,

I created a class named ExceptionInterceptor that implements IInterceptor which can be used with Autofac interception. This class, in turn, delegates Intercept method calls to the class that implements IAsyncInterceptor. Class ExceptionInterceptorAsync is the one actually doing the interception as I wanted, i.e. processing asynchronous methods properly.

Finally, I registered everything with Autofac:

builder.RegisterType<ExceptionInterceptor>();

builder.RegisterType<ExceptionInterceptorAsync<().As<IAsyncInterceptor>();

Finally, the registration of a class for interception:

builder.RegisterType<SomeClass<().As<ISomeInterface>()
    .EnableClassInterceptors()
    .InterceptedBy(typeof(ExceptionInterceptor));

I think I got it right. I attached files with the code. It might be helpful for someone with the same doubts as me.

Thank you very much for your help and guidance.

AsyncInterception.zip

It should be ok if inherit AsyncInterceptorBase, IInterceptor at the same time. then just simply Intercept as below:

        public void Intercept(IInvocation invocation)
        {
            this.ToInterceptor().Intercept(invocation);
        }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants