I have a large table containing items that generally has a few new items inserted every second. I want to build a realtime application where users can subscribe to certain types of items and have a realtime feed whenever an item of a certain type is inserted.
As a start, I used
http://techbrij.com/database-change-notifications-asp-net-signalr-sqldependency but it is missing some crucial pieces that I am not sure how to implement.
What is the best way to track which messages to push to which clients? How can I track which messages have already been sent without the overhead of updating a "pushed" flag every time an update is received?
You can use an open source realization of the SqlDependency class - SqlDependencyEx. It uses a database trigger and native Service Broker notification to receive events about the table changes. The test project for this component does exactly the same functionality what you are looking for. This is an usage example:
int changesReceived = 0;
using (SqlDependencyEx sqlDependency = new SqlDependencyEx(
TEST_CONNECTION_STRING, TEST_DATABASE_NAME, TEST_TABLE_NAME))
{
sqlDependency.TableChanged += (o, e) => changesReceived++;
sqlDependency.Start();
// Make table changes.
MakeTableInsertDeleteChanges(changesCount);
// Wait a little bit to receive all changes.
Thread.Sleep(1000);
}
Assert.AreEqual(changesCount, changesReceived);
With SqlDependecyEx you are able to monitor INSERT, DELETE, UPDATE separately and receive actual changed data (xml) in the event args object. Hope this help.
SignalR provides a Context.ConnectionId in the hub. You can use this as a key in a collection to track which items have been sent.
Here's an example from a logging chat application I wrote:
public class PushNotificationHub : Hub {
public void Send(string name, string message) {
ChatService.ChatUsers[Context.ConnectionId].AddMessage(message);
Clients.All.addMessage(name, message);
}
public override Task OnConnected() {
ChatService.Current.LogMessage($"Client connected: {Context.ConnectionId}");
ChatService.ChatUsers.Add(Context.ConnectionId, new Common.ChatUser(Context.QueryString["name"]));
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled) {
//Application.Current.Dispatcher.Invoke(() => ((MainWindow)Application.Current.MainWindow).LogMessage($"Client disconnected: {Context.ConnectionId}"));
ChatService.Current.LogMessage($"Client disconnected: {Context.ConnectionId}");
ChatService.Current.LogMessage(ChatService.ChatUsers[Context.ConnectionId].PrintLog());
return base.OnDisconnected(stopCalled);
}
}
Related
My client is attempting to send messages to the receiver. However I noticed that the receiver sometimes does not receive all the messages sent by the client thus missing a few messages (not sure where the problem is ? Client or the receiver).
Any suggestions on why that might be happening. This is what I am currently doing
On the receiver side this is what I am doing.
This is the Event Processor
async Task IEventProcessor.ProcessEventsAsync(PartitionContext context, IEnumerable<EventData> messages)
{
foreach (var eventData in messages)
{
var data = Encoding.UTF8.GetString(eventData.Body.Array, eventData.Body.Offset, eventData.Body.Count);
}
}
This is how the client connects to the event hub
var StrBuilder = new EventHubsConnectionStringBuilder(eventHubConnectionString)
{
EntityPath = eventHubName,
};
this.eventHubClient = EventHubClient.CreateFromConnectionString(StrBuilder.ToString());
How do I direct my messages to specific consumers
I'm using this sample code from eventhub official doc, for sending and receiving.
And I have 2 consumer groups: $Default and newcg. Suppose you have 2 clients, the client_1 are using the default consumer group($Default), and client_2 are using the other consumer group(newcg)
First, after create the send client, in the SendMessagesToEventHub method, we need to add a property with value. The value should be the consumer group name. Sample code like below:
private static async Task SendMessagesToEventHub(int numMessagesToSend)
{
for (var i = 0; i < numMessagesToSend; i++)
{
try
{
var message = "444 Message";
Console.WriteLine($"Sending message: {message}");
EventData mydata = new EventData(Encoding.UTF8.GetBytes(message));
//here, we add a property named "cg", it's value is the consumer group. By setting this property, then we can read this message via this specified consumer group.
mydata.Properties.Add("cg", "newcg");
await eventHubClient.SendAsync(mydata);
}
catch (Exception exception)
{
Console.WriteLine($"{DateTime.Now} > Exception: {exception.Message}");
}
await Task.Delay(10);
}
Console.WriteLine($"{numMessagesToSend} messages sent.");
}
Then in the client_1, after create the receiver project, which use the default consumer group($Default)
-> in the SimpleEventProcessor class -> ProcessEventsAsync method, we can filter out the unnecessary event data. Sample code for ProcessEventsAsync method:
public Task ProcessEventsAsync(PartitionContext context, IEnumerable<EventData> messages)
{
foreach (var eventData in messages)
{
//filter the data here
if (eventData.Properties["cg"].ToString() == "$Default")
{
var data = Encoding.UTF8.GetString(eventData.Body.Array, eventData.Body.Offset, eventData.Body.Count);
Console.WriteLine($"Message received. Partition: '{context.PartitionId}', Data: '{data}'");
Console.WriteLine(context.ConsumerGroupName);
}
}
return context.CheckpointAsync();
}
And in another client, like client_2, which use another consumer group, like it's name is newcg, we can follow the steps in client_1, just a little changes in ProcessEventsAsync method, like below:
public Task ProcessEventsAsync(PartitionContext context, IEnumerable<EventData> messages)
{
foreach (var eventData in messages)
{
//filter the data here, using another consumer group name
if (eventData.Properties["cg"].ToString() == "newcg")
{
//other code
}
}
return context.CheckpointAsync();
}
This happens only when there are 2 or more Event Processor Host reading from same consumer group.
If you have event hub with 32 partitions and 2 event processor host reading from same consumer group. Then each event processor host will read from 16 partition and so on.
Similarly if 4 Event processor host parallelly reading from same consumer group then each will read from 8 partitions.
Check if you have 2 or more event processor host running on same consumer group.
I have tested your code and slightly modified it(different overload of EventProcessorHost constructor, and added CheckpointAsync after consuming the messages), and then did some tests.
By using the default implementation and default EventProcessorOptions(EventProcessorOptions.DefaultOptions) I can say that I did experience some latency when it comes to consuming messages, but all messages were processed successfully.
So, sometimes it seems like I am not getting the messages from the certain partition, but after a certain period of time, all messages arrive:
Here you can find the actual modified code that worked for me. It is a simple console app that prints to the console if something arrives.
string processorHostName = Guid.NewGuid().ToString();
var Options = new EventProcessorOptions()
{
MaxBatchSize = 1, //not required to make it working, just for testing
};
Options.SetExceptionHandler((ex) =>
{
System.Diagnostics.Debug.WriteLine($"Exception : {ex}");
});
var eventHubCS = "event hub connection string";
var storageCS = "storage connection string";
var containerName = "test";
var eventHubname = "test2";
EventProcessorHost eventProcessorHost = new EventProcessorHost(eventHubname, "$Default", eventHubCS, storageCS, containerName);
eventProcessorHost.RegisterEventProcessorAsync<MyEventProcessor>(Options).Wait();
For sending the messages to the event hub and testing I used this message publisher app.
I am a beginner in using Signalr and am checking out some examples.
Is it possible to send a message to the client from the server and wait for a return from it? Or is it possible to guarantee that after the answer the same session will be used?
My question is because in a given process, within a transaction, I need to ask the user if he wants to continue with the changes. I have not been able to ask this question before because validations should be done in the same session where changes have been made (but not yet confirmed).
Reiterating the comment from Jaime Yule, WebSockets are bidirectional communication and do not follow the Request/Response architecture for messaging. Given the very fluid nature of communication around WebSockets, these bullet points are good to keep in mind for your current (& future) scenarios:
SignalR is great if you're going to use it for fire & forget (Display a pop-up to a user and that's it)
It's not designed around request-response like you're asking, and trying to use it as such is an anti-pattern
Messages may be sent from either end of the connection at any time,
and there is no native support for one message to indicate it is
related to another
This makes the protocol poorly suited for transactional requirements
It is possible, but i would not recommend (relying on) it.
And it's not a pretty solution (using a static event and being pretty complex for such a simple thing).
Story goes like this:
Make sure client and server know the connectionId - They probably know that already, but i could not figure out a way to access it.
Await NotificationService.ConfirmAsync
... which will call confirm on the client
... which will await the user supplied answer
... and send it back to the server using Callback from The hub.
... which will notify the Callback from the NotificationService over a static event
... which will hand off the message back to ConfirmAsync (using a AutoResetEvent)
... which is hopefully still waiting :)
Client and server both have a 10 second timeout set.
The hub:
// Setup as /notification-hub
public class NotificationHub : Hub {
public string ConnectionId() => Context.ConnectionId;
public static event Action<string, string> Response;
public void Callback(string connectionId, string message) {
Response?.Invoke(connectionId, message);
}
}
Service:
// Wire it up using DI
public class NotificationService {
private readonly IHubContext<NotificationHub> _notificationHubContext;
public NotificationService(IHubContext<NotificationHub> notificationHubContext) {
_notificationHubContext = notificationHubContext;
}
public async Task<string> ConfirmAsync(string connectionId, string text, IEnumerable<string> choices) {
await _notificationHubContext.Clients.Client(connectionId)
.SendAsync("confirm", text, choices);
var are = new AutoResetEvent(false);
string response = null;
void Callback(string connId, string message) {
if (connectionId == connId) {
response = message;
are.Set();
}
}
NotificationHub.Response += Callback;
are.WaitOne(TimeSpan.FromSeconds(10));
NotificationHub.Response -= Callback;
return response;
}
}
Client side js:
var conn = new signalR.HubConnectionBuilder().withUrl("/notification-hub").build();
var connId;
// using Noty v3 (https://ned.im/noty/)
function confirm(text, choices) {
return new Promise(function (resolve, reject) {
var n = new Noty({
text: text,
timeout: 10000,
buttons: choices.map(function (b) {
return Noty.button(b, 'btn', function () {
resolve(b);
n.close();
});
})
});
n.show();
});
}
conn.on('confirm', function(text, choices) {
confirm(text, choices).then(function(choice) {
conn.invoke("Callback", connId, choice);
});
});
conn.start().then(function() {
conn.invoke("ConnectionId").then(function (connectionId) {
connId = connectionId;
// Picked up by a form and posted to the server
document.querySelector(".connection-id-input").value = connectionId;
});
});
For me this is way to complex to put it into the project i am working on.
It really looks like something that will come back and bite you later...
Is it possible to send a message to the client from the server and wait for a return from it? Or is it possible to guarantee that after the answer the same session will be used?
None of this is possible. Currently there's no way to wait for the client's response or even to get to know if the client received the message. There's some discussion implementing this on GitHub. Also here's the feature request.
Until then, the workaround is to send a "notification" from the server with a fire and forget attitude and let the client get the required data via a HTTP request to the server.
This is now possible with .NET 7 using Client Results.
Today, I've highlighted this issue in dotnet's Github page and got a good response from one of the developers of SignalR.
This requires the server to use ISingleClientProxy.InvokeAsync to be able to make request to the client and wait for response.
Quote from the documentation
In addition to making calls to clients, the server can request a
result from a client. This requires the server to use
ISingleClientProxy.InvokeAsync and the client to return a result from
its .On handler.
From the client (js/ts)
hubConnection.on("GetMessage", async () => {
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(new { data: "message" });
}, 100);
});
return promise;
});
From the server (C#)
//By calling Client(...) on an instance of IHubContext<T>
async Task<object> SomeMethod(IHubContext<MyHub> context)
{
string result = await context.Clients.Client(connectionID).InvokeAsync<string>(
"GetMessage");
return result;
}
//---------------------------//
//Or by calling Client(...) or Caller on the Clients property in a Hub method
public class ChatHub : Hub
{
public async Task<string> WaitForMessage(string connectionId)
{
var message = await Clients.Client(connectionId).InvokeAsync<string>(
"GetMessage");
return message;
}
}
Using the following form with Invoke waits for and returns the response directly (just like a "real" synchronous method call)
var persons = hubProxy.Invoke<IEnumerable<Person>>("GetPersonsSynchronous", SearchCriteria, noteFields).Result;
foreach (Person person in persons)
{
Console.WriteLine($"{person.LastName}, {person.FirstName}");
}
I'm trying to replicate behavior like a client browser but in C# (Performance reason). What I'm trying to set out to achieve is that for every new events received, my program should trigger the server side (Hub) which would then notify the client. Rather than having a while loop which would repeatedly hit the hub method every time even if theres no messages, is there a way to treat it as a trigger/detection so that once message is detected then execute Hub method ? Hope this makes sense
Snapshot of Client code below:
IHubProxy _hub;
string url = #"http://localhost:8080/";
var connection = new HubConnection(url);
_hub = connection.CreateHubProxy("PersonHub");
connection.Start().Wait();
//client side method
_hub.On("checkedIn", x => Console.WriteLine(x));
Console.WriteLine("Enter Person Name");
var answer = Console.ReadLine();
while (true) // Better way doing this? trigger or detect new message?
{
//server side method
_hub.Invoke("GetByName", answer).Wait();
}
Snapshot of Hub code below:
[HubName("PersonHub")]
public class PersonHub: Hub
{
public Person GetByName(string name)
{
//logic and etc ...
Clients.All.checkedIn(name);
}
}
By setting the while loop to true this means this will always call the server side method (Hub method) which I dont want to. If theres new events triggered then it should hit the hub method. Is there a way to somehow listen for new message but not to execute if no messages has been detected?
A possible solution is:
string line;
while ((line = Console.ReadLine()) != "exit")
{
_hub.Invoke("GetByName", line).Wait();
}
I have 1 exe which is nothing bit a Windows form which will continuously run in background and will watch my serial port and I have 1 event data receive event which fires as my serial port receive data.
As soon as I receive data in this event I will pass this data to another event handler which saves this data in database through web api method.
But data to my serial port will be coming frequently so I want to save this data to my database independently so that my database insert operation doesn't block my incoming serial port data.
This is my code:
void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)//Fires as my serial port receives data
{
int dataLength = _serialPort.BytesToRead;
byte[] data = new byte[dataLength];
int nbrDataRead = _serialPort.Read(data, 0, dataLength);
if (nbrDataRead == 0)
return;
// Send data to whom ever interested
if (NewSerialDataRecieved != null)
{
NewSerialDataRecieved(this, new SerialDataEventArgs(data)); //pass serial port data to new below event handler.
}
}
void _spManager_NewSerialDataRecieved(object sender, SerialDataEventArgs e) //I want this event handler to run independently so that database save operation doenst block incoming serial port data
{
if (this.InvokeRequired)
{
// Using this.Invoke causes deadlock when closing serial port, and BeginInvoke is good practice anyway.
this.BeginInvoke(new EventHandler<SerialDataEventArgs>(_spManager_NewSerialDataRecieved), new object[] { sender, e });
return;
}
//data is converted to text
string str = Encoding.ASCII.GetString(e.Data);
if (!string.IsNullOrEmpty(str))
{
//This is where i will save data to through my web api method.
RunAsync(str).Wait();
}
}
static async Task RunAsync(string data)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:33396/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = new StringContent(data);
var response = await client.PostAsJsonAsync<StringContent>("api/Service/Post", content);//nothing happens after this line.
}
}
Web api controller:
public class MyController : ApiController
{
[HttpPost]
public HttpResponseMessage Post(HttpRequestMessage request)
{
var someText = request.Content.ReadAsStringAsync().Result;
return new HttpResponseMessage() { Content = new StringContent(someText) };
}
}
But here problem is:
var response = await client.PostAsJsonAsync<StringContent>("api/Service/Post", content);
Nothing happens after this line that is operation blocks on this line.
So can anybody guide me with this?
By independently we determined in the SO C# chat room that you really mean "Asynchronously".
Your solution is the code above, saving this data to a WebAPI endpoint so any solution to the problem needs to be in 2 parts ...
PART 1: The Client Part
On the client all we need to do is make the call asynchronously in order to free up the current thread to carry on receiving data on the incoming serial port, we can do that like so ...
// build the api client, you may want to keep this in a higher scope to avoid recreating on each message
var api = new HttpClient();
api.BaseAddress = new Uri(someConfigVariable);
// asynchronously make the call and handle the result
api.PostAsJsonAsync("api/My", str)
.ContinueWith(t => HandleResponseAsync(t.Result))
.Unwrap();
...
PART 2: The Server Part
Since you have web api i'm also going to assume you are using EF too, the common and "clean" way to do this, with all the extras stripped out (like model validation / error handling) might look something like this ...
// in your EF code you will have something like this ...
Public async Task<User> SaveUser(User userModel)
{
try
{
var newUser = await context.Users.AddAsync(userModel);
context.SavechangesAsync();
return newUser;
}
catch(Exception ex) {}
}
// and in your WebAPI controller something like this ...
HttpPost]
public async Task<HttpResponseMessage> Post(User newUser)
{
return Ok(await SaveUser(newUser));
}
...
Disclaimer:
The concepts involved here go much deeper and as I hinted above, much has been left out here like validation, error checking, ect but this is the core to getting your serial port data in to a database using the technologies I believe you are using.
Key things to read up on for anyone wanting to achieve this kind of thing might include: Tasks, Event Handling, WebAPI, EF, Async operations, streaming.
From what you describe it seems like you might want to have a setup like this:
1) your windows form listens for serial port
2) when new stuff comes to port your windows forms app saves it to some kind of a queue (msmq, for example)
3) you should have separate windows service that checks queue and as it finds new messages in a queue it sends request to web api
Best solution for this problem is to use ConcurrentQueue.
Just do search on google and you will get planty of samples.
ConcurrentQueue is thread safe and it support writing and reading from multiple threads.
So the component listening to the searal port can write data to the queue. And you can have 2 or more tasks running parallel which listening to this queue and update db as soon as it receives data.
Not sure if it's the problem, but you shouldn't block on async code. You are doing RunAsync(str).Wait(); and I believe that's the problem. Have a look at this blog post by Stephen Cleary:
http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
I want to create html5 web multiroom chat, based on HTML5 websocket.
But I need some little help to start.
I want to do server side code in c#, but I can not find any tutorials how to do chat websocket server with multi room in c#.
Is there any server which is already implemented in .net, or which I can update to multi room chat ?
It is a little project, one room for 10 peoples. Could you help to me how to start ?
Thank you very much !
I prepare example code structure:
Main server class:
class Program
{
// List of courses, which are currentli avalible ( REPRESENT CHAT ROOM)
protected static ConcurrentDictionary<Course, string> OnlineUsers = new ConcurrentDictionary<Course, string>();
static void Main(string[] args)
{
// Initialize the server on port 81, accept any IPs, and bind events.
var aServer = new WebSocketServer(81, IPAddress.Any)
{
OnReceive = OnReceive,
OnSend = OnSend,
OnConnected = OnConnect,
OnDisconnect = OnDisconnect,
TimeOut = new TimeSpan(0, 5, 0)
};
aServer.Start();
// Accept commands on the console and keep it alive
var command = string.Empty;
while (command != "exit")
{
command = Console.ReadLine();
}
aServer.Stop();
}
// event when the clients connect to server
// Server send to client list of Lessons which are avalible, after
private static void OnConnect(UserContext context)
{
throw new NotImplementedException();
}
// event whent the client, want to disconnect from server
private static void OnDisconnect(UserContext context)
{
throw new NotImplementedException();
}
// event, when client is sending some data
private static void OnSend(UserContext context)
{
throw new NotImplementedException();
}
// event, when server receive data from client
// client choose which room want to join and, we add cleint to list of lessons which he choose
// another method ... Register, Rename, LogOff ...
private static void OnReceive(UserContext context)
{
throw new NotImplementedException();
}
}
Course class: (ROOMS)
class Course
{
// Every course has list of active users
protected static ConcurrentDictionary<User, string> OnlineUsers = new ConcurrentDictionary<User, string>();
// Name of course
public String CourseName { get; set; }
}
User class:
class User
{
// Name of User
public string Name = String.Empty;
// UserContext - Contains data we will export to the Event Delegates.
public UserContext Context { get; set; }
}
It is good structure for my purpose ? I have many courses (room), with one teacher, in one course can be 20 pupils example .. In one course the pupils can talk with techer using chat (web socket) and drawing board ..
That's how I would build the object hierarchy:
The chat server should have a list of ChatRooms.
Each ChatRoom should have a list of ChatUsers.
Each ChatUser should have one or no ChatRoom and an outbound socket.
(this assumes that a user is only in one room at a time. Allowing multiple rooms would make things a bit more complex)
That's how room selection could work:
When a client connects, a ChatUser is created. The first thing the server does is send the list of chatrooms to the user. The client then responds with the name of the chatroom it wants to join. When a chatroom of that name doesn't exist, it is created and added to the global list of rooms.
The client is then added to the room, and the room is set on the client.
That's how chatting could work:
When the socket of the user receives a chat message, it should call a SendToAllClients method on the room the user is currently in (when the room is null, it should return an error message to the user that they must join a room first).
The SendToAll method of the room should then call a SendToClient of all users which are on its list of users.
The SendToClient method of the class would then send the chat message to the client.
How to expand this for multiple chatrooms per user
To allow a client to join multiple chatrooms at once and have separate conversations in them, the client must be able to:
request a list of rooms at any time, not just at startup
join rooms at any time, not just at startup
leave rooms
specify the room when sending a message
That means that the action the client wants to perform can not be deduced from the state it is currently in. You need to add this information to the messages of the user. You could, for example, do this as prefixes. Like
!list
requests the list of rooms
!join:asdf
join/create the room asdf
_asdf:Hello
sends the message Hello to the room asdf.
The messages from the server should have similar prefixes, so that the client can deduce if a message is a room list or a chat message and from what room it originates.
You should try to look into SignalR for ASP.NET (example : jabbr.net/). This may be more helpful and handy.