Search code examples
c#genericsdependency-injectionautofaccomposite

How to inject collection with open generic types to service with Autofac


I've tried to explain my admittedly complex problem to the best of my abilities. Let me know if there is anything I can add to clarify.

A brief background

I have a DbWrapperCollection I use to store DbWrapper<TInput. TOutput> (since TInput and TOutput will vary, the collection is really just a list of non-generic “containers” containing the generic as an object as well as the input and out as System.Types – see my implementation below)

On the other hand, I have a variable number of services, all with their own IDbWrapperCollection that I want to inject with autofac at startup.

Essentially what I want to do is this:

builder.RegisterType<SaveApplDbWrapper>().As<DbWrapper<AppleDto, SavedAppleDbEntity>>()
    .Named<string>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>().As<IUcHandler<OrangeDto, OrangeDbEntity>>()
    .Named<string>("fruitService");
builder.RegisterType<SaveMelon>().As<IUcHandler<MelonDto, MelonDbEntity>>()
    .Named<string>("appleService");

builder.Register(c => new FruitService(c.ResolveNamed("appleService")))
    .As<IDbMapperService>();

My problem

As you can see above, I specifically left out the expected type parameters when calling ResolveNamed(). That’s because I, being new to autofac (and to some extent, generics), I specifically don’t know if there are any strategies to inject a list of open generic DbWrappers and defer closing of my generic type.

I will try to explain which strategies i've researched for dealing with this below, as well as my implementation so far

My own research

The way I see it, I could either create a non-generic baseclass for my wrappers and save them as that baseclass, delegating the responsibility of resolving the original generic type to that baseclass, or ditch my wrapper collection idea in favor of specific parameters on my service constructor (boring – and not compatible with my composite-inspired implementation).

With the popularity of composite pattern, I’d imagine I’m not the first person with a composite-pattern-like solution with “generic leafs” wanting to use DI and an IoC.

I’m planning to use my fruitService like this:

myFruitService.GetDbMapper<MyFruitDto, DbEntityForThatSaidFruit(myOrange);

The service looks in it’s DbMapperCollection, finds the mapper with the provided type arguments, and call its implementation of Save();

Implementation so far

For those curious, here is my implementations:

DbWrappers:

class SaveApplebWrapper : DbWrapper<TInput, TOutput>
// and plenty more wrapppers for any fruit imaginable

Service:

public abstract class DbMapperService : IDbMapperService
{
    public IWrapperCollection Wrappers { get; set; }

    protected BestandService(IWrapperCollection wrappers)
    {
        Wrappers = wrappers;
    }

    public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
    {
        return Wrappers.GetWrapper<TInput, TResult>();
    }
}

My WrapperCollection helper classes:

public struct WrapperKey
{
    public static WrapperKey NewWrapperKey <TInput, TResult>()
    {
        return new WrapperKey { InputType = typeof(TInput), ResultType = typeof(TResult) };
    }

    public Type InputType { get; set; }
    public Type ResultType { get; set; }
}

public struct WrapperContainer
{
    public WrapperContainer(object wrapper) : this()
    {
        Wrapper= wrapper;
    }

    public object Wrapper{ get; set; }

    public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
    {
        return Wrapper as DbWrapper<TInput, TResult>;
    }
}

And my WrapperCollection:

public class UcWrapperCollection : Dictionary<WrapperKey, WrapperContainer>,
    IDbWrapperCollection
{
    public void AddWrapper<TInput, TResult>(UcHandler<TInput, TResult> handler)
    {
        Add(WrapperKey.NewWrapperKey<TInput, TResult>(), new WrapperContainer(handler));
    }

    public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
    {
        var key = WrapperKey.NewWrapperKey<TInput, TResult>();
        return this[key].GetWrapper<TInput, TResult>();
    }
}

Questions I've looked at without luck

Some questions I've looked at, none of which seemed relevant to my case (although my problem could potentially be solved with a generic delegate, I don't think it's a very optimal solution for my problem.

  • Injecting Generic type parameters with Autofac
  • Autofac. How to inject a open Generic Delegate in constructor
  • How to inject a factory of generic types with Autofac
  • Autofac with nested open generics

