I'm looking for insight on how to instrument a runtime platform to expose the source type of a Microsoft .Net binary deserialization failure.
When using BinaryFormatter.Deserialize(StreamingContextStates.CrossMachine)
and one of the types does not exist in the current binaries; instead of throwing an error, .Net inserts the object [TypeLoadExceptionHolder]
. Particularly for collections, this causes no immediate problem.
Subsequently when the collection is serialized for transmission between application tiers; the platform receives a 'serialization failure' because [TypeLoadExceptionHolder]
cannot be serialized. So the resulting error is useless for actually providing clues as to the source-type that caused the problem. Now the hunt (time suck) is on to see which developer (of hundreds) added a new type to a million-line platform.
This problem happens with some frequency because of the serialization stream used to support the platform sessioncache. Code is deployed fairly often and in an incremental fashion. Customer page-requests can bounce between old and new versions of the codebase during the deployment window. Careless introduction of a new type will cause the page-requests on the old version to blow up.
Any thoughts about providing runtime rich error/trap would be appreciated.
(SerializationException)
Type 'System.Runtime.Serialization.TypeLoadExceptionHolder' in Assembly 'mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' is not marked as serializable.
- at System.Runtime.Serialization.FormatterServices.InternalGetSerializableMembers(RuntimeType type)
- at System.Runtime.Serialization.FormatterServices.GetSerializableMembers(Type type, StreamingContext context)
- at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitMemberInfo()
- at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitSerialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter)
- at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.Serialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter)
- at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo)
- at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(Object graph, Header[] inHeaders, __BinaryWriter serWriter, Boolean fCheck)
- at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers, Boolean fCheck)
Well, one approach you could take is to use a custom SerializationBinder
that overrides BindToType
and checks the type name during deserialization.
Depending on what you'd like to accomplish, when you identify an unknown type you could either:
Raise an Exception (Pessimistic): Catch the issue early on and can easily identity the type with a custom message or exception.
Log the Type Name (Optimistic): If there are scenarios where unknown types are OK, logging would provide the details necessary to diagnose exceptions if they occur later during serialization.
You could also select a different approach depending on characteristics of the type name (i.e. if the type appears to be part of your application or part of a 3rd party library).
The TypeLoadExceptionHolder
instance created during deserialization does contain a non-public member TypeName
that contains the name of the type that could not be resolved. However, the instance is not available from the SerializationException
you encounter later on, and even so, the value would only be available via reflection in trusted contexts.
public class CustomSerializationBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
Type t = Type.GetType(string.Concat(typeName, ", ", assemblyName));
if (t == null)
{
throw new SerializationException(string.Format("Type {0} from assembly {1} could not be bound.", typeName, assemblyName));
}
return t;
}
public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
base.BindToName(serializedType, out assemblyName, out typeName);
}
}
...
BinaryFormatter.Binder = new CustomSerializationBinder();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With