SignalR Typenamehandling [英] SignalR Typenamehandling
问题描述
我试图让SignalR用其有效载荷定制JsonSerializerSettings工作,特别是我想设置 TypeNameHandling = TypeNameHandling.Auto
。
这个问题似乎是,这SignalR在 hubConnection.JsonSerializer
中的设置和 GlobalHost.DependencyResolver.Resolve< JsonSerializer>()
为它的内部数据结构以及进而导致各种混乱的(内部服务器崩溃时我设置 TypeNameHandling.All
作为最粗鲁的例子,但 TypeNameHandling.Auto
我也得问题,特别是当 IProgress<>
回调是介入)
有任何解决方法还是我只是做错了
示例代码来演示:
服务器:
类节目
{
静态无效的主要(字串[] args)使用
{
(WebApp.Start(HTTP://本地主机:8080))
{
到Console.ReadLine();
}
}
}
公共类启动
{
公共无效配置(IAppBuilder应用程序)
{
VAR hubConfig =新HubConfiguration()
{
EnableDetailedErrors = TRUE
};
GlobalHost.DependencyResolver.Register(typeof运算(JsonSerializer),ConverterSettings.GetSerializer);
app.MapSignalR(hubConfig);
}
}
公共接口IFoo的
{
串的Val {获得;组; }
}
公共类Foo:IFoo的
{
公共字符串的Val {获得;组; }
}
公共类MyHub:集线器
{
公共IFoo的发送()
{
返回新的Foo {瓦尔=你好世界};
}
}
客户:
类节目
{
静态无效的主要(字串[] args)
{
Task.Run(异步( )=方式>伺机开始())等待();
}
公共静态异步任务开始()
{
变种hubConnection =新HubConnection(HTTP://本地主机:8080);
hubConnection.JsonSerializer = ConverterSettings.GetSerializer();
变种代理= hubConnection.CreateHubProxy(MyHub);
等待hubConnection.Start();
VAR的结果=等待proxy.Invoke<的IFoo>(发送);
Console.WriteLine(result.GetType());
}
分享:
公共静态类ConverterSettings
{
公共静态JsonSerializer GetSerializer()
{
返回JsonSerializer.Create(新JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All
});
}
}
这可以通过采取这一事实的类型和SignalR类型在不同的组件的。这个想法是创建一个 JsonConverter
适用于的所有类型从组件< / em>的。当从组件中的一个类型的对象图(可能为根对象)是第一次遇到,转换器将临时设置的 jsonSerializer.TypeNameHandling = TypeNameHandling.Auto
,然后与该类型的标准系列化着手,禁止本身的持续时间防止无限递归:
公共类PolymorphicAssemblyRootConverter:JsonConverter
{
[ThreadStatic]
静布尔禁用;
//禁止以线程安全的方式转换器。
布尔残疾人{{返回禁用; } {设置禁用=价值; }}
公众覆盖布尔CanWrite {{返回禁用!; }}
公众覆盖布尔{的CanRead获得{返回禁用!; }}
只读的HashSet<大会>组件;
公共PolymorphicAssemblyRootConverter(IEnumerable的<大会>组件)
{
如果(组件== NULL)
抛出新的ArgumentNullException();
this.assemblies =新的HashSet<大会>(组件);
}
公众覆盖布尔CanConvert(类型的objectType)
{
返回assemblies.Contains(objectType.Assembly);
}
公众覆盖对象ReadJson(JsonReader读者,类型的objectType,对象existingValue,JsonSerializer串行)
{
使用(新PushValue<布尔>(真的,( )=>禁用,VAL =>禁用= VAL))//使用(新PushValue<的转换器
防止无限递归; TypeNameHandling>(TypeNameHandling.Auto,()=> serializer.TypeNameHandling,VAL => ; serializer.TypeNameHandling = VAL))
{
返回serializer.Deserialize(读者的objectType);
}
}
公共覆盖无效WriteJson(JsonWriter作家,对象的值,JsonSerializer串行)
{使用(新PushValue<
;布尔>(真()=>禁用,VAL =>禁用= VAL))//使用(新PushValue< TypeNameHandling>(TypeNameHandling.Auto,()的转换器
防止无限递归=> serializer.TypeNameHandling,VAL = GT; serializer.TypeNameHandling = VAL))
{
//强制$类型进行传递的typeof(对象)作为类型被序列化无条件地写入。
serializer.Serialize(作家,值的typeof(对象));
}
}
}
公共结构PushValue< T> :IDisposable的
{
动作< T> setValue方法; $ B $(B T)的属性oldValue;
公共PushValue(吨价,Func键< T>的getValue,动作< T>的setValue)
{
如果(的getValue == NULL ||的setValue == NULL)
抛出新的ArgumentNullException();
this.setValue = setValue方法;
this.oldValue =的getValue();
的setValue(值);
}
#地区IDisposable的会员
//使用一次性的结构,我们避免分配和释放一个终结类的一个实例的开销。
公共无效的Dispose()
{
如果
的setValue(属性oldValue)(setValue方法!= NULL);
}
#endregion
}
然后在启动时,你会此转换器添加到默认 JsonSerializer
,传递您想要的程序集$键入
应用。
更新
如果出于某种原因它的不方便通过名单在启动的组件,你可以使通过的 objectType.Namespace
。所有住在你指定的命名空间的类型,将自动获取序列与 TypeNameHandling.Auto
。
另外,您可以引入属性
其中的目标的程序集,类或接口,使 TypeNameHandling.Auto
当相应的转换器结合使用:
公共类EnableJsonTypeNameHandlingConverter:JsonConverter
{
[ThreadStatic]
静态布尔禁用;
//禁止以线程安全的方式转换器。
布尔残疾人{{返回禁用; } {设置禁用=价值; }}
公众覆盖布尔CanWrite {{返回禁用!; }}
公众覆盖布尔{的CanRead获得{返回禁用!; }}
公众覆盖布尔CanConvert(类型的objectType)
{
如果(禁用)
返回FALSE;
如果(objectType.Assembly.GetCustomAttributes< EnableJsonTypeNameHandlingAttribute>()任何()。)
返回真;
如果(objectType.GetCustomAttributes< EnableJsonTypeNameHandlingAttribute>(真)。任何())
返回真;
的foreach(在objectType.GetInterfaces VAR类型())
如果(type.GetCustomAttributes< EnableJsonTypeNameHandlingAttribute>(真)。任何())
返回真;
返回FALSE;
}
公众覆盖对象ReadJson(JsonReader读者,类型的objectType,对象existingValue,JsonSerializer串行)
{
使用(新PushValue<布尔>(真的,( )=>禁用,VAL =>禁用= VAL))//使用(新PushValue<的转换器
防止无限递归; TypeNameHandling>(TypeNameHandling.Auto,()=> serializer.TypeNameHandling,VAL => ; serializer.TypeNameHandling = VAL))
{
返回serializer.Deserialize(读者的objectType);
}
}
公共覆盖无效WriteJson(JsonWriter作家,对象的值,JsonSerializer串行)
{使用(新PushValue<
;布尔>(真()=>禁用,VAL =>禁用= VAL))//使用(新PushValue< TypeNameHandling>(TypeNameHandling.Auto,()的转换器
防止无限递归=> serializer.TypeNameHandling,VAL = GT; serializer.TypeNameHandling = VAL))
{
//强制$类型进行传递的typeof(对象)作为类型被序列化无条件地写入。
serializer.Serialize(作家,值的typeof(对象));
}
}
}
[System.AttributeUsage(System.AttributeTargets.Assembly | System.AttributeTargets.Class | System.AttributeTargets.Interface)]
酒店的公共类EnableJsonTypeNameHandlingAttribute:System.Attribute
{
公共EnableJsonTypeNameHandlingAttribute()
{
}
}
请注意 - 与各种测试用例,测试,但因为我目前还没有安装它没有SignalR本身
I am trying to get SignalR to work with custom JsonSerializerSettings for its payload, specifically I'm trying to set TypeNameHandling = TypeNameHandling.Auto
.
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?
Sample code to demonstrate:
Server:
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" };
}
}
Client:
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());
}
Shared:
public static class ConverterSettings
{
public static JsonSerializer GetSerializer()
{
return JsonSerializer.Create(new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All
});
}
}
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
}
Then in startup you would add this converter to the default JsonSerializer
, passing in the assemblies for which you want "$type"
applied.
Update
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
.
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()
{
}
}
Note - tested with various test cases but not SignalR itself since I don't currently have it installed.
这篇关于SignalR Typenamehandling的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!