11

I cannot figure out how I can call into a SignalR hub from a WebAPI ApiController. I have put together a sample you can download here that simplifies the problem and demonstrates the issue.

  1. I created a new project from the ASP.NET MVC WebAPI template.
  2. I added a new SignalR Hub to the project called ChatHub.
  3. Added a HTML page that on load, connects to ChatHub, joins to a group and sends a message to that group. This works great.
  4. HTML page also has a button that when clicked will fire an ajax call to the ValuesController's post method. In the ValuesController's post method, I want to broadcast a message to all the connected clients of the group. I cannot get this to work.

I have a simple SignalR hub with just 2 methods.

[HubName("chat")]
public class ChatHub : Hub
{
    public void Join(string room)
    {
        // NOTE: this is not persisted - ....
        Groups.Add(Context.ConnectionId, room);
    }

    public void Send(string room, string message)
    {
        var msg = String.Format(
            "{0}: {1}", Context.ConnectionId, message);
        Clients.Group(room).newMessage(msg);
    }
}

I created a very simple HTML page that connects to the Chat hub when the DOM is ready as shown here.

<html>
<head>
    <title>Simple Chat</title>
    <script src="Scripts/jquery-1.8.2.js" type="text/javascript"></script>
    <script src="Scripts/jquery.signalR-1.0.0.js"></script>
    <script src="signalr/hubs"></script>
    <script type="text/javascript">
        var chat;

        //$(function () {
        //    connectToHubs();
        //});
        $(connectToHubs);
        function connectToHubs() {
            $.connection.hub.logging = true;

            chat = $.connection.chat;
            chat.client.newMessage = onNewMessage;

            $.connection.hub.start({ transport: 'longPolling' }).done(function () {
                chat.server.join("TestGroup").done(function () {
                    chat.server.send("TestGroup", "message from html");
                });
            });

            $('#controller').click(postProficiencyUserAction);



        }
        var postProficiencyUserAction = function () {
            //var token = $('[name=__RequestVerificationToken]').val();
            var headers = {};
            //headers["__RequestVerificationToken"] = token;
            //var userAction = { createdOn: "2013-05-21T00:00:00", userId: "12345678-1234-1234-1234-000000000001", actionId: "12345678-1234-1234-1234-000000000003" };
            $.ajax({
                type: 'POST',
                url: 'http://localhost:58755/api/values',
                cache: false,
                headers: headers,
                contentType: 'application/json; charset=utf-8',
                data: 'test',
                dataType: "json",
                success: function () {

                },
                error: function () {

                }
            });
        };
        function onNewMessage(message) {
            // ... todo: validation !!!! :)
            $('#messages').append('<li>' + message + '</li>');
        };

    </script>
</head>
<body>
    <div>
        <h2>Chat</h2>
        <input type="button" id="controller" value="Controller Method" />
        <div>
            <h2>Message(s) Received</h2>
            <ul id="messages"></ul>
        </div>
    </div>
</body>
</html>

Nothing fancy. Whenever the connected hub receives a new message, a new item is added to the unordered list. There is a button that makes an Ajax call into the ValuesController post method.

public class ValuesController : ApiController
{
    // POST api/values
    public void Post([FromBody]string value)
    {
        var hubContext = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
        hubContext.Clients.Group("TestGroup").send("TestGroup", "Called from Controller");
    }

The Hub call does not work. An error is not thrown, but, the message is never received. Putting a breakpoint in the "Send" method of the Hub does not work either. I feel like I am doing it right. Anyone help? Again, source code can be found here

Samuel Caillerie
  • 8,259
  • 1
  • 27
  • 33
DapperDanh
  • 555
  • 2
  • 5
  • 17

2 Answers2

16

You are calling different methods on the client:

API controller

hubContext.Clients.Group("TestGroup").send("TestGroup", "Called from Controller");

Hub

Clients.Group(room).newMessage(msg);

The method you want to call is newMessage not send

chat.client.newMessage = onNewMessage;
davidfowl
  • 37,120
  • 7
  • 93
  • 103
  • David, thanks so much. So it appears on the server, I can call any method off of hubContext.Clients.Group("TestGroup") such as hubContext.Clients.Group("TestGroup").helloNasty and the client will automagically be able to respond to that same made up method. Crazy. I need to study up on C# dynamic capabilities :-) – DapperDanh May 22 '13 at 17:16
  • @dfowler: Is there any way to call the actual ChatHub.Send method from the ApiController? The above way of doing it seems to bypass ChatHub and just go straight to the Hub Context to do the equivelant of what ChatHub.Send does - but anything else I have in ChatHub.Send won't run\would need to be replicated in the ApiController? Maybe I'm just missing something here. – mutex Nov 07 '13 at 22:47
  • 1
    Nope. You can't call the hub method from the server. You'd need to move the logic to a shared class that can be called from both inside and outside the hub. – davidfowl Nov 08 '13 at 04:19
2

For ASP.NET Core you can now do the following:

[Route("some-route")]
public class MyController : ControllerBase
{
    private readonly IHubContext<MyHub> _hubContext;

    public MyController(IHubContext<MyHub> hubContext)
    {
        _hubContext = hubContext;
    }

    [HttpPost("message")]
    public async Task<ActionResult> SendMessage(string message)
    {
        await _hubContext.Clients.All.SendAsync("Message", message);
        return Ok();
    }
}
randomsolutions
  • 2,075
  • 1
  • 19
  • 22