I have a problem. I am doing exactly as mentioned here: Progress bar for long running server calls in ASP.Net MVC, but it's not working. There is no exceptions nor errors, the problem is that is not doing anything. Why is not my code reporting the progress to the client side? Another thing is that i don't know why signalR does not generate the script
<script src="~/signalr/hubs"></script>
controller
ProgressHub.SendMessage("Iniciando proceso", 2);
startup class
using Microsoft.Owin;
using Owin;
using Microsoft.AspNet.SignalR;
[assembly: OwinStartup(typeof(DimexCEUI.Startup))]
namespace DimexCEUI
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
//ConfigureAuth(app);
app.MapSignalR();
}
}
}
my hub
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Web;
using Microsoft.AspNet.SignalR;
namespace DimexCEUI
{
public class ProgressHub : Hub
{
public string msg = "Initializing and Preparing...";
public int count = 1;
public static void SendMessage(string msg, int count)
{
var message = "Proceso completado " + msg;
var hubContext = GlobalHost.ConnectionManager.GetHubContext<ProgressHub>();
hubContext.Clients.All.sendMessage(string.Format(message), count);
}
public void GetCountAndMessage()
{
Clients.Caller.sendMessage(string.Format(msg), count);
}
}
}
Client:
function StartInvoicing() {
var progressNotifier = $.connection.progressHub;
// client-side sendMessage function that will be called from the server-side
progressNotifier.client.sendMessage = function (message, count) {
// update progress
UpdateProgress(message, count);
alert(message);
};
// establish the connection to the server and start server-side operation
$.connection.hub.start().done(function () {
// call the method CallLongOperation defined in the Hub
progressNotifier.server.getCountAndMessage();
});
}
// Update the progress bar
function UpdateProgress(message, count) {
var result = $("#progress-data");
result.html(message);
$("#progress-count").html(count);
}
Related
I have some service method which is executing for 3 seconds. I would like to send information to the client when processing starts (HTTPStatus.PROCESSING), and after (3 seconds) when its done(HTTPSTATUS.OK). Now i have this. Can't realize how to improove. It doesn't work correctly
Controller
#RestController
public class MainController {
private ExecutorService nonBlockingService = Executors
.newCachedThreadPool();
#Async
#CrossOrigin
#GetMapping("/sse")
public SseEmitter handleSse() throws IOException, InterruptedException {
SseEmitter emitter = new SseEmitter();
emitter.send(HttpStatus.PROCESSING);
TestService.doSMG();
emitter.send(HttpStatus.OK);
return emitter;
}
}
Service
public class TestService {
public static void doSMG() throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
}
}
Client
<html>
<head>
<script>
var sse = new EventSource('http://localhost:8080/sse');
sse.onmessage = function (evt) {
var el = document.getElementById('sse');
el.appendChild(document.createTextNode(evt.data));
el.appendChild(document.createElement('br'));
};
</script>
</head>
<body>
<p id = "sse">
</p>
</body>
</html>
Your current solution is fully sequential which means it executes everything in order. You should return the SseEmitter as soon as possible and run everything else in a background thread.
To run things in a background thread you want to inject a TaskExecutor or even better an AsyncTaskExecutor so you can submit tasks to it for later execution (as soon there is a thread available for processing).
This would look something like this
#RestController
public class MainController {
private final AsyncTaskExecutor taskExecutor;
public MainController(AsyncTaskExecutor taskExecutor) {
this.tashExecutor=taskExecutor;
}
#CrossOrigin
#GetMapping("/sse")
public SseEmitter handleSse() throws IOException, InterruptedException {
SseEmitter emitter = new SseEmitter();
taskExecutor.submit(() -> {
emitter.send(HttpStatus.PROCESSING);
TestService.doSMG();
emitter.send(HttpStatus.OK);
});
return emitter;
}
}
This will execute the task in the background, while immediatly returning the SseEmitter.
I have a blazor server app, that calls an injected service to call a web socket in javascript to get some data. I want the web socket to return the data to a c# instance callback on the service. The c# callback works fine from the javascript method, but not from the any of the websocket callbacks (e.g. onmessge, onerror, onclose, etc.). No errors, just no data.
I have tried making the instance a global javascript and even a static value in the calling class, but still does not work. I have googled to no end and tried every suggestion, but still no luck.
Here is index page:
#page "/"
#inject BlazorJavaScriptCallback.IService1 _service1
<h1>Hello, world!</h1>
<p>And the answer back from the web service is #Message</p>
#code {
protected string Message {get; set;}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
Message = await _service1.CallWebService();
StateHasChanged();
}
}
}
The service:
using Microsoft.JSInterop;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace BlazorJavaScriptCallback
{
public interface IService1
{
Task<string> CallWebService();
}
public class Service1 : IService1
{
AutoResetEvent _stopWaitHandle = new AutoResetEvent(false);
String _message;
private IJSRuntime _jsruntime;
public Service1(IJSRuntime jsruntime)
{
_jsruntime = jsruntime;
}
public async Task<string> CallWebService()
{
_message = string.Empty;
var dotNetObjRef = DotNetObjectReference.Create(this);
await _jsruntime.InvokeVoidAsync("callWebService", dotNetObjRef, "OutputMessage");
_stopWaitHandle.WaitOne();
return _message;
}
[JSInvokable]
public async Task OutputMessage(string message)
{
_message = message;
Console.WriteLine($"**** OutputMessage {message}");
_stopWaitHandle.Set();
}
}
}
The javascript:
async function callWebService(instance, method) {
instance.invokeMethodAsync(method, "start");
var wsUri = "wss://127.0.0.1:80/Test/";
var websocket = new WebSocket(wsUri);
console.log("websocket has been setup");
websocket.onopen = function (e) {
console.log("websocket connected");
websocket.send("Hellow world")
};
websocket.onclose = function (e) {
instance.invokeMethodAsync(method, "closed");
console.log("websocket closed");
};
websocket.onmessage = function (e) {
console.log("websocket got message" + e.data);
instance.invokeMethodAsync(method, e.data);
websocket.close();
};
websocket.onerror = function (e) {
console.log("websocket error");
instance.invokeMethodAsync(method, "error");
websocket.close();
};
}
You will need to add this to the startup in ConfigureServices:
services.AddScoped<IService1, Service1>();
You will need to add the following to your _Hosts.cshtml:
<script src="~/scripts/Javascript.js"></script>
EDIT: it works when I try this without an injected service (putting the call and callback right in the razor page). Hrmmm.
By using Blazor's [JSInvokable] tag, call the method in .net in the JS WebSocket callback function onMessage(e). can solve the problem
E.g:
JS file
var url = "ws://127.0.0.1:1234";
var websocket;
var connected = false;
/**
* init webSocket
* #param callback
* #param value
* #constructor
*/
function ConnectServer(callback, value) {
if ('WebSocket' in window) {
websocket = new WebSocket(url);
} else if (window.WebSocket) {
websocket = new WebSocket(url);
} else if ('MozWebSocket' in window) {
websocket = new MozWebSocket(url);
} else {
alert("The browser version is too low! Please use Chrome, Firefox, IE10+ browsers!");
}
websocket.onopen = function () {
connected = true;
callback(value);
}
websocket.onclose = function (e) {
connected = false;
}
websocket.onmessage = function (e) {
onMessage(e);
}
websocket.onerror = function (e) {
alert("The websocket server is not connected, please make sure the server is running!")
};
}
function onMessage(e) {
var json = JSON.stringify(e.data);
// call .net method
DotNet.invokeMethodAsync('BlazorServerSample', 'CallBackMethod', json);
}
/**
* A shared method for sending information to the server
* #param jsonStr
*/
function sendMessage(jsonStr) {
connected ? websocket.send(jsonStr) : alert("websocket server is disconnected or not running")
}
function callWebSocket(data){
var json = JSON.stringify(data)
connected ? sendMessage(json ) : ConnectServer(sendMessage, json)
}
BlazorPage
#code{
#inject IJSRuntime JSRuntime
public static string? jsonStr { get; set; }
[JSInvokable]
public static Task<int> produceMessage(string json)
{
jsonStr = json;
return Task.FromResult(1);
}
public async Task CallWebSocket()
{
jsonStr = "";
// mock request data
data = "{'position': 3,'has_more_items': true,'items_html': 'Car'}";
await JSRuntime.InvokeVoidAsync("callWebSocket", data);
bool t = true;
int sec = 0;
do
{
await Task.Delay(1000);
sec++;
if (!string.IsNullOrEmpty(jsonStr) || sec > 10)
{
t= false;
}
} while (t);
// your business
}
}
Hope my answer can help you
I'm following this tutorial : https://www.baeldung.com/websockets-spring
I tested the app and it works perfectly when running on the embedded-tomcat server. However, when I try to deploy and run the same app on an external tomcat server it breaks, because instead of the URL being
localhost:8080/chat
it becomes
myhostIP:port/spring-boot-web-jsp/chat
So I modified the javascript file adding /spring-boot-web-jsp in front of the existing URLs. When I run the webapp the sockets connect successfully and send data. However now my Spring MVC Controller doesn't work.
My javascript :
var stompClient = null;
function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('conversationDiv').style.visibility
= connected ? 'visible' : 'hidden';
document.getElementById('response').innerHTML = '';
}
function connect() {
var socket = new SockJS('/spring-boot-web-jsp-1.0/chat');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/spring-boot-web-jsp-1.0/topic/messages', function(messageOutput) {
showMessageOutput(JSON.parse(messageOutput.body));
});
});
}
function disconnect() {
if(stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendMessage() {
var from = document.getElementById('from').value;
var text = document.getElementById('text').value;
stompClient.send("/spring-boot-web-jsp-1.0/app/chat", {},
JSON.stringify({'from':from, 'text':text}));
}
function showMessageOutput(messageOutput) {
var response = document.getElementById('response');
var p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.appendChild(document.createTextNode(messageOutput.from + ": "
+ messageOutput.text + " (" + messageOutput.time + ")"));
response.appendChild(p);
}
My Controller :
#MessageMapping("/chat")
#SendTo("/topic/messages")
public OutputMessage send(Message message) throws Exception {
String time = new SimpleDateFormat("HH:mm").format(new Date());
return new OutputMessage(message.getFrom(), message.getText(), time);
}
My message broker :
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat");
registry.addEndpoint("/chat").withSockJS();
}
}
I've tried modifying the Controller to :
#MessageMapping("app/chat")
#SendTo("/topic/messages")
public OutputMessage send(Message message) throws Exception {
String time = new SimpleDateFormat("HH:mm").format(new Date());
return new OutputMessage(message.getFrom(), message.getText(), time);
}
#MessageMapping("spring-boot-web-jsp-1.0/app/chat")
#SendTo("spring-boot-web-jsp-1.0/topic/messages")
public OutputMessage send(Message message) throws Exception {
String time = new SimpleDateFormat("HH:mm").format(new Date());
return new OutputMessage(message.getFrom(), message.getText(), time);
}
and a bunch of other variations but none of them work.
How can I modify the Controller and javascript file to work when testing through external Apache Tomcat as well as embedded (setting a relative URL of some sort)? And how can I get this to work properly on the external Tomcat?
Remove your tomcat/webapps/ROOT directory
Rename your final jar/war/ear file to ROOT.jar/war/ear
Deploy it on tomcat
Tomcat will deploy your app under root directory localhost:8080/chat
My scenario is this:
Server application A saves a assigned task to a database.The change in
database is monitored by a change notification and there is a server hub running.
Client side Javascript code,running along with server application B, has to connect to the remote hub. so that it receives a notification whenever App A inserts into the DB.
Here is my server code
I had downloaded owin.cors package.
[assembly: OwinStartup(typeof(Global))]
namespace Demo
{
public class Global : System.Web.HttpApplication
{
public static void Configuration(IAppBuilder app)
{
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
EnableDetailedErrors=true,
};
map.RunSignalR(hubConfiguration);
});
}
notificationHub.cs
public class NotificationHub : Hub
{
public static Hashtable UserIdLookupTable = new Hashtable(20);
public static Dictionary<string,Job> PendingNotificationTable = new
Dictionary<string,Job>(20);
public void OnChange(string userId,string task,string description,string
duration)
{
if (UserIdLookupTable.ContainsKey(userId))
{
this.Clients.Client(UserIdLookupTable[userId].ToString()).Notify(userId,
task);
UserIdLookupTable.Remove(userId);
if (PendingNotificationTable.ContainsKey(userId))
PendingNotificationTable.Remove(userId);
}
else
PendingNotificationTable.Add(userId, new Job(userId, task,
description, duration));
}
public override Task OnConnected()
{
string name =Context.QueryString["userId"];
registerConnectionId(name);
return base.OnConnected();
}
public void registerConnectionId(string userId)
{
if (UserIdLookupTable.ContainsKey(userId))
UserIdLookupTable[userId] = Context.ConnectionId;
else
UserIdLookupTable.Add(userId, Context.ConnectionId);
if(PendingNotificationTable.ContainsKey(userId))
{
Job j=PendingNotificationTable[userId];
OnChange(j.UserId, j.Description, j.EmployeeName, j.Duration);
}
}
Client Side Code connecting to the remote hub
My script includes
<script src="~/Scripts/jquery-1.6.4.min.js"></script>
<script src="~/Scripts/jquery.signalR-2.2.2.min.js"></script>
<script src="~/Scripts/HubConnection.js"></script>
HubConnection.js
function ConnectToHub()
{
jQuery.support.cors = true;
$.connection.hub.url = "http://myip:56698";
$.connection.hub.qs = { 'UserId' : '35' };
var connection = $.hubConnection();
var hub = connection.createHubProxy('NotificationHub');
hub.on('Notify', function(userName, message) {
console.log(userName + ' ' + message);
});
connection.logging = true;
connection.start().done(function () {
console.log('Now connected, connection ID=' + connection.id);
})
.fail(function (a)
{
console.log('Could not connect'+ a );
});
}
While debugging using chrome, it comes to connection.start and does not go in to success or fail.Just leaves the script.There is no sign that it has connected to the server.
It has to hit OnConnected on server right?
Wondering whether i have missed something.
The above client code is all i did on the client project (apart from installing signalr.client package.) Thats enough right?
I'm learning how to get my C# program communicate with browser.
I'm using TCP in C# and WebSocket in my HTML5 browser.
C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.IO;
namespace ShouldWork
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
TCPServer server = new TCPServer();
System.Threading.Thread obj_thread = new System.Threading.Thread(server.startServer);
obj_thread.Start();
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
}
private void button2_Click(object sender, EventArgs e)
{
TcpClient client = new TcpClient("127.0.0.1", 7000);
NetworkStream ns = client.GetStream();
byte[] data_tosend = createDataPacket(Encoding.UTF8.GetBytes(tx_data_send.Text));
ns.Write(data_tosend, 0, data_tosend.Length);
}
private byte[] createDataPacket(byte[] data)
{
byte[] initialize = new byte[1];
initialize[0] = 2;
byte[] separator = new byte[1];
separator[0] = 4;
byte[] datalength = Encoding.UTF8.GetBytes(Convert.ToString(data.Length));
MemoryStream ms = new MemoryStream();
ms.Write(initialize, 0, initialize.Length);
ms.Write(datalength, 0, datalength.Length);
ms.Write(separator, 0, separator.Length);
ms.Write(data, 0, data.Length);
return ms.ToArray();
}
}
class TCPServer
{
TcpListener listener;
public TCPServer()
{
listener = new TcpListener(IPAddress.Any, 7000);
}
public void startServer()
{
listener.Start();
while (true)
{
TcpClient client = listener.AcceptTcpClient();
NetworkStream ns = client.GetStream();
if(ns.ReadByte() == 2)
{
byte[] recv_data = readStream(ns);
Form1.ActiveForm.Invoke(new MethodInvoker(delegate
{
((TextBox)Form1.ActiveForm.Controls.Find("tx_recv_data", true)[0]).Text = Encoding.UTF8.GetString(recv_data);
}));
}
}
}
public byte[] readStream(NetworkStream ns)
{
byte[] data_buff = null;
int b = 0;
String buff_length = "";
while ((b = ns.ReadByte()) != 4)
{
buff_length += (char)b;
}
int data_length = Convert.ToInt32(buff_length);
data_buff = new byte[data_length];
int byte_read = 0;
int byte_offset = 0;
while (byte_offset < data_length)
{
byte_read = ns.Read(data_buff, byte_offset, data_length - byte_offset);
byte_offset += byte_read;
}
return data_buff;
}
}
}
UI
When user clicks Start Server button, the program would then start the TCPServer. Then we can enter some text in the second textbox and my code will then process it and display the message on the first textbox.
Now I'd like to move the client part to the browser, so I created a simple web app and test out connection.
Code snippet
<!DOCTYPE html>
<html>
<head>
<title>Socket demo</title>
</head>
<body>
<button type="button" onclick="testWebSocket()">Connect to C# server</button><br><br>
<script language="javascript" type="text/javascript">
function testWebSocket(){
var socket = new WebSocket('ws://127.0.0.1:7000');
console.log("ssss");
socket.onopen = function(){
console.log("on open");
socket.send("Hello");
}
socket.onclose = function(evt){
console.log("on close");
}
socket.onerror = function(evt){
console.log("on error");
console.log(evt.data);
}
}
</script>
</body>
</html>
Here is what's wrong..... the onopen function doesn't seems to work at all, chrome console does display "sss" and that's it....
Is there something wrong with the approach I'm using?
As indicated in the comments, WebSocket uses its own framing schema.
Take a look to this link: https://hpbn.co/websocket/, specially the part where the framing is explained: https://hpbn.co/websocket/#binary-framing-layer
The reason why the connection never gets open, is because it is unable of completing the handshake : https://hpbn.co/websocket/#http-upgrade-negotiation