SignalR Typenamehandling

本文关键字:Typenamehandling SignalR | 更新日期: 2023-09-27 18:10:29

我试图让SignalR与自定义JsonSerializerSettings一起工作,特别是我试图设置TypeNameHandling = TypeNameHandling.Auto

问题似乎是,SignalR使用hubConnection.JsonSerializerGlobalHost.DependencyResolver.Resolve<JsonSerializer>()的设置作为其内部数据结构,然后导致各种破坏(当我将TypeNameHandling.All设置为最粗糙的例子时,内部服务器崩溃,但TypeNameHandling.Auto我也会遇到问题,特别是当涉及IProgress<>回调时)。

是否有任何解决方法或我只是做错了?

示例代码演示:

服务器:

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" };
    }
}
客户:

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());
    }
共享:

public static class ConverterSettings
{
    public static JsonSerializer GetSerializer()
    {
        return JsonSerializer.Create(new JsonSerializerSettings()
        {
            TypeNameHandling = TypeNameHandling.All
        });
    }
}

SignalR Typenamehandling

这可以通过利用您的类型和SignalR类型位于不同程序集中的事实来完成。这个想法是创建一个JsonConverter,应用于程序集中的所有类型。当第一次在对象图中遇到来自某个程序集的类型(可能作为根对象)时,转换器将临时设置jsonSerializer.TypeNameHandling = TypeNameHandling.Auto,然后继续对该类型进行标准序列化,在持续时间内禁用自身以防止无限递归:

public class PolymorphicAssemblyRootConverter : JsonConverter
{
    [ThreadStatic]
    static bool disabled;
    // Disables the converter in a thread-safe manner.
    bool Disabled { get { return disabled; } set { disabled = value; } }
    public override bool CanWrite { get { return !Disabled; } }
    public override bool CanRead { get { return !Disabled; } }
    readonly HashSet<Assembly> assemblies;
    public PolymorphicAssemblyRootConverter(IEnumerable<Assembly> assemblies)
    {
        if (assemblies == null)
            throw new ArgumentNullException();
        this.assemblies = new HashSet<Assembly>(assemblies);
    }
    public override bool CanConvert(Type objectType)
    {
        return assemblies.Contains(objectType.Assembly);
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        using (new PushValue<bool>(true, () => Disabled, val => Disabled = val)) // Prevent infinite recursion of converters
        using (new PushValue<TypeNameHandling>(TypeNameHandling.Auto, () => serializer.TypeNameHandling, val => serializer.TypeNameHandling = val))
        {
            return serializer.Deserialize(reader, objectType);
        }
    }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        using (new PushValue<bool>(true, () => Disabled, val => Disabled = val)) // Prevent infinite recursion of converters
        using (new PushValue<TypeNameHandling>(TypeNameHandling.Auto, () => serializer.TypeNameHandling, val => serializer.TypeNameHandling = val))
        {
            // Force the $type to be written unconditionally by passing typeof(object) as the type being serialized.
            serializer.Serialize(writer, value, typeof(object));
        }
    }
}
public struct PushValue<T> : IDisposable
{
    Action<T> setValue;
    T oldValue;
    public PushValue(T value, Func<T> getValue, Action<T> setValue)
    {
        if (getValue == null || setValue == null)
            throw new ArgumentNullException();
        this.setValue = setValue;
        this.oldValue = getValue();
        setValue(value);
    }
    #region IDisposable Members
    // By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
    public void Dispose()
    {
        if (setValue != null)
            setValue(oldValue);
    }
    #endregion
}

然后在启动时,将此转换器添加到默认的JsonSerializer,传入您希望应用"$type"的程序集。

如果由于某种原因不方便在启动时传递程序集列表,您可以通过objectType.Namespace启用转换器。您指定的名称空间中的所有类型都将自动使用TypeNameHandling.Auto进行序列化。

或者,您可以引入一个Attribute,它以程序集、类或接口为目标,并在与适当的转换器结合时启用TypeNameHandling.Auto:

