ASP.NET Core 2 学习笔记(四)依赖注入

ASP.NET Core使用了大量的依赖注入(Dependency Injection, DI),把控制反转(Inversion Of Control, IoC)运用的相当巧妙。DI可算是ASP.NET Core最精华的一部分,有用过Autofac或类似的DI Framework对此应该不陌生。
本篇将介绍ASP.NET Core的依赖注入(Dependency Injection)。

DI 容器介绍

在没有使用DI Framework 的情况下,假设在UserController 要调用UserLogic,会直接在UserController 实例化UserLogic,如下:


public class UserLogic {
public void Create(User user) {
        // ...
    }
}

public class UserController : Controller {

    public void Register(User user){
        var logic = new UserLogic();
        logic.Create(user);
        // ...
    }
}

以上程序基本没什么问题,但是依赖关系差了点。UserController 必须要依赖UserLogic才可以运行,拆出接口改成:


public interface IUserLogic {
    void Create(User user);
}

 

public class UserLogic : IUserLogic {
    public void Create(User user) {
        // ...
    }
}

 

public class UserController : Controller {
    private readonly IUserLogic _userLogic;
    public UserController() {
       _userLogic = new UserLogic();
    }

 

    public void Register(User user){
        _userLogic.Create(user);
        // ...
    }
}

UserController 与UserLogic 的依赖关系只是从Action 移到构造方法中,依然还是很强的依赖关系。

ASP.NET Core透过DI容器,切断这些依赖关系,实例的产生不在使用方(指上例UserController构造方法中的new),而是在DI容器。
DI容器的注册方式也很简单,在Startup.ConfigureServices注册。如下:

Startup.cs


// ...

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }
}

services就是一个DI容器。
此例把MVC的服务注册到DI容器,等到需要用到MVC服务时,才从DI容器取得对象实例。

基本上要注入到Service的类没什么限制,除了静态类。
以下示例就只是一般的Class继承Interface:


public interface ISample
{
    int Id { get; }
}

 

public class Sample : ISample
{
    private static int _counter;
    private int _id;
 
    public Sample()
   {
        _id = ++_counter;
    }

    public int Id => _id;
}

要注入的Service需要在Startup.ConfigureServices中注册实例类型。如下:
Startup.cs


// ...

public class Startup

{
    public void ConfigureServices(IServiceCollection services)
    {
        // ...
       services.AddScoped<ISample, Sample>();
    }
}

第一个泛型为注入的类型
建议用Interface来包装,这样在才能把依赖关系拆除。
•第二个泛型为实例的类型

DI 运行方式

ASP.NET Core的DI是采用Constructor Injection,也就是说会把实例化的物件从建构子传入。
如果要取用DI容器内的物件,只要在建构子加入相对的Interface即可。例如:
Controllers\HomeController.cs


using Microsoft.AspNetCore.Mvc;
using MyWebsite.Models;
 
namespace MyWebsite.Controllers
{
    public class HomeController : Controller
    {
        private readonly ISample _sample;
        public HomeController(ISample sample)
        {
            _sample = sample;
        }

        public string Index()
        {

            return $"[ISample]\r\n"

                 + $"Id: {_sample.Id}\r\n"

                 + $"HashCode: {_sample.GetHashCode()}\r\n"

                 + $"Tpye: {_sample.GetType()}";
        }
    }
}

输出内容如下:

[ISample]
Id: 1
HashCode: 52582687
Tpye: MyWebsite.Models.Sample 

ASP.NET Core 实例化Controller 时,发现构造方法中有ISample 这个类型的参数,就把Sample 的实例注入给该Controller。
每个Request 都会把Controller 实例化,所以DI 容器会从构造方法注入ISample 的实例,把sample 存到变量_sample 中,就能确保Action 能够使用到被注入进来的ISample 实例。
注入实例过程,情境如下:


1215970-20180524102210957-1502985396.png

Service 生命周期

注册在DI 容器的Service 分三种生命周期:
•Transient
每次注入时,都重新new一个新的实例。
•Scoped
每个Request都重新new一个新的实例,同一个Request不管经过多少个Pipeline都是用同一个实例。上例所使用的就是Scoped。
•Singleton
被实例化后就不会消失,程式运行期间只会有一个实例。

小改一下Sample 类的代码:


namespace MyWebsite.Models
{
    public interface ISample
    {
        int Id { get; }
    }

 
    public interface ISampleTransient : ISample
    {
    }

 

