0

I would like the ASPNetCore2.0 webapp I'm working on to send a notification to specific users using SignalR. I would like to call the hub's method from another controller's action (as opposed to a client's JS call).

I have learned that this is not how SignalR is intended to be used, but I've found many users who had the same 'desire' and also some solutions. I have checked several proposed solutions, but the simplest and cleaner seemed to be the accepted answer to this post: Get Hub Context in SignalR Core from within another object.

So I gave it a go, and I get no errors at all. The server's output is error-free, and so are the browser's console and network tabs (I'm using Chrome). When debugging, the flow is smooth and the program does exactly what it should do... except the users don't get any notification...

Do any of you spot the problem?

I created a class that contains the shared methods for the hub:

using Microsoft.AspNetCore.SignalR;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace WebApp.Hubs
{
    public class HubMethods
    {
        private readonly IHubContext<PostsHub> _hubContext;

        public HubMethods(IHubContext<PostsHub> hubContext)
        {
            _hubContext = hubContext;
        }

        public async Task Notify(string postId, string sender, List<string> users)
        {
            await _hubContext.Clients.Users(users).SendAsync("Notify", sender, postId);
        }
    }
}

Then I created a hub:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace WebApp.Hubs
{
    [Authorize]
    public class PostsHub : Hub
    {
        private HubMethods _hubMethods;

        public PostsHub(HubMethods hubMethods)
        {
            _hubMethods = hubMethods;
        }

        public async Task Notify(string postId, string sender, List<string> users)
        {
            await _hubMethods.Notify(postId, sender, users);
        }
    }
}

Added these bits to Startup's ConfigureServices method:

[...]// Services before these...
services.AddSignalR();
services.AddScoped<HubMethods>();

services.AddMvc();

And Startup's Configure method:

app.UseSignalR(routes =>
{
    routes.MapHub<PostsHub>("/postshub");
});

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});

Then these lines to the view:

<script src="~/lib/signalr/signalr.js"></script>
@await Html.PartialAsync("_NotifyScriptsPartial")

And this is "_NotifyScriptsPartial.cshtml":

<script>
    var connection = new signalR.HubConnectionBuilder().withUrl('/PostsHub').build();

    connection.on('Notify', function (sender, postId) {
        var post = postId;
        var sentBy = sender;
        var content = '<a href=\'#\' class=\'close\' data-dismiss=\'alert\' aria-label=\'close\'>&times;</a>' +
        'You just received a new comment from <strong>' +
        sentBy + '</strong>. Click <a href = \'#\' class=\'alert-link\' >here</a> to view the post.'
        var alert = document.createElement('div');
        alert.classList.add('alert', 'alert-info', 'alert-dismissible');
        alert.html(content);
        document.getElementById('pageContent').appendChild(alert);
    });
</script>

Finally, in the controller that is supposed to send the notification, I added these:

public class PostsController : Controller
{
    private readonly HubMethods _hubMethods;

    public PostsController(HubMethods hubMethods)
    {
        _hubMethods = hubMethods;
    }

    // POST: Create a new post
    [Authorize]
    [HttpPost]
    public async Task<IActionResult> Create(DetailsModel model, List<string> readers)
    {
        if (ModelState.IsValid)
        {

        // Do stuff here... including creating the newPostId, userId and receipients variables used below

            await _hubMethods.Notify(newPostId, userId, receipients);

            // Do more stuff and eventually...

            return View();
        }
    }
}

Any idea?

Joe F
  • 25
  • 6
  • Did you tried once to direct inject the hub (remove hubmethodes for tests)? – Stephu Jun 12 '18 at 18:50
  • @Stephu No, I didn’t because I was pretty sure that wouldn’t work, but I will as soon as I get back to work tomorrow. – Joe F Jun 12 '18 at 22:21
  • @Stephu I tried now, and no, it doesn't work either. – Joe F Jun 13 '18 at 07:19
  • You injected hub context like described here: https://stackoverflow.com/questions/46904678/call-signalr-core-hub-method-from-controller/46906849#46906849 – Stephu Jun 14 '18 at 01:03
  • @Stephu Sort of... I tried solution 1 from that thread, but as you can see the example is pretty stripped down, and I didn't really know what I was doing, so I made a mess out of it. I then tried to inject the hub directly (not the hubcontext) in my controller. I spent a few hours between these and more googling until I could not afford it anymore and I gave up on it. I changed everything else and arranged things to suit the approach of calling the hub from js... – Joe F Jun 14 '18 at 06:37
  • Injection hub is wrong. The last sentence of your comment is not clear for me. Pls update this question to your actual code – Stephu Jun 14 '18 at 11:01
  • @Stephu What I meant with the last sentence is that I changed my code entirely, and I am no longer trying to invoke the hub's method from another controller's action. Instead, I am using SignalR the way it's designed to be used, which is to invoke the hub's method from the client's js file. And of course it works. It's just that Iwas looking for a way to fire off a message to clients as a result of a business logic, as opposed to a user's action. But thanks anyway! – Joe F Jun 14 '18 at 11:48

1 Answers1

0

In Asp.Net Core 2.1 I can use hub like this, It solves my problem, also You used like this in your controller. Hope it helps.

public class SomeController : Controller
{
    private readonly IHubContext<MyHub> _myHub;

    public SomeController (IHubContext<MyHub> myHub)
    {
        _myHub = myHub;
    }

    public void SomeAction()
    {
        //for your example 
        _myHub.Clients.All.SendAsync("Notify", "data");
    }
}

I can get the "data" text from browser's console. If you use jQuery in your project, add those codes between jQuery(document).ready(function () { }); because you tried to load a partial html and I think your code needs to run after ready() event. Sorry If I misunderstood you.

hrnsky
  • 51
  • 1
  • 2
  • Thank you for your answer. Unfortunately, the post is a couple of months old, so I had to work around the problem and I'm now working on something else, but I will try your solution at the next opportunity. Thanks a lot. – Joe F Aug 17 '18 at 05:18
  • No problem @JoeF i was trying to find out how to call a hub in a controller action and it works for my situation. Hope it will helps another guy who try to do something like this. – hrnsky Aug 17 '18 at 09:25