From b183fbd9cb38c1165c4b05da43a5f615d1475143 Mon Sep 17 00:00:00 2001 From: Fei Xu Date: Tue, 2 Apr 2024 20:55:27 -0400 Subject: [PATCH] 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. --- .../Autofac.Extras.DynamicProxy.csproj | 2 +- ...ServiceMiddlewareRegistrationExtensions.cs | 43 ++++++++ .../Autofac.Extras.DynamicProxy.Test.csproj | 1 + ...eMiddlewareInterfaceInterceptorsFixture.cs | 97 +++++++++++++++++++ 4 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 src/Autofac.Extras.DynamicProxy/ServiceMiddlewareRegistrationExtensions.cs create mode 100644 test/Autofac.Extras.DynamicProxy.Test/ServiceMiddlewareInterfaceInterceptorsFixture.cs diff --git a/src/Autofac.Extras.DynamicProxy/Autofac.Extras.DynamicProxy.csproj b/src/Autofac.Extras.DynamicProxy/Autofac.Extras.DynamicProxy.csproj index 1330b98..07b6acb 100644 --- a/src/Autofac.Extras.DynamicProxy/Autofac.Extras.DynamicProxy.csproj +++ b/src/Autofac.Extras.DynamicProxy/Autofac.Extras.DynamicProxy.csproj @@ -62,7 +62,7 @@ - + all diff --git a/src/Autofac.Extras.DynamicProxy/ServiceMiddlewareRegistrationExtensions.cs b/src/Autofac.Extras.DynamicProxy/ServiceMiddlewareRegistrationExtensions.cs new file mode 100644 index 0000000..3298b05 --- /dev/null +++ b/src/Autofac.Extras.DynamicProxy/ServiceMiddlewareRegistrationExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Autofac.Core.Resolving.Pipeline; +using Castle.DynamicProxy; + +namespace Autofac.Extras.DynamicProxy; + +/// +/// Provides a set of static methods for registering middleware services. +/// +public static class ServiceMiddlewareRegistrationExtensions +{ + /// + /// Represents an extension method on the for enabling interface interceptors. + /// + /// The type of the service to enable interceptors for. + /// The container builder. + /// The proxy generation options. + public static void EnableInterfaceInterceptors(this ContainerBuilder builder, ProxyGenerationOptions? options = null) + { + builder.RegisterServiceMiddleware(PipelinePhase.ScopeSelection, (context, next) => + { + next(context); + ProxyHelpers.ApplyProxy(context, options); + }); + } + + /// + /// Represents an extension method on the for enabling interface interceptors. + /// + /// The container builder. + /// The type of the service to enable interceptors for. + /// The proxy generation options. + public static void EnableInterfaceInterceptors(this ContainerBuilder builder, Type serviceType, ProxyGenerationOptions? options = null) + { + builder.RegisterServiceMiddleware(serviceType, PipelinePhase.ScopeSelection, (context, next) => + { + next(context); + ProxyHelpers.ApplyProxy(context, options); + }); + } +} diff --git a/test/Autofac.Extras.DynamicProxy.Test/Autofac.Extras.DynamicProxy.Test.csproj b/test/Autofac.Extras.DynamicProxy.Test/Autofac.Extras.DynamicProxy.Test.csproj index d510886..9b5107c 100644 --- a/test/Autofac.Extras.DynamicProxy.Test/Autofac.Extras.DynamicProxy.Test.csproj +++ b/test/Autofac.Extras.DynamicProxy.Test/Autofac.Extras.DynamicProxy.Test.csproj @@ -44,6 +44,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + all diff --git a/test/Autofac.Extras.DynamicProxy.Test/ServiceMiddlewareInterfaceInterceptorsFixture.cs b/test/Autofac.Extras.DynamicProxy.Test/ServiceMiddlewareInterfaceInterceptorsFixture.cs new file mode 100644 index 0000000..99543af --- /dev/null +++ b/test/Autofac.Extras.DynamicProxy.Test/ServiceMiddlewareInterfaceInterceptorsFixture.cs @@ -0,0 +1,97 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Castle.DynamicProxy; +using Moq; +using IInvocation = Castle.DynamicProxy.IInvocation; + +namespace Autofac.Extras.DynamicProxy.Test; + +public class ServiceMiddlewareInterfaceInterceptorsFixture +{ + public interface IPublicInterface + { + string PublicMethod(); + } + + public interface IDecoratorInterface + { + void Decorate(); + } + + [Fact] + public void InterceptsPublicInterfacesUseGenericMethod() + { + Mock mockDecorator = new(); + ContainerBuilder builder = new(); + builder.RegisterType(); + builder.RegisterDecorator(); + builder.RegisterInstance(mockDecorator.Object); + builder + .RegisterType() + .InterceptedBy(typeof(StringMethodInterceptor)) + .As(); + builder.EnableInterfaceInterceptors(); + IContainer container = builder.Build(); + IPublicInterface obj = container.Resolve(); + Assert.Equal("intercepted-PublicMethod", obj.PublicMethod()); + mockDecorator.Verify(e => e.Decorate(), Times.Never); + } + + [Fact] + public void InterceptsPublicInterfacesUseNoneGenericMethod() + { + Mock mockDecorator = new(); + ContainerBuilder builder = new(); + builder.RegisterType(); + builder.RegisterDecorator(); + builder.RegisterInstance(mockDecorator.Object); + builder + .RegisterType() + .InterceptedBy(typeof(StringMethodInterceptor)) + .As(); + builder.EnableInterfaceInterceptors(typeof(IPublicInterface)); + IContainer container = builder.Build(); + IPublicInterface obj = container.Resolve(); + Assert.Equal("intercepted-PublicMethod", obj.PublicMethod()); + mockDecorator.Verify(e => e.Decorate(), Times.Never); + } + + public class Decorator : IPublicInterface + { + private readonly IPublicInterface _decoratedService; + private readonly IDecoratorInterface _decoratorInterface; + + public Decorator(IPublicInterface decoratedService, IDecoratorInterface decoratorInterface) + { + _decoratedService = decoratedService; + _decoratorInterface = decoratorInterface; + } + + public string PublicMethod() + { + _decoratorInterface.Decorate(); + return _decoratedService.PublicMethod(); + } + } + + public class Interceptable : IPublicInterface + { + public string PublicMethod() => throw new NotImplementedException(); + } + + private class StringMethodInterceptor : IInterceptor + { + public void Intercept(IInvocation invocation) + { + if (invocation.Method.ReturnType == typeof(string)) + { + invocation.ReturnValue = "intercepted-" + invocation.Method.Name; + } + else + { + invocation.Proceed(); + } + } + } +}