Autofac全局接口拦截器与Autofac.Extras.DynamicProxy2

时间:2014-04-01 09:42:27

标签: c# autofac

我在Autofac DynamicProxy2中使用接口拦截器,并且我能够为每个寄存器启用接口拦截器:

   var registration = builder.RegisterType<AType>().As<AInterface>();
   registration.EnableInterfaceInterceptors().InterceptedBy<AInterceptor>()

我想对所有注册类型应用某些特征。类似的东西:

   var registrations = builder.GetAllRegistrations(); // ops, this does not exist...
   foreach (var registration in registrations) {
       registration.EnableInterfaceInterceptors().InterceptedBy<AInterceptor>()
   }

我无法找到获得所有注册的方法。我知道我们可以做到:

   builder.RegisterCallback(cr =>
   {
       foreach (var registration in cr.Registrations)
       {
            // registration is IComponentRegistration
       }
   });

但此处的注册时间为IComponentRegistration,我需要IRegistrationBuilder才能应用EnableInterfaceInterceptors()

3 个答案:

答案 0 :(得分:7)

您可以动态添加拦截器,但需要一些工作。要做的是创建一个附加到所有组件注册的自定义Autofac.Module。我将在一个例子中向您展示。

您无法在全球范围内真正做到EnableInterfaceInterceptors我会在示例结尾处找到它。

首先,示例设置:我们有一个简单的接口,一个简单的实现,以及一个处理日志记录调用的拦截器。 (我偷了拦截器代码from the Autofac wiki):

public interface IInterface
{
  void DoWork();
}

public class Implementation : IInterface
{
  public void DoWork()
  {
    Console.WriteLine("Implementation doing work.");
  }
}

public class CallLogger : IInterceptor
{
  TextWriter _output;

  public CallLogger(TextWriter output)
  {
    _output = output;
  }

  public void Intercept(IInvocation invocation)
  {
    _output.WriteLine("Calling method {0} with parameters {1}... ",
      invocation.Method.Name,
      string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray()));

    invocation.Proceed();

    _output.WriteLine("Done: result was {0}.", invocation.ReturnValue);
  }
}

我们希望使用我们的调用记录器拦截所有内容(已启用拦截器)。我们通过创建一个自定义Autofac.Module来实现这一点,它们都使用Autofac注册拦截器并附加到组件动态注册以添加拦截器元数据。

警告:这里有一点点黑客行为可行,但有点像是在搜索数据&#34;进入一个众所周知的位置。它有效,但我不知道它为什么会发生变化,但请注意,因为它有点像是在私人化身上工作。这些东西,这可能会在未来的版本中破裂。请注意。

好的,免责声明完成了。这是模块:

public class InterceptorModule : Autofac.Module
{
  // This is a private constant from the Autofac.Extras.DynamicProxy2 assembly
  // that is needed to "poke" interceptors into registrations.
  const string InterceptorsPropertyName = "Autofac.Extras.DynamicProxy2.RegistrationExtensions.InterceptorsPropertyName";

  protected override void Load(ContainerBuilder builder)
  {
    // Register global interceptors here.
    builder.Register(c => new CallLogger(Console.Out));
  }

  protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
  {
    // Here is where you define your "global interceptor list"
    var interceptorServices = new Service[] { new TypedService(typeof(CallLogger)) };

    // Append the global interceptors to any existing list, or create a new interceptor
    // list if none are specified. Note this info will only be used by registrations
    // that are set to have interceptors enabled. It'll be ignored by others.
    object existing;
    if (registration.Metadata.TryGetValue(InterceptorsPropertyName, out existing))
    {
      registration.Metadata[InterceptorsPropertyName] =
        ((IEnumerable<Service>)existing).Concat(interceptorServices).Distinct();
    }
    else
    {
      registration.Metadata.Add(InterceptorsPropertyName, interceptorServices);
    }
  }
}

要使其工作,请将模块与其余依赖项一起注册。对于这个例子,它看起来像:     var builder = new ContainerBuilder();

// Notice this registration doesn't include
// the interceptor - that gets added by the
// module.
builder.RegisterType<Implementation>()
       .As<IInterface>()
       .EnableInterfaceInterceptors();

// Here's the magic module:
builder.RegisterModule<InterceptorModule>();
var container = builder.Build();

如果您运行这些注册并解决...

var impl = container.Resolve<IInterface>();
impl.DoWork();

您可以看到拦截器正常工作,因为您将看到控制台输出:

Calling method DoWork with parameters ... 
Implementation doing work.
Done: result was .

(这有点奇怪,因为我的示例中有一个无参数/无效方法,但拦截器正在工作!)

关于EnableInterfaceInterceptors来电... EnableInterfaceInterceptorsEnableClassInterceptors实际上会在后端执行大量疯狂的DynamicProxy2工作。它为组件上的激活事件添加了一些非平凡的事件处理程序,它们将对象包装在动态代理中。这些事件处理程序目前不会单独使用,我不知道尝试并附加所有这些事情需要做多少工作&#34;事后&#34;我们在这里与实际拦截器一起做的方式。

