SignalR invoke method doesn't work on throttled connections - c#

I have a .net core backend with SignalR and a react frontend. I have a basic hub set up with ConcurrentDictionary to manage connection ids:
namespace backend.Hubs
{
public class OrderHub : Hub, IOrderHub
{
private readonly IHubContext<OrderHub> hubContext;
public static ConcurrentDictionary<string, List<string>> ConnectedUsers = new ConcurrentDictionary<string, List<string>>();
public OrderHub(IHubContext<OrderHub> hubContext)
{
this.hubContext = hubContext;
}
public async Task SendMessage(OrderResponseDto order, string id)
{
List<string> connectedUser = null;
ConnectedUsers.TryGetValue(id, out connectedUser);
await hubContext.Clients.Clients(connectedUser).SendAsync("neworder",order);
}
public void AddMapping(string id)
{
List<string> existingUserConnectionIds;
ConnectedUsers.TryGetValue(id, out existingUserConnectionIds);
if (existingUserConnectionIds == null)
{
existingUserConnectionIds = new List<string>();
}
existingUserConnectionIds.Add(Context.ConnectionId);
ConnectedUsers.TryAdd(id, existingUserConnectionIds);
}
public override Task OnDisconnectedAsync(Exception e)
{
List<string> existingUserConnectionIds = null;
foreach(var val in ConnectedUsers.Values)
{
if (val.Contains(Context.ConnectionId))
{
existingUserConnectionIds = val;
break;
}
}
if (existingUserConnectionIds != null)
{
existingUserConnectionIds.Remove(Context.ConnectionId);
}
var keys = ConnectedUsers.Where(en => en.Value.Count == 0).Select(k => k.Key);
foreach(var key in keys)
{
List<string> garb = null;
ConnectedUsers.TryRemove(key, out garb);
}
return base.OnDisconnectedAsync(e);
}
}
}
On the frontend, I establish the connection and call the AddMapping method to save the client connection id to the concurrent dictionary. All works fine when no throttling is enabled in the developer console in the frontend. However, if I change the throttling to slow or fast 3g I encounter a weird problem. The connection is established as usual and I invoke the method. From debugging in .net core I see that the method is called on the backend however the value isn't saved in the concurrent dictionary and it doesn't return anything. The react client thinks the connection is lost and reconnects. Specifically, I am getting the following errors:
This error only happens when I try to invoke a hub method from the client on throttled connection. On an unthrottled connection, it works fine.
Additional client code :
const newConnection = new HubConnectionBuilder()
.withUrl(env.realtimeBaseUrl+'orderhub', {
// skipNegotiation: true,
// transport: HttpTransportType.WebSockets
})
.withAutomaticReconnect()
.build();
newConnection.start().then(r => {
fetchOrders()
setConnectionStatus(connectingForFirstTime);
newConnection.invoke("AddMapping",business.id.toString()).then(res=>{
console.log(res)
setConnection(newConnection);
setConnectionStatus(connected)
})
newConnection.on("neworder",(order)=>{
// data handling
})
newConnection.onreconnecting(()=>setConnectionStatus(reconnecting))
newConnection.onreconnected(()=>{
newConnection.invoke("AddMapping",business.id.toString()).then(res=>{
console.log(res)
setConnection(newConnection);
setConnectionStatus(connected)
})
});
newConnection.onclose(()=>setConnection(null));
}).catch(err => setConnectionStatus(off));

I just tried it on one of my sites and it seems like the way the dev tools performs the throttling disrupts websocket connections to the point that it doesn't seem to work bi-directionally whether it is on slow3g or fast3g simulation. I can reproduce your error on my otherwise working site. My suspicion is the simulator, not your code.

Related

Signalr Web socket communication in .Net core is broadcasting messages to only few web socket connection

