一个案例说清楚 AddSingleton vs AddScoped vs AddTransient 三者的差异性

Author Image
admin Thursday, June 3, 2021 阅读数: 47
Share:

一个案例说清楚 AddSingleton vs AddScoped vs AddTransient 三者的差异性

本文作者:梁桐铭- 微软最有价值专家(Microsoft MVP)
本文出自《从零开始学 ASP.NET Core 与 EntityFramework Core》目录
视频课程效果更佳:跨平台开发实战掌握 ASP.NET Core 与 EntityFramework Core

AddSingleton vs AddScoped vs AddTransient 三者的差异行

在本章节中,我们将通过一个示例讨论 ASP.NET Core 中 AddSingleton(),AddScoped()和 AddTransient()方法之间的差异。

IStudentRepository 接口

我们来回顾下 IStudentRepository 接口。

  • Add()方法将新学生添加到存储中。
  • GetAllStudents()方法返回存储库中的所有的学生信息。

public  interface IStudentRepository
    {

      Student GetStudent(int id);

        IEnumerable<Student> GetAllStudents();

        Student Add(Student student);

    }

学生类

  public class Student
    {
      public int Id { get; set; }
        [Required(ErrorMessage = "请输入名字"), MaxLength(50, ErrorMessage = "名字的长度不能超过50个字符")]
        [Display(Name = "名字")]
        public string Name { get; set; }
        [Required]
        [Display(Name = "主修科目")]
        public MajorEnum? Major { get; set; }

        [Display(Name = "电子邮件")]
        [RegularExpression(@"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",
        ErrorMessage = "邮箱的格式不正确")]
        [Required(ErrorMessage = "请输入邮箱地址")]
        public string Email { get; set; }

    }

MockStudentRepository 仓储服务

MockStudentRepository 实现了 IStudentRepository。 为了使示例简单,我们通过硬编码把所有的学生信息都存放在内存中,定义了一个私有字段_studentList来调用。


     public class MockStudentRepository : IStudentRepository
    {
        private List<Student> _studentList;


        public MockStudentRepository()
        {

            _studentList = new List<Student>()
            {
            new Student() { Id = 1, Name = "张三", Major = MajorEnum.FirstGrade, Email = "Tony-zhang@52abp.com" },
            new Student() { Id = 2, Name = "李四", Major = MajorEnum.SecondGrade, Email = "lisi@52abp.com" },
            new Student() { Id = 3, Name = "王二麻子", Major = MajorEnum.GradeThree, Email = "wang@52abp.com" },
            };

        }

        public Student Add(Student student)
        {
            student.Id = _studentList.Max(s => s.Id) + 1;
            _studentList.Add(student);
            return student;
        }

        public IEnumerable<Student> GetAllStudents()
        {
            return _studentList;

        }
        public Student GetStudent(int id)
        {
            return _studentList.FirstOrDefault(a => a.Id == id);
        }
    }

Home 控制器

IStudentRepository 被注入 HomeController ,

响应 POST 请求的Create()操作方法使用注入的实例将 student 对象添加到存储库。


      public class HomeController : Controller
    {
        private readonly IStudentRepository _studentRepository;

       //使用构造函数注入的方式注入IStudentRepository
        public HomeController(IStudentRepository studentRepository)
        {
            _studentRepository = studentRepository;

        }



        public IActionResult Index()
        {
            IEnumerable<Student> students = _studentRepository.GetAllStudents();

            return View(students);

        }





        public IActionResult Details(int? id)
        {

           //实例化HomeDetailsViewModel并存储Student详细信息和PageTitle
            HomeDetailsViewModel homeDetailsViewModel = new HomeDetailsViewModel()
            {
                Student = _studentRepository.GetStudent(id ?? 1),
                PageTitle = "学生详细信息"
            };
            return View(homeDetailsViewModel);
        }

        [HttpGet]
        public ViewResult Create()
        {
            return View();
        }

        [HttpPost]
        public IActionResult Create(Student student)
        {
            if (ModelState.IsValid)
            {

                Student newStudent = _studentRepository.Add(student);
                return RedirectToAction("Details", new { id = newStudent.Id });
            }
            return View();
        }
    }
}

创建视图

我们使用@inject 指令将 IStudentRepository 服务注入到 Create 视图中。 我们使用注入的服务来显示存储库中的学生总数。

@model Student @inject IStudentRepository _studentRepository @{ ViewBag.Title =
"创建学生信息"; }

<form asp-controller="home" asp-action="create" method="post" class="mt-3">
  <div asp-validation-summary="All" class="text-danger"></div>

  <div class="form-group row">
    <label asp-for="Name" class="col-sm-2 col-form-label"></label>
    <div class="col-sm-10">
      <input asp-for="Name" class="form-control" placeholder="请输入名字"/>
      <span asp-validation-for="Name" class="text-danger"></span>
    </div>
  </div>

  <div class="form-group row">
    <label asp-for="Email" class="col-sm-2 col-form-label"></label>
    <div class="col-sm-10">
      <input
        asp-for="Email"
        class="form-control"
        placeholder="请输入邮箱地址"
     />
      <span asp-validation-for="Email" class="text-danger"></span>
    </div>
  </div>

  <div class="form-group row">
    <label asp-for="Major" class="col-sm-2 col-form-label"></label>
    <div class="col-sm-10">
      <select
        asp-for="Major"
        class="custom-select mr-sm-2"
        asp-items="Html.GetEnumSelectList<MajorEnum>()"
      >
        <option value=""> 请选择</option>
      </select>
      <span asp-validation-for="Major" class="text-danger"></span>
    </div>
  </div>

  <div class="form-group row">
    <div class="col-sm-10">
      <button type="submit" class="btn btn-primary">创建</button>
    </div>
  </div>

  <div class="form-group row">
    <div class="col-sm-10">
      学生总人数 = @_studentRepository.GetAllStudents().Count().ToString()
    </div>
  </div>
