Json.NET fails to serialize dictionary of sets - c#

I'm trying to serialize (and later on deserialize) a Dictionary<string, ISet<string>>. Unfortunately, Json.NET (v6.0.3 via NuGet) fails to do this. What I do is
var value = new Dictionary<string, ISet<string>>
{
{"foo", new HashSet<string>{"bar"}}
};
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects,
TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple
};
var json = JsonConvert.SerializeObject(value, settings);
The variable json then holds the string
{"$type":"System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Collections.Generic.ISet`1[[System.String, mscorlib]], System]], mscorlib","foo":["bar"]}
Now I want to deserialize the string with
JsonConvert.DeserializeObject<IDictionary<string, ISet<string>>>(json, settings);
which fails in resolving the Dictionary type, because it cannot find the System assembly that ISet belongs to. When I instead do the following
JsonConvert.DeserializeObject<IDictionary<string, ISet<string>>>(json);
everything works just fine. So it appears that the TypeNameHandling setting (changing/omitting the format does not change anything) actually breaks deserialization.
I found that, if I don't set the TypeNameHandling settings, the serialized $type properties are simply ignored. I.e., when deserializing, the type information is taken solely from the target type where they are resolved. This works.
When setting the TypeNameHandling, type resolution happens via reflection within the Json.NET assembly, which does not have a dependency on the System assembly and therefore fails to resolve the ISet interface. I can fix this by registering on AppDomain.CurrentDomain.AssemblyResolve and resolving the assembly like so
if (args.Name == "System")
{
return typeof (ISet<>).Assembly;
}
but this seems very fragile to me, as it solves the problem for only this one specific assembly and I have to add another case for every assembly I need somewhere.
Does anyone have experience on how to solve this? Any help is greatly appreciated!

Similar problem was addressed in the following thread: How can I deserialize with TypeNameHandling.Objects in Json.NET Silverlight?
The problem seems to be that Json is failing to load the assembly with partial name. You could overcome this issue by using TypeNameAssemblyFormat = FormatterAssemblyStyle.Full.

If you check the source code for NewtonSoft.Json in DefaultSerializationBinder.GetTypeNameFromTypeNameKey approx. row 83.. It tries to get the type
System.Collections.Generic.Dictionary2[[System.String, mscorlib],[System.Collections.Generic.ISet1[[System.String, mscorlib]], System]]
from assembly
mscorlib.dll
Type type = assembly.GetType(typeName);
Which returns null. This is with FormatterAssemblyStyle.Simple.
If you use FormatterAssemblyStyle.Full then the same type will be:
System.Collections.Generic.Dictionary2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Collections.Generic.ISet1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
And then:
Type type = assembly.GetType(typeName);
Will work and hence the deserialization works as well.
I cant say why this is the case though.

Related

Binary serialization between .NET Core and .NET

So i have dictionary of key and value where value is an object, when exchanging the binary data between .NET Core and .NET and vice versa the serialization fails with
System.Runtime.Serialization.SerializationException: 'Unable to load type System.Collections.Generic.Dictionary`2[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Collections.Generic.IDictionary`2[[SharedLib.HostPropertyType, DataInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]] required for deserialization.'
which makes perfect sense since target type is located in another library, the question is whether there is a way to avoid this without creating custom types ?
Thanks.
Although it's something you haven't specified, it looks like you're using the BinaryFormatter. This is a awkward formatter to use, becasue it can be very picky about what assemblies the types it uses comes from, which it looks like you've run in to.
However, you should be able to specify how the formatter binds its types by setting the Binder property.
If you check out the MS Docs link above, you should be able to see what you need to do. I've copied it here too:
sealed class Version1ToVersion2DeserializationBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
Type typeToDeserialize = null;
// For each assemblyName/typeName that you want to deserialize to
// a different type, set typeToDeserialize to the desired type.
String assemVer1 = Assembly.GetExecutingAssembly().FullName;
String typeVer1 = "Version1Type";
if (assemblyName == assemVer1 && typeName == typeVer1)
{
// To use a type from a different assembly version,
// change the version number.
// To do this, uncomment the following line of code.
// assemblyName = assemblyName.Replace("1.0.0.0", "2.0.0.0");
// To use a different type from the same assembly,
// change the type name.
typeName = "Version2Type";
}
// The following line of code returns the type.
typeToDeserialize = Type.GetType(String.Format("{0}, {1}",
typeName, assemblyName));
return typeToDeserialize;
}
}
The general idea that you want to do I think is essentially tell the formatter that it's OK to use the .net core version of the IDictionary, by swapping the assembly that it's looking for with the one you're using.
As you haven't specified exactly what versions of .net core and fraemwork you're using, I can't really be any more specific, but that should help you out.

