EasyNetQ 不同框架序列化反序列化问题
当.net core 项目给.net framework 用easynetq发送信息的时候。出现异常,例如以string对象为例。抛出异常:“Could not load assembly 'System.Private.CoreLib'”
因为:.net core 的string 对象是在System.Private.CoreLib程序集下,System.String命名空间下;而.net framework的string对象是在mscorlib程序集下,System.String命名空间下
easynetq反序列化对象前,会拿到当前对象的类型,拿到类型之后会从对应的程序集去加载,而.net framework当然没有System.Private.CoreLib.dll,所以失败。
解决办法:替换ITypeNameSerializer组件 官方替换各种组件方法
关键源码(版本6.3.1):
private static Type GetTypeFromTypeNameKey(TypeNameKey typeNameKey) { var assemblyName = typeNameKey.AssemblyName; var typeName = typeNameKey.TypeName; if (assemblyName != null) { var assembly = Assembly.Load(new AssemblyName(assemblyName)); if (assembly == null) { throw new EasyNetQException($"Could not load assembly '{assemblyName}'"); } var type = assembly.GetType(typeName); if (type == null) { // if generic type, try manually parsing the type arguments for the case of dynamically loaded assemblies // example generic typeName format: System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] if (typeName.IndexOf('`') >= 0) { try { type = GetGenericTypeFromTypeName(typeName, assembly); } catch (Exception ex) { throw new EasyNetQException($"Could not find type '{typeName}' in assembly '{assembly.FullName}'", ex); } } if (type == null) { throw new EasyNetQException($"Could not find type '{typeName}' in assembly '{assembly.FullName}'"); } } return type; } return Type.GetType(typeName); }
只要在源码中做兼容代码就行。
下面是兼容代码整个类(其实只是增加了一个if判断,暂时可以这样写,以后类型多了不好这样写),以及easynetq组件替换方式。
public class MyTypeNameSerializer : ITypeNameSerializer { private readonly ConcurrentDictionary<Type, string> serializedTypes = new ConcurrentDictionary<Type, string>(); private readonly ConcurrentDictionary<string, Type> deSerializedTypes = new ConcurrentDictionary<string, Type>(); /// <inheritdoc /> public string Serialize(Type type) { return serializedTypes.GetOrAdd(type, t => { var typeName = RemoveAssemblyDetails(t.AssemblyQualifiedName); if (typeName.Length > 255) { throw new EasyNetQException($"The serialized name of type '{t.Name}' exceeds the AMQP maximum short string length of 255 characters"); } return typeName; }); } /// <inheritdoc /> public Type DeSerialize(string typeName) { return deSerializedTypes.GetOrAdd(typeName, t => { var typeNameKey = SplitFullyQualifiedTypeName(t); return GetTypeFromTypeNameKey(typeNameKey); }); } private static string RemoveAssemblyDetails(string fullyQualifiedTypeName) { var builder = new StringBuilder(fullyQualifiedTypeName.Length); // loop through the type name and filter out qualified assembly details from nested type names var writingAssemblyName = false; var skippingAssemblyDetails = false; foreach (var character in fullyQualifiedTypeName) { switch (character) { case '[': writingAssemblyName = false; skippingAssemblyDetails = false; builder.Append(character); break; case ']': writingAssemblyName = false; skippingAssemblyDetails = false; builder.Append(character); break; case ',': if (!writingAssemblyName) { writingAssemblyName = true; builder.Append(character); } else { skippingAssemblyDetails = true; } break; default: if (!skippingAssemblyDetails) { builder.Append(character); } break; } } return builder.ToString(); } private static TypeNameKey SplitFullyQualifiedTypeName(string fullyQualifiedTypeName) { var assemblyDelimiterIndex = GetAssemblyDelimiterIndex(fullyQualifiedTypeName); string typeName; string assemblyName; if (assemblyDelimiterIndex != null) { typeName = fullyQualifiedTypeName.Trim(0, assemblyDelimiterIndex.GetValueOrDefault()); assemblyName = fullyQualifiedTypeName.Trim(assemblyDelimiterIndex.GetValueOrDefault() + 1, fullyQualifiedTypeName.Length - assemblyDelimiterIndex.GetValueOrDefault() - 1); } else { typeName = fullyQualifiedTypeName; assemblyName = null; } return new TypeNameKey(assemblyName, typeName); } private static Type GetTypeFromTypeNameKey(TypeNameKey typeNameKey) { var assemblyName = typeNameKey.AssemblyName; var typeName = typeNameKey.TypeName; if (assemblyName == "System.Private.CoreLib" && typeName == "System.String") { return typeof(string); } if (assemblyName != null) { //#if NETFX // // look, I don't like using obsolete methods as much as you do but this is the only way // // Assembly.Load won't check the GAC for a partial name //#pragma warning disable 618,612 var assembly = Assembly.LoadWithPartialName(assemblyName); //#pragma warning restore 618,612 //#else // var assembly = Assembly.Load(new AssemblyName(assemblyName)); //#endif //#if NETFX if (assembly == null) { // will find assemblies loaded with Assembly.LoadFile outside of the main directory var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (var a in loadedAssemblies) { // check for both full name or partial name match if (a.FullName == assemblyName || a.GetName().Name == assemblyName) { assembly = a; break; } } } //#endif if (assembly == null) { throw new EasyNetQException($"Could not load assembly '{assemblyName}'"); } var type = assembly.GetType(typeName); if (type == null) { // if generic type, try manually parsing the type arguments for the case of dynamically loaded assemblies // example generic typeName format: System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] if (typeName.IndexOf('`') >= 0) { try { type = GetGenericTypeFromTypeName(typeName, assembly); } catch (Exception ex) { throw new EasyNetQException($"Could not find type '{typeName}' in assembly '{assembly.FullName}'", ex); } } if (type == null) { throw new EasyNetQException($"Could not find type '{typeName}' in assembly '{assembly.FullName}'"); } } return type; } return Type.GetType(typeName); } private static Type GetGenericTypeFromTypeName(string typeName, Assembly assembly) { Type type = null; var openBracketIndex = typeName.IndexOf('['); if (openBracketIndex >= 0) { var genericTypeDefName = typeName.Substring(0, openBracketIndex); var genericTypeDef = assembly.GetType(genericTypeDefName); if (genericTypeDef != null) { var genericTypeArguments = new List<Type>(); var scope = 0; var typeArgStartIndex = 0; var endIndex = typeName.Length - 1; for (var i = openBracketIndex + 1; i < endIndex; ++i) { var current = typeName[i]; switch (current) { case '[': if (scope == 0) { typeArgStartIndex = i + 1; } ++scope; break; case ']': --scope; if (scope == 0) { var typeArgAssemblyQualifiedName = typeName.Substring(typeArgStartIndex, i - typeArgStartIndex); var typeNameKey = SplitFullyQualifiedTypeName(typeArgAssemblyQualifiedName); genericTypeArguments.Add(GetTypeFromTypeNameKey(typeNameKey)); } break; } } type = genericTypeDef.MakeGenericType(genericTypeArguments.ToArray()); } } return type; } private static int? GetAssemblyDelimiterIndex(string fullyQualifiedTypeName) { // we need to get the first comma following all surrounded in brackets because of generic types // e.g. System.Collections.Generic.Dictionary`2[[System.String, mscorlib,Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 var scope = 0; for (var i = 0; i < fullyQualifiedTypeName.Length; i++) { var current = fullyQualifiedTypeName[i]; switch (current) { case '[': scope++; break; case ']': scope--; break; case ',': if (scope == 0) { return i; } break; } } return null; } private readonly struct TypeNameKey { public string AssemblyName { get; } public string TypeName { get; } public TypeNameKey(string assemblyName, string typeName) { AssemblyName = assemblyName; TypeName = typeName; } } }
var bus = RabbitHutch.CreateBus(connectString, serviceRegister => serviceRegister.Register<ITypeNameSerializer, MyTypeNameSerializer>());
这样就结束了。