Skip to content

Commit 7d14cce

Browse files
author
Fei Xu
committed
Add service middleware and associated tests for DynamicProxy
DelegateMiddleware class is copied from autofac, after autofac add the overload method, remove this. Additionally, ServiceMiddlewareRegistrationExtensions has been added to provide static methods for registering middleware services. Tests have been provided to ensure correct interception of public interfaces. The Moq testing library has been added as a package reference.
1 parent f731a36 commit 7d14cce

File tree

4 files changed

+174
-0
lines changed

4 files changed

+174
-0
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) Autofac Project. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
using Autofac.Core.Resolving.Pipeline;
5+
6+
namespace Autofac.Extras.DynamicProxy;
7+
8+
/// <summary>
9+
/// Wraps pipeline delegates from the Use* methods in <see cref="PipelineBuilderExtensions" />.
10+
/// </summary>
11+
internal class DelegateMiddleware : IResolveMiddleware
12+
{
13+
private readonly string _descriptor;
14+
private readonly Action<ResolveRequestContext, Action<ResolveRequestContext>> _callback;
15+
16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="DelegateMiddleware"/> class.
18+
/// </summary>
19+
/// <param name="descriptor">The middleware description.</param>
20+
/// <param name="phase">The pipeline phase.</param>
21+
/// <param name="callback">The callback to execute.</param>
22+
public DelegateMiddleware(string descriptor, PipelinePhase phase, Action<ResolveRequestContext, Action<ResolveRequestContext>> callback)
23+
{
24+
_descriptor = descriptor ?? throw new ArgumentNullException(nameof(descriptor));
25+
Phase = phase;
26+
_callback = callback ?? throw new ArgumentNullException(nameof(callback));
27+
}
28+
29+
/// <inheritdoc />
30+
public PipelinePhase Phase { get; }
31+
32+
/// <inheritdoc />
33+
public void Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
34+
{
35+
_callback(context, next);
36+
}
37+
38+
/// <inheritdoc />
39+
public override string ToString() => _descriptor;
40+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) Autofac Project. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
using Autofac.Core.Resolving.Pipeline;
5+
using Castle.DynamicProxy;
6+
7+
namespace Autofac.Extras.DynamicProxy;
8+
9+
/// <summary>
10+
/// Provides a set of static methods for registering middleware services.
11+
/// </summary>
12+
public static class ServiceMiddlewareRegistrationExtensions
13+
{
14+
private const string AnonymousDescriptor = "anonymous";
15+
16+
/// <summary>
17+
/// Represents an extension method on the <see cref="ContainerBuilder"/> for enabling interface interceptors.
18+
/// </summary>
19+
/// <typeparam name="TService">The type of the service to enable interceptors for.</typeparam>
20+
/// <param name="builder">The container builder.</param>
21+
/// <param name="options">The proxy generation options.</param>
22+
public static void EnableInterfaceInterceptors<TService>(this ContainerBuilder builder, ProxyGenerationOptions? options = null)
23+
{
24+
builder.RegisterServiceMiddleware<TService>(PipelinePhase.ScopeSelection, (context, next) =>
25+
{
26+
next(context);
27+
ProxyHelpers.ApplyProxy(context, options);
28+
});
29+
}
30+
31+
/// <summary>
32+
/// Represents an extension method on the <see cref="ContainerBuilder"/> for enabling interface interceptors.
33+
/// </summary>
34+
/// <param name="builder">The container builder.</param>
35+
/// <param name="serviceType">The type of the service to enable interceptors for.</param>
36+
/// <param name="options">The proxy generation options.</param>
37+
public static void EnableInterfaceInterceptors(this ContainerBuilder builder, Type serviceType, ProxyGenerationOptions? options = null)
38+
{
39+
builder.RegisterServiceMiddleware(serviceType, new DelegateMiddleware(AnonymousDescriptor, PipelinePhase.ScopeSelection, (context, next) =>
40+
{
41+
next(context);
42+
ProxyHelpers.ApplyProxy(context, options);
43+
}));
44+
}
45+
}

test/Autofac.Extras.DynamicProxy.Test/Autofac.Extras.DynamicProxy.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
4545
</PackageReference>
4646
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
47+
<PackageReference Include="Moq" Version="4.20.70" />
4748
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435">
4849
<PrivateAssets>all</PrivateAssets>
4950
</PackageReference>
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright (c) Autofac Project. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
using Castle.DynamicProxy;
5+
using Moq;
6+
using IInvocation = Castle.DynamicProxy.IInvocation;
7+
8+
namespace Autofac.Extras.DynamicProxy.Test;
9+
10+
public class ServiceMiddlewareInterfaceInterceptorsFixture
11+
{
12+
public interface IPublicInterface
13+
{
14+
string PublicMethod();
15+
}
16+
17+
public interface IDecoratorInterface
18+
{
19+
void Decorate();
20+
}
21+
22+
[Fact]
23+
public void InterceptsPublicInterfacesUseGenericMethod()
24+
{
25+
Mock<IDecoratorInterface> mockDecorator = new();
26+
ContainerBuilder builder = new();
27+
builder.RegisterType<StringMethodInterceptor>();
28+
builder.RegisterDecorator<Decorator, IPublicInterface>();
29+
builder.RegisterInstance(mockDecorator.Object);
30+
builder
31+
.RegisterType<Interceptable>()
32+
.InterceptedBy(typeof(StringMethodInterceptor))
33+
.As<IPublicInterface>();
34+
builder.EnableInterfaceInterceptors<IPublicInterface>();
35+
IContainer container = builder.Build();
36+
IPublicInterface obj = container.Resolve<IPublicInterface>();
37+
Assert.Equal("intercepted-PublicMethod", obj.PublicMethod());
38+
mockDecorator.Verify(e => e.Decorate(), Times.Never);
39+
}
40+
41+
[Fact]
42+
public void InterceptsPublicInterfacesUseNoneGenericMethod()
43+
{
44+
Mock<IDecoratorInterface> mockDecorator = new();
45+
ContainerBuilder builder = new();
46+
builder.RegisterType<StringMethodInterceptor>();
47+
builder.RegisterDecorator<Decorator, IPublicInterface>();
48+
builder.RegisterInstance(mockDecorator.Object);
49+
builder
50+
.RegisterType<Interceptable>()
51+
.InterceptedBy(typeof(StringMethodInterceptor))
52+
.As<IPublicInterface>();
53+
builder.EnableInterfaceInterceptors(typeof(IPublicInterface));
54+
IContainer container = builder.Build();
55+
IPublicInterface obj = container.Resolve<IPublicInterface>();
56+
Assert.Equal("intercepted-PublicMethod", obj.PublicMethod());
57+
mockDecorator.Verify(e => e.Decorate(), Times.Never);
58+
}
59+
60+
public class Decorator(IPublicInterface decoratedService, IDecoratorInterface decoratorInterface) : IPublicInterface
61+
{
62+
public string PublicMethod()
63+
{
64+
decoratorInterface.Decorate();
65+
return decoratedService.PublicMethod();
66+
}
67+
}
68+
69+
public class Interceptable : IPublicInterface
70+
{
71+
public string PublicMethod() => throw new NotImplementedException();
72+
}
73+
74+
private class StringMethodInterceptor : IInterceptor
75+
{
76+
public void Intercept(IInvocation invocation)
77+
{
78+
if (invocation.Method.ReturnType == typeof(string))
79+
{
80+
invocation.ReturnValue = "intercepted-" + invocation.Method.Name;
81+
}
82+
else
83+
{
84+
invocation.Proceed();
85+
}
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)