From 4fb202ba9ec876408de6bd06204f6988c56a96dc Mon Sep 17 00:00:00 2001 From: seatlemorning <49031535+seatlemorning@users.noreply.github.com> Date: Wed, 22 Apr 2026 08:54:36 +0300 Subject: [PATCH 1/4] UnitTests for TypeIgnoreVersionConveter --- CoreRemoting.Tests/BsonSerializationTests.cs | 340 ++++++++++++++++++- 1 file changed, 339 insertions(+), 1 deletion(-) diff --git a/CoreRemoting.Tests/BsonSerializationTests.cs b/CoreRemoting.Tests/BsonSerializationTests.cs index 0ffaed9..cbe3c94 100644 --- a/CoreRemoting.Tests/BsonSerializationTests.cs +++ b/CoreRemoting.Tests/BsonSerializationTests.cs @@ -701,4 +701,342 @@ public void BsonSerializerAdapter_should_deserialize_HashtableAsObjectParamInLis Assert.Equal(paramsList[0].Value.GetType(), desDto[0].Value.GetType()); Assert.Equal(paramsList[1].Something.GetType(), desDto[1].Something.GetType()); } -} \ No newline at end of file + + #region TypeIgnoreVersionConverter + + private readonly TypeIgnoreVersionConverter _converter; + private readonly MethodInfo _getTypeWithoutVersionMethod; + + public BsonSerializationTests() + { + _converter = new TypeIgnoreVersionConverter(); + _getTypeWithoutVersionMethod = typeof(TypeIgnoreVersionConverter) + .GetMethod("GetTypeWithoutVersion", BindingFlags.NonPublic | BindingFlags.Instance); + } + + #region Base tests + + [Fact] + public void TrySplitTypeAndAssembly_ShouldSplitValidTypeName() + { + var fullTypeName = "System.String, System.Private.CoreLib, Version=8.0.0.0"; + + var result = _converter.TrySplitTypeAndAssembly(fullTypeName, out var typeName, out var assemblyName); + + Assert.True(result); + Assert.Equal("System.String", typeName); + Assert.Equal("System.Private.CoreLib, Version=8.0.0.0", assemblyName); + } + + [Fact] + public void TrySplitTypeAndAssembly_ShouldHandleListOfLongArrays() + { + var fullTypeName = "System.Collections.Generic.List`1[[System.Int64[]]], System.Private.CoreLib"; + + var result = _converter.TrySplitTypeAndAssembly(fullTypeName, out var typeName, out var assemblyName); + + Assert.True(result); + Assert.Equal("System.Collections.Generic.List`1[[System.Int64[]]]", typeName); + Assert.Equal("System.Private.CoreLib", assemblyName); + } + + [Fact] + public void TrySplitTypeAndAssembly_ShouldHandleComplexGenericWithAssembly() + { + var fullTypeName = + "System.Collections.Generic.List`1[[System.Int64, System.Private.CoreLib, Version=8.0.0.0]], System.Private.CoreLib"; + + var result = _converter.TrySplitTypeAndAssembly(fullTypeName, out var typeName, out var assemblyName); + + Assert.True(result); + Assert.Equal("System.Collections.Generic.List`1[[System.Int64, System.Private.CoreLib, Version=8.0.0.0]]", + typeName); + Assert.Equal("System.Private.CoreLib", assemblyName); + } + + [Fact] + public void TrySplitTypeAndAssembly_ShouldReturnFalse_WhenNoComma() + { + var result = _converter.TrySplitTypeAndAssembly("System.String", out _, out _); + Assert.False(result); + } + + [Fact] + public void GetAssemblySimpleNameSafe_ShouldExtractNameFromFullQualifiedName() + { + var result = _converter.GetAssemblySimpleNameSafe( + "System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e"); + Assert.Equal("System.Private.CoreLib", result); + } + + [Fact] + public void GetAssemblySimpleNameSafe_ShouldExtractNameFromCustomAssembly() + { + var result = _converter.GetAssemblySimpleNameSafe( + "MyCompany.MyLibrary, Version=3.5.5436.0, Culture=neutral, PublicKeyToken=null"); + Assert.Equal("MyCompany.MyLibrary", result); + } + + [Fact] + public void GetAssemblySimpleNameSafe_ShouldHandleAssemblyWithoutVersion() + { + var result = _converter.GetAssemblySimpleNameSafe("System.Private.CoreLib"); + Assert.Equal("System.Private.CoreLib", result); + } + + [Fact] + public void GetAssemblySimpleNameSafe_ShouldReturnNull_WhenNullInput() + { + var result = _converter.GetAssemblySimpleNameSafe(null); + Assert.Null(result); + } + + [Fact] + public void GetBaseAndArgumentTypesNames_ShouldParseListOfInt() + { + var result = _converter.GetBaseAndArgumentTypesNames( + "System.Collections.Generic.List`1[[System.Int32]]", + out var baseType, + out var argumentType); + + Assert.True(result); + Assert.Equal("System.Collections.Generic.List`1", baseType); + Assert.Equal("System.Int32", argumentType); + } + + [Fact] + public void GetBaseAndArgumentTypesNames_ShouldParseListOfLongArrays() + { + var result = _converter.GetBaseAndArgumentTypesNames( + "System.Collections.Generic.List`1[[System.Int64[]]]", + out var baseType, + out var argumentType); + + Assert.True(result); + Assert.Equal("System.Collections.Generic.List`1", baseType); + Assert.Equal("System.Int64[]", argumentType); + } + + [Fact] + public void GetBaseAndArgumentTypesNames_ShouldParseListOfListOfLong() + { + var result = _converter.GetBaseAndArgumentTypesNames( + "System.Collections.Generic.List`1[[System.Collections.Generic.List`1[[System.Int64]]]]", + out var baseType, + out var argumentType); + + Assert.True(result); + Assert.Equal("System.Collections.Generic.List`1", baseType); + Assert.Equal("System.Collections.Generic.List`1[[System.Int64]]", argumentType); + } + + [Fact] + public void GetBaseAndArgumentTypesNames_ShouldParseDictionaryStringToListOfLongArrays() + { + var result = _converter.GetBaseAndArgumentTypesNames( + "System.Collections.Generic.Dictionary`2[[System.String],[System.Collections.Generic.List`1[[System.Int64[]]]]]", + out var baseType, + out var argumentType); + + Assert.True(result); + Assert.Equal("System.Collections.Generic.Dictionary`2", baseType); + Assert.Equal("System.String],[System.Collections.Generic.List`1[[System.Int64[]]]", argumentType); + } + + [Fact] + public void GetBaseAndArgumentTypesNames_ShouldParseTupleWithTwoLongArrays() + { + var result = _converter.GetBaseAndArgumentTypesNames( + "System.Tuple`2[[System.Int64[]],[System.Int64[]]]", + out var baseType, + out var argumentType); + + Assert.True(result); + Assert.Equal("System.Tuple`2", baseType); + Assert.Equal("System.Int64[]],[System.Int64[]", argumentType); + } + + [Fact] + public void GetBaseAndArgumentTypesNames_ShouldHandleGenericWithAssemblyQualifiedNames() + { + var result = _converter.GetBaseAndArgumentTypesNames( + "System.Collections.Generic.List`1[[TestUserData, CoreRemoting.Tests, Version=2.1.0.0, Culture=neutral, PublicKeyToken=null]]", + out var baseType, + out var argumentType); + + Assert.True(result); + Assert.Equal("System.Collections.Generic.List`1", baseType); + Assert.Equal("TestUserData, CoreRemoting.Tests, Version=2.1.0.0, Culture=neutral, PublicKeyToken=null", + argumentType); + } + + [Fact] + public void GetBaseAndArgumentTypesNames_ShouldReturnFalse_WhenNoBrackets() + { + var result = _converter.GetBaseAndArgumentTypesNames("System.Int32", out _, out _); + Assert.False(result); + } + + #endregion + + #region SCENARIO: Integration tests with version differences + + [Fact] + public void Integration_ClientOlder_ServerNewer_ListOfLong() + { + var serverTypeName = + "System.Collections.Generic.List`1[[System.Int64, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]"; + + var resultType = _getTypeWithoutVersionMethod?.Invoke(_converter, new object[] { serverTypeName }) as Type; + + Assert.NotNull(resultType); + Assert.Equal(typeof(List), resultType); + } + + [Fact] + public void Integration_ClientOlder_ServerNewer_ListOfLongArrays() + { + var serverTypeName = + "System.Collections.Generic.List`1[[System.Int64[], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]"; + + var resultType = _getTypeWithoutVersionMethod?.Invoke(_converter, new object[] { serverTypeName }) as Type; + + Assert.NotNull(resultType); + Assert.Equal(typeof(List).Name, resultType.Name); + var genericArg = resultType.GetGenericArguments()[0]; + Assert.True(genericArg.IsArray); + Assert.Equal(typeof(long), genericArg.GetElementType()); + } + + [Fact] + public void Integration_ClientOlder_ServerNewer_TupleWithTwoLongArrays() + { + var serverTypeName = + "System.Tuple`2[[System.Int64[], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Int64[], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]"; + + var resultType = _getTypeWithoutVersionMethod?.Invoke(_converter, new object[] { serverTypeName }) as Type; + + Assert.NotNull(resultType); + var genericArgs = resultType.GetGenericArguments(); + Assert.Equal(2, genericArgs.Length); + Assert.Equal(typeof(long[]), genericArgs[0]); + Assert.Equal(typeof(long[]), genericArgs[1]); + } + + [Fact] + public void Integration_ClientNewer_ServerOlder_ListOfLong() + { + var serverTypeName = + "System.Collections.Generic.List`1[[System.Int64, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]"; + + var resultType = _getTypeWithoutVersionMethod?.Invoke(_converter, new object[] { serverTypeName }) as Type; + + Assert.NotNull(resultType); + Assert.Equal(typeof(List), resultType); + } + + [Fact] + public void Integration_ClientNewer_ServerOlder_ListOfLongArrays() + { + var serverTypeName = + "System.Collections.Generic.List`1[[System.Int64[], System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]"; + + var resultType = _getTypeWithoutVersionMethod?.Invoke(_converter, new object[] { serverTypeName }) as Type; + + Assert.NotNull(resultType); + Assert.Equal(typeof(List).Name, resultType.Name); + } + + [Fact] + public void Integration_ClientNewer_ServerOlder_DifferentPublicKeyToken() + { + var serverTypeName = + "System.Collections.Generic.List`1[[System.Int64, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=1234567890abcdef]]"; + + var resultType = _getTypeWithoutVersionMethod?.Invoke(_converter, new object[] { serverTypeName }) as Type; + + Assert.NotNull(resultType); + Assert.Equal(typeof(List), resultType); + } + + [Fact] + public void Integration_NullInput_ShouldReturnNull() + { + var result = _getTypeWithoutVersionMethod?.Invoke(_converter, new object[] { null }); + Assert.Null(result); + } + + [Fact] + public void Integration_EmptyInput_ShouldReturnNull() + { + var resultType = _getTypeWithoutVersionMethod?.Invoke(_converter, new object[] { string.Empty }) as Type; + Assert.Null(resultType); + } + + [Fact] + public void Integration_Version9_ShouldWork() + { + var serverTypeName = + "System.Collections.Generic.List`1[[System.Int64, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]"; + + var resultType = _getTypeWithoutVersionMethod?.Invoke(_converter, new object[] { serverTypeName }) as Type; + + Assert.NotNull(resultType); + Assert.Equal(typeof(List), resultType); + } + + [Fact] + public void Integration_MissingVersion_ShouldWork() + { + var serverTypeName = "System.Collections.Generic.List`1[[System.Int64, System.Private.CoreLib]]"; + + var resultType = _getTypeWithoutVersionMethod?.Invoke(_converter, new object[] { serverTypeName }) as Type; + + Assert.NotNull(resultType); + } + + [Fact] + public void Integration_DeeplyNestedGenerics_ShouldWork() + { + var serverTypeName = + "System.Collections.Generic.List`1[[System.Collections.Generic.List`1[[System.Collections.Generic.List`1[[System.Int64[], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]"; + + var resultType = _getTypeWithoutVersionMethod?.Invoke(_converter, new object[] { serverTypeName }) as Type; + + Assert.NotNull(resultType); + Assert.Equal(typeof(List>>).Name, resultType.Name); + } + + [Fact] + public void TryExtractArrayElementType_ShouldExtractFromSimpleArray() + { + var result = _converter.TryExtractArrayElementType("System.Int64[]", out var elementType); + + Assert.True(result); + Assert.Equal("System.Int64", elementType); + } + + [Fact] + public void TryExtractArrayElementType_ShouldExtractFromJaggedArray() + { + var result = _converter.TryExtractArrayElementType("System.Int64[][]", out var elementType); + + Assert.True(result); + Assert.Equal("System.Int64[]", elementType); + } + + [Fact] + public void TryExtractArrayElementType_ShouldExtractFromGenericArray() + { + var result = _converter.TryExtractArrayElementType( + "System.Collections.Generic.List`1[[System.Int32]][]", + out var elementType); + + Assert.True(result); + Assert.Equal("System.Collections.Generic.List`1[[System.Int32]]", elementType); + } + + #endregion + + #endregion +} From 186b915b9218318a2316a5eb776545ce49fb38bd Mon Sep 17 00:00:00 2001 From: seatlemorning <49031535+seatlemorning@users.noreply.github.com> Date: Wed, 22 Apr 2026 09:01:52 +0300 Subject: [PATCH 2/4] Fix a connection with different version client/server taking into a generic type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Сonsider generic types while serializing --- .../Bson/TypeIgnoreVersionConverter.cs | 150 +++++++++++++----- 1 file changed, 114 insertions(+), 36 deletions(-) diff --git a/CoreRemoting/Serialization/Bson/TypeIgnoreVersionConverter.cs b/CoreRemoting/Serialization/Bson/TypeIgnoreVersionConverter.cs index 30e867f..1904720 100644 --- a/CoreRemoting/Serialization/Bson/TypeIgnoreVersionConverter.cs +++ b/CoreRemoting/Serialization/Bson/TypeIgnoreVersionConverter.cs @@ -34,37 +34,31 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s private Type GetTypeWithoutVersion(string fullTypeName) { - var resultType = Type.GetType(fullTypeName); + if (string.IsNullOrEmpty(fullTypeName)) + return null; - if (resultType != null || string.IsNullOrEmpty(fullTypeName)) + var resultType = Type.GetType(fullTypeName); + if (resultType != null) return resultType; - var commaIndex = fullTypeName.IndexOf(','); - if (commaIndex <= 0) + if (!TrySplitTypeAndAssembly(fullTypeName, out var typeName, out var assemblyQualifiedName)) return null; - string typeName = fullTypeName.Substring(0, commaIndex).Trim(); - string assemblyQualifiedName = fullTypeName.Substring(commaIndex + 1).Trim(); - string assemblySimpleName = new AssemblyName(assemblyQualifiedName).Name; + string assemblySimpleName = GetAssemblySimpleNameSafe(assemblyQualifiedName); + if (string.IsNullOrEmpty(assemblySimpleName)) + return null; var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - Assembly targetAssembly = null; - foreach (var assembly in assemblies) - { - var name = assembly.GetName().Name; - if (name != null && name.Equals(assemblySimpleName, StringComparison.OrdinalIgnoreCase)) - { - targetAssembly = assembly; - break; - } - } + + Assembly targetAssembly = assemblies.FirstOrDefault(a => + a.GetName().Name?.Equals(assemblySimpleName, StringComparison.OrdinalIgnoreCase) == true); if (targetAssembly != null) { resultType = FindType(targetAssembly, typeName); if (resultType != null) return resultType; } - + foreach (var assembly in assemblies) { resultType = FindType(assembly, typeName); @@ -80,17 +74,30 @@ private Type FindType(Assembly assembly, string fullTypeName) if (fullTypeName.EndsWith("[]")) { var elementTypeName = fullTypeName.Substring(0, fullTypeName.Length - 2); - var elementType = assembly.GetType(elementTypeName) ?? FindGenericType(assembly, elementTypeName); + var elementType = FindType(assembly, elementTypeName); if (elementType != null) { return elementType.MakeArrayType(); } } - + var type = assembly.GetType(fullTypeName); if (type != null) return type; - return FindGenericType(assembly, fullTypeName); + if (GetBaseAndArgumentTypesNames(fullTypeName, out var baseTypeName, out var argumentTypeName)) + { + Type baseType = assembly.GetType(baseTypeName); + if (baseType != null) + { + Type argType = FindType(assembly, argumentTypeName); + if (argType != null) + { + return baseType.MakeGenericType(argType); + } + } + } + + return null; } catch { @@ -98,30 +105,101 @@ private Type FindType(Assembly assembly, string fullTypeName) } } - private Type FindGenericType(Assembly assembly, string fullTypeName) + #region Help Methods + + internal bool TrySplitTypeAndAssembly(string fullTypeName, out string typeName, out string assemblyQualifiedName) { - try + typeName = null; + assemblyQualifiedName = null; + + int bracketDepth = 0; + int commaIndex = -1; + + for (int i = 0; i < fullTypeName.Length; i++) { - if (!fullTypeName.Contains('[')) - return assembly.GetType(fullTypeName); + char c = fullTypeName[i]; + + if (c == '[') + { + bracketDepth++; + } + else if (c == ']') + { + bracketDepth--; + } + else if (c == ',' && bracketDepth == 0) + { + commaIndex = i; + break; + } + } - int indexOfBracket = fullTypeName.IndexOf('['); - string baseTypeName = fullTypeName.Substring(0, indexOfBracket).Trim(); - string argumentTypeName = fullTypeName.Substring(indexOfBracket + 1, fullTypeName.Length - indexOfBracket - 2); + if (commaIndex <= 0) + return false; - Type baseType = assembly.GetType(baseTypeName); - if (baseType == null) - return null; + typeName = fullTypeName.Substring(0, commaIndex).Trim(); + assemblyQualifiedName = fullTypeName.Substring(commaIndex + 1).Trim(); + return true; + } - Type argType = FindGenericType(assembly, argumentTypeName); - if (argType == null) - return null; + internal string GetAssemblySimpleNameSafe(string assemblyQualifiedName) + { + if (string.IsNullOrEmpty(assemblyQualifiedName)) + return null; - return baseType.MakeGenericType(argType); + try + { + var assemblyName = new AssemblyName(assemblyQualifiedName); + return assemblyName.Name; } - catch (Exception) + catch { + var firstCommaIndex = assemblyQualifiedName.IndexOf(','); + if (firstCommaIndex > 0) + { + var simpleName = assemblyQualifiedName.Substring(0, firstCommaIndex).Trim(); + return simpleName; + } + + var versionIndex = assemblyQualifiedName.IndexOf("Version=", StringComparison.OrdinalIgnoreCase); + if (versionIndex > 0) + { + var name = assemblyQualifiedName.Substring(0, versionIndex - 1).Trim(); + name = name.TrimEnd(','); + return name; + } return null; } } + + internal bool GetBaseAndArgumentTypesNames(string fullTypeName, out string baseTypeName, + out string argumentTypeName) + { + baseTypeName = null; + argumentTypeName = null; + + if (string.IsNullOrEmpty(fullTypeName)) + return false; + + int indexOfBracket = fullTypeName.IndexOf('['); + if (indexOfBracket < 0) + return false; + + baseTypeName = fullTypeName.Substring(0, indexOfBracket).Trim(); + + int lastIndexOfBracket = fullTypeName.LastIndexOf(']'); + if (lastIndexOfBracket <= indexOfBracket) + return false; + + argumentTypeName = fullTypeName.Substring(indexOfBracket + 1, lastIndexOfBracket - indexOfBracket - 1); + + while (argumentTypeName.StartsWith("[") && argumentTypeName.EndsWith("]")) + { + argumentTypeName = argumentTypeName.Substring(1, argumentTypeName.Length - 2); + } + + return true; + } + + #endregion } From 20cb1e0db551f03faf7b7cd80fd7e33e483a13b3 Mon Sep 17 00:00:00 2001 From: Alexey Yakovlev Date: Tue, 28 Apr 2026 23:32:14 +0300 Subject: [PATCH 3/4] Added missing using System.Reflection. --- CoreRemoting.Tests/BsonSerializationTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/CoreRemoting.Tests/BsonSerializationTests.cs b/CoreRemoting.Tests/BsonSerializationTests.cs index cbe3c94..ee84123 100644 --- a/CoreRemoting.Tests/BsonSerializationTests.cs +++ b/CoreRemoting.Tests/BsonSerializationTests.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Net; using System.Numerics; +using System.Reflection; using System.Text; using CoreRemoting.RpcMessaging; using CoreRemoting.Serialization.Bson; From 75f18f2c68c8bca72bd0cdf3e9e564ee9a7c8b47 Mon Sep 17 00:00:00 2001 From: seatlemorning <49031535+seatlemorning@users.noreply.github.com> Date: Wed, 29 Apr 2026 08:37:58 +0300 Subject: [PATCH 4/4] Delete extra tests --- CoreRemoting.Tests/BsonSerializationTests.cs | 29 -------------------- 1 file changed, 29 deletions(-) diff --git a/CoreRemoting.Tests/BsonSerializationTests.cs b/CoreRemoting.Tests/BsonSerializationTests.cs index ee84123..cdfdce9 100644 --- a/CoreRemoting.Tests/BsonSerializationTests.cs +++ b/CoreRemoting.Tests/BsonSerializationTests.cs @@ -1007,35 +1007,6 @@ public void Integration_DeeplyNestedGenerics_ShouldWork() Assert.NotNull(resultType); Assert.Equal(typeof(List>>).Name, resultType.Name); } - - [Fact] - public void TryExtractArrayElementType_ShouldExtractFromSimpleArray() - { - var result = _converter.TryExtractArrayElementType("System.Int64[]", out var elementType); - - Assert.True(result); - Assert.Equal("System.Int64", elementType); - } - - [Fact] - public void TryExtractArrayElementType_ShouldExtractFromJaggedArray() - { - var result = _converter.TryExtractArrayElementType("System.Int64[][]", out var elementType); - - Assert.True(result); - Assert.Equal("System.Int64[]", elementType); - } - - [Fact] - public void TryExtractArrayElementType_ShouldExtractFromGenericArray() - { - var result = _converter.TryExtractArrayElementType( - "System.Collections.Generic.List`1[[System.Int32]][]", - out var elementType); - - Assert.True(result); - Assert.Equal("System.Collections.Generic.List`1[[System.Int32]]", elementType); - } #endregion