基于ABP框架的SignalR,使用Winform程序进行功能测试

简介: 基于ABP框架的SignalR,使用Winform程序进行功能测试

在ABP框架里面,默认会带入SignalR消息处理技术,它同时也是ABP框架里面实时消息处理、事件/通知处理的一个实现方式,SignalR消息处理本身就是一个实时很好的处理方案,我在之前在我的Winform框架中的相关随笔也有介绍过SIgnalR的一些内容《基于SignalR的服务端和客户端通讯处理》,本篇基于.net Core的ABP框架介绍SignalR的后端处理,以及基于Winform程序进行一些功能测试,以求我们对SignalR的技术应用有一些了解。

SignalR是一个.NET Core/.NET Framework的开源实时框架. SignalR的可使用Web Socket, Server Sent Events 和 Long Polling作为底层传输方式。

SignalR基于这三种技术构建, 抽象于它们之上, 它让你更好的关注业务问题而不是底层传输技术问题。

SignalR将整个信息的交换封装起来,客户端和服务器都是使用JSON来沟通的,在服务端声明的所有Hub信息,都会生成JavaScript输出到客户端,.NET则依赖Proxy来生成代理对象,而Proxy的内部则是将JSON转换成对象。

Hub类里面, 我们就可以调用所有客户端上的方法了. 同样客户端也可以调用Hub类里的方法.

SignalR可以将参数序列化和反序列化. 这些参数被序列化的格式叫做Hub 协议, 所以Hub协议就是一种用来序列化和反序列化的格式.

Hub协议的默认协议是JSON, 还支持另外一个协议是MessagePack。MessagePack是二进制格式的, 它比JSON更紧凑, 而且处理起来更简单快速, 因为它是二进制的.

此外, SignalR也可以扩展使用其它协议。

SignalR 可以与ASP.NET Core authentication一起使用,以将用户与每个连接相关联。 在中心中,可以从HubConnectionContext属性访问身份验证数据。

1、ABP框架中后端对SignalR的处理

如果需要在.net core使用SignalR,我们首先需要引入aspnetcore的SiganlR程序集包

另外由于我们需要使用ABP基础的SignalR的相关类,因此需要引入ABP的SignalR模块,如下所示。

[DependsOn(
       typeof(WebCoreModule),
       typeof(AbpAspNetCoreSignalRModule))]
    public class WebHostModule: AbpModule
    {
        private readonly IWebHostEnvironment _env;
        private readonly IConfigurationRoot _appConfiguration;
        public WebHostModule(IWebHostEnvironment env)
        {
            _env = env;
            _appConfiguration = env.GetAppConfiguration();
        }

然后在Web.Host中发布SiganlR的服务端名称,如下所示。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
   ........................
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHub<AbpCommonHub>("/signalr");
        endpoints.MapHub<ChatHub>("/signalr-chat");
        endpoints.MapControllerRoute("defaultWithArea", "{area}/{controller=Home}/{action=Index}/{id?}");
        endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
    });

注册 SignalR 和 ASP.NET Core 身份验证中间件的顺序。 UseSignalR 之前始终调用 UseAuthentication,以便 SignalR 在 HttpContext上有用户。

在基于浏览器的应用程序中,cookie 身份验证允许现有用户凭据自动流向 SignalR 连接。 使用浏览器客户端时,无需额外配置。 如果用户已登录到你的应用,则 SignalR 连接将自动继承此身份验证。

客户端可以提供访问令牌,而不是使用 cookie。 服务器验证令牌并使用它来标识用户。 仅在建立连接时才执行此验证。 连接开启后,服务器不会通过自动重新验证来检查令牌是否撤销。

 

ABP框架本身提供了可用的基类OnlineClientHubBase和AbpHubBase,内置了日志、会话、配置、本地化等组件,都继承自基类Microsoft.AspNetCore.SignalR.Hub。

 

