我正在尝试让SignalR使用自定义JsonSerializerSettings来处理其有效负载,特别是我试图设置TypeNameHandling = TypeNameHandling.Auto
。
问题似乎是,SignalR使用hubConnection.JsonSerializer
和GlobalHost.DependencyResolver.Resolve<JsonSerializer>()
中的设置作为其内部数据结构,然后导致各种破坏(当我设置{{1时内部服务器崩溃作为最粗鲁的例子,但是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<IFoo>("Send");
Console.WriteLine(result.GetType());
}
答案 0 :(得分:8)
这可以通过利用您的类型和SignalR类型位于不同assemblies的事实来实现。我们的想法是创建一个适用于程序集中所有类型的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<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"
的程序集。
更新
如果由于某种原因在启动时传递程序集列表不方便,您可以通过objectType.Namespace
启用转换器。生活在指定命名空间中的所有类型都将自动使用TypeNameHandling.Auto
进行序列化。
或者,您可以引入Attribute
targets一个程序集,类或接口,并在与适当的转换器结合使用时启用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<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本身,因为我目前还没有安装它。
TypeNameHandling
小心
使用TypeNameHandling
时,请注意Newtonsoft docs中的这一注意事项:
当您的应用程序从外部源反序列化JSON时,应谨慎使用TypeNameHandling。使用非None以外的值进行反序列化时,应使用自定义SerializationBinder验证传入类型。
有关可能需要执行此操作的讨论,请参阅 TypeNameHandling caution in Newtonsoft Json 。
答案 1 :(得分:3)
我知道这是一个相当陈旧的主题并且有一个可接受的答案。
然而,我遇到的问题是我无法使服务器正确读取收到的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。
答案 2 :(得分:0)
您的想法更容易。我遇到了相同的问题,试图序列化派生类,但是没有发送派生类型的属性。
如果您指定类型为“对象”的模型,而不是强类型的“基本类型”,则将其序列化,然后发送属性。 如果您有一个很大的对象图,则需要一直将其放下。它违反了强类型(类型安全),但允许该技术将数据发送回,而无需更改代码,仅更改模型。
例如:
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;}
}
如果您使用此Type的主要模型如下所示:
public class UserModel
{
public string Name { get; set; }
public IEnumerable<object> Notifications { get; set; } // note the "object"
..
}
如果您尝试
var model = new UserModel() { ... }
JsonSerializer.Serialize(model);
您将从派生类型发送所有属性。
该解决方案并不完美,因为您丢失了强类型模型,但是如果将ViewModel传递给javascript,则可以使用SignalR。