C# 信号机类型名称处理

C# 信号机类型名称处理,c#,json,json.net,signalr,.net-4.5,C#,Json,Json.net,Signalr,.net 4.5,我试图让Signal为其负载使用自定义JsonSerializerSettings,特别是我试图设置TypeNameHandling=TypeNameHandling.Auto 问题似乎是,信号器使用hubConnection.JsonSerializer和GlobalHost.dependencysolver.Resolve()中的设置作为其内部数据结构,从而导致各种破坏(当我设置typenameholling.All作为最粗糙的示例时,内部服务器崩溃,但是使用typenameholling.

我试图让Signal为其负载使用自定义JsonSerializerSettings,特别是我试图设置
TypeNameHandling=TypeNameHandling.Auto

问题似乎是,信号器使用
hubConnection.JsonSerializer
GlobalHost.dependencysolver.Resolve()
中的设置作为其内部数据结构,从而导致各种破坏(当我设置
typenameholling.All
作为最粗糙的示例时,内部服务器崩溃,但是使用
typenameholling.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 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
}
注意-使用各种测试用例进行测试,但不使用Signal本身,因为我目前没有安装它

字体名称处理
注意事项

使用
typenameholling
时,请注意以下注意事项:

当应用程序从外部源反序列化JSON时,应小心使用TypeNameHandling。当使用非None值反序列化时,应使用自定义SerializationBinder验证传入类型


有关为什么有必要这样做的讨论,请参阅。

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

然而,我有一个问题,我不能让服务器正确读取接收到的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。

您的想法更容易理解。我遇到了同样的问题,试图序列化派生类,但没有发送派生类型的属性

正如微软在这里所说:

如果将模型指定为“Object”类型而不是强类型的“Base type”,则会将其序列化,然后发送属性。 如果你有一个大的对象图,你需要将它一直保存下来。这违反了强类型(类型安全),但它允许该技术将数据发送回,而不改变你的代码,只改变你的模型

例如:

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"
    
    ..
}
您将从派生类型发送所有属性


该解决方案并不完美,因为您丢失了强类型模型,但是如果这是一个ViewModel被传递到javascript,而在使用信号器的情况下,它就可以正常工作。

您不想使用来自信号器的默认Json序列化器有什么特别的原因吗?@Matei_Radu,因为它使用了
类型名称处理。无
和I需要
Auto
。我没有signar来测试;问题是您需要根json对象或某个嵌套对象上的
$type
属性吗?如果是前者,有什么方法可以放松这一要求,也许可以通过将包装器对象作为根返回?@dbc只要我开始打包每个参数并返回将n值放入自己的处理(反)序列化的小包装器中,我还可以将SignalR完全抛出窗口..或者更确切地说,只要没有任何自动方法可以做到这一点。要求是我可以将接口反序列化为其确切类型。您只需要一个通用包装器:
公共类数据{public T data{get;set;}}
。这里的困难是,即使您设置了
typenameholling.Auto
(我知道如何为您做),它不适用于根对象,除非Signal在内部调用a。@Voo-这个答案对Signal不起作用,或者是因为通过程序集或命名空间启用
TypeNameHandling
不方便?我主要希望有一个可能更通用的解决方案,但这似乎是目前最好的选择。SignalR 2.2,JsonSerializerSettings或此实现不成功。我有一个方法返回JsonSerializer,并将此转换器添加到转换器列表中。我使用GlobalHost.DependencyResolver注册此方法。我还将其添加到客户端的转换器列表中。相同的行为,没有错误,服务器上的基类型列表相同。@Thypari-在更新之前,它在答案第一部分的代码中。向下滚动到
polymorphicsEmblyrootConverter
@Thypari-很抱歉它不适用于您。1)如果您正在使用
EnableJsonTypeNameHandlingConverter
您是否向类型添加了
EnableJsonTypeNameHandlingAttribute
或正在序列化的程序集?2)您是否如原始问题所示在客户端和服务器端都设置了转换器?出于安全原因,必须在客户端和服务器端手动启用
TypeNameHandling
。如果您使用
[JsonConverter(typeof)直接将此应用于基类型(PolymorphicAssemblyRootConverter))
,您可以完全消除
哈希集程序集;
并从
CanConvert
抛出一个异常,因为直接通过属性应用时不会调用它。OP有一个特定的要求,即能够完全通过设置启用
TypeNameHandling
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);