Skip to content

Commit 871432e

Browse files
committed
Accuracy of docs & exception regarding Proceed
ea8fa09 made it permissible to call `invocation.Proceed()` more than once from the same interceptor. The introductory article in the docs does not yet reflect that. That change also removed an error case for which `AbstractInvocation` goes to great lengths to build an error message. Today it is no longer needed as the error will only get triggered by seriously faulty client code. Replace it with a simpler constant message. Finally, this slightly increases the accuracy of the description of how the interception pipeline and `Proceed` work.
1 parent b066111 commit 871432e

4 files changed

Lines changed: 66 additions & 18 deletions

File tree

docs/dynamicproxy-introduction.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ Schematic view of how DynamicProxy works.
4747
The picture above shows schematically how that works.
4848

4949
* The blue rectangle is the proxy. Someone calls a method on the proxy (denoted by yellow arrow). Before the method reaches the target object it goes through a pipeline of interceptors.
50-
* Each interceptor gets a `IInvocation` object (which is another important interface from Dynamic Proxy), that holds all the information about current request, like the `MethodInfo` of the method called, along with its parameters and returned value, reference to the proxy, as well as proxied object, and few others. Each interceptor gets its chance to inspect and change those values before the actual method on the target object is called. So for example at this stage you can log debug information about what parameters were passed to the method, or validate them. Then, the interceptor has to call `invocation.Proceed()`, to pass control further down the pipeline. An interceptor can call `Proceed` at most once, otherwise an exception is thrown.
51-
* After last interceptor calls `Proceed`, the actual method on proxied object is invoked, and then the call travels back, up the pipeline (green arrow) giving each interceptor chance to inspect and act on, returned value, or thrown exceptions.
50+
* Each interceptor gets an `IInvocation` object (which is another important interface from DynamicProxy) that holds all the information about the current request, such as the `MethodInfo` of the intercepted method, along with its parameters and preliminary return value; references to the proxy and the proxied object; and a few other bits. Each invoked interceptor gets a chance to inspect and change those values before the actual method on the target object is called. For example, an interceptor may log debug information about the arguments passed to the method, or validate them.
51+
* Each interceptor can call `invocation.Proceed()` to pass control further down the pipeline. Interceptors usually call this method just once, but multiple calls are allowed, e.g. when implementing a "retry". (It is also permissible to cut short the interception pipeline and omit the call to `Proceed` altogether.)
52+
* When the last interceptor calls `Proceed`, the actual method on the proxied object is invoked, and then the call travels back, up the pipeline (green arrow) giving each interceptor another chance to inspect and act on the returned value or thrown exceptions.
5253
* Finally the proxy returns the value held by `invocation.ReturnValue` as the return value of called method.
5354

5455
### Interceptor example
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2004-2019 Castle Project - http://www.castleproject.org/
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
namespace Castle.DynamicProxy.Tests.InterClasses
16+
{
17+
using System;
18+
19+
using Castle.DynamicProxy.Tests.Interfaces;
20+
21+
public sealed class WithCallbackSimple : ISimple
22+
{
23+
private readonly Action method;
24+
25+
public WithCallbackSimple(Action method)
26+
{
27+
this.method = method;
28+
}
29+
30+
public void Method()
31+
{
32+
this.method?.Invoke();
33+
}
34+
}
35+
}

src/Castle.Core.Tests/DynamicProxy.Tests/InvocationProceedInfoTestCase.cs renamed to src/Castle.Core.Tests/DynamicProxy.Tests/InvocationProceedTestCase.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,34 @@ namespace Castle.DynamicProxy.Tests
2525
using NUnit.Framework;
2626

2727
[TestFixture]
28-
public class InvocationProceedInfoTestCase
28+
public class InvocationProceedTestCase
2929
{
3030
private readonly ProxyGenerator generator = new ProxyGenerator();
3131

32+
[Test]
33+
public void Proceed_when_called_in_proxy_target_method_throws_InvalidOperationException()
34+
{
35+
IInvocation cachedInvocation = null;
36+
InvalidOperationException ex = null;
37+
38+
var proxy = generator.CreateInterfaceProxyWithTarget<ISimple>(
39+
interceptors: new[]
40+
{
41+
new WithCallbackInterceptor(invocation =>
42+
{
43+
cachedInvocation = invocation;
44+
invocation.Proceed();
45+
})
46+
},
47+
target: new WithCallbackSimple(method: () =>
48+
{
49+
ex = Assert.Throws<InvalidOperationException>(() => cachedInvocation.Proceed());
50+
}));
51+
52+
proxy.Method();
53+
Assert.NotNull(ex); // ensure that interception actually made it to the target
54+
}
55+
3256
[Test]
3357
public void Proxy_without_target_and_last_interceptor_ProceedInfo_succeeds()
3458
{

src/Castle.Core/DynamicProxy/AbstractInvocation.cs

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -115,21 +115,9 @@ public void Proceed()
115115
}
116116
else if (currentInterceptorIndex > interceptors.Length)
117117
{
118-
string interceptorsCount;
119-
if (interceptors.Length > 1)
120-
{
121-
interceptorsCount = " each one of " + interceptors.Length + " interceptors";
122-
}
123-
else
124-
{
125-
interceptorsCount = " interceptor";
126-
}
127-
128-
var message = "This is a DynamicProxy2 error: invocation.Proceed() has been called more times than expected." +
129-
"This usually signifies a bug in the calling code. Make sure that" + interceptorsCount +
130-
" selected for the method '" + Method + "'" +
131-
"calls invocation.Proceed() at most once.";
132-
throw new InvalidOperationException(message);
118+
throw new InvalidOperationException(
119+
"Cannot proceed past the end of the interception pipeline. " +
120+
"This likely signifies a bug in the calling code.");
133121
}
134122
else
135123
{

0 commit comments

Comments
 (0)