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:
When instead, on first break, I should have the first item in my array like it does on my local machine:
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:
However, on my local machine, it works as expected:
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