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

2

8

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));
Ottoottoman answered 29/11, 2018 at 11:39 Comment(2)
Use a DI approach and resolve the hub context when it is neededGrenadines
@Ottoottoman review the following answer I gave here. 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.Grenadines
O
5

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));
Ottoottoman answered 9/12, 2018 at 18:4 Comment(0)
G
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));
Grenadines answered 2/12, 2018 at 13:41 Comment(3)
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.Grenadines
your answer showed me the path. The solution was to use BackgroundJob.Schedule not on IHubContext<MySignalRHub>, but rather on MyHubHelper whose constructor was injected via DI with IHubContext<MySignalRHub>. The scheduling of the job becomes : BackgroundJob.Schedule<MyHubHelper>( x => x.SendOutAlert(userId), TimeSpan.FromMinutes(2));Ottoottoman
this solution throws System.InvalidOperationException: variable 'hubContext' of type 'Microsoft.AspNetCore.SignalR.IHubContext`1[SpaceRTS_Server.Hubs.AppHub]' referenced from scope '', but it is not definedOblige

© 2022 - 2024 — McMap. All rights reserved.