SignalR 类型名称处理 [英] SignalR Typenamehandling
问题描述
我正在尝试让 SignalR 为其有效负载使用自定义 JsonSerializerSettings,特别是我正在尝试设置 TypeNameHandling = TypeNameHandling.Auto
.
I am trying to get SignalR to work with custom JsonSerializerSettings for its payload, specifically I'm trying to set TypeNameHandling = TypeNameHandling.Auto
.
问题似乎是,SignalR 也使用 hubConnection.JsonSerializer
和 GlobalHost.DependencyResolver.Resolve
中的设置作为其内部数据结构然后导致各种严重破坏(当我将 TypeNameHandling.All
设置为最粗鲁的示例时,内部服务器崩溃,但是使用 TypeNameHandling.Auto
我也会遇到问题,特别是当 IProgress<>
涉及回调).
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?
示例代码演示:
服务器:
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 类型在不同的组件.这个想法是创建一个 JsonConverter
适用于程序集中的所有类型.当第一次在对象图中遇到来自某个程序集的类型(可能作为根对象)时,转换器会临时设置 jsonSerializer.TypeNameHandling = TypeNameHandling.Auto
,然后继续对该类型进行标准序列化,在持续时间内禁用自身以防止无限递归:>
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 set jsonSerializer.TypeNameHandling = TypeNameHandling.Auto
, then proceed with the standard serialization for the type, disabling itself for the duration to prevent infinite recursion:
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"
的程序集.
Then in startup you would add this converter to the default JsonSerializer
, passing in the assemblies for which you want "$type"
applied.
更新
如果由于某种原因在启动时传递程序集列表不方便,您可以通过 objectType.Namespace
.位于指定命名空间中的所有类型都将使用 TypeNameHandling.Auto
自动序列化.
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 with TypeNameHandling.Auto
.
或者,您可以引入一个 Attribute
,其中 目标一个程序集、类或接口,并在与适当的转换器结合时启用TypeNameHandling.Auto
:
Alternatively, you could introduce an Attribute
which targets an assembly, class or interface and enables TypeNameHandling.Auto
when combined with the appropriate converter:
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 本身进行测试,因为我目前没有安装它.
Note - tested with various test cases but not SignalR itself since I don't currently have it installed.
TypeNameHandling
注意
TypeNameHandling
Caution
在使用 TypeNameHandling
时,请注意 Newtonsoft 文档:
When using TypeNameHandling
, do take note of this caution from the Newtonsoft docs:
当您的应用程序从外部源反序列化 JSON 时,应谨慎使用 TypeNameHandling.使用 None 以外的值反序列化时,应使用自定义 SerializationBinder 验证传入类型.
TypeNameHandling should be used with caution when your application deserializes JSON from an external source. Incoming types should be validated with a custom SerializationBinder when deserializing with a value other than None.
有关为什么需要这样做的讨论,请参阅Newtonsoft Json 中的 TypeNameHandling 警告.
For a discussion of why this may be necessary, see TypeNameHandling caution in Newtonsoft Json.
这篇关于SignalR 类型名称处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!