Update UI with SignalR and Knockout when manually

2019-02-28 07:54发布

问题:

I have had a search around SO for this, but didn't come across anything obvious. I have a dashboard of jobs, and their status continually changes throughout the day and I'm trying to put together some proof of concept applcation and manually trigger the updates by running update commands in the database. This is what I have set up, but when I execute an update, I don't see any changes in the UI, can you see where I have gone wrong?

Hub:

public class DashboardHub : Hub
{
    private readonly Repository _repository;

    public DashboardHub()
    {
        _repository= new Repository();
    }

    public void GetJobs()
     {
         var jobs =  _repository.GetJobs();

        Clients.All.allJobsRetrieved(jobs);
     }
}

Knockout View Model

$(function () {
    $(function () {
        function jobViewModel(id, name, isPaused, currentStatus, cob, owner) {
            this.hub = $.connection.dashboardHub;

            //job variables, initialised from params
            this.Id = id;
            this.Name = ko.observable(name);
            this.IsPaused = ko.observable(isPaused);
            this.CurrentStatus = ko.observable(currentStatus);
            this.Cob = ko.observable(cob);
        }

        function dashboardViewModel() {
            this.hub = $.connection.dashboardHub;

            //jobs collection
            this.jobs = ko.observableArray([]);

            //reference to jobs collection
            var jobs = this.jobs;

            //load jobs, calling server side hub method
            this.init = function () {
                this.hub.server.getJobs();
            };

            //callback from server side hub sending jobs to client
            this.hub.client.allJobsRetrieved = function (allJobs) {
                var mappedJobs = $.map(allJobs, function (job) {
                    return new jobViewModel(job.Id, job.Name, job.IsPaused, job.CurrentStatus, job.CoB, self);
                });

                jobs(mappedJobs);
            };

            //callback from server side hub sending error messages to client
            this.hub.client.raiseError = function (error) {
                $("#error").text(error);
            };
        }

        //set up the viewmodel
        var viewModel = new dashboardViewModel();
        ko.applyBindings(viewModel);

        //call to initialise
        $.connection.hub.start(function () {
            viewModel.init();
        });
    });
});

回答1:

Update I've extracted my code and put it on Github/nuget https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy

To install to a MVC proejct

Install-Package SignalR.EventAggregatorProxy

Old answer

I just did this for one of my customers project, I used this EventAggregator that I created for a open source project called FreePIE

https://github.com/AndersMalmgren/FreePIE/tree/master/FreePIE.Core/Common/Events

Quick read up on EventAggregators http://codebetter.com/jeremymiller/2009/07/22/braindump-on-the-event-aggregator-pattern/

The repository code looks look like this

public class FooRepository : Repository<Foo> 
{
   private readonly IEventAggregator eventAggregator;

   public FooRepository(IEventAggregator eventAggregator) {
      this.eventAggregator = eventAggregator;
   }

   public void SaveFoo(Foo foo)
   {
      //save foo
      this.eventAggregator.Publish(new FooSavedEvent(foo));
   }
}

In the event aggregator hub (SignalR) i do

public class ClientEventAggregator : Hub
{
   public ClientEventAggregator (IEventAggregatorClientProxy proxy)
   {
      //This is just a way of injecting the singleton proxy for the first hub connection
   }
}

Proxy that handles all backend events fired by the EventAggregator.
IHandle<object> can be changed to for example a base class IHandle<ForwardToClientEvent> this way you can choose which backend events to forward to client

public class EventAggregatorClientProxy : IEventAggregatorClientProxy, IHandle<object>
{
   public EventAggregatorClientProxy(IEventAggregator eventAggregator) 
   {
      eventAggregator.Subscribe(this);
   }

   public void Handle(object event)
   {
      var context = GlobalHost.ConnectionManager.GetHubContext<ClientEventAggregator>();
      context.Clients.All.event(new { Type = event.GetType().Name, Event = event });
   }
}

In my example all events will be sent to all clients you could implement rules for that and change context.Clients.All

On the client I re publish the event using this little event aggreagator that i wrote for another SO question http://jsfiddle.net/wJtun/4/

MyApp.MasterViewModel = function() {
    var event = $.connection.clientEventAggregator;

    event.client.event = this.onEvent.bind(this);

    $.connection.hub.start().done();
    });
};
MyApp.MasterViewModel.prototype = {
   onEvent: function(event) {
      var type = MyApp.Events[event.type];          
      MyApp.eventAggregator.publish(new type(event.event));
   }
};

var type = MyApp.Events[event.type]

Note that this requires that you have defined a javascript class MyApp.Events.FooSavedEvent. Or that you dynamically create a JS that contains all Events that can be published (You can look at the SignalR code how they create the Hub proxy).

Disclaimer: All code above was written directly in the SO editor out of memory, it can contain errors



回答2:

this makes sense, the application has no idea that data in the DB has changed. You need some trigger/notification to inform the application to query the database.

ideally you would not update the database manually/directly. You would use some service to query consume the data, process/validate the data, push the data to the database. send notifications to other systems that data was imported. (lots of different was to do this.)

in this case your website could receive a notification data was imported and kick off the dashboard queries.