Solution

  • I don't think you're going to be able to do what you want. Sorry, probably not the answer you'd like. I'll show you why, and maybe some workarounds, but having an arbitrary collection of closed generics that don't get closed until resolution isn't really a thing.

    Let's ignore DI for a second and just consider FruitService, which I don't see in the question, but which we see in a usage here:

    builder.Register(c => new FruitService(c.ResolveNamed("appleService")))
        .As<IDbMapperService>();
    

    Note we can see that FruitService implements IDbMapperService because it's registered as that interface.

    Further, we can see FruitService looks like it should take some sort of collection of things, since there are two things named the same in the registration example.

    builder.RegisterType<SaveApplDbWrapper>().As<DbWrapper<AppleDto, SavedAppleDbEntity>>()
        .Named<string>("fruitService");
    builder.RegisterType<SaveOrangeDbWrapper>().As<IUcHandler<OrangeDto, OrangeDbEntity>>()
        .Named<string>("fruitService");
    

    I noticed that these both implement different generic types. I have to assume based on the rest of the question that these have no common base class.

    To make it more concrete and get past the Autofac part, which I don't think is really relevant to the larger issue, let's consider it like this:

    var wrapper = new[] { CreateWrapper("appleService"), CreateHandler("appleService") };
    var service = new FruitService(wrapper);
    

    Let's assume CreateWrapper and CreateHandler both take a string and, magically, creates the appropriate wrapper/handler types. Doesn't matter how it happens.

    There are two things to consider here that relate closely:

    • What is the type of the parameter in the FruitService constructor?
    • What do you expect CreateWrapper("appleService") and CreateHandler("appleService") to return?

    There are basically only two options here I can see.

    Option 1: Use object.

    If there's no common base class, then everything has to be object.

    public class FruitService : IDBMapperService
    {
      private readonly IEnumerable<object> _wrappers;
      public FruitService(IEnumerable<object>wrapper)
      {
        this._wrapper = wrapper;
      }
    
      public object GetWrapper<TInput, TResult>()
      {
        object foundWrapper = null;
        // Search through the collection using a lot of reflection
        // to find the right wrapper, then
        return foundWrapper;
      }
    }
    

    It's not clear that DbWrapper<TInput, TResult> can be cast to IUcHandler<TInput, TResult> so you can't even rely on that. There's no commonality.

    But let's say there is common base class.

    Option 2: Use the common base class

    It seems there's already a notion of DbWrapper<TInput, TResult>. It's important to note that even if you have that generic defined, once you close it, they're two different types. DbWrapper<AppleDto, SavedAppleDbEntity> is not castable to DbWrapper<OrangeDto, SavedOrangeDbEntity>. Generics are more like "class templates" than base classes. They're not the same thing.

    You can't, for example, do:

    var collection = new DbWrapper<,>[]
    {
      new DbWrapper<AppleDto, SavedAppleDbEntity>(),
      new DbWrapper<OrangeDto, SavedOrangeDbEntity>()
    };
    

    However, if you have a common interface or base class, you can do...

    var collection = new IDbWrapper[]
    {
      new DbWrapper<AppleDto, SavedAppleDbEntity>(),
      new DbWrapper<OrangeDto, SavedOrangeDbEntity>()
    };
    

    But that'd mean you can switch to that and, ostensibly, use the common interface.

    public class FruitService : IDBMapperService
    {
      private readonly IEnumerable<object> _wrappers;
      public FruitService(IEnumerable<object>wrapper)
      {
        this._wrapper = wrapper;
      }
    
      public IDbWrapper GetWrapper<TInput, TResult>()
      {
        IDbWrapper foundWrapper = null;
        // Search through the collection using a lot of reflection
        // to find the right wrapper, then
        return foundWrapper;
    
        // IDbWrapper could expose those `TInput` and `TResult`
        // types as properties on the interface, so the reflection
        // could be super simple and way more straight LINQ.
      }
    }
    

    Your consuming code could just take IDbWrapper and call non-generic methods to get things done.

    Bringing it back to Autofac...

    Remember I mentioned the key was in figuring out what the Create methods should return; or what the FruitService constructor expects? That. That in spades.

    You could register everything as keyed objects.

    builder.RegisterType<SaveApplDbWrapper>()
           .Named<object>("fruitService");
    builder.RegisterType<SaveOrangeDbWrapper>()
           .Named<object>("fruitService");
    builder.RegisterType<SaveMelon>()
           .Named<object>("appleService");
    
    builder
      .Register(c => new FruitService(c.ResolveNamed<IEnumerable<object>>("appleService")))
      .As<IDbMapperService>();
    

    The Resolve operations in Autofac are the create methods from my example. There's no magic there; it's just creating objects. You still have to know what type you want it to provide.

    Or you could use a common base class.

    builder.RegisterType<SaveApplDbWrapper>()
           .Named<IDbWrapper>("fruitService");
    builder.RegisterType<SaveOrangeDbWrapper>()
           .Named<IDbWrapper>("fruitService");
    builder.RegisterType<SaveMelon>()
           .Named<IDbWrapper>("appleService");
    
    builder
      .Register(c => new FruitService(c.ResolveNamed<IEnumerable<IDbWrapper>>("appleService")))
      .As<IDbMapperService>();
    

    If you don't mind mixing the DI system into the FruitService you can do something like this:

    public class FruitService
    {
      private readonly ILifetimeScope _scope;
      public FruitService(ILifetimeScope scope)
      {
        this._scope = scope;
      }
    
      public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
      {
        var type = typeof(DbWrapper<TInput, TResult>);
        var wrapper = this._lifetimeScope.Resolve(type);
        return wrapper;
      }
    }
    

    You'd have to register things without them being named and As a DbWrapper, but it'd work if everything is based on that.

    builder.RegisterType<SaveApplDbWrapper>()
           .As<DbWrapper<AppleDto, SavedAppleDbEntity>>();
    // Must be DbWrapper, can also be other things...
    builder.RegisterType<SaveOrangeDbWrapper>()
           .As<IUcHandler<OrangeDto, OrangeDbEntity>>()
           .As<DbWrapper<OrangeDto, OrangeDbEntity>>();
    builder.RegisterType<SaveMelon>()
           .As<DbWrapper<MelonDto, MelonDbEntity>>()
           .As<IUcHandler<MelonDto, MelonDbEntity>>();
    
    builder.RegisterType<FruitService>()
           .As<IDbMapperService>();
    

    When you resolve IDbMapperService the FruitService constructor will get a reference to the lifetime scope from which it was resolved. All of the wrappers will be resolved from that same scope.

    Folks generally don't like mixing IoC references into their code like this, but it's the only way I can see you'd get away from having to mess with reflection or casting up and down all over.

    Good luck!