SignalR: Connection must be started before data ca

2019-06-13 19:34发布

问题:

Only recently started using SignalR and I had this working a few days ago and now cannot workout what has gone wrong.

I have my hub:

[HubName("roleManagementHub")]
public class RoleManagementHub : GenericHub<RoleManagementRole>
{
    public RoleManagementHub(ICurrentlyViewingUserService service) : base(service)
    {
    }
}

Which extends a generic hub I have created:

public class GenericHub<TEntity> : Hub where TEntity : class, IBusinessObject<TEntity>
{
    private readonly ICurrentlyViewingUserService service;

    public GenericHub(ICurrentlyViewingUserService service)
    {
        this.service = service;
    }

    public void ImViewing(string id)
    {
        string colour;
        service.AddCurrentlyViewingUser<TEntity>(id, HttpContext.Current.User.Identity.Name, out colour);
        Clients.handleImViewingMessage(HttpContext.Current.User.Identity.Name, colour, id);
    }

    public void ImLeaving(string id)
    {
        service.RemoveCurrentlyViewingUser<TEntity>(id, HttpContext.Current.User.Identity.Name);
        Clients.handleImLeavingMessage(HttpContext.Current.User.Identity.Name, id);
    }

    public void IHaveEdited(string id, string property, string content)
    {
        string colour = service.GetCurrentlyViewingUserColour<TEntity>(id, HttpContext.Current.User.Identity.Name);

        if (string.IsNullOrEmpty(colour))
            return;

        Clients.handleIHaveEdited(id, property, content);
    }
}

I am including <script type="text/javascript" src="@Url.Content("~/signalr/hubs")"></script>.

Now to the main javascript. I have create a reusable Knockout JS component which takes in the hub to attach the relevant handlers on. A lot of this is irrelevant but I thought I would post it all just incase. I start my connection at the beginning of the closure and create my knockout component passing in the roleManagementHub.

var user_role_edit = {};

    (function (index) {

        user_role_edit = index;

        jQuery.connection.hub.start();

        var val = ko.setupValidation([], []);
        val.createErrorCollections({Name: 0, General: 0}, index, 'rename-role-form');
        var dmp = new diff_match_patch();

        index.Id = ko.observable();
        index.Name = ko.observable();
        index.currentViewersViewModel = new ko.currentlyViewingComponent.viewModel({
            hub: jQuery.connection.roleManagementHub,
            id: index.Id,
            modalSelector: '#user-role-edit-modal'
        });

        index.rename = function (form) {
            jQuery.post('" + @Url.Action("Rename", "RoleManagement") + @"', {
                'id': index.Id(),
                'name': index.Name()
            }, function (dataReturned) {
                if (dataReturned.IsValid) {
                    jQuery(document).trigger('userRoleUpdated', index);
                    index.editModal.modal('hide');
                }
                else {
                    val.rebindValidations({Name: 0, General: 0}, index, dataReturned.Errors);   
                }
            });
        };

        index.raiseClose = function () {
            index.editModal.modal('hide');
        };

        index.raiseDetails = function () {
            jQuery(document).trigger('userRoleDetails', [index]);
            index.editModal.modal('hide');
        }

        jQuery(document).bind('userRoleEdit', function (event, id) {

            jQuery.getJSON('" + @Url.Action("GetNewsArticleForUpdateOrDelete", "RoleManagement") + @"', { id: id }, function (data) {

                index.Id(data.Role.Id);
                index.Name(data.Role.Name);

                index.currentViewersViewModel.initialiseCurrentViewers(data.CurrentlyViewingUsers);

                val.clearValidations({Name: 0, General: 0}, index);
                index.editModal.modal('show');
            });
        });

        jQuery.connection.roleManagementHub.handleIHaveEdited = function(id, property, content) {
            if (index.Id() != id)
                return;

            if (index[property] == undefined)
                return;

            dmp.Match_Distance = 1000;
            dmp.Match_Threshold = 0.5;
            dmp.Patch_DeleteThreshold = 0.5;

            var patches = dmp.patch_make(index[property](), content);
            var results = dmp.patch_apply(patches, index[property]());
            index[property](results[0]);
        };

        jQuery(function () {
            ko.applyBindings(index, jQuery('#user-role-edit-container')[0]);
            index.editModal = jQuery('#user-role-edit-modal').modal({ backdrop: true, closeOnEscape: true, modal: true, show: false });

            jQuery('#rename-role-form > fieldset > div > div > input#Name').blur(function(event) {
                jQuery.connection.roleManagementHub.iHaveEdited(index.Id(), 'Name', index.Name());
            });
        });
    } (user_role_edit));");

