依赖注入
文献来源:《Pro ASP.NET Core MVC》 Adam Freeman 第18章 依赖注入
1 依赖注入原理
- 所有可能变化的地方都用接口
- 在使用接口的地方用什么实体类通过在
ConfigureService
中注册解决 - 注册的实体类需要指定在何种生命周期中有效
- Transient
- Scoped
- Singleton
2 接口
接口只定义契约,不定义实现。
//IRepository.cs
using System.Collections.Generic;
namespace DependencyInjection.Models{public interface IRepository{IEnumerable<Product> Products{get;}Product this[string name]{get;}void AddProduct(Product product);void DeleteProduct(Product product);}
}//MemoryRepository.cs
using System.Collections.Generic;namespace DependencyInjection.Models{public class MemoryRepository:IRepository{private Dictionary<string, Product> products;public MemoryRepository(){products = new Dictionary<string, Product>();new List<Product{new Product{Name="Kayak", Price=275M},new Product{Name="Lifejacket", Price=48.95M},new Product{Name="Soccer ball", Price=19.50M},}.ForEach(p=>AddProduct(p));}public IEnumerable<Product> Products => products.Values;public Product this[string name] => products[name];public void AddProduct(Product product) => products[product.Name] = product;public void DeleteProduct(Product product) => products.Remove(product.Name);}
}
3 数据绑定页面
页面绑定代码使用@model和ViewBag
实现。
//Index.cshtml
@using DependencyInjection.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@model IEnumerable<Product>
@{layout=null;}<!DOCTYPE html>
<html><head><meta name="viewport" content="width=device-width" /><title>Dependency Injection</title><link rel="stylesheet" asp-href-include="lib/bootstrap/dist/css/*.min.css" /></head><body class="panel-body">@if(ViewData.Count>0){<table class="table table-bordered table-condense table-striped">@foreach(var kvp in ViewData){<tr><td>@kvp.Key</td><td>@kvp.Value</td></tr>}</table>}<table class="table table-bordered table-condense table-striped"><thead><tr><th>Name</th><th>Price</th></tr></thead><tbody>@if(Model==null){<tr><td colspan="3" class="text-center">No Model Data</td></tr>}else{@foreach(var p in Model){<tr><td>@p.Name</td><td>@string.Format("{0:C2}",p.Price)</td></tr>}}</tbody></table></body>
</html>
该页面绑定了2组数据集
ViewData
集合@model IEnumerable<Product>
后台绑定
//HomeController.cs
//使用实体类的Products属性绑定页面
using Microsoft.AspNetCore.Mvc;
using DependencyInjection.Models;public class HomeController:Controller{public ViewResult Index() => View(new MemoryRepository().Products);
}
4 ASP.NET MVC定义依赖注入
4.1 接口依赖注入
- 单实例依赖注入
首先将后台处理由指定的实体类换成接口
//HomeController.cs
using Microsoft.AspNetCore.Mvc;
using DependencyInjection.Models;
using DependencyInjection.Infrastructure;namespace DependencyInjection.Controllers{//增加接口私有变量private IRepository repository;//通过构造函数传参至该私有变量public HomeController(IRepository repo){repository = repo;}//绑定接口的Products属性//运行时由ASP.NET MVC框架确定使用何种实体类public ViewResult Index()=>View(repository.Products);
}
然后配置接口与实体类之间的关系
//Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using DependencyInjection.Infrastructure;
using DependencyInjection.Model;namespace DependencyInjection{public void ConfigureServices(IServicesCollection services){services.AddTransient<IRepository, MemoryRepository>();...}...
}
services.AddTransient
告诉service provider
如何处理依赖。这里要注意一点,service provider
是整个MVC
框架服务的总提供者。
-
链式依赖
某实体类依赖于某接口,且该实体类中的成员变量又依赖于另一接口。
//MemoryRepository.cs
using DependencyInjection.Models{public class MemoryRepository:IRepository{private IModelStorage storage;//新增私有变量用于指定所属的仓库public MemoryStorage(IModelStorage storage){storage = modelStorage;new List<Product>{new Product{Name="Kayak",Price=275M},new Product{Name="Lifejacket", Price=48.95M},new Product{Name="Soccer ball", Price=19.50M}}.ForEach(p=>AddProduct(p));}public IEnumerable<Product> Products=>storage.Items;public Product this[string name] => storage[name];public void AddProduct(Product product){storage[product.Name] = product;}public void DeleteProduct(Product product){storage.RemoveItem(product.Name); }}
}
在ConfigureServices
中注册。
//Startup.cs
...
namespace DependencyInjection{public class Startup{public void ConfigureServices(IServiceCollection services){services.AddTransient<IRepository, MemoryRepository>();services.AddTransient<IModelStorage, DictionaryStorage>();...}...}
}
4.2 实体类依赖注入
假设存在实体类ProductTotalizer
用于计算总价。
using System.Linq;
namespace DependencyInjection.Models{public class ProductTotalizer{public ProductTotalizer(IRepository repo){Repository = repo;}//所述仓库用接口表示public IRepository Repository {get;set;}//总价public decimal Total => Repository.Products.Sum(p=>p.Price);}
}
在HomeController
中加入ProductTotalizer
变量。
...
public class HomeController:Controller{private IRepository repository;private ProductTotalizer totalizer;public HomeController(IRepository repo, ProductTotalizer total){repository = repo;totalizer = total;}public viewResult Index(){ViewBag.Total = totalizer.Total;return View(repository.Products);}
}
在Index.cshtml
中包含了对ViewBag
的显示,这里实际绑定了2个数据,第一个是ViewBag
,第二个是repository
。
为了明确在Controller中使用何种ProductTotalizer
(可能在后面的开发中,ProductTotalizer
是父类),可以对其进行强行指定。
//Startup.cs
...
public void ConfigureServices(IServicesCollection services){...services.AddTransient<ProductTotalizer>();...
}
5 服务生命周期
类别 | 生命周期 |
---|---|
Transient | 只要有调用就创建新的对象。就算类型一样,但是对象不一样 |
Scoped | 在一个Controller 之中,只要类型一样,对象就一样。刷新Controller 之后对象变了,但是多个同一类型的对象还是同一个。 |
Singleton | 只要初始化了,就一直是这个对象,不管是否刷新Controller |
5.1 Transient
首先在service provider
中注册IRepository
的调用使用MemoryRepository
。
...
public void ConfigureServices(IServiceCollection services){services.AddTransient<IRepository, MemoryRepository>();...
}
...
在repository
中加入ToString()
函数,该函数的实现中包含了GUID
的生成。
//MemoryRepository.cs
using System.Collections.Generic;
namespace DependencyInjection.Models{public class MemoryRepository:IRepository{private IModelStorage storage;private string guid = System.Guid.NewGuid().ToString();public MemoryRepository(IModelStorage modelStore){storage = modelStore;...}...public override string ToString(){return guid;}}
}
在下图中可以看到,当HomeController
调用repository
时,service provider
会针对每一次调用都生成一个新的对象。
5.2 Scoped
其他的调用都一样,除了在service
中注册使用AddScoped
之外。
//Startup.cs
...
public void ConfigureServices(IServiceCollection services){service.AddScoped<IRepository, MemoryRepository>();...
}
5.3 Singleton
其他的调用都一样,除了在service
中注册使用AddSingleton
之外。
//Startup.cs
...
public void ConfigureServices(IServiceCollection services){services.AddSingleton<IRepository, MemoryRepository>();...
}
...
6 其他依赖注入
6.1 Action依赖注入
上述的依赖注入是针对整个Controller,但有的时候只想针对某一个Action进行依赖注入。此时只需要使用[FromServices]
即可。
using Microsoft.AspNetCore.Mvc;
using DependencyInjection.Models;
using DependencyInjection.Infrastructure;namespace DependencyInjection.Controllers{public class HomeController:Controller{private IRepository repository;public HomeController(IRepository repo){repository = repo;}public ViewResult Index([FromServices]ProductTotalizer totalizer){ViewBag.HomeController = repository.ToString();ViewBag.Totalizer = totalizer.Repository.ToString();return View(repository.Products);}}
}
上述代码表示在Index
中需要使用ProductTotalizer
时,从service provider
处获得。
6.2 手工进行依赖注入
除了上述的注册方式外,还有一些其他的注册方式。比如,当没有依赖出现时,而你又想获得一个接口的实例,该实例依赖于接口。在这种情况下,你可以直接通过service provider
来实现。
...
public class HomeController:Controller{public ViewResult Index([FromServices]ProductTotalizer totalizer){IRepository repository=HttpContext.RequestServices.GetService<IRepository>();...}
}
...