Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deserialization fails for generic types when mixing .NET Core and .NET Framework #327

Open
sebgrohn opened this issue Dec 18, 2020 · 4 comments · May be fixed by #340
Open

Deserialization fails for generic types when mixing .NET Core and .NET Framework #327

sebgrohn opened this issue Dec 18, 2020 · 4 comments · May be fixed by #340
Labels
Milestone

Comments

@sebgrohn
Copy link

Hi!

We have an issue with CacheManager and our Redis instance when reading and writing cached values from both ASP.NET Core and ASP.NET Framework. The two applications both read and write the same keys, and CacheManager sometimes can't find the correct type when deserializing values. When we started digging into what goes wrong we saw that we had some values serialized with .NET Core assembly name and some with the .NET Framework one: System.Private.CoreLib vs. mscorlib.

I found this code in TypeCache.GetType which, according to the comment, it's supposed to handle exactly this issue. And for simple type strings it works fine:

System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

is simplified to System.Boolean => no problem to look up.

On the other hand, for generic types such as List<string>, it doesn't work:

System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

is simplified to System.Collections.Generic.List`1[[System.String instead of the correct System.Collections.Generic.List`1[[System.String]] => look-up fails. Considering that you can nest generic types arbitrarily, this gives me a headache to think about. 😵

Right now I'm trying out workarounds via RegisterResolveType, such as this a bit hacky one:

TypeCache.RegisterResolveType(typeName => {
    const string NET_CORE_ASSEMBLY_NAME = "System.Private.CoreLib";
    const string NET_FRAMEWORK_ASSEMBLY_NAME = "mscorlib";

    type ??= Type.GetType(typeName.Replace(NET_CORE_ASSEMBLY_NAME, NET_FRAMEWORK_ASSEMBLY_NAME), false);
    type ??= Type.GetType(typeName.Replace(NET_FRAMEWORK_ASSEMBLY_NAME, NET_CORE_ASSEMBLY_NAME), false);

    return type;
});

What are your thoughts about this? 😃

@MichaCo
Copy link
Owner

MichaCo commented Dec 20, 2020

Hi @sebgrohn,
Thanks for reporting this issue. Yeah, that looks like a bug.

As another workaround, maybe try arrays instead of list?
Because, string[] should work just fine, and technically, for serialization there isn't a difference between array and list anyways, performance wise or for your app.

@MichaCo MichaCo added the bug label Dec 20, 2020
@sebgrohn
Copy link
Author

sebgrohn commented Dec 21, 2020

Thanks for the suggestion, that's a good point!

I will admit that my example above was a bit simplified; what we have seen reported in exception logs is for example this:

System.Tuple<System.DateTime, {generic data collection type}<System.Collections.Generic.List<{DTO type}>>>

I'm afraid that we are caching similar types from other places in the code as well.

@sebgrohn
Copy link
Author

sebgrohn commented Dec 21, 2020

Here's the full workaround that we're adding to our shared code, if it is of any use for someone.

When testing it, we saw that it is only a problem when .NET Framework tries to read .NET Core-serialized values, not the other way around: Type.GetType in .NET Core seem to have a built-in fallback for the "mscorlib" assembly name.

TypeCache.RegisterResolveType(typeName => {
    const string NET_CORE_ASSEMBLY_NAME = "System.Private.CoreLib";
    const string NET_FRAMEWORK_ASSEMBLY_NAME = "mscorlib";

    // Try to get the type from the full qualified name.
    var type = Type.GetType(typeName, false);

    // Try remove version from the type string and resolve it (should work even for signed assemblies).
    typeName = Regex.Replace(typeName, @", Version=\d+.\d+.\d+.\d+", string.Empty);
    type ??= Type.GetType(typeName, false);

    // Try replacing .NET Core's core-lib assembly name with corresponding
    // .NET Framework name. This is needed because some caches are shared
    // between .NET Core and .NET Framework
    // projects, with differing assembly names for the standard system types.
    type ??= Type.GetType(typeName.Replace(NET_CORE_ASSEMBLY_NAME, NET_FRAMEWORK_ASSEMBLY_NAME), false);

    return type;
});

MichaCo added a commit that referenced this issue May 31, 2021
… work, but it should improve nested types, generics and such in general

 #327
@MichaCo MichaCo added this to the 2.0.0 milestone May 31, 2021
@MichaCo MichaCo linked a pull request May 31, 2021 that will close this issue
5 tasks
@MichaCo
Copy link
Owner

MichaCo commented May 31, 2021

This will be improved with the next PR.
It's still not 100% going to work for all types because e.g. HashSet was in System.Core in .NET4x, not mscorlib.
But it should work much better for lists and other generics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants