I have created a sample signalR for POC.
I want to call a hub method from Global.asax and pass a string value to client.
My Message hub is :-
[HubName("messageHub")]
public class MessageHub : Hub
{
public static IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MessageHub>();
public void Message()
{
/*
* services and updates the messages
* property on the PushMessage hub
*/
//IHubContext context = GlobalHost.ConnectionManager.GetHubContext<SignalR_Error_Logging.Models.ErrorModel>();
List<GenerateError.Repository.ErrorModel> model = ErrorRepository.GetError();
context.Clients.pushMessages(model[0].ErrorMessage);
}
I have defined two of the scripts in layout.cshtml
<script type="text/javascript" src="../../Scripts/jquery-1.6.4.js"></script>
<script type="text/javascript" src="../../Scripts/jquery.signalR-0.5.3.js"></script>
My Index.html is as below:-
#{
ViewBag.Title = "Receive Error message";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<script src="/signalr/hubs" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
var myHub = $.connection.messageHub;
myHub.pushMessages = function (value) {
console.log('Server called addMessage(' + value + ')');
$("#messages").append("<li>" + value + "</li>");
};
$("#btnMessage").click(function () {
myHub.message();
});
$.connection.hub.start().done(function () { alert("Now connected!"); }).fail(function () { alert("Could not Connect!"); });
});
</script>
<h2>Receive Error Messages</h2>
<ul id="messages"></ul>
<input type="button" id="btnMessage" value="Get Error" />
In Global.asax
I have written
SignalR_Error_Logging.SignalRHub.MessageHub hub = new SignalRHub.MessageHub();
hub.Message();
In Application_Start();
I am not able to display message in my UI(i.e Index.cshtml).
Things that i have tried:-
Running the application as IIS.
Changing the way of creating HubContext.
IHubContext _context = GlobalHost.ConnectionManager.GetHubContext<MessageHub>();
context.Clients.notify("Hello world");
if (Clients != null)
{
Clients.shootErrorMessage(message);
this.Clients.shootErrorMessage(message);
}
Gone thru links of StackoverflowCalling SignalR hub clients from elsewhere in system
Any advice???
When i call my hub method by creating a button in Index.html it works fine.
Apologies for not framing my question properly!!
I figured out, way of calling the Hub was not correct.
For the time being i have modified my code as in Global.asax :-
private void CallSignalR()
{
var context = SignalR.GlobalHost.ConnectionManager.GetHubContext<SignalR_Error_Logging.SignalRHub.MessageHub>();
List<GenerateError.Repository.ErrorModel> err = GenerateError.Repository.ErrorRepository.GetError();
foreach (var item in err)
{
item.ErrorDescription = item.ErrorDescription + DateTime.Now.ToString();
}
context.Clients.pushMessages(err);
}
Works absolutely fine now :)
Still figuring out better alternatives !!!!
This will never work. Application_Start() in Global.asax is only called once for the lifetime of your AppDomain. It happens when the website starts up and at this point in time, no clients are connected yet (as the website isn't fully initialized) so you can't send messages via SignalR.
Related
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.
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:
I seem to have an issue with SignalR's JS Client Hub.
The problem is that the 'on' handler does not seem to work - it generates no error but doesn't receive any signals sent by the server.
The code below shows an extract where I call the server (using the invoke) which works fine - then on the server I call back to acceptHubData which should be picked up on the client but isn't.
My objective is when navigating to pages that each page will open a connection to a specific hub and releases this connection when the user moves to another page!!
EDIT: using the following code snippet works but I wonder why the code further below using the 'on' event doesn't work!
var superHub = $.connection.mySuperHub;
superHub.client.acceptHubData = function (data) {
$('<li>hello there' + data + '</li>').prependTo($('#ul1'))
}
$.connection.hub.start().done(function () {
$('<li>done phase 1</li>').prependTo($('#ul1'))
});
Any help would be much appreciated!
This is the client code (in js)
$(document).ready(function () {
var myHub;
try {
var connection = $.hubConnection();
connection.start().done(function () {
myHub = connection.createHubProxy("mySuperHub");
myHub.on('acceptHubData', function (data) {
alert(data); // THIS IS NOT CALLED!
});
myHub.invoke('AcceptSignal', "hello from the client2");
});
}
catch (e) {
alert(e.message);
}
});
This is the Server code:
[HubName("mySuperHub")]
public class MyHub : Hub
{
private readonly HubEngine _hubEngine;
public MyHub() : this(HubEngine.Instance) { }
public MyHub(HubEngine hubEngine)
{
_hubEngine = hubEngine;
}
public void AcceptSignal(string msg)
{
Clients.Caller.acceptHubData("hi");
Clients.All.acceptHubData("hi");
}
}
You can still use the on method to add events for JS client hub method calls in the latest version of SignalR, but if you do not add any event listeners to a hubProxy before calling hubConnection.start(), you will not be subscribed to the hub. SignalR subscribes to the hubs you have event handlers for when the hubConnection starts. If you are not subscribed to your hub, adding any events to that hub after start() won't work.
If you add at least one event listener to the hub before start(), even if it doesn't do anything, you can then add any additional event handlers you want to the hub using on after start() and your handlers will be called.
It doesn't matter if you add an event using hubProxy.on('eventName', function (... or autogeneratedHubProxy.client.eventName = function (... before you call start(), but only on will successfully add event listeners after start() is called.
Not sure which version of SignalR you are using, but I have had more success using the following syntax on my server:
var context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
context.Clients.All.acceptHubData("hello");
and on my clients:
myHub.client.acceptHubData = function (data) {
console.log(data);
}
I'm following "Broadcasting over a Hub from outside of a Hub", but my client browser isn't getting any messages. No errors either. It's like signalR doesn't know about my browser when it comes time to pull the hubContext and send messages.
However, my hub DOES act as expected when calling from client to hub.
My hub:
[HubName("myHub")]
public class MyHub : Hub
{
public void SaySomething(string message)
{
Clients.say(message);
}
public void SayHelloWorld()
{
Clients.say("hello world");
}
}
Code from other place and time in server:
var context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
context.Clients.say(message);
And my client script:
<script src="Scripts/jquery.signalR-0.5.3.js" type="text/javascript" > </script>
<script src="signalr/hubs"> </script>
<script type="text/javascript">
var hub = $.connection.myHub;
$.extend(hub, {
Say: function(message) {
alert(message); //this only works when the sayHelloWorld() method is executed
}
});
$.connection.hub.start()
.done(function(){
hub.sayHelloWorld(); //this works
});
</script>
If you are using the latest nuget package (v 1.0.1) try:
$.extend(hub.client, {
say: function(message) {
alert(message); //this only works when the sayHelloWorld() method is executed
}
And:
$.connection.hub.start()
.done(function(){
hub.server.sayHelloWorld(); //this works
});
SignalR defined client and server properties for the hub, I was trying the MoveShape sample from a video and faced the same issue.
Hope it helps,
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)