SignalR Typenamehandling
我试图让SignalR与自定义JsonSerializerSettings一起使用它的有效负载,特别是我试图设置TypeNameHandling = TypeNameHandling.Auto
。
问题似乎是,SignalR使用hubConnection.JsonSerializer
和hubConnection.JsonSerializer
GlobalHost.DependencyResolver.Resolve()
作为其内部数据结构,然后导致各种破坏(当我设置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("Send"); Console.WriteLine(result.GetType()); }
共享:
public static class ConverterSettings { public static JsonSerializer GetSerializer() { return JsonSerializer.Create(new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All }); } }
这可以通过利用类型和SignalR类型在不同程序集中的事实来完成。 我们的想法是创建一个适用于程序 JsonConverter
所有类型的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 assemblies; public PolymorphicAssemblyRootConverter(IEnumerable assemblies) { if (assemblies == null) throw new ArgumentNullException(); this.assemblies = new HashSet (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(true, () => Disabled, val => Disabled = val)) // Prevent infinite recursion of converters using (new PushValue(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(true, () => Disabled, val => Disabled = val)) // Prevent infinite recursion of converters using (new PushValue(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 : IDisposable { Action setValue; T oldValue; public PushValue(T value, Func getValue, Action 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().Any()) return true; if (objectType.GetCustomAttributes (true).Any()) return true; foreach (var type in objectType.GetInterfaces()) if (type.GetCustomAttributes (true).Any()) return true; return false; } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { using (new PushValue(true, () => Disabled, val => Disabled = val)) // Prevent infinite recursion of converters using (new PushValue(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(true, () => Disabled, val => Disabled = val)) // Prevent infinite recursion of converters using (new PushValue(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以外的值进行反序列化时,应使用自定义SerializationBindervalidation传入类型。
有关可能需要这样做的讨论,请参阅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。
- 如何将zip文件内容提取到.NET 4.5中的文件夹中
- 如何在Windows 8.1应用程序和Windows Phone 8应用程序中使用SOAP?
- HttpContext在global.asax.cs中不起作用
- 使用IReadOnlyCollection 而不是IEnumerable 来获取参数,以避免可能的多次枚举
- 具有动态maxCount的SemaphoreSlim
- 如何以编程方式检测Windows Phone 8.1操作系统版本?
- 通过部分匹配其名称来查找多个控件
- 当提供的字符串不为null时,string.IsNullOrEmpty返回true
- IL中的属性getter / setter的方法命名是否标准化?