web-dev-qa-db-fra.com

Appelez la méthode SignalR Core Hub depuis le contrôleur

Comment appeler la méthode SignalR Core Hub depuis Controller?
J'utilise ASP.NET Core 2.0 avec Microsoft.AspNetCore.SignalR (1.0.0-alpha2-final).

J'ai un service Windows qui communique avec Excel, SolidEdge ... Une fois l'opération terminée, il envoie une demande à mon contrôleur dans l'application ASP.NET Core. Maintenant, je dois informer tous les clients connectés au serveur avec SignalR que le programme externe a effectué une tâche.
Je ne peux pas changer le fonctionnement du service de fenêtre. (Impossible de se connecter à SignalR à partir du service Windows).
J'ai trouvé de nombreuses solutions pour l'ancien SignalR (GlobalHost.ConnectionManager.GetHubContext), mais beaucoup de choses ont changé et ces solutions ne fonctionnent plus.

Mon contrôleur:

[Route("API/vardesigncomm")]
public class VarDesignCommController : Controller
{
    [HttpPut("ProcessVarDesignCommResponse/{id}")]
    public async Task<IActionResult> ProcessVarDesignCommResponse(int id)
    {
        //call method TaskCompleted in Hub !!!! How?

        return new JsonResult(true);
    }
}

Mon hub:

public class VarDesignHub : Hub
{
    public async Task TaskCompleted(int id)
    {
        await Clients.All.InvokeAsync("Completed", id);
    }
}
35
Makla

Solution 1

Une autre possibilité consiste à injecter votre HubContext dans votre contrôleur de la manière suivante:

public VarDesignCommController(IHubContext<VarDesignHub> hubcontext)
{
    HubContext = hubcontext;
    ...
}

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

Ensuite, vous pouvez également appeler

await this.HubContext.Clients.All.InvokeAsync("Completed", id);

Mais ensuite, vous dirigerez les méthodes d’appel sur tous les clients.

Solution 2

Vous pouvez également travailler avec des concentrateurs typés: Créez simplement une interface dans laquelle vous définissez les méthodes que votre serveur peut appeler sur les clients:

public interface ITypedHubClient
  {
    Task BroadcastMessage(string name, string message);
  }

Hériter de Hub:

 public class ChatHub : Hub<ITypedHubClient>
      {
        public void Send(string name, string message)
        {
          Clients.All.BroadcastMessage(name, message);
        }
      }

Injectez votre contexte hub tapé dans votre contrôleur et utilisez-le:

[Route("api/demo")]
  public class DemoController : Controller
  {   
    IHubContext<ChatHub, ITypedHubClient> _chatHubContext;
    public DemoController(IHubContext<ChatHub, ITypedHubClient> chatHubContext)
    {
      _chatHubContext = chatHubContext;
    }
    // GET: api/values
    [HttpGet]
    public IEnumerable<string> Get()
    {
      _chatHubContext.Clients.All.BroadcastMessage("test", "test");
      return new string[] { "value1", "value2" };
    }
  }
50
Stephu

La réponse actuelle ne répond pas à la question posée.

La réponse simple est que vous ne pouvez pas appeler directement une méthode de concentrateur depuis un contrôleur MVC ou ailleurs. C'est par conception. Considérez le concentrateur comme contenant les points de terminaison que les clients SignalR Core doivent appeler, et non les méthodes de serveur ou de contrôleur.

Voici ce que Microsoft dit (il s'agit de la documentation antérieure à SignalR Core, mais elle s'applique toujours à SignalR Core):

Vous n'instanciez pas la classe Hub et n'appeliez pas ses méthodes à partir de votre propre code sur le serveur. tout cela est fait pour vous par le pipeline de SignalR Hubs. SignalR crée une nouvelle instance de votre classe Hub chaque fois qu'elle doit gérer une opération sur un concentrateur, par exemple lorsqu'un client se connecte, se déconnecte ou effectue un appel de méthode au serveur.

Comme les instances de la classe Hub sont transitoires, vous ne pouvez pas les utiliser pour conserver l'état d'un appel de méthode à l'autre. Chaque fois que le serveur reçoit un appel de méthode d'un client, une nouvelle instance de votre classe Hub traite le message. Pour conserver l'état via plusieurs connexions et appels de méthode, utilisez une autre méthode, telle qu'une base de données ou une variable statique dans la classe Hub, ou une classe différente qui ne dérive pas de Hub. Si vous conservez des données en mémoire, à l'aide d'une méthode telle qu'une variable statique sur la classe Hub, les données seront perdues lors du recyclage du domaine d'application.

Si vous souhaitez envoyer des messages aux clients à partir de votre propre code exécuté en dehors de la classe Hub, vous ne pouvez pas le faire en instanciant une instance de classe Hub, mais vous pouvez le faire en obtenant une référence à l'objet de contexte SignalR de votre classe Hub. ...

Si vous devez appeler du code dans le concentrateur, il est préférable de le placer dans une classe ou un service externe accessible de n'importe où.

Voici donc un exemple utilisant le framework DI simple et intégré pour ASP.NET Core:

En supposant que le code que vous devez appeler se trouve dans DoStuff.cs:

public class DoStuff : IDoStuff
{
    public string GetData()
    {
        return "MyData";
    }
}

public interface IDoStuff
{
    string GetData();
}

Dans Startup.cs, configurez un singleton à l'aide du conteneur intégré:

        services.AddSingleton<IDoStuff, DoStuff>();

Le fichier Startup.cs complet ressemble à ceci:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<CookiePolicyOptions>(options =>
        {
            // This lambda determines whether user consent for non-essential cookies is needed for a given request.
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });

        services.AddSignalR();

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

        services.AddSingleton<IDoStuff, DoStuff>();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseCookiePolicy();
        app.UseSignalR(routes =>
        {
            routes.MapHub<MyHub>("/myhub");
        });

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

Pour votre classe hub, injectez le singleton et utilisez-le dans une méthode:

public class MyHub : Hub
{
    private readonly IDoStuff _doStuff;

    public MyHub(IDoStuff doStuff)
    {
        _doStuff = doStuff;
    }

    public string GetData()
    {
       return  _doStuff.GetData();
    }
}

Ensuite, dans votre contrôleur, injectez le IHubContext et le singleton:

public class HomeController : Controller
{
    private readonly IDoStuff _doStuff;
    private readonly IHubContext<MyHub> _hub;

    public HomeController(IDoStuff doStuff, IHubContext<MyHub> hub)
    {
        _doStuff = doStuff;
        _hub = hub;
    }

    public async Task<IActionResult> Index()
    {
        var data = _doStuff.GetData();
        await _hub.Clients.All.SendAsync("show_data", data);

        return View();
    }
}

Bien entendu, votre client JavaScript ou autre doit avoir un rappel show_data configuré.

Notez que nous utilisons le contexte de concentrateur injecté pour envoyer les données à tous les clients SignalR: _hub.Clients.All.SendAsync (...)

18
swiftest

Ceci est maintenant bien documenté ici

Vous pouvez injecter une instance de IHubContext dans un contrôleur en l'ajoutant à votre constructeur:

public class HomeController : Controller
{
    private readonly IHubContext<NotificationHub> _hubContext;

    public HomeController(IHubContext<NotificationHub> hubContext)
    {
        _hubContext = hubContext;
    }
}

Maintenant, avec l'accès à une instance de IHubContext, vous pouvez appeler des méthodes hub comme si vous étiez dans le hub lui-même.

public async Task<IActionResult> Index()
{
    await _hubContext.Clients.All.SendAsync("Notify", $"Home page loaded at: {DateTime.Now}");
    return View();
}
4
DrSatan1