locked
Creating a separate SignalR Hub instance for each client and passing parameters RRS feed

  • Question

  • User-961853402 posted

    I am trying to create a page that updates data in real time when ever something changes in the database. I have used SignalR. There is only one hub, here is the code to the hub:

    public class MyHub : Hub
    {
    public static int prodId { get; set; }
    
    public void setProdID(int pid)
    {
    prodId = pid;
    }
    
    public BidDetailViewModel GetChanges()
    {
    string conStr = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
    SqlConnection connection = new SqlConnection(conStr);
    
    SqlDependency.Start(conStr);
    string query = @"select Id,
    BidDate,
    BidAmount,
    BidStatusId,
    BidderId,
    ProductId 
    from [dbo].[Bids] 
    where ProductId = " + prodId + 
    " order by BidDate desc ";
    SqlCommand command = new SqlCommand(query, connection);
    command.Notification = null;
    SqlDependency dependency = new SqlDependency(command);
    
    //If Something will change in database and it will call dependency_OnChange method.
    dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
    connection.Open();
    SqlDataReader dr = command.ExecuteReader();
    
    var bid = new Bid();
    
    while (dr.Read())
    {
    bid.Id = dr.GetInt32(0);
    bid.BidDate = DateTime.Now; //fake value, dont need it
    bid.BidAmount = dr.GetFloat(2);
    bid.BidStatusId = dr.GetInt32(3);
    bid.BidderId = dr.GetString(4);
    bid.ProductId = dr.GetInt32(5);
    
    //Break after reading the first row
    //Using this becasue we can not use TOP(1) in the query due to SignalR restrictions
    break;
    }
    connection.Close();
    var vm = new BidDetailViewModel
    {
    HighestBid = bid,
    NoOfBids = -1
    //NoOfBids is no longer needed, assigning a random value of -1
    //will remove it later, just testing rn
    };
    
    return vm;
    }
    
    private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
    {
    if (e.Type == SqlNotificationType.Change) SendNotifications();
    }
    
    private void SendNotifications()
    {
    BidDetailViewModel vm = GetChanges();
    IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
    
    //Will update all the client with new bid values
    context.Clients.All.broadcastMessage(vm);
    }
    }


    I am calling the hub in two different pages. First one is `/Home/About` and second is `/Home/About2`. They both have the same JS code except for one change, the prodId being passed in `/Home/About` is `40`, and for `/Home/About2` it's `41`. The JS code is: (This is the code for `/Home/About` as you can see it's sending `40`. Again, `/Home/About2` has the exact same code but with `41`).

    <script>
    $(function () {
    // Declare a proxy to reference the hub.
    var ew = $.connection.myHub;
    //This method will fill all the Messages in case of any database change.
    ew.client.broadcastMessage = function (response) {
    //Changing HTML content here using document.getElementById()
    };
    
    //This method will fill all the Messages initially
    $.connection.hub.start().done(function () {
    ew.server.setProdID(40);
    //Hardcoding the prodID for now, will get it using document.getElementById() later
    //ProdId will be different for each page
    ew.server.getChanges().done(function(response) {
    //Changing HTML content here using document.getElementById()
    });
    });
    });
    </script>
    
    


    As you can see, I'm setting the `ProdId` in `MyHub` hub by calling the `SetProdID` function. I've set `ProdId` to a static member so I can set it without making an object of the hub. Now If visit `/Home/About`, it sets the `prodId` to `40` (as expected) and I get the results from the query. But, when I visit `\Home\About2` it sets `prodId` to `41` (again, as expected) and I get the results from the query that correspond to parameter `41`.

    Now if I update the item with id `41` in the database, both the `/Home/About` and `/Home/About2` pages are updated in real time but with the data that corresponds to item with id `41`. I know this post is gettting really long, but my question is, shouldn't SignalR be making a completely separate instance of `MyHub1` for the two different pages? Is this happening because I've made the `ProdId` a static member? If that's it, how do I set it without making it a static member? I basically want multiple instances of the same `MyHub`, each instance for a completely different `prodId`. Is this possible? or do I have to write multiple hubs (MyHub1, MyHub2, .... MyHubn). The problem with writing multiple hubs is that I don't know how many items there are going to be.


    I've also tried passing the `prodId` as a parameter to `GetChanges()` method but I'm not sure what to pass in the `sendNotification()` method that calls the `GetChanges()` method.

    public class MyHub : Hub
    {
    public BidDetailViewModel GetChanges(int pid)
    {
    ............
    string query = @"select Id,
    BidDate,
    BidAmount,
    BidStatusId,
    BidderId,
    ProductId 
    from [dbo].[Bids] 
    where ProductId = " + pid + 
    " order by BidDate desc ";
    ...............
    }
    
    private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
    {
    if (e.Type == SqlNotificationType.Change) SendNotifications();
    }
    
    private void SendNotifications()
    {
    //What do I pass here?
    BidDetailViewModel vm = GetChanges();
    ..............
    }
    }


    I'm not sure how to proceed from here on, any pointers would be appreciated! Also, thanks for reading this long post.

    Saturday, April 14, 2018 4:06 PM

