Sending Signalr message from hub to signal has no effect - asp.net

I have a asp.net net core application, where I want to send a message from the hub to the client (the converse is already working for me).
So I have this controller action, where I am injecting my Hub into:
public IActionResult to()
{
_hub.Clients.All.SendAsync("ReceiveMessage", "user", "message");
return View("~/Views/msg/ClientReceiver.cshtml");
}
So, it's basically just sending a message, and returning a view.
here's the view:
<!DOCTYPE html>
<html>
<body>
<button onclick="receive()">Receive msg from server</button>
<script src="~/lib/signalr/signalr.js"></script>
<script src="~/js/ClientReceiver.js"></script>
</body>
</html>
and the ``ClientReceiver.js` file that is being referenced looks like so:
function receive() {
const connection = new signalR.HubConnectionBuilder()
.withUrl("/NotificationHub")
.configureLogging(signalR.LogLevel.Information)
.build();
connection.on("ReceiveMessage", (user, message) => {
alert(message);
});
}
When looking at the documentation (going to the heading "Call client methods from the hub"), then it seems like this should work.
This does'nt work though, no Alert message appears when it should.
The console in my browser indicates that the correct connection has been established though:
[2021-06-24T23:11:48.359Z] Information: Normalizing '/NotificationHub' to 'https://localhost:44385/NotificationHub'.

When you enter the to method to return to your ClientReceiver.cshtml page, your ClientReceiver is not connected yet, so the page cannot receive your message, you should rewrite a method, and visit the method every time you click the button and send message.
You can try to change your ClientReceiver.js like below:
function receive() {
$.ajax({
url: '/home/send',
type: 'get',
});
}
var connection = new signalR.HubConnectionBuilder()
.withUrl("/NotificationHub")
.build();
connection.on("ReceiveMessage", function (user,message) {
alert(message);
});
connection.start();
Controller:
private readonly IHubContext<NotificationHub> _hub;
public HomeController( IHubContext<NotificationHub> hub)
{
_hub = hub;
}
public IActionResult To()
{
return View("~/Views/msg/ClientReceiver.cshtml");
}
public async Task SendAsync()
{
await _hub.Clients.All.SendAsync("ReceiveMessage", "user", "message");
}
Test result:

Related

ASP.NET CORE [Razor Pages] real time webAPP with SignalR and Typescript

i am working with SignalR and real time web development atm.
I am little confused because of JavaScript and TypeScript.
There are two scenarios i am working on:
First scenario with TypeScript:
./clients/index.html looks like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script defer src="/main.509cbdfefce2f16684e8.js"></script></head>
<body>
<h1>Watching connection list</h1>
<p>People watching this page: <span id="viewCounter">0</span></p>
<h1>Passing arguments to the hub with a button</h1>
<p>
<input id="inputFirstName" />
<input id="inputLastName" />
<button id="btnGetFullName">Get Full Name</button>
</p>
</body>
</html>
./clients/index.ts looks like this:
import * as signalR from "#microsoft/signalr";
//COUNTER
var counter = document.getElementById("viewCounter");
//BUTTON - PASSING VALUES FROM WEBSITE TO HUB CONTROLLER
var button = document.getElementById("btnGetFullName");
// create connection
let connection = new signalR.HubConnectionBuilder()
.configureLogging(signalR.LogLevel.Trace)
.withUrl("/hub/view")
.build();
// client event
connection.on("viewCountUpdate", (value: number) => {
counter.innerText = value.toString();
});
//BUTTON - PASSING VALUES FROM WEBSITE TO HUB CONTROLLER
button.addEventListener("click", function (evt) {
var firstName = (document.getElementById("inputFirstName") as HTMLInputElement).value;
var lastName = (document.getElementById("inputLastName") as HTMLInputElement).value;
connection.invoke("getFullName", firstName, lastName).then((name: string) => { alert(name); });
});
// notify server we're watching
function notify() {
connection.send ("notifyWatching");
}
// start the connection
function startSuccess() {
console.log("Connected.");
//COUNTER
notify();
}
function startFail() {
console.log("Connection failed.");
}
connection.start().then(startSuccess, startFail);
and in Program.cs i am using UseStaticFiles and UseDefaultFiles and it looks like this:
using RealTimeApp.Hubs;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddSignalR();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.UseEndpoints(configure => {
configure.MapHub<DevHub>("/hub/view");
});
app.Run();
Now the thing is:
I want to combine Razor Pages with SignalR to make some functions around the project in real time and some not.
Is this possible to combine razor and make calls to my HUB from in example ./Pages/Account/Details (and vice versa)
[from hub to razor page]
[from razor page to hub]
If yes, please leave me instruction how to..
Second scenario is:
working with JS, but i was trying to go with it, but it doesnt work for me.
i want to try typescript because it fits me more.
Best Regards,
####UPDATE
I see my index.ts is generating file "main.~~~~~~.js
Is this possible to use some references like with section?
#####UPDATE
I can invoke my method from Razor Page with button and it looks like this:
Razor.cshtml:
<form method="post" asp-page-handler="Test">
<button>OnClickTest</button>
</form>
Razor.cshtml.cs
public class SiteModel : PageModel
{
private readonly IHubContext<DevHub> _hub;
public SiteModel(IHubContext<DevHub> hub)
{
_hub = hub;
}
public void OnGet()
{
}
public async Task OnPostTest()
{
await _hub.Clients.All.SendAsync("viewCountUpdate", 66);
}
}
If you have any ideas how to go along with it, please leave a comment or even a solution.
I'm not sure if this is what you need but you can define a render section inside _Layout.cshtml
<body>
...
...
...
#RenderSection("Scripts", required: false)
</body>
Then inside your razor page add:
<div id="counter"></div>
#section Scripts {
<script defer src="/main.509cbdfefce2f16684e8.js"></script>
}
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/layout?view=aspnetcore-6.0#sections
Edit:
In your Hub you can define a method that pushes the viewCountUpdate event to the clients.
public class DevHub : Hub
{
public async Task CountUpdate()
=> await Clients.All.SendAsync("viewCountUpdate", 66);
}
Then call it from typescript using
await connection.invoke("CountUpdate");
Now, few hours later i am comming here to tell you how i find out of my problem.
I decide to leave TS and i decide to go along with JS.
This is simple explanation:
My Hub class:
public class MainHub : Hub<IMainHub>
{
public static int ViewCount { get; set; } = 0;
//FROM HUB TO CLIENT
public async override Task OnConnectedAsync()
{
ViewCount++;
await Clients.All.ViewCountUpdate(ViewCount);
await base.OnConnectedAsync();
}
public async override Task OnDisconnectedAsync(Exception exception)
{
ViewCount--;
await Clients.All.ViewCountUpdate(ViewCount);
await base.OnDisconnectedAsync(exception);
}
My Hub Interface:
public interface IMainHub
{
Task ViewCountUpdate(int viewCount);
}
Razor Index.cshtml:
<h1>Watching connection list</h1>
<p>People watching this page: <span id="viewCounter">0</span></p>
#section Scripts{
<script src="~/lib/signalr/signalr.js"></script>
<script src="~/index.js"></script>
}
My index.js
const connection = new signalR.HubConnectionBuilder()
.withUrl("/testhub")
.configureLogging(signalR.LogLevel.Trace)
.withAutomaticReconnect([0, 10, 30, 60, 90, 150])
.build();
async function start() {
try {
await connection.start();
console.log("SignalR Connected.");
} catch (err) {
console.log(err);
setTimeout(start, 5000);
}
};
connection.onclose(async () => {
await start();
});
//FROM HUB TO CLIENT [EVENTS]
connection.on("ViewCountUpdate", (viewCount) => {
counter.innerHTML = viewCount;
});
// Start the connection.
start();
And now i can add reference to js scripts in everysingle page and it works.

Azure Function SignalR Negotiate function works but Send function fails

i have a xamarin app that is trying to talk to use SignalR in Azure functions.
i have 2 azure functions as per the documentation.
public static class NegotiateFunction
{
[FunctionName("negotiate")]
public static SignalRConnectionInfo GetSignalRInfo(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequest req,
[SignalRConnectionInfo(HubName = "chat")] SignalRConnectionInfo connectionInfo)
//, UserId = "{headers.x-ms-client-principal-id}"
{
return connectionInfo;
}
}
and
public static class SendMessageFunction
{
[FunctionName("Send")]
public static Task SendMessage(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]object message,
[SignalR(HubName = "chat")]IAsyncCollector<SignalRMessage> signalRMessages)
{
// var chatObj = (ChatObject)(message);
return signalRMessages.AddAsync(
new SignalRMessage
{
// the message will only be sent to this user ID
// UserId = chatObj.ReciversId,
Target = "Send",
Arguments = new[] { message }
});
}
}
in my xamarin client i am connecting like this.
try
{
_connection = new HubConnectionBuilder()
.WithUrl("http://192.168.1.66:7071/api")
.Build();
_connection.On<string>("Send", (message) =>
{
AppendMessage(message);
});
await _connection.StartAsync();
}
I send message using this code in one of the pages of Xamarin app page.
try
{
await _connection.SendAsync("Send", MessageEntry.Text);
MessageEntry.Text = "";
}
connection code works it hits "negotiate" function properly but when i call SendAsync it does not hit break-point in [FunctionName("Send")] and nothing happens. It doesn't give me any exception as well.
local settings are like this
Update
i also tried Invoke. it didnt worked.
Should i try making a POST call to [FunctionName("Send")] ?
The way SignalR SaaS works in Functions is slightly different to using the NuGet package in a .NET Application.
You can't invoke a function using the SignalR library, as you can see on the attribute in your function, it's expecting a Http trigger so you have to do a POST to this endpoint instead of invoking it as you normally would.
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]
You still want to listen to the Send target as normal.

Can't call javascript client method in signalr

The following code works fine in IIS Express, but failed in IIS10.
The weird thing is serverside method can successfully be invoked, however clientside method can't.
JavaScript
var hub = $.connection.liveRoomHub;
hub.client.addMessageToPage = function(data){
debugger;//here, this method never gets invoked
console.log(JSON.stringify(data));
};
$.connection.hub.start()
.done(function() {
hub.server.join('room1')
.done(function(){
debugger; //code can run into here
hub.server.sendMessage('user','test','room1');
})
});
C#
public class LiveRoomHub : Microsoft.AspNet.SignalR.Hub
{
public ILogger Logger { get; set; }
public async Task SendMessage(string name, string message, string roomName)
{
await Clients.Group(roomName)
.addMessageToPage(new
{
Name = name,
Message = message
});
Logger.Info($"{name}send msg:{message}in room:{roomName},");//logged
}
public async Task Join(string roomName)
{
await Groups.Add(Context.ConnectionId, roomName);
Logger.Info($"{Context.ConnectionId} enter room: {roomName}");//logged
}
}
All right, problem solved.
I'm using aspnetboilerplate, and abp.signalr.js automatically calls the hub connection before my JavaScript code is loaded.
Obviously, at that time, my hub.client.addMessageToPage isn't registered yet.
That's the common Connection started before subscriptions are added error.

Public method not firing in SignalR

I have a simple application, like a chat, integrated with SignalR. I added a new method on my Hub and a new function on client side, like you can see below.
The problem is, my method called SendMessageChat isn't firing, because occurs the following error
TypeError: chat2.server.SendMessageChat is not a function
but the method chat2.server.send works fine, and I don't know why my second method doesn't work. Can someone help me ?
JavaScript
$(function () {
var chat2 = $.connection.redirectTask;
chat2.client.broadcastMessage = function (name, message) {
// Do something here
};
chat2.client.sendMessage = function (name, message) {
// Do something here
};
//$.connection.hub.logging = true;
$.connection.hub.start().done(function () {
/* BUTTON CLICK IN ANOTHER PAGE */
$('#btnFinish').click(function () {
chat2.server.send($.cookie("User"), $("#lista :selected").text());
});
/* CASE HIT ENTER INSIDE THE TEXT FIELD IN CHAT */
$(document).on("keypress", "#txtChat", function (e) {
var code = (e.keyCode ? e.keyCode : e.which);
if (code == 13) {
var message = $(this).val();
$(this).val("");
chat2.server.SendMessageChat($.cookie("User"), message);
}
});
});
});
SERVER SIDE
public class RedirectTask : Hub
{
public void Send(string nome, string message)
{
Clients.All.broadcastMessage(name, message);
}
public void SendMessageChat(string nome, string message)
{
Clients.All.sendMessage(name, message);
}
}
Reference
Need to change to
chat2.server.sendMessageChat($.cookie("User"), message);
Camel-casing of method names in JavaScript clients
By default, JavaScript clients refer to Hub methods by using a camel-cased version of the method name. SignalR automatically makes this change so that JavaScript code can conform to JavaScript conventions.
Server
public void NewContosoChatMessage(string userName, string message)
JavaScript client using generated proxy
contosoChatHubProxy.server.newContosoChatMessage(userName, message);
If you want to specify a different name for clients to use, add the HubMethodName attribute.
Server
[HubMethodName("PascalCaseNewContosoChatMessage")]
public void NewContosoChatMessage(string userName, string message)
JavaScript client using generated proxy
contosoChatHubProxy.server.PascalCaseNewContosoChatMessage(userName, message);

File uploads via Web Api fail on second upload

I am working with Web Api to create a way to upload files via web api. I have found several blog posts on how to accomplish this, and the code is all very similar with a key commonality being the Request.Content.ReadAsMultipartAsync() call. The problem I have is the first upload works fine, but then IIS gets into a faulted state where subsequent uploads fail. The first 32Kb comes in, but then it quits. Debugging shows only a null reference exception that occurs somewhere in the ASP.NET framework.
Here is the ApiController definition I have...
public class FileUploadController : ApiController
{
public void Post()
{
if (Request.Content.IsMimeMultipartContent())
{
var path = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(path);
var task = Request.Content.ReadAsMultipartAsync(provider);
task.ContinueWith(t =>
{
if (t.IsFaulted || t.IsCanceled)
throw new HttpResponseException(HttpStatusCode.InternalServerError);
});
}
else
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
}
}
}
Also, here is the page I am posting from...
<!doctype html>
<head>
<title>File Upload Progress Demo #3</title>
</head>
<body>
<h1>File Upload Progress Demo #3</h1>
<code><input type="file" name="myfile[]"></code><br>
<form action="/api/fileupload" method="post" enctype="multipart/form-data">
<input type="file" name="myfile"><br>
<input type="submit" value="Upload File to Server">
</form>
<div class="progress">
<div class="bar"></div>
<div class="percent">0%</div>
</div>
<div id="status"></div>
</body>
The above code can be downloaded in a default WebApi solution from https://github.com/JohnLivermore/FileUploadTest. Run and navigate to http://localhost:{port}/FormPost.html. The first upload succeeds (uploads to App_Data), but subsequent uploads only upload the first 32 Kb and then fail.
You shouldn't use a void method.
Void and async don't play well together for a number of reasons.
public Task<HttpResponseMessage> Post()
{
var rootUrl = "c:/uploads";
if (Request.Content.IsMimeMultipartContent())
{
var streamProvider = new MultipartFormDataStreamProvider(rootUrl);
var task = Request.Content.ReadAsMultipartAsync(streamProvider).ContinueWith<HttpResponseMessage>(t =>
{
if (t.IsFaulted || t.IsCanceled)
throw new HttpResponseException(HttpStatusCode.InternalServerError);
//do stuff with files if you wish
return new HttpResponseMessage(HttpStatusCode.OK);
});
return task;
}
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
}
The main issue of your code is exiting your method without waiting for all asynchronous tasks to finish. You can use .Wait() for that purpose:
public class FileUploadController : ApiController
{
public void Post()
{
if (Request.Content.IsMimeMultipartContent())
{
var path = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(path);
var readAsMultipartTask = Request.Content.ReadAsMultipartAsync(provider);
var continueWithTask = readAsMultipartTask.ContinueWith(t =>
{
if (t.IsFaulted || t.IsCanceled)
throw new HttpResponseException(HttpStatusCode.InternalServerError);
});
continueWithTask.Wait();
}
else
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
}
}
}
This will make the upload work properly, but you should be aware that your method is breaking HTTP protocol because you are not sending back any response in case of proper upload. Your method should be more like this:
public class FileUploadController : ApiController
{
public async Task<HttpResponseMessage> Post()
{
if (Request.Content.IsMimeMultipartContent())
{
var path = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(path);
await Request.Content.ReadAsMultipartAsync(provider).ContinueWith(t =>
{
if (t.IsFaulted || t.IsCanceled)
throw new HttpResponseException(HttpStatusCode.InternalServerError);
});
//Here you should return a meaningful response
return Request.CreateResponse(HttpStatusCode.OK);
}
else
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
}
}
}
(Thanks to use of async/await the synchronization of asynchronous tasks is handled by framework)

Resources