Blazor University (49)依赖注入 —— 比较依赖范围

原文链接:https://blazor-university.com/dependency-injection/dependency-lifetimes-and-scopes/comparing-dependency-scopes/

比较依赖范围

源代码[1]

在本节中,我们将创建一个 Blazor 应用程序来演示各种依赖注入作用域的不同生命周期。

为此,我们将创建三个不同的服务(每个范围一个)。每个服务都将跟踪它的创建时间,以及一个递增的 InstanceNumber,以便我们可以跟踪已经创建了多少个该类型的实例。

首先,创建一个新的 Blazor 服务器端应用程序并添加一个静态类来跟踪应用程序启动的 DateTime,并计算自启动以来经过了多少时间。

public static class AppLifetime
{public static DateTime StartTimeUtc { get; } = DateTime.UtcNow;public static TimeSpan ElapsedTime => DateTime.UtcNow - StartTimeUtc;
}

服务接口

接下来,创建三个接口:IMyTransientService``、IMyScopedServiceIMySingletonService。每个接口都是相同的。

注意:有一些方法可以编写不需要重复的代码,但为了简单起见,此示例将重复代码。

public interface IMyTransientService
{public TimeSpan DeltaCreationTime { get; }public int InstanceNumber { get; }
}public interface IMyScopedService
{// As above
}public interface IMySingletonService
{// As above
}

服务实现

同样,这些服务的代码将被复制以避免复杂性。创建后,我们的服务将从 AppLifetime 类中获取 ElapsedTime,因此我们可以判断该实例是否是最近创建的。它还将从静态字段中为自己分配一个 InstanceId

public class MyTransientService : IMyTransientService
{public TimeSpan DeltaCreationTime { get; }public int InstanceNumber { get; }private static volatile int PreviousInstanceNumber;public MyTransientService(){DeltaCreationTime = DateTime.UtcNow - AppLifetime.StartTimeUtc;InstanceNumber = System.Threading.Interlocked.Increment(ref PreviousInstanceNumber);}
}public class MyScopedService : IMyScopedService
{//As above
}public class MySingletonService : IMySingletonService
{// As above
}

注册我们的服务

编辑 Startup.cs 文件,并在 ConfigureServices 方法中注册我们的服务,如下所示。

services.AddSingleton<IMySingletonService, MySingletonService>();
services.AddScoped<IMyScopedService, MyScopedService>();
services.AddTransient<IMyTransientService, MyTransientService>();

用户界面

在我们的页面中,我们想要展示实例是如何被多个组件共享的。我们将通过创建一个使用我们所有三个服务的组件来简化这一点,然后让我们的组件的两个实例同时显示在我们的页面中。

4cd6a3fc9b3c7c441dcbb9ceeb6e5531.jpeg

创建我们的服务消费组件

首先我们需要注入我们的服务,然后我们将显示每个实例的 InstanceNumber 以及注入的服务是否是最近创建的。为此,如果服务是在最后 500 毫秒内创建的,我们将在 UI 中包含一个 CSS 类。

@inject IMySingletonService MySingletonService
@inject IMyScopedService MyScopedService
@inject IMyTransientService MyTransientService
<dl><dt>@Caption</dt><dd><ul><li><span class="scope-name">Singleton</span><span class="@GetNewIndicatorCss(MySingletonService.DeltaCreationTime)">Instance #@MySingletonService.InstanceNumber</span></li><li><span class="scope-name">Scoped</span><span class="@GetNewIndicatorCss(MyScopedService.DeltaCreationTime)">Instance #@MyScopedService.InstanceNumber</span></li><li><span class="scope-name">Transient</span><span class="@GetNewIndicatorCss(MyTransientService.DeltaCreationTime)">Instance #@MyTransientService.InstanceNumber</span></li></ul></dd>
</dl>@code
{[Parameter]public string Caption { get; set; }private string GetNewIndicatorCss(TimeSpan time){if (AppLifetime.ElapsedTime - time < TimeSpan.FromMilliseconds(500)){return "instance-info new-instance";}return "instance-info";}
}
  • 第 1-3 行

    指示 Blazor 将我们的服务实例注入此组件。

  • 第 10、14 和 18 行

    显示每个注入依赖项的 InstanceNumber,并通过调用 GetNewIndicatorCss 设置 HTML 元素的类属性。

  • 第 31 行

    如果服务的 DeltaCreationTime(应用程序启动和实例创建之间的时间)在 AppLifetime.ElapsedTime(应用程序启动和当前时间之间的时间)的 500 毫秒内,则组件将使用 CSS 类 instance-info new-instance 呈现,否则它只有 CSS 类实例信息。这将让我们以不同的方式显示新实例的 UI。

