autofac / Autofac.Extras.DynamicProxy

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

Allowing interceptor parameters to be specified. #7

Closed roald-di closed 7 years ago

roald-di commented 8 years ago

Currently it's not possible to specify parameters to be used when resolving an interceptor service. I recently had a need for this and I thought that it might be useful for others as well.

One scenario would be to supply configuration options to the interceptor.

Usage would be as follows:

builder.RegisterType<Service>()
    .As<IService>()  
    .EnableInterfaceInterceptors()  
    .InterceptedBy(typeof(CachingInterceptor), TypedParameter.From(new InMemoryCache()), [...]);

or with attributes

[Intercept(typeof(LoggingInterceptor), LogLevel.Debug, [...])]
class Service : IService {}

I have a PR ready that we can discuss if this is of interest.

tillig commented 8 years ago

Was there a reason you couldn't use the existing registration mechanism to pass parameters?

builder.RegisterType<CachingInterceptor>()
  .WithParameter(TypedParameter.From(new InMemoryCache()))
  .Named<IInterceptor>("cache");
builder.RegisterType<Service>()
  .As<IService>()
  .EnableInterfaceInterceptors()
  .InterceptedBy("cache");

Even if you need different parameters on a per interceptor basis, you could register them with different names.

roald-di commented 8 years ago

What I was trying to do was to create an extension method on a registration that would enable caching but still allow for some configuration, something like this:

builder.RegisterType<Service>()
    .Cache(config => 
    {
        config.ForMethod(service => service.GetData()).Expires([..]);
    });

Now I agree with you that the same end result could still be obtained by putting the extension on the builder instead and passing in a name to wire things up, however I would find that a little awkward to use and harder not to make a mistake.

This is more a convenience feature than anything groundbreaking but I still wanted this.. :blush:

tillig commented 8 years ago

I think you could still do something really close to what you're looking for with a custom extension using stuff out of the box. Here's a full working console app example showing what I mean:

using System;
using System.IO;
using Autofac;
using Autofac.Builder;
using Autofac.Extras.DynamicProxy2;
using Castle.DynamicProxy;

namespace InterceptorExample
{
  class Program
  {
    static void Main(string[] args)
    {
      var builder = new ContainerBuilder();
      builder.Register(ctx => Console.Out).As<TextWriter>();

      // This shows how the extension would be called - very similar
      // to the way your example is described. Easy to modify the
      // semantics to take an Action<Options> instead of a concrete
      // parameter value.
      builder.RegisterType<Worker>().LogCalls(builder, "a");
      var container = builder.Build();

      using (var scope = container.BeginLifetimeScope())
      {
        var worker = scope.Resolve<Worker>();
        worker.DoWork();
      }

      Console.ReadKey();
    }
  }

  // Super simple interceptor based on the doc examples
  public class CallLogger : IInterceptor
  {
    TextWriter _output;
    string _prefix;

    public CallLogger(TextWriter output, string prefix)
    {
      _prefix = prefix;
      _output = output;
    }

    public void Intercept(IInvocation invocation)
    {
      _output.WriteLine("{0}: calling {1}", _prefix, invocation.Method.Name);
      invocation.Proceed();
    }
  }

  // Super simple class to intercept
  public class Worker
  {
    TextWriter _writer;

    public Worker(TextWriter writer)
    {
      _writer = writer;
    }

    public virtual void DoWork()
    {
      _writer.WriteLine("Doing work.");
    }
  }

  public static class InterceptionExtensions
  {
    public static IRegistrationBuilder<TLimit, ConcreteReflectionActivatorData, TRegistrationStyle>
      LogCalls<TLimit, TRegistrationStyle>(
        this IRegistrationBuilder<TLimit, ConcreteReflectionActivatorData, TRegistrationStyle> registration,
        ContainerBuilder builder,
        string prefix)
    {
      // Register a different unique interceptor for each wire-up so you can
      // provide different parameters as needed.
      var interceptorId = Guid.NewGuid().ToString();
      registration.EnableClassInterceptors().InterceptedBy(interceptorId);

      // You need the builder passed in so you can make a second registration
      // on the fly like this. Not perfect, but not too much to sacrifice.
      builder.RegisterType<CallLogger>().WithParameter("prefix", prefix).Named<IInterceptor>(interceptorId);
      return registration;
    }
  }
}
tillig commented 7 years ago

Looks like the example solved the problem. If not, consider opening another question on StackOverflow and tag it autofac. We have a lot of folks out there that can help with ideas on additional ways to use the existing extensibility points.