I am trying to get SignalR to work with custom JsonSerializerSettings for its payload, specifically I'm trying to set TypeNameHandling = TypeNameHandling.Auto
.
The problem seems to be, that SignalR uses the settings in hubConnection.JsonSerializer
and GlobalHost.DependencyResolver.Resolve<JsonSerializer>()
for its internal data structures as well which then causes all kinds of havoc (internal server crashes when I set TypeNameHandling.All
as the most crass example, but with TypeNameHandling.Auto
I also get problems, particularly when IProgress<>
callbacks are involved).
Is there any workaround or am I just doing it wrong?
Sample code to demonstrate:
Server:
class Program
{
static void Main(string[] args)
{
using (WebApp.Start("http://localhost:8080"))
{
Console.ReadLine();
}
}
}
public class Startup
{
public void Configuration(IAppBuilder app)
{
var hubConfig = new HubConfiguration()
{
EnableDetailedErrors = true
};
GlobalHost.DependencyResolver.Register(typeof(JsonSerializer), ConverterSettings.GetSerializer);
app.MapSignalR(hubConfig);
}
}
public interface IFoo
{
string Val { get; set; }
}
public class Foo : IFoo
{
public string Val { get; set; }
}
public class MyHub : Hub
{
public IFoo Send()
{
return new Foo { Val = "Hello World" };
}
}
Client:
class Program
{
static void Main(string[] args)
{
Task.Run(async () => await Start()).Wait();
}
public static async Task Start()
{
var hubConnection = new HubConnection("http://localhost:8080");
hubConnection.JsonSerializer = ConverterSettings.GetSerializer();
var proxy = hubConnection.CreateHubProxy("MyHub");
await hubConnection.Start();
var result = await proxy.Invoke<IFoo>("Send");
Console.WriteLine(result.GetType());
}
Shared:
public static class ConverterSettings
{
public static JsonSerializer GetSerializer()
{
return JsonSerializer.Create(new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All
});
}
}
I know that this is a rather old thread and that there is an accepted answer.
However, I had the problem that I could not make the Server read the received json correctly, that is it did only read the base classes
However, the solution to the problem was quite simple:
I added this line before the parameter classes:
No need to set JsonSerializer on the proxy connection of the client and add it to the GlobalHost.DependencyResolver.
It took me a long time to figure it out, I am using SignalR 2.2.1 on both client and server.
This can be done by taking advantage of the fact that your types and the SignalR types are in different assemblies. The idea is to create a
JsonConverter
that applies to all types from your assemblies. When a type from one of your assemblies is first encountered in the object graph (possibly as the root object), the converter would temporarily setjsonSerializer.TypeNameHandling = TypeNameHandling.Auto
, then proceed with the standard serialization for the type, disabling itself for the duration to prevent infinite recursion:Then in startup you would add this converter to the default
JsonSerializer
, passing in the assemblies for which you want"$type"
applied.Update
If for whatever reason it's inconvenient to pass the list of assemblies in at startup, you could enable the converter by
objectType.Namespace
. All types living in your specified namespaces would automatically get serialized withTypeNameHandling.Auto
.Alternatively, you could introduce an
Attribute
which targets an assembly, class or interface and enablesTypeNameHandling.Auto
when combined with the appropriate converter:Note - tested with various test cases but not SignalR itself since I don't currently have it installed.
TypeNameHandling
CautionWhen using
TypeNameHandling
, do take note of this caution from the Newtonsoft docs:For a discussion of why this may be necessary, see TypeNameHandling caution in Newtonsoft Json.