C
C#5mo ago
Verdur

✅ Complicated error with C# SignalR client

Hi, I'm trying to receive base class in signalr using connection.on function. My base class has inherits and they all may be sent to the client. Problem is in connection.on<T> method. It converts whatever JSON serialized data comes to it straight to the T type, ignoring T's dervied types. I have tested it by wrapping my Foo class in wrapper class Bar and passing it to the method:
c#
connection.On<Bar>(...)
c#
connection.On<Bar>(...)
This way, if I store Baz: Foo type in Bar and pass it to client, it will recieve Bar with foo field having Baz type. Is there a way to make connection.on<T> convert incoming data not just to T, but to it's derivative too without using wrapper classes?
15 Replies
Denis
Denis5mo ago
What you need is a type discriminator Alternatively, you could write a custom json converter, but that can get ugly very fast, if the only thing you need it for is type discrimination
Verdur
Verdur5mo ago
I have JsonDerivedType attribute on my base class, but connection.on still refuses to convert data to the derived classes, it just stops converting at the type you passed in generic
Denis
Denis5mo ago
And when serializing, each type inheriting your base has a $type json element? I've recently faced the same issue and this helped me solve it
Verdur
Verdur5mo ago
Just checked it and looks like problem is in serializer indeed. It doesn't provide $type element if I'm serailizing base class, but if I serialize wrapper class that contains base class it does provide $type element
Denis
Denis5mo ago
Consider the following hierarchy:
public interface IPet
{
string Name { get; }
}

public class Dog : IPet
{
public required string Name { get; init; }
public bool CanFetch { get; set; } = true;
}

public class Cat : IPet
{
public required string Name { get; init; }
public bool CanMeow { get; set; } = true;
}
public interface IPet
{
string Name { get; }
}

public class Dog : IPet
{
public required string Name { get; init; }
public bool CanFetch { get; set; } = true;
}

public class Cat : IPet
{
public required string Name { get; init; }
public bool CanMeow { get; set; } = true;
}
And the following collection:
var pets = new List<IPet>
{
new Dog { Name = "Snoopy", CanFetch = false },
new Cat { Name = "Garfield", CanMeow = true }
};
var pets = new List<IPet>
{
new Dog { Name = "Snoopy", CanFetch = false },
new Cat { Name = "Garfield", CanMeow = true }
};
Serializing the pets as is will produce this JSON:
[
{
"Name": "Snoopy"
},
{
"Name": "Garfield"
}
]
[
{
"Name": "Snoopy"
},
{
"Name": "Garfield"
}
]
Decorating the IPet interface with:
[JsonPolymorphic]
[JsonDerivedType(typeof(Dog), nameof(Dog))]
[JsonDerivedType(typeof(Cat), nameof(Cat))]
[JsonPolymorphic]
[JsonDerivedType(typeof(Dog), nameof(Dog))]
[JsonDerivedType(typeof(Cat), nameof(Cat))]
....and serializing again shall produce the following result:
[
{
"$type": "Dog",
"Name": "Snoopy",
"CanFetch": false
},
{
"$type": "Cat",
"Name": "Garfield",
"CanMeow": true
}
]
[
{
"$type": "Dog",
"Name": "Snoopy",
"CanFetch": false
},
{
"$type": "Cat",
"Name": "Garfield",
"CanMeow": true
}
]
Are you using System.Text.Json or something else? I forgot to provide the code for serializing
var json = JsonSerializer.Serialize(pets);
var json = JsonSerializer.Serialize(pets);
Verdur
Verdur5mo ago
Yes, I'm using System.Text.Json. There is no problem with manual serialization, but when I pass a value to signalr's SendAsync function it doesn't serialize it's value with $type property Here's simple example to reproduce this bug: Models:
c#
[JsonDerivedType(typeof(Bar), nameof(Bar))]
public class Foo
{
public int IntField { get; set; }
}

public class Bar: Foo
{
public string StringField { get; set; } = null!;
}

public class FooWrapper
{
public Foo Foo { get; set; } = null!;
}
c#
[JsonDerivedType(typeof(Bar), nameof(Bar))]
public class Foo
{
public int IntField { get; set; }
}

public class Bar: Foo
{
public string StringField { get; set; } = null!;
}

public class FooWrapper
{
public Foo Foo { get; set; } = null!;
}
Hub:
c#
public class MyHub : Hub
{
public void SendFoo()
{
Foo foo = new Bar { IntField = 1, StringField = "hello" };

var wrapper = new FooWrapper { Foo = foo };
Clients.All.SendAsync("ReceiveFoo", wrapper);
Clients.All.SendAsync("ReceiveFoo", foo);
}
}
c#
public class MyHub : Hub
{
public void SendFoo()
{
Foo foo = new Bar { IntField = 1, StringField = "hello" };

var wrapper = new FooWrapper { Foo = foo };
Clients.All.SendAsync("ReceiveFoo", wrapper);
Clients.All.SendAsync("ReceiveFoo", foo);
}
}
When I'm sending foo, json serializator doesn't include $type property to json. When I'm sending wrapper, json serializator includes $type property to json.
Verdur
Verdur5mo ago
I've checked it via WIreshark
No description
No description
Verdur
Verdur5mo ago
if I serialize and deserialize manually, then derived types are resolved properly
No description
Denis
Denis5mo ago
seems like a bug in signalr 💀 https://github.com/dotnet/aspnetcore/issues/52342
GitHub
The JSON returned by SignalR does not contain the $type field · Iss...
Is there an existing issue for this? I have searched the existing issues Describe the bug When serializing descendant types, for methods that take a single (not a collection) parameter of the base ...
Verdur
Verdur5mo ago
Oh, thank you for sending a link, I was thinking about creating an issue myself 😅
Denis
Denis5mo ago
this kind of an issue is very unfortunate... Is it possible for you to ser/deser your self and send JSON directly via SignalR?
Verdur
Verdur5mo ago
Yes, I can do that or use wrapper type for now. Thank you for help, I think this problem is now resolved
Denis
Denis5mo ago
$close
MODiX
MODiX5mo ago
Use the /close command to mark a forum thread as answered