在页面上显示我们的组件

编辑 Pages/Index.razor 并更改标记如下

@page "/"
<MyStandardComponent Caption="Component 1" />
<MyStandardComponent Caption="Component 2" />

运行应用程序,您将看到以下输出。

906d6490181676f9737a912ebf31ba8a.jpeg

当我们的 Scoped Instance 应该是第一个实例时,它却是 #2,这可能会让人感到惊讶。这是因为在浏览器和服务器之间建立 SignalR 连接以实际启动用户会话之前,服务器端 Blazor 应用程序会预渲染我们的页面以发回完整的 HTML 响应。我们现在可以通过执行以下操作来禁用它。

  • 编辑 /Pages/_Host.cshtml

  • 找到文本 render-mode="ServerPrerendered"

  • ServerPrerendered 更改为 Server

现在重新运行应用程序会给我们预期的结果。

cf9f1f8513f3affd25316021afc284a7.jpeg

Singleton Instance 将始终为 #1,因为它由所有用户共享。Scoped Instance 将是应用程序的第一个用户的 #1,第二个用户的 #2,依此类推。Transient Instance 对于第一个组件将是 #1,对于第二个组件将是 #2,因为它们是为每个组件创建的。如果用户离开页面然后返回,唯一会改变的实例编号是 Transient 实例,它将递增到 #3#4,并且在下次访问页面时将递增到 #5#6

交互式示例

为了使生命周期更加明显,我们将修改我们的 Index.razor 页面,以便它有条件地呈现我们的组件。我们还将展示实际的页面刷新如何影响我们的范围服务。

我们将通过以下步骤创建一个简单的向导式 UI

  1. 网站启动

  2. 更新的 UI – 重新创建了组件

  3. 在浏览器中重新加载页面

  4. 更新的 UI – 重新创建了组件

确保在每一步都重新创建组件

为了确保我们的组件在每个导航中创建,我们将有一个 CurrentStep 字段并根据 CurrentStep 是奇数还是偶数来显示一个或另一组组件。

@if (CurrentStep % 2 == 1)
{<MyStandardComponent Caption="Component 1" /><MyStandardComponent Caption="Component 2" />
}
else
{<MyStandardComponent Caption="Component 1" /><MyStandardComponent Caption="Component 2" />
}

强制在浏览器中刷新页面

要强制刷新页面,我们将使用 NavigationManager 进行导航,并将 true 传递给 forceLoad 参数。为了让我们知道在页面刷新后我们正在继续,我们将导航到 /continue。此请求将由同一页面提供服务,但我们知道我们应该从第 3 步而不是第 1 步开始。

@page "/"
@page "/{Continue}"
@inject NavigationManager NavigationManager@code
{[Parameter]public string Continue { get; set; }private void GoToNextStep(){CurrentStep++;if (CurrentStep == 3)NavigationManager.NavigateTo("/continue", forceLoad: true);}protected override void OnInitialized(){base.OnInitialized();if (!string.IsNullOrWhiteSpace(Continue))CurrentStep = 3;}
}

完成示例

以下(已完成)代码基本上是到目前为止所概述的内容,并添加了以下内容。

  1. 添加了显示当前步骤名称的文本。

  2. 添加了一个按钮以单击以进行下一步。

  3. 添加了在没有下一步时禁用按钮的代码。

  4. 添加了一些 CSS 样式,以使新实例通过脉冲来吸引我们的注意力。

