Using SignalR to notify current caller

独自空忆成欢 提交于 2019-12-08 10:26:24

问题


The short version

In my POST action routine, how can I send a notification to some JavaScript running in the client? It seems that SignalR should be an easy solution, but I don't know how to do a callback to the SignalR client that lives in JavaScript in the POST action's client page.

Specifically, it seems that I need a "connection ID" in order to ask SignalR to talk to a specific client, but I don't know how to get one of those either in my POST action or in the client JavaScript.

The whole (not so ugly) story

In my MVC app, my POST action may take a long time to complete. If the POST action decides that it will take a long time to complete, I want to notify some JavaScript on the page so that it can display a "please wait..." notification to the user.

This seems like something that SignalR should make really easy for me. Following the basic tutorial, I added a Hub and created a JS callback in my view:

Note that the Hub has no methods. My client only needs a read-only notification. It has no need to call any methods on the Hub to write message to the outside world.

public class MyHub: Hub
{
}

The view just has a form with a submit button and a hidden "Please wait" message that the SignalR routine can display. The view model has a string property that I can use to pass the SignalR "connection ID" to the POST controller (assuming I can figure out how to get it).

@model SignalRTest.Models.MyViewModel

@using (Html.BeginForm()) {
    @Html.HiddenFor(m => m.SignalrConnectionId)
    <button type="submit" class="btn btn-primary">Go to it!</button>
}

<div id="hidden-msg" hidden="hidden">
    <p>Please wait...</p>
</div>

@section scripts {
    <!-- Reference the SignalR library. -->
    <script src="~/Scripts/jquery.signalR-2.1.2.min.js"></script>

    <!-- Reference the autogenerated SignalR hub script. -->
    <script src="~/signalr/hubs"></script>

    <!-- SignalR script to update the page -->
    <script>
        $(function () {
            // Get a reference to the server "hub" class (camelCase)
            var hub = $.connection.interviewDoneHub;

            // Get our connection ID and store it in a hidden field
            // so that it is sent to the POST action
            // var connectionId = $.connection.hub.id; //This doesn't work!
            // $('#@Html.IdFor(m => m.SignalrConnectionId)').attr(connectionId, '');

            // Create a function that the hub can call
            hub.client.myCallback = function () {
                $('#hidden-msg').show();
            };

            // Start the connection.
            $.connection.hub.start().done(function () {
            });
        });
    </script>
}

Meanwhile back in my POST controller action, I call the JS callback:

[HttpPost]
public async Task<ActionResult> MyAction(MyViewModel model)
{
    // We've decided that this will take a while.  Tell the client about it...
    var context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
    context.Clients.All.myCallback();
    //context.Clients.Client(model.SignalrConnectionId).myCallback();

    await Task.Delay(2000);
    return RedirectToAction("NextPage");
}

Now, my problem: As a proof-of-concept test, I use this code to call the JS callback:

var context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
context.Clients.All.myCallback();

which works just dandy. But, obviously, I want to call the specific client associated with the POST action. C# Intellisense tells me that I should be able to call

context.Clients.Client("connectionId").myCallback();

but I can't figure out how to get the desired "connectionId" string. I don't think I'll be able to get the client ID in the POST controller because I don't have any sort of SignalR connection to the client at that point. I figured I'd let the client get its connection ID and give it to the POST controller in the view model, but I haven't found the magic JS code that fetches the connection ID out of the SignalR framework.

I found several SO articles that stated matter-of-factly:

connectionId = $.connection.hub.id;

but this returns undefined. I found this SignalR JS Client Wiki page, which says:

connection.id
Gets or sets the client id for the current connection

but that also returns undefined.

Which gets back to my original question. Maybe I'm not understanding something fundamental about what a connection ID is...


回答1:


Okay I believe I see what the issue is. You want to start the your connection in javascript

$.connection.hub.start()

After you do that, then your should be able to do connection.hub.id - if the connection is not started then there will be no connection id at all, which is why you are getting an "undefined" value because it has not been set until you start the connection.


Actually this could be a bastardization of things, not sure if it will work but should require minimal changes to your code. Each time a client connects to your hub you can add this to the connect method

Groups.Add(Context.ConnectionId, Context.ConnectionId); 
// name it the same as your connection id since that is the identified you are using

Then in your controller you can add the following in your action call

var context = GlobalHost.ConnectionManager.GetHubContext<InterviewDoneHub>();
context.Clients.Groups(connectionId).myCallback();

If the above does not work then you are going to have to connect to the hub using .NET Client. Just like you connected from javascript you can connect to the hub using C# and call a method that will then notify the client you want notified. I did this using WebAPI in the past but I am fairly certain that it can be done in Asp.Net MVC as well.




回答2:


Thanks to GJohn for the reference to the .NET Client docs, which contain this example:

$.connection.hub.start()
    .done(function(){ console.log('Now connected, connection ID=' + $.connection.hub.id); })
    .fail(function(){ console.log('Could not Connect!'); });
});

So, it turns out that $.connection.hub.id does, in fact, return the connection ID. But I was trying to call that routine at page load time, before the connection is established! The correct JS code for my view looks like this:

@section scripts {
    <!-- Reference the SignalR library. -->
    <script src="~/Scripts/jquery.signalR-2.1.2.min.js"></script>

    <!-- Reference the autogenerated SignalR hub script. -->
    <script src="~/signalr/hubs"></script>

    <!-- SignalR script to update the page -->
    <script>
        $(function () {
            // Get a reference to the server "hub" class (camelCase)
            var hub = $.connection.interviewDoneHub;

            // Create a function that the hub can call
            hub.client.myCallback = function () {
                $('#hidden-msg').show();
            };

            // Start the connection.
            $.connection.hub.start()
                .done(function () {
                    // Get our connection ID and store it in a hidden field
                    // so that it is sent to the POST action
                    $('#@Html.IdFor(m => m.SignalrConnectionId)').attr('value', $.connection.hub.id);
                })

                .fail(function () { });
        });
    </script>
}

But now I have a minor problem: I need to add a bit more JS code to disable the submit button when the form loads, and re-enable it in the connection "done" function; otherwise the user can click on the submit button before the connection ID exists. I'll leave that as an exercise for the reader...



来源:https://stackoverflow.com/questions/26454331/using-signalr-to-notify-current-caller

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!