【解決方法】Web コントローラーからシグナル メッセージを MVC クライアントに送信する方法

プログラミングQA


こんにちは、

Visual Studio 2017 15.7.5 に Asp.Net Core 2.1、MVC Web アプリケーションがあります。

このタイプのリンクを使用して、MVC ビューから呼び出されるコントローラー メソッドから単一の SignalR 文字列テキスト メッセージを MVC View.cshtml に送信する方法を見つけようとしています: @Html.RouteLink(“Get Products”,” GetProducts”)。

別のコンテキストに入れると、IHubContext のインスタンスをコントローラーに挿入し、それをコンストラクターに追加して、ハブの外部から (サーバーからクライアントに直接) メッセージを送信したいと考えています。

私はこのようにアプリケーションをセットアップしています:

Startup.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" });

        }
    }
}

Chvハブ:

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

チャット.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();
});

ProductsController.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")

から生成されたリンクをクリックすると: @Html.RouteLink(“Get Products”, “GetProducts”)、返された View(“~/Views/API/Products.cshtml”, multipleProductModel); から表示する前に “GetProducts” のすべてを処理しますが、AccessToken.cshtml ビュー画面にメッセージを表示することはありません。

Firefox で開発者ツールを使用すると、コンソールに SignalR Web Socket 接続 (ws://localhost:64370/chvHub?id=QeZ8xRJSFHuzzQIZ72X2dg に接続された WebSocket) が表示されますが、次から生成されたリンクをクリックすると: @ Html.RouteLink(“Get Products”, “GetProducts”)、切断されていることを示します(接続が切断されました)。

また、Visual Studio デバッガーを使用して GetProducts メソッドで停止すると、_hubContext が接続されません。

何らかの理由で、制御が ProductsController に移動すると、SignalR 接続が失われます。

ビューに 1 つの SignalR メッセージを表示するために誰でも提供できるヘルプをいただければ幸いです。

ありがとう、
トニー

私が試したこと:

Startup.cs の ConfigureServices メソッドに次の行を追加しました。

services.AddSignalR();

Startup.cs の Configure メソッドに次の行を追加しました。

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

この方法で IHubContext のインスタンスを挿入するようにコントローラーを構成しました。

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

メッセージをクライアントに送信するために、コントローラーに次の行を追加しました。

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

Nuget パッケージ マネージャー コンソールで、次のコマンドを実行しました。

npm init -y
npm install @aspnet/signalr

Manage Nuget Packages からインストールしました。 Microsoft.AspNetCore.SignalR 最新の安定版 1.0.2。

解決策 1

私はこれを理解したとは言えませんが、特にこれに触発されたさまざまなコーディング戦略をテストすることで、なんとか機能させることができました 記事[^] ディノ・エスポジートによる。

「GetProducts」へのボタンを作成し、AccessToken.cshtml ファイルの一番下の「div」を次のように変更しました。

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>

ボタンがアクティブになっているかどうかを確認するためのデバッグ支援として、一番下のスクリプトを追加しました。

上記のコードの一部がないと、クライアントへのメッセージは機能しません。 それを説明することはできません。その一部を削除すると、メッセージがクライアントに表示されないことだけはわかっています。

クライアントへのメッセージは表示されますが、この成功には代償が伴います。 GetProducts メソッドがこの行に到達すると:

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

Products.cshtml ビュー画面は表示されません。 エラーにはなりませんが、表示されないだけです。

GetProducts メソッドは本来あるべき方法で処理しますが、そのビューは表示されません。

そのビューが表示されない理由を誰か教えていただければ幸いです。

ありがとう、
トニー

コメント

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