Search code examples
c#.netdependency-injectionwrapper

Is it possible to wrap (replace) a registered service with my own but still keep the old instance?


Assuming I have this service registration:

services.AddScoped<IFoo, Foo>();

I know you can "Replace service registration in ASP.NET Core built-in DI container?" but is it possible to replace it but still keep the former instance intact like this?

services.AddScoped<IFoo>(sp => new MyFooWrapper(/* I need the original Foo here */));

This would be helpful in case I am performing a unit test where I can decide if I want to replace, for example, an external API call or not:

public class MyFooWrapper(IFoo wrapped) : IFoo
{
    
    public void DoSomething()
    {
        if (TestingWithExternal)
        {
            wrapped.DoSomething(); // Original method that calls external HTTP API
        }
        else
        {
            FakeSomething();
        }
    }

}

Another use-case is that I want to wrap Blazor's IJSRuntime since MS makes it very difficult to change its behaviors as the implementation is internal and re-implement everything is just very difficult in case they update it.


Thanks to the answer, this is the code that can replace multiple services as well

// Replace with fakes
foreach (var s in services.ToList())
{
    // Other logic
    if (s.ServiceType == typeof(IOaApiService))
    {
        services.Remove(s);

        var implType = s.ImplementationType;
        ArgumentNullException.ThrowIfNull(implType);

        services.Add(new(implType, implType, s.Lifetime));
        services.Add(new(
            s.ServiceType,
            sp => new FakeOaAiService((IOaApiService)sp.GetRequiredService(implType)),
            s.Lifetime));
    }
}

Solution

  • You can register wrapped Foo service as itself, i.e.:

    service.AddScoped<Foo>();
    

    Then, when registering wrapper class, use ServiceProvider to get registered instance:

    services.AddScoped<IFoo>(sp => new MyFooWrapper(sp.GetRequiredService<Foo>()));
    

    This way, you will replace entirely all Foo with its wrapper. If you want to still use both implementations, you have to separate them at interface level (define IFooWrapper) or use keyed services (but it's latest feature in .NET).