Hi,
i noticed a strange bug affecting the OnDeserializedHandler (with the OnDeserializedAttribute): when this handler is called, the items of some collections or dictionary objects are not yet deserialized. I ecountered this playing with the new Shared Validation Rules: some rules in my BO depend on a Dictionary(Of String, String) object, which was not properly populated in the handler (.Count = 0) . But inspecting the object after deserialization shows that all items are there. It looks like the OnDeserializedHandler is called too early.
Here is a simple console application to reproduce this bug:
Imports System.IO
Imports System.Runtime.Serialization.Formatters.Binary
Imports System.Runtime.Serialization
Imports System.Collections.Specialized
Imports System.ComponentModel
Module Module1
Sub Main()
Dim obj1 As New MySerializableClass
obj1.Populate()
Using buffer As New MemoryStream()
Dim formatter As New BinaryFormatter
formatter.Serialize(buffer, obj1)
buffer.Position = 0
obj1.Print(" before Serialization")
Dim obj2 As MySerializableClass = _
DirectCast(formatter.Deserialize(buffer), MySerializableClass)
obj2.Print(" after Deserialization")
End Using
End Sub
End Module
<Serializable()> _
Public Class MySerializableClass
Public genDic As Dictionary(Of String, String)
Public lst As List(Of String)
Public hashTbl As Hashtable
Public hd, hdl As HybridDictionary
Public sd As StringDictionary
Public ld As ListDictionary
Public sc As StringCollection
Public sl As SortedList
Public gensl As SortedList(Of String, String)
Public gensd As Generic.SortedDictionary(Of String, String)
Public bl As BindingList(Of String)
Public Sub Populate()
genDic = New Dictionary(Of String, String)
genDic.Add("foo", "bar")
lst = New List(Of String)
lst.Add("foo")
hashTbl = New Hashtable
hashTbl.Add("foo", "bar")
hd = New HybridDictionary
hd.Add("foo", "bar")
hdl = New HybridDictionary
For i As Integer = 1 To 10
hdl.Add(i, i)
Next
sd = New StringDictionary
sd.Add("foo", "bar")
ld = New ListDictionary
ld.Add("foo", "bar")
sc = New StringCollection
sc.Add("foo")
sl = New SortedList
sl.Add("foo", "bar")
gensl = New SortedList(Of String, String)
gensl.Add("foo", "bar")
gensd = New Generic.SortedDictionary(Of String, String)
gensd.Add("foo", "bar")
bl = New BindingList(Of String)
bl.Add("foo")
End Sub
<OnDeserialized()> _
Private Sub OnDeserializedHandler(ByVal context As StreamingContext)
Me.Print(" in OnDeserializedHandler")
End Sub
Public Sub Print(ByVal location As String)
Print("small HybridDictionary : ", hd.Count, location)
Print("ListDictionary : ", ld.Count, location)
Print("StringCollection : ", sc.Count, location)
Print("SortedList : ", sl.Count, location)
Print("List(Of String) : ", lst.Count, location)
Print("SortedList(Of String, String) : ", gensl.Count, location)
Print("BindingList(Of String) : ", bl.Count, location)
'these have no items in OnDeserializedHandler:
Print("Hashtable : ", hashTbl.Count, location)
Print("large HybridDictionary : ", hdl.Count, location)
Print("StringDictionary : ", sd.Count, location)
Print("Dictionary(Of String, String) : ", genDic.Count, location)
Print("SortedDictionary(Of String, String): ", gensd.Count, location)
Console.WriteLine()
End Sub
Private Sub Print(ByVal s As String, ByVal iCount As Integer, _
ByVal location As String)
Console.WriteLine(s & iCount.ToString & location)
End Sub
End Class
I think a problem I'm seeing is a result of this behavior, and wanted to see if people agree or if I'm off base:
Apparently, Dictionary(Of String, Object) falls into this not-available-in-OnDeserialized category. This has the (very unfortunate) effect of preventing me from accessing business object properties from the OnDeserialized handler. What seems to be happening is: the CanReadProperty() call in a property's Get method (after several levels of other calls) calls GetRolesForProperty, which tries to access the AuthorizationRules.Rules property, and fails with a NullReferenceException.
This is a very subtle and non-intuitive problem. (It's been driving me crazy for hours now.)
Does this sound reasonable or am I missing something else? Has anyone else run into this?
Thanks,
Andy
Andy,
I just read this thread. Wow. What a repro from olafb.
As far as your problem goes, can you skip the Property Get/Set and use the member variable directly instead?
===================================
Rocky,
You win the bet - all the objects do implement ISerializable.
Can you please elaborate on the implications of this with respect to Andy's problem and other things we should be careful about while handling OnDesrialized? Also, do the calls that you make to OnDeserialized in the framework work in all cases? Or are there other problems we should be aware of?
Joe
Thanks everyone for the Info. For my current case, I'm now using a friend property that doesn't check rules. (I didn't want to expose the field directly.)
Rocky, could you elaborate on your statement that "by the time an object is created, all objects it depends on exist", please? Does this mean that when any of the objects in the graph's OnDeserialize method is called, all objects in the graph will at least exist, if not be fully initialized?
I ask because I have a model where some great-great-grandchildren of the base object have an interface reference to the base object. (It provides some services to these children.) For this to work if/when I clone all or part of the tree, each of the children needs to keep a reference to this interface, for instantiating their children, and so on down to the bottom, where the great-great grandchildren consume the interface. (This is loosely based on the CSLA Parent references.) My question is how I should propagate this down the tree.
I see two options: First, I could have each child have it's own OnDeserialize method that updates its children. But, do I know that the child's parent has updated the child yet? The other option seems to be to have each child's OnDeserialize call the base object and let it walk through its children, who in turn walk thier own children, etc. But, this seems like a lot of redundant calling.
Thanks,
Andy
Okay, here's what I decided: I'm overloading the Clone method in each of my child objects, so it calls the base Clone and then calls a SetInterface method on the clone, which in turn calls its children etc. That way I ensure that each of the child objects will exist.
This works fine in a 1-machine scenario, but I suspect I will have trouble if/when I start to use remoting, since the serialization/deserialization won't be done as a result of the Clone method call. I'm under some time pressure, so I guess I'll have to cross that bridge when I come to it. I'll keep you posted if I learn anything or have any brilliant insights.
Thanks for the help,
Andy
One thing I forgot to mention. To override the Clone method, I had to change the Clone function signature in BusinessListBase.vb from:
Public Overloads Function Clone() As T
to
Public Overridable Function Clone() As T
Since this now matches the signature from BusinessBase.vb, is it safe to assume that the original signature was incorrect, or am I missing something?
Thanks,
Andy
From: AndrewCr [mailto:cslanet@lhotka.net]
Sent: Wednesday, August 30, 2006 12:05 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] MS-bug in OnDeserializedHandler?One thing I forgot to mention. To override the Clone method, I had to change the Clone function signature in BusinessListBase.vb from:
Public Overloads Function Clone() As T
to
Public Overridable Function Clone() As T
Since this now matches the signature from BusinessBase.vb, is it safe to assume that the original signature was incorrect, or am I missing something?Thanks,
Andy
Will do.
Thanks,
Andy
Andy, have you tried calling OnDeserialization() in your dictionary??
From: xal [mailto:cslanet@lhotka.net]
Sent: Wednesday, August 30, 2006 7:31 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: MS-bug in OnDeserializedHandler?
As I said in a previous post, that forces deserialization, and after that, you can use it inside your OnDeserialized()...
AndrC)s
I had this same issue and came up with another workaround. Basically, I'm serializing the Dictionary's keys and values separately and rebuilding it during deserialization.
Here's more info:
http://blogs.claritycon.com/blogs/bryan_dougherty/archive/2006/09/08/1775.aspx
I am using activeobjects , and am using custom activelookups, when debugging vs is complaining about ondeserialized method , when getting a value from the activelookup.
What should i do , the activelookups inherit from NameValueListBase
Thank's.
Willem
That would be nice, cause in csla /activeobjects 1.5, there were no such issues.
My email adress is : wilfredvancasteren@hotmail.com
thank you
xal:I've attached my version here so that anybody can get it...
Cheers,
Andrés
I have a problem creating a custom activelookup , should i use T as below, or use (of K, V) or something else. it's just not easy using templates.
Public Class ActiveDatatypeLookup(Of T)
Inherits NameValueListBase(Of String, String)
.....
End class
Public Class ActiveDatatypeLookup
Inherits NameValueListBase(Of String, String)
.....End class
Andrés
Copyright (c) Marimer LLC