@page "/"
@page "/{Continue}"
@inject NavigationManager NavigationManager<h1>Step @CurrentStep: @CurrentStepName</h1>
@if (CurrentStep % 2 == 1)
{<MyStandardComponent Caption="Component 1" /><MyStandardComponent Caption="Component 2" />
}
else
{<MyStandardComponent Caption="Component 1" /><MyStandardComponent Caption="Component 2" />
}
<button @onclick=GoToNextStep disabled=@IsButtonDisabled>Next step</button><style>.scope-name {width: 5rem;display: inline-block;font-weight: bold;}.instance-info {color: white;background-color: #888;padding: 0 4px;margin: 2px;display: inline-block;}.instance-info.new-instance {background-color: #3f8f42;animation: flash-green 2s;}@@keyframes flash-green {from {background-color: #4cff00;}to {background-color: #3f8f42;}}
</style>@code
{[Parameter]public string Continue { get; set; }private int CurrentStep = 1;private string CurrentStepName => StepNames[CurrentStep - 1];private bool IsButtonDisabled => CurrentStep >= StepNames.Length;private string[] StepNames = new string[]{"Website started","Updated UI - Components recreated","Reloaded page in browser","Updated UI - Components recreated"};protected override void OnInitialized(){base.OnInitialized();if (!string.IsNullOrWhiteSpace(Continue))CurrentStep = 3;}private void GoToNextStep(){CurrentStep++;if (CurrentStep == 3)NavigationManager.NavigateTo("/continue", forceLoad: true);}
}
  • 第 6 行

    根据 CurrentStep 是奇数还是偶数,渲染一组或另一组组件实例。这可确保为向导的每个步骤重新创建生成 UI 的组件。

  • 第 16 行

    转到向导的下一步(如果有)。

  • 第 69 行

    如果 Continue route 参数不为 null,则从步骤 3 继续。

  • 第 76 行

    如果单击下一步按钮并在第 3 步结束,则强制重新加载页面。

运行应用程序

当我们的网站第一次运行时,我们获得了所有注入依赖项的第一个实例。除了 Transient 依赖,因为它们是按需创建的,而不是缓存起来以供重用,所以我们得到了实例 #1#2

40194c5152298faf1ddb55bae569ab52.jpeg

当用户单击 Next step 按钮时,CurrentStep 增加到 2,我们的第一对组件被丢弃,而第二对组件被创建用于渲染。因为这些是在同一个用户会话中运行的新实例,所以它们将收到相同的 Scoped 依赖项以及两个新的 Transient 依赖项。

34cbcb1e85d11ddffbd23d5c2f8a47c4.jpeg

当用户再次单击 Next step 按钮时,应用程序将强制在新路径 /continue 处重新加载应用程序。由于在重新加载页面时忘记了 SignalR 连接的 ID,因此将为用户设置一个新连接,从而设置一个新范围。所以现在当前几个组件被渲染时,它们是 Scoped 实例 #2。

4c07d0f551cd7d14e46022fe88a6cd5f.jpeg

最后,当用户最后一次单击 Next step 按钮时,将创建第二对组件以进行渲染,并将注入由共享 Singleton 容器(实例#1)中缓存的实例缓存的 IMySingletonService,与当前用户的注入容器(实例 #2)缓存的 IMyScopedService,以及 IMyTransientService 的两个新实例。

c6a94b5ed43f04b20724863bf7da5db7.jpeg

WebAssembly 依赖范围

源代码[2]

因为 WebAssembly 在用户的浏览器中运行,并且每个选项卡都是一个完全独立的进程,所以我们的输出将与服务器端 Blazor 应用程序产生的略有不同。

首先,应用程序在浏览器选项卡中启动,我们得到了我们期望在服务器端 Blazor 应用程序上看到的相同输出。

d9b19e1a5350fdcd7070c087e8451969.jpeg

第一次单击 Next step 也向我们展示了一个与我们的服务器端 Blazor 应用程序相同的屏幕,其中 SingletonScoped 实例保持不变,因为它们都是缓存实例,并且按需创建了两个 Transient 实例。

0fbe60093f619cf612d95c53f2d5b6cb.jpeg

当我们的应用程序执行强制重新加载时,情况就不同了。在服务器端应用程序中,用户获得一个新的 SignalR 连接 ID,因此在与该 ID 绑定的服务器上获得一个新的依赖注入容器。在 WebAssembly 中,页面没有可重新连接的应用程序状态。一旦页面重新加载,整个应用程序状态就会被销毁,然后重新创建。结果,我们的实例数从头开始。

7b9420a39a26c5132ea02599ce0a2c37.jpeg

然后最后。

dd00d4243bdeb7ef79b494c7c77d9ff5.jpeg

结论

由于用户界面和 UI 逻辑在 Blazor 应用程序中绑定在一起,因此没有每个请求的依赖注入范围。

Singleton 注册的依赖项在服务器端应用程序中的用户之间共享,但在 WebAssembly 应用程序中每个浏览器选项卡都是唯一的。

Scoped 依赖的行为与 Singleton 注册的依赖几乎相同,除了它们与其他用户/其他浏览器选项卡隔离。

Transient 依赖在服务器端和 WebAssembly 上的工作方式相同,并且与 ASP.NET MVC 中的工作方式相同——除了依赖注入容器在 ASP.NET MVC 中的页面请求之后被释放。请参阅 Transient 依赖项的避免内存泄漏[3]部分。

有一些方法可以为每个用户引入额外的范围。这种技术将在后面的部分中介绍。

参考资料

[1]

源代码: https://github.com/mrpmorris/blazor-university/tree/master/src/DependencyInjection/ServerDependencyScopes

[2]

源代码: https://github.com/mrpmorris/blazor-university/tree/master/src/DependencyInjection/WebAssemblyDependencyScopes

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/283153.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计模式概论

此文转载于 http://blog.csdn.net/hguisu/article/details/74968191. 设计模式设计模式&#xff08;Design pattern&#xff09;是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性…

只需要2个工具,百度云盘大文件就能用迅雷和IDM下载

不会代码&#xff0c;不懂脚本&#xff0c;没关系 &#xff0c;能找到一座通往它们的桥梁&#xff0c;照样能到达彼岸。 这里以360极速浏览器为例。 在浏览器地址框输入以下地址直接到达浏览器安装扩展插件的地方&#xff08;偷个懒&#xff0c;复制网址吧&#xff09;&#xf…

rsync服务器的配置

一、rsync 简介Rsync&#xff08;remote synchronize&#xff09;是一个远程数据同步工具&#xff0c;可通过LAN/WAN快速同步多台主机间的文件&#xff0c;也可以使用 Rsync 同步本地硬盘中的不同目录。 Rsync 是用于取代rcp的一个工具&#xff0c;Rsync使用所谓的 “Rsync 算法…

Vue学习笔记入门篇——数据及DOM

本文为转载&#xff0c;原文&#xff1a;Vue学习笔记入门篇——数据及DOM 数据 data 类型 Object | Function 详细 Vue 实例的数据对象。Vue 将会递归将 data 的属性转换为 getter/setter&#xff0c;从而让 data 的属性能够响应数据变化。对象必须是纯粹的对象(含有零个或多个…

BZOJ 3144 [Hnoi2013]切糕

3144: [Hnoi2013]切糕 Description Input 第一行是三个正整数P,Q,R&#xff0c;表示切糕的长P、 宽Q、高R。第二行有一个非负整数D&#xff0c;表示光滑性要求。接下来是R个P行Q列的矩阵&#xff0c;第z个 矩阵的第x行第y列是v(x,y,z) (1≤x≤P, 1≤y≤Q, 1≤z≤R)。 100%的数据…

《ASP.NET Core 6框架揭秘》实例演示[18]:HttpClient处理管道

在《《ASP.NET Core 6框架揭秘》实例演示[17]&#xff1a;利用IHttpClientFactory工厂来创建HttpClient》之后&#xff0c;我们将关注点放到HttpClient对象上。我们知道ASP.NET的核心就是由中间件组成的请求处理管道&#xff0c;HttpClient也采用了类似的设计。HttpClient管道由…

雅诗兰黛天猫超级品牌日:未央唇膏、红装小棕瓶“当红不让”

随着年末圣诞季的临近&#xff0c;各大美妆品牌陆续推出了圣诞套装&#xff0c;红红火火的超豪华套装&#xff0c;算是对用户最实在的回馈。高端美妆品牌的“领头羊”雅诗兰黛&#xff0c;当然也“当红不让”&#xff0c;趁着圣诞季&#xff0c;与天猫超级品牌日联手打造了一场…

JAVA常见算法题(三十一)---冒泡排序

package com.jege.spring.boot.hello.world;/*** java算法之冒泡排序<br>* 将数组按照从大到小的顺序排列<br>* * * author Administrator**/ public class BubbleSort{public static void main(String[] args){int score[] {67, 69, 75, 87, 89, 90, 99, 100};fo…

WPF实现物理效果 拉一个小球

原文:WPF实现物理效果 拉一个小球一直以来都对物理效果有神秘感,完全不知道怎么实现的.直到看到了周银辉在老早前写的一篇博客:http://www.cnblogs.com/zhouyinhui/archive/2007/06/23/793724.html 终于知道是怎么实现的了. CompositionTarget类的Rendering事件.在每一帧成功渲…

C# CM框架下一行代码实现多页面管理

概述之前我分享过一个wpf的项目实践&#xff0c;主页面左侧是个listbox&#xff0c;每次选择改变后呈现对应的页面&#xff0c;界面图如下&#xff1a;要实现这样一个功能&#xff0c;我之前是采用传统方式实现的&#xff0c;本节我采用CM框架下的Conductor<T>去实现&…

关于JAVA异常处理的20个最佳实践

关于JAVA异常处理的20个最佳实践 在我们深入了解异常处理最佳实践的深层概念之前&#xff0c;让我们从一个最重要的概念开始&#xff0c;那就是理解在JAVA中有三种一般类型的可抛类: 检查性异常(checked exceptions)、非检查性异常(unchecked Exceptions) 和 错误(errors)。 异…

SSM框架搭建(四) springmvc和mybatis的配置

SSM框架搭建&#xff08;一&#xff09; JDK和MAVEN环境搭建 SSM框架搭建&#xff08;二&#xff09; 创建MAVEN项目 SSM框架搭建&#xff08;三&#xff09; 数据库创建和MyBatis生成器自动生成实体类、DAO接口和Mapping映射文件 SSM框架搭建&#xff08;四&#xff09; sprin…

10 个有关 String 的面试问题

2019独角兽企业重金招聘Python工程师标准>>> 下面是面试中最容易问到的有关String的问题。 1. 如何比较两个字符串&#xff1f;使用“”还是equals()方法&#xff1f; 简单来讲&#xff0c;“”测试的是两个对象的引用是否相同&#xff0c;而equals()比较的是两个字…

基于ASP.NET Core 6.0的整洁架构

背景最近尝试录制了一个系列视频&#xff1a;《ASP.NET Core 6.0Vue.js 3 实战开发》&#xff0c;本节是视频内部整洁架构的理论和实战的文字稿。因为在录制之前&#xff0c;我通常会编写完整的文字内容作为视频文案&#xff0c;这里分享给大家&#xff0c;希望对你有所帮助。如…

大并发数据队列

图解#include<stdio.h>#include<stdlib.h>#include<memory.h>#define N 100#define mytype intstruct MyQueue{mytype data[N];//数组存储队列int front;//拉屎int rear;//吃东西};typedef struct MyQueue myQ;//初始化void init(myQ *p){p->front p->…

总在用户态调试 C# 程序,终还是搭了一个内核态环境

一&#xff1a;背景 一直在用 WinDbg 调试用户态程序&#xff0c;并没有用它调试过 内核态&#xff0c;毕竟不是做驱动开发&#xff0c;也没有在分析 dump 中需要接触用内核态的需求&#xff0c;但未知的事情总觉得很酷&#xff0c;加上最近在看 《深入解析 Windows 操作系统》…

bat kafka启动_windows下搭建Kafka,并通过命令窗口收发消息

参考网址&#xff1a;前提条件&#xff1a;windows环境需要安装jdk2.由于Kafka依赖于zookeeper&#xff0c;所以也需要下载zookeeper,可以通过官网下载http://zookeeper.apache.org/3.安装zookeeper将压缩包解压后&#xff0c;到bin目录下&#xff0c;启动zkServer.bat即可注意…

异常处理、socke基于TCP协议编程

一、异常处理 1、错误和异常 1.程序中难免出现错误&#xff0c;而错误分成两种 &#xff08;1&#xff09;语法错误&#xff08;这种错误过不了Python解释器的语法检测&#xff0c;必须在程序执行前改正&#xff09; #语法错误示范一 if#语法错误示范二 def test:pass#语法错误…

windows下apache报错The requested operation has failed解决方法

2019独角兽企业重金招聘Python工程师标准>>> Apache报错The requested operation has failed&#xff0c;基本上是因为端口被占用。解决方法如下&#xff1a; 第一步&#xff0c;运行cmd&#xff0c;cd 定位到Apache安装目录的bin目录下&#xff0c;输入httpd.exe -…

stm32 usmart使用

我直接用正点原子给的&#xff0c;步骤如下 先添加三个.c进工程&#xff0c;添加两个头文件的编译路径 #include "usart.h"#include "usmart.h" main函数里添加如下 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2 uart_init(960…