4
votes

I'm fairly new to SignalR so I will try to be as clear as possible.Here is what I'm trying to accomplish :

1.Create self-hosted web application using SignalR
2.Create WPF rich client which will communicate with above-mentioned server app.
3.Use strongly typed request/response messages which inherit from base types respectively to pass data from client to server and vice-versa.

Here are some definitions of my request/response messages :

//Base request message
public class HubRequestMessageBase
{

}

//Base response message
public class HubResponseMessageBase
{
    public bool Success { get; set; }
    public Exception Error { get; set; }
}

//Request message to query node by name
public class QueryNodeRequest : HubRequestMessageBase
{
    public string Name { get; set; }
    public Guid Identifier { get; set; }
}

//Response message carrying metadata for the specified node
public class QueryNodeResponse : HubResponseMessageBase
{
   public NodeMetadata NodeMetadata { get; set; }
}

Now, if I define on of my server methods as follows :

//Main method for handling client requests
public void HandleClientRequest(HubRequestMessageBase message)
{
    //Omitted for brevity
}

and call server method from client like this :

internal async void QueryNode(string name)
{
    QueryNodeRequest req = new QueryNodeRequest();
    req.Name = name;

    await HubProxy.Invoke("HandleClientRequest", new object[] { req });
}

on the server side, I still get HubRequestMessageBase as type of message parameter inside HandleClientRequest method instead of QueryNodeRequest.Now, after some digging, I learned that SignalR does not handle polymorphism by default (or message does not get serialized/deserialized properly, to type I would expect - in this case QueryNodeRequest which inherits from HubRequestMessageBase).

My question is : Is there any possibility to accomplish this somehow utilizing JsonSerializer settings on both client and server? Note that I already tried following code (and also some variants) without any success (on the server):

GlobalHost.DependencyResolver.Register(typeof(JsonSerializer),
                                                () => JsonSerializer.Create(new JsonSerializerSettings
                                                {
                                                    TypeNameHandling = TypeNameHandling.All
                                                }));

Thanks in advance!

Cheers, Civa

2

2 Answers

1
votes

I have been able to solve this.

The root cause of the issue is this line in the signalr code base https://github.com/SignalR/SignalR/blob/dev/src/Microsoft.AspNet.SignalR.Core/Json/JRawValue.cs#L28 where signalr uses a default serializer and does not let you inject your own.

My solution is to bypass that serializer by making the type of my parameter JObject and then turing that JObject into my concrete type in the hub itself by using a customized instance of the serializer, and a JTokenReader based on the JObject

My Hub Method (note that ClientCommand is an abstract base class for all my possible commands)

public async Task DispatchCommand(Envelope envelope)
{
    var settings = new JsonSerializerSettings
                   {
                       TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
                       TypeNameHandling = TypeNameHandling.Objects
                   };
    var serializer = JsonSerializer.Create(settings);
    var c = (ClientCommand) serializer.Deserialize(new JTokenReader(envelope.Command), typeof(ClientCommand));

    await commandDispatcher.SendCommand(c);
}

The Envelope type

public sealed class Envelope
{
    public JObject Command { get; set; }
}

By using TypeNameHandling = TypeNameHandling.Objects on the JsonSerializer all I need to do is include the $type property in my object that I send from the client.

0
votes

You would have to implement IParameterResolver and register it. The default parameter resolver simply converts JValues to target parameter types.