Here is the knockout component:

(function () {

function currentUserViewModel(username, colour) {
    this.Username = username;
    this.Colour = colour;
}

ko.currentlyViewingComponent = {
    // Defines a view model class you can use to populate a grid
    viewModel: function (configuration) {

        this.currentViewers = ko.observableArray([]);

        var index = this;

        this.initialiseCurrentViewers = function (currentUsers) {

            index.currentViewers.removeAll();

            ko.utils.arrayForEach(currentUsers, function (item) {
                index.currentViewers.push(new currentUserViewModel(item.Username, item.Colour));
            });
        };

        configuration.hub.handleImViewingMessage = function (username, colour, id) {
            if (configuration.id() != id)
                return;

            var findResult = ko.utils.arrayFirst(index.currentViewers(), function (item) {
                return item.Username == username;
            });

            if (findResult == null)
                index.currentViewers.push(new currentUserViewModel(username, colour));

            jQuery('a[rel=tooltip]').tooltip();
        };

        configuration.hub.handleImLeavingMessage = function (username, id) {
            if (configuration.id() != id)
                return;

            index.currentViewers.remove(function (item) {
                return item.Username == username;
            });
        };

        jQuery(configuration.modalSelector).bind('show', function () {
            configuration.hub.imViewing(configuration.id());
        });

        jQuery(configuration.modalSelector).bind('hide', function () {
            configuration.hub.imLeaving(configuration.id());
        });
    }
};

// Templates used to render the grid
var templateEngine = new ko.nativeTemplateEngine();

templateEngine.addTemplate = function (templateName, templateMarkup) {
    document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "<" + "/script>");
};

templateEngine.addTemplate("ko_currentlyViewing", "<div data-bind=\"foreach: currentViewers\" class=\"pull-left\"><a data-bind=\"attr: { title: Username, 'class': Colour }\" rel='tooltip'>&nbsp;</a></div>");

ko.bindingHandlers.currentlyViewingComponent = {
    init: function () {
        return { 'controlsDescendantBindings': true };
    },
    // This method is called to initialize the node, and will also be called again if you change what the grid is bound to
    update: function (element, viewModelAccessor, allBindingsAccessor) {
        var viewModel = viewModelAccessor(), allBindings = allBindingsAccessor();

        // Empty the element
        while (element.firstChild)
            ko.removeNode(element.firstChild);

        // Allow the default templates to be overridden
        var currentlyViewingTemplateName = allBindings.currentlyViewingTemplate || "ko_currentlyViewing";

        // Render the main grid
        var currentlyViewingContainer = element.appendChild(document.createElement("DIV"));
        ko.renderTemplate(currentlyViewingTemplateName, viewModel, { templateEngine: templateEngine }, currentlyViewingContainer, "replaceNode");
    }
};

})();

As you can see on a modal show event it sends a message using the hub to tell other connected users that a users has started to view that item.

However I am now greeted with "SignalR: Connection must be started before data can be sent. Call .start() before .send()".

I've already started the connection! Also even if I try;

jQuery(function() {
        jQuery.connection.hub.start();
        jQuery.connection.userManagementHub.imViewing("1");
    });

I get the Connection must be started before data can be sent message.

Any ideas??

Thanks in advanced!

Jon

回答1:

The call to start() is not instant and could be the cause of your issue. You may want to move code that should happen after start into a callback in the start method like:

jQuery(function(){
    jQuery.connection.hub.start(function(){
         jQuery.connection.userManagementHub.imViewing("1");
    });
});