All replies

  • User475983607 posted

    This is a logical bug.

    public static int prodId { get; set; }

    Static means there is one copy of prodId in memory that every user shares.

    The C# programming guide explains static

    https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members

    SignalR reference

    https://docs.microsoft.com/en-us/aspnet/signalr/overview/getting-started/

    Saturday, April 14, 2018 7:51 PM
  • User-961853402 posted

    Static means there is one copy of prodId in memory that every user shares.

    I figured that was the problem. What do you suggest I do to pass & store the prodId in such a way that it is not static?

    Saturday, April 14, 2018 8:40 PM
  • User475983607 posted

    hbk6969

    I figured that was the problem. What do you suggest I do to pass & store the prodId in such a way that it is not static?

    I do not fully understand the problem you are trying to solve.

    Web sites in general pass data from the client to the server,  If prodId is a bit of data specific to a client then the client is responsible for saving the actual data or a the key to the data.  Use the browser's local storage to save the prodId.

    I would broadcast an alert to all the clients when an event on the server occurs. In that event message pass at least the prodId to the clients and let the clients decide what to do next.  For example, if the client is looking at prodId 40 and the server sends a message that prodId 40 changed the client can make a request to refresh the data.  Or maybe the entire record is returned in the broadcast and the client just uses the broadcast to update the DOM.

    Clients not looking at prodId 40 ignores the broadcast message.

    Saturday, April 14, 2018 10:07 PM
  • User-961853402 posted

    mgebhard

    I do not fully understand the problem you are trying to solve.

    Sorry for not being clear enough. I'll try to explain the whole scenario to you. Basically I am trying to make a bidding website. The bidding will happen in real time. For example, there's in an Item that is current up for bidding. The item's ID is lets say 10. Since the bidding is happening is real time, I need to update the current highest bid (and all the relevant info; all part of the Bid class) in real time, so the user doesn't have to reload the page every time there is a new bid placed on the item. I was using Ajax to poll the database every 5 seconds but that was not efficient at all. So I moved on to websockets with SignalR. 

    mgebhard

    Web sites in general pass data from the client to the server,  If prodId is a bit of data specific to a client then the client is responsible for saving the actual data or a the key to the data.  Use the browser's local storage to save the prodId.

    ProdId is specific to each item/product that's currently up for bid. There will obv we multiple items/products up for bidding at the same time. What I am looking for is to have only one Hub class (MyHub), have the hub instance take one parameter (ProdId) in such a way that each product page passes a unique ProdId to the hub and the hub instance responds to changes in the corresponding ProdId so that if there's any change in Product/Item with Id 10, only the view for Product/item 10 is updated and not the view for Product with Id 11.  



    I like your idea. What I understood is that whenever the hub broadcasts a response, I check if the response's ProdId is equal to the ProdId on the view/page. If it is, update the html content and if it isn't, just discard it. But is there an other way of doing it such that the hub only responds to the corresponding ProdId? I am asking because there will be multiple products up for bidding at once and multiple clients will be bidding on each product. I just want to keep the transfer efficient and not clog up the back/front end with too many requests.  


    There is also one more problem that (I think) could occur with the idea that you suggested. If there are two products up for bidding with IDs 10 and 11 respectively. What happens if a client A bids on product 10 and a cleint B bids on product 11 at the exact same time? Will the Hub brodcast the latest change only? If that's going to happen, will a client miss out on an update that should've happened but didn't becuase another update came at the exact same time? Or will the database handle each request one by one, giving the hub a chance to brodcast each change regardless of whether they were made at the exact same time?

    Sunday, April 15, 2018 10:44 AM
  • User61956409 posted

    Hi hbk6969,

    Now if I update the item with id `41` in the database, both the `/Home/About` and `/Home/About2` pages are updated in real time but with the data that corresponds to item with id `41`.

    According to your code of SendNotifications() method, I can find that the message will be sent to all connecting clients (browser tabs), as you mentioned, if you update the item with id `41` in the database, both the `/Home/About` and `/Home/About2` pages are updated.

    It seems that you’d like to push updates notification message to specific client (or browser tab), you can get the connection id of client and send message to that specific client.

    Clients.Client("{connectionId_here}").broadcastMessage(vm);

    Another approach to send message to specific user, you can create a group for each user when establishing connection, and then you can send message to specific user by sending message to that single-user group.

    With Regards,

    Fei Han

    Monday, April 16, 2018 8:53 AM
  • User-961853402 posted

    It seems that you’d like to push updates notification message to specific client (or browser tab), you can get the connection id of client and send message to that specific client.

    How do I figure out which client is looking at which ProdId? I have to group them on the basis of the product they are currently looking at. But how?

    Tuesday, April 17, 2018 2:49 PM
  • User475983607 posted

    How do I figure out which client is looking at which ProdId? I have to group them on the basis of the product they are currently looking at. But how?

    Similar to my initial response, it is a matter of maintaining state.  Grouping means the responsibility for maintaining user state moved from the clients to the server.  The clients must send the server which groups they wish to be a part of.  It's the responsibility of the server to maintain user state and send responses to the correct group.   The clients still need to handle and validate the responses.

    The SignalR reference documentation explains how to create and maintain groups.  Again, it's tough for forum members to answer design questions as we do not know your requirements but I assume the clients select the product they wish to follow. 

    https://docs.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/working-with-groups

    Keep in mind, the simplest approach is the one I described above where the clients maintain state and ignore message not address to them.  This approach makes the app a bit chatty though.  Since you'll always need to validate the response anyway, you can start with the simple approach then refactor to groups later.

    Tuesday, April 17, 2018 3:19 PM