SignalR Typenamehandling [英] SignalR Typenamehandling

查看:229
本文介绍了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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