The question was long, long ago, the answer was missing ...
In the discussion mentioned in the comments, I came up with - I would say - an elegant solution:
Do not save type name, but identifier (versions)
The identifier is set by the attribute at the class level, i.e.
namespace CurrentEvents { [Versioned("EventSomethingHappened", 0)]
This identifier must be serialized in / next to the payload. In serialized form
"Some.Name.Space.EventSomethingHappened" β "EventSomethingHappened | 0"
If a different version of this event is required, the current version is copied in an "obsolete" assembly or simply in a different namespace and renamed (type-name) to "EventSomethingHappenedV0", but the Versioned attribute remains untouched (in this copy)
namespace LegacyEvents { [Versioned("EventSomethingHappened", 0)]
In the new version (ibid., Under the same name), only part of the attribute version is increased. What is it!
namespace CurrentEvents { [Versioned("EventSomethingHappened", 1)]
Json.NET supports binding, which displays type identifiers of types and vice versa. Here is the binder ready for production:
public class VersionedSerializationBinder : DefaultSerializationBinder { private Dictionary<string, Type> _getImplementationLookup = new Dictionary<string, Type>(); private static Type[] _versionedEvents = null; protected static Type[] VersionedEvents { get { if (_versionedEvents == null) _versionedEvents = AppDomain.CurrentDomain.GetAssemblies() .Where(x => x.IsDynamic == false) .SelectMany(x => x.GetExportedTypes() .Where(y => y.IsAbstract == false && y.IsInterface == false)) .Where(x => x.GetCustomAttributes(typeof(VersionedAttribute), false).Any()) .ToArray(); return _versionedEvents; } } public VersionedSerializationBinder() { } private VersionedAttribute GetVersionInformation(Type type) { var attr = type.GetCustomAttributes(typeof(VersionedAttribute), false).Cast<VersionedAttribute>().FirstOrDefault(); return attr; } public override void BindToName(Type serializedType, out string assemblyName, out string typeName) { var versionInfo = GetVersionInformation(serializedType); if (versionInfo != null) { var impl = GetImplementation(versionInfo); typeName = versionInfo.Identifier + "|" + versionInfo.Revision; } else { base.BindToName(serializedType, out assemblyName, out typeName); } assemblyName = null; } private VersionedAttribute GetVersionInformation(string serializedInfo) { var strs = serializedInfo.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); if (strs.Length != 2) return null; return new VersionedAttribute(strs[0], strs[1]); } public override Type BindToType(string assemblyName, string typeName) { if (typeName.Contains('|')) { var type = GetImplementation(GetVersionInformation(typeName)); if (type == null) throw new InvalidOperationException(string.Format("VersionedEventSerializationBinder: No implementation found for type identifier '{0}'", typeName)); return type; } else { var versionInfo = GetVersionInformation(typeName + "|0"); if (versionInfo != null) { var type = GetImplementation(versionInfo); if (type != null) return type;
... and Versioned -attribute
[AttributeUsage(AttributeTargets.Class)] public class VersionedAttribute : Attribute { public string Revision { get; set; } public string Identifier { get; set; } public VersionedAttribute(string identifier, string revision = "0") { this.Identifier = identifier; this.Revision = revision; } public VersionedAttribute(string identifier, long revision) { this.Identifier = identifier; this.Revision = revision.ToString(); } }
Finally, use standard binding like this
JsonSerializer.Create(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple, Binder = new VersionedSerializationBinder() });
For a full implementation of Json.NET ISerialize see (a bit dated): https://gist.github.com/warappa/6388270