Vadym Chekan
Vadym Chekan

Reputation: 5157

Replace registration in Autofac

I have an application which does data processing. There is

class Pipeline {
  IEnumerable<IFilter> Filters {get; set;}

I register filters implementations as

builder.RegisterType<DiversityFilter>().As<IFilter>();
builder.RegisterType<OverflowFilter>().As<IFilter>();
...

So far so good. Now, for experimentation and fine-tuning I want to be able to override any filter implementation in config file with a program(script) which would read data from stdin, process it and send data to stdout. I've implemented a module with "fileName", "args" and "insteadOf" custom properties, described module in xml and got it called.

In the module I register my "ExecutableFilter" but how do I make it run "instead of" desired service? If I try do it like this:

builder.RegisterType<ExecutableFilter>().As<DiversityFilter>()

then I get an exception " The type 'ExecutableFilter' is not assignable to service 'DiversityFilter'.". Ok, this is logical. But what are my options then?

Upvotes: 8

Views: 12350

Answers (3)

Nicholas Blumhardt
Nicholas Blumhardt

Reputation: 31757

Once you've overridden the registration for IFilter "After" with your wire-tap, you won't be able to resolve it from the container, as the new registration will be activated instead, hence the circular lookup.

Instead, create and register a module that hooks into the filter's creation, and replaces the instance with the 'wire tapped' one:

class WiretapModule : Module
{
  override void AttachToComponentRegistration(
           IComponentRegistration registration,
           IComponentRegistry registry)
  {
    if (registration.Services.OfType<KeyedService>().Any(
          s => s.ServiceKey == After && s.ServiceType == typeof(IFilter))) 
    {
      registration.Activating += (_, e) =>
      {
        e.Instance = new WireTap(
            (IFilter)e.Instance,
            new ExecuteProvider(fileName, args));
      };
    }
  }
}

(Cross-posted to the Autofac group: https://groups.google.com/forum/#!topic/autofac/yLbTeuCObrU)

Upvotes: 11

Manuel Amstutz
Manuel Amstutz

Reputation: 1388

It's been a while since this question was asked, but I think there is now a simpler solution.

You can register a decorator with a condition. It doesn't matter if the "decorator" is really a decorator or a completely new implementation

 builder.RegisterDecorator<ExecutableFilter, IFilter>(c => c.ImplementationType == typeof(DiversityFilter));

This way the concrete type 'DiversityFilter' will be replaced with 'ExecutableFilter'

Upvotes: 0

Peter Lillevold
Peter Lillevold

Reputation: 33910

What you describe is part container work, part business logic. The challenge is to keep separation of concerns here. IMO, the container should do what it is supposed to do, that is building and serving up instances or collections thereof. It should not do the "instead of" in this case. I would rather "enrich" the services with enough information so that the pipeline make the decision.

The "enrichment" can be accomplished by making the ExecutableFilter implement a more distinct interface.

interface IInsteadOfFilter : IFilter { }

...

builder.RegisterType<ExecutableFilter>().As<IFilter>();

...

class Pipeline
{
    IEnumerable<IFilter> Filters {get;set;}

    public void DoTheFiltering()
    {
        var filters = Filters.OfType<IInsteadOfFilter>();
        if (!insteadof.Any())
            filters = Filters;

        foreach(var filter in filters)
        {
            ...
        }
    }

You could also solve this using the metadata infrastructure, which gives us an even more expressive way of differentiating services.

Upvotes: 2

Related Questions