4

Please feel free to suggest a better title for the question. I can't figure out a good name for describing the problem.

I was in the need to make an class accessible via the dependency injection on startup. The class should not only be available via it's concrete implementation but also via the interface it implements. And I need to ensure it is the same object instance returned via both injections.

The real worlds scenario that led me to the singleton case was a nuget for abstractions providing the interface (IStore), multiple nugets holding concrete implementations (DBStore, RedisStore). When I tried to implement the health checks for each store implementation I could get IStore injected but not the concrete implementation. And I wanted to use some variables that were initialized and modified in the concrete implementation (thats why I need the same instance for both injections). Since the stores are (hopefully) used as singletons it worked. I am not saying there is a real world scenario for the scoped and transient way. I am just curious if that would be possible if they are not singletons.

The following codes describe how I managed to do this with singletons.

The way that led me to the singleton solution:

Having this interface:

public interface ITestInterface
{
  string ReturnAString();
  int ReturnAnInt(); 
}

and this concrete implementation

public class TestImplementation : ITestInterface
{
  private int counter = 0;
  public string ReturnAString() {return "a string"; }
  public int ReturnAnInt() { return counter++; }
}

They are used in two (let's say) services. One wants the interface injected in the constructor and the other needs the concrete implementation.

Trial and errors in the Startup.ConfigureServices method for making the same instance injected in both cases:

Try 1:

// only ITestInterface is injected but not TestImplemenation
services.AddSingleton<ITestInterface, TestImplementation>();

Try 2:

//only TestImplementation is injected (DI does not recognize it implements the Interface)
services.AddSingleton<TestImplementation>();

Try 3:

// both are injected but they are not singleton any more (counters increment independently)
services.AddSingleton<ITestInterface, TestImplementation>();
services.AddSingleton<TestImplementation, TestImplementation>();

Try 4:

TestImplementation instance = new TestImplementation();
services.AddSingleton<ITestInterface>(instance);
services.AddSingleton(instance);
//services.AddSingleton<TestImplementation>(instance);

Well, at try 4 I have the same instance for both injections.

Now lets assume the TestImplementation needs some (e.g. connection) settings injected.

I can write an extension method for getting the settings from the Configuration and pass it to the singleton instance.

TestImplementation instance = new TestImplementation(Configuration.GetTestSettings());
services.AddSingleton<ITestInterface>(instance);
services.AddSingleton(instance);

So how would I achieve that both injections are the same instance using the same settings with scoped or transient? Since I don't think I can create the instance by hand/code there.

monty
  • 7,888
  • 16
  • 63
  • 100
  • I had similar requirements and I came across the blog post from Andre Lock which describes how to achieve the same using ASP.NET Core DI. Here is the link - https://andrewlock.net/how-to-register-a-service-with-multiple-interfaces-for-in-asp-net-core-di/ – tyrion Oct 25 '20 at 18:34

3 Answers3

5

Basically you want to register a single service implementation type as two service contracts (concrete class + interface). This is pretty common case but unfortunately the default Microsoft DI container (ServiceCollection) has limited functionality and the only way I see to achieve the desired effect is to use factory delegates:

services.AddScoped<TestImplementation>();
services.AddScoped<ITestInterface>(s => s.GetRequiredService<TestImplementation>());

Although this will do the trick (for some additional runtime cost) I would highly recommend to use one of the full-featured DI containers such as Autofac or Ninject

Max
  • 51
  • 3
1

If I'm not wrong, Autofac is capable of doing what you wish through the use of decorators.

However, adding a dependency to a 3rd party lib is often undesirable or even prohibited due to company policies.

In that case, I think you would be better off creating a factory. For instance:

public class TestFactory
{
    public ITestInterface Create(string flavor)
    {
        if (flavor == "concrete")
        {
            return new TestImplementation();
        }
        else
        {
            return new OtherTestImplementation();
        }
    }
}

And then, in your Startup.cs, you would inject it like:

services.AddSingleton<TestFactory>();

Note that, ultimately, the ITestInterface service lifetime would be dependent on where you would store the reference to the result of the Create(...) method invocation.

Gabriel Lima
  • 348
  • 3
  • 11
1

Using Autofac you will able to add both the Interface and the Implementing interface using the AsSelf() method:

container.RegisterType<TestImplementation>.As<ITestInterface>().AsSelf();

See this answer for more explanations.

In your case - using it as a Singleton with: SingleInstance().

container.RegisterType<TestImplementation>.As<ITestInterface>().AsSelf().SingleInstance();
Shahar Shokrani
  • 7,598
  • 9
  • 48
  • 91