Closed henriblMSFT closed 3 years ago
If I understand correctly, you are trying to wrap each IRootService<T>
implementation in a generic AdapterService<T>
and inject them as a collection of IAdapterService<T>
into a consumer.
This is not something that is very easy to achieve (not impossible, but it's not something I can present from the top of my head, so it will certainly take more code to achieve).
Instead, would it be a possibility for you to, besides applying the Adapter design pattern, apply the Composite design pattern as well? This allows consumers to depend on a single IAdapterService<T>
instead of an IEnumerable<IAdapterService<T>>
. This would severely simplify your scenario, because that would lead to a situation similar to your AdapterServiceCollection<T>
:
public class AdapterRootServiceComposite<T> : IAdapterService<T>
{
public AdapterService(IEnumerable<IRootService<T>> roots) { }
public void Adapt()
{
foreach (var root in this.root)
{
// Do something useful with all roots.
}
}
}
// Registration
container.Collection.Register(typeof(IRootService<>), typeof(IRootService<>).Assembly);
container.Register(
typeof(IAdapterService<>),
typeof(AdapterRootServiceComposite<>),
Lifestyle.Singleton);
container.GetInstance<IAdapterService<int>>().Adapt();
Let me know if that would work for you.
Unfortunately we need to support a decorator for each instance of IAdapterService<T>
so the composite pattern didn't work well in this case.
I went ahead and fully implemented option 2 registering events through unregistered type and it seems to work. I'm assuming this code would not work if the underlying collection was uncontrolled collection but I'm ok with this limitation.
I also can't seem to support registering anything else then IEnumerable<IAdapter<T>>
as I couldn't find a way to create a IReadOnlyList<>
registration for container controller collection.
That's an interesting problem you have at hand. It took me some time to find a suitable solution. My initial tries failed because I tried to use features that are internal to Simple Injector. But I found a somewhat elegant solution. This solution, however, disallows decorating IRootService<T>
implementations. If you need this as well, let me know, I'll go back to the drawing board :-)
In order for it to work, you have to change your adapter implementation to the following:
public class AdapterService<T, TRoot> : IAdapterService<T> where TRoot : IRootService<T>
{
public AdapterService(TRoot root) { }
}
I'll explain in a moment why adding this extra generic argument solves the problem. But first, the registrations:
var rootTypes =
container.GetTypesToRegister(typeof(IRootService<>), this.GetType().Assembly);
container.Collection.Register(typeof(IAdapterService<>),
from implementation in rootTypes
from serviceType in implementation.GetClosedTypesOf(typeof(IRootService<>))
let genericTypeArgument = serviceType.GetGenericArguments()[0]
select typeof(AdapterService<,>).MakeGenericType(genericTypeArgument, implementation));
foreach (var type in rootTypes) container.Register(type);
// Register some decorators for IAdapterService<T>
container.RegisterDecorator(typeof(IAdapterService<>), typeof(AdapterServiceDecorator<>));
I created the following three test classes to verify its working:
// Notice that each type implements multiple interfaces. Not sure if you need this.
// But to be sure.
public class RIntDouble : IRootService<int>, IRootService<double> { }
public class RIntChar : IRootService<int>, IRootService<char> { }
public class RDoubleChar : IRootService<double>, IRootService<char> { }
And when I inspect the container's RootRegistrations
in the debugger, Simple Injector visualizes these collections as follows:
IEnumerable<IAdapterService<int>>( // Singleton
AdapterServiceDecorator<int>( // Transient
AdapterService<int, RIntDouble>( // Transient
RIntDouble())), // Transient
AdapterServiceDecorator<int>( // Transient
AdapterService<int, RIntChar>( // Transient
RIntChar()))) // Transient
Which roughly translates to the following C#:
new IAdapterService<int>[]
{
new AdapterServiceDecorator<int>(
new AdapterService<int, RIntDouble>(
new RIntDouble())),
new AdapterServiceDecorator<int>(
new AdapterService<int, RIntChar>(
new RIntChar())),
}
So what is going on here. Let's disassemble this:
var rootTypes =
container.GetTypesToRegister(typeof(IRootService<>), this.GetType().Assembly);
Container.GetTypesToRegister
is a helper method that allows Simple Injector to go look for all implementations of a given (generic) interface. GetTypesToRegister
is internally used when you register a collection by supplying an assembly. This call results in a list of all concrete, non-generic IRootService<T>
implementations in the given assembly.
The next call is the meat and potatoes of the example:
container.Collection.Register(typeof(IAdapterService<>),
from implementation in rootTypes
from serviceType in implementation.GetClosedTypesOf(typeof(IRootService<>))
let genericTypeArgument = serviceType.GetGenericArguments()[0]
select typeof(AdapterService<,>).MakeGenericType(genericTypeArgument, implementation));
Given the three previously shown example classes, the above call is the equivalent of this:
container.Collection.Register(typeof(IAdapterService<>), new Type[]
{
typeof(AdapterService<int, RIntDouble>),
typeof(AdapterService<double, RIntDouble>),
typeof(AdapterService<int, RIntChar>),
typeof(AdapterService<char, RIntChar>),
typeof(AdapterService<double, RDoubleChar>),
typeof(AdapterService<char, RDoubleChar>),
});
Which, again, whould be the equivalent of having three seperate registrations:
container.Collection.Register(typeof(IAdapterService<int>), new Type[]
{
typeof(AdapterService<int, RIntDouble>),
typeof(AdapterService<int, RIntChar>),
});
container.Collection.Register(typeof(IAdapterService<double>), new Type[]
{
typeof(AdapterService<double, RIntDouble>),
typeof(AdapterService<double, RDoubleChar>),
});
container.Collection.Register(typeof(IAdapterService<char>), new Type[]
{
typeof(AdapterService<char, RIntChar>),
typeof(AdapterService<char, RDoubleChar>),
});
The TRoot
generic type argument of AdapterService<T, TRoot>
is used as constructor argument:
public AdapterService(TRoot root)
By registering a different type per root service implementation, it allows Simple Injector knows what to inject into each implementation. TRoot
will become, for instance, the RIntChar
implementation. Using the IRootService<T>
abstaction as constructor argument wouldn't work, because there will be more IRootService<T>
implementations to choose from.
The concequence of this, however, is that because the concrete type is forced into the constructor, no decorators around IRootService<T>
can be applied with this solution.
Because the adapter implementations, such as AdapterService<char, RIntChar>
depend on a concrete type, e.g. RIntChar
, those concrete types must be registered in Simple Injector as well. This is what the following calls achieves:
foreach (var type in rootTypes) container.Register(type);
I hope you find this useful or at least informative.
Oh that's clever, we don't currently have a need to decorate the root service so this will do nicely. Thanks
I'm trying to support the creation of generic adapter that works with all type of supported collection and I'm looking for advice on how to best implement this. I have a few ideas but none of them feel ideal.
Essentially I want to be able to register a collection of ServiceA and resolve it as ServiceB
given the following service type:
ideally I'd like to be able to do this:
While a generic way of doing it would be ideal I'm open to other ideas on how to accomplish this.
I've considered creating my own collection and registering that directly with the container, however it then becomes impossible to register decorators through simple injector
I've also considered using an UnregsiteredTypeResolution to create a container controlled collection registration based on the existing collection but I'm unsure how to register anything else but an IEnumerable<> as container.Collection.CreateRegistration always returns an IEnumerable registration.
Would there be a simpler method to accomplish this that I'm not thinking of?