Open murtymaganti opened 11 months ago
Unfortunately, the official Microsoft documentation doesn't seem to know about a Microsoft.Reporting.WebForms.ReportHierarchy
. The only MS documentation regarding the Microsoft.Reporting.WebForms
namespace is regarding SQL Server 2016 (https://learn.microsoft.com/en-us/dotnet/api/microsoft.reporting.webforms?view=sqlserver-2016), which appears to be rather old, and doesn't mention a ReportHierarchy
type.
Without knowing details regarding the ReportHierarchy
and how it is being used by the MS report viewer, i think your best bet is writing (or finding) a JsonConverter that deals with writing and reading ReportHierarchy
to/from json.
Ideally, you would base this converter on the generic JsonConverter<T>
class. The implementation of the WriteJson
method would deal with writing the appropriate json text for a given ReportHierarchy
instance (which would be called during serialization). The reading of such json text back and contructing a ReportHierarchy
instance from it would be done by the implementation of the ReadJson
method (which would be called during deserialization). If you prefer using the non-generic JsonConverter
type as the base class for your converter, you would also need to implement the CanConvert
method accordingly so that the converter will apply to values of type ReportHierarchy
.
Then configure the (de)serializer to use this JsonConverter. This can be done in a number of ways, and which you choose depends specifically on how you set up and do the de/serialization. Arguably, the most common way is to configure a JsonSerializerSettings
instance with the converter and pass it to the Newtonsoft.Json (de)serialization method you are using.
(P.S.: I am not the author/maintainer of the library; just a user like yourself.)
Thanks @elgonzo
It is weird that Microsoft.Reporting.WebForms.ReportHierarchy class is not documented any where online but it is part of the Microsoft reporting viewer.
Now I am trying to do pretty much what you suggested i.e. create an instance of ReportHeirarchy and populate all members from Json text. But that seems to be not possible either as that class doesn't have a parameter less constructor (public or private) so Activator.CreateInstance throws exception (I have to use reflection because it is not part of the referenced DLL but report viewer creates it runtime and saves it to session). Wonder how Newtonsoft library is creating instances without parameter less constructors.
I wish there is a way to pass 'JsonObject' attribute through JsonSerializationSettings in addition to placing the attribute on the class. That will solve this problem.
I wish there is a way to pass 'JsonObject' attribute through JsonSerializationSettings in addition to placing the attribute on the class. That will solve this problem.
There is a way to achieve something similar by creating a custom contract resolver (by deriving from DefaultContractResolver
) that would create and set up a JsonObjectContract for ReportHierarchy
instead of an JsonArrayContract.
But quite likely, this wouldn't get you far. I don't know the ReportHierarchy
implementation, but it being a stack as you said means it can contain stack items that perhaps need to be part of the serialized data. And i suspect these stack items it holds by virtue of being a stack aren't represented by fields or properties of ReportHierarchy
. And if that's the case, instructing Newtonsoft.Json to treat ReportHierarchy
as a json object would then ultimately mean that Newtonsoft.Json would treat ReportHierarchy
not as a stack/collection, with the result that the stack items would be missing in the serialized json.
[...] doesn't have a parameter less constructor (public or private) so Activator.CreateInstance throws exception
Activator.CreateInstance is not limited to parameterless constructors. There are Activator.CreateInstance overloads that accept values to be passed as arguments to parameterized constructors.
Generally speaking, i wonder what you are trying to do here. Considering that ReportHierarchy
seems to be an internal/private type, i am beginning to suspect you don't just want to (de)serialize ReportHierarchy
instances. In case you want to serialize a report viewer instance, think twice. The report viewer is a control, not a data object. Control instances shouldn't really be (de)serialized. Instead serialize (only) the data the report viewer is supposed to present, don't serialize the report viewer itself...
ReportHeirarchy doesn't have it's own state other than being a stack. As you pointed out, serializing the stack would lose the data as the data is held in it's private fields. I need to somehow apply MemberSerialization.Feilds to it so it saves the data.
I am aware that Activator.CreateInstance has overloads that take constructor parameters but I can't or don't want to call those. If the internal state is restored back during deserialization then there is no need for calling the constructor. Similar to a binary formatter serializer which simply saves the 'state' of an instance and restores back.
What I am doing here is part of moving our AspNet sessions to an external storage like Redis. I am not trying to serialize the report control, of course we shouldn't save controls or pages to the session. But the report controller internally saves the data to session (probably to maintain current page, current report etc.). Unfortunately we are using a very old report viewer library which was probably designed for binary serialization but not Json.
Now, I am trying outside the application using the simple class below to see if it can be done. Let me know how (if possible) the below can be serialized and deserialized back as Dummy object (considering you can't modify the class) . If this works, most likely ReportHeirarchy also will work. Appreciate and thank you for all your suggestions so far.
public class Dummy : Stack<string>
{
public Dummy(string name)
{
this.Push(name);
}
}
Dummy dummy = new Dummy("Newtonsoft");
string str = JsonConvert.SerializeObject(dummy);
dummy = JsonConvert.DeserializeObject(str);
ReportHeirarchy doesn't have it's own state other than being a stack. As you pointed out, serializing the stack would lose the data as the data is held in it's private fields. I need to somehow apply MemberSerialization.Feilds to it so it saves the data.
No, that's not what i was saying. I am saying that treating an object that is a stack as a JsonObject during serialization will lead to the stack items not being serialized.
Let me know how (if possible) the below can be serialized and deserialized back as Dummy object (considering you can't modify the class)
Due to a parameterless constructor not existing for your Dummy type, you can't deserialize it from a json array solely relying on Newtonsoft.Json's default serialization behavior.
But even with a paremterless constructor, Dummy wouldn't be simply and easily deserializable, due to pecularities of Newtonsoft.Json's implementation failing on trying to deserialize Stack-derived types even when they have a parameterless constructor (a Dummy type derived from List\<T> for example would be of no issue for Newtonsoft.Json). In such a case where a type derived from Stack\<T> needs to be deserialized, a json converter would be one of the possible workarounds to avoid this issue Newtonsoft.Json's implementation is having with Stack-derived types. (As a side note: If you were to rely on Newtonsoft.Jsons serialization/deserialization for stacks, note that it has a peculiar behavior regarding stacks where the order of the items in the deserialized stack instance are being reversed compared to the original stack instance used for serialization. Iirc - but i might be wrong - System.TextJson also has this peculiar behavior regarding stack serialization/deserialization.)
I am aware that Activator.CreateInstance has overloads that take constructor parameters but I can't or don't want to call those. If the internal state is restored back during deserialization then there is no need for calling the constructor. Similar to a binary formatter serializer which simply saves the 'state' of an instance and restores back.
But you are not using the old binary serialization mechanism here anymore. Don't equate a json serializer to .NET's old and deprecated binary serialization mechanism. Whatever the old binary serialization infrastructure does has no bearing on Newtonsoft.Json's capabilities. Newtonsoft.Json has not the luxury of being an integrated component of the CLR. So, if you don't have an ReportHierarchy
instance existing already that the deserializer could populate with data, then you gotta create a ReportHierarchy
somehow. If you don't want to invoke any of the constructors ReportHierarchy
has (regardless of whether it's public or private, parameterless or parameterized) then your only chance is to find some factory method in that library that would create ReportHierarchy
instances for you. If there is no such factory methods to be found either, well, then you either have to swallow the pill regarding Activator.CreateInstance, or stop right here with your attempts of wanting to deserialize ReportHierarchy
and think of an entirely different approach that's basically avoiding ReportHierarchy
being part of the overall (de)serialization process.
No, that's not what i was saying. I am saying that treating an object that is a stack as a JsonObject during serialization will lead to the stack items not being serialized
Stack items are serialized even when it is serialized as JsonObject. This can be done by adding MemberSerialization.Feilds to the JsonObject attribute of the class. Sadly it is not working when setting it through contract resolver. I have created a ContractResolver extending the DefaultContractResolver and override the CreateContract method and setting the MemberSerialization.Feilds. It just ignores it. It works if I set IgnoreSerializableAttribute to false but that gives another error as the assemblies are not trusted. So in the end, using a custom contract resolver is also not working.
protected override JsonContract CreateContract(Type objectType)
{
if (objectType.FullName.Contains("ReportHeirarchy"))
{
JsonObjectContract jsonObjectContract = base.CreateObjectContract(objectType);
jsonObjectContract.MemberSerialization = Newtonsoft.Json.MemberSerialization.Fields;
return jsonObjectContract;
}
return base.CreateContract(objectType);
}
Stack items are serialized even when it is serialized as JsonObject. This can be done by adding MemberSerialization.Feilds to the JsonObject attribute of the class.
Ah, okay. So there is some ReportHierarchy field through which the stack items are accessible. I missed that earlier. My apologies...
If ReportHierarchy doesn't have a public constructor, setting the contract type to JsonObjectContract alone won't work, as Newtonsoft.Json by default does not invoke private/internal/protected constructors by default. At the least you would have to manually configure the JsonObjectContract to use any such non-public constructor. (As you can't set attributes on the desired ReportHierarchy constructor of choice, you would have to directly manipulate the JsonObjectContract. And how to do that correctly and robustly i don't know, because the whole contract business is very poorly undocumented. Look at the contract's DefaultCreator
and OverrideCreator
properties, beyond that Newtonsoft.Json's source code is your friend...)
Aside from that, not sure what to suggest anymore. I don't know any implementation details of that ReportHierarchy type, so i am flying blind here :-(
@elgonzo I think I can make it work somehow by setting 'IgnoreSerializableAttribute' to false. But running into a trust issue and getting error '[....] implements ISerializable but cannot be serialized using the ISerializable interface because the current application is not fully trusted and ISerializable can expose secure data'
Do you know how to address this issue? At runtime I can see my AppDomain is fully trusted but still getting the error.
Do you know how to address this issue?
No, unfortunately not. :-(
When deserializing an object, getting the error 'Cannot create and populate list type Microsoft.Reporting.WebForms.ReportHierarchy'. ReportHierarchy is part of Microsoft report viewer and this class extends Stack<Reportinfo> so when serializing it, it gets serialized an array instead of as an object. On stack overflow I see suggestions to add JsonObject attribute to the class but this class is in a third party library and I have no access to the class. What is the possible solution to fix this issue? Is there anything that can be set to JsonSerializationSettings instead of an attribute?
Thanks Murty