Just trying to wrap my mind around handling pushing data to some users through SignalR.
Say, in a private chat situation, Person A sends a message to Person B. If I'm understanding the concept correctly, I need to create a Group for each user who logged into the system. This way, when the new message from Person A comes in, in my SignalR Hub, I'd send the message to the group for Person B which only has only one user in it i.e. Person B.
In this approach, I'd essentially create a unique Group for each user so that I have the flexibility to push data to each unique user. I could use the UserId as the Group Id.
Is this the way to handle pushing data selectively to each unique user?
You can grab client Ids manully like so and then send them using the hubcontext to avoid creating a lot of groups (you just need to implement a menthod which get you're connections from your resource in my case im using dtos)
I'm assuming your hubs have some type of connection manager, here's a sample of one from the docs
In my case I have a dynamic configuration which I use to configure my hubs using the Type of DTO, and I send hubs using a generic hub base here is a sample implementation:
Warning: things may slightly be different I'm using .Net-Core in this example
//NOTE: THub must be the one Registered with MapHubs in the Startup
//class or your connections won't be there because Hubs are not contravarient
public abstract class HubBase<TDTO, THub> : Hub
Where THub: HubBase<TDTO, THub>
{
protected IHubContext<THub> hubContext;
HubBase(IHubContext<THub> hubContext)
{
this._hubContext = hubContext;
}
protected abstract List<string> GetConnectionsByDto(TDTO dto)
protected async Task SendFilteredAsync(string eventName, TDTO dto)
{
var clientIds = await this.GetConnectionsByDto(dto);
if (false == clientIds.Any())
{
return;
}
var broadcastTasks = clientIds.Select(async clientId =>
await this._hubContext.Clients.Client(clientId).SendAsync(eventName, dto));
await Task.WhenAll(broadcastTasks);
}
}
Related
I have two projects
WebApp(SpringMVC)
Microservice
The idea is that I have a page in which I list all the users from DB, so basically I need a listener on WebApp side and a producer in the microservice side, typically the flow is as follow
Whitout rabbitmq(synchronous)
Click page "List Users"
UserController that redirect me to a specific service
public List<User>getUsers(){//no args!!
service.getUsers();//no args
}
UserService with logic to access DB and retrieve all users
public List<User>getUsers(){//no args!!
//connect to DB and retrieve all users
return users
}
Render users on jsp
With RabbitMQ and assuming users has already been produced the list of users on the microservice's side
My question is about if I introduce rabbitmq then I need a method in which I listen a message(List of products as a JSON) but now the flow change a little bit comparing with the first one because
Click button "List Users"
Controller need a method findAll(Message message), here I need pass a message because the service is expecting one as the service is Listener
public List<User>getUsers(Message message){
service.getAllUsers(**String message**);
}
The service as right now is listen a message, I need to pass a Message
arg in which I will be listen the queues
#RabbitListener(queues = "${queue}", containerFactory = "fac")
public List<User> getUsers(String message){
//Transform JSON to POJO
//some logic...
return users;
}
So basically my question is the second flow is correct?
If so how I have to pass the Message object from controller to service
because in controller I do not needed a Message, but in order to
listen I have, is this correct?
If so how pass the message arg
There is a better way to achieve this?
Regards
Use a Jackson2JsonMessageConverter and the framework will do all the conversion for you.
Controller:
List<User> users = template.convertSendAndReceiveAsType("someExchange", "someRoutingKey",
myRequestPojo, new ParameterizedTypeReference<List<User>>() {});
Server:
#RabbitListener(queues = "${queue}", containerFactory = "fac")
public List<User> getUsers(SomeRequestPojo request){
//some logic...
return users;
}
Wire the converter into both the listener container factory and the RabbitTemplate.
Request Pojo and Users have to be Jackson-friendly (no-arg constructor, setters).
I've seen one article that said groups were bad to use if you needed anything to be secure, but I'm wondering if that is still true or how someone would take advantage of this scenario:
Say I had one company with multiple subdivisions. I want to send notifications only to a particular subdivision when an action happens. Even though it's odd, say in this scenario that it would be bad security wise if a different subdivision saw the notification.
The client hub does not have any "send" methods on it so it looks like this:
[Authorize]
public class NotificationsHub : Hub
{
}
I then have a service class on the api that uses the IHubContext to send a notification to the group
public class NotificationService : INotificationService{
public async Task UpdateScheduleAsync(string subdivision, string message)
{
await _hubContext.Clients.Group(subdivision).SendAsync("ReceiveMessage",message);
}
}
If I added in the NotificationsHub the method:
public override async Task OnConnectedAsync()
{
//may or may not use the identifier
var someUserIdentityInfo = Context.UserIdentifier;
//would grab the subdivision the authorized user is apart of
var subdivision = GrabSubdivisonFromIdentity(someUserIdentityInfo);
//add to the subdivison group
foreach(var division in subdivison){
await Groups.AddToGroupAsync(Context.ConnectionId, division);
}
await base.OnConnectedAsync();
}
Is there any way for a person to connect to the group if they know the other division names? Is this still a bad idea?
I'm trying to avoid keeping a list of "logged in" users and sending out the change by user individually one by one for each change.
I am new to asp.net. I have gone through this link which has shown how to count the online users connected to a server using asp.net. (which is working when I tried)
My question is: What should I change in that code (Global.asax) so that It shows all the names of the connected users instead of counting them.
I created a chat application which stores the name of the connected user in a variable chatUsername in js file as shown below:
js file
var chatUsername = window.prompt("Enter Username:", "");
//
chat.client.addMessage = //Function
//
chat.server.send(chatUsername);
.aspx.cs file
//Using SignalR (I think this doesnt matter)
public class Chat : Hub
{
public void Send(string from)
{
// Call the addMessage method on all clients
Clients.All.addMessage(from);
}
}
You can find my complete code here
EDIT: Please provide a simple example related only to asp.net or signalr (no other technologies like MVC)
Please help.
Edit: following code refers to SignalR v0.5, not the latest 1.0Alpha2, but I believe the reasoning is the same
To do this you need to add several steps to your SignalR connection process, both in the server and in the client:
on the server side:
on application start-up, for example, you can instantiate a static in-memory repository (can be a dictionary of ) that will serve as the user repository to store all currently connected users.
In the hub you need to handle the Disconnect event (when a user disconnects, needs to be removed from the user repository as well) and notify all other clients that this user disconnected
In the hub you need to add two new methods (the names can be whatever you want) that will help client connect to the system and get the list of currently connected users:
GetConnectedUsers() that just returns a collection of connected users
Joined() where the Hub will create a new User, using the info stored in the round-trip state (the username selected by the client) and the SignalR connection ID, and add the newly created user to the in-memory repository.
on the client side:
First you need to instantiate the javascript object that relates to your server-side hub
var chat = $.connection.chat;
chat.username = chatUsername;
Then implements all the functions that will be called by the hub and finally connect to the hub:
// Step 1: Start the connection
// Step 2: Get all currenlty connected users
// Step 3: Join to the chat and notify all the clients (me included) that there is a new user connected
$.connection.hub.start()
.done(function () {
chat.getConnectedUsers()
.done(/*display your contacts*/);
});
}).done(function () {
chat.joined();
});
});
});
If you are asking why we need to add a stage like "chat.joined()" is because in the method on the Hub that is handling the connection event, the round-trip state is not yet available, so the hub cannot retrieve the username chosen by the user.
Anyway I made a blog post to show more in detail how to create a basic SignalR chat web application using Asp.Net MVC, and it is available at:
http://thewayofcode.wordpress.com/2012/07/24/chatr-just-another-chat-application-using-signalr/
In the post you will also find a link to the github repository where the source is published.
I hope this helps.
Valerio
Apparently, you are using Signal-R - so try tracking state of online users (i.e. connected clients) in java-script itself. Use Connected/Disconnected/Reconnected server side events to broadcast to all clients - from documentation:
public class Chat : Hub
{
public override Task OnConnected()
{
return Clients.All.joined(Context.ConnectionId, DateTime.Now.ToString());
}
public override Task OnDisconnected()
{
return Clients.All.leave(Context.ConnectionId, DateTime.Now.ToString());
}
public override Task OnReconnected()
{
return Clients.All.rejoined(Context.ConnectionId, DateTime.Now.ToString());
}
}
A global server side store (for example - a static dictionary) can be used to store state against the connection id - that way, this dictionary can give you users for needed connection ids. For example,
// dis-claimer: untested code - just to give the idea/hint/outline
public class Chat : Hub
{
// change to use Concurrent Dictionary (or do thread-safe access)
static Dictionary<string, User> _users = new Dictionary<string, User>()
// call from client when it goes online
public void Join(string name)
{
var connId = this.Context.ConnectionId;
__users.Add(connId, new User(connId, name));
}
public override Task OnConnected()
{
return Clients.All.joined(_users[Context.ConnectionId], DateTime.Now.ToString());
}
public override Task OnDisconnected()
{
var user = _users[Context.ConnectionId];
_users.Remove(Context.ConnectionId);
return Clients.All.leave(user, DateTime.Now.ToString());
}
public List<User> GetUsers()
{
return _users.Values.ToList()
}
}
I think this should work for you :-
void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
Application["OnlineUsers"] = 0;
List<string> list = new List<string>();
}
//First check if it is Authenticated request:-
void Session_Start(object sender, EventArgs e)
{
if(Request.IsAuthenticated)
list.Add(User.Identity.Name);
//your rest of code .......
}
list will return you all the username who are online :-
I want to make a service that notify the user in case there are some new messages sent to him. Thus I want to use some Comet framework that provide the server push ability. So I have looked into PokeIn.
Just wondering a thing. I have checked on the samples that they have on the website. None of them look into the database to retrieve new entries if there are some. But it is just a matter of modification to it I guess.
One of the sample implement this long polling by using a sleep on the server side. So if I use the same approach I can check the database, if there are any new entries, every 5 seconds. However this approach doesn't seem to be much different from when using polling on the client side with javascript.
This part is from a sample. As can be seen they put a sleep there for to update current time for everybody.
static void UpdateClients()
{
while (true)
{
//.. code to check database
if (CometWorker.ActiveClientCount > 0)
{
CometWorker.SendToAll(JSON.Method("UpdateTime", DateTime.Now));
}
Thread.Sleep(500);
}
}
So I wonder is this how I should implement the message notifier? It seems that the above approach is still going to push a huge load demand on the server side. The message notifier is intend to work same way as the one found Facebook.
You shouldn't implement this way, that sample is only implemented like that because the keep PokeIn related part is clear. You should implement SQL part as mentioned http://www.codeproject.com/Articles/12335/Using-SqlDependency-for-data-change-events
in order to track changes on database.
So, when you have something to send, call one of the PokeIn methods for the client side delivery. I don't know, how much your application is time critical because in addition to reverse ajax, PokeIn's internal websocket feature is very easy to activate and delivers messages to client quite fast.
You can do this with database as #Zuuum said, but I implemented it in a different way.
I'm using ASP.NET MVC with PokeIn and EF in a Windows Azure environment:
I have domain events similar to this approach: Strengthening your domain: Domain Events
When someone invokes an action, that's a Unit of Work
If that UOW succeeds then I raise a domain event (e.g. ChatMessageSent)
I have subscribers for these domain events so they can receive the event and forward the message to the PokeIn listeners
I use this pattern for all my real-time needs on my game site (making moves, actions etc in a game), I don't want to advertise it here, you can find it through me if you want.
I always use this pattern as a duplex communication solution so everybody gets their update via PokeIn, even the user who invoked the action so every client will behave the same. So when someone calls an action it won't return anything except the success signal.
The next examples are won't work because they are only snippets to demonstrate the flow
Here is an action snippet from my code:
[HttpPost]
[UnitOfWork]
[RestrictToAjax]
[ValidateAntiForgeryToken]
public JsonResult Post(SendMessageViewModel msg)
{
if (ModelState.IsValid)
{
var chatMessage = new ChatMessage
{
ContainerType = msg.ContainerType,
ContainerID = msg.ContainerID,
Message = _xssEncoder.Encode(msg.Message),
User = _profileService.CurrentUser
};
_chatRepository.AddMessage(chatMessage);
OnSuccessfulUoW = () => EventBroker.Current.Send(this, new ChatMessageSentPayload(chatMessage));
}
return Json(Constants.AjaxOk);
}
And the (simplified) EventBroker implementation:
public class UnityEventBroker : EventBroker
{
private readonly IUnityContainer _container;
public UnityEventBroker(IUnityContainer container)
{
_container = container;
}
public override void Send<TPayload>(object sender, TPayload payload)
{
var subscribers = _container.ResolveAll<IEventSubscriber<TPayload>>();
if (subscribers == null) return;
foreach (var subscriber in subscribers)
{
subscriber.Receive(sender, payload);
}
}
}
And the even more simplified subscriber:
public class ChatMessageSentSubscriber : IEventSubscriber<ChatMessageSentPayload>
{
public void Receive(object sender, ChatMessageSentPayload payload)
{
var message = payload.Message;
var content = SiteContent.Global;
var clients = Client.GetClients(c => c.ContentID == message.ContainerID && c.Content == content)
.Select(c => c.ClientID)
.ToArray();
var dto = ObjectMapper.Current.Map<ChatMessage, ChatMessageSentDto>(message);
var json = PokeIn.JSON.Method("pokein", dto);
CometWorker.SendToClients(clients, json);
}
}
I want to use signalR for my clint browser website so it can receive messages from the server when a new order is added. So I want it to react to a server side event that is not triggered by any browser.
There are multiple users on the website. A user should be notified when there is a new order placed for him on he server. How an I notify only a specific user, and do this from the method that adds the user?
Is there any code like:
var chat=new Chat();
chat.Send("hihi");
placed in AddOrder method, with
public class Chat : Hub
{
public void Send(string message)
{
// Call the addMessage method on all clients
Clients.refresh(message);
}
}
You can override the default client id (used to identify the users browser window) and replace with your own. Your client id would come from your membership provider.
Create a new class and implement IConnectionIdGenerator.
public class UserIdClientIdFactory : IConnectionIdGenerator
{
public string GenerateConnectionId(IRequest request)
{
return Guid.NewGuid().ToString();
}
}
The method above just creates a new Guid, but you would return the customers id from your membership provider.
You then need to register this new class with SignalR dependencyresolver, so in the Application_Start method in the global.asax file add the following line
GlobalHost.DependencyResolver.Register(typeof(IConnectionIdGenerator),
() => new UserIdClientIdFactory());
When a new order is placed you would then get the specific client and broadcast a message to them, for example:
//clientId matches the user id from you membership provider.
var clients = GlobalHost.ConnectionManager.GetHubContext().Clients;
clients[clientId].yourClientSideCallBackMethodGoesHere(someValue);
You have to store the Context.ConnectionId for all connected users, tie that to your website users and then use Clients[connectionId].addMessage(data);
One way you can do this is to hold a collection of Users (website users) each paired to a connection Id. You can then use SignalR events OnConnected / OnDisconnected to pop users in and out of this list.
E.g.
public override Task OnConnected()
{
// Add users here with Context.ConnectionId
}
public override Task OnDisconnected()
{
// Remove users from collection here by identifying them with Context.ConnectionId
}