Issue Found With CSLA 3.6.0.0 MobileFormatter (+Fix)

Issue Found With CSLA 3.6.0.0 MobileFormatter (+Fix)

Old forum URL: forums.lhotka.net/forums/t/6078.aspx


jcrivello posted on Sunday, January 04, 2009

I am using the latest versions of Csla & CslaLight to help build a 3 tier Silverlight application. I quickly ran into an issue when MobileFormatter would try to serialize types with properties of type DateTimeOffset. Oddly enough, it seems that System.Runtime.Serialization.DataContractSerializer does not treat DateTimeOffset as a known type by default, even though it is treated as a primitive in the serialized XML form (very odd IMHO)..

Excerpt from http://msdn.microsoft.com/en-us/library/ms731923.aspx :

Unlike other primitive types, DateTimeOffset is not a known type by default. For more information, see Data Contract Known Types).

Because of this I was running into the following (very long) exception on the server-side when performing data portal fetches:

System.Runtime.Serialization.SerializationException: Type 'System.Runtime.Serialization.DateTimeOffsetAdapter' with data contract name 'DateTimeOffset:http://schemas.datacontract.org/2004/07/System' is not expected. Add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeAndVerifyType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, Boolean verifyKnownType, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithXsiType(XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle objectTypeHandle, Type objectType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle, Type declaredType)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerializeReference(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
   at WriteSerializationInfo.FieldDataToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , ClassDataContract )
   at System.Runtime.Serialization.ClassDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerializeReference(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
   at WriteKeyValueOfstringSerializationInfo.FieldDataOzoZvLrmToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , ClassDataContract )
   at System.Runtime.Serialization.ClassDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
   at WriteArrayOfKeyValueOfstringSerializationInfo.FieldDataOzoZvLrmToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , CollectionDataContract )
   at System.Runtime.Serialization.CollectionDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerializeReference(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
   at WriteSerializationInfoToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , ClassDataContract )
   at System.Runtime.Serialization.ClassDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
   at WriteArrayOfSerializationInfoToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , CollectionDataContract )
   at System.Runtime.Serialization.CollectionDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
   at System.Runtime.Serialization.DataContractSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph)
   at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph)
   at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph)
   at System.Runtime.Serialization.DataContractSerializer.WriteObject(XmlWriter writer, Object graph)
   at Csla.Serialization.Mobile.MobileFormatter.Serialize(XmlWriter writer, Object graph) in C:\TFS\Common\Csla\Csla\Serialization\Mobile\MobileFormatter.cs:line 85

I was able to fix this problem with a simple change to to the Csla MobileFormatter class (which is also apparently included in CslaLight). Basically I just provided DateTimeOffset as a known type to the DataContractSerializer constructor. I thought I would post my solution here so you guys can incorporate the fix into your code if you liked. Here is the diff:

---     Sun Jan 4 07:58:41 2009 UTC
+++ C:\TFS\Common\Csla\Csla\Serialization\Mobile\MobileFormatter.cs    Sun Jan 4 07:54:15 2009 UTC
@@ -80,7 +80,7 @@
 
       DataContractSerializer dc = new DataContractSerializer(
         typeof(List<SerializationInfo>),
-        new Type[] { typeof(List<int>), typeof(byte[]) });
+        new Type[] { typeof(List<int>), typeof(byte[]), typeof(DateTimeOffset) });
 
       dc.WriteObject(writer, serialized);
     }
@@ -182,7 +182,7 @@
     {
       DataContractSerializer dc = new DataContractSerializer(
         typeof(List<SerializationInfo>),
-        new Type[] { typeof(List<int>), typeof(byte[]) });
+        new Type[] { typeof(List<int>), typeof(byte[]), typeof(DateTimeOffset) });
 
       List<SerializationInfo> deserialized = dc.ReadObject(reader) as List<SerializationInfo>;

- Joe Crivello

RockfordLhotka replied on Monday, January 05, 2009

Thank you! Added to the bug list at http://www.lhotka.net/cslabugs/edit_bug.aspx?id=274

markell replied on Sunday, December 06, 2009

Dear sirs.
The problem still exists. I have stumbled upon the same issue in my unit tests when I am trying to serialize a MobileList<object>, which contains (among other things) DateTimeOffest instances. This is because DataContractSerializer used in MobileList never given any known types.

In fact, MobileList of any enumerated type does not serialize as well (I think any, though I have tested with just two). This is also something that gets treated in MobileFormatter (ConvertEnumsToIntegers - ConvertEnumsFromIntegers), but is not treated in MobileList.

How do I proceed from here? Adding known type is easy, but looks expensive - examine the list and collect all the enum types.

And what is your opinion?

Thanks.

RockfordLhotka replied on Sunday, December 06, 2009

I think there's a bug already recorded for DateTimeOffset not working.

MobileFormatter only handles "primitive types", though we've augmented that definition by adding a few well-known common types like Guid, etc. Actually it is DataContractSerializer that is the limitation here, not really MobileFormatter itself, but you get the idea.

Of course any IMobileObject works too - so primitive types, IMobileObject types and the few extra types we added are the only support field types.

This means that you do need to make your enum into some primitive type. Enums are pretty easy though, since they always convert into a numeric, so you might consider just making the internal type int and the external type your enum. I think SetPropertyConvert() will handle this automatically - I made CoerceValue() handle enums a long time ago.

markell replied on Monday, December 07, 2009

Hi Rocky.
Thanks for the prompt reply.

My problem is not with PropertyInfo of a business object. What I have is a list of objects declared as MobileList<object>. It may contain all sorts of types of objects. When there is instance of some Enum type or DateTimeOffset the list fails to serialize.

The thing is that MobileFormatter knows how to deal with them - it adds DateTimeOffset to the known types of DCS and replaces all enums with integers. MobileList, on the other hand, lags behind, because it deals with neither.

I hardly see how to fix it, except change the MobileList (and other mobile collections) to add this extra processing:
1. Add a protected virtual method GetKnownTypes(). The default implementation will scan the list and collect all the distinct enum types which instances are found. In addition, if a DateTimeOffset is found, its type is added as well. This way mobile collections are aligned with MobileFormatter with respect to handling DCS known types. If more are needed, the user will have to derive from MobileList and provide a custom implementation.
2. DCS will be created with the known types returned with GetKnownTypes()
3. The list of the known types is serialized as well as a list of strings using MobileFormatter.
4. On deserialization, the known types are deserialized first, the list of known types is composed and passed to DCS constructor.

The purpose of the described scheme is bring the mobile collections on a par with the MobileFormatter.

What do you think?

P.S.
Another improvement to MobileList would be introducing a virtual property named MobileChildren, which indicates whether to use MobileFormatter when serializing the items. The default implementation would return true if and only if either the list is empty or the first item is a mobile object. Rationale - we should not mix mobile and non mobile objects in a single collection. The current static check is not good enough. Also, OnSetChildren method must not use MobileChildren property to determine how to deserialize. Rather it would deserialize "$list" and check whether it is IList<int> (MobileFormatter) or byte[] (DCS).
This change allows to declare MobileList<ISomeInterface>, where the interface itself does not derive from IMobileObject, but the concrete types are.
I have the code working and tested, so I can post it somewhere, if you want.

markell replied on Wednesday, December 16, 2009

Anyone?

RockfordLhotka replied on Wednesday, December 16, 2009

Try this and see if it works, then let us know.

markell replied on Wednesday, December 16, 2009

Yes, it does.
I will be glad to share my changes with the community.

Copyright (c) Marimer LLC