You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 04:08:12