Calling SignalR hub from Web API controller in ASP.NET Core


 If you are a beginner in SignalR and working on a project that consists of client and server components, and you want to consume the SignalR hub inside a controller, then you've come to the right place. 

In this post, we will discuss how you can call a SignalR Core Hub method from a controller in ASP.NET Core.

In my example, the client is a JavaScript file that defines the hub connection and receives or sends messages.

In my server, which is an ASP.NET Core Web API, I simply want to send or receive messages from clients and save those messages on the server.

So here is My SignalR hub :


 [Authorize]
    public class ChatHub : Hub
    {

        public override Task OnDisconnectedAsync(Exception exception)
        {
            Debug.WriteLine("Client disconnected: " + Context.ConnectionId);
            return base.OnDisconnectedAsync(exception);

        }
        public override Task OnConnectedAsync()
        {
            Debug.WriteLine("Client connected: " + Context.ConnectionId);
            return base.OnConnectedAsync();

        }
        //Create Group for each user to chat sepeartely
        public void SetUserChatGroup(string userId)
        {
            var id = Context.ConnectionId;
            Debug.WriteLine($"Client {id} added to group " + userId);
            Groups.AddToGroupAsync(Context.ConnectionId, userId.ToLower());
        }
        //Send message to user Group
        public async Task SendMessageToOtherUser(ReceiveMessageDTO receiveMessageDTO)
        {
            if (Clients != null)
            {
                await Clients.Group(receiveMessageDTO.ReceiverId.ToString().ToLower()).SendAsync("ReceiveMessage", receiveMessageDTO);
            }
        }
        //Send message to user Group
        public async Task SendNotification(string userId)
        {
            if (Clients != null)
            {
                await Clients.Group(userId.ToLower()).SendAsync("Notification", "New notification recived!");
            }

        }
    }
  • We have decorated our Hub with [Authorize] attribute restricts access to the ChatHub to authenticated users only.
  • SetUserChatGroup(string userId) this method adds the current connection to a specified chat group identified by userId. This allows clients to communicate within specific chat groups.
  • SendMessageToOtherUser(ReceiveMessageDTO receiveMessageDTO) this sends a message to a specific user identified by receiveMessageDTO.ReceiverId. It uses SignalR's group feature to send the message to the appropriate group.
  • SendNotification(string userId) this function used to sends a notification message to a specific user identified by userId. It also uses SignalR's group feature to send the notification to the appropriate group.
Above SignalR Hub used to handle user connections, disconnections, group management, and message sending in a SignalR hub for a chat application.

Method 1: Call SignalR Core Hub method from Controller By Injecting SignalR  Hub

Now, let's say I want to call the `SendMessageToOtherUser(ReceiveMessageDTO receiveMessageDTO)` method of the SignalR hub inside my controller. How can we do that? Let's take an example.


