Issue
I am trying to implement direct message to specific user using SignalR(.NET 6) and Angular ~13 and so far I got so many information regarding implementation but none of them work.
So in client side (Angular) I have method to send message
export interface IChatMessage {
id?: number;
message: string;
from?: string;
to?: string;
created?: Date | null;
}
where sending to hub I send only message
and to
and to
is a receiver of that message.
My chat.service.ts
is
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));
}
}
On server side I have hub
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;
}
}
and I get the connectionId
but it's different for each user and cannot map this to connect sender with receiver.
In Program.cs
I have
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();
});
and I also have CustomUserIdProvider : IUserIdProvider
public class CustomUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
var uniqueId = connection.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
return uniqueId;
}
}
but uniqueId
in CustomUserIdProvider
is empty. For all controllers I have var user = _userClaims.GetUserId();
which takes uniqueId
from token from User
entity which is string
and is unique. And it works. If I do await Clients.All.SendAsync("ReceiveMessage", message);
it send message to all users, 'connectionId` is available but only of the sender so receiver does not receive this message. And I have no clue how to implement this in .NET 6 + Angular 13 because like I said I have tried for the last 5 days basically everything that is available in the internet but still no luck. Does anyone have any idea how to implement this so it could be sent in 1-to-1 manner using receiver's uniqueId identyfier?
Solution
Finally solved this.
In Program.cs
in AddAuthentication
in AddJwtBearer
(because authentication is with JWT) I added
config.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/api/chat")))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
next added builder.Services.AddSingleton<IUserIdProvider, CustomUserIdProvider>();
and after Useauthorization
I added
var idProvider = new CustomUserIdProvider();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
I have also created CustomUserIdPrvider
public class CustomUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
var uniqueId = connection.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
return uniqueId;
}
}
and SendMessage in
ChatHub`
public async Task SendMessage(ChatMessage message)
{
var httpContext = Context.GetHttpContext();
var user = httpContext.User.Claims.Where(user => user.Type == ClaimTypes.NameIdentifier).FirstOrDefault()?.Value;
var token = httpContext.Request.Query["access_token"];
message.From = user;
message.Created = DateTime.Now.ToLocalTime();
await Clients.Users(message.To, user).SendAsync("ReceiveMessage", message);
}
so with
Clients.Users(message.To, user)
I am able to send message content to specific user with uniqueId (NOT! connectionId!)
Answered By - MichaĆ Sawicki
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.