简体   繁体   中英

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(...

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.

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!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM