Comment envoyer un message de signal à un client MVC à partir d’un contrôleur Web

la programmation


Bonjour,

J’ai une application Web Asp.Net Core 2.1, MVC dans Visual Studio 2017 15.7.5.

J’ai essayé de comprendre comment envoyer un seul message texte de chaîne SignalR à un MVC View.cshtml à partir de la méthode du contrôleur invoquée à partir de la vue MVC en utilisant ce type de lien : @Html.RouteLink(“Get Products”, ” ObtenirProduits”).

En le plaçant dans un contexte différent, je souhaite envoyer un message depuis l’extérieur d’un hub (directement du serveur au client) en injectant une instance de IHubContext dans mon contrôleur et en l’ajoutant au constructeur.

J’ai configuré mon application de cette façon :

Démarrage.cs :

C#
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using ChinavasionAPI.Data;
using ChinavasionAPI.Hubs;

namespace ChinavasionAPI
{
    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)
        {
               // Adds a default in-memory implementation of IDistributedCache.
               services.AddDistributedMemoryCache();

               services.AddSession(options =>
               {
                    // Set a short timeout for easy testing.
                    options.IdleTimeout = TimeSpan.FromSeconds(10);
                    options.Cookie.HttpOnly = true;
               });

               services.AddRouting();

               services.AddSignalR();
               services.AddMvc();

            services.AddDbContext<ChinavasionAPIContext>(options =>
                    options.UseSqlServer(Configuration.GetConnectionString("ChinavasionAPIContext")));

          }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();

            app.UseSession();

            app.UseSignalR(routes =>
            {
               routes.MapHub<ChvHub>("/chvHub");
            });

               app.UseMvc(routes =>
               {
                 routes.MapRoute(
                    name: "token",
                    template: "token",
                    defaults: new { controller = "API", action = "GetAccessToken" });

                 routes.MapRoute(
                    name: "GetCustomers",
                    template: "customers",
                    defaults: new { controller = "Customers", action = "GetCustomers" });

                 routes.MapRoute(
                    name: "GetProducts",
                    template: "products",
                    defaults: new { controller = "Products", action = "GetProducts" });

        }
    }
}

ChvHub :

C#
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;

namespace  ChinavasionAPI.Hubs
{
     public class ChvHub : Hub
     {
          public async Task SendMessage(string user, string message)
          {
               await Clients.All.SendAsync("ReceiveMessage", user, message);
          }
     }
}

chat.js :

Javascript
const connection = new signalR.HubConnectionBuilder()
     .withUrl("/chvHub")
     .build();

connection.on("ReceiveMessage", (user, message) => {
     const msg = message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
     const encodedMsg = user + " says " + msg;
     const li = document.createElement("li");
     li.textContent = encodedMsg;
     document.getElementById("messagesList").appendChild(li);
});

connection.start().catch(err => console.error(err.toString()));

document.getElementById("sendButton").addEventListener("click", event => {
     const user = document.getElementById("userInput").value;
     const message = document.getElementById("messageInput").value;
     connection.invoke("SendMessage", user, message).catch(err => console.error(err.toString()));
     event.preventDefault();
});

ProduitsController.cs :

C#
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using Lakeside.Api.AdapterLibrary;
using ChinavasionAPI.DTOs;
using ChinavasionAPI.Models;
using ChinavasionAPI.Models.CAPIViewModels;
using ChinavasionAPI.Data;
using ChinavasionAPI.Hubs;
using Microsoft.AspNetCore.SignalR;

namespace ChinavasionAPI.Controllers
{
    public class ProductsController : Controller
    {
          private readonly ChinavasionAPIContext _context;
          private readonly IHubContext<ChvHub> _hubContext;

          public ProductsController(ChinavasionAPIContext context, IHubContext<ChvHub> hubcontext)
          {
               _context = context;
               _hubContext = hubcontext;
          }

