SignalR 以 Angular 方式向单个用户发送直接消息

问题描述 投票:0回答:1

我正在尝试使用 SignalR(.NET 6)和 Angular ~13 向特定用户实现直接消息,到目前为止我得到了很多有关实现的信息,但没有一个起作用。

所以在客户端(Angular)我有发送消息的方法

export interface IChatMessage {
    id?: number;
    message: string;
    from?: string;
    to?: string;
    created?: Date | null;
} 

发送到集线器时,我仅发送

message
to
,并且
to
是该消息的接收者。 我的
chat.service.ts

export class ChatService {
    private hubConnection: HubConnection;
    private receivedMessageSubject = new Subject<IChatMessage>();
    public connectionId: string;
    receivedMessage$: Observable<IChatMessage> = this.receivedMessageSubject.asObservable();

    constructor(private apiService: ApiService) {
        this.hubConnection = new HubConnectionBuilder()
            .withUrl(environment.apiUrl + API_ROUTES.CHAT, {accessTokenFactory: () => localStorage.getItem('token')})
            .build();
        this.hubConnection.start().then(() => console.log('connection started')).then(() => this.getConnectionId()).catch((err) => console.error(err));

        this.hubConnection.on('ReceiveMessage', (message) => {
            console.log('Received message from server:', message);
            this.receivedMessageSubject.next(message);
        });
    }

    private getConnectionId = () => {
        this.hubConnection.invoke('getconnectionid')
            .then((data) => {
                this.connectionId = data;
            });
    }

    getMessages(id: string) {
        return this.apiService.post(environment.apiUrl + API_ROUTES.CHAT_MESSAGES, {userId: id});
    }

    sendMessage(message: IChatMessage): void {
        this.hubConnection.invoke('SendMessage', message, this.connectionId)
            .then((response) => console.log('Server response:', response))
            .catch(error => console.error('Error sending message through SignalR:', error));
    }
}

在服务器端我有集线器

ChatHub

        public async Task SendMessage(ChatMessage message, string connectionId)
        {
            try
            {
                var headers = Context.GetHttpContext().Request.Headers;
                var user = Context.GetHttpContext().User.Identity;
                Console.WriteLine(JsonSerializer.Serialize(Context.ConnectionId));
                await Clients.User(connectionId).SendAsync("ReceiveMessage", message);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error in SendMessage: {ex.Message}");
                throw;
            }
        }

我得到了

connectionId
,但每个用户的情况都不同,无法映射它以连接发送者与接收者。 在
Program.cs
我有

using IUserIdProvider = Microsoft.AspNetCore.SignalR.IUserIdProvider;
...
builder.Services.AddSingleton<IUserIdProvider, CustomUserIdProvider>();
builder.Services.AddSignalR();

var app = builder.Build();
...
app.UseAuthentication();
...
app.UseEndpoints(endpoints =>
{
    endpoints.MapHub<ChatHub>("/api/chat");
    endpoints.MapControllers();
});

我也有

CustomUserIdProvider : IUserIdProvider

    public class CustomUserIdProvider : IUserIdProvider
    {
        public string GetUserId(HubConnectionContext connection)
        {
            var uniqueId = connection.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
            return uniqueId;
        }

    }

但是

uniqueId
中的
CustomUserIdProvider
是空的。对于所有控制器,我都有
var user = _userClaims.GetUserId();
,它从
uniqueId
实体的令牌中获取
User
,该实体是
string
并且是唯一的。它有效。如果我这样做
await Clients.All.SendAsync("ReceiveMessage", message);
它会向所有用户发送消息,“connectionId”可用,但仅适用于发送者,因此接收者不会收到此消息。我不知道如何在 .NET 6 + Angular 13 中实现这一点,因为就像我说的,我在过去 5 天里基本上尝试了互联网上可用的所有内容,但仍然没有运气。有谁知道如何实现这一点,以便可以使用接收者的 uniqueId identyfier 以一对一的方式发送它?

.net angular signalr-hub asp.net-core-signalr
1个回答
0
投票

