Blazor University (47)依赖注入 —— Singleton 依赖

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

Singleton 依赖

Singleton 依赖是一个由依赖它的每个对象共享的单个对象实例。在 WebAssembly 应用程序中,这是在浏览器的当前选项卡中运行的当前应用程序的生命周期。当类没有状态或(在服务器端应用程序中)具有可以在连接到同一服务器的所有用户之间共享的状态时,将依赖项注册为 Singleton 是可以接受的;Singleton 依赖项必须是线程安全的。

为了说明这种共享状态,让我们创建一个非常简单(即不可扩展)的聊天应用程序。

Singleton 聊天服务

源代码[1]

首先,创建一个新的 Blazor 服务器应用。然后新建一个名为 Services 的文件夹,添加如下界面。这是我们的 UI 将用来向其他用户发送消息的服务,每当用户发送消息时都会收到通知,并且当我们的用户首次连接时,他们将能够看到迄今为止有限的聊天历史记录。因为这是在 Blazor 服务器端应用程序上运行的 Singleton 依赖项,所以它将由同一服务器上的所有用户共享。

public interface IChatService
{bool SendMessage(string username, string message);string ChatWindowText { get; }event EventHandler TextAdded;
}

为了实现这项服务,我们将使用 List<string> 来存储聊天历史记录,并在队列中超过 50 条时从列表的开头删除消息。我们将使用 lock() 语句来确保线程安全。

public class ChatService : IChatService
{public event EventHandler TextAdded;public string ChatWindowText { get; private set; }private readonly object SyncRoot = new object();private List<string> ChatHistory = new List<string>();public bool SendMessage(string username, string message){if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(message))return false;string line = $"<{username}> {message}";lock (SyncRoot){ChatHistory.Add(line);while (ChatHistory.Count > 50)ChatHistory.RemoveAt(0);ChatWindowText = string.Join("\r\n", ChatHistory.Take(50));}TextAdded?.Invoke(this, EventArgs.Empty);return true;}
}
  • 第 3 行

    每当有新消息发布到我们的聊天服务器时,我们的 UI 可以挂钩的事件。

  • 第 4 行

    代表最多 50 行聊天记录的字符串。

  • 第 16-23 行

    锁定 SyncRoot 以防止并发问题,将当前行添加到聊天历史记录,如果超过 50 行,则删除最旧的历史记录,然后重新创建 ChatWindowText 属性的内容。

  • 第 25 行

    通知聊天服务的所有使用者 ChatWindowText 已更新。

要注册服务,请打开 Startup.cs 并在 ConfigureServices 中添加以下内容

services.AddSingleton<IChatService, ChatService>();

定义用户界面

为了将 C# 聊天代码与显示标记分开,我们将使用代码隐藏方法。在 Pages 文件夹中创建一个名为 Index.razor.cs 的新文件,Visual Studio 应自动将其嵌入到 Index.Razor 文件下方。然后我们需要将我们的新索引类标记为部分。

public partial class Index
{
}

那么需要我们的组件类来做以下事情

  1. 初始化后,订阅 ChatService.TextAdded

  2. 为了避免我们的 Singleton 持有已处置对象的引用,当我们的组件被处置时,我们应该取消订阅 ChatService.TextAdded

  3. ChatService.TextAdded 被触发时,我们应该更新用户界面以显示新的 IChatService.ChatWindowText 内容。

  4. 我们应该允许用户输入他们的名字+一些文本发送给其他用户。

让我们从最简单的步骤开始,也就是第 4 步,然后按照列出的顺序实现其他要求。

为简单起见,我们将向当前类添加 NameText 属性,而不是创建视图模型,我们还将使用 RequiredAttribute 装饰它们,以便在用户尝试发布文本而不填写所需输入时向他们提供反馈.

public partial class Index
{[Required(ErrorMessage = "Enter name")]public string Name { get; set; }[Required(ErrorMessage = "Enter a message")]public string Text { get; set; }
}

初始标记和验证

我们将替换 Index.razor 的内容并将其替换为一个简单的 EditForm,该 EditForm 由一个 DataAnnotationsValidator 组件和一些用于输入用户名和文本的 Bootstrap CSS 修饰的 HTML 组成。

@page "/"
<h1>Blazor web chat</h1><EditForm Model=@this><DataAnnotationsValidator/><div class="row mt-1"><div class="col-3"><InputText class="form-control" placeholder="Name" @bind-Value=Name maxlength=20/><ValidationMessage For=@( () => Name )/></div><div class="col-9"><div class="input-group"><InputText class="form-control" placeholder="..." @bind-Value=Text maxlength=100 /><div class="input-group-append"><button class="btn btn-primary" type=submit>Send</button></div></div><ValidationMessage For=@( () => Text )/></div></div>
</EditForm>
  • 第 4 行

    创建一个绑定到 this 的 EditForm。

  • 第 5 行

    启用基于数据注释(如RequiredAttribute)的验证。

  • 第 8 行

    将 Blazor InputText 组件绑定到 Name 属性。

  • 第 9 行

    显示 Name 属性的任何验证错误。

  • 第 13 行

    将 Blazor InputText 组件绑定到 Text 属性。

  • 第 18 行

    显示 Text 属性的任何验证错误。

使用 IChatService

接下来,我们将注入 IChatService 并将其完全连接到我们的组件。为此,我们需要执行以下操作。

public partial class Index : IDisposable
{[Required(ErrorMessage = "Enter name")]public string Name { get; set; }[Required(ErrorMessage = "Enter a message")]public string Text { get; set; }[Inject]private IChatService ChatService { get; set; }private string ChatWindowText => ChatService.ChatWindowText;protected override void OnInitialized(){base.OnInitialized();ChatService.TextAdded += TextAdded;}private void SendMessage(){if (ChatService.SendMessage(Name, Text))Text = "";}private void TextAdded(object sender, EventArgs e){InvokeAsync(StateHasChanged);}void IDisposable.Dispose(){ChatService.TextAdded -= TextAdded;}
}
  • 第 8-9 行

    声明应自动注入的对 IChatService 的依赖项。

  • 第 11 行

    声明一个使访问 IChatService.ChatWindowText 变得简单的属性。

  • 第 16 行

    订阅 IChatService.TextAdded 事件。

  • 第 21 行

    将当前用户的输入发送到聊天服务。

  • 第 27 行

    每次调用 IChatService.TextAdded 时刷新用户界面。

  • 第 32 行

    当组件被释放时,取消订阅 IChatService.TextAdded 以避免内存泄漏。

注意:我们必须将 StateHasChanged 调用包装在对 InvokeAsync 的调用中。这是因为 IChatService.TextAdded 事件将由添加文本的任何用户触发,因此将由各种线程触发。我们需要 Blazor 使用 InvokeAsync 编组这些调用,以确保我们组件上的所有线程调用都按顺序执行。

将聊天窗口添加到我们的用户界面

我们现在只需要在我们的标记中添加一个 HTML <textarea> 控件并将它绑定到我们的 ChatWindowText 属性,并确保在提交 EditForm 时没有验证错误,它会调用我们的 SendMessage 方法。

最终的用户界面标记如下所示。

@page "/"<h1>Blazor web chat</h1><EditForm Model=@this OnValidSubmit=@SendMessage><DataAnnotationsValidator/><div class="row"><textarea class="form-control" rows=20 readonly>@ChatWindowText</textarea></div><div class="row mt-1"><div class="col-3"><InputText class="form-control" placeholder="Name" @bind-Value=Name maxlength=20/><ValidationMessage For=@( () => Name )/></div><div class="col-9"><div class="input-group"><InputText class="form-control" placeholder="..." @bind-Value=Text maxlength=100 /><div class="input-group-append"><button class="btn btn-primary" type=submit>Send</button></div></div><ValidationMessage For=@( () => Text )/></div></div>
</EditForm>
  • 第 5 行

    当用户在 InputText 上按 enter 并且输入验证通过时调用 SendMessage

  • 第 7-9 行

    添加 HTML <textarea> 并将其绑定到 WindowChatText

WebAssembly 应用程序中的 Singleton 依赖项

如果 Blazor 应用程序是 Blazor 服务器端应用程序,则上述应用程序将仅允许用户相互聊天。

这是因为 Singleton 依赖项是每个应用程序进程共享的。Blazor 服务器端应用程序实际上在服务器上运行,因此单例实例在同一服务器应用程序进程中运行的多个用户之间共享。

在 WebAssembly 应用程序中运行时,每个浏览器选项卡都是它自己独立的应用程序进程,因此如果用户在浏览器中运行单独的进程(WebAssembly 托管应用程序),用户将无法相互聊天,因为他们不共享任何公共状态。

使用多个服务器时也是如此。一旦我们的聊天服务流行到足以保证一个或多个额外的服务器,就不再有所有用户的全局共享状态,只有每个服务器的共享状态。

一旦我们需要扩展我们的服务器,或者我们希望将我们的聊天客户端实现为 WebAssembly 应用程序以从我们的服务器中移除一些工作负载,我们就需要设置一种更强大的共享状态的方法。这不在本节的范围内,因为本节的目的只是演示注册为单例的依赖项如何在单个应用程序进程中共享。

读者任务

浏览器不可能有足够的垂直空间同时显示 50 条聊天消息,因此用户必须手动滚动聊天区域才能看到最新消息。

为了改善用户体验,我们的组件应该在每次添加新文本时真正将 <textarea> 滚动条滚动到底部。如果您不想自己解决这个问题,那么只需看看本节随附的项目,工作已经为您完成。如果您确实想解决它,这里有一些线索。

  1. 您将编写一些将控件作为参数并设置 control.scrollTop = control.scrollHeight 的 JavaScript。

  2. 每次我们的组件呈现后,您都需要调用此 JavaScript[2]

  3. 您需要将 <textarea> 的 ElementReference[3] 传递给 JavaScript。

参考资料

[1]

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

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

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

相关文章

nginx log_format详解

nginx服务器日志相关指令主要有两条&#xff0c;一条是log_format&#xff0c;用来设置日志格式&#xff0c;另外一条是access_log&#xff0c;用来指定日志文件的存放路径、格式和缓存大小&#xff0c;一般在nginx的配置文件中日记配置(/usr/local/nginx/conf/nginx.conf)。 n…

Django-admin管理工具

admin组件使用 Django 提供了基于 web 的管理工具。 Django 自动管理工具是 django.contrib 的一部分。你可以在项目的 settings.py 中的 INSTALLED_APPS 看到它&#xff1a; # Application definitionINSTALLED_APPS [django.contrib.admin,django.contrib.auth,django.contr…

mybatis报错invalid types () or values ()解决方法

报错信息&#xff1a;org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: Error instantiating class com.org.tfkj.bean.AppFile.T_contactList with invalid types () or values (). Cause: java.lang.NoSu…

[转]mysql的sql优化工具

原标题&#xff1a;DBA的五款最佳SQL查询优化工具&#xff0c;收藏了 一般来说&#xff0c;SQL查询优化器分析给定查询的许多选项&#xff0c;预估每个选项的成本&#xff0c;最后选择成本最低的选项。如果查询优化器选择了错误的计划&#xff0c;则性能差异可能从几毫秒到几分…

log4j 新建日志 重启_Log4j 动态修改日志级别,不用重启服务器

生产环境正常情况下 只需要打印Info级别的日志就够了&#xff0c;碰到有问题的时候&#xff0c;或者在开发过程中 需要把debug 级别的日志打印出来。把下面的代码放到一个 Controller 里&#xff0c;通过页面一个的按钮调用即可。//修改全局LogManager.getLoggerRepository().s…

.NET 7 中的 HttpResult 接口

.NET 7 中的 HttpResult 接口Intro在前面的文章中&#xff0c;我们提到了 .NET 7 引入了 Endpoint Filter 来支持 Endpoint 的过滤器&#xff0c;有了这个接口就想着把之前的统一 API response 的 filter 改造一下支持 endpoint filter&#xff0c;然而这个一直等到了 .NET 7 P…

5、Hive的自定义UDF函数

2019独角兽企业重金招聘Python工程师标准>>> 1、pom.xml引入依赖及打包 <dependencies><dependency><groupId>org.apache.hive</groupId><artifactId>hive-exec</artifactId><version>1.1.0</version></depende…

多线程-单生产单消费模型

2019独角兽企业重金招聘Python工程师标准>>> 创建资源对象&#xff0c;提供保存和取出方法&#xff08;使用synchronized代码块实现&#xff09; /*** Created by shaoqinghua on 2018/5/3.* 定义一个负责保存和取出的资源类*/ public class Resource {/*** 定义成员…

nj08---process、console

概念&#xff1a;所有属性都可以在程序的任何地方访问&#xff0c;即全局变量。在JavaScript中&#xff0c;通常window是全局对象&#xff0c;而Node.js的全局对象是global&#xff0c;所有全局变量都是global对象的属性&#xff0c;如&#xff1a;console、process等。一、全局…

github1s 油猴插件

github1s 是一个非常有趣的项目&#xff0c;它可以让你在 1 秒内&#xff08;俗称 1s&#xff09;通过在线版本的 VS Code 来打开 GitHub 上的代码&#xff0c;只需要在对应项目的 URL 后面加上 1s 即可。 这是一个很有创意、很赞的项目&#xff0c;只需要在对应的 GitHub 项目…

WPF-02 布局

WPF中布局控件继承Panel&#xff0c;可以实现非常复杂的布局。我们介绍一下常用的布局控件1. Grid 布局控件Grid是网格布局控件&#xff0c;在WPF开发中我们用的最多的一个布局控件&#xff0c;可以自定义行和列&#xff0c;分别设置Height和Witdh值&#xff0c;一般推荐通过比…

275mib为什么不能联网_大众迈腾(275)MIB之导航使用教程

大众可以说近几年的发展非常快&#xff0c;仅车载收音机都更换了好几代了。从最初的单纯收音机到后来的6碟CD机RCD510&#xff0c;最初国内上市的导航RNS510&#xff0c;还有后来自带蓝牙的RNS315&#xff0c;再到PQ平台187A&#xff0c;当初抄的也火的几乎每天都看到187…

微信多开工具 可以同时在电脑上打开多个微信 免费开源

本程序用C#代码编写&#xff0c;运行环境FrameWork5.0以上。 主要代码&#xff1a; using Microsoft.Win32; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Drawing; using Sys…

Linux 第十周学习笔记(2)smtp服务的部署

SMTP服务的部署介绍&#xff1a;SMTP(Simple Mail Transfer Protocol)简单邮件传输协议是一种提供可靠且有效电子邮件传输的协议。 SMTP 是建模在 FTP 文件传输服务上的一种邮件服务&#xff0c;主要用于传输系统之间的邮件信息并提供来信有关的通知。一&#xff0e;环境的部署…

关于CTeX的几个大坑

https://blog.csdn.net/zjutczj/article/details/53463478 最近一直忙着写小论文&#xff0c;毕业设计中期答辩&#xff0c;没有更新博客&#xff0c;忙过这一阵我会把这段时间学习机器学习的一些心得分享。今天分享几个刚刚使用CTeX遇到的大坑&#xff0c;希望能挽救一些还没有…

西安电子地图下载 来自谷歌电子地图库 地图展示15、17、19级

西安电子地图下载 来自谷歌电子地图库 地图展示15、17、19级 一般来说商业用图几乎相同就是17级左右&#xff0c;提供的下载全然满足大多数人浏览使用&#xff0c;假设用于旅行外出当然级数越高越好&#xff0c;假设是驴行&#xff0c;那就更有必要下载完整的地图以作不时之需。…

0x0000050蓝屏srvsys_蓝屏代码0x00000050的原因及解决方法

经常使用电脑的朋友都知道&#xff0c; 电脑开机 出现蓝屏现象很普遍&#xff0c;相信大家都有遇到过吧?让我们经常的手足无措&#xff0c;不知道接下来应该怎么去操作!那么现在小编汇总起来告诉给大家出现蓝屏代码0x00000050怎么解决的具体内容&#xff0c;先对电脑进行局部的…

​Magicodes.Pay已支持Volo Abp

简介Magicodes.Pay希望打造一个统一支付库&#xff0c;相关库均使用.NET标准库编写&#xff0c;支持.NET Framework以及.NET Core。目前已提供Abp及Abp VNext模块的封装&#xff0c;支持开箱即用。主要功能目前已提供Abp&#xff08;含Abp VNext&#xff09;模块的封装&#xf…

js继承

js继承 一、总结 1、js继承&#xff1a;和c&#xff0c;java不一样&#xff0c;是通过对象冒充&#xff0c; 原型链&#xff0c;混合模式来实现的 2、基础打牢&#xff1a;基础打牢&#xff0c;后面就很轻松 二、js继承 继承继承的相关概念 这里的继承和我们现实生活的中儿子继…

Linux console on LCD

有时候需要将开机启动的信息输出到LCD上&#xff0c;并且在终端上进行调试。本文记录更改的方法。 参考链接 http://blog.csdn.net/chenbang110/article/details/7870072 https://e2e.ti.com/support/embedded/linux/f/354/t/324198 https://blackfin.uclinux.org/doku.php?id…