          public async Task<IActionResult> GetProducts()
          {
               var model = new AccessModel();
               model.UserAccessModel = _context.UserAccessModels.Single(a => a.ID == 1);

               var accessToken = (model.UserAccessModel.AccessToken ?? TempData["accessToken"]).ToString();
               var serverUrl = (model.UserAccessModel.ServerUrl ?? TempData["serverUrl"]).ToString();

               var nopApiClient = new ApiClient(accessToken, serverUrl);
               MultipleProductModel multipleProductModel = new MultipleProductModel();
               List<Category> categories = new List<Category>();

               string jsonUrl = $"/api/products?limit=75&fields=id,sku,name,images,categories";
               object productsData = nopApiClient.Get(jsonUrl);

               var productsRootObject = JsonConvert.DeserializeObject<ProductsRootObject>(productsData.ToString());             
               var products = productsRootObject.Products.Where(
                    product => !string.IsNullOrEmpty(product.Name));

               multipleProductModel.MProductsApi = productsRootObject.Products;

               int? setupID = 1;
               if (setupID == null)
               {
                    return NotFound();
               }

               var setup = await _context.Setups.SingleOrDefaultAsync(m => m.ID == setupID);
               if (setup == null)
               {
                    return NotFound();
               }

               serverUrl = setup.ServerUrl;
               string serverKey = setup.Key;
               string queryCall = "api/getProductDetails.php";
               string parameterName = "model_code";
               multipleProductModel.MProducts.Clear();
               var productsService = new Service.ProductService();

               foreach(ProductApi nopProduct in products)
               {
                    Product chvproduct = (await productsService.GetProductBySkuAPIAsync(serverUrl, serverKey, queryCall, parameterName, nopProduct.Sku));
                    if (chvproduct == null)
                    {
                         multipleProductModel.MProducts.Add(new Product { product_id = 0, model_code = "Not on file" });
                    }
                    else
                    {
                         multipleProductModel.MProducts.Add(chvproduct);
                    }
                    await this._hubContext.Clients.All.SendAsync("ReceiveMessage", "NewMessage", "AnotherNewMessage");
               }

               return View("~/Views/API/Products.cshtml", multipleProductModel);
          }

AccessToken.cshtml :

HTML
@model ChinavasionAPI.Models.AccessModel
@using Microsoft.AspNetCore.Http.Extensions

@{
     ViewData["Title"] = "Access Token";
}

@section Scripts {
     <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
     <script src="~/lib/signalr/signalr.js"></script>
     <script src="~/js/chat.js"></script>
}
     <div>
          <h2>Access Data</h2>
          @using (Html.BeginRouteForm("Submit", FormMethod.Post))
          {
               <table>
                    <tr>
                         <td>
                              <label for="serverUrl">Server url: </label>
                         </td>
                         <td>
                              <input name="serverUrl" type="text" id="serverUrl" value="@Model.UserAccessModel.ServerUrl" style="width:400px" />
                         </td>
                    </tr>
                    <tr>
                         <td>
                              <label for="clientId">Client Id: </label>
                         </td>
                         <td>
                              <input name="clientId" type="text" id="clientId" value="@Model.UserAccessModel.ClientId" style="width:400px" />
                         </td>
                    </tr>
                    <tr>
                         <td>
                              <label for="clientSecret">Client Secret: </label>
                         </td>
                         <td>
                              <input name="clientSecret" type="text" id="clientSecret" value="@Model.UserAccessModel.ClientSecret" style="width:400px" />
                         </td>
                    </tr>
                    <tr>
                         <td>
                              <label for="redirectUrl">Redirect Url: </label>
                         </td>
                         <td>
                              <input name="redirectUrl" type="text" id="redirectUrl" readonly="readonly" value="@Model.UserAccessModel.RedirectUrl" style="width:400px" />
                         </td>
                    </tr>
                    <tr>
                         <td></td>
                         <td>
                              <br />
                              <input type="submit" value="Get Access Token" />
                              <button type="button" id="refresh-token-button">Refresh Access Token</button>
                         </td>
                    </tr>
               </table>
          }
     </div>

     <div class="container">
          <div class="row">
               <div class="col-6"> </div>
               <div class="col-6">
                    <ul id="messagesList"></ul>
               </div>
          </div>
     </div>

