Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Signalr deserializes my objects incorrectly in IIS 7.5 and Edge/IE, foreverFrame broken?

So I have a simple Signalr/Knockout project that uses the mapping plugin to bind a simple object (item with an array of more items) to viewModels I defined in JS:

var someObjectMapping = {
    'MyItemArray': {
        create: function (options) {
            return new MyItemViewModel(options.data);
        }
    }
}

var myItemMapping = {
    'ItemChildren': {
        create: function (options) {
            return new ItemChildViewModel(options.data);
        }
    }
}

var SomeObjectViewModel = function (data) {
    ko.mapping.fromJS(data, someObjectMapping, this);
}

var MyItemViewModel = function (data) {
    ko.mapping.fromJS(data, myItemMapping, this);
}

var ItemChildViewModel = function (data) {
    ko.mapping.fromJS(data, null, this);
}

I use SignalR's default settings to connect to my hub like so:

    var myHubProxy = $.connection.myHub;

    myHubProxy.client.processSomeObject = function(someObject) {
        console.log('SomeObject received');
        var viewModel = new SomeObjectViewModel(someObject);
        ko.applyBindings(viewModel);
    }

    $.connection.hub.start().done(function() {
        console.log('Now connected, connection ID=' + $.connection.hub.id);
        myHubProxy.server.getSomeObject();
    });

When my object comes back, knockout applies the binding and the mapping gets processed. Then the object and its child arrays are naturally rendered on the page:

<h2 data-bind="text: MyItem"></h2>
<ul data-bind="foreach: MyItemArray">
    <li>
        <span data-bind="text: Name"></span>
        <ul data-bind="foreach: ItemChildren">
            <li data-bind="text: Name"></li>
        </ul>
    </li>
</ul>

Now for the kicker: This works in on my local machine (Win 10, IIS Express), in all browsers (Chrome/Firefox/Safari/IE), no problem. When I publish this to IIS 7.5 however, it works in all browsers except for Internet Explorer 8-10 and Microsoft Edge. Same code.

When I drill through F12 I notice that, from IIS, the create function in the first mapping gets an array:

iis viewmodel

When instead, on first break, I should have the first item in my array like it does on my local machine:

local viewmodel

Drilling up the callstack reveals that the parent object in knockout.mapping's createCallback function is not being interpreted as an array as it should:

iis callback

However, on my local machine, it works as expected:

local callback

Strangely, one of two things can workaround the issue: First, if I serialize the object I get back from SignalR and then deseralize it before binding it my knockout model, everything works from all browsers from IIS 7.5:

  myHubProxy.client.processSomeObject = function(someObject) {
        console.log('SomeObject received');
        var jsonStr = JSON.stringify(someObject);
        var viewModel = new SomeObjectViewModel(JSON.parse(jsonStr));
        ko.applyBindings(viewModel);
    }

But this can affect performance.

OR if I force SignalR's transport to longPolling, again everything works in all browsers from IIS 7.5:

    $.connection.hub.start({ transport: "longPolling" }).done(function () {
        console.log('Now connected, connection ID=' + $.connection.hub.id);
        myHubProxy.server.getSomeObject();
    });

But then I wouldn't have the advantages WebSockets provides over polling.

IIS 7.5 doesn't support WebSockets

I can also hardcode my json and bind it with knockout, which works as expected in all browsers.

It took me forever to discover what was going on, and I've been struggling to figure this one out. It's strange that this works from IIS 7.5 in all the other browsers but IE/Edge when they're running the same simple script. It also works with in all browsers IIS 10 (non-Express), which isn't an option for the server I'm publishing to.

Edit: Uffe has pointed out that IIS 7.5 doesn't support WebSockets. After enabling logging, I saw that, for IIS 7.5, Signalr will instead fallback to foreverFrame for IE and serverSentEvents (which isn't supported in IE) for other browsers.

I also tested forcing foreverFrame, which reproduced the issue on my machine with IIS 10 Express:

    $.connection.hub.start({ transport: 'foreverFrame'}).done(function () {
        console.log('Now connected, connection ID=' + $.connection.hub.id);
        myHubProxy.server.getSomeObject();
    });

So another workaround would be to skip foreverFrame from the transport altogether when publishing to IIS 7.5 like so:

    $.connection.hub.start({ transport: ['serverSentEvents','longPolling']}).done(function () {
        console.log('Now connected, connection ID=' + $.connection.hub.id);
        myHubProxy.server.getSomeObject();
    });

Here's a sample project which reproduces the issue: https://onedrive.live.com/redir?resid=D4E23CA0ED671323!1466815&authkey=!AEAEBajrZx3y8e4&ithint=folder%2csln

like image 642
Jarem Avatar asked Jul 23 '15 05:07

Jarem


2 Answers

You will never get WebSockets with SignalR on IIS 7.5

See the docs - You will need win8/2012 Server and IIS8

Edit: Sorry for not answering the stuff about serialization, but since you mention that you want websockets I thought it was important to mention this...

like image 20
Uffe Avatar answered Oct 10 '22 03:10

Uffe


The problem is that using ForeverFrame, the JSON parsing is done in another frame, and the resulted array is not instanceof Array in the current frame, as described here.

In order to bypass that, SignalR (starting from 2.1.0) allows you to supply your own JSON parser to ForeverFrame:

$.connection.hub.json = {
    parse: function(text, reviver) {
        console.log("Parsing JSON");
        return window.JSON.parse(text, reviver);
    },
    stringify: function(value, replacer, space) {
        return window.JSON.stringify(value, replacer, space);
    }
};

By calling window.JSON.parse, you ensure that the array will be parsed correctly.

like image 117
Tomer Avatar answered Oct 10 '22 04:10

Tomer