Passing multiple implementations of the same interface using DI
Asked Answered
O

1

2

One of our company's products is composed of many small web-applications and a windows service, a.k.a. components, each potentially residing in a different computer. One of them is a WebForms project, which serves as the configuration hub for all the others.

We are designing a feature now to expose general information of a component. Imagine a simple interface like this for instance:

public interface IStatistics
{
    Statistics GetStatistics();
}

We want to use this same interface on all components, so this is centralized on a common, shared assembly. The implementation will initially be the same also, so it is in this same assembly, alongside the interface.

The idea then is to expose a Wcf service on each component, using both the implementation and interface on the common assembly. The implementation uses environmental classes that return different things depending on where they are running, like local machine time.

The problem I would like to solve elegantly is how to pass to the webform all implementations of every component, using the same interface.

We currently use Unity, but I suppose I would face the same problem with any other DI solution. I would like to inject 5 implementations of this same interface (one for each component), and be able to differentiate them by component (think a Dictionary<Component, IStatistics>, where Component is an Enum). This is needed because there will be a dropdown on the page to select which component's information is visible, and the page would then call the correct implementation to retrieve the results.

I know I can use named registrations for all implementations and then inject them. Unfortunatelly, this would lead me to:

  1. Have 5 different parameters on the page, each pointing to a component
  2. Register each implementation on the container with a different name
  3. Explicitly register my webform on the container, with a custom InjectionConstructor specifying each registered interface, in the correct order, for the page injection method

I know Unity has a ResolveAll method and thus allows one to receive a IStatistics[] or IEnumerable<IStatistics>, but then I won't be able to differentiate them.

I think MEF solves this brilliantly with the concept of Metadata interfaces. Perhaps going MEF on this would be a way to solve this issue? I think it would be inappropriate here because these are wcf proxies we are talking about, I can't see how this would integrate with MEF at all.

Maybe using a factory would be a better strategy, but I fail to see how I'll inject the services on the factory also, so the problem would persist.

Ottoman answered 30/9, 2013 at 17:10 Comment(4)
If naming is the problem, why not decorate the IStatistics implementations with a DisplayNameAttribute? You can let your factory read those attributes and return an IEnumerable<KeyValuePair<string, IStatistics>> or something similar.Hardly
@Hardly Notice that in my question I stated that the implementation is also centralized in the common assembly, since it does not actually require specialization. Your approach would only work if I actually implemented it multiple times on each component. Currently, this would not make sense. Thanks for the insight though, it would probably be a nice approach to it otherwise.Ottoman
@julealgon, how will you know how many IStatistics proxies there are and what their display names be in the drop down list?Incise
@Tuzo it's currently hardwired by the number/position of parameters on the page, as I'm not passing an array or IEnumerable. The page itself knows that each parameter corresponds to a certain component, and associates it with an enum (a Dictionary<Enum, Proxy>. This enum value is what is displayed on the dropdown, and serves as a key to find the implementation when the service needs to be computed. Ideally, this would all be dynamic I think: the page would get all implementations, and them somehow show all possibilities on the dropdown, which in turn would call the correct service.Ottoman
T
2

I'm wondering, whether you could design the whole thing slightly different. I think what you're trying to do is a bit of against what IoC is about. If I understand you correctly you need five instances of the same type which you can somehow relate to five other objects of different types. 'IStatistics' is the interface, defined in a common assembly. The implementation, say Statistics is in the same assembly. The five instances of different types are defined in their own, 'local', assemblies which are resolved by DI and not directly referenced.

Here is an idea which might be more straightforward and achieves what you need, while remaining maintainable and extendable, especially if you need different implementations for each component type at some point:

UML

DefaultStatisticsProvider can be used by components to implement the IStatisticsProvider interface. There is no DI involved here, because the implementation is available in the common project.

public class DefaultStatisticsProvider : IStatisticsProvider
{
    public Statistics GetStatistics()
    {
        var statistics = new Statistics();
        // Generate statistics data
        return statistics;
     }
} 

The components implement IStatisticProvider directly and relay the methods to a private field of type DefaultStatisticsProvider which they initialize in their constructor:

public class ComponentA : IStatisticsProvider
{
    private readonly DefaultStatisticsProvider _statisticsProvider;

    public ComponentA()
    {
        _statisticsProvider = new DefaultStatisticsProvider();
    }

    Statistics IStatisticsProvider.GetStatistics()
    {
        // You could change this implementation later to
        // use a custom statistics provider
        return _statisticsProvider.GetStatistics();
    }
}

Now you can register your components as IStatisticsProvider directly and don't have to maintain some kind of artificial lookup table which relates types to statistic provider instances, because your types are statistic providers, which kind of makes sense to me in a logical way, too. Something like (pseudo code):

Container.Register<ComponentA>().As<IStatisticsProvider>();
Container.Register<ComponentB>().As<IStatisticsProvider>();

so that

Container.ResolveAll<IStatisticsProvider>();

will give you

{ Instance of ComponentA, Instance of ComponentB }

Plus, as mentioned, if you at some point need to have custom implementations for providing statistics, there is no redesign required at all. You just change the implementation of GetStatistics() in the component.

Tronna answered 1/10, 2013 at 7:23 Comment(2)
Nicely written answer ;). I'll take a deeper look at it later on, but the whole point of the OP was to avoid duplicating code unnecessarily. Since the implementation is the same, I would gain very little by implementing the interface on every component. Also, I wonder how you would provide the "dropdownlist" with components and call the correct implementation using this approach, could you perhaps elaborate a bit on that one? Maybe a combination of your idea and Steven's one, about putting an attribute over the implementation (or even a property on the interface, which I would try to avoid).Ottoman
Also, I would like to add that, currently, we actually have a different interface on each component, each one inheriting from the original interface in the shared assembly. This is only to be able to differentiate the registrations on the container and to pass all typed implementations to the webpage. This is undoubtedly a very bad solution, but it is working for what I need :(Ottoman

© 2022 - 2024 — McMap. All rights reserved.