Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Core DI: Resolve same instance if scoped service is registered both as service type and implementation type

Say I have a service that I want to resolve with a scoped lifetime. But sometime I try to resolve it as the interface type and sometimes at the implementation type.

The first thing I tried to do was this:

ServiceCollection services;
services.AddScoped<MyClass>();
services.AddScoped<IMyInterface, MyClass>();

The problem with the above sample is that a different instance is used if I resolve IMyInterface, and than resolve MyClass. Basically it's possible that 2 scoped instances are alife at the same time.

I work around this issue in the following way. But it's very error-prone because you can easily forget to do this at one place, and it's really hard to notice.

serviceCollection.AddScoped<MyClass>();
serviceCollection.AddScoped<IMyInterface, MyClass>(sp => sp.GetRequiredService<MyClass>());

Is there any way to accomplish what I want in a way that is less error prone. Preferrably, but not necessarily, in a single registration?

I.e. as an xUnit test:

public class Tests
{
    [Fact]
    public void ReturnsSameInstanceForImplementationAndServiceType()
    {
        var serviceCollection = new ServiceCollection();

        // TODO: Change these lines so they're less error prone.
        serviceCollection.AddScoped<MyClass>();
        serviceCollection.AddScoped<IMyInterface, MyClass>(sp => sp.GetRequiredService<MyClass>());

        var services = serviceCollection.BuildServiceProvider();
        var myInt = services.GetRequiredService<IMyInterface>();
        var myCls = services.GetRequiredService<MyClass>();

        Assert.Equal(myCls, myInt);
    }

    class MyClass : IMyInterface { }
    interface IMyInterface { }
}
like image 719
Nicky Muller Avatar asked Oct 15 '25 15:10

Nicky Muller


1 Answers

One option would be to create your own extension method that wraps up the two lines you've shown in your question. For example:

public static class ServiceCollectionExtensions
{
    public static void AddScopedInterfaceAndClass<TInterface, TClass>(this IServiceCollection serviceCollection)
        where TInterface : class
        where TClass : class, TInterface
    {
        serviceCollection.AddScoped<TClass>();
        serviceCollection.AddScoped<TInterface, TClass>(sp => sp.GetRequiredService<TClass>());
    }
}

You could call this like so:

serviceCollection.AddScopedInterfaceAndClass<IMyInterface, MyClass>();

I appreciate that AddScopedInterfaceAndClass isn't the perfect name - it's just an example to demonstrate the idea. Also, there is still the downside that you'd have to remember to use this extension rather then AddScoped.

Note: You could simplify the second AddScoped in the extension method by removing the second generic (TClass) as this is inferred by the compiler.

like image 136
Kirk Larkin Avatar answered Oct 18 '25 08:10

Kirk Larkin