0

I have a method:

public virtual async Task<IActionResult> GetEmployees([HttpTrigger(AuthorizationLevel.Admin, "get", Route = null)] HttpRequest req) {
  return OkObjectResult(null);
}

I know I can intercept this synchronously using autofac:

public class CallLogger : IInterceptor
{
  TextWriter _output;

  public CallLogger(TextWriter output)
  {
    _output = output;
  }

 public void Intercept(IInvocation invocation)
  {
    _output.Write("Calling method {0} with parameters {1}... ",
      invocation.Method.Name,
      string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray()));

    invocation.Proceed();

    _output.WriteLine("Done: result was {0}.", invocation.ReturnValue);
  }
}

But how can I do this asynchronously while potentially overwriting the result the method I'm intercepting returns? It currently returns an OkObjectResult, I may want to return a 404 instead, for example.

Psuedo Code

public async Task Intercept(IInvocation invocation)
{
    var myAsyncResult = await _myAsyncClass.MyAsyncMethod();

    if (myAsyncResult == expected)
    {
       invocation.Proceed();
    }
    else
    {
       invocation.ReturnValue = // some overwrite of the value - and don't proceed with the invocation.
    }
}

Note

I know there are clever approaches to async in autofac, but this doesn't allow me to prevent execution of the original method and overwrite the value, instead I need to 'proceed' the invocation and use it's return: https://stackoverflow.com/a/39784559/12683473

JᴀʏMᴇᴇ
  • 218
  • 2
  • 15

1 Answers1

1

The main thing to keep in mind with Castle.Core is that you cannot call IInvocation.Proceed after an await because the IInvocation instance is reused after the interceptor (synchronously) returns.

However, modern versions of Castle.Core do support IInvocation.CaptureProceedInfo, which can be used as such:

public class CallLogger : IInterceptor
{
  public void Intercept(IInvocation invocation)
  {
    invocation.ReturnValue = InterceptAsync<MyResult>(invocation.CaptureProceedInfo());
  }

  private async Task<TResult> InterceptAsync<TResult>(IInvocationProceedInfo proceed)
  {
    var myAsyncResult = await _myAsyncClass.MyAsyncMethod();

    if (myAsyncResult == expected)
    {
      proceed.Invoke();
    }
    else
    {
      return ...;
    }
  }
}

This simple code assumes your code can specify a type MyResult that is the known result of the intercepted method. The more general case requires reflection and TaskCompletionSource<T>.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thanks for the reply mate! Much appreciated. I'm confused, though - not all of those code paths return a value. I'm assuming my `return ...;` is my overwrite (i.e. the value the original invocation wouldn't have returned, it's some value I'm returning on the fly). But how do I return the value from the original invocation if necessary? Thanks. – JᴀʏMᴇᴇ Jul 30 '20 at 08:50
  • @JᴀʏMᴇᴇ: Good catch; I did do that wrong. You'd need to pass `IInvocation` in to `InterceptAsync` and then use `ReturnValue` (cast to `Task`) after the `Invoke`. See the [source of `AsyncInterceptor` for details](https://github.com/stakx/DynamicProxy.AsyncInterceptor). – Stephen Cleary Jul 30 '20 at 21:00