7

How can I pass a SignalR hub context to a Hangfire job on ASP .NET Core 2.1?

It seems that since passing arguments to Hangfire is done via serialization/deserialization, it seems that Hangfire has hard-time reconstructing the SignalR hub context.

I schedule the job (in my controller) using :

BackgroundJob.Schedule(() => _hubContext.Clients.All.SendAsync(
        "MyMessage",
        "MyMessageContent", 
        System.Threading.CancellationToken.None), 
    TimeSpan.FromMinutes(2));

Then after 2 minutes, when the job tries to execute, I have the error :

Newtonsoft.Json.JsonSerializationException: Could not create an instance of type Microsoft.AspNetCore.SignalR.IClientProxy. Type is an interface or abstract class and cannot be instantiated.

Any idea?

Update 1

I ended up using a static context defined in Startup.cs, and assigned from Configure()

hbctx = app.ApplicationServices.GetRequiredService<IHubContext<MySignalRHub>>(); 

So now Hangfire schedules instead a hub helper that uses the static context :

BackgroundJob.Schedule(() => new MyHubHelper().Send(), TimeSpan.FromMinutes(2)); 

and the hub helper gets the context with Startup.hbctx

Even though this is working, it is a little smelly

Update 2

I tried also using the approach in Access SignalR Hub without Constructor Injection:

My background job scheduling became :

BackgroundJob.Schedule(() => Startup.GetService().SendOutAlert(2), TimeSpan.FromMinutes(2));

However this time, I have an exception when I reach the above line:

An unhandled exception has occurred while executing the request System.ObjectDisposedException: Cannot access a disposed object. Object name: 'IServiceProvider'.

Update 3

Thanks all. The solution was to create a helper that gets the hubcontext via its constructor via DI, and then using hangfire to schedule the helper method Send as the background job.

public interface IMyHubHelper
{
    void SendOutAlert(String userId);
}

public class MyHubHelper : IMyHubHelper
{
    private readonly IHubContext<MySignalRHub> _hubContext;

    public MyHubHelper(IHubContext<MySignalRHub> hubContext)
    {
        _hubContext = hubContext;
    }

    public void SendOutAlert(String userId)
    {
        _hubContext.Clients.All.SendAsync("ReceiveMessage", userId, "msg");
    }
}

Then launching the background job from anywhere with :

BackgroundJob.Schedule<MyHubHelper>( x => x.SendOutAlert(userId), TimeSpan.FromMinutes(2));
Yahia
  • 805
  • 7
  • 25
  • Use a DI approach and resolve the hub context when it is needed – Nkosi Nov 29 '18 at 12:06
  • @Yahia review the following answer I gave [here](https://stackoverflow.com/a/44241812/5233410). Though it is primarily targeted around a recurring job, the principal remains the same about having the job resolve the necessary dependencies to carry out its function. – Nkosi Nov 29 '18 at 13:25

2 Answers2

4

The answer from Nkosi suggesting to use Schedule<T> generics pointed me to the final solution I used:

First, my MySignalRHub is just an empty class inheriting from Hub.

public class MySignalRHub 
{
}

Then, I created a hub helper which maintains a hubcontext on my MySignalRHub. The hubcontext is injected in the helper class via the ASP.Net Core built-in DI mechanism (as explained here).

The helper class:

public class MyHubHelper : IMyHubHelper
{
    private readonly IHubContext<MySignalRHub> _hubContext;

    public MyHubHelper(IHubContext<MySignalRHub> hubContext)
    {
        _hubContext = hubContext;
    }

    public void SendData(String data)
    {
        _hubContext.Clients.All.SendAsync("ReceiveMessage", data);
    }
}

The helper interface:

public interface IMyHubHelper
{
    void SendData(String data);
}

Finally, I can use hangfire to schedule from anywhere in the code the method SendData() of the hub helper as a background job with:

BackgroundJob.Schedule<MyHubHelper>(h => h.SendData(myData), TimeSpan.FromMinutes(2));
Yahia
  • 805
  • 7
  • 25
3

Using Schedule<T> generics you should be able to take advantage of the dependency injection capabilities of the framework.

BackgroundJob.Schedule<IHubContext<MySignalRHub>>(hubContext => 
    hubContext.Clients.All.SendAsync(
        "MyMessage",
        "MyMessageContent", 
        System.Threading.CancellationToken.None), 
    TimeSpan.FromMinutes(2));
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • 1
    By using the hangfire container you wont need to inject the hub context into the controller. Hangfire will resolve the dependency and that is what would be used in the expression. – Nkosi Dec 02 '18 at 14:09
  • your answer showed me the path. The solution was to use `BackgroundJob.Schedule` not on `IHubContext`, but rather on `MyHubHelper` whose constructor was injected via DI with `IHubContext`. The scheduling of the job becomes : `BackgroundJob.Schedule( x => x.SendOutAlert(userId), TimeSpan.FromMinutes(2));` – Yahia Dec 04 '18 at 16:48