IoC之Ninject
一、Ninject安装
Ninject是一个轻量级的开源的DI容器,可以通过Nuget直接安装:
二、Ninject的简单使用
模型代码:
//计算器接口 public interface ICalculator { decimal GetTotalValue(IEnumerable<Product> products); } //计算器实现类 public class LinqValueCalculator: IValueCalculator { public decimal GetTotalValue(IEnumerable<Product> products) { return products.Sum(p => p.Price); } } //购物车 public class ShoppingCart { //计算器 private ICalculator calc; public ShoppingCart(ICalculator calcParam) { calc = calcParam; } public IEnumerable<Product> Products { get; set; } //算出商品价格 public decimal CalcProductTotal() { return calc.GetTotalValue(Products); } }
Ninject的简单使用:
1 public class HomeController : Controller 2 { 3 Product[] products ={ 4 new Product{Name="kayak",Category="WaterSports",Price=275M}, 5 new Product{Name="lifejacket",Category="WaterSports",Price=48.95M}, 6 new Product{Name="soccer ball",Category="soccer",Price=19.50M}, 7 new Product{Name="corner flag",Category="soccer",Price=34.95M}}; 8 // GET: Home 9 10 public ActionResult Index() 11 { 12 //获取一个ninject内核对象,该对象负责解析依赖项和创建实例 13 IKernel ninjectKernel = new StandardKernel(); 14 //注册服务,当我们需要ICalculator实例的时候,获取的是一个Calculator的实例 15 ninjectKernel.Bind<ICalculator>().To<Calculator>(); 16 //获取实例 17 ICalculator calc= ninjectKernel.Get<ICalculator>(); 18 19 //ICalculator calc = new Calculator();使用IoC容器就不用直接new了 20 ShoppingCart cart = new ShoppingCart(calc) { Products = products }; 21 decimal total = cart.CalcProductTotal(); 22 return View(total); 23 } 24 }
三、Ninject的封装使用
第一步:创建依赖项解析器
服务解析器用于注册服务, IDependencyResolver 接口在System.Mvc命名空间下
1 public class NinjectResolver : IDependencyResolver 2 { 3 private IKernel kernel; 4 public NinjectResolver() 5 { 6 kernel = new Ninject.StandardKernel(); 7 AddBindings(); 8 } 9 //获取服务实现类实例,没有合适的绑定是返回null 10 public object GetService(Type serviceType) 11 { 12 return kernel.TryGet(serviceType); 13 } 14 //当接口绑定多个服务实现类,可以使用getAll 15 public IEnumerable<object> GetServices(Type serviceType) 16 { 17 return kernel.GetAll(serviceType); 18 } 19 20 private void AddBindings() 21 { 22 kernel.Bind<ICalculator>().To<LinqValueCalculator>(); 23 //...这里注册服务 24 } 25 }
第二步:注册依赖项解析器
创建了一个实现IDependencyResolver接口的实现是不够的,我们需要告诉MVC框架使用它
方法1:在MVC5中可以通过在APP_Start文件下的NinjectWebCommon.cs文件来注册依赖项解析器
private static void ResisterServices(IKernel kernel){ System.Web.Mvc.DependencyResolver.SetResolver(new NinjectResolver(); }
方法2:在global文件中添加注册
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RouteConfig.RegisterRoutes(RouteTable.Routes); //在这里添加 System.Web.Mvc.DependencyResolver.SetResolver(new NinjectResolver()); }
第三步:重构Controller
1 public class HomeController : Controller 2 { 3 Product[] products ={ 4 new Product{Name="kayak",Category="WaterSports",Price=275M}, 5 new Product{Name="lifejacket",Category="WaterSports",Price=48.95M}, 6 new Product{Name="soccer ball",Category="soccer",Price=19.50M}, 7 new Product{Name="corner flag",Category="soccer",Price=34.95M}}; 8 ICalculator calc;//重构改动1 9 public HomeController(ICalculator calcParam)//重构改动2 10 { 11 calc = calcParam; 12 } 13 public ActionResult Index() 14 { 15 ShoppingCart cart = new ShoppingCart(calc) { Products = products }; 16 decimal total = cart.CalcProductTotal(); 17 return View(total); 18 } 19 }
四、Ninject的一些补充
Ninject中有一些很好用的功能,这里只列出几种常用的:
4.1 依赖项链
一句话解释就是解析依赖项的依赖项,一个栗子,当计算器类(ICalculator的实现类)依赖于一个打折类,当创建HomeController时,要解析HomeController的依赖LinqValueCalculator,而LinqValueCalculator又依赖于打折类FlexibleDiscountHelper打折类,那么Ninject能在创建HomeController时将FlexibleDiscountHelper也解析出来,打折服务(IDiscountHelper)和一些打折类代码如下:
namespace EssentialTools.Models { //打折服务 public interface IDiscountHelper { decimal ApplyDiscount(decimal totalParm); } //默认打折类 public class DefaultDiscountHelper : IDiscountHelper { public decimal DiscountSize { get; set; } public decimal ApplyDiscount(decimal totalParm) { return totalParm - DiscountSize / 100M * totalParm; } } /// <summary> /// 弹性打折类,100元以上折扣70%,少于100元折扣25% /// </summary> public class FlexibleDiscountHelper : IDiscountHelper { public decimal ApplyDiscount(decimal totalParm) { decimal discount = totalParm > 100 ? 70 : 25; return totalParm - discount / 100M * totalParm; } } /// <summary> /// 最小打折类,大于100元打9折,10~100元之间减5元,10元以下无优惠 /// </summary> public class MinimunDiscountHelper : IDiscountHelper { public decimal ApplyDiscount(decimal totalParm) { if (totalParm < 0) { throw new ArgumentOutOfRangeException(); } else if (totalParm > 100) { return totalParm * 0.9M; } else if (10 <= totalParm && totalParm <= 100) { return totalParm - 5; } else { return totalParm; } } } }
修改LinqValueCalculator为:
//Linq计算器,用于计算商品总价 public class LinqValueCalculator:ICalculator { private IDiscountHelper discounter;//打折 public LinqValueCalculator(IDiscountHelper discountParm)//计算器依赖于打折类 { discounter = discountParm; } public decimal ValueProducts(IEnumerable<Product> products){ return discounter.ApplyDiscount(products.Sum(p => p.Price)); } }
使用Ninject注册打折服务:
private void AddBindings() { kernel.Bind<ICalculator>().To<LinqValueCalculator>(); kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize",50M);//默认打折类打5折 kernel.Bind<IDiscountHelper>().To<MinimunDiscountHelper>().WhenInjectedInto<LinqValueCalculator>();//给LinqValueCalculator注入打折服务时,解析FlexibleDiscountHelper }
完成以上步骤后,不必更改HomeController中代码,运行程序即可,在程序创建HomeController的实例时,Ninject会将所有的依赖项都解析出来。
4.2 解析依赖项时传入属性值或构造器参数
//1.注入依赖项,给属性赋值。实例:注册默认打折类(DefaultDiscount)到打折服务(IDiscount),默认打折类的属性DiscountSize的值0.5(五折) kernel.Bind<IDiscount>().To<DefaultDiscount>().WithPropertyValue("DiscountSize",0.5); //2.注入依赖项时给构造器参数传值。还是上边的例子,如果DiscountSize不是DefaultDiscount的属性,而是构造函数的参数则使用下边代码进行赋值 kernel.Bind<IDiscount>().To<DefaultDiscount>().WithConstructorArgument("DiscountSize",0.5);
4.3 使用条件绑定
//当总价小于100时,选择的打折服务是默认打折类 kernel.Bind<IDiscount>().To<DefaultDiscount>().When(total<100); //当为LinqValueCalcutor计算器类注入打折服务时,打折服务选择Linqdiscount
kernel.Bind<IDiscount>().To<MinimunDiscountHelper>.WhenInjectedInto<LinqValueCalcutor>();
4.4 设置对象作用域
Niject中对象作用域的内容很多,这里只列出几种常用的方法
1 kernel.Bind<IA>().To<A>().InTransientScope();//每个依赖项一个实例默认的 2 kernel.Bind<IA>().To<A>().InSingletonScope();//整个应用程序一个实例,单例 3 kernel.Bind<IA>().To<A>().InThreadScope();//每个线程一个实例 4 kernel.Bind<IA>().To<A>().InRequestScope();//每个请求一个实例
参考文献:
本文参考的书籍是《精通ASP.NET MVC5》中文版,想了解更多内容的话可以参考这本经典的MVC教程。
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决