欢迎您自己尝试一下 - the source is on GitHub。但是,基本上,当&#34;添加一个全局拦截器&#34;事情很有效,在一个模块中做全局EnableInterfaceInterceptors远远不够。你绝对可以自己选择那个。

答案 1 :(得分:6)

在我们执行注册表的那一刻,放弃EnableInterfaceInterceptors调用显然是微不足道的。

由于我的目标是实现通过Autofac解决的所有接口共有的拦截器,我最终推出了自己的解决方案,灵感来自Autofac的DynamicProxy实现。

我们的想法是覆盖Autofac Activating事件并手动创建DynamicProxy。这里的大部分工作是确保我们可以安全地代理正在解决的类型。

public static class AutofacExtensions
{
    // DynamicProxy2 generator for creating proxies
    private static readonly ProxyGenerator generator = new ProxyGenerator();

    /// <summary>
    /// Intercept ALL registered interfaces with provided interceptors.
    /// Override Autofac activation with a Interface Proxy.
    /// Does not intercept classes, only interface bindings.
    /// </summary>
    /// <param name="builder">Contained builder to apply interceptions to.</param>
    /// <param name="interceptors">List of interceptors to apply.</param>
    public static void InterceptInterfacesBy(this ContainerBuilder builder, params IInterceptor[] interceptors)
    {
        builder.RegisterCallback((componentRegistry) =>
        {
            foreach (var registration in componentRegistry.Registrations)
            {
                InterceptRegistration(registration, interceptors);
            }
        });
    }

    /// <summary>
    /// Intercept a specific component registrations.
    /// </summary>
    /// <param name="registration">Component registration</param>
    /// <param name="interceptors">List of interceptors to apply.</param>
    private static void InterceptRegistration(IComponentRegistration registration, params IInterceptor[] interceptors)
    {
        // proxy does not get allong well with Activated event and registrations with Activated events cannot be proxied.
        // They are casted to LimitedType in the IRegistrationBuilder OnActivated method. This is the offending Autofac code:
        // 
        // public IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> OnActivated(Action<IActivatedEventArgs<TLimit>> handler)
        // {
        //    if (handler == null) throw new ArgumentNullException("handler");
        //    RegistrationData.ActivatedHandlers.Add(
        //        (s, e) => handler(new ActivatedEventArgs<TLimit>(e.Context, e.Component, e.Parameters, (TLimit)e.Instance)));
        //    return this;
        // }
        Delegate[] handlers = GetActivatedEventHandlers(registration);
        if (handlers.Any(h => handlers[0].Method.DeclaringType.Namespace.StartsWith("Autofac")))
        {
            return;
        }

        registration.Activating += (sender, e) =>
        {
            Type type = e.Instance.GetType();

            if (e.Component.Services.OfType<IServiceWithType>().Any(swt => !swt.ServiceType.IsInterface || !swt.ServiceType.IsVisible) || 
                // prevent proxying the proxy 
                type.Namespace == "Castle.Proxies")
            {
                return;
            }

            var proxiedInterfaces = type.GetInterfaces().Where(i => i.IsVisible).ToArray();

            if (!proxiedInterfaces.Any())
            {
                return;
            }

            // intercept with all interceptors
            var theInterface = proxiedInterfaces.First();
            var interfaces = proxiedInterfaces.Skip(1).ToArray();

            e.Instance = generator.CreateInterfaceProxyWithTarget(theInterface, interfaces, e.Instance, interceptors);
        };
    }

    /// <summary>
    /// Get Activated event handlers for a registrations
    /// </summary>
    /// <param name="registration">Registration to retrieve events from</param>
    /// <returns>Array of delegates in the event handler</returns>
    private static Delegate[] GetActivatedEventHandlers(IComponentRegistration registration)
    {
        FieldInfo eventHandlerField = registration.GetType().GetField("Activated", BindingFlags.NonPublic | BindingFlags.Instance);
        var registrations = eventHandlerField.GetValue(registration);
        System.Diagnostics.Debug.WriteLine(registrations);
        return registrations.GetType().GetMethod("GetInvocationList").Invoke(registrations, null) as Delegate[];
    }
}

答案 2 :(得分:1)

通过利用构建器模式,可以非常轻松地将拦截器添加到所有构建器中。就像这样:

var interceptedBuilders = new List<IRegistrationBuilder<object, ConcreteReflectionActivatorData, SingleRegistrationStyle>>();
ContainerBuilder builder = new ContainerBuilder();

interceptedBuilders.Add(builder.RegisterType<First>().As<IFirst>());
interceptedBuilders.Add(builder.RegisterType<Second>().As<ISecond>());

foreach (var x in interceptedBuilders)
{              x.EnableInterfaceInterceptors().InterceptedBy(typeof(AInterceptor));
}