    public interface ISampleScoped : ISample
    {
    }

 

    public interface ISampleSingleton : ISample
    {

    }

 
    public class Sample : ISampleTransient, ISampleScoped, ISampleSingleton
    {
        private static int _counter;
        private int _id;

        public Sample()
        {
            _id = ++_counter;
        }

        public int Id => _id;
    }
}

在Startup.ConfigureServices中注册三种不同生命周期的服务。如下:


public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<ISampleTransient, Sample>();
        services.AddScoped<ISampleScoped, Sample>();
        services.AddSingleton<ISampleSingleton, Sample>();
        // Singleton 也可以用以下方法注册
        // services.AddSingleton<ISampleSingleton>(new Sample());
    }
}

Service Injection

只要是透过WebHost产生实例的类型,都可以在构造方法注入。
所以Controller、View、Filter、Middleware或自定义的Service等都可以被注入。
此篇我只用Controller、View、Service做为范例。

Controller

在TestController 中注入上例的三个Services:

Controllers\TestController.cs

using Microsoft.AspNetCore.Mvc;
using MyWebsite.Models;

namespace MyWebsite.Controllers
{
    public class TestController : Controller
    {
        private readonly ISample _transient;
        private readonly ISample _scoped;
        private readonly ISample _singleton;

        public TestController(
            ISampleTransient transient,
            ISampleScoped scoped,
            ISampleSingleton singleton)
        {

            _transient = transient;

            _scoped = scoped;

            _singleton = singleton;

        }

        public IActionResult Index()
        {
            ViewBag.TransientId = _transient.Id;
            ViewBag.TransientHashCode = _transient.GetHashCode();
            ViewBag.ScopedId = _scoped.Id;
            ViewBag.ScopedHashCode = _scoped.GetHashCode();

 

            ViewBag.SingletonId = _singleton.Id;
            ViewBag.SingletonHashCode = _singleton.GetHashCode();
            return View();
        }
    }
}

Views\Test\Index.cshtml


<table border="1">
    <tr><td colspan="3">Cotroller</td></tr>
    <tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
    <tr><td>Transient</td><td>@ViewBag.TransientId</td><td>@ViewBag.TransientHashCode</td></tr>
    <tr><td>Scoped</td><td>@ViewBag.ScopedId</td><td>@ViewBag.ScopedHashCode</td></tr>
    <tr><td>Singleton</td><td>@ViewBag.SingletonId</td><td>@ViewBag.SingletonHashCode</td></tr>
</table>

输出内容如下:


1215970-20180524104230344-1150323681.png

从左到又打开页面三次,可以发现Singleton的Id及HashCode都是一样的,现在还看不太能看出来Transient及Scoped的差异。

Service 实例产生方式:


1215970-20180524104924886-1888529082.gif

图例说明:
•A为Singleton对象实例
一但实例化,就会一直存在于DI容器中。
•B为Scoped对象实例
每次Request就会产生新的实例在DI容器中,让同Request周期的使用方,拿到同一个实例。
•C为Transient对象实例
只要跟DI容器请求这个类型,就会取得新的实例。

View

View注入Service的方式,直接在*.cshtml使用@inject:

Views\Test\Index.cshtml

@using MyWebsite.Models
@inject ISampleTransient transient
@inject ISampleScoped scoped
@inject ISampleSingleton singleton


<table border="1">
    <tr><td colspan="3">Cotroller</td></tr>
    <tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
   <tr><td>Transient</td><td>@ViewBag.TransientId</td><td>@ViewBag.TransientHashCode</td></tr>
    <tr><td>Scoped</td><td>@ViewBag.ScopedId</td><td>@ViewBag.ScopedHashCode</td></tr>
    <tr><td>Singleton</td><td>@ViewBag.SingletonId</td><td>@ViewBag.SingletonHashCode</td></tr>
</table>
<hr />
<table border="1">
    <tr><td colspan="3">View</td></tr>
    <tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
    <tr><td>Transient</td><td>@transient.Id</td><td>@transient.GetHashCode()</td></tr>
    <tr><td>Scoped</td><td>@scoped.Id</td><td>@scoped.GetHashCode()</td></tr>
    <tr><td>Singleton</td><td>@singleton.Id</td><td>@singleton.GetHashCode()</td></tr>

</table>

输出内容如下:


1215970-20180524105407300-1696309491.png

从左到又打开页面三次,Singleton的Id及HashCode如前例是一样的。
Transient及Scoped的差异在这次就有明显差异,Scoped在同一次Request的Id及HashCode都是一样的,如红紫篮框。

Service

简单建立一个CustomService,注入上例三个Service,代码类似TestController。如下:

Services\CustomService.cs

using MyWebsite.Models;
namespace MyWebsite.Services
{

    public class CustomService
    {

        public ISample Transient { get; private set; }
        public ISample Scoped { get; private set; }
        public ISample Singleton { get; private set; }

        public CustomService(ISampleTransient transient,
            ISampleScoped scoped,
            ISampleSingleton singleton)
        {

            Transient = transient;
            Scoped = scoped;
            Singleton = singleton;
        }
    }
}

注册CustomService
Startup.cs


public class Startup

{

    public void ConfigureServices(IServiceCollection services)

    {

        // ...

        services.AddScoped<CustomService, CustomService>();

    }

}

第一个泛型也可以是类,不一定要是接口。
缺点是使用方以Class作为依赖关系,变成强关联的依赖。

在View 注入CustomService:

Views\Home\Index.cshtml


@using MyWebsite

@using MyWebsite.Models

@using MyWebsite.Services

 

@inject ISampleTransient transient

@inject ISampleScoped scoped

@inject ISampleSingleton singleton

@inject CustomService customService

 

<table border="1">

    <tr><td colspan="3">Cotroller</td></tr>

    <tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>

    <tr><td>Transient</td><td>@ViewBag.TransientId</td><td>@ViewBag.TransientHashCode</td></tr>

    <tr><td>Scoped</td><td>@ViewBag.ScopedId</td><td>@ViewBag.ScopedHashCode</td></tr>

    <tr><td>Singleton</td><td>@ViewBag.SingletonId</td><td>@ViewBag.SingletonHashCode</td></tr>

</table>

<hr />

<table border="1">

    <tr><td colspan="3">View</td></tr>

    <tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>

    <tr><td>Transient</td><td>@transient.Id</td><td>@transient.GetHashCode()</td></tr>

    <tr><td>Scoped</td><td>@scoped.Id</td><td>@scoped.GetHashCode()</td></tr>

    <tr><td>Singleton</td><td>@singleton.Id</td><td>@singleton.GetHashCode()</td></tr>

</table>

<hr />

<table border="1">

    <tr><td colspan="3">Custom Service</td></tr>

    <tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>

    <tr><td>Transient</td><td>@customService.Transient.Id</td><td>@customService.Transient.GetHashCode()</td></tr>

    <tr><td>Scoped</td><td>@customService.Scoped.Id</td><td>@customService.Scoped.GetHashCode()</td></tr>

    <tr><td>Singleton</td><td>@customService.Singleton.Id</td><td>@customService.Singleton.GetHashCode()</td></tr>

</table>

输出内容如下:


1215970-20180524110357485-889019439.png

从左到又打开页面三次:
•Transient
如预期,每次注入都是不一样的实例。
•Scoped
在同一个Requset中,不论是在哪边被注入,都是同样的实例。
•Singleton
不管Requset多少次,都会是同一个实例。

参考

Introduction to Dependency Injection in ASP.NET Core
ASP.NET Core Dependency Injection Deep Dive

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,270评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,489评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,630评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,906评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,928评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,718评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,442评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,345评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,802评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,984评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,117评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,810评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,462评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,011评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,139评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,377评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,060评论 2 355

推荐阅读更多精彩内容

  • DI在.NET Core里面被提到了一个非常重要的位置, 这篇文章主要再给大家普及一下关于依赖注入的概念,身边有工...
    AI云栈阅读 1,112评论 0 10
  • 团队开发框架实战—ASP.NET Core的依赖注入ASP.NET Core 1.0在设计上原生就支持和有效利用依...
    Bobby0322阅读 1,262评论 1 1
  • 一、概念解释 使用 .NET,通过 new 运算符(即,new MyService 或任何想要实例化的对象类型)调...
    不是隔壁的老王啊阅读 1,913评论 0 0
  • 夏,滂沱大雨偃旗息鼓,幽林,蝉鸣鸟叫,欢快的享受着清凉。 骄阳,倔强挂在山头,散发着一天之中最后的余威,却被一习凉...
    汉江渔夫石泉阅读 278评论 0 0
  • 今天的晨读《爱的五种语言》相当实用,可以堪称一部美满婚姻实用手册。 可为什么现在离婚率依然暴涨,家庭问题那么多,有...
    海星_love阅读 116评论 1 4