I am using Autofac as my IoC in my WCF service. I have a situation where I want to pass an object to a nested type (ie a type that is not resolved directly, but when resolving another type). As far as I understood, passing this object as a constructor parameter is the preferred way in Autofac. Here is an example of such a situation.
The nested type:
public class EventLogger<T> : IEventLogger<T>
{
public EventLogger(IRepository<T> repository, User currentUser) { ... }
}
The type I am actually trying to resolve:
public class SomeBusinessObject
{
public SomeBusinessObject(IEventLogger<SomeLogEventType> logger, ...) { ... }
}
The registration:
var builder = new ContainerBuilder();
builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>));
builder.RegisterGeneric(typeof(EventLogger<>)).As(typeof(IEventLogger<>));
builder.RegisterType<SomeBusinessObject>();
The resolving inside my WCF service operation:
var currentUser = GetUserFromServiceContext();
var bo = lifetimeScope.Resolve<SomeBusinessObject>();
How and where should I pass the current user to my logger? Should I assume that the WCF operation has to know that resolving SomeBusinessObject requires to resolve IEventLogger first and pass a resolved instance when resolving SomeBusinessObject? Something like this (pardon me if this does not work, it is just an idea):
var currentUser = GetUserFromServiceContext();
var logger = lifetimeScope.Resolve<IEventLogger<SomeLogEventType>>(new NamedParameter("currentUser", currentUser));
var bo = lifetimeScope.Resolve<SomeBusinessObject>(new NamedParameter("logger", logger));
If this is the solution, what happens if the type is nested deeper? Doesn't that defeat at least some of the purpose of dependency injection?
IMHO, I think you're violating one of the principles of IOC in that a component should not need to know about the dependencies of it's dependencies. In your case, the container doesn't know that SomeBusinessObject
has a dependency on User
.
That being said, you may be able to leverage Autofac's Delegate Factories. You could manually register a Func<User, SomeBusinessObject>
to hide the dependency chain details from the client code:
var builder = new ContainerBuilder();
builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>));
builder.RegisterGeneric(typeof(EventLogger<>)).As(typeof(IEventLogger<>));
builder.RegisterType<SomeBusinessObject>();
builder.Register<Func<User, SomeBusinessObject>>(c => {
// Autofac should be able to resolve these Func<> automatically:
var loggerFactory = c.Resolve<Func<User, IEventLogger<SomeLogEventType>>>();
var sboFactory = c.Resolve<Func<IEventLogger<SomeLogEventType>, SomeBusinessObject>>();
// Now we can chain the Funcs:
return u => sboFactory(loggerFactory(u));
});
Now in your client code, you can do:
var currentUser = GetUserFromServiceContext();
var sboFactory = lifetimeScope.Resolve<Func<User, SomeBusinessObject>>();
var bo = sboFactory(currentUser);
As an aside, I think the lamba/Func support is what makes Autofac the best IOC container. You can do some crazy powerful things if you know how to compose Funcs.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With