Why are these Type objects not equal?

I have an application where I load plugins by reading their DLL file and then loading the bytes using AppDomain.CurrentDomain.Load(bytes). Note that the application and the plugin are loaded in the same AppDomain. The plugin contains several classes which register themselves in a service locator system using a static constructor.
Later, my main application tries to find and instantiate one of these service classes using the service locator, but it cannot find the class. Upon manual inspection, I can see that the registry entry is present in the locator, so it was registered, but for some unknown reason the types aren't equal.
I then put a breakpoint at the place where the type is registered and discovered the following weirdness:
How can typeof(IViewFor<CompactDashboardViewModel>) not be equal to itself?
I then tested a few more things:
t == t
true
typeof(IViewFor<CompactDashboardViewModel>) == typeof(IViewFor<CompactDashboardViewModel>)
true
t.AssemblyQualifiedName == typeof(IViewFor<CompactDashboardViewModel>).AssemblyQualifiedName
true
In fact, everything about these 2 Type objects seems to be equal, except the m_handle and m_cache fields.
typeof(IViewFor<CompactDashboardViewModel>).TypeHandle
{System.RuntimeTypeHandle}
Value: 0x08690784
m_type: {Name = "IViewFor`1" FullName = "ReactiveUI.IViewFor`1[[PluginMTSICS.ViewModel.CompactDashboardViewModel, PluginMTSICS, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]"}
t.TypeHandle
{System.RuntimeTypeHandle}
Value: 0x0f8cf5a8
m_type: {Name = "IViewFor`1" FullName = "ReactiveUI.IViewFor`1[[PluginMTSICS.ViewModel.CompactDashboardViewModel, PluginMTSICS, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]"}
Does anybody know what is happening here? I am using .NET 4.7.1.
I am trying to create an MCVE, but unsuccessfully so far.
Maybe this works:
Type t = typeof(IViewFor<CompactDashboardViewModel>);
//this should evaluate to true:
bool result = t.Equals(typeof(IViewFor<CompactDashboardViewModel>));
Type.Equals docs:
https://msdn.microsoft.com/en-us/library/3ahwab82(v=vs.110).aspx
EDIT:
After reading this post Type Checking: typeof, GetType, or is? i would expect this to work:
Type t = typeof(IViewFor<CompactDashboardViewModel>);
//this should evaluate to true:
bool result = t is IViewFor<CompactDashboardViewModel>;
OK, so I fixed the issue. Here is what I did:
My main application had a reference to a library project, which in turn referenced the plugin project. This probably caused the assembly to be loaded twice, in different load contexts (see links below for more info). I removed the reference. The problem was not fixed, and now weird stuff was happening such as typeof(CompactDashboardViewModel) == null.
My plugin loading code originally used appdomain.Load(bytes). I replaced this with Assembly.LoadFrom. typeof() now worked correctly, and works as expected. However, Type.GetType() still returns null sometimes.
I replaced Assembly.LoadFrom with Assembly.Load and added my plugin directory to the probing path using the <probing> tag in app.config. Everything works correctly now, however I can't load the plugins by filepath, as Assembly.Load requires the assembly name. Not ideal, but I can live with that.
Useful sources:
Best practices for assembly loading
<probing> element
Type.GetType() docs
LoadFrom isolation
Choosing a binding context

'object' does not contain a definition for '...' within same assembly

