Open idiotsky opened 7 months ago
The last version of Autofac 4.x was v4.9.4 released in 2019. A lot has happened since then to accommodate for .NET Core and some compatibility issues.
If there is a problem you're seeing, please express it in the form of a unit test or something we can try to reproduce. There may be something broken, but there it may be that there are things that just work differently now. However, it's somewhat difficult to say without seeing code. The text/prose explanation is not entirely clear and before responding it'd be better to see exactly what you're talking about in concrete code.
Please make sure that repro is as absolutely minimal as possible. No extra classes, no crazy indirection with extra methods running registration, no Autofac modules, no assembly scanning, no full application stack. As absolutely minimal as possible.
public interface IService
{
void DoWork();
}
public class Service : IService
{
public void DoWork()
{
Console.WriteLine("Service is doing work.");
}
}
public class ServiceDecorator(IService decoratedService) : IService
{
public void DoWork()
{
Console.WriteLine("Before decorated service work.");
decoratedService.DoWork();
Console.WriteLine("After decorated service work.");
}
}
public class LoggingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine($"Intercepting call: {invocation.Method.Name}");
invocation.Proceed();
Console.WriteLine($"Completed call: {invocation.Method.Name}");
}
}
// Program.cs
var builder = new ContainerBuilder();
// Register the service and interceptor
builder.RegisterType<Service>().As<IService>().EnableInterfaceInterceptors().InterceptedBy(typeof(LoggingInterceptor));
builder.RegisterDecorator<ServiceDecorator, IService>();
// Enable class interceptors and register the logging interceptor to be applied to the decorator
builder.RegisterType<LoggingInterceptor>();
IContainer? container = builder.Build();
// Resolve and use the service
var service = container.Resolve<IService>();
service.DoWork();
if we use autofac 4.x and Autofac.Extras.DynamicProxy 4.5.0 LoggingInterceptor will act on ServiceDecorator(if multiple decorator it will act on the outmost decorator) and output is Intercepting call: DoWork Before decorated service work. Service is doing work. After decorated service work. Completed call: DoWork
if we use autofact 8.0 and Autofac.Extras.DynamicProxy 7.1.0 (actually is since 5+ the behaviors changed) LoggingInterceptor will act on Service and output is Before decorated service work. Intercepting call: DoWork Service is doing work. Completed call: DoWork After decorated service work.
and It also put the sample on github https://github.com/idiotsky/AutofacWithDecoratorAndInterceptor
and I saw this closed issue too, it should be similar issue as mine. https://github.com/autofac/Autofac/issues/1296
thanks for your reply
This is kind of a tough one.
I haven't dug into it super deep, but I'm guessing that the order of operations changed here due to the changes in the way the registration pipelines got updated. All of those internals changed for .NET Core for compatibility and to try fixing issues where there wasn't a consistent order of operations, especially when it came to trying to add things like diagnostics and such.
I'll admit we've always had troubles when it came to trying to put all the things together all at once - interceptors and decorators and adapters and everything. I'm not sure we can really "win" on the order of operations. On one hand, if we do the interception after all the decoration, we have problems like the fact that the interceptor is on the service registration rather than the decorator registration so it's not really obvious that the interceptor will "move" during decoration. On the other hand, I can see the desire to intercept at the resolved service level, which is potentially considered to be the "outermost" object in that hierarchy.
While I recognize we appear to have changed things as of around five years ago, I'm not sure we'll change it back now. It's been broken, we released the major version for the breaking change, and it's been running pretty steady like this for five years now.
I wonder if there's a way we can "fail forward," as it were?
For example, is there a way we can allow interceptors to be added to decorator registrations? This wouldn't solve the problem of totally switching the order of operations, but it would allow you to at least manage where the interception happens in a more intentional fashion. It'd also allow the registration to be more readable - the interceptor would be attached to the thing being intercepted, not "magically" moved around.
ok, question, Is it possible register interceptor on decorator for now?
I'm not in a place where I can test. Did you try it?
I try this
builder.RegisterType<ServiceDecorator>()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(LoggingInterceptor));
but the Interceptor dose not apply. still in trying if i can make it working, any suggestion? thanks.
I don't have any suggestions right now. If it works out of the box, it would be on the RegisterDecorator<>
line since the decorator isn't registered as a separate service. If that's not supported, I might look at how the code in the Autofac DynamicProxy library works and see if there's a way to use that to set up the interception pipeline. Without digging in here at the code level - something I cannot do at this very specific moment in time - I can't provide additional guidance.
If it's not supported right out of the box, like if builder.RegisterDecorator<X, Y>().EnableInterfaceInterceptors()
doesn't work - then it will not be like two lines of code. It will require some spelunking into the Autofac DynamicProxy integration to see how it builds up the metadata and pipeline stuff to allow interception to work. But, again, I can't provide specific guidance, hints, or suggestions at this point in time.
builder.RegisterDecorator<X, Y>() return void, so we can not use builder.RegisterDecorator<X, Y>().EnableInterfaceInterceptors()
I try this still not working, may be it does not support now. the interceptor does not take affect use the following code.
// Register the base service normally.
builder.RegisterType<Service>().Named<IService>("baseService");
// Register the LoggingInterceptor.
builder.RegisterType<LoggingInterceptor>();
// Register the decorator to wrap the base service and enable interception on the decorator.
builder.RegisterDecorator<IService>(
(context, parameters, service) => new ServiceDecorator(service),
fromKey: "baseService")
.EnableInterfaceInterceptors() // Enable interception on the decorator
.InterceptedBy(typeof(LoggingInterceptor)); // Specify the interceptor
var container = builder.Build();
// Resolve and use the service.
var service = container.Resolve<IService>();
service.DoWork();
Ideally we should support two mode interceptor on decorator or interceptor on service, both has its user case, or even more complicate, we can support interceptor on decorator and interceptor on service the same time.
I've marked this as an enhancement. I'm guessing the changes will be in the Autofac.Extras.DynamicProxy library for the most part, but I'm not positive.
We would entertain a pull request for this feature if it can be done without breaking the API; or breaking things very minimally (e.g., changing a void
return type to return an object of some type). I'm not sure we'd be interested in supporting something terribly complex like "dual mode interception" or lots of parameters/config options - complexity adds overhead in testing, maintenance, and support, and given this is fairly edge-case, keeping that to a minimum is important.
Given this is all volunteer work and that there is no "development team" or anything like that, there is no "promised timeline" or schedule for this. If folks want this, the fastest way to get it done will be a pull request rather than waiting for a maintainer to get time to take it on.
I'm not familiar with the source code of Autofac.Extras.DynamicProxy, I think add an .InterceptedOnDecorator() method like this
builder.RegisterType<Service>().As<IService>().EnableInterfaceInterceptors().InterceptedBy(typeof(LoggingInterceptor))
.InterceptedOnDecorator()
use this it won't break the current API, and can support the interceptor on decorator mode. any way I'll take an look Autofac.Extras.DynamicProxy to see if I can do something. thanks for your reply.
public static IRegistrationBuilder<TLimit, TActivatorData, TSingleRegistrationStyle> EnableInterfaceInterceptors<TLimit, TActivatorData, TSingleRegistrationStyle>(
this IRegistrationBuilder<TLimit, TActivatorData, TSingleRegistrationStyle> registration, ProxyGenerationOptions? options = null, bool createProxyOnDecorator = false)
{
ArgumentNullException.ThrowIfNull(registration);
if (createProxyOnDecorator)
{
registration.OnActivated(e =>
{
var componentContext = (ResolveRequestContext)e.Context;
CreateProxy(componentContext);
});
}
else
{
registration.ConfigurePipeline(p => p.Use(PipelinePhase.ScopeSelection, MiddlewareInsertionMode.StartOfPhase, (ctx, next) =>
{
next(ctx);
CreateProxy(ctx);
}));
}
void CreateProxy(ResolveRequestContext ctx)
{
EnsureInterfaceInterceptionApplies(ctx.Registration);
// The instance won't ever _practically_ be null by the time it gets here.
var proxiedInterfaces = ctx.Instance!
.GetType()
.GetInterfaces()
.Where(ProxyUtil.IsAccessible)
.ToArray();
if (proxiedInterfaces.Length == 0)
{
return;
}
var theInterface = proxiedInterfaces.First();
var interfaces = proxiedInterfaces.Skip(1).ToArray();
var interceptors = GetInterceptorServices(ctx.Registration, ctx.Instance.GetType())
.Select(s => ctx.ResolveService(s))
.Cast<IInterceptor>()
.ToArray();
ctx.Instance = options == null
? ProxyGenerator.CreateInterfaceProxyWithTarget(theInterface, interfaces, ctx.Instance, interceptors)
: ProxyGenerator.CreateInterfaceProxyWithTarget(theInterface, interfaces, ctx.Instance, options, interceptors);
}
return registration;
}
I have made a change to the Autofac.Extras.DynamicProxy.RegistrationExtensions class and it is working. Initially, I considered adding a middleware to the service pipelines, but I couldn't find a way to integrate the service middleware using this method. Therefore, I opted to use OnActivated instead. I'm uncertain if there's a need to modify EnableClassInterceptors. @tillig, if this adjustment is acceptable, could you please implement it? If it's not suitable, I would appreciate any suggestions for improvement, and I'll make the necessary corrections.
The right way to suggest a fairly large code change like this is to submit a pull request. That way we can verify that all the unit tests still pass and we can get additional eyes on it to catch things that I may not catch - @alistairjevans sometimes sees stuff I don't, for example. I won't be able to take this large block of code and verify it works or suggest alternatives.
I will say:
EnableDecoratorInterfaceInterception
or something similar. (That's not a recommendation for a name, I'm just saying, a different name to differentiate rather than a parameter may be something to consider.) already submit 2 pull request one is on autofac, add these extension methods will help remove the duplicate code in the second pull request on Autofac.Extras.DynamicProxy. any feed back let me know, I'll try to fix it.
Register service with Interceptor, then register multiple decorator, auto apply Interceptor to the outermost decorator Actually this is the default behaviors when I use autofac 4.x. after upgrade version 8.0.0, this not working any more. I know explicit configure interceptor on the outermost decorator make it more clarity and flexibility. but I thinks we should keep the original behaviors some how, even it not the default behaviors. This behaviors will benefit in case when add new decorator, we don't need remove the interceptor and add the interceptor to the new decorator.