'How can I tell if IRegistrationBuilder.EnableInterfaceInterceptors() has already been called?
I'm using Autofac.Extras.DynamicProxy
to write a couple of IInterceptor
s. They can be used individually, or both together. I want consumers of these interceptors to be able to attach them to Autofac registrations easily, so I wrote an IRegistrationBuilder
extension method for each of them:
public static class RegistrationBuilderExtensions
{
public static IRegistrationBuilder<T, TActivatorData, TRegistrationStyle> WithTelemetryLogging<T, TActivatorData, TRegistrationStyle>(this IRegistrationBuilder<T, TActivatorData, TRegistrationStyle> builder)
{
return builder.EnableInterfaceInterceptors().InterceptedBy(typeof(TelemetryLoggingInterceptor));
}
public static IRegistrationBuilder<T, TActivatorData, TRegistrationStyle> WithCorrelationRoots<T, TActivatorData, TRegistrationStyle>(this IRegistrationBuilder<T, TActivatorData, TRegistrationStyle> builder)
{
return builder.EnableInterfaceInterceptors().InterceptedBy(typeof(NewCorrelationInterceptor));
}
}
The extension methods work well when one or the other is used. However, if a consumer uses both extension methods, as in:
builder.RegisterAssemblyTypes(GetType().Assembly)
.AsImplementedInterfaces()
.WithCorrelationRoots()
.WithTelemetryLogging()
.PreserveExistingDefaults();
I get an exception about creating a proxy of a proxy:
Castle.DynamicProxy.ProxyGenerationException: This is a DynamicProxy2 error: Target type for the proxy implements Castle.DynamicProxy.IProxyTargetAccessor which is a DynamicProxy infrastructure interface and you should never implement it yourself. Are you trying to proxy an existing proxy?
I believe it's due to EnableInterfaceInterceptors()
being called twice, once in each extension method.
I'd rather not remove the calls to EnableInterfaceInterceptors()
from the extension methods and force the consumers to remember to call it themselves. Instead, I'd like to be able to conditionally call it based on whether or not it had already been called.
How I can inspect the IRegistrationBuilder
object to determine if this call has already been made, and/or use one of its conditional registration mechanisms? Something like:
if (!builder.EIIHasBeenCalled)
{
builder.EnableInterfaceInterceptors();
}
return builder.InterceptedBy(...
or
builder.EnableInterfaceInterceptors.OnlyIf(b => !b.EIIHasBeenCalled).InterceptedBy(...
Solution 1:[1]
You can't inspect the interception bits post-facto. However, ContainerBuilder
has a Properties
dictionary that you could use.
public static class RegistrationBuilderExtensions
{
public static IRegistrationBuilder<T, TActivatorData, TRegistrationStyle>
WithTelemetryLogging<T, TActivatorData, TRegistrationStyle>(
this IRegistrationBuilder<T, TActivatorData, TRegistrationStyle> builder, ContainerBuilder cb)
{
var key = PropertyKey(builder);
if(!cb.Properties.ContainsKey(key))
{
cb.Properties[key] = true;
return builder
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(TelemetryLoggingInterceptor));
}
return builder;
}
public static IRegistrationBuilder<T, TActivatorData, TRegistrationStyle>
WithCorrelationRoots<T, TActivatorData, TRegistrationStyle>(
this IRegistrationBuilder<T, TActivatorData, TRegistrationStyle> builder, ContainerBuilder cb)
{
var key = PropertyKey(builder);
if(!cb.Properties.ContainsKey(key))
{
cb.Properties[key] = true;
return builder
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(NewCorrelationInterceptor));
}
return builder;
}
private static string PropertyKey<T, TActivatorData, TRegistrationStyle>(IRegistrationBuilder<T, TActivatorData, TRegistrationStyle> registration)
{
// Uniquely identify the registration however you want.
return registration.GetType().ToString();
}
}
Unfortunately, it looks a little messy when you use it because the ContainerBuilder
is what has the properties. You'd have to pass that in.
builder.RegisterAssemblyTypes(GetType().Assembly)
.AsImplementedInterfaces()
.WithCorrelationRoots(builder)
.PreserveExistingDefaults();
Container building and registration isn't multi-threaded so you won't hit any race conditions with that ContainsKey
call.
There is a caveat on the PropertyKey
method - this will basically be unique per registration type but if you have a bunch of services that are identical types, that could be a challenge. There is not a good way right now to uniquely identify a registration. I've filed an issue about this on your behalf. In the meantime, this is one idea on how to do it.
Solution 2:[2]
Inspired by Travis' response, I found a dictionary within IRegistrationBuilder
that I'm using with success. I now have code that looks like this:
const string MF_ENABLE_INTERFACE_INTERCEPTORS = "MFEnableInterfaceInterceptors";
public static IRegistrationBuilder<T, TActivatorData, TRegistrationStyle> WithTelemetryLogging<T, TActivatorData, TRegistrationStyle>(this IRegistrationBuilder<T, TActivatorData, TRegistrationStyle> builder)
{
if (!_AreInterfaceInterceptorsEnabled(builder))
{
builder.EnableInterfaceInterceptors();
_TrackEnableInterfaceInterceptors(builder);
}
return builder.InterceptedBy(typeof(TelemetryLoggingInterceptor));
}
public static IRegistrationBuilder<T, TActivatorData, TRegistrationStyle> WithCorrelationRoots<T, TActivatorData, TRegistrationStyle>(this IRegistrationBuilder<T, TActivatorData, TRegistrationStyle> builder)
{
if (!_AreInterfaceInterceptorsEnabled(builder))
{
builder.EnableInterfaceInterceptors();
_TrackEnableInterfaceInterceptors(builder);
}
return builder.InterceptedBy(typeof(CorrelatedInterceptor));
}
private static bool _AreInterfaceInterceptorsEnabled<T, TActivatorData, TRegistrationStyle>(IRegistrationBuilder<T, TActivatorData, TRegistrationStyle> builder)
{
if (builder.RegistrationData.Metadata.TryGetValue(MF_ENABLE_INTERFACE_INTERCEPTORS, out var metadata))
{
return (bool?)metadata ?? false;
}
return false;
}
private static void _TrackEnableInterfaceInterceptors<T, TActivatorData, TRegistrationStyle>(IRegistrationBuilder<T, TActivatorData, TRegistrationStyle> builder)
{
builder.RegistrationData.Metadata[MF_ENABLE_INTERFACE_INTERCEPTORS] = true;
}
Thanks, Travis, for the inspiration!
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Dave Werner |