I would need some guidelines from SignalR developers what is the best way to tweak HUB method's parameters serialization.
I started migrating my project from WCF polling duplex (Silverlight 5 - ASP.NET 4.5) to SignalR (1.1.2). The message (data contract) is polymorphic based on interfaces. (Like IMessage, MessageA : IMessage, etc. - there is actually a hierarchy of interfaces implemented by classes but it is not of much significancy for the question). (I know polymorphic objects are not good for clients but the client will handle it as JSON and mapping to objects is done only on the server side or client if it is .NET/Silverlight)
On the hub I defined method like this:
public void SendMessage(IMessage data) { .. }
I created custom JsonConverters and verified the messages could be serialized/deserialized using Json.NET. Then I replaced JsonNetSerializer in DependencyResolver with proper settings. Similarly on the Silverlight client-side. So far so good.
But when I sent the message from client to server (message got serialized to JSON correctly - verified in Fiddler), the server returned an error that the parameter cannot be deserialized. With help of debugger, I found a bug in SignalR (JRawValue class responsible for deserialization of parameter creates internally its own instance of JsonSerializer ignoring the provided one). Seemed to be quite easy fix by replacing
var settings = new JsonSerializerSettings
{
MaxDepth = 20
};
var serializer = JsonSerializer.Create(settings);
return serializer.Deserialize(jsonReader, type);
with
var serializer = GlobalHost.DependencyResolver.Resolve<IJsonSerializer>();
return serializer.Parse(jsonReader, type);
but I also found that the interface IJsonSerializer is going to be removed in a future version of SignalR. What I need, basically, is to get either raw JSON (or byte stream) from HUB method so I could deserialize it by myself or a possibility to tweak the serializer by specifying converters, etc.
For now, I ended up with defining the method with JObject parameter type:
public void SendMessage(JObject data)
followed by manual deserialization of data using
JObject.ToObject<IMessage>(JsonSerializer)
method. But I would prefer to customize the serializer and having the type/interface on the hub method. What is the "right way" to do it regarding design of the next SignalR?
I also found useful to have a possibility to send back to clients raw JSON from my code, i.e. so that the object is not serialized again by SignalR again. How could I achieve this?
If you use connection API instead of Hub API, you can handle OnReceive event and get requests as raw JSON (string). Take a look at this example.
Ability to send pre-serialized data to clients using Hub API was added in 2.x version and I don't know about any way to do that in 1.x (see github issue)
I tried changing the client and server serialization configuration using the
EnableJsonTypeNameHandlingConverter
published here plus the following client and server code for a bidirectional connection.As you can see, there is code to set up custom serialization on both the client and server... but it doesn't work!
SignalR calls the lambda passed to
GlobalHost.DependencyResolver
no less than 8 times, yet in the end it ignores the serializer provided.I couldn't find any documentation on SignalR parameter serialization, so I used Rider's decompiling debugger to help find out what was going on.
Inside SignalR there's a
HubRequestParser.Parse
method which uses the correctJsonSerializer
, but it does not actually deserialize the parameters. The parameters are deserialized later inDefaultParameterResolver.ResolveParameter()
which indirectly callsCreateDefaultSerializerSettings()
in the following call stack:In the SignalR source code the problem is evident:
So SignalR uses your custom (de)serializer for part of its job, just not for parameter deserialization.
What I can't figure out is that the 2015 answer on this other question has 8 votes, which seems to imply that this solution worked at some point for somebody in the last 4 years, but if so there must be a trick to it that we don't know about.
Perhaps the .NET Core version of SignalR fixes this problem. It looks like that version has been refactored significantly and no longer has a
DefaultParameterResolver.cs
file. Anyone care to check?