Abp的AbpCommonHub提供了用户链接到服务和断开链接时,ConnectionId和UserId的维护,可以在IOnlineClientManger中进行访问,IOnlineClientManger提供如下方法:

bool IsOnline()

而ChatHub则是我们自定义的SignalR聊天处理类,它同样继承于OnlineClientHubBase,并整合了其他一些对象接口及进行消息的处理。

 

例如,我们这里SendMessage发送SIgnalR消息的逻辑如下所示。

/// <summary>
        /// 发送SignalR消息
        /// </summary>
        /// <param name="input">发送的消息体</param>
        /// <returns></returns>
        public async Task<string> SendMessage(SendChatMessageInput input)
        {
            var sender = Context.ToUserIdentifier();
            var receiver = new UserIdentifier(input.TenantId, input.UserId);
            try
            {
                using (ChatAbpSession.Use(Context.GetTenantId(), Context.GetUserId()))
                {
                    await _chatMessageManager.SendMessageAsync(sender, receiver, input.Message, input.TenancyName, input.UserName, input.ProfilePictureId);
                    return string.Empty;
                }
            }
            catch (UserFriendlyException ex)
            {
                Logger.Warn("Could not send chat message to user: " + receiver);
                Logger.Warn(ex.ToString(), ex);
                return ex.Message;
            }
            catch (Exception ex)
            {
                Logger.Warn("Could not send chat message to user: " + receiver);
                Logger.Warn(ex.ToString(), ex);
                return _localizationManager.GetSource("AbpWeb").GetString("InternalServerError");
            }
        }

而消息对象实体,如下所示

/// <summary>
    /// 发送的SignalR消息
    /// </summary>
    public class SendChatMessageInput
    {
        /// <summary>
        /// 租户ID
        /// </summary>
        public int? TenantId { get; set; }
        /// <summary>
        /// 用户ID
        /// </summary>
        public long UserId { get; set; }
        /// <summary>
        /// 用户名
        /// </summary>
        public string UserName { get; set; }
        /// <summary>
        /// 租户名
        /// </summary>
        public string TenancyName { get; set; }
        /// <summary>
        /// 个人图片ID
        /// </summary>
        public Guid? ProfilePictureId { get; set; }
        /// <summary>
        /// 发送的消息内容
        /// </summary>
        public string Message { get; set; }
    }

为了和客户端进行消息的交互,我们需要存储用户发送的SignalR的消息到数据库里面,并需要知道用户的好友列表,以及获取未读消息,消息的已读操作等功能,那么我们还需要在应用层发布一个ChatAppService的应用服务接口来进行交互。