     @Html.RouteLink("Get Customers", "GetCustomers")
     @Html.RouteLink("Get Products", "GetProducts")

Lorsque je clique sur le lien généré à partir de : @Html.RouteLink(“Obtenir des produits”, “Obtenir des produits”)il traite tout dans “GetProducts” avant de s’afficher à partir du retour View(“~/Views/API/Products.cshtml”, multipleProductModel);, mais il n’affiche jamais de message sur l’écran d’affichage AccessToken.cshtml.

Lorsque j’utilise les outils de développement dans Firefox, la console affiche la connexion SignalR Web Socket dans la console (WebSocket connecté à ws://localhost:64370/chvHub?id=QeZ8xRJSFHuzzQIZ72X2dg), mais lorsque je clique sur le lien généré à partir de : @ Html.RouteLink(“Get Products”, “GetProducts”), il montre qu’il est déconnecté (Connexion déconnectée.).

De plus, lorsque j’utilise le débogueur Visual Studio et que je l’arrête dans la méthode GetProducts, _hubContext n’a pas de connexion.

Pour une raison quelconque, lorsque le contrôle passe au ProductsController, la connexion SignalR est perdue.

Toute aide que quiconque peut fournir pour aider à afficher un message SignalR dans une vue serait grandement appréciée.

Merci,
Tony

Ce que j’ai essayé :

J’ai ajouté cette ligne à la méthode ConfigureServices dans Startup.cs :

services.AddSignalR();

J’ai ajouté ces lignes à la méthode Configure dans Startup.cs :

C#
app.UseSignalR(routes =>
            {
               routes.MapHub<ChvHub>("/chvHub");
            });

J’ai configuré mon contrôleur pour injecter une instance de IHubContext de cette façon :

C#
public class ProductsController : Controller
    {
          private readonly ChinavasionAPIContext _context;
          private readonly IHubContext<ChvHub> _hubContext;
          public ProductsController(ChinavasionAPIContext context, IHubContext<ChvHub> hubcontext)
          {
               _context = context;
               _hubContext = hubcontext;
          }

Ajout de cette ligne dans le contrôleur pour envoyer le message au client :

C#
await this._hubContext.Clients.All.SendAsync("ReceiveMessage", "NewMessage", "AnotherNewMessage");

Dans la console Nuget Package Manager, j’ai exécuté ces commandes :

npm init -y
npm installer @aspnet/signalr

J’ai installé via Gérer les packages Nuget : Microsoft.AspNetCore.SignalR Dernière version stable 1.0.2.

Solution 1

Je ne peux pas dire que j’ai compris cela, mais j’ai réussi à le faire fonctionner en testant différentes stratégies de codage particulièrement inspirées de celle-ci. article[^] par Dino Esposito.

J’ai créé un bouton pour “GetProducts” et modifié le “div” inférieur dans le fichier AccessToken.cshtml comme ceci :

Javascript
<div class="container">
     <div class="row">
          <div class="col-6"</div>
          <div class="col-6">
               <ul id="messagesList"></ul>
          </div>
     </div>
     <form asp-controller="Products" asp-action="GetProducts">
          <div class="form-group">
               <br />
               <input type="submit" id="get-products-button" value="Get Products" class="btn btn-default" onclick="startTask()" />
          </div>
     </form>
</div>

<script>
     function startTask() {
          $.get("/products/getproducts/");
     }
</script>

<script>
     document.getElementById("get-products-button").addEventListener("click", event => {
          event.preventDefault();
     });
</script>

J’ai ajouté le script du bas juste comme aide au débogage pour voir si le bouton était activé.

Sans aucune partie du code ci-dessus, les messages adressés au client ne fonctionnent pas. Je ne peux pas expliquer cela, je sais juste que si j’en supprime une partie, les messages ne sont pas affichés au client.

Les messages adressés au client s’affichent, mais ce succès a un prix. Lorsque la méthode GetProducts arrive à cette ligne :

C#
return View("~/Views/API/Products.cshtml", multipleProductModel);

Il n’affiche pas l’écran d’affichage Products.cshtml. Cela ne donne pas d’erreur, cela ne s’affiche tout simplement pas.

La méthode GetProducts traite comme elle le devrait, mais elle n’affichera pas cette vue.

J’apprécierais que quelqu’un puisse me dire pourquoi cette vue ne s’affichera pas.

Merci,
Tony

コメント

タイトルとURLをコピーしました