Asp.Net Core: register implementation with multiple interfaces and lifestyle Singleton

C#Dependency Injectionasp.net CoreInversion of-Control

C# Problem Overview


Considering the following interface and class definitions:

public interface IInterface1 { }
public interface IInterface2 { }
public class MyClass : IInterface1, IInterface2 { }

is there any way to register one instance of MyClass with multiple interfaces like this:

...
services.AddSingleton<IInterface1, IInterface2, MyClass>();
...

and resolve this single instance of MyClass with different interfaces like this:

IInterface1 interface1 = app.ApplicationServices.GetService<IInterface1>();
IInterface2 interface2 = app.ApplicationServices.GetService<IInterface2>();

C# Solutions


Solution 1 - C#

The service collection by definition is a collection of ServiceDescriptors, which are pairs of service type and implementation type.

You can however get around this by creating your own provider function, something like this (thanks user7224827):

services.AddSingleton<IInterface1>();
services.AddSingleton<IInterface2>(x => x.GetService<IInterface1>());

More options below:

private static MyClass ClassInstance;

public void ConfigureServices(IServiceCollection services)
{
    ClassInstance = new MyClass();
    services.AddSingleton<IInterface1>(provider => ClassInstance);
    services.AddSingleton<IInterface2>(provider => ClassInstance);
}

Another way would be:

public void ConfigureServices(IServiceCollection services)
{
    ClassInstance = new MyClass();
    services.AddSingleton<IInterface1>(ClassInstance);
    services.AddSingleton<IInterface2>(ClassInstance);
}

Where we just provide the same instance.

Solution 2 - C#

You can wrap user7224827's answer to create a nice extension method matching your original desired API:

    public static class ServiceCollectionExt
    {
        public static void AddSingleton<I1, I2, T>(this IServiceCollection services) 
            where T : class, I1, I2
            where I1 : class
            where I2 : class
        {
            services.AddSingleton<I1, T>();
            services.AddSingleton<I2, T>(x => (T) x.GetService<I1>());
        }
    }

Solution 3 - C#

Another option to keep the DI mechanism is to do as following:

services.AddSingleton<MyClass>();
services.AddSingleton<Interface1>(p => p.GetRequiredService<MyClass>());
services.AddSingleton<Interface2>(x => x.GetRequiredService<MyClass>());

Solution 4 - C#

The above answers are cool, using that as inspiration I changed it to make use of the type constraints that come with the framework avoiding the need for casting and the most helpful, compiler errors when I use classes and interfaces that are not compatible. Compiler errors are easier to solve than a "what the f** why is this null" at run-time ;.)

[TestClass()]
public class ServiceCollectionExtensionTests
{
    interface MyInterface
    {
        Guid Id { get; }
    }
    class MyClas : MyInterface
    {
        Guid id = Guid.NewGuid();

        public Guid Id => id;

    }


    [TestMethod()]
    public void AddSingletonTest()
    {
        var service = new ServiceCollection()
                            .AddSingleton<MyClas>()
                            .ReUseSingleton<MyClas,MyInterface>()
                            .BuildServiceProvider();

        var foo1 = service.GetService<MyClas>();
        var foo2 = service.GetService<MyInterface>();
        Assert.AreEqual(foo1.Id, foo2.Id);
        Assert.AreSame(foo1, foo2);
    }
}

The code for the "ReUseXYZ" is here:

namespace Microsoft.Extensions.DependencyInjection
{
    /// <summary>
    /// Class ServiceCollectionExtension allowing to registered 
    /// derived implementations of a already registered service 
    /// to re-use the same service without having to register 
    /// the same class 2x ending up with 2 instances of the 
    /// same type in the same scope.
    /// </summary>
    public static class ServiceCollectionExtension
    {

        /// <summary>
        /// Adds a singleton service of the type specified in TBase with a factory based on the registered type T that has been specified in implementation factory to the specified <see cref="Microsoft.Extensions.DependencyInjection.IServiceCollection"/>.
        /// </summary>
        /// <typeparam name="T">The registered type</typeparam>
        /// <typeparam name="TBase">The type that T is derived from, can be the base class or base interface.</typeparam>
        /// <param name="services">The services.</param>
        /// <returns>the IServiceCollection used to register the interface with.</returns>
        public static IServiceCollection ReUseSingleton<T, TBase>(this IServiceCollection services)
            where T : TBase
            where TBase : class
        {
            services.AddSingleton<TBase>(a => a.GetRequiredService<T>());
            return services;
        }

        /// <summary>
        /// Adds a transient service of the type specified in TBase with a factory based on the registered type T that has been specified in implementation factory to the specified <see cref="Microsoft.Extensions.DependencyInjection.IServiceCollection"/>.
        /// </summary>
        /// <typeparam name="T">The registered type</typeparam>
        /// <typeparam name="TBase">The type that T is derived from, can be the base class or base interface.</typeparam>
        /// <typeparam name="TS">The IServiceCollection instance to extend.</typeparam>
        /// <param name="services">The services.</param>
        /// <returns>the IServiceCollection used to register the interface with.</returns>
        public static IServiceCollection ReUseTransient<T, TBase>(this IServiceCollection services)
            where T : TBase
            where TBase : class

        {
            services.AddTransient<TBase>(a => a.GetRequiredService<T>());
            return services;
        }

        /// <summary>
        /// Adds a scoped service of the type specified in TBase with a factory based on the registered type T that has been specified in implementation factory to the specified <see cref="Microsoft.Extensions.DependencyInjection.IServiceCollection"/>.
        /// </summary>
        /// <typeparam name="T">The registered type</typeparam>
        /// <typeparam name="TBase">The type that T is derived from, can be the base class or base interface.</typeparam>
        /// <typeparam name="TS">The IServiceCollection instance to extend.</typeparam>
        /// <param name="services">The services.</param>
        /// <returns>the IServiceCollection used to register the interface with.</returns>
        public static IServiceCollection ReUseScoped<T, TBase>(this IServiceCollection services)
            where T : TBase
            where TBase : class
        {
            services.AddScoped<TBase>(a => a.GetRequiredService<T>());
            return services;
        }
    }
}

Solution 5 - C#

Antoher way is to use the Type.GetInterfaces() method to get all interfaces. Then you can use the overload AddSingleton( Type serviceType, object implementationInstance ) inside a foreach to register every interface.

Code would be as following:

        object instance = new MyClass();

        // Gets all Interfaces from instance
        Type[] interfaces = instance.GetType().GetInterfaces();

        // Register As AllImplementedInterfaces
        foreach( Type type in interfaces )
        {
            services.AddSingleton( type, instance );
        }

        // Register As Self
        services.AddSingleton( instance.GetType(), instance );

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMaximView Question on Stackoverflow
Solution 1 - C#juunasView Answer on Stackoverflow
Solution 2 - C#dawView Answer on Stackoverflow
Solution 3 - C#HDolevView Answer on Stackoverflow
Solution 4 - C#Walter VerhoevenView Answer on Stackoverflow
Solution 5 - C#DavidView Answer on Stackoverflow