[AbpAuthorize]
    public class ChatAppService : MyServiceBase, IChatAppService
    {
        private readonly IRepository<ChatMessage, long> _chatMessageRepository;
        private readonly IUserFriendsCache _userFriendsCache;
        private readonly IOnlineClientManager<ChatChannel> _onlineClientManager;
        private readonly IChatCommunicator _chatCommunicator;

客户端通过和 signalr-chat 和ChatAppService进行联合处理,前者是处理SignalR消息发送操作,后者则是应用层面的数据处理。

 

2、Winform程序对SignalR进行功能测试

前面说过,SignalR消息应用比较多,它主要用来处理实时的消息通知、事件处理等操作,我们这里用来介绍进行聊天回话的一个操作。

客户端使用SignalR需要引入程序集包Microsoft.AspNetCore.SignalR.Client。

首先我们建立一个小的Winform程序,设计一个大概的界面功能,如下所示。

这个主要就是先通过ABP登录认证后,传递身份,并获取用户好友列表吧,连接到服务端的SiganlR接口后,进行消息的接收和发送等操作。

首先是用户身份认证部分,先传递用户名密码,登陆认证成功后获取对应的令牌,存储在缓存中使用。

private async void btnGetToken_Click(object sender, EventArgs e)
        {
            if(this.txtUserName.Text.Length == 0)
            {
                MessageDxUtil.ShowTips("用户名不能为空");return;
            }
            else if (this.txtPassword.Text.Length == 0)
            {
                MessageDxUtil.ShowTips("用户密码不能为空"); return;
            }
            var data = new AuthenticateModel()
            {
                UserNameOrEmailAddress = this.txtUserName.Text,
                Password = this.txtPassword.Text
            }.ToJson();
            helper.ContentType = "application/json";//指定通讯的JSON方式
            helper.MaxTry = 2;
            var content = helper.GetHtml(TokenUrl, data, true);
            Console.WriteLine(content);
            var setting = new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() };
            var result = JsonConvert.DeserializeObject<AbpResponse<AuthenticateResultModel>>(content, setting);
            if (result != null && result.Success && !string.IsNullOrWhiteSpace(result.Result.AccessToken))
            {
                //获取当前用户
                Cache.Instance["AccessToken"] = result.Result.AccessToken;//设置缓存,方便ApiCaller调用设置Header
                currentUser = await UserApiCaller.Instance.GetAsync(new EntityDto<long>(result.Result.UserId));
                Console.WriteLine(result.Result.ToJson());
                Cache.Instance["token"] =  result.Result; //设置缓存后,APICaller不用手工指定RequestHeaders的令牌信息
                EnableConnectState(false);
            }
            this.Text = string.Format("获取Token{0}", (result != null && result.Success) ? "成功" : "失败");
            //获取用户身份的朋友列表
            GetUserFriends();
        }

其次是获取用户身份后,获得对应的好友列表加入到下拉列表中,如下代码所示。

private void GetUserFriends()
        {
            var result = ChatApiCaller.Instance.GetUserChatFriendsWithSettings();
            this.friendDict = new Dictionary<long, FriendDto>();
            foreach (var friend in result.Friends)
            {
                this.friendDict.Add(friend.FriendUserId, friend);
                this.txtFriends.Properties.Items.Add(new CListItem(friend.FriendUserName, friend.FriendUserId.ToString()));
            }
        }

然后就是SignalR消息通道的连接了,通过HubConnection连接上代码如下所示。

connection = new HubConnectionBuilder()
.WithUrl(ChatUrl, options =>
{
    options.AccessTokenProvider = () => Task.FromResult(token.AccessToken);
    options.UseDefaultCredentials = true;
})
.Build();

整块创建SignalR的连接处理如下所示。

private async Task StartConnection()
        {
            if (connection == null)
            {
                if (!Cache.Instance.ContainKey("token"))
                {
                    MessageDxUtil.ShowTips("没有登录,请先登录");
                    return;
                }
                var token = Cache.Instance["token"] as AuthenticateResultModel;
                if (token != null)
                {
                    connection = new HubConnectionBuilder()
                    .WithUrl(ChatUrl, options =>
                    {
                        options.AccessTokenProvider = () => Task.FromResult(token.AccessToken);
                        options.UseDefaultCredentials = true;
                    })
                    .Build();
                    //connection.HandshakeTimeout = new TimeSpan(8000);//握手过期时间
                    //收到消息的处理
                    connection.On<string>("MessageReceived", (str) =>
                    {
                        Console.WriteLine(str);
                        this.richTextBox.AppendText(str);
                        this.richTextBox.AppendText("\r\n");
                        this.richTextBox.ScrollToCaret();
                    });
                    await connection.StartAsync();
                    EnableConnectState(true);
                }
            }
           await  Task.CompletedTask;
        }

客户端传递身份进行SignalR连接,连接成功后,收到消息回显在客户端。

每次用户登录并连接后,显示未读的消息到客户即可。