I have the below NotificationUserHub class for Signalr Hub in .Net core Api implemented using https://www.zealousweb.com/signalr-to-send-real-time-notifications-with-asp-net-core/
using Microsoft.AspNetCore.SignalR;
public class NotificationUserHub: Hub
{
}
Below is the class where Im trying to send messages to client:
public class SampleClass
{
private readonly IHubContext<NotificationUserHub> _notificationUserHubContext;
public SampleClass(IHubContext<NotificationUserHub> notificationUserHubContext)
{
_notificationUserHubContext = notificationUserHubContext ?? throw new ArgumentNullException(nameof(notificationUserHubContext));
}
public async Task<bool> SendMessageToClient(message)
{
await _notificationUserHubContext.Clients.All.SendAsync("MethodName",message);
return true;
}
}
I have a client which is a angular application is connecting to this web socket
async startSignalrConnection() {
const uname = "string";
const hc = this._hubConnection;
let that = this;
await this._hubConnection
.start()
.catch((err) =>
console.log("Error while establishing connection :("))
return this._hubConnection;
}
getSignalRConnection() {
let token = this.tokenService.getToken();
this._hubConnection = new HubConnectionBuilder()
.withUrl(
'https://localhost:44353/NotificationUserHub?' +'jwt='+ token,
{
skipNegotiation: true,
transport: HttpTransportType.WebSockets
}
)
.build();
this.startSignalrConnection().then(function (res) {
})
this._hubConnection.on(
"MethodName",
(receivedMessage: any) => {
this.messageList.push(receivedMessage);
}
);
The issue is if I open multiple tabs in browser or use multiple computer browsers and connect to same web socket, I see only some tabs are getting the message sent from the _notificationUserHubContext.Clients.All.SendAsync("MethodName",message);
Below is the browser developer tools where the messages are retrieved:

What is the alternative of Signalr for a server side communication with web socket in C# .Net core? [duplicate]

I have the below NotificationUserHub class for Signalr Hub in .Net core Api implemented using https://www.zealousweb.com/signalr-to-send-real-time-notifications-with-asp-net-core/
using Microsoft.AspNetCore.SignalR;
public class NotificationUserHub: Hub
{
}
Below is the class where Im trying to send messages to client:
public class SampleClass
{
private readonly IHubContext<NotificationUserHub> _notificationUserHubContext;
public SampleClass(IHubContext<NotificationUserHub> notificationUserHubContext)
{
_notificationUserHubContext = notificationUserHubContext ?? throw new ArgumentNullException(nameof(notificationUserHubContext));
}
public async Task<bool> SendMessageToClient(message)
{
await _notificationUserHubContext.Clients.All.SendAsync("MethodName",message);
return true;
}
}
I have a client which is a angular application is connecting to this web socket
async startSignalrConnection() {
const uname = "string";
const hc = this._hubConnection;
let that = this;
await this._hubConnection
.start()
.catch((err) =>
console.log("Error while establishing connection :("))
return this._hubConnection;
}
getSignalRConnection() {
let token = this.tokenService.getToken();
this._hubConnection = new HubConnectionBuilder()
.withUrl(
'https://localhost:44353/NotificationUserHub?' +'jwt='+ token,
{
skipNegotiation: true,
transport: HttpTransportType.WebSockets
}
)
.build();
this.startSignalrConnection().then(function (res) {
})
this._hubConnection.on(
"MethodName",
(receivedMessage: any) => {
this.messageList.push(receivedMessage);
}
);
The issue is if I open multiple tabs in browser or use multiple computer browsers and connect to same web socket, I see only some tabs are getting the message sent from the _notificationUserHubContext.Clients.All.SendAsync("MethodName",message);
Below is the browser developer tools where the messages are retrieved:

MassTransit - Can Multiple Consumers All Receive Same Message?

I have one .NET 4.5.2 Service Publishing messages to RabbitMq via MassTransit.
And multiple instances of a .NET Core 2.1 Service Consuming those messages.
At the moment competing instances of the .NET core consumer service steal messages from the others.
i.e. The first one to consume the message takes it off the queue and the rest of the service instances don't get to consume it.
I want ALL instances to consume the same message.
How can I achieve this?
Publisher Service is configured as follows:
builder.Register(context =>
{
MessageCorrelation.UseCorrelationId<MyWrapper>(x => x.CorrelationId);
return Bus.Factory.CreateUsingRabbitMq(configurator =>
{
configurator.Host(new Uri("rabbitmq://localhost:5671"), host =>
{
host.Username(***);
host.Password(***);
});
configurator.Message<MyWrapper>(x => { x.SetEntityName("my.exchange"); });
configurator.Publish<MyWrapper>(x =>
{
x.AutoDelete = true;
x.Durable = true;
x.ExchangeType = true;
});
});
})
.As<IBusControl>()
.As<IBus>()
.SingleInstance();
And the .NET Core Consumer Services are configured as follows:
serviceCollection.AddScoped<MyWrapperConsumer>();
serviceCollection.AddMassTransit(serviceConfigurator =>
{
serviceConfigurator.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://localhost:5671"), hostConfigurator =>
{
hostConfigurator.Username(***);
hostConfigurator.Password(***);
});
cfg.ReceiveEndpoint(host, "my.exchange", exchangeConfigurator =>
{
exchangeConfigurator.AutoDelete = true;
exchangeConfigurator.Durable = true;
exchangeConfigurator.ExchangeType = "topic";
exchangeConfigurator.Consumer<MyWrapperConsumer>(provider);
});
}));
});
serviceCollection.AddSingleton<IHostedService, BusService>();
And then MyWrapperConsumer looks like this:
public class MyWrapperConsumer :
IConsumer<MyWrapper>
{
.
.
public MyWrapperConsumer(...) => (..) = (..);
public async Task Consume(ConsumeContext<MyWrapper> context)
{
//Do Stuff
}
}
It sounds like you want to publish messages and have multiple consumer service instances receive them. In that case, each service instance needs to have its own queue. That way, every published message will result in a copy being delivered to each queue. Then, each receive endpoint will read that message from its own queue and consume it.
All that excessive configuration you're doing is going against what you want. To make it work, remove all that exchange type configuration, and just configure each service instance with a unique queue name (you can generate it from host, machine, whatever) and just call Publish on the message producer.
You can see how RabbitMQ topology is configured: https://masstransit-project.com/advanced/topology/rabbitmq.html
Thanks to the Answer from Chris Patterson and the comment from Alexey Zimarev I now believe I have this working.
The guys pointed out (from my understanding, correct me if I am wrong) that I should get rid of specifying the Exchanges and Queues etc myself and stop being so granular with my configuration.
And let MassTransit do the work in knowing which exchange to create & publish to, and which queues to create and bind to that exchange based on my type MyWrapper. And my IConsumerimplementation type MyWrapperConsumer.
Then giving each consumer service its own unique ReceiveEndpoint name we will end up with the exchange fanning out messages of type MyWrapper to each unique queue which gets created by the unique names specified.
So, in my case..
THE PUBLISHER SERVICE config relevant lines of code changed FROM:
configurator.Message<MyWrapper>(x => { x.SetEntityName("my.exchange"); });
configurator.Publish<MyWrapper>(x =>
{
x.AutoDelete = true;
x.Durable = true;
x.ExchangeType = true;
});
TO THIS
configurator.Message<MyWrapper>(x => { });
configurator.AutoDelete = true;
AND EACH CONSUMERS SERVICE instance config relevant lines of code changed FROM:
cfg.ReceiveEndpoint(host, "my.exchange", exchangeConfigurator =>
{
exchangeConfigurator.AutoDelete = true;
exchangeConfigurator.Durable = true;
exchangeConfigurator.ExchangeType = "topic";
exchangeConfigurator.Consumer<MyWrapperConsumer>(provider);
});
TO THIS:
cfg.ReceiveEndpoint(host, Environment.MachineName, queueConfigurator =>
{
queueConfigurator.AutoDelete = true;
queueConfigurator.Consumer<MyWrapperConsumer>(provider);
});
Note, the Environment.MachineName gives the unique queue name for each instance
We can achieve it by having separate queue for each consumer services and each queue bind with a single exchange. When we publish message to exchange it will send copy of message to each queue and eventually received by each consumer services.
Messages :
namespace Masstransit.Message
{
public interface ICustomerRegistered
{
Guid Id { get; }
DateTime RegisteredUtc { get; }
string Name { get; }
string Address { get; }
}
}
namespace Masstransit.Message
{
public interface IRegisterCustomer
{
Guid Id { get; }
DateTime RegisteredUtc { get; }
string Name { get; }
string Address { get; }
}
}
Publisher Console App :
namespace Masstransit.Publisher
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("CUSTOMER REGISTRATION COMMAND PUBLISHER");
Console.Title = "Publisher window";
RunMassTransitPublisher();
}
private static void RunMassTransitPublisher()
{
string rabbitMqAddress = "rabbitmq://localhost:5672";
string rabbitMqQueue = "mycompany.domains.queues";
Uri rabbitMqRootUri = new Uri(rabbitMqAddress);
IBusControl rabbitBusControl = Bus.Factory.CreateUsingRabbitMq(rabbit =>
{
rabbit.Host(rabbitMqRootUri, settings =>
{
settings.Password("guest");
settings.Username("guest");
});
});
Task<ISendEndpoint> sendEndpointTask = rabbitBusControl.GetSendEndpoint(new Uri(string.Concat(rabbitMqAddress, "/", rabbitMqQueue)));
ISendEndpoint sendEndpoint = sendEndpointTask.Result;
Task sendTask = sendEndpoint.Send<IRegisterCustomer>(new
{
Address = "New Street",
Id = Guid.NewGuid(),
RegisteredUtc = DateTime.UtcNow,
Name = "Nice people LTD"
}, c =>
{
c.FaultAddress = new Uri("rabbitmq://localhost:5672/accounting/mycompany.queues.errors.newcustomers");
});
Console.ReadKey();
}
}
}
Receiver Management console app :
namespace Masstransit.Receiver.Management
{
class Program
{
static void Main(string[] args)
{
Console.Title = "Management consumer";
Console.WriteLine("MANAGEMENT");
RunMassTransitReceiver();
}
private static void RunMassTransitReceiver()
{
IBusControl rabbitBusControl = Bus.Factory.CreateUsingRabbitMq(rabbit =>
{
rabbit.Host(new Uri("rabbitmq://localhost:5672"), settings =>
{
settings.Password("guest");
settings.Username("guest");
});
rabbit.ReceiveEndpoint("mycompany.domains.queues.events.mgmt", conf =>
{
conf.Consumer<CustomerRegisteredConsumerMgmt>();
});
});
rabbitBusControl.Start();
Console.ReadKey();
rabbitBusControl.Stop();
}
}
}
Receiver Sales Console app:
namespace Masstransit.Receiver.Sales
{
class Program
{
static void Main(string[] args)
{
Console.Title = "Sales consumer";
Console.WriteLine("SALES");
RunMassTransitReceiver();
}
private static void RunMassTransitReceiver()
{
IBusControl rabbitBusControl = Bus.Factory.CreateUsingRabbitMq(rabbit =>
{
rabbit.Host(new Uri("rabbitmq://localhost:5672"), settings =>
{
settings.Password("guest");
settings.Username("guest");
});
rabbit.ReceiveEndpoint("mycompany.domains.queues.events.sales", conf =>
{
conf.Consumer<CustomerRegisteredConsumerSls>();
});
});
rabbitBusControl.Start();
Console.ReadKey();
rabbitBusControl.Stop();
}
}
}
You can find a working solution on https://github.com/prasantj409/Masstransit-PublishMultipleConsumer.git
By default, RabbitMQ sends each message to all the consumers in sequence. This type of dispatching is called "round-robin" and made for load balancing (you can have multiple instances of your service consuming the same message).
As Chris pointed, to ensure that your service always receives its copy of a message, you need to provide the unique Queue name.
What you need to do:
Make sure that your consumers implements IConsumer interface with the same generic type
Register all this consumers
Use Publish method to send message
Generally there are two types of messages in MassTransit: Events and Commands, and in this case your message is Event. In the case when your message is a Command, only one consumer receives message and you need to use Send method.
Example of Event DTO:
public class OrderChecked
{
public Guid OrderId { get; set; }
}
Consumers:
public class OrderSuccessfullyCheckedConsumer : IConsumer<OrderChecked>
{
public async Task Consume(ConsumeContext<OrderChecked> context)
{
// some your consuming code
}
}
public class OrderSuccessfullyCheckedConsumer2 : IConsumer<OrderChecked>
{
public async Task Consume(ConsumeContext<OrderChecked> context)
{
// some your second consuming code
}
}
Configuring:
services.AddMassTransit(c =>
{
c.AddConsumer<OrderSuccessfullyCheckedConsumer>();
c.AddConsumer<OrderSuccessfullyCheckedConsumer2>();
c.SetKebabCaseEndpointNameFormatter();
c.UsingRabbitMq((context, cfg) =>
{
cfg.ConfigureEndpoints(context);
});
});
services.AddMassTransitHostedService(true);
Publishing the message:
var endpoint = await _bus.GetPublishSendEndpoint<OrderChecked>();
await endpoint.Send(new OrderChecked
{
OrderId = newOrder.Id
});
I want to share a slightly different code example.
instanceId:
Specifies an identifier that uniquely identifies the endpoint
instance, which is appended to the end of the endpoint name.
services.AddMassTransit(x => {
x.SetKebabCaseEndpointNameFormatter();
Guid instanceId = Guid.NewGuid();
x.AddConsumer<MyConsumer>()
.Endpoint(c => c.InstanceId = instanceId.ToString());
x.UsingRabbitMq((context, cfg) => {
...
cfg.ConfigureEndpoints(context);
});
});

How to implement "user interactive" dependency injection in ASP MVC

I have a full engine that relies on abstractions based on user interactions. This works great with WPF/Xamarin app, cause I can implements this abstractions with window/form.
I have a little problem for porting this engine into ASP MVC.
A simple example can be show as this.
Abstraction interface (simplified)
public interface IQuestionBox
{
Task<bool> ShowYesNoQuestionBox(string message);
}
For WPF, it's really simple, I implement this interface as return the result of a window by calling ShowDialog().
In a simple business class, I can have this kind of calls (simplified) :
public async Task<string> GetValue(IQuestionBox qbox)
{
if(await qbox.ShowYesNoQuestionBox("Question ?"))
{
return "Ok";
}
return "NOk";
}
I really don't see how can I implement this kind of behavior in ASP, due to stateless of HTTP, knowing that this kind of call can be as various as domain/business need. The way I think it should be done is by returning a PartialView to inject into popup, but I don't see how to do this without breaking all the process ...
Anyone has ever done this ?
as I've said, I strongly doesn't recommend this pratice, but its possible, bellow the code that allows to do it, let's go:
To become it's possible I abused the use from TaskCompletionSource, this class allow us to set manually result in a task.
First we need to create a structure to encapsulate the process:
public class Process
{
// this dictionary store the current process running status, you will use it to define the future answer from the user interaction
private static Dictionary<string, Answare> StatusReport = new Dictionary<string, Answare>();
// this property is the secret to allow us wait for the ShowYesNoQuestion call, because til this happen the server doesn't send a response for the client.
TaskCompletionSource<bool> AwaitableResult { get; } = new TaskCompletionSource<bool>(true);
// here we have the question to interact with the user
IQuestionBox QuestionBox { get; set; }
// this method, receive your bussiness logical the receive your question as a parameter
public IQuestionBox Run(Action<IQuestionBox> action)
{
QuestionBox = new QuestionBox(this);
// here we create a task to execute your bussiness logical processment
Task.Factory.StartNew(() =>
{
action(QuestionBox);
});
// and as I said we wait the result from the processment
Task.WaitAll(AwaitableResult.Task);
// and return the question box to show the messages for the users
return QuestionBox;
}
// this method is responsable to register a question to receive future answers, as you can see, we are using our static dictionary to register them
public void RegisterForAnsware(string id)
{
if (StatusReport.ContainsKey(id))
return;
StatusReport.Add(id, new Answare()
{
});
}
// this method will deliver an answer for this correct context based on the id
public Answare GetAnsware(string id)
{
if (!StatusReport.ContainsKey(id))
return Answare.Empty;
return StatusReport[id];
}
// this method Releases the processment
public void Release()
{
AwaitableResult.SetResult(true);
}
// this method end the process delivering the response for the user
public void End(object userResponse)
{
if (!StatusReport.ContainsKey(QuestionBox.Id))
return;
StatusReport[QuestionBox.Id].UserResponse(userResponse);
}
// this method define the answer based on the user interaction, that allows the process continuing from where it left off
public static Task<object> DefineAnsware(string id, bool result)
{
if (!StatusReport.ContainsKey(id))
return Task.FromResult((object)"Success on the operation");
// here I create a taskcompletaionsource to allow get the result of the process, and send for the user, without it would be impossible to do it
TaskCompletionSource<object> completedTask = new TaskCompletionSource<object>();
StatusReport[id] = new Answare(completedTask)
{
HasAnswared = true,
Value = result
};
return completedTask.Task;
}
}
After that the question implementation
public interface IQuestionBox
{
string Id { get; }
Task<bool> ShowYesNoQuestionBox(string question);
HtmlString ShowMessage();
}
class QuestionBox : IQuestionBox
{
Process CurrentProcess { get; set; }
public string Id { get; } = Guid.NewGuid().ToString();
private string Question { get; set; }
public QuestionBox(Process currentProcess)
{
CurrentProcess = currentProcess;
CurrentProcess.RegisterForAnswer(this.Id);
}
public Task<bool> ShowYesNoQuestionBox(string question)
{
Question = question;
CurrentProcess.Release();
return AwaitForAnswer();
}
public HtmlString ShowMessage()
{
HtmlString htm = new HtmlString(
$"<script>showMessage('{Question}', '{Id}');</script>"
);
return htm;
}
private Task<bool> AwaitForAnswer()
{
TaskCompletionSource<bool> awaitableResult = new TaskCompletionSource<bool>(true);
Task.Factory.StartNew(() =>
{
while (true)
{
Thread.Sleep(2000);
var answare = CurrentProcess.GetAnswer(this.Id);
if (!answare.HasAnswered)
continue;
awaitableResult.SetResult(answare.Value);
break;
}
});
return awaitableResult.Task;
}
}
The differences for yours implementaion are:
1 - I create an Identifier to know for who I have to send the aswer, or just to stop the process.
2 - I receive a Process as parameter, because this allows us to call the method
CurrentProcess.Release(); in ShowYesNoQuestion, here in specific, releases the process to send the response responsable to interact with the user.
3 - I create the method AwaitForAnswer, here one more time we use from the TaskCompletionSource class. As you can see in this method we have a loop, this loop is responsable to wait for the user interaction, and til receive a response it doesn't release the process.
4 - I create the method ShowMessage that create a simple html script alert to simulate the user interaction.
Then a simple process class as you should be in your bussiness logical:
public class SaleService
{
public async Task<string> GetValue(IQuestionBox qbox)
{
if (await qbox.ShowYesNoQuestionBox("Do you think Edney is the big guy ?"))
{
return "I knew, Edney is the big guy";
}
return "No I disagree";
}
}
And then the class to represent the user answer
public class Answer
{
// just a sugar to represent empty responses
public static Answer Empty { get; } = new Answer { Value = true, HasAnswered = true };
public Answer()
{
}
// one more time abusing from TaskCompletionSource<object>, because with this guy we are abble to send the result from the process to the user
public Answer(TaskCompletionSource<object> completedTask)
{
CompletedTask = completedTask;
}
private TaskCompletionSource<object> CompletedTask { get; set; }
public bool Value { get; set; }
public bool HasAnswered { get; set; }
// this method as you can see, will set the result and release the task for the user
public void UserResponse(object response)
{
CompletedTask.SetResult(response);
}
}
Now we use all the entire structure create for this:
[HttpPost]
public IActionResult Index(string parametro)
{
// create your process an run it, passing what you want to do
Process process = new Process();
var question = process.Run(async (questionBox) =>
{
// we start the service
SaleService service = new SaleService();
// wait for the result
var result = await service.GetValue(questionBox);
// and close the process with the result from the process
process.End(result);
});
return View(question);
}
// here we have the method that deliver us the user response interaction
[HttpPost]
public async Task<JsonResult> Answer(bool result, string id)
{
// we define the result for an Id on the process
var response = await Process.DefineAnswer(id, result);
// get the response from process.End used bellow
// and return to the user
return Json(response);
}
and in your view
<!-- Use the question as the model page -->
#model InjetandoInteracaoComUsuario.Controllers.IQuestionBox
<form asp-controller="Home" asp-action="Index">
<!-- create a simple form with a simple button to submit the home -->
<input type="submit" name="btnDoSomething" value="All about Edney" />
</form>
<!-- in the scripts section we create the function that we call on the method ShowMessage, remember?-->
<!-- this method request the action answer passing the questionbox id, and the result from a simple confirm -->
<!-- And to finalize, it just show an alert with the process result -->
#section scripts{
<script>
function showMessage(message, id) {
var confirm = window.confirm(message);
$.post("/Home/Answer", { result: confirm, id: id }, function (e) {
alert(e);
})
}
</script>
#Model?.ShowMessage()
}
As I've said, I realy disagree with this pratices, the correct should to write a new dll, to support the web enviroment, but I hope it help you.
I put the project on github to you can download an understand all the solution
I realy hope it can help you
You can create a web socket connection from client side to server side. And work with front-end content with web socket request. It could be implemented as following:
Client side:
$app = {
uiEventsSocket : null,
initUIEventsConnection : function(url) {
//create a web socket connection
if (typeof (WebSocket) !== 'undefined') {
this.uiEventsSocket = new WebSocket(url);
} else if (typeof (MozWebSocket) !== 'undefined') {
this.uiEventsSocket = new MozWebSocket(url);
} else {
console.error('WebSockets unavailable.');
}
//notify if there is an web socket error
this.uiEventsSocket.onerror = function () {
console.error('WebSocket raised error.');
}
this.uiEventsSocket.onopen = function () {
console.log("Connection to " + url + " established");
}
//handling message from server side
this.uiEventsSocket.onmessage = function (msg) {
this._handleMessage(msg.data);
};
},
_handleMessage : function(data){
//the message should be in json format
//the next line fails if it is not
var command = JSON.parse(data);
//here is handling the request to show prompt
if (command.CommandType == 'yesNo') {
var message = command.Message;
var result = confirm(message);
//not sure that bool value will be successfully converted
this.uiEventsSocket.send(result ? "true" : "false");
}
}
}
And init it from ready or load event:
window.onload = function() { $app.initUIEventsConnection(yourUrl); }
Note that you url should begin with ws:// instead of http:// and wss:// instead of https:// (Web Sockets and Web Sockets Secure).
Server side.
Here is a good article for how to setup web sockets at asp.net core application or you could find another one. Note that you should group web socket connections from single user and if you want to send a message to the concrete user, you should send message for every connection from this user.
Every web socket you should accept with AcceptWebSocketAsync() method call and then add instance of this web socket to singleton, which contains a set of web sockets connection groupped by user.
The following class will be used to operate commands:
public class UICommand
{
public string CommandType { get; set; }
public string Message { get; set; }
public Type ReturnType { get; set; }
}
And a full code of singleton for handling sockets
public class WebSocketsSingleton
{
private static WebSocketsSingleton _instance = null;
//here stored web sockets groupped by user
//you could use user Id or another marker to exactly determine the user
private Dictionary<string, List<WebSocket>> _connectedSockets;
//for a thread-safety usage
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
public static WebSocketsSingleton Instance {
get {
if (this._instance == null)
{
this._instance = new WebSocketsSingleton();
}
return this._instance;
}
}
private WebSocketsSingleton()
{
this._connectedSockets = new Dictionary<string, List<WebSocket>>();
}
/// <summary>
/// Adds a socket into the required collection
/// </summary>
public void AddSocket(string userName, WebSocket ws)
{
if (!this._connectedSockets.ContainsKey(userName))
{
Locker.EnterWriteLock();
try
{
this._connectedSockets.Add(userName, new List<WebSocket>());
}
finally
{
Locker.ExitWriteLock();
}
}
Locker.EnterWriteLock();
try
{
this._connectedSockets[userName].Add(ws);
}
finally
{
Locker.ExitWriteLock();
}
}
/// <summary>
/// Sends a UI command to required user
/// </summary>
public async Task<string> SendAsync(string userName, UICommand command)
{
if (this._connectedSockets.ContainsKey(userName))
{
var sendData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(command));
foreach(var item in this._connectedSockets[userName])
{
try
{
await item.SendAsync(new ArraySegment<byte>(sendData), WebSocketMessageType.Text, true, CancellationToken.None);
}
catch (ObjectDisposedException)
{
//socket removed from front end side
}
}
var buffer = new ArraySegment<byte>(new byte[1024]);
var token = CancellationToken.None;
foreach(var item in this._connectedSockets[userName])
{
await Task.Run(async () => {
var tempResult = await item.ReceiveAsync(buffer, token);
//result received
token = new CancellationToken(true);
});
}
var resultStr = Encoding.Utf8.GetString(buffer.Array);
if (command.ReturnType == typeof(bool))
{
return resultStr.ToLower() == "true";
}
//other methods to convert result into required type
return resultStr;
}
return null;
}
}
Explanation:
on establishing connection from web socket it will be added with
AddSocket method
on sending request to show a message, the required command will be passed into SendAsync method
the command will be serialized to JSON (using Json.Net, however you could serialize in your way) and send to all sockets, related to the required user
after the command sent, application will wait for respond from front end side
the result will be converted to required type and sent back to your IQuestionBox
In the web socket handling your should add some kind of the following code:
app.Use(async (http, next) =>
{
if (http.WebSockets.IsWebSocketRequest)
{
var webSocket = await http.WebSockets.AcceptWebSocketAsync();
var userName = HttpContext.Current.User.Identity.Name;
WebSocketsSingleton.Instance.AddSocket(userName, webSocket);
while(webSocket.State == WebSocketState.Open)
{
//waiting till it is not closed
}
//removing this web socket from the collection
}
});
And your method implementation of ShowYesNoQuestionBox should be kind of following:
public async Task<bool> ShowYesNoQuestionBox(string userName, string text)
{
var command = new UICommand
{
CommandType = "yesNo",
Message = text,
ReturnType = typeof(bool)
};
return await WebSocketsSingleton.Instance.SendAsync(string userName, command);
}
Note that there should be added userName to prevent sending the same message to all of the connected users.
WebSocket should create the persistent connection between server and client sides, so you could simply send commands in two ways.
I am kindly new to Asp.Net Core, so the final implementation could be a bit different from this.
It's actually much the same, except your UI is sort of disconnected and proxied with the HTTP protocol for the most part.
you essentially need to build the same code as your WPF code but then in the browser construct ajax calls in to the controller actions to apply your logic.
To clarify ...
so lets say you are building up a process over a series of questions that based on the users answer you put different steps in to the process.
You can either ...
build the process in the database
build it in session on the server
build it on the client as a js object
then do a post for execution ofthe constructed process.
think of the "statelessness" as a series of short interactions, but the state you keep between them can be done either on the client, in a db or in the users logged in session on the web server.
In your controller you can add an ActionResult that will give you the html response to your jquery modal popup request. Here is an example
public class MController : Controller {
public ActionResult doWork(requirement IQuestionBox)
{
// model is already modelBound/IOC resolved
return PartialView("_doWork", requirement );
}
}
//scripts
$(function(){
$.ajax({
url:"/m/doWork",
type:"get",
success:function(data){
$modal.html(data); // bind to modal
}
});
});
Apologies for not fully understanding the question.
hope this helps!

SignalR (v.1.1.1) Call from Server

I am using latest SignalR (v:1.1.1), and trying to simple call the Hub method periodically, every 3 seconds. I have seen many questions here and have duplicated the way, but GetHubContext method doesn't seem to be returning the correct instance of the class, so I can't call the methods of that class. You can duplicate the case with the following steps:
MyHub.cs:
public class MyHub : Hub
{
public void SendMessage(string message)
{
Clients.All.triggerMessage(message);
}
}
Global.asax:
Task.Factory.StartNew(() =>
{
while (true)
{
var myHub = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
myHub.Clients.All.SendMessage("Hello World");
System.Threading.Thread.Sleep(3000);
}
})
.ContinueWith(t => { throw new Exception("The task threw an exception", t.Exception); }, TaskContinuationOptions.OnlyOnFaulted);
I think this is as simple as it gets. I think this is correct way of doing it, but the debugger never hits SendMessage method. Does anyone know am I missing something very obvious? I am just trying to schedule a call to the SignalR client from the server for every 3 seconds.
On your Global.asax file, when you call 'myHub.Clients.All.SendMessage("Hello World")' it sends a message to the client, it does not call the SendMessage method in your class MyHub.
Please read SignalR Documentation to see some samples
I ended changing the way the hub was created:
MyHostHub.cs
private readonly MyHost _host;
public MyHostHub(){ _host = new MyHost(); }
MyHost:
private readonly static Lazy<IHubConnectionContext> _clients = new Lazy<IHubConnectionContext>(() => GlobalHost.ConnectionManager.GetHubContext<MyHostHub>().Clients);
private IHubConnectionContext Clients
{
get { return _clients.Value; }
}
public void SendMessage(string message)
{
Clients.All.triggerMessage(message);
}
My Global.asax:
Task.Factory.StartNew(() =>
{
while (true)
{
var myHost = ObjectFactory.GetInstance<MyHost>();
myHost.SendMessage();
Thread.Sleep(3000);
}
})
.ContinueWith(t => { throw new Exception("The task threw an exception", t.Exception); }, TaskContinuationOptions.OnlyOnFaulted);
This seems to be working just fine. Basically I moved out the code from the Hub class to another class that I can call in Global.asax but my hub has a reference for host.

Categories

Resources