Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2004-2016 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace CastleTests.DynamicProxy.Tests
{
using System;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

using Castle.DynamicProxy;
using Castle.DynamicProxy.Tests;

using CastleTests.DynamicProxy.Tests.Classes;
using CastleTests.DynamicProxy.Tests.Interfaces;
using CastleTests.Interceptors;

using NUnit.Framework;

[TestFixture]
public class AsyncInterceptorTestCase : BasePEVerifyTestCase
{
[Test]
public async Task Should_Intercept_Asynchronous_Methods_With_An_Async_Operations_Prior_To_Calling_Proceed()
{
// Arrange
IInterfaceWithAsynchronousMethod target = new ClassWithAsynchronousMethod();
IInterceptor interceptor = new AsyncInterceptor();

IInterfaceWithAsynchronousMethod proxy =
generator.CreateInterfaceProxyWithTargetInterface(target, interceptor);

// Act
await proxy.Method().ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2004-2010 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace CastleTests.DynamicProxy.Tests.Classes
{
using System;
using System.Threading;
using System.Threading.Tasks;

using CastleTests.DynamicProxy.Tests.Interfaces;

public class ClassWithAsynchronousMethod : IInterfaceWithAsynchronousMethod
{
public async Task Method()
{
Console.WriteLine(
$"Before Await ClassWithAsynchronousMethod:Method ThreadId='{Thread.CurrentThread.ManagedThreadId}'.",
Thread.CurrentThread.ManagedThreadId);

await Task.Delay(10).ConfigureAwait(false);

Console.WriteLine(
$"After Await ClassWithAsynchronousMethod:Method ThreadId='{Thread.CurrentThread.ManagedThreadId}'.",
Thread.CurrentThread.ManagedThreadId);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2004-2010 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace CastleTests.DynamicProxy.Tests.Interfaces
{
using System.Threading.Tasks;

public interface IInterfaceWithAsynchronousMethod
{
Task Method();
}
}
45 changes: 45 additions & 0 deletions src/Castle.Core.Tests/Interceptors/AsyncInterceptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2004-2010 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace CastleTests.Interceptors
{
using System.Threading.Tasks;
using Castle.DynamicProxy;

public class AsyncInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
invocation.ReturnValue = InterceptAsyncMethod(invocation);
}

private static async Task InterceptAsyncMethod(IInvocation invocation)
{
var interceptionProgress = invocation.GetMemento(InvocationMementoOptions.InterceptionProgress);

// It all falls down when executing async before calling Proceed().
await Task.Delay(10).ConfigureAwait(false);

interceptionProgress.Restore();

invocation.Proceed();

// Hmmmmm, now with it simplified down to this, I see the glaring hole that is the return value being set
// in two situations.
Task returnValue = (Task)invocation.ReturnValue;

await returnValue.ConfigureAwait(false);
}
}
}
56 changes: 56 additions & 0 deletions src/Castle.Core/DynamicProxy/AbstractInvocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ public void Proceed()
}
}

public IInvocationMemento GetMemento(InvocationMementoOptions options)
{
return new Memento(this, options);
}

protected abstract void InvokeMethodOnTarget();

protected void ThrowOnNoTarget()
Expand Down Expand Up @@ -188,5 +193,56 @@ private MethodInfo EnsureClosedMethod(MethodInfo method)
}
return method;
}

private sealed class Memento : IInvocationMemento
{
private readonly AbstractInvocation invocation;
private readonly InvocationMementoOptions options;

private readonly object[] arguments;
private readonly object returnValue;
private readonly int interceptorIndex;

public Memento(AbstractInvocation invocation, InvocationMementoOptions options)
{
this.invocation = invocation;
this.options = options;

if ((options & InvocationMementoOptions.Arguments) != 0)
{
var argumentCount = invocation.arguments.Length;
this.arguments = new object[argumentCount];
Array.Copy(invocation.arguments, this.arguments, argumentCount);
}

if ((options & InvocationMementoOptions.ReturnValue) != 0)
{
this.returnValue = invocation.ReturnValue;
}

if ((options & InvocationMementoOptions.InterceptionProgress) != 0)
{
this.interceptorIndex = invocation.currentInterceptorIndex;
}
}

public void Restore()
{
if ((options & InvocationMementoOptions.Arguments) != 0)
{
Array.Copy(arguments, invocation.arguments, arguments.Length);
}

if ((options & InvocationMementoOptions.ReturnValue) != 0)
{
invocation.ReturnValue = returnValue;
}

if ((options & InvocationMementoOptions.InterceptionProgress) != 0)
{
invocation.currentInterceptorIndex = interceptorIndex;
}
}
}
}
}
6 changes: 6 additions & 0 deletions src/Castle.Core/DynamicProxy/IInvocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,11 @@ public interface IInvocation
/// <param name = "index">The index of the argument to override.</param>
/// <param name = "value">The new value for the argument.</param>
void SetArgumentValue(int index, object value);

/// <summary>
/// Gets an object that captures the specified parts of this invocation's state for later restoration.
/// </summary>
/// <param name="options">Specifies which parts of this invocation's state should be captured.</param>
IInvocationMemento GetMemento(InvocationMementoOptions options);
}
}
30 changes: 30 additions & 0 deletions src/Castle.Core/DynamicProxy/IInvocationMemento.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2004-2019 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Castle.DynamicProxy
{
using System.ComponentModel;

/// <summary>
/// Represents a full or partial state of an <see cref="IInvocation"/> captured at an earlier time.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public interface IInvocationMemento
{
/// <summary>
/// Restores the state captured by this instance to the associated <see cref="IInvocation"/>.
/// </summary>
void Restore();
}
}
46 changes: 46 additions & 0 deletions src/Castle.Core/DynamicProxy/InvocationMementoOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2004-2019 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Castle.DynamicProxy
{
using System;

/// <summary>
/// Specifies which parts of an invocation's state to capture.
/// </summary>
[Flags]
public enum InvocationMementoOptions
{
/// <summary>
/// Specifies that arguments should be captured.
/// </summary>
Arguments = 1,

/// <summary>
/// Specifies that the return value should be captured.
/// </summary>
ReturnValue = 2,

/// <summary>
/// Specifies that the invocation's position in the interception pipeline should be captured.
/// <para>
/// Note that interception of an invocation may have finished by the time a call to
/// <see cref="IInvocationMemento.Restore"/> is made. While such a "stale" invocation
/// can be procesed again by interceptors, setting the return value or by-ref arguments
/// will no longer have any effect on the calling code.
/// </para>
/// </summary>
InterceptionProgress = 4,
}
}