this.messages = new List<ChatMessageDto>();//清空数据
var result = await ChatApiCaller.Instance.GetUserChatMessages(input);
if (result != null && result.Items.Count > 0)
{
    this.messages = result.Items.Concat(this.messages);
    await ChatApiCaller.Instance.MarkAllUnreadMessagesOfUserAsRead(new MarkAllUnreadMessagesOfUserAsReadInput() { TenantId = 1, UserId = currentUser.Id });
}
this.richTextBox.Clear();
foreach (var item in this.messages)
{
    var message = string.Format("User[{0}]:{1}  -{2}", item.TargetUserId, item.Message, item.CreationTime);
    this.richTextBox.AppendText(message);
    this.richTextBox.AppendText("\r\n");
}
this.richTextBox.ScrollToCaret();

而客户端需要发送消息给另外一个好友的时候,就需要按照消息体的对象进行属性设置,然后调用SignalR接口进行发送即可,也就是直接调用服务端的方法了。

//当前用户id为2,发送给id为8的 
var data = new SendChatMessageInput()
{
    Message = this.txtMessage.Text,
    UserId = friend.FriendUserId,
    TenantId = friend.FriendTenantId,
    UserName = friend.FriendUserName,
    TenancyName = friend.FriendTenancyName,
    ProfilePictureId = Guid.NewGuid()
};
try
{
    //调用服务chathub接口进行发送消息
    var result = await connection.InvokeAsync<string>("SendMessage", data);
    Console.WriteLine(result);
    if (!string.IsNullOrWhiteSpace(result))
    {
        MessageDxUtil.ShowError(result);
    }
    else
    {
        await GetUserChatMessages(); //刷新消息
        this.txtMessage.Text = ""; //清空输入
        this.Text = string.Format("消息发送成功:{0}", DateTime.Now.ToString());
    }
}
catch (Exception ex)
{
    MessageDxUtil.ShowTips(ex.Message);
}

最后我们看看程序的效果,如下所示。

 

消息已经被 序列化到ABP的系统表里面了,我们可以在表中查看到。

用户的好友列表在表AppFriendships中,发送的消息则存储在AppChatMessages中

我们在ABP开发框架的基础上,完善了Winform端的界面,以及Vue&Element的前端界面,并结合代码生成工具的快速辅助,使得利用ABP框架开发项目,更加方便和高效。

ABP框架代码生成

 

专注于代码生成工具、.Net/.NetCore 框架架构及软件开发,以及各种Vue.js的前端技术应用。著有Winform开发框架/混合式开发框架、微信开发框架、Bootstrap开发框架、ABP开发框架、SqlSugar开发框架等框架产品。
 转载请注明出处:撰写人:伍华聪  http://www.iqidi.com

