图三
这就是ASP.NET Core 中默认的依赖注入方式, 对比一下图二是不是很像?
上篇文章说要将Startup放大介绍一下, 那么打开Startup这个文件, 看一下里面的ConfigureServices方法。顾名思义, 这个方法是用来配置服务,
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
此方法接收一个IServiceCollection类型的参数, 查看它的定义, 被定义在Microsoft.Extensions.DependencyInjection这个NuGet包中, 功能就是依赖注入, 在ASP.NET Core中被广泛使用.
①IServiceCollection
它正是图三中的①IServiceCollection,它是一个IList<ServiceDescriptor>类型的集合。也就是上门的维修工的例子中领导制定的清单, 而Startup中的ConfigureServices这个方法的作用就是让我们作为"领导"来配置这个清单。方法中默认调用的services.AddMvc(), 是IServiceCollection的一个扩展方法 public static IMvcBuilder AddMvc(this IServiceCollection services); , 作用就是向这个清单中添加了一些MVC需要的服务,例如Authorization、RazorViewEngin、DataAnnotations等。
系统需要的添加好了, 剩下的就是我们把自己需要的用的添加进去了。 这里我们可以创建一个ServiceDescriptor然后把它添加到这个集合里, 系统①IServiceCollection也提供了AddSingleton、AddScoped和AddTransient这样的方法, 三种方法定义了所添加服务的生命周期, 具体见②ServiceDescriptor.
当然我们可以在ConfigureServices中通过一堆AddXXX将服务添加到IServiceCollection, 但这样好多堆在一起不易于修改和阅读, 特别还有一些功能会包含好几个服务的添加, 所以推荐像系统默认的AddMvc() 这样封装到一个扩展方法中去。
现在来看一下清单中的内容。
②ServiceDescriptor
既然①IServiceCollection是一个IList<ServiceDescriptor>, 那么ServiceDescriptor也就是这个集合中的内容了, 也就是仓库中物品的描述.对照图三中的②ServiceDescriptor看一下它的各个属性。
A. Type ServiceType: 服务的类型 --7mm六角扳手
B. Type ImplementationType: 实现的类型 --大力牌扳手
C. ServiceLifetime Lifetime: 服务的生命周期 --若干(谁要都给一把新的)
D. object ImplementationInstance: 实现服务的实例
E: Func<IServiceProvider, object> ImplementationFactory: 创建服务实例的工厂
ServiceLifetime是一个枚举, 上文说的AddSingleton、AddScoped和AddTransient就是对应这个枚举,分别为:
Singleton: 单例, 例子中的卡车, 全单位只有一辆, 谁调用都是返回这个实例。
Scoped: 区域内单例, 例子中的傻瓜相机, 每小组一台, 小组内谁要都是同一台, 不同小组的相机不同。
Transient: 临时的 例子中的扳手和锤子, 谁要都给一把新的, 所有人的都不是同一把。
从这些属性的介绍来看,ServiceDescriptor规定了当有人需要ServiceType这个类型服务的时候, 提供给他一个ImplementationType类型的实例, 其他几个属性规定了提供的方法和生命周期.
③IServiceProvider
③IServiceProvider 服务提供者,由①IServiceCollection的扩展方法BuildServiceProvider创建,当需要它提供某个服务的时候, 它会根据创建它的①IServiceCollection中的对应的②ServiceDescriptor提供相应的服务实例.。它提供了⑤GetService、GetRequiredService、GetServices、GetRequiredServices这样的几个用于提供服务实例的方法,就像库管老张一样, 告诉他你需要什么服务的实例, 他会根据清单规定给你对应的工具。
GetService和GetRequiredService的区别:
维修工老李: "老张, 给我一架空客A380." --GetService<IA380>();
老张: "这个没有." -- return null;
维修工老李: "老张, 必须给我一架空客A380!" -- GetRequiredService<IA380>();
老张: "这个真TMD没有." -- System.InvalidOperationException:“No service for type 'IA380' has been registered.”;
GetServices和GetRequiredServices这两个加了"s"的方法返回对应的集合。
④IServiceScope
上文中的ServiceDescriptor的Lifetime属性为Scoped的时候,IServiceProvider会为其创建一个新的区域④IServiceScope,
public interface IServiceScope : IDisposable
{
IServiceProvider ServiceProvider { get; }
}
从上面的代码可以看出它只是对IServiceProvider进行了一个简单的封装, 原始的IServiceProvider通过CreateScope()创建了一个IServiceScope, 而这个IServiceScope的ServiceProvider属性将负责这个区域内的服务提供, 而Lifetime为Scoped的ServiceDescriptor创建的实例在本区域内是以"单例"的形式存在的.
在ASP.NET Core中,Lifetime为Scoped的实例在每次请求中只创建一次.
4.使用方法及需要注意的问题
对于上面的维修工的例子,ASP.NET Core的依赖注入还是有一些不一样的地方, 比如用卡车 (全单位只有一辆, 谁借都是这一辆) 来类比单例, 只有一个确实没问题, 但对于卡车, A把它借走了, B只有等他被还回来才能去借。 同样标记为Scoped的傻瓜相机即使在小组内也是需要轮换使用的。 没错, 就是并发问题,对于ASP.NET Core的依赖注入提供的Singleton和Scoped的实例来说, 它是很有可能同时被多个地方获取并调用的。通过下面的例子看一下这个问题, 顺便巩固一下上面的内容。
public interface ITest
{
Guid Guid { get; }
string Name { get; set; }
}
public class Test : ITest
{
public Test()
{
this.Guid = Guid.NewGuid();
}
public Guid Guid { get; }
public string Name { get; set; }
}
一个Test类继承自ITest, 为了方便比较是不是同一个实例, 在构造方法里对它的Guid属性赋一个新值,然后将其注册一下
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddTransient<ITest,Test>();
}
现在通过三种方法来获取这个Test, Controller中如下
public class HomeController : Controller
{
private ITest _test;
public HomeController( ITest test)
{
this._test = test;
}
public IActionResult Index()
{
ViewBag.Test = this._test; //构造方法获取
ViewBag.TestFromContext = HttpContext.RequestServices.GetService<ITest>(); //通过HttpContext获取
return View();
}
}
View中通过@inject ITest viewITest的方式获取, 然后把他们的Guid值显示出来:
@inject ITest viewITest
<ul>
<li>@ViewBag.Test.Guid</li>
<li>@ViewBag.TestFromContext.Guid</li>
<li>@viewITest.Guid</li>
</ul>
结果如下
ad79690e-1ee2-41bd-82f1-062de4c124b2
92cd97fc-7083-4b10-99e4-13b6b6926c16
cd0105f4-fa9d-4221-b395-af06798d96a2
说明三种方式获取了三个不同的实例, 刷新一下页面, 又变成了另外三个不同的值.
现在在startup文件中将原来的services.AddTransient<ITest,Test>()改为services.AddSingleton<ITest,Test>(), 其他不变, 重新运行一下, 结果如下
dd4c952e-b64c-4dc8-af01-2b9d667cf190
dd4c952e-b64c-4dc8-af01-2b9d667cf190
dd4c952e-b64c-4dc8-af01-2b9d667cf190
发现三组值是一样的, 说明获得的是同一个实例, 在刷新一下页面, 仍然是这三组值, 说明多次请求获得的结果也是同一个实例.
再将services.AddSingleton<ITest,Test>()改为services.AddScoped<ITest,Test>(), 重新运行, 这次结果是
ad5a600b-75fb-43c0-aee9-e90231fd510c
ad5a600b-75fb-43c0-aee9-e90231fd510c
ad5a600b-75fb-43c0-aee9-e90231fd510c
三组数字相同, 刷新一下, 又变成了另外三组一样的值, 这说明在同一次请求里, 获取的实例是同一个。
因为无论在Singleton还是Scoped的情况下, 可能在应用的多个地方同时使用同一个实例, 所以在程序设置的时候就要注意了, 如果存在像在上面的Test有个Name属性提供了{get;set; }的时候,多个引用者处理它的值, 会造成一些不可预料的错误。
5.服务的Dispose
对于每次请求, 我们最初配置的根IServiceProvider通过CreateScope()创建了一个新的IServiceScope, 而这个IServiceScope的ServiceProvider属性将负责本次该次请求的服务提供, 当请求结束, 这个ServiceProvider的dispose会被调用, 同时它负责由它创建的各个服务。
在 1.0 版中,ServiceProvider将对所有 IDisposable
对象调用 dispose,包括那些并非由它创建的对象。
而在2.0中,ServiceProvider只调用由它创建的IDisposable
类型的Dispose
。如果将一个实例添加到容器,它将不会被释放。
例如: services.AddSingleton<ITest>(new Test());
6.我想换个容器
可以将默认的容器改为其他的容器, 比如Autofac,这需要将ConfigureServices方法由返回void改为IServiceProvider。
1 public IServiceProvider ConfigureServices(IServiceCollection services)
2 {
3 services.AddMvc();
4 // Add other framework services
5
6 // Add Autofac
7 var containerBuilder = new ContainerBuilder();
8 containerBuilder.RegisterModule<DefaultModule>();
9 containerBuilder.Populate(services);
10 var container = containerBuilder.Build();
11 return new AutofacServiceProvider(container);
12 }
优质内容筛选与推荐>>
1、挑子学习笔记:对数似然距离(Log-Likelihood Distance)2、Openldap服务器日志及权限配置3、为什么需要if选择结构4、Kattis - horrorfilmnight 【贪心】5、python学习第二天