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

简介: 原文:ASP.NET Core 2 学习笔记(四)依赖注入ASP.NET Core使用了大量的依赖注入(Dependency Injection, DI),把控制反转(Inversion Of Control, IoC)运用的相当巧妙。
原文: 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 实例。

注入实例过程,情境如下:

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>

输出内容如下:

 

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

Service 实例产生方式:

 

图例说明:

  • A为Singleton对象实例
    一但实例化,就会一直存在于DI容器中。
  • B为Scoped对象实例
    每次Request就会产生新的实例在DI容器中,让同Request周期的使用方,拿到同一个实例。
  • CTransient对象实例
    只要跟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>

输出内容如下:

从左到又打开页面三次,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>

输出内容如下:

 

从左到又打开页面三次:

  • Transient
    如预期,每次注入都是不一样的实例。
  • Scoped
    在同一个Requset中,不论是在哪边被注入,都是同样的实例。
  • Singleton
    不管Requset多少次,都会是同一个实例。

参考

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

 

老司机发车啦:https://github.com/SnailDev/SnailDev.NETCore2Learning

目录
相关文章
|
8月前
|
开发框架 .NET C#
ASP.NET Core Blazor 路由配置和导航
大家好,我是码农刚子。本文系统介绍Blazor单页应用的路由机制,涵盖基础配置、路由参数、编程式导航及高级功能。通过@page指令定义路由,支持参数约束、可选参数与通配符捕获,结合NavigationManager实现页面跳转与参数传递,并演示用户管理、产品展示等典型场景,全面掌握Blazor路由从入门到实战的完整方案。
652 6
|
开发框架 .NET 开发者
简化 ASP.NET Core 依赖注入(DI)注册-Scrutor
Scrutor 是一个简化 ASP.NET Core 应用程序中依赖注入(DI)注册过程的开源库,支持自动扫描和注册服务。通过简单的配置,开发者可以轻松地从指定程序集中筛选、注册服务,并设置其生命周期,同时支持服务装饰等高级功能。适用于大型项目,提高代码的可维护性和简洁性。仓库地址:&lt;https://github.com/khellang/Scrutor&gt;
743 5
|
开发框架 算法 中间件
ASP.NET Core 中的速率限制中间件
在ASP.NET Core中,速率限制中间件用于控制客户端请求速率,防止服务器过载并提高安全性。通过`AddRateLimiter`注册服务,并配置不同策略如固定窗口、滑动窗口、令牌桶和并发限制。这些策略可在全局、控制器或动作级别应用,支持自定义响应处理。使用中间件`UseRateLimiter`启用限流功能,并可通过属性禁用特定控制器或动作的限流。这有助于有效保护API免受滥用和过载。 欢迎关注我的公众号:Net分享 (239字符)
448 1
|
开发框架 缓存 .NET
GraphQL 与 ASP.NET Core 集成:从入门到精通
本文详细介绍了如何在ASP.NET Core中集成GraphQL,包括安装必要的NuGet包、创建GraphQL Schema、配置GraphQL服务等步骤。同时,文章还探讨了常见问题及其解决方法,如处理复杂查询、错误处理、性能优化和实现认证授权等,旨在帮助开发者构建灵活且高效的API。
507 3
|
前端开发 .NET Linux
|
前端开发 .NET Linux
【翻译】Asp.net Core介绍
ASP.NET Core is a significant redesign of ASP.NET. This topic introduces the new concepts in ASP.NET Core and explains how they help you develop modern web apps. Asp.net Core是重新设计过得新一代Asp.Net。
1345 0
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
902 0
|
存储 开发框架 前端开发
[回馈]ASP.NET Core MVC开发实战之商城系统(五)
经过一段时间的准备,新的一期【ASP.NET Core MVC开发实战之商城系统】已经开始,在之前的文章中,讲解了商城系统的整体功能设计,页面布局设计,环境搭建,系统配置,及首页【商品类型,banner条,友情链接,降价促销,新品爆款】,商品列表页面,商品详情等功能的开发,今天继续讲解购物车功能开发,仅供学习分享使用,如有不足之处,还请指正。
516 0
|
开发框架 前端开发 .NET
[回馈]ASP.NET Core MVC开发实战之商城系统(一)
[回馈]ASP.NET Core MVC开发实战之商城系统(一)
621 0
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
453 7