相关文章
|
6天前
|
设计模式 前端开发 JavaScript
自动化测试框架设计原则与最佳实践####
本文深入探讨了构建高效、可维护的自动化测试框架的核心原则与策略,旨在为软件测试工程师提供一套系统性的方法指南。通过分析常见误区,结合行业案例,阐述了如何根据项目特性定制自动化策略,优化测试流程,提升测试覆盖率与执行效率。 ####
28 6
|
6天前
|
人工智能 前端开发 测试技术
探索软件测试中的自动化框架选择与优化策略####
本文深入剖析了当前主流的自动化测试框架,通过对比分析各自的优势、局限性及适用场景,为读者提供了一套系统性的选择与优化指南。文章首先概述了自动化测试的重要性及其在软件开发生命周期中的位置,接着逐一探讨了Selenium、Appium、Cypress等热门框架的特点,并通过实际案例展示了如何根据项目需求灵活选用与配置框架,以提升测试效率和质量。最后,文章还分享了若干最佳实践和未来趋势预测,旨在帮助测试工程师更好地应对复杂多变的测试环境。 ####
26 4
|
12天前
|
机器学习/深度学习 前端开发 测试技术
探索软件测试中的自动化测试框架选择与优化策略####
本文深入探讨了在当前软件开发生命周期中,自动化测试框架的选择对于提升测试效率、保障产品质量的重要性。通过分析市场上主流的自动化测试工具,如Selenium、Appium、Jest等,结合具体项目需求,提出了一套系统化的选型与优化策略。文章首先概述了自动化测试的基本原理及其在现代软件开发中的角色变迁,随后详细对比了各主流框架的功能特点、适用场景及优缺点,最后基于实际案例,阐述了如何根据项目特性量身定制自动化测试解决方案,并给出了持续集成/持续部署(CI/CD)环境下的最佳实践建议。 --- ####
|
26天前
|
测试技术 C# 数据库
C# 单元测试框架 NUnit 一分钟浅谈
【10月更文挑战第17天】单元测试是软件开发中重要的质量保证手段,NUnit 是一个广泛使用的 .NET 单元测试框架。本文从基础到进阶介绍了 NUnit 的使用方法,包括安装、基本用法、参数化测试、异步测试等,并探讨了常见问题和易错点,旨在帮助开发者有效利用单元测试提高代码质量和开发效率。
136 64
|
13天前
|
Java 测试技术 持续交付
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
本文重点讲解如何搭建App自动化测试框架的思路,而非完整源码。主要内容包括实现目的、框架设计、环境依赖和框架的主要组成部分。适用于初学者,旨在帮助其快速掌握App自动化测试的基本技能。文中详细介绍了从需求分析到技术栈选择,再到具体模块的封装与实现,包括登录、截图、日志、测试报告和邮件服务等。同时提供了运行效果的展示,便于理解和实践。
49 4
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
|
12天前
|
测试技术 API Android开发
探索软件测试中的自动化框架选择与实践####
本文深入探讨了软件测试领域内,面对众多自动化测试框架时,如何依据项目特性和团队需求做出明智选择,并分享了实践中的有效策略与技巧。不同于传统摘要的概述方式,本文将直接以一段实践指南的形式,简述在选择自动化测试框架时应考虑的核心要素及推荐路径,旨在为读者提供即时可用的参考。 ####
|
16天前
|
测试技术 Android开发 UED
探索软件测试中的自动化框架选择
【10月更文挑战第29天】 在软件开发的复杂过程中,测试环节扮演着至关重要的角色。本文将深入探讨自动化测试框架的选择,分析不同框架的特点和适用场景,旨在为软件开发团队提供决策支持。通过对比主流自动化测试工具的优势与局限,我们将揭示如何根据项目需求和团队技能来选择最合适的自动化测试解决方案。此外,文章还将讨论自动化测试实施过程中的关键考虑因素,包括成本效益分析、维护难度和扩展性等,确保读者能够全面理解自动化测试框架选择的重要性。
32 1
|
22天前
|
监控 安全 jenkins
探索软件测试的奥秘:自动化测试框架的搭建与实践
【10月更文挑战第24天】在软件开发的海洋里,测试是确保航行安全的灯塔。本文将带领读者揭开软件测试的神秘面纱,深入探讨如何从零开始搭建一个自动化测试框架,并配以代码示例。我们将一起航行在自动化测试的浪潮之上,体验从理论到实践的转变,最终达到提高测试效率和质量的彼岸。
|
25天前
|
Web App开发 敏捷开发 存储
自动化测试框架的设计与实现
【10月更文挑战第20天】在软件开发的快节奏时代,自动化测试成为确保产品质量和提升开发效率的关键工具。本文将介绍如何设计并实现一个高效的自动化测试框架,涵盖从需求分析到框架搭建、脚本编写直至维护优化的全过程。通过实例演示,我们将探索如何利用该框架简化测试流程,提高测试覆盖率和准确性。无论你是测试新手还是资深开发者,这篇文章都将为你提供宝贵的洞见和实用的技巧。
|
14天前
|
机器学习/深度学习 自然语言处理 物联网
探索自动化测试框架的演变与未来趋势
随着软件开发行业的蓬勃发展,软件测试作为保障软件质量的重要环节,其方法和工具也在不断进化。本文将深入探讨自动化测试框架从诞生至今的发展历程,分析当前主流框架的特点和应用场景,并预测未来的发展趋势,为软件开发团队选择合适的自动化测试解决方案提供参考。