</form>

注册服务

ASP.NET Core 提供 3 种方法来注册服务到依赖注入容器中。 而我们使用的方法决定了注册服务的生命周期。

  • AddSingleton() : 顾名思义,AddSingleton()方法创建一个 Singleton 服务。首次请求时会创建 Singleton 服务。然后,所有后续的请求中都会使用相同的实例。因此,通常,每个应用程序只创建一次 Singleton 服务,并且在整个应用程序生命周期中使用该单个实例。

  • AddTransient() :此方法创建一个 Transient 服务。每次请求时,都会创建一个新的 Transient 服务实例。

  • AddScoped() - 此方法创建一个 Scoped 服务。在范围内的每个请求中创建一个新的 Scoped 服务实例。例如,在 Web 应用程序中,它为每个 http 请求创建 1 个实例,但在同一 Web 请求中的其他服务在调用这个请求的时候,都会使用相同的实例。注意,它在一个客户端请求中是相同的,但在多个客户端请求中是不同的。

在 ASP.NET Core 中,这些服务都是在Startup.cs文件的ConfigureServices()方法中注册。

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IStudentRepository, MockStudentRepository>();

 }

AddSingleton 服务

  • 目前我们正在使用 AddSingleton()方法来注册 MockStudentRepository 服务。
  • AddSingleton()在第一次请求时创建服务的单个实例,并在需要该服务的所有位置复用该实例。
  • 这意味着应用程序在整个生命周期内的所有请求都使用相同的实例。
  • 目前,在我们的示例中,我们需要 2 个位置的 MockStudentRepository 服务实例, 在 HomeController 中的 Create 视图和 Create 操作方法中。
  • 此时,当我们导航到 http://localhost:13380/home/create 时,我们看到总学生人数 为 3。
  • 而要提供此请求,首先要创建 HomeController 的实例。HomeController 依赖于 IStudentRepository
  • 这是第一次请求服务实例。所以 ASP.NET Core 创建了一个服务实例并将其注入 HomeController
  • Create 视图还需要服务实例来计算学生总数。因为是 Singleton服务,使用相同的服务实例。因此,已创建的服务实例也会提供给Create视图
  • 现在,如果您在"Name"文本框中,并填入名字,并单击" 创建"按钮,则每次单击按钮时都会看到计数增加。
  • 这是因为使用 Singleton 时,使用相同的对象,因此可以在所有HTTP请求的所有位置查看到对象所做的更改。

AddScoped 服务

现在,使用 AddScoped()方法注册服务。

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddScoped <IStudentRepository, MockStudentRepository>();
}
  • 我们向 http://localhost:13380/home/create发出请求。
  • 我们看到学生总数是 3,我们再输入名字,然后单击" 创建"按钮。
  • 将学生总数增加至 4。
  • 当我们再次单击" 创建"按钮时,"学生总数"仍为 4。

这是因为对于每个 http 请求的作用域服务,我们都会得到一个新实例。但是,如果在同一个 http 请求中,如果在视图和控制器等多个位置需要服务,都会为该 http 请求的整个范围提供相同的实例。

如果我们将此与示例相关联作出的解释,则是 HomeControllerCreate 视图将对给定的 http 请求使用相同的服务实例。这就是 Create 视图还能够看到 HomeControllerCreate 操作方法添加的新学生的原因。 因此,我们将学生总数为 4。

但是每个新的 http 请求都将获得该服务的新实例。这就是在一个 http 请求中添加的学生无法在另一个 http 请求中看到的原因。所以这意味着每次我们点击 创建 按钮时,我们都会发出一个新的 http 请求,因此总学生人数不会超过 4。

AddTransient 服务

最后让我们使用 AddTransient()方法来注册我们的服务

public void ConfigureServices(IServiceCollection services)
        {
 services.AddMvc().AddXmlSerializerFormatters();
services.AddTransient<IStudentRepository, MockStudentRepository>();

        }

  • 我们向 http://localhost:13380/home/create发出请求。
  • 我们看到的学生总数是 3
  • 输入学生姓名后,然后单击"创建"按钮。
  • 请注意,在 Create 视图中,我们仍然看到 总学生人数 为 3

这是因为对于临时服务,每次请求服务实例时都会提供新实例,无论它是在同一 http 请求的范围内还是在不同的 http 请求中。

由于即使在给定 http 请求的同一范围内也提供了新实例,因此 Create 视图无法看到 HomeController 的 Create 方法中添加的新学生。即使在添加新学生之后,这也是学生数量为 3 的原因。

Scoped(作用域)服务与 Transient(暂时性) 服务与 Singleton(单例)服务

以下是 Scoped 服务和 Transient 服务之间的主要区别。

使用作用域服务,我们在给定的 http 请求范围内获得相同的实例,但跨不同的 http 请求 获得新实例.

对于瞬时服务,每次请求实例时都会提供一个新实例,无论它是否在同一 http 的范围内请求或跨越不同的 http 请求

使用 Singleton 单例服务,只有一个实例。首次请求服务时,将创建一个实例,并且整个应用程序中的所有 http 请求都使用该实例。

小结

截止到目前为止,MVC 的基础我们也就完毕了。

文章说明

如果您觉得我的文章质量还不错,欢迎打赏,也可以订阅我的视频哦
未得到授权不得擅自转载本文内容,52abp.com 保留版权
感谢您对我的支持

关注微信公众号:角落的白板报

公众号:角落的白板报