Per my boss' recommendations, we are using Dapper to access our database. As this is just a quick, throwaway project, he would like to forego creating POCO objects for the results of these calls to the database, so we're just having Dapper return dynamic objects.
So I have several Web API controllers, all deriving from the same BaseController. In the base class, we have a method like this:
protected dynamic ExecuteSingle(string sql, object parameters = null, bool useStoredProcedure = true) {
using (var db = ObjectFactory.GetInstance<IDbManager>())
{
var cmd = useStoredProcedure ? db.SetSpCommand(sql) : db.SetCommand(sql);
if (parameters != null)
{
cmd = cmd.SetParameters(parameters);
}
return cmd.ExecuteObject<dynamic>();
}
}
We are able to use that successfully when taking that result and passing it as the parameter to a Ok() return value. All of the properties of the object get successfully parsed into the JSON object.
I'm now working on another section of code where instead of immediately spitting it back out, I need to use the data coming back to hit another set of functionality.
var sql = " SELECT Id FROM Table WHERE ReviewId = :ReviewId ";
dynamic dbSurvey = ExecuteSingle(sql, new {ReviewId = reviewId}, false);
var survey = sg.GetSurvey(new GetSurveyRequest(dbSurvey.Id));
It then fails on that last line where calling dbSurvey.Id with an exception saying
'object' does not contain a definition for 'Id'
Checking the object, there is a property on there named "Id".
I have checked several questions already, and even though they are all dealing with Anonymous objects (which I could understand dynamic being under that heading), I am staying within the same assembly, so those points about Anonymous objects being declared as "internal" wouldn't apply in this case.
I also tried changing the return type of ExecuteSingle to an ExpandoObject, but am getting the same results.
EDIT
Here is a screenshot of how it is being called, versus the object and properties that it contains. Perhaps the format of the properties could help determine why it's not being found.
I also used the Watch menu to try various ways to access the property, and none of the following worked:
dbSurvey["Id"]
(dbSurvey as IDictionary<string, long>)["Id"]
((IDictionary<string, int>)dbSurvey)["Id"]
Here is the result of dbSurvey.GetType() from the immediate window while running:
{<>f__AnonymousType2`1[System.Int64]}
base: {Name = "<>f__AnonymousType2`1" FullName = "<>f__AnonymousType2`1[[System.Int64, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}
Assembly: {***.Web.Test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null}
AssemblyQualifiedName: "<>f__AnonymousType2`1[[System.Int64, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], ***.Web.Test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
BaseType: {Name = "Object" FullName = "System.Object"}
ContainsGenericParameters: false
DeclaringMethod: 'dbSurvey.GetType().DeclaringMethod' threw an exception of type 'System.InvalidOperationException'
DeclaringType: null
FullName: "<>f__AnonymousType2`1[[System.Int64, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"
GenericParameterAttributes: 'dbSurvey.GetType().GenericParameterAttributes' threw an exception of type 'System.InvalidOperationException'
GenericParameterPosition: 'dbSurvey.GetType().GenericParameterPosition' threw an exception of type 'System.InvalidOperationException'
GUID: {382c0269-d631-3c89-a105-38a1be8a3db7}
IsConstructedGenericType: true
IsEnum: false
IsGenericParameter: false
IsGenericType: true
IsGenericTypeDefinition: false
IsSecurityCritical: true
IsSecuritySafeCritical: false
IsSecurityTransparent: false
MemberType: TypeInfo
MetadataToken: 33554480
Module: {***.Web.Test.dll}
Name: "<>f__AnonymousType2`1"
Namespace: null
ReflectedType: null
StructLayoutAttribute: {System.Runtime.InteropServices.StructLayoutAttribute}
TypeHandle: {System.RuntimeTypeHandle}
UnderlyingSystemType: {Name = "<>f__AnonymousType2`1" FullName = "<>f__AnonymousType2`1[[System.Int64, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}
OK, so I resolved this issue. I just figured out what the problem was. Thanks to xanatos' help in the comments, we noticed that the Module property of the type was coming from the Test harness. That didn't make any sense to me at the time, because the dynamic object was being created in the same assembly.
However, what I didn't think about until I came back to the issue this morning was that the source of the object being "created" in the base API Controller was an anonymous object that I was creating in the Test harness. So that Module was correct.
If I went and created a separate POCO object for the Mock object in the Test DLL, then it was no longer creating a dynamic off of an anonymous object created in another assembly.

Why am I getting assembly version as 0.0.0.0? Will that make any issues if real DLL has some version number and using Type class to retrieve values?

I have a project named "Test.LiveModel" (Test.LiveModel.dll) and its version is 8.0.7.0 in my solution which contains 25 projects. I can see the information of Test.LiveModel in AssemblyInfo.cs. I have two category of objects named 'base class category' and 'user-defined class category' which are displaying in my application UI. I am displaying this through a property which is of class Type
Now I am considering one base class category object named "Server" and one user-defined class category object RoundedTree. When I set value as "Server" in Property in Grid after saving it when I restart my application I can see the saved value, but for "RoundedTree" which is not happening due to type becomes null. So I did a thorough analysis and came to know that issue is in ToType() method shown below
This is ToType() metho
For base class Server xmlSerializableType.Name, I am getting as Test.LiveModel.Server and AssemblyName I am getting as Test.LiveModel, Version=8.0.7.0, Culture=neutral, PublicKeyToken=23bd062a94e26d58 and type I am getting by using Type.GetType as type = {Name = "Server" FullName = "Test.LiveModel.Server"}
But for user defined class xmlSerializableType.Name I am getting as _Rounded_Tree. 'type' I am getting as null by using Type.GetType. AssemblyName I am getting as _Rounded_TreeTest-Machine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null, but even assembly.GetType I am getting as null. What is the reason behind it? Why am I getting assembly version 0.0.0.0? I mean full assembly _Rounded_TreeTest-Machine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null.
This is the method CreateType() which will create assembly and type as myTypeBuilder for userdefined class:
public Type CreateType()
{
// Create the assembly name by appending the machine name to the typename.
myAsmName.Name = this.TypeName + Environment.MachineName;
// Define assembly that can be executed but not saved
this.UserClassAssemblyBuilder = myDomain.DefineDynamicAssembly(myAsmName, AssemblyBuilderAccess.Run);
// Create dynamic module with symbol information
this.UserClassModuleBuilder = this.UserClassAssemblyBuilder.DefineDynamicModule("userdefinedmodule", true);
So here is my question: if real Dll has some version number, and user defined class assembly has version 0.0.0.0, is that the reason why I am getting type as null after using Type.GetType and assembly.GetType method?
Here are some suggestions which may solve the problems.
Define a assembly version
new AssemblyName(this.TypeName + Environment.MachineName)
{
Version = new Version("1.0.0.0")
};
Use full qualified names for the serialization
myObject.GetType().FullName

C# - Custom Attribute not found from GetCustomAttribute from Interface

I am attempting to setup a custom attribute like the following:
[AttributeUsageAttribute(AttributeTargets.Method)]
public sealed class AuthorizationAttribute : Attribute
{
public AuthorizationAttribute(bool required)
{
Required = required;
}
public bool Required;
}
In my service contract interface, I have a method like such:
[OperationContract]
[Authorization(true)]
[WebGet(ResponseFormat = WebMessageFormat.Json, UriTemplate = "randommethod")]
ReturnObject RandomMethod();
When I do the following I see it in the list, but but the 'is' comparison, fails:
foreach(object attribute in methodInfo.GetCustomAttributes(true)) // Returns all 3 of my attributes.
if (attribute is AuthorizationAttribute) //Does not pass
I have tried to do the following that returns false:
Attribute.IsDefined(methodInfo, typeof(AuthorizationAttribute));
attribute.GetType().IsAssignableFrom(typeof(AuthorizationAttribute));
I have also done the following 2 things that returns null:
AuthorizationAttribute authAttribute = attribute as AuthorizationAttribute;
Attribute attribute = Attribute.GetCustomAttribute(methodInfo, typeof(AuthorizationAttribute));
I am not sure what I am doing wrong here. It seems like it should work, but I am sure I am making a simple mistake somewhere. Any insight?
Thanks for any assistance.
Edit:
I am not sure if it adds any meaning, but the AuthorizationAttribute declaration exists in a different project from my services project. The Service Contract interface exists in the same project as the AuthorizationAttribute.
I tried doing a cast and got the following exception:
[A]Lib.OAuth.AuthorizationAttribute cannot be cast to [B]Lib.OAuth.AuthorizationAttribute.
Type A originates from 'Lib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the
context 'LoadNeither' at location 'F:\RestServices\bin\Lib.dll'. Type B originates from 'Lib,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location
'C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\oauth_rest\951069b9
\9f7b77fe\assembly\dl3\54c48906\f928a6ad_01facb01\Lib.dll'.
Any ideas?
The exception contains the answer:
Type A originates...at location 'F:\RestServices\bin\Lib.dll'. Type B originates...at location
'C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\oauth_rest\951069b9
\9f7b77fe\assembly\dl3\54c48906\f928a6ad_01facb01\Lib.dll'
The issue is that the Lib.OAuth.AuthorizationAttribute type which attributes your method is found in an assembly that is different than the assembly loaded at runtime when you try to cast.
Is it possible that one of your projects is using an old version of Lib.dll?
Thanks to Wesley's response, I was able to figure this out. It is more of a 'duh' moment than anything.
I was using some example code for reflection to load an assembly via the Assembly.LoadFile(...) method. The problem is that since my assembly was not registered with the GAC, it was reading the local copy on the IIS server and the comparison failed.
For reference, this was my solution:
Assembly.GetExecutingAssembly();
Once I did that, everything worked.

Categories

Resources