public class EnableJsonTypeNameHandlingConverter : JsonConverter
{
    [ThreadStatic]
    static bool disabled;
    // Disables the converter in a thread-safe manner.
    bool Disabled { get { return disabled; } set { disabled = value; } }
    public override bool CanWrite { get { return !Disabled; } }
    public override bool CanRead { get { return !Disabled; } }
    public override bool CanConvert(Type objectType)
    {
        if (Disabled)
            return false;
        if (objectType.Assembly.GetCustomAttributes<EnableJsonTypeNameHandlingAttribute>().Any())
            return true;
        if (objectType.GetCustomAttributes<EnableJsonTypeNameHandlingAttribute>(true).Any())
            return true;
        foreach (var type in objectType.GetInterfaces())
            if (type.GetCustomAttributes<EnableJsonTypeNameHandlingAttribute>(true).Any())
                return true;
        return false;
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        using (new PushValue<bool>(true, () => Disabled, val => Disabled = val)) // Prevent infinite recursion of converters
        using (new PushValue<TypeNameHandling>(TypeNameHandling.Auto, () => serializer.TypeNameHandling, val => serializer.TypeNameHandling = val))
        {
            return serializer.Deserialize(reader, objectType);
        }
    }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        using (new PushValue<bool>(true, () => Disabled, val => Disabled = val)) // Prevent infinite recursion of converters
        using (new PushValue<TypeNameHandling>(TypeNameHandling.Auto, () => serializer.TypeNameHandling, val => serializer.TypeNameHandling = val))
        {
            // Force the $type to be written unconditionally by passing typeof(object) as the type being serialized.
            serializer.Serialize(writer, value, typeof(object));
        }
    }
}
[System.AttributeUsage(System.AttributeTargets.Assembly | System.AttributeTargets.Class | System.AttributeTargets.Interface)]
public class EnableJsonTypeNameHandlingAttribute : System.Attribute
{
    public EnableJsonTypeNameHandlingAttribute()
    {
    }
}

注意-测试了各种测试用例,但没有SignalR本身,因为我目前没有安装它。

TypeNameHandling注意事项

当使用TypeNameHandling时,请注意Newtonsoft文档中的警告:

当应用程序从外部源反序列化JSON时,应该谨慎使用

TypeNameHandling。当使用非None的值进行反序列化时,应该使用自定义SerializationBinder验证传入类型。

关于为什么需要这样做的讨论,请参见Newtonsoft Json中的TypeNameHandling注意事项。

我知道这是一个相当古老的问题,并且有一个公认的答案。

然而,我有一个问题,我不能使服务器读取收到的json正确,也就是说,它只读取基类

然而,解决这个问题的方法很简单:

我在参数类之前添加了这一行:

[JsonConverter(typeof(PolymorphicAssemblyRootConverter), typeof(ABase))]
public class ABase
{
}
public class ADerived : ABase
{
    public AInner[] DifferentObjects { get; set;}
}
public class AInner
{
}
public class AInnerDerived : AInner
{
}
...
public class PolymorphicAssemblyRootConverter: JsonConverter
{
    public PolymorphicAssemblyRootConverter(Type classType) :
       this(new Assembly[]{classType.Assembly})
    {
    }
    // Here comes the rest of PolymorphicAssemblyRootConverter
}

不需要在客户端的代理连接上设置JsonSerializer并将其添加到GlobalHost.DependencyResolver中。

我花了很长时间才弄清楚,我在客户端和服务器上都使用SignalR 2.2.1。

这比你想的要简单。我遇到了同样的问题,试图序列化派生类,但没有从派生类型发送属性。

微软在这里说:https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to?pivots=dotnet-5-0#serialize-properties-of-derived-classes

如果你指定你的模型类型为"Object"而不是强类型的"基类型"它将被序列化,然后发送属性。如果你有一个大的物体图你需要一直向下。它违反了强类型(类型安全),但它允许技术在不更改代码的情况下将数据发送回,只更改您的模型。

为例:

public class NotificationItem
{
   public string CreatedAt { get; set; }
}
public class NotificationEventLive : NotificationItem
{
    public string Activity { get; set; }
    public string ActivityType { get; set;}
    public DateTime Date { get; set;}
}

如果你的主模型使用这个类型是这样的:

public class UserModel
{
    public string Name { get; set; }
    
    public IEnumerable<object> Notifications { get; set; } // note the "object"
    
    ..
}

如果你尝试

var model = new UserModel() { ... }
JsonSerializer.Serialize(model); 

您将发送派生类型中的所有属性。

解决方案是不完美的,因为你失去了强类型模型,但如果这是一个ViewModel被传递给javascript,这是在SignalR使用的情况下,它工作得很好。

相关文章:
  • 没有找到相关文章