如何在ASP.NET依赖注入中注入带默认实现的接口?
带默认实现接口的依赖注入问题
问题场景
我有一个包含默认实现方法的接口:
public interface ITestService { string GetName(string name) => $"{name} coming from interface"; }
尝试直接将其注入DI容器时:
services.AddScoped<ITestService>();
运行时抛出错误:
System.ArgumentException: 'Cannot instantiate implementation type 'ITestService' for service type 'ITestService'.'
手动编写空实现类后可以正常注入:
public class TestService : ITestService { } // 注册 services.AddScoped<ITestService, TestService>();
解决方案
接口本身无法被实例化,DI容器必须绑定到具体实现类型,但可以通过以下方式避免手动编写空实现类:
1. 动态生成实现类(通用方案)
通过反射Emit动态创建一个实现目标接口的空类,然后注册到容器:
using System.Reflection; using System.Reflection.Emit; public static class ServiceCollectionExtensions { public static IServiceCollection AddDefaultInterfaceImpl<TInterface>(this IServiceCollection services, ServiceLifetime lifetime) where TInterface : class { if (!typeof(TInterface).IsInterface) throw new ArgumentException("泛型参数必须是接口类型", nameof(TInterface)); // 构建动态程序集与类型 var assemblyName = new AssemblyName($"Dynamic_{typeof(TInterface).Name}Impl"); var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name); var typeBuilder = moduleBuilder.DefineType( $"Dynamic_{typeof(TInterface).Name}", TypeAttributes.Public | TypeAttributes.Class, null, new[] { typeof(TInterface) }); // 添加无参构造函数 typeBuilder.DefineDefaultConstructor(MethodAttributes.Public); // 创建类型并注册 var implType = typeBuilder.CreateType(); switch (lifetime) { case ServiceLifetime.Scoped: services.AddScoped(typeof(TInterface), implType); break; case ServiceLifetime.Singleton: services.AddSingleton(typeof(TInterface), implType); break; case ServiceLifetime.Transient: services.AddTransient(typeof(TInterface), implType); break; } return services; } }
使用方式:
services.AddDefaultInterfaceImpl<ITestService>(ServiceLifetime.Scoped);
2. 直接使用工厂委托(简化方案)
如果只是针对单个接口,也可以直接在注册时通过工厂委托创建动态实例:
services.AddScoped<ITestService>(sp => { var type = typeof(ITestService); var assemblyName = new AssemblyName($"Temp_{type.Name}"); var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name); var typeBuilder = moduleBuilder.DefineType($"Temp_{type.Name}Impl", TypeAttributes.Public | TypeAttributes.Class, null, new[] { type }); typeBuilder.DefineDefaultConstructor(MethodAttributes.Public); var implType = typeBuilder.CreateType(); return (ITestService)Activator.CreateInstance(implType); });
说明
接口的默认方法只是提供了默认逻辑,但接口本身依然不能被实例化,必须依赖具体的实现类型。上述方案本质都是自动生成空实现类,从而满足DI容器的实例化要求。
内容的提问来源于stack exchange,提问作者sa-es-ir
相关产品推荐
相关产品推荐

