autofac / Autofac.Extras.DynamicProxy

Interceptor and decorator support for Autofac IoC via Castle DynamicProxy
MIT License
106 stars 33 forks source link

Class Interceptors are not working with .NET 6 #48

Closed tapan-p closed 2 years ago

tapan-p commented 2 years ago

Class Interceptors with .NET 6

I am trying to implement simple class interceptors for ASP.NET 6 WebApi. I had the same code previously in .NET core 3.1 and it was working. With .NET 6, class interceptors are not working for controller methods despite marking them as virtual. Is there anything I am missing or configuring wrong? It is only an issue with class interceptors (even if I decorate class with Intercept attribute). Interface interceptors are working fine.

Steps to Reproduce

  1. Create a simple web api (.NET 6) with just a weather controller.
  2. Added packages ( Autofac 6.4.0, Autofac.Extension.DependencyInjection 8.0.0, Autofac.Extras.DynamicProxy 6.0.1, Castle.Core.AsyncInterceptor 2.1.0) and configured startup.cs class to register dependencies with Autofac container.

Program.cs

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory())
    .ConfigureServices((context, services) =>
    {
        services.AddControllers();
        services.AddEndpointsApiExplorer();
        services.AddSwaggerGen();
    })
    .ConfigureContainer<ContainerBuilder>(autofacBuilder =>
    {
        autofacBuilder.RegisterType<MethodExecutionMetricsLoggerAspect>();
        autofacBuilder.RegisterType<WeatherForecastController>()
            .EnableClassInterceptors().InterceptedBy(typeof(MethodExecutionMetricsLoggerAspect));
    });

var app = builder.Build();

MethodExecutionMetricsLoggerAspect

    public class MethodExecutionMetricsLoggerAspect : IInterceptor
    {
        public MethodExecutionMetricsLoggerAspect()
        {

        }

        public void Intercept(IInvocation invocation)
        {
            Debug.WriteLine("Inside interceptor");
            invocation.Proceed();
        }
    }

WeatherController endpoint

   public virtual IEnumerable<WeatherForecast> Get()
        {
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            })
            .ToArray();
        }

Expected Behavior

Call to above method should be intercepted by MethodExecutionMetricsLoggerAspect class. This was working fine in .NET core 3.1 but I cannot get it to work in .NET 6.

Dependency Versions

Autofac: 6.4.0 Autofac.Extension.DependencyInjection: 8.0.0 Autofac.Extras.DynamicProxy: 6.0.1 Castle.Core.AsyncInterceptor: 2.1.0

Additional Info

tillig commented 2 years ago

Any way to simplify this down? For example, can you reproduce it in a unit test without involving ASP.NET at all? You've shown a couple of interceptors here -are they both required for the repro or can you reduce it to one? How simple can the interceptor be? Can you register all the dependencies in one spot rather than having a module in the middle? Is the problem with both sync and async methods? If it's only one or the other, can you remove the methods that aren't affected?

Basically, what can you remove here?

The reason I ask is that time to try reproducing things is very limited. The more you can remove while still showing the repro, the more likely someone will come and help. Not only that, if you find out that you remove something and it starts working again.... Well, maybe you've debugged your own problem.

tapan-p commented 2 years ago

Thank you @tillig , for pointing it out. I simplified the example by removing async part of the repo and also moved registration inline instead of a separate module. I still have the issue while trying it out. I have pushed this example here: Demo.Autofac

tillig commented 2 years ago

Try also calling services.AddControllersAsServices(). services.AddControllers() doesn't register the controllers, it registers the stuff in MVC required to support controllers. By default, controllers themselves aren't resolved - only their constructor parameters are. Hence, the controller isn't going through Autofac and isn't getting intercepted.

That hasn't changed in ASP.NET Core from 3.1 to 6 so I'd be surprised if it was working before without that.

tapan-p commented 2 years ago

Try also calling services.AddControllersAsServices(). services.AddControllers() doesn't register the controllers, it registers the stuff in MVC required to support controllers. By default, controllers themselves aren't resolved - only their constructor parameters are. Hence, the controller isn't going through Autofac and isn't getting intercepted.

That hasn't changed in ASP.NET Core from 3.1 to 6 so I'd be surprised if it was working before without that.

Thank you @tillig , you are right. I think I missed the point where I have to register controller as services. I added below to fix the issue. Thank you for your input. It is working fine now.

services.AddMvc().AddControllersAsServices();