代码改变世界

.Net Core3.0依赖注入DI

2019-10-31 09:19  成天  阅读(...)  评论(... 编辑 收藏

构建ASP.NET Core应用程序的时候,依赖注入已成为了.NET Core的核心,这篇文章,我们理一理依赖注入的使用方法。

不使用依赖注入

 首先,我们创建一个ASP.NET Core Mvc项目,定义个表达的爱服务接口,中国小伙类实现这个类如下:

public interface ISayLoveService
    {
        string SayLove();
    }

 public class CNBoyService : ISayLoveService
    {
        public string SayLove()
        {
            return "安红,我喜欢你";
        }
    }

在LoveController 控制器中调用 ISayLoveService的SayLove方法。

public class LoveController : Controller

    {       
       private  ISayLoveService loveService;     
       public IActionResult Index()
        {
            loveService = new CNBoyService(); //中国小伙对安红的表达
            ViewData["SayLove"] = loveService.SayLove();    
            return View();
        }
    }

输出如图:

 

小结:LoveController控制器调用ISayLoveService服务的SayLove方法;我们的做法,直接在控制器去new CNBoyService()实例对象,也就是LoveController依赖ISayLoveService类。

思考:能不能有种模式,new实例不要在使用的时候进行创建,而是在外部或者有一个容器进行管理;这不就是ioc思想吗?好处,代码的解耦、代码更好的维护等等。

使用依赖注入

上面的疑惑,答案是肯定的,有!并且ASP.NET Core 支持依赖关系注入 (DI) 软件设计模式(当然也可以兼容第三方)。我们还使用上面的代码,

服务注册

 在Startup类ConfigureServices方法中注册服务容器中的依赖关系

  public void ConfigureServices(IServiceCollection services)

        {
            services.AddSingleton<ISayLoveService, CNBoyService>();
            services.AddControllersWithViews();
        }

 在LoveControlle控制器中,通过构造函数注入

 private readonly  ISayLoveService loveService;  
public LoveController(ISayLoveService loveService)
{

this.loveService = loveService; }

public IActionResult Index() {
ViewData[
"SayLove"] = loveService.SayLove();

return View(); }

LoveController 正在将ISayLoveService作为依赖项注入其构造函数中,然后在Index方法中使用它。

推荐:

  • 将注入的依赖项分配给只读字段/属性(以防止在方法内部意外为其分配另一个值)。

  • 使用接口或基类抽象化依赖关系实现。

小结:在控制器中,还有几种使用如:[FromServices] 标签 、 HttpContext.RequestServices.GetService<T>();我们发现可以使用ASP.NET Core 提供了一个内置的服务容器 IServiceProvider。服务只需要在Startup.ConfigureServices 方法中注册,然后在运行时将服务注入 到使用它的类的构造函数中。 框架负责创建依赖关系的实例,并在不再需要时对其进行处理。

思考:服务注册的时候使用的是 AddSingleton,如services.AddSingleton<ISayLoveService, CNBoyService>();还有其他的吗?

服务生命周期

服务注册的时候,ASP.NET Core支持指定三种生命周期如:

  1. Singleton 单例

  2. Scoped 范围

  3. Transient 短暂的

Singleton 仅创建一个实例。该实例在需要它的所有组件之间共享。因此始终使用同一实例。

Scoped 每个范围创建一个实例。在对应用程序的每个请求上都会创建一个范围,因此每个请求将创建一次注册为Scoped的任何组件。

Transient 在每次被请求时都会创建,并且永不共享。

为了能够更好的裂解生命周期的概念,我们把上面代码稍作改动,做一个测试:

ISayLoveService 新增个属性LoveId,类型为guid,

public interface ISayLoveService
    {
        Guid LoveId { get; }
        string SayLove();
    }
    public interface ITransientSayLoveService : ISayLoveService
    {
    }
    public interface IScopedSayLoveService : ISayLoveService
    {
    }
    public interface ISingletonSayLoveService : ISayLoveService
    {
    }
    public interface ISingletonInstanceSayLoveService : ISayLoveService
    {
    }

 BoyService也很简单,在构造函数中传入一个Guid,并对它进行赋值。

public class BoyService : ITransientSayLoveService,
        IScopedSayLoveService,
        ISingletonSayLoveService,
        ISingletonInstanceSayLoveService
    {
        public BoyService():this(Guid.NewGuid()) { }
        public BoyService(Guid id)
        {
            LoveId = id;
        }
        public Guid LoveId { get; private set; }

        public string SayLove()
        {
            return LoveId.ToString();
        }
    }

 每个实现类的构造函数中,我们都产生了一个新的guid,通过这个GUID,我们可以判断这个类到底重新执行过构造函数没有.

服务注册代码如下:

   public void ConfigureServices(IServiceCollection services)
        {
            //生命周期设置为Transient,因此每次都会创建一个新实例。
            services.AddTransient<ITransientSayLoveService, BoyService>();
            services.AddScoped<IScopedSayLoveService, BoyService>();
            services.AddSingleton<ISingletonSayLoveService, BoyService>();
            services.AddSingleton<ISingletonInstanceSayLoveService>(new BoyService(Guid.Empty));

            services.AddControllersWithViews();
        }

在LifeIndex方法中多次调用ServiceProvider的GetService方法,获取到的都是同一个实例。

  public IActionResult LifeIndex()
        {
            ViewData["TransientSayLove1"] = HttpContext.RequestServices.GetService<ITransientSayLoveService>().SayLove();
            ViewData["ScopedSayLove1"] = HttpContext.RequestServices.GetService<IScopedSayLoveService>().SayLove();
            ViewData["SingletonSayLove1"] = HttpContext.RequestServices.GetService<ISingletonSayLoveService>().SayLove();
            ViewData["SingletonInstanceSayLove1"] = HttpContext.RequestServices.GetService<ISingletonInstanceSayLoveService>().SayLove();
            //同一个HTTP请求 ,在从容器中获取一次
            ViewData["TransientSayLove2"] = HttpContext.RequestServices.GetService<ITransientSayLoveService>().SayLove();
            ViewData["ScopedSayLove2"] = HttpContext.RequestServices.GetService<IScopedSayLoveService>().SayLove();
            ViewData["SingletonSayLove2"] = HttpContext.RequestServices.GetService<ISingletonSayLoveService>().SayLove();
            ViewData["SingletonInstanceSayLove2"] = HttpContext.RequestServices.GetService<ISingletonInstanceSayLoveService>().SayLove();

            return View();
        }

我们编写view页面,来展示这些信息如下:

@{
    ViewData["Title"] = "LifeIndex";
}

    <div class="row">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h2 class="panel-title">Operations</h2>
            </div>
            <div class="panel-body">
                <h3>获取第一次</h3>
                <dl>
                    <dt>Transient1</dt>
                    <dd>@ViewData["TransientSayLove1"] </dd>
                    <dt>Scoped1</dt>
                    <dd>@ViewData["ScopedSayLove1"]</dd>
                    <dt>Singleton1</dt>
                    <dd>@ViewData["SingletonSayLove1"] </dd>
                    <dt>Instance1</dt>
                    <dd>@ViewData["SingletonInstanceSayLove1"]</dd>
                </dl>
                <h3>获取第二次</h3>
                <dl>
                    <dt>Transient2</dt>
                    <dd>@ViewData["TransientSayLove2"]</dd>
                    <dt>Scoped2</dt>
                    <dd>@ViewData["ScopedSayLove2"]</dd>
                    <dt>Singleton2</dt>
                    <dd>@ViewData["SingletonSayLove2"]</dd>
                    <dt>Instance2</dt>
                    <dd>@ViewData["SingletonInstanceSayLove2"]</dd>
                </dl>
            </div>
        </div>
    </div>

运行代码第一次输出:

我们发现,在一次请求中,发现单例、范围的生命周期的guid 没有变化,说明分别用的是同一个对象,而瞬态guid不同,说明对象不是一个。

刷新之后,查看运行效果

我们发现通过刷新之后,单例模式的guid还是跟首次看到的一样,其他的都不同;

总结:如果您将组件A注册为单例,则它不能依赖已注册“作用域”或“瞬态”生存期的组件。一般而言:组件不能依赖寿命短于其寿命的组件。如果默认的DI容器不能满足项目需求,可以替换成第三方的如功能强大的Autofac。