[Route("api/[controller]/[action]")]
    [ApiController]
    [Authorize]
    public class ChatController : BaseController
    {
        DatabaseContext _context;
        ChatHub _chatHub;
        
        
        public ChatController(DatabaseContext context, ChatHub chatHub)
        {
            _context = context;
            _chatHub = chatHub;
            _logger = logger;
            _dbThreadLogic = dbThreadLogic;
        }
        //api/Chat/SendMessage
        [HttpPost]
        public async Task<ActionResult<ResponseDTO<ReceiveMessageDTO>?>> SendMessage(SendMessageDTO sendMessage)
        {
            ResponseDTO<ReceiveMessageDTO> response = new ResponseDTO<ReceiveMessageDTO>();
            response.StausCode = ResponseCode.Success;
            try
            {
                var user = _context.Users.FirstOrDefault(a => a.Id == userId);
                UserChatHistory chatHistory = new UserChatHistory();
                chatHistory.Message = sendMessage.Message;
                chatHistory.SenderUserId = userId;
                chatHistory.ReceiverUserId = sendMessage.ReceiverId;
                chatHistory.CreatedAt = DateTime.UtcNow;

                var userRecentlyReceived = await _context.UserRecentlyReceivedMessage.Where(a => (a.ReceiverUserId == sendMessage.ReceiverId
                       && a.SenderUserId == userId) || (a.ReceiverUserId == userId
                       && a.SenderUserId == sendMessage.ReceiverId)).FirstOrDefaultAsync();
                if (userRecentlyReceived == null)
                {
                    userRecentlyReceived = new UserRecentlyReceivedMessage();
                    userRecentlyReceived.SenderUserId = userId;
                    userRecentlyReceived.ReceiverUserId = sendMessage.ReceiverId;
                    userRecentlyReceived.Message = sendMessage.Message;
                    userRecentlyReceived.LastUpdated = DateTime.UtcNow;
                    await _context.UserRecentlyReceivedMessage.AddAsync(userRecentlyReceived);
                }
                else
                {
                    userRecentlyReceived.Message = sendMessage.Message;
                    userRecentlyReceived.LastUpdated = DateTime.UtcNow;
                }

                await _context.UserChatHistory.AddAsync(chatHistory);
                await _context.SaveChangesAsync();
                var message = new ReceiveMessageDTO
                {
                    ReceiverId = sendMessage.ReceiverId,
                    SenderName = user.FullName,
                    SenderId = userId,
                    Message = sendMessage.Message,
                };
                await _chatHub.SendMessageToOtherUser(message);
            }
            catch (Exception ex)
            {
                return StatusCode((int)HttpStatusCode.InternalServerError, ex.Message);
            }
            return Ok(response);
        }
    }
    
public class SendMessageDTO
    {
        public required Guid ReceiverId { get; set; }
        public required string Message { get; set; }
    }    
    
 public class ReceiveMessageDTO
    {
        public  Guid SenderId { get; set; }
        public string SenderName { get; set; }
        public  Guid ReceiverId { get; set; }
        public  string Message { get; set; }
    }    
We have a ChatController that serves as our API controller for handling chat-related operations. It's annotated [Authorize] to ensure that only authenticated users can access its actions.

In the constructor, we inject the DatabaseContext, ChatHub dependencies needed for the controller's functionality.

ActionMethod SendMessage, which is responsible for sending a message to another user. It takes a SendMessageDTO object containing the receiver's ID and the message content. 
Inside this action:

  • We try to fetch the user information from the database based on the sender's ID then creating a new UserChatHistory object to store the message details and save it to the database.
  • We also update the UserRecentlyReceivedMessage entry to track the most recent message exchanged between the sender and receiver.
  • After saving the changes to the database, we create a ReceiveMessageDTO object containing the message details and then we call the SendMessageToOtherUser method of the injected ChatHub instance to send the message to the recipient.

Method 2: Call SignalR Core Hub method from Controller Using Inject the IHubContext<THub>

To call a SignalR Core Hub method from a controller in ASP.NET Core, we inject the IHubContext into the constructor of controller. Use the IHubContext to access the methods of SignalR hub and invoke them as needed.
[Route("api/[controller]")]
[ApiController]
public class ChatController : ControllerBase
{
    private readonly IHubContext<ChatHub> _hubContext;

    public ChatController(IHubContext<ChatHub> hubContext)
    {
        _hubContext = hubContext;
    }

    [HttpPost("send-message")]
    public async Task<IActionResult> SendMessage(string senderName,string message)
    {
        // Invoke the SendMessage method on the SignalR hub
        await _hubContext.Clients.All.SendAsync("ReceiveMessage",senderName, message);

        return Ok();
    }
}
Here we have a ChatController that serves as our API controller for chat-related operations.In the constructor, we inject IHubContext<ChatHub>, which allows us to access methods of the ChatHub SignalR hub.

The controller has a single action method named SendMessage, which is accessible via HTTP POST requests to the route /api/Chat/send-message. This api endpoint takes two parameters: senderName, representing the name of the message sender, and message, representing the content of the message.

Within the SendMessage method, we invoke the SendAsync method on _hubContext.Clients.All to send a message to all connected clients. We pass the method name "ReceiveMessage" and the senderName and message as arguments. ReceiveMessag will be invoked on client side when this message is received.