当用户(

browser
other devices
)连接到Hub端点时,生成的
connectionId
是由
signalr
本身随机生成的,如果应用程序停止/崩溃,
connectionIds
将消失,我们可以别再用了。

一个用户只有一个uniqueId,但是他可以打开很多网页,和同事使用多个移动设备,所以我们需要自己维护他们之间的关系。想要了解更多详情,你可以在这个帖子里查看我的回答

这是我用于测试的示例。

namespace SignalRMiddleawre.Hubs
{
    /// <summary>
    /// </summary>
    [Authorize]
    public partial class MainHub : Hub
    {
        #region Connection
        /// <summary>
        /// Manage Connected Users
        /// </summary>
        private static ConcurrentDictionary<string?, List<string>>? ConnectedUsers = new ConcurrentDictionary<string?, List<string>>();
        /// <summary>
        /// OnConnect Event
        /// </summary>
        /// <param name="userid"></param>
        /// <returns></returns>
        ///
        public override async Task OnConnectedAsync()
        {
            // Get HttpContext In asp.net core signalr
            //IHttpContextFeature hcf = (IHttpContextFeature)this.Context.Features[typeof(IHttpContextFeature)];
            //HttpContext hc = hcf.HttpContext;
            //string uid = hc.Request.Path.Value.Split(new string[] { "=", "" }, StringSplitOptions.RemoveEmptyEntries)[1].ToString();

            string? userid = Context.User?.Identity?.Name;
            if (userid == null || userid.Equals(string.Empty))
            {
                Trace.TraceInformation("user not loged in, can't connect signalr service");
                return;
            }
            Trace.TraceInformation(userid + "connected");
            // save connection
            List<string>? existUserConnectionIds;
            ConnectedUsers.TryGetValue(userid, out existUserConnectionIds);
            if (existUserConnectionIds == null)
            {
                existUserConnectionIds = new List<string>();
            }
            existUserConnectionIds.Add(Context.ConnectionId);
            ConnectedUsers.TryAdd(userid, existUserConnectionIds);

            await Clients.All.SendAsync("ServerInfo", userid, userid + " connected, connectionId = " + Context.ConnectionId);
            await base.OnConnectedAsync();
        }

        /// <summary>
        /// OnDisconnected event
        /// </summary>
        /// <param name="userid"></param>
        /// <returns></returns>
        public override async Task OnDisconnectedAsync(Exception? exception)
        {
            string? userid = Context.User?.Identity?.Name;
            // save connection
            List<string>? existUserConnectionIds;
            ConnectedUsers.TryGetValue(userid, out existUserConnectionIds);

            existUserConnectionIds.Remove(Context.ConnectionId);

            if (existUserConnectionIds.Count == 0)
            {
                List<string> garbage;
                ConnectedUsers.TryRemove(userid, out garbage);
            }

            await base.OnDisconnectedAsync(exception);
        }
        #endregion

        #region Message
        /// <summary>
        /// Send msg to all user
        /// </summary>
        /// <param name="userid"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        public async Task SendMessage(string msgType, string message)
        {
            await Clients.All.SendAsync("ReceiveMessage", msgType, message);
        }

        /// <summary>
        /// Send msg to user by userid
        /// </summary>
        /// <param name="connectionId"></param>
        /// <param name="message">message format : type-message </param>
        /// <returns></returns>
        public async Task SendToSingleUser(string userid, string message)
        {
            List<string>? existUserConnectionIds;
            // find all the connectionids by userid
            ConnectedUsers.TryGetValue(userid, out existUserConnectionIds);
            if (existUserConnectionIds == null)
            {
                existUserConnectionIds = new List<string>();
            }
            existUserConnectionIds.Add(Context.ConnectionId);
            ConnectedUsers.TryAdd(userid, existUserConnectionIds);
            await Clients.Clients(existUserConnectionIds).SendAsync("ReceiveMessage", message);
        }
        #endregion

    }
}
© www.soinside.com 2019 - 2024. All rights reserved.