Ioc框架Ninject:如何向构造函数添加接口以外的参数
没问题,我来帮你搞定用Ninject给构造函数加非接口参数的事儿!先看你的场景,MyClass目前依赖IMapper和IManager两个接口,现在要加个非接口类型的参数(比如字符串、整数这类具体值)对吧?下面给你几种实用的解决方案,都是Ninject里的标准玩法:
首先先修改下你的MyClass构造函数做示例,假设我们要加一个string connectionString参数:
public class MyClass : IPerformer { private readonly IMapper _mapper; private readonly IManager _manager; private readonly string _connectionString; // 新增的非接口参数 // 修改后的构造函数 public MyClass(IMapper mapper, IManager manager, string connectionString) { _mapper = mapper; _manager = manager; _connectionString = connectionString; } // 原有方法保持不变 public void Perform_A(int id, string name) { _manager.DoSomething_A(id, name); // 后续可以用_connectionString做数据库操作之类的 } public void Perform_B(int id, string name) { /* ... */ } public void Perform_C(int id, string name) { /* ... */ } }
方法1:绑定阶段直接指定构造参数
如果参数值是固定的(比如配置好的连接字符串),直接在Ninject模块里绑定的时候用WithConstructorArgument传递就行:
public class MyDependencyModule : NinjectModule { public override void Load() { // 先绑定好接口类型的依赖 Bind<IMapper>().To<MyMapperImplementation>(); Bind<IManager>().To<MyManagerImplementation>(); // 绑定IPerformer到MyClass,同时传入非接口参数 Bind<IPerformer>().To<MyClass>() .WithConstructorArgument("connectionString", "Server=localhost;Database=MyDb;Uid=sa;Pwd=123456;"); } }
这里要注意:"connectionString"必须和构造函数里的参数名称完全一致(大小写敏感),怕写错的话也可以用表达式指定,更安全:
Bind<IPerformer>().To<MyClass>() .WithConstructorArgument(c => c._connectionString, "Server=localhost;Database=MyDb;Uid=sa;Pwd=123456;");
方法2:从配置文件读取参数传入
如果参数值存在配置文件(比如appsettings.json)里,先读取配置再传入就行:
public class MyDependencyModule : NinjectModule { private readonly IConfiguration _appConfig; // 初始化模块时传入配置对象 public MyDependencyModule(IConfiguration appConfig) { _appConfig = appConfig; } public override void Load() { Bind<IMapper>().To<MyMapperImplementation>(); Bind<IManager>().To<MyManagerImplementation>(); // 从配置文件读取连接字符串 var dbConnString = _appConfig.GetConnectionString("DefaultDatabase"); Bind<IPerformer>().To<MyClass>() .WithConstructorArgument("connectionString", dbConnString); } }
方法3:工厂模式处理动态参数
如果这个非接口参数是动态变化的(比如每次创建MyClass实例都要传不同的值),直接绑定就不太灵活了,这时候用工厂模式最合适:
方式A:利用Ninject自动支持的Func委托
Ninject默认能解析Func类型的注入,不用额外写工厂类,直接在需要创建IPerformer的地方注入Func<string, IPerformer>就行:
// 比如在某个业务服务里 public class OrderService { private readonly Func<string, IPerformer> _performerFactory; // 注入Func委托 public OrderService(Func<string, IPerformer> performerFactory) { _performerFactory = performerFactory; } public void ProcessOrder(int orderId) { // 动态传入不同的连接字符串创建实例 var performer = _performerFactory("Server=OrderDb;Database=OrderSystem;"); performer.Perform_A(orderId, "OrderProcessing"); } }
然后Ninject模块里只需要正常绑定接口就行,不用额外配置:
public class MyDependencyModule : NinjectModule { public override void Load() { Bind<IMapper>().To<MyMapperImplementation>(); Bind<IManager>().To<MyManagerImplementation>(); Bind<IPerformer>().To<MyClass>(); } }
Ninject会自动把Func<string, IPerformer>解析成一个委托,帮你自动注入IMapper和IManager,同时接收你传入的动态参数。
方式B:手动实现工厂类(复杂场景用)
如果需要更复杂的创建逻辑,比如参数校验、额外初始化,就手动写个工厂:
// 先定义工厂接口 public interface IPerformerFactory { IPerformer Create(string connectionString); } // 实现工厂类 public class PerformerFactory : IPerformerFactory { private readonly IKernel _ninjectKernel; public PerformerFactory(IKernel ninjectKernel) { _ninjectKernel = ninjectKernel; } public IPerformer Create(string connectionString) { // 可以加参数校验逻辑 if (string.IsNullOrEmpty(connectionString)) { throw new ArgumentNullException(nameof(connectionString)); } // 用Ninject的Get方法传入构造参数 return _ninjectKernel.Get<IPerformer>(new ConstructorArgument("connectionString", connectionString)); } }
然后在Ninject模块里绑定工厂:
public class MyDependencyModule : NinjectModule { public override void Load() { Bind<IMapper>().To<MyMapperImplementation>(); Bind<IManager>().To<MyManagerImplementation>(); Bind<IPerformer>().To<MyClass>(); Bind<IPerformerFactory>().To<PerformerFactory>(); } }
之后在需要的地方注入IPerformerFactory,调用Create方法就能动态创建实例了。
小提醒
- Ninject会自动解析所有能绑定的接口类型参数,你只需要指定那些无法自动解析的具体值参数就行。
- 尽量别在构造函数里塞太多具体值参数,不然类的依赖会变得很乱,优先考虑把配置封装成接口(比如
IAppSettings)再注入,更符合依赖注入的设计原则。
内容的提问来源于stack exchange,提问作者Max Bertoli

