使用 IHubContext 创建 class 个实例

Creating class instance with IHubContext

我有以下class。我需要创建此调用的实例,以便我可以从静态方法调用非静态方法,我已经尝试过

EstablishmentHub establishmenthub = new EstablishmentHub();

但这行不通,因为这一行:

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

我可以创建一个实例吗?

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;
    }
}

我正在尝试使用以下方法在启动时启动计时器功能

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

但它抱怨 TimeElapsed(非静态字段需要对象引用)

我不想让 TimeElasped 成为静态的,因为它下面有一些函数是非静态的

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

如果您正在使用 ASP.NET 核心,您可以使用依赖注入来获取 IHubContext 的实例。

如果您正在使用 ASP.NET 4.x,您可以使用 GlobalHost 访问 IHubContext。

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

假设 ASP 核心

您似乎将集线器与一些 long-running 东西(计时器)混合在一起。您不应该那样做,也不需要集线器内的 IHubContext,因为此 IHubContext 只允许您访问集线器而实际上没有集线器的实例。
您应该从 Hub class 中删除 IHubContext,而是将该 IHubContext 注入 long-running 后台任务。您可以阅读有关实施后台任务的更多信息 here。有了这个系统,你就不会使用计时器,而是会有某种延迟(可能),但这并不难改变,所以不用担心。

之后,您只需要做一些事情就可以让它协同工作。
- 在集线器上结束 Hub-classname(已经完成)。
- 将 Hub-class 放在名为 Hubs 的文件夹中。
- 从 Hub-class 中删除 IHubContext 并将其添加到后台任务,就像您当前在 Hub-class.

中一样

您现在可以使用 IServiceCollection.AddHostedService<YourBackgroundService>() 将后台任务添加到您的系统中。您可以在 Startup-class 中的 ConfigureServices 方法中执行此操作。

您现在可以在后台服务中使用 IHubContext 访问集线器,它将能够执行所有基于 timeout/timer 的操作。围绕集线器本身的逻辑不应放在 Hub-class 内。

希望对您有所帮助。如果您不使用 ASP 核心,我将继续并删除此答案。

编辑:
您的 background-service 可能看起来像这样:

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;
            }
        }
    }
}

将 stoppingToken 添加到 Task.Delay 将抛出一个 TaskCanceledException 如果它被取消。发生这种情况时,只需停止循环和 return 即可。

我还建议您使用 strongly-typed hubs,因为它们有很多好处。

一个完整的解决方案(包括您所写的关于您的应用程序的所有内容)如下所示:

// 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
    }
}

Startup.ConfigureServices(IServiceCollection services)内你需要调用:
services.AddHostedService<EstablishmentsService>();

当然你也必须添加js。在我的项目中,我使用一些代码在 Startup-class 的 Configure 方法中注册了 signalR-url:

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

在 js 中,您现在可以使用相同的 url 获取集线器(几乎可以肯定还有其他没有 url 的方法,但我是这样做的):

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

获得此连接后,您可以对所有客户端方法使用 connection.on(xxxx, ..),对所有集线器方法使用 connection.xxxx(..)
在此示例中,您将执行以下操作:

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
});

现在您已经设置了客户端发生的情况。您现在可以使用 connection.start(); 开始连接。

请注意,在 js 中,您只需使用 connection.SomeHubMethod() 即可调用集线器中定义的方法(我在完整解决方案中将该方法添加到集线器中)。如果您使用 parameters/return 值,它会在 json 和 c# 对象之间全部转换。

这就是您需要做的全部,没那么多:)。

我们到了。您现在已经删除了不应与远程处理一起使用的所有 non-public 方法,非常好。这些方法与 sql 相关,因此您应该将它们放在一个单独的 class 中,并使用一个好听的名称来表明它用于检索数据。
假设您定义并填充了 class。您现在需要做的是将其注册到 DI-system。使用 db-stuff 你通常想要一个单例,但我不能在没有看到 class contains/does.
的情况下做出决定 您可以在 Startup-class 中的 ConfigureServices 方法中注册 class。 services.AddSingleton<YourSQLThingy>();

现在 DI-system 知道你的 class 并且你可以简单地将它注入任何你想要的地方。为此,将其放入 Hubs 构造函数(我更新了 "full" 解决方案”)。DI-system 将在激活您的集线器时自动找到已注册的 class 并像那样注入它。请注意因为你在这里使用的是单例,所以它总是被注入同一个实例!