Asp.Net核心:注册实现多接口和生活方式单例
Asp.Net Core: register implementation with multiple interfaces and lifestyle Singleton
考虑以下接口和 class 定义:
public interface IInterface1 { }
public interface IInterface2 { }
public class MyClass : IInterface1, IInterface2 { }
有什么方法可以像这样用多个接口注册一个 MyClass
的实例:
...
services.AddSingleton<IInterface1, IInterface2, MyClass>();
...
并使用不同的接口解析 MyClass
的单个实例,如下所示:
IInterface1 interface1 = app.ApplicationServices.GetService<IInterface1>();
IInterface2 interface2 = app.ApplicationServices.GetService<IInterface2>();
根据定义,服务集合是 ServiceDescriptor
的集合,它们是服务类型和实现类型的对。
然而,您可以通过创建自己的提供程序函数来解决这个问题,就像这样(感谢 user7224827):
services.AddSingleton<IInterface1>();
services.AddSingleton<IInterface2>(x => x.GetService<IInterface1>());
下面有更多选项:
private static MyClass ClassInstance;
public void ConfigureServices(IServiceCollection services)
{
ClassInstance = new MyClass();
services.AddSingleton<IInterface1>(provider => ClassInstance);
services.AddSingleton<IInterface2>(provider => ClassInstance);
}
另一种方式是:
public void ConfigureServices(IServiceCollection services)
{
ClassInstance = new MyClass();
services.AddSingleton<IInterface1>(ClassInstance);
services.AddSingleton<IInterface2>(ClassInstance);
}
我们只提供相同的实例。
您可以包装 user7224827 的答案以创建一个与您最初想要的相匹配的不错的扩展方法 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>());
}
}
另一种保留 DI 机制的方法是:
services.AddSingleton<MyClass>();
services.AddSingleton<Interface1>(p => p.GetRequiredService<MyClass>());
services.AddSingleton<Interface2>(x => x.GetRequiredService<MyClass>());
上面的答案很酷,以此为灵感,我将其更改为利用框架附带的类型约束,避免了强制转换和最有用的编译器错误,当我使用 类和不兼容的接口。编译器错误比 运行 时的“what f** why is this null”更容易解决 ;.)
[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);
}
}
“ReUseXYZ”的代码在这里:
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;
}
}
}
Antoher的方法是使用Type.GetInterfaces()
方法获取所有接口。然后您可以在 foreach 中使用重载 AddSingleton( Type serviceType, object implementationInstance )
来注册每个接口。
代码如下:
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 );
考虑以下接口和 class 定义:
public interface IInterface1 { }
public interface IInterface2 { }
public class MyClass : IInterface1, IInterface2 { }
有什么方法可以像这样用多个接口注册一个 MyClass
的实例:
...
services.AddSingleton<IInterface1, IInterface2, MyClass>();
...
并使用不同的接口解析 MyClass
的单个实例,如下所示:
IInterface1 interface1 = app.ApplicationServices.GetService<IInterface1>();
IInterface2 interface2 = app.ApplicationServices.GetService<IInterface2>();
根据定义,服务集合是 ServiceDescriptor
的集合,它们是服务类型和实现类型的对。
然而,您可以通过创建自己的提供程序函数来解决这个问题,就像这样(感谢 user7224827):
services.AddSingleton<IInterface1>();
services.AddSingleton<IInterface2>(x => x.GetService<IInterface1>());
下面有更多选项:
private static MyClass ClassInstance;
public void ConfigureServices(IServiceCollection services)
{
ClassInstance = new MyClass();
services.AddSingleton<IInterface1>(provider => ClassInstance);
services.AddSingleton<IInterface2>(provider => ClassInstance);
}
另一种方式是:
public void ConfigureServices(IServiceCollection services)
{
ClassInstance = new MyClass();
services.AddSingleton<IInterface1>(ClassInstance);
services.AddSingleton<IInterface2>(ClassInstance);
}
我们只提供相同的实例。
您可以包装 user7224827 的答案以创建一个与您最初想要的相匹配的不错的扩展方法 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>());
}
}
另一种保留 DI 机制的方法是:
services.AddSingleton<MyClass>();
services.AddSingleton<Interface1>(p => p.GetRequiredService<MyClass>());
services.AddSingleton<Interface2>(x => x.GetRequiredService<MyClass>());
上面的答案很酷,以此为灵感,我将其更改为利用框架附带的类型约束,避免了强制转换和最有用的编译器错误,当我使用 类和不兼容的接口。编译器错误比 运行 时的“what f** why is this null”更容易解决 ;.)
[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);
}
}
“ReUseXYZ”的代码在这里:
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;
}
}
}
Antoher的方法是使用Type.GetInterfaces()
方法获取所有接口。然后您可以在 foreach 中使用重载 AddSingleton( Type serviceType, object implementationInstance )
来注册每个接口。
代码如下:
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 );