Skip to content

Instantly share code, notes, and snippets.

@stakx
Last active January 10, 2021 22:06
Show Gist options
  • Save stakx/26fd79d5c59f90067e439bf2927e8982 to your computer and use it in GitHub Desktop.
Save stakx/26fd79d5c59f90067e439bf2927e8982 to your computer and use it in GitHub Desktop.
AsyncInterceptor for Castle DynamicProxy, assuming that IInvocation.GetProceedInfo() is available. Doesn't yet support exceptions.
public abstract class AsyncInterceptor : IInterceptor
{
private static readonly MethodInfo transitionMethod =
typeof(AsyncInterceptor).GetMethod(nameof(TransitionToAsync), BindingFlags.NonPublic | BindingFlags.Instance);
protected AsyncInterceptor()
{
}
void IInterceptor.Intercept(IInvocation invocation)
{
var returnType = invocation.Method.ReturnType;
if (returnType == typeof(Task))
{
invocation.ReturnValue = this.InterceptAsync(new AsyncInvocation(invocation, false));
}
else if (returnType.BaseType == typeof(Task))
{
Debug.Assert(returnType.IsConstructedGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>));
var resultType = returnType.GetGenericArguments()[0];
invocation.ReturnValue = transitionMethod.MakeGenericMethod(resultType).Invoke(this, new object[] { invocation });
}
else
{
this.Intercept(invocation);
}
}
private async Task<T> TransitionToAsync<T>(IInvocation invocation)
{
var asyncInvocation = new AsyncInvocation(invocation, true);
await this.InterceptAsync(asyncInvocation);
return (T)asyncInvocation.Result;
}
protected abstract void Intercept(IInvocation invocation);
protected abstract Task InterceptAsync(AsyncInvocation invocation);
}
public sealed class AsyncInvocation
{
private readonly IInvocation invocation;
private readonly IInvocationProceedInfo proceed;
private object result;
private readonly bool mustReturnValue;
internal AsyncInvocation(IInvocation invocation, bool mustReturnValue)
{
this.invocation = invocation;
this.proceed = invocation.CaptureProceedInfo();
this.mustReturnValue = mustReturnValue;
}
public object[] Arguments => this.invocation.Arguments;
public MethodInfo Method => this.invocation.Method;
public object Result
{
get => this.result;
set
{
if (!this.mustReturnValue)
{
throw new InvalidOperationException();
}
else
{
this.result = value;
}
}
}
public async Task ProceedAsync()
{
var previousReturnValue = this.invocation.ReturnValue;
try
{
this.proceed.Invoke();
var returnValue = this.invocation.ReturnValue;
if (returnValue is Task task)
{
await task;
if (this.mustReturnValue)
{
var returnType = returnValue.GetType();
Debug.Assert(returnType.IsConstructedGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>));
var result = returnType.GetProperty("Result").GetValue(returnValue);
this.Result = result;
}
}
}
finally
{
this.invocation.ReturnValue = previousReturnValue;
}
}
}
@stakx
Copy link
Author

stakx commented Jan 10, 2021

IInvocationProceedInfo has since become available and I have published stakx/DynamicProxy.AsyncInterceptor on NuGet: stakx.DynamicProxy.AsyncInterceptor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment