美文网首页
ASP.NET Core 2 学习笔记(四)依赖注入

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

作者: 懒懒的程序员一枚 | 来源:发表于2019-05-25 19:45 被阅读0次

    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

    相关文章

      网友评论

          本文标题:ASP.NET Core 2 学习笔记(四)依赖注入

          本文链接:https://www.haomeiwen.com/subject/owvzzqtx.html