ASP.NET Core MVC+SignalR一对一聊天应用消息无法即时显示的技术问题
一对一聊天应用消息无法即时显示问题排查与修复
问题描述
基于ASP.NET Core MVC、Identity和SignalR开发一对一聊天应用,发送消息后无法即时在聊天界面显示,仅能刷新浏览器(消息已存入数据库)后看到内容。
控制器代码
public class MessagingController : Controller { private readonly ApplicationDbContext _context; private readonly UserManager<AppDbContext> _userManager; private readonly IHubContext<MessageHub> _hubContext; public MessagingController(ApplicationDbContext context, UserManager<AppDbContext> userManager, IHubContext<MessageHub> hubContext) { _context = context; _userManager = userManager; _hubContext = hubContext; } public IActionResult Index() { var currentUserId = _userManager.GetUserId(User); var users = _userManager.Users.Where(u => u.Id != currentUserId).ToList(); var messages = _context.Messages .Where(m => m.SenderId == currentUserId || m.ReceiverId == currentUserId) .Include(m => m.Sender) .Include(m => m.Receiver) .OrderByDescending(m => m.Timestamp) .ToList(); var viewModel = new IndexViewModel { Users = users, Messages = messages, CurrentUserId = currentUserId // 传递当前用户ID到视图 }; return View(viewModel); } [HttpPost] public async Task<IActionResult> SendMessage(string receiverId, string content) { var senderId = _userManager.GetUserId(User); var senderName = _userManager.GetUserName(User); var message = new Message { SenderId = senderId, ReceiverId = receiverId, Content = content, Timestamp = DateTime.Now }; _context.Messages.Add(message); await _context.SaveChangesAsync(); // 通过SignalR发送消息给发送方和接收方 await _hubContext.Clients.User(senderId).SendAsync("ReceiveMessage", new { senderName, content, receiverId }); await _hubContext.Clients.User(receiverId).SendAsync("ReceiveMessage", new { senderName, content, receiverId }); return RedirectToAction(nameof(Index)); } public IActionResult LoadMessages(string receiverId) { var currentUserId = _userManager.GetUserId(User); var messages = _context.Messages .Where(m => (m.SenderId == currentUserId && m.ReceiverId == receiverId) || (m.SenderId == receiverId && m.ReceiverId == currentUserId)) .Include(m => m.Sender) .Include(m => m.Receiver) .OrderBy(m => m.Timestamp) .ToList(); //return PartialView("_MessageListPartial", messages); return PartialView("_ChatPartial", messages); } }
视图代码
@using System.Security.Claims <!-- Index.cshtml --> @model IndexViewModel <div style="display: flex;"> <!-- 左侧:用户列表 --> <div style="flex: 1; padding-right: 20px;"> <h1>用户列表</h1> <form asp-action="SendMessage" asp-controller="Messaging" method="post"> <label>选择用户:</label> @foreach (var user in Model.Users) { <label> <input type="radio" name="receiverId" value="@user.Id" /> @user.UserName </label> <br /> } <label>消息内容:</label> <input type="text" name="content" /> <button type="submit">发送消息</button> </form> </div> <!-- 右侧:聊天消息 --> <div style="flex: 2;"> <h1>聊天消息</h1> <ul id="messagesList"> @if (Model.Messages.Any()) { @foreach (var message in Model.Messages) { <li> @if (message.Sender != null) { <strong>@message.Sender.UserName:</strong> } else { <strong>未知用户:</strong> } @message.Content </li> } } else { <p>暂无消息</p> } </ul> </div> </div> <script src="~/lib/signalr/dist/browser/signalr.js"></script> <script> const connection = new signalR.HubConnectionBuilder() .withUrl("/messageHub") .build(); connection.start().then(function () { console.log("SignalR连接已建立"); }).catch(function (err) { return console.error(err.toString()); }); connection.on("ReceiveMessage", function (message) { console.log("收到消息:", message); // 获取当前用户ID const currentUserId = "@User.FindFirst(ClaimTypes.NameIdentifier)?.Value"; // 获取选中的接收方ID const receiverId = document.querySelector('input[name="receiverId"]:checked').value; // 处理自己发送的消息 if (message.senderId === currentUserId && message.receiverId === receiverId) { const messagesList = document.getElementById("messagesList"); const listItem = document.createElement("li"); listItem.innerHTML = `<strong>我:</strong> ${message.content}`; messagesList.appendChild(listItem); } // 处理对方发送的消息 if (message.receiverId === currentUserId && message.senderId === receiverId) { const messagesList = document.getElementById("messagesList"); const listItem = document.createElement("li"); listItem.innerHTML = `<strong>${message.senderName}:</strong> ${message.content}`; messagesList.appendChild(listItem); } }); </script>
问题原因及修复方案
1. 表单提交触发页面刷新,SignalR回调被覆盖
SendMessage方法执行后返回RedirectToAction(nameof(Index)),导致页面整体刷新,SignalR的消息回调还未更新UI就被重新渲染的页面覆盖。
修复:
改用AJAX异步提交表单,避免页面刷新:
<!-- 替换原表单 --> <form id="sendMessageForm"> <label>选择用户:</label> @foreach (var user in Model.Users) { <label> <input type="radio" name="receiverId" value="@user.Id" /> @user.UserName </label> <br /> } <label>消息内容:</label> <input type="text" name="content" id="contentInput" /> <button type="submit">发送消息</button> @Html.AntiForgeryToken() </form> <!-- 新增AJAX提交脚本 --> <script> document.getElementById('sendMessageForm').addEventListener('submit', async function(e) { e.preventDefault(); // 阻止默认表单提交 const receiverId = document.querySelector('input[name="receiverId"]:checked')?.value; const content = document.getElementById('contentInput').value; const token = document.querySelector('input[name="__RequestVerificationToken"]').value; if (!receiverId || !content) return; // 异步提交消息 await fetch('/Messaging/SendMessage', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'RequestVerificationToken': token }, body: `receiverId=${receiverId}&content=${encodeURIComponent(content)}` }); // 清空输入框 document.getElementById('contentInput').value = ''; }); </script>
同时修改控制器返回值:
[HttpPost] public async Task<IActionResult> SendMessage(string receiverId, string content) { // 原有消息保存和SignalR发送代码不变 return Ok(); // 返回成功状态,无需重定向 }
2. SignalR发送的消息缺少senderId字段
后端发送消息时未传递senderId,但前端回调依赖该字段判断消息归属,导致条件不成立,UI不更新。
修复:
后端补充senderId字段:
await _hubContext.Clients.User(senderId).SendAsync("ReceiveMessage", new { senderId, senderName, content, receiverId }); await _hubContext.Clients.User(receiverId).SendAsync("ReceiveMessage", new { senderId, senderName, content, receiverId });
3. 未处理未选中用户的异常情况
前端获取receiverId时,若未选中用户会报错,需增加判断:
connection.on("ReceiveMessage", function (message) { console.log("收到消息:", message); const currentUserId = "@User.FindFirst(ClaimTypes.NameIdentifier)?.Value"; const selectedRadio = document.querySelector('input[name="receiverId"]:checked'); if (!selectedRadio) return; // 未选中用户时跳过处理 const receiverId = selectedRadio.value; // 原有消息处理逻辑不变 });
4. UserManager泛型参数错误
原代码中UserManager<AppDbContext>是错误的,应该传入你的用户实体类型(比如AppUser),否则用户相关操作会异常:
// 修改为正确的用户实体类型 private readonly UserManager<AppUser> _userManager; // 构造函数同步修改 public MessagingController(ApplicationDbContext context, UserManager<AppUser> userManager, IHubContext<MessageHub> hubContext) { _context = context; _userManager = userManager; _hubContext = hubContext; }
5. 确认SignalR Hub注册
确保在Program.cs中正确注册SignalR Hub:
app.MapHub<MessageHub>("/messageHub");
内容的提问来源于stack exchange,提问作者Tara Raine
相关产品推荐
相关产品推荐

