views:

248

answers:

1

I'm using Autofac. I want to inject a different implementation of a dependency based on an attribute I apply to the constructor parameter. For example:

class CustomerRepository
{
    public CustomerRepository([CustomerDB] IObjectContainer db) { ... }
}

class FooRepository
{
    public FooRepository([FooDB] IObjectContainer db) { ... }
}

builder.Register(c => /* return different instance based on attribute on the parameter */)
       .As<IObjectContainer>();

The attributes will be providing data, such as a connection string, which I can use to instance the correct object.

How can I do this?

+2  A: 

It sounds like you want to provide different implementations of IObjectContainer to CustomerRepository and FooRepository. If that is the case, attributes might be a thin metal ruler. Instead, I'll show you how I would implement multiple implementations with Autofac.

(Calls such as .ContainerScoped() have been left out for brevity.)

First, register a version of IObjectContainer for each connection string by naming the registrations:

builder
    .Register(c => new ObjectContainer(ConnectionStrings.CustomerDB))
    .As<IObjectContainer>()
    .Named("CustomerObjectContainer");

builder
    .Register(c => new ObjectContainer(ConnectionStrings.FooDB))
    .As<IObjectContainer>()
    .Named("FooObjectContainer");

Then, resolve the specific instances in the repository registrations:

builder.Register(c => new CustomerRepository(
    c.Resolve<IObjectContainer>("CustomerObjectContainer"));

builder.Register(c => new FooRepository(
    c.Resolve<IObjectContainer>("FooObjectContainer"));

This leaves the repositories free of configuration information:

class CustomerRepository
{
    public CustomerRepository(IObjectContainer db) { ... }
}

class FooRepository
{
    public FooRepository(IObjectContainer db) { ... }
}
Bryan Watts
Thanks for the great answer. You raise a fair point about keeping the repositories free of configuration. However, I still wish that AutoFac could provide a way to be context sensitive about selecting services. I know Ninject can do this.I might go down the route of injecting a Func<connection_string, IObjectContainer> instead. That way the repository can decide what it wants.
Andrew Davey
Autofac (lower-case f) can support literally any type of custom context-sensitive construction you want. This is because `Register<T>` can accept a lambda expression (`Func<>`), and you can build lambda expressions with Expression Trees (http://msdn.microsoft.com/en-us/library/bb397951.aspx). This means you can use reflection to build whatever lambda you need to run the custom construction.
Bryan Watts
It seems you want repositories to be coupled to connection strings, either via attributes or arguments to a `Func<string, IObjectContainer>`. I ask in response: why does the repository need to know a connection string? If one is needed to create an `IObjectContainer`, isn't that the responsibility of the configuration of *that* type? Why does a repository even need to know a connection string is involved at all? Why not simply accept `IObjectContainer`?
Bryan Watts
My concern is for the understandability of each repository. The constructor parameter IObjectContainer is not very intention revealing, which is why I'm interested in annotating with an attribute to provide extra meta-data. It seems more intention revealing to me.I'm not actually going to have real connection strings in the attributes or repository implementations. It's more that I want to provide meta-data that will let something else determine the correct object container to inject.
Andrew Davey
I think my solution here is to reflect over my repository types and register each one individually, using the attribute to determine the object container required.
Andrew Davey
The point of IoC/DI is to remove the configuration of your object graph from the individual objects involved. As soon as you add configuration metadata to an object, you go directly against the precepts of DI. You can accomplish this scenario with completely external configuration of each repository, so why require anything more of the repository?
Bryan Watts
I understand you feel the attributes are more intention-revealing. I ask you describe the revealed intentions in concrete, specific terms. The fact that the `IObjectContainer` needs to be configured to support `Foo` objects is immediately apparent by its usage in a `FooRepository`. I don't see what else the attributes bring to the table besides extra complexity and coupling. I would ask yourself whether you might be a bit too attached to the attribute idea, since my original approach is the official, recommended way to enable multiple implementations with Autofac.
Bryan Watts
I also understand your desire to use reflection to register the repositories. While less code is a worthwhile goal, abstracting away container registrations is usually more trouble than it's worth. Honestly, since you have a known set of repositories, you are much better off simply using the straightforward registration mechanism I used in my post. Concentrate your efforts on the actual business problem you are solving with your code.
Bryan Watts