Hello,
I have an Asp.Net Core 2.1, MVC Web application in Visual Studio 2017 15.7.5.
I have been trying to figure out how to send a single SignalR string text message to an MVC View.cshtml from the controller method that is invoked from the MVC View using this type of link: @Html.RouteLink("Get Products", "GetProducts").
Putting it in a different context, I want to send a message from outside a hub (directly from the server to the client) by injecting an instance of IHubContext into my controller and adding it to the constructor.
I have my application setup this way:
Startup.cs:
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; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
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:
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:
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:
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:
@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")
When I click on the link generated from:
@Html.RouteLink("Get Products", "GetProducts"), it processes everything in "GetProducts" before it displays from the return View("~/Views/API/Products.cshtml", multipleProductModel);, but it never displays any message on the AccessToken.cshtml view screen.
When I use the developer tools in Firefox, the console shows the SignalR Web Socket connection in the console(WebSocket connected to ws://localhost:64370/chvHub?id=QeZ8xRJSFHuzzQIZ72X2dg), but then when I click the link generated from: @Html.RouteLink("Get Products", "GetProducts"), it shows it being disconnected(Connection disconnected.).
Also, when I use the Visual Studio debugger and stop it in the GetProducts method, _hubContext does not have a connection.
For some reason, when control goes to the ProductsController, the SignalR connetion is lost.
Any help that anyone can provide to help in displaying one SignalR message to a view would be gratefully appreciated.
Thanks,
Tony
What I have tried:
I have added this line to the ConfigureServices method in Startup.cs:
services.AddSignalR();
I have added these lines to the Configure method in Startup.cs:
app.UseSignalR(routes =>
{
routes.MapHub<ChvHub>("/chvHub");
});
I have configured my controller to inject an instance of IHubContext this way:
public class ProductsController : Controller
{
private readonly ChinavasionAPIContext _context;
private readonly IHubContext<ChvHub> _hubContext;
public ProductsController(ChinavasionAPIContext context, IHubContext<ChvHub> hubcontext)
{
_context = context;
_hubContext = hubcontext;
}
Added this line in controller to send the message to the client:
await this._hubContext.Clients.All.SendAsync("ReceiveMessage", "NewMessage", "AnotherNewMessage");
In the Nuget Package Manager Console I ran these commands:
npm init -y
npm install @aspnet/signalr
I installed through Manage Nuget Packages:
Microsoft.AspNetCore.SignalR Latest Stable 1.0.2.