Is this a bug in JSON.NET or Entity Framework or am I doing something wrong while trying to serialize a list of Exceptions with JSON.NET?

前端 未结 3 1354
野性不改
野性不改 2021-01-21 11:41

Got this error when trying to serialize a set of errors:

\"ISerializable type \'System.Data.Entity.Infrastructure.DbUpdateConcurrencyException\' does not have a valid co

3条回答
  •  渐次进展
    2021-01-21 12:04

    The issue appears to be that, for ISerializable objects, Json.NET does not support serialization of proxy types defined via the SerializationInfo.SetType(Type) method, nor deserialization of proxy objects that implement the IObjectReference interface that replaces the deserialized proxy with the corresponding "real" object. Specifically, changing the serialized type during serialization would need to be supported in JsonSerializerInternalWriter.SerializeISerializable() and JsonSerializerInternalReader.CreateISerializable() -- but it is not.

    And as it turns out, exceptions subtypes such as DbUpdateConcurrencyException apparently no longer provide their own streaming constructor, but instead rely on this very proxy mechanism to serialize themselves, in particular setting the SerializationInfo.ObjectType to the internal type SafeSerializationManager -- which, as stated, Json.NET does not support, causing the problem you are seeing.

    If your code is running in full trust, as a workaround you can add a custom JsonConverter that handles serializing and deserializing of serialization proxies:

    public class SerializableConverter : JsonConverter where T : ISerializable
    {
        public override bool CanConvert(Type objectType)
        {
            return typeof(T).IsAssignableFrom(objectType);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null)
                return null;
            var wrapper = serializer.Deserialize(reader);
            var proxy = wrapper.SerializableValues;
            if (proxy == null)
                return null;
            return proxy.GetRealObject(serializer.Context);
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var serializable = (ISerializable)value;
            var proxy = SerializableProxy.ToProxy(serializable, serializer.Context);
            serializer.Serialize(writer, new SerializableProxyWrapper { SerializableValues = proxy });
        }
    }
    
    sealed class SerializableProxyWrapper
    {
        [JsonProperty(TypeNameHandling = TypeNameHandling.All)]
        public SerializableProxy SerializableValues { get; set; }
    }
    
    abstract class SerializableProxy : IObjectReference
    {
        public static SerializableProxy ToProxy(ISerializable value, StreamingContext context)
        {
            if (value == null)
                return null;
            SerializationInfo serializationInfo = new SerializationInfo(value.GetType(), new FormatterConverter());
            value.GetObjectData(serializationInfo, context);
            var objectType = serializationInfo.GetObjectType();
    
            return (SerializableProxy)Activator.CreateInstance(typeof(SerializableProxy<>).MakeGenericType(new[] { objectType }), new object[] { serializationInfo, context });
        }
    
        #region IObjectReference Members
    
        public abstract object GetRealObject(StreamingContext context);
    
        #endregion
    }
    
    [Serializable]
    sealed class SerializableProxy : SerializableProxy, ISerializable, IObjectReference where T : ISerializable
    {
        SerializationInfo originalInfo;
        StreamingContext context;
    
        public SerializableProxy(SerializationInfo originalInfo, StreamingContext context)
        {
            this.originalInfo = originalInfo;
            this.context = context;
        }
    
        #region ISerializable Members
    
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            foreach (SerializationEntry entry in originalInfo)
                info.AddValue(entry.Name, entry.Value, entry.ObjectType);
        }
    
        #endregion
    
        #region IObjectReference Members
    
        public override object GetRealObject(StreamingContext context)
        {
            var realObject = Activator.CreateInstance(typeof(T), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, 
                new object[] { originalInfo, context }, CultureInfo.InvariantCulture);
            return realObject.GetFinalRealObject(context);
        }
    
        #endregion
    }
    
    static partial class SerializationExtensions
    {
        public static object GetFinalRealObject(this object obj, StreamingContext context)
        {
            while (obj is IObjectReference)
            {
                var realObj = ((IObjectReference)obj).GetRealObject(context);
                if (!(realObj is IObjectReference))
                    return realObj;
                if (realObj == obj)
                    return realObj; // Avoid an infinite loop
                obj = (IObjectReference)realObj;
            }
            return obj;
        }
    }
    
    static partial class SerializationExtensions
    {
        public static Type GetObjectType(this SerializationInfo info)
        {
            if (info == null)
                return null;
            return info.ObjectType;
        }
    }
    

    Then it should be usable as follows:

    var settings = new JsonSerializerSettings
    {
        Converters = new [] { new SerializableConverter() },
        Formatting = Formatting.Indented,
    };
    var json = JsonConvert.SerializeObject(ex, settings);
    Console.WriteLine(json);
    

    However, if your code is not fully trusted, this workaround might fail, and support from Json.NET itself would be required.

    Note - tested in some mockup scenarios but not with the actual exceptions you are using.

提交回复
热议问题