简体   繁体   中英

Creating class instance with IHubContext

I have the below class. I need to create a instance of this call so I can call a non-static method from a static one, I have tried

EstablishmentHub establishmenthub = new EstablishmentHub();

but this is not working because of this line:

public EstablishmentsHub(IHubContext<EstablishmentsHub> hubcontext)
{
    HubContext = hubcontext;
}

Is there anyway I can create an instance of this?

public class EstablishmentsHub : Hub
{
    public readonly static System.Timers.Timer _Timer = new System.Timers.Timer();

    public EstablishmentsHub(IHubContext<EstablishmentsHub> hubcontext)
    {
        HubContext = hubcontext;
    }

    private IHubContext<EstablishmentsHub> HubContext
    {
        get;
        set;
    }
}

I'm trying to Start a Timer function on start, using the below

static EstablishmentsHub()
{
    _Timer.Interval = 10000;
    _Timer.Elapsed += TimerElapsed;
    _Timer.Start();
}

but its complaining about TimeElapsed (an object reference is required for non-static field)

I don't want to make TimeElasped static because there are functions underneath it that are non-static

void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    broadcaststatus("ayl-3",10);
}

Assuming ASP Core

It seems like you're mixing the hub with some long-running stuff (the timer). You shouldn't do that and also you don't need the IHubContext inside of your hub since this IHubContext only allows you to access the hub without actually having an instance of the hub.
You should remove the IHubContext from your Hub class and instead inject that IHubContext into a long-running Background task. You can read more about implementing a background task here . With this system you wouldn't work with a timer but with some sort of delay (probably) but that's not hard to change so don't worry about that.

After that you only need a few things to make it work together.
- Have the Hub-classname end on Hub (already done).
- Place the Hub-class in a folder named Hubs.
- Remove the IHubContext from the Hub-class and add it instead to the background task the same way you currently have it in your Hub-class.

You can now add the background task into your system using IServiceCollection.AddHostedService<YourBackgroundService>() . You'd do this in the ConfigureServices -method in the Startup -class.

You have now access to the hub using IHubContext inside of your background service which will be able to do all the timeout/timer based stuff. Having logic around the hub itself shouldn't be placed inside the Hub-class.

I hope this helps. If you're not using ASP Core I'm going to go ahead and remove this answer.

Edit:
Your background-service could look something like this:

public class SomeTimedService : Microsoft.Extensions.Hosting.BackgroundService
{
    private IHubContext<EstablishmentsHub> _hubContext;

    public SomeTimedService(IHubContext<EstablishmentsHub> hubcontext)
    {
        _hubContext = hubcontext;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            await _hubContext.Clients.All.BroadcastStatus("ayl-3", 10);

            try
            {
                await Task.Delay(10000, stoppingToken);
            }
            catch (TaskCanceledException) {
                return;
            }
        }
    }
}

Adding the stoppingToken to the Task.Delay will throw a TaskCanceledException if it get's canceled. Just stop the loop and return when that happens.

I would also suggest you to use strongly-typed hubs since they have a lot of benefits.

A full solution (including all you've written about your application) would be like this:

// background service for sending a (currently hardcoded) status every 10 seconds to every client
public class EstablishmentsService : Microsoft.Extensions.Hosting.BackgroundService
{
    private IHubContext<EstablishmentsHub, IEstablishmentsClient> _hubContext;

    public EstablishmentsService(IHubContext<EstablishmentsHub, IEstablishmentsClient> hubcontext)
    {
        _hubContext = hubcontext;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            await _hubContext.Clients.All.ReceiveStatus("ayl-3", 10);

            try
            {
                await Task.Delay(10000, stoppingToken);
            }
            catch (TaskCanceledException) {
                return;
            }
        }
    }
}

// the client of the hub. Here you place methods which should be called by the application.
public interface IEstablishmentsClient
{
    Task ReceiveStatus(string someString, int someInt);
}

// the hub itself. You'd place methods here which should be called from the client
public class EstablishmentsHub : Hub<IEstablishmentsClient>
{
    private readonly YourSQLThingy _sqlThingy;

    public EstablishmentsHub(YourSQLThingy sQLThingy)
    {
        _sqlThingy = sQLThingy;
    }

    public SomeObject GetFirstObject()
    {
        return _sqlThingy.Whatever.First();
    }

    public void SomeHubMethod()
    {
        // do something here
    }
}

Inside Startup.ConfigureServices(IServiceCollection services) you need to call:
services.AddHostedService<EstablishmentsService>();

Of course you'll have to add the js too. In my project I used some code to register the signalR-url in the Configure method of the Startup-class:

app.UseSignalR(routes =>
{
   routes.MapHub<EstablishmentsHub>("/establishmentsSignalR");
});

In the js you can now get the hub using that same url (there are other ways without the url almost certainly but I did it this way):

var connection = new signalR.HubConnectionBuilder().withUrl("/establishmentsSignalR").build();

After you get this connection you have the possibility to use connection.on(xxxx, ..) for all the client methods and connection.xxxx(..) for all the hub methods.
In this example here you would do the following:

connection.on("ReceiveStatus", function (someString, someInt) {
    // do whatever with those two values
    // if it's a bigger object it will simply be converted to a jsonObject
});

Now you've set what happens on the clientside. You can now start the connection using connection.start(); .

Note that in the js you can call the methods defined in the hub simply by using connection.SomeHubMethod() (I added that method to the hub in the full solution). If you use parameters/return values it's all converted between json and c# objects.

That's all you need to do, not that much :).

We're getting there. You have now removed every non-public method which isn't supposed to be used with remoting, very good. These methods were something with sql so you are supposed to place them into a seperate class with a good name that tells that it's for retrieving data.
Let's say you have your class defined and filled. What you need to do now, is register it to the DI-system. With db-stuff you usually want a singleton but I can't decide for that without seeing what the class contains/does.
You can register the class inside the ConfigureServices method in the Startup-class. services.AddSingleton<YourSQLThingy>();

Now the DI-system knows your class and you can simply inject it anywhere you want. For that place it into the Hubs constructor (I updated the "full" solution"). The DI-system will automatically find the registered class when activating your hub and inject it just like that. Note that since you're using singleton here, it will always be the same instance which gets injected!

If you are using ASP.NET Core you can use dependency injection to get an instance of IHubContext.

If you are using ASP.NET 4.x you can access IHubContext with GlobalHost.

var hubContext = GlobalHost.ConnectionManager.GetHubContext<SomeHub>();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM