93 votes

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

Comment puis-je appeler la méthode SignalR Core Hub à partir du contrôleur ?
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 ... Lorsque l'opération est terminée, il envoie une requête à mon contrôleur dans l'application ASP.NET Core. J'ai maintenant besoin d'informer tous les clients connectés au serveur avec SignalR que le programme externe a terminé une tâche.
Je ne peux pas changer la façon dont le service des fenêtres fonctionne. (Impossible de se connecter à SignalR depuis le service fenêtre).
J'ai trouvé une solution complète 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);
    }
}

126voto

Stephu Points 1503

Solution 1

Une autre possibilité est d'injecter votre HubContext dans votre contrôleur comme :

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

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

Alors vous pouvez aussi appeler

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

Mais vous devrez alors appeler directement les méthodes sur tous les clients.

Solution 2

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

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

Héritage de Hub :

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

Injectez votre hubcontext dans votre contrôleur, et travaillez avec :

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

10 votes

Cette réponse est géniale. Merci, mais elle ne répond pas vraiment à ma question. Elle le fera pour l'instant. Mais tôt ou tard, j'aurai besoin d'appeler la méthode Hub. Et non pas envoyer un message à tous les clients.

0 votes

@Makla : J'ai changé la réponse, voir le lien

1 votes

@Makla : J'ai trouvé une meilleure solution. Voir la solution 2. Je pense que cela vous aidera.

55voto

swiftest Points 191

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 hub depuis un contrôleur MVC ou autre. C'est une question de conception. Pensez au hub comme contenant les points finaux que les clients SignalR Core doivent appeler, et non les méthodes du serveur ou du contrôleur.

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

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

Les instances de la classe Hub étant transitoires, vous ne pouvez pas les utiliser pour maintenir 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 maintenir l'état à travers de multiples connexions et appels de méthode, utilisez une autre méthode telle qu'une base de données, une variable statique de la classe Hub ou une classe différente qui ne dérive pas de Hub. Si vous conservez les données en mémoire, en utilisant une méthode telle qu'une variable statique sur la classe Hub, les données seront perdues lorsque le domaine d'application se recycle.

Si vous voulez envoyer des messages aux clients à partir de votre propre code qui s'exécute 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 pour votre classe Hub...

S'il y a du code dans le concentrateur que vous devez appeler, il est préférable de le placer dans une classe ou un service externe accessible de partout.

Voici donc un exemple utilisant le simple cadre DI intégré à ASP.NET Core :

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

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

public interface IDoStuff
{
    string GetData();
}

Dans Startup.cs, configurez un singleton en utilisant le 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 callback show_data configuré.

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

2 votes

En conséquence, devons-nous appeler la méthode Clients.All à partir du contrôleur et du hub ? Cela semble être redondant et je me demande également comment appeler les méthodes côté client. Devons-nous les appeler dans le Hub comme nous le faisons généralement ?

0 votes

Qu'en est-il de l'utilisation de cette approche dans Microsoft.AspNet.SignalR ? Une idée sur SignalR : Comment utiliser l'interface IHubContext<THub,T> dans ASP.NET MVC ?

24voto

DrSatan1 Points 569

C'est maintenant bien documenté aquí

Vous pouvez injecter une instance de IHubContext dans un contrôleur en 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 de 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();
}

0 votes

Qu'en est-il de l'utilisation de cette approche dans Microsoft.AspNet.SignalR ? Une idée sur SignalR : Comment utiliser l'interface IHubContext<THub,T> dans ASP.NET MVC ?

0 votes

@hexadecimal utiliser GlobalHost comme répondu ici stackoverflow.com/questions/47902981/

0 votes

@Stephu Est-ce que c'est une bonne habitude d'appeler la méthode Hub à partir du contrôleur ? ? Au début, j'ai appelé la méthode de mon concentrateur à partir de mon contrôleur également, mais ensuite je les ai appelés du côté client afin de séparé des préoccupations . Qu'en pensez-vous ?

6voto

Higty Points 292

Une autre réponse pour ne pas utiliser l'injection est ici.

Je conçois ma classe hub comme ci-dessous.

public class NotificationHub : Microsoft.AspNetCore.SignalR.Hub
{
    public static IHubContext<NotificationHub> Current { get; set; }
}

Dans votre classe de démarrage

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    NotificationHub.Current = app.ApplicationServices.GetService<IHubContext<NotificationFromServerHub>>();

}

Donc, vous pouvez l'utiliser comme ça de n'importe où.

public class MyBizClass
{
    public void DoSomething()
    {
        NotificationHub.Current.MyMethod(...);
    }
}

0 votes

NotificationHub.Current ne vous donne pas accès à une instance de NotificationHub donc vous ne pouvez pas appeler MyMethod sur elle. Vous avez accès à ses contexte et de là, vous pouvez envoyer des messages à ses clients (je pense... je suis sur le point d'essayer). C'est vraiment très compliqué

2voto

Lukáš Kmoch Points 299

Une solution possible est d'utiliser le client hub C#. Il vous suffit de créer une nouvelle instance de HubConnection et de l'utiliser pour invoquer la méthode requise. C'est presque la même chose que d'appeler la méthode à partir de javascript/typescript.

using (var hubConnection = new HubConnection("http://www.contoso.com/")) 
{
    IHubProxy hubproxy = hubConnection.CreateHubProxy("MyHub");

    hubproxy.Invoke("TaskCompleted", id);
)

PS : Je sais que c'est exagéré, mais c'est vraiment la seule réponse correcte à la question initiale.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X