I'm hoping to use SignalR to provide updates to the client, the updates are going to come from a message table which is updated when things happen across the application..
My problem is that the application will have around 500-600 concurrent users and I cant have all them having a connection to the database and constantly polling against the table..
What id like to do is have a single thing{?} polling the table and then updating the hubs rather than each connection polling.. I was thinking of using a singleton for this? so maybe when the application starts something is created that will then do all the work really..
My question is - say I had a singleton that had an event which was fired every time there was an update.. what would the performance be like for say 500 controllers subscribing to this event?
Also.. if there is a better way to do this then pleases say.. this is my first and only idea sadly!
any help would be fantastic!
EDIT: the data is bring provided by a legacy application and I have no control over how the data is entered so database polling will be needed.
ste.
I'd rather not to poll the database as it would be wasteful. I would approach this problem by opening only one single point of entry for my data (an HTTP API, etc) and then broadcast the update to all connected clients through the SignalR Hub. Brad Wilson has a super cool presentation which demonstrate this approach:
Brad Wilson - Microsoft’s Modern Web Stack, Starring ASP.NET Web API
Here is a code sample for this approach which uses ASP.NET Web API technology for data entry. It uses in-memory dictionary for data store but the data storage technique is not the concern here:
// This hub has no inbound APIs, since all inbound communication is done
// via the HTTP API. It's here for clients which want to get continuous
// notification of changes to the ToDo database.
[HubName("todo")]
public class ToDoHub : Hub { }
public abstract class ApiControllerWithHub<THub> : ApiController
where THub : IHub {
Lazy<IHubContext> hub = new Lazy<IHubContext>(
() => GlobalHost.ConnectionManager.GetHubContext<THub>()
);
protected IHubContext Hub {
get { return hub.Value; }
}
}
public class ToDoController : ApiControllerWithHub<ToDoHub> {
private static List<ToDoItem> db = new List<ToDoItem> {
new ToDoItem { ID = 0, Title = "Do a silly demo on-stage at NDC" },
new ToDoItem { ID = 1, Title = "Wash the car" },
new ToDoItem { ID = 2, Title = "Get a haircut", Finished = true }
};
private static int lastId = db.Max(tdi => tdi.ID);
// Lines removed for brevity
public HttpResponseMessage PostNewToDoItem(ToDoItem item) {
lock (db) {
// Add item to the "database"
item.ID = Interlocked.Increment(ref lastId);
db.Add(item);
// Notify the connected clients
Hub.Clients.addItem(item);
// Return the new item, inside a 201 response
var response = Request.CreateResponse(HttpStatusCode.Created, item);
string link = Url.Link("apiRoute", new { controller = "todo", id = item.ID });
response.Headers.Location = new Uri(link);
return response;
}
}
// Lines removed for brevity
}
The full source code for the application which Brad demoed is also available: https://github.com/bradwilson/ndc2012.
The other option, which you don't prefer, is make your database to fire notifications as soon as data is changed. Then, you can pick that up and broadcast it through SignalR. Here is an example:
Database Change Notifications in ASP.NET using SignalR and SqlDependency
Sorry that this solution is not signalR, but maybe you can get ideas from it.
Here is the full example for download on GitHub
Related
So I am fairly new with signalR and had worked with it a bit with MVCs. Now I am using it in webapi with angularjs and am a bit confused or have forgotten of what I have done. I am using bearer tokens with webapi and am trying to create a notification system.
What I want to figure out is the proper way of using angularjs with signalR. I see many people use the proxy on/invoke. Is the proxy.on is when I call the hubcontext from the server as so:
IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
hubContext.Clients.User(UserId).broadcastNotification("Good morning! The time is " + DateTime.Now.ToString());
and the proxy.invoke method is from the client side? If so, which would be the best way for using notification systems (I would assume the proxy.on)?
My second question is more on sending notifications to specific users. For sending requests to specific users, I would assume you would want to do this on the hub as so:
public void SendNotification(string userId)
{
Clients.User(userId).broadcastNotification("Good morning! The time is " + DateTime.Now.ToString());
}
My startup is something like this:
public class MyProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
var userId = request.User.Identity.Name;
return userId.ToString();
}
}
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
Database.SetInitializer(new MigrateDatabaseToLatestVersion<AuthContext, Travelfy.API.Migrations.Configuration>());
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => new MyProvider());
app.MapSignalR("/hubs", new HubConfiguration());
}
When I refresh my pages, I notice that all my userids are all empty strings "". I was reading that maybe it was due to using bearer tokens. If so, how would I use bearer tokens to specific the userId that I would want to send to? When I use the Clients.All everything works fine, so I'm assuming it has to be something with the startup/userIds I am getting.
Thanks
To answer your first question:
Which would be the best way for using notification systems
If you want to push notifications from the server towards the client, you have to define a new handler is to define a method on the client (with the generated proxy) like this:
How to define methods on the client that the server can call
If you want the client to call a method that lies on the server, you have to use this method:
How to call server methods from the client
To answer your second question:
For sending requests to specific users, I would assume you would want
to do this on the hub
You could use the connection ID of the client you wish to target. See this:
Calling client methods
So after a while, I was able to figure out the right answer. Because I was using bearerTokens, I really had to determine another method of obtaining the userId rather than just relying on request.User.Identity.Name. What I needed to do was pass my bearerToken to the connection.qs value.
connection.qs = { Bearer: token };
Once I was able to do that I had to route the find my user based on the token that I had sent in.
var token = request.QueryString.Get("Bearer");
var authenticationTicket = Startup.OAuthBearerOptions.AccessTokenFormat.Unprotect(token);
Imagine there is a chat web app between two browser clients using SignalR. What is the best approach to survive accidental F5 hit done by one of the client?
Here is described the strategy using cookies together with custom SignalR IConnectionIdFactory. But the author of the article says "The SignalR team doesn’t recommend this action". So what is the recommended architecture?
You can map the temporary connection id to a permanent user id/name (which might be stored in your DB) when the client connects. See the ShootR example which does this (last time I checked, at least).
For example, you could keep a ConcurrentDictionary of objects representing users that you track active users with and associate them with their connection ids in OnConnected (and revert this in OnDisconnected) similar to this:
private static readonly ConcurrentDictionary<string, User> Users
= new ConcurrentDictionary<string, User>();
public override Task OnConnected() {
string userName = Context.User.Identity.Name;
string connectionId = Context.ConnectionId;
var user = Users.GetOrAdd(userName, _ => new User {
Name = userName,
ConnectionIds = new HashSet<string>()
});
lock (user.ConnectionIds) {
user.ConnectionIds.Add(connectionId);
}
return base.OnConnected();
}
I am using MassTransit request and response with SignalR. The web site makes a request to a windows service that creates a file. When the file has been created the windows service will send a response message back to the web site. The web site will open the file and make it available for the users to see. I want to handle the scenario where the user closes the web page before the file is created. In that case I want the created file to be emailed to them.
Regardless of whether the user has closed the web page or not, the message handler for the response message will be run. What I want to be able to do is have some way of knowing within the response message handler that the web page has been closed. This is what I have done already. It doesnt work but it does illustrate my thinking. On the web page I have
$(window).unload(function () {
if (event.clientY < 0) {
// $.connection.hub.stop();
$.connection.exportcreate.setIsDisconnected();
}
});
exportcreate is my Hub name. In setIsDisconnected would I set a property on Caller? Lets say I successfully set a property to indicate that the web page has been closed. How do I find out that value in the response message handler. This is what it does now
protected void BasicResponseHandler(BasicResponse message)
{
string groupName = CorrelationIdGroupName(message.CorrelationId);
GetClients()[groupName].display(message.ExportGuid);
}
private static dynamic GetClients()
{
return AspNetHost.DependencyResolver.Resolve<IConnectionManager>().GetClients<ExportCreateHub>();
}
I am using the message correlation id as a group. Now for me the ExportGuid on the message is very important. That is used to identify the file. So if I am going to email the created file I have to do it within the response handler because I need the ExportGuid value. If I did store a value on Caller in my hub for the web page close, how would I access it in the response handler.
Just in case you need to know. display is defined on the web page as
exportCreate.display = function (guid) {
setTimeout(function () {
top.location.href = 'GetExport.ashx?guid=' + guid;
}, 500);
};
GetExport.ashx opens the file and returns it as a response.
Thank you,
Regards Ben
I think a better bet would be to implement proper connection handling. Specifically, have your hub implementing IDisconnect and IConnected. You would then have a mapping of connectionId to document Guid.
public Task Connect()
{
connectionManager.MapConnectionToUser(Context.ConnectionId, Context.User.Name);
}
public Task Disconnect()
{
var connectionId = Context.ConnectionId;
var docId = connectionManager.LookupDocumentId(connectionId);
if (docId != Guid.Empty)
{
var userName = connectionManager.GetUserFromConnectionId(connectionId);
var user = userRepository.GetUserByUserName(userName);
bus.Publish( new EmailDocumentToUserCommand(docId, user.Email));
}
}
// Call from client
public void GenerateDocument(ClientParameters docParameters)
{
var docId = Guid.NewGuid();
connectionManager.MapDocumentIdToConnection(Context.ConnectionId, docId);
var command = new CreateDocumentCommand(docParameters);
command.Correlationid = docId;
bus.Publish(command);
Caller.creatingDocument(docId);
}
// Acknowledge you got the doc.
// Call this from the display method on the client.
// If this is not called, the disconnect method will handle sending
// by email.
public void Ack(Guid docId)
{
connectionManager.UnmapDocumentFromConnectionId(connectionId, docId);
Caller.sendMessage("ok");
}
Of course this is from the top of my head.
I want to be able to have individual users send messages to each other using SignalR, therefore I need to send to a Specific Client ID. How can I define the client ID for a specific user at the start of the session - say a GUID Primary Key for the user?
Replace the IConnectionIdFactory with your own https://github.com/SignalR/SignalR/wiki/Extensibility.
Sample usage:
http://www.kevgriffin.com/maintaining-signalr-connectionids-across-page-instances/
EDIT: This is no longer supported in the latest versions of SignalR. But you can define a user id for a specific connection using the new IUserIdProvider
In SignalR version 1, using the Hubs approach, I override the Hub OnConnected() method and save an association of a .NET membership userId with the current connection id (Context.ConnectionId) in a SQL database.
Then I override the Hub OnDisconnected() method and delete the association between the .NET membership userId and the current connection id. This means, on a page reload, the userId/connectionId association will be up-to-date.
Something along the lines of:
public class MyHub : Hub
{
private MembershipUser _user
{
get { return Membership.GetUser(); }
}
private Guid _userId
{
get { return (Guid) _user.ProviderUserKey; }
}
private Guid _connectionId
{
get { return Guid.Parse(Context.ConnectionId); }
}
public override Task OnConnected()
{
var userConnectionRepository = new UserConnectionRepository();
userConnectionRepository.Create(_userId, _connectionId);
userConnectionRepository.Submit();
return base.OnConnected();
}
public override Task OnDisconnected()
{
var userConnectionRepository = new UserConnectionRepository();
userConnectionRepository.Delete(_userId, _connectionId);
userConnectionRepository.Submit();
return base.OnDisconnected();
}
}
Then when I need to trigger a SignalR event for a specific user, I can work out the connectionId from the database association(s) with the current userId - there may be more than one association if multiple browser instances are involved.
The SignalR Client Side documentation outlines the following:
connection.id
- Gets or sets the client id for the current connection
This certainly indicates that one should be able to set the clientID client side, without all the above plumbing. Is this not working? If working, how would this line of code look like?
I want to implement a facebook like notification system in ASP.NET MVC 3 : notifications are sent to a specific user to notify him for an action on one of his items.
Is signalr suited for such requirement?
How could i send a notification to a specific user (all opened sessions of this user) using SignalR?
Edit
Ok, Here what i did
In the client side
$(function () {
// Proxy created on the fly
var chat = $.connection.chat;
var username = '#Html.ViewContext.HttpContext.User.Identity.Name';
// Declare a function on the chat hub so the server can invoke it
chat.addMessage = function (message) {
$('#messages').append('<li>' + message + '</li>');
};
// Start the connection
$.connection.hub.start(function (){
chat.join(username);
});
});
In the server side
public class Chat : Hub
{
public void Join(string username)
{
AddToGroup(username);
}
}
And every time i need to notify a user in the controller i do the following:
IConnectionManager connectionManager = AspNetHost.DependencyResolver.Resolve<IConnectionManager>();
dynamic clients = connectionManager.GetClients<Chat>();
clients[username].addMessage("test");
Yes, SignalR is a good choice for that. Take a look at the documentation regarding Hubs (server and JS client).
You need to implement the server logic to associate your client's session with SignalR's session. You can use groups to notify all the open sessions of each user.
It is appropriate for this or you use polling, those are the two choices.
Heres a brand new video from today on this:
http://channel9.msdn.com/Shows/Web+Camps+TV/Damian-Edwards-and-David-Fowler-Demonstrate-SignalR?utm_source=dlvr.it&utm_medium=twitter