Search code examples
dictionaryasp.net-core-2.0case-sensitivelowercaseasp.net-core-signalr

Why does SignalR convert the first letters of Dictionary keys to lower case when it serializes the dictionary and sends it to clients?


This is what is being sent from the server to the client:

public class BattleHub : Hub
{
    public override async Task OnConnectedAsync()
    {
        await Clients.Client(Context.ConnectionId).SendAsync("ConfigurationInfo",
            new Dictionary<string, string> { { "WtF", "WtF" } });

        await base.OnConnectedAsync();
    }
}

This is what the client receives:

{"wtF":"WtF"}

As evidenced by this following client code:

const battle_connection = new signalR.HubConnectionBuilder().withUrl("/battlehub").build()

battle_connection.on("ConfigurationInfo", (bigDict) => {
    window.alert(JSON.stringify(bigDict))
})

Notice the change of the case in the first letter of the dictionary key? "WtF" was supposed to be sent, but "wtF" was sent instead.

Why does this happen? Is there any way to prevent it?

Curiously, this only happens with dictionary keys: as you can see, values are left unchanged. Also sending a single string only, not wrapped in a dictionary, does not result in the first letter being turned to lower case. A List<string> is also being sent correctly.

The caseness of all letters of all keys are actually meaningful here, so it makes the application wrong if any of them is forcibly converted to lowercase! While of course we can think of many more or less ugly ways to work around the problem, is there any way to switch this conversion off in SignalR serializer?

ASP .NET Core 2.1


Solution

  • Why does this happen?

    "We use camelCase for names in JSON as it fits JavaScript patterns better."
    aspnet/SignalR#1415 (comment)

    Is there any way to prevent it?

    ... is there any way to switch this conversion off in SignalR serializer?

    Yes.

    services
        .AddSignalR()
        .AddJsonProtocol(options =>
        {
            options.PayloadSerializerSettings.ContractResolver = new DefaultContractResolver();
        });