MarimerLLC / cslaforum

Discussion forum for CSLA .NET
https://cslanet.com
Other
31 stars 6 forks source link

Confused about the mobile formatter #320

Open ajj7060 opened 7 years ago

ajj7060 commented 7 years ago

We're working on porting our business library to Xamarin for our mobile app support (in addition to supporting our main WinForms application) and I'm hitting an issue with serialization which I'm not sure how to solve.

My understanding is that Csla will automatically use the MobileFormatter on Xamarin (including UWP), and we're using HttpProxy. On the server side it seems that serializing the response to one of our command objects is failing.

This is the exception:

Type 'Library.ReadOnlyObject' with data contract name 'ReadOnlyObject:http://schemas.datacontract.org/2004/07/Library' is not expected. Consider using a DataContractResolver if you are using DataContractSerializer or 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 the serializer.

   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeAndVerifyType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, Boolean verifyKnownType, RuntimeTypeHandle declaredTypeHandle, Type declaredType)
   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 WriteArrayOfanyTypeToXml(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, DataContractResolver dataContractResolver)
   at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
   at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
   at System.Runtime.Serialization.XmlObjectSerializer.WriteObject(XmlDictionaryWriter writer, Object graph)
   at System.Runtime.Serialization.XmlObjectSerializer.WriteObject(Stream stream, Object graph)
   at Csla.Core.MobileList`1.OnGetChildren(SerializationInfo info, MobileFormatter formatter)
   at Csla.Serialization.Mobile.MobileFormatter.SerializeObject(Object obj)
   at Csla.Core.FieldManager.FieldDataManager.Csla.Serialization.Mobile.IMobileObject.GetChildren(SerializationInfo info, MobileFormatter formatter)
   at Csla.Serialization.Mobile.MobileFormatter.SerializeObject(Object obj)
   at Csla.ReadOnlyBase`1.OnGetChildren(SerializationInfo info, MobileFormatter formatter)
   at Csla.Serialization.Mobile.MobileFormatter.SerializeObject(Object obj)
   at Csla.Core.MobileList`1.OnGetChildren(SerializationInfo info, MobileFormatter formatter)
   at Csla.Serialization.Mobile.MobileFormatter.SerializeObject(Object obj)
   at Csla.Core.FieldManager.FieldDataManager.Csla.Serialization.Mobile.IMobileObject.GetChildren(SerializationInfo info, MobileFormatter formatter)
   at Csla.Serialization.Mobile.MobileFormatter.SerializeObject(Object obj)
   at Csla.ReadOnlyBase`1.OnGetChildren(SerializationInfo info, MobileFormatter formatter)
   at Csla.Serialization.Mobile.MobileFormatter.SerializeObject(Object obj)
   at Csla.Core.FieldManager.FieldDataManager.Csla.Serialization.Mobile.IMobileObject.GetChildren(SerializationInfo info, MobileFormatter formatter)
   at Csla.Serialization.Mobile.MobileFormatter.SerializeObject(Object obj)
   at Csla.ReadOnlyBase`1.OnGetChildren(SerializationInfo info, MobileFormatter formatter)
   at Csla.Serialization.Mobile.MobileFormatter.SerializeObject(Object obj)
   at Csla.Core.MobileObservableCollection`1.OnGetChildren(SerializationInfo info, MobileFormatter formatter)
   at Csla.Serialization.Mobile.MobileFormatter.SerializeObject(Object obj)
   at Csla.Core.FieldManager.FieldDataManager.Csla.Serialization.Mobile.IMobileObject.GetChildren(SerializationInfo info, MobileFormatter formatter)
   at Csla.Serialization.Mobile.MobileFormatter.SerializeObject(Object obj)
   at Csla.ReadOnlyBase`1.OnGetChildren(SerializationInfo info, MobileFormatter formatter)
   at Csla.Serialization.Mobile.MobileFormatter.SerializeObject(Object obj)
   at Csla.Core.FieldManager.FieldDataManager.Csla.Serialization.Mobile.IMobileObject.GetChildren(SerializationInfo info, MobileFormatter formatter)
   at Csla.Serialization.Mobile.MobileFormatter.SerializeObject(Object obj)
   at Csla.ReadOnlyBase`1.OnGetChildren(SerializationInfo info, MobileFormatter formatter)
   at Csla.Serialization.Mobile.MobileFormatter.SerializeObject(Object obj)
   at Csla.Core.FieldManager.FieldDataManager.Csla.Serialization.Mobile.IMobileObject.GetChildren(SerializationInfo info, MobileFormatter formatter)
   at Csla.Serialization.Mobile.MobileFormatter.SerializeObject(Object obj)
   at Csla.Core.ManagedObjectBase.OnGetChildren(SerializationInfo info, MobileFormatter formatter)
   at Csla.Serialization.Mobile.MobileFormatter.SerializeObject(Object obj)
   at Csla.Serialization.Mobile.MobileFormatter.SerializeAsDTO(Object graph)
   at Csla.Serialization.Mobile.MobileFormatter.Serialize(Object obj)
   at Csla.Server.Hosts.HttpPortal.<Update>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Csla.Server.Hosts.HttpPortalController.<InvokePortal>d__5.MoveNext()

The exception seems to suggest DataContractSerializer is involved, but I thought that the HttpPortalController (which I'm using as the remote host) should always use the MobileFormatter.

Some calls do succeed though, so I don't think its a general problem, but something with the object graph which the command object is returning.

First, any idea why I'm getting this error? Second, why is the DCS involved at all? We do have BinaryFormatter set as the HttpPortalController is hosted in an Asp.Net site which also currently hosts a .Net remoting portal as well.

rockfordlhotka commented 7 years ago

The MobileFormatter does use DCS to create the XML that is sent over the network. MobileFormatter pulls all the data out of the business object graph into a very simple array of values, and then DCS is used to write that array of values into a byte stream.

What you are seeing is the result of some value in your graph being of a type that DCS can't serialize into XML - so something that isn't a primitive type is a property of your graph.

ajj7060 commented 7 years ago

Hi @rockfordlhotka. That's what I don't understand; the message is complaining about a type (Library.ReadOnlyObject) which inherits ReadOnlyBase. It contains primitives (including enum valued properties) and MobileList; each property which has MobileList has a T which is also a ReadOnlyBase class.

jonnybee commented 7 years ago

I believe that Enums could be the culprit here.

Read more here about the DCS and Enum: https://msdn.microsoft.com/en-us/library/aa347875(v=vs.110).aspx

ajj7060 commented 7 years ago

@jonnybee But I thought the mobileformatter handled the enums itself. https://github.com/MarimerLLC/cslaforum/issues/284#issuecomment-266848073

Looking at the code, it seems like mobileformatter converts everything to a SerializationInfo (and from Rocky's comment, I would think enums become ints) and that's what goes to the DCS.

rockfordlhotka commented 7 years ago

I could be misremembering @ajj7060 - I thought we did some special handling for enum, but maybe not?

ajj7060 commented 7 years ago

@rockfordlhotka I believe it does; I have a sample app where I've been trying to replicate the issue (unsuccessfully so far) and I added a property which is an Enum, and it seems to work fine.

ajj7060 commented 7 years ago

Ok, I think I figured this out. Library.ReadOnlyObject implements an interface Library.IReadOnlyObject. We have a ROLB, Library.ReadOnlyObjectList<ReadOnlyObjectList, IReadOnlyObject> We also have another class, Library.ReadOnlyObject2 which has a MobileList<Library.IReadOnlyObject>.

It compile and ran, giving the above exception, which was misleading. The issue seemed to be that Library.IReadOnlyObject didn't for implementers to also implement Csla.Serialization.Mobile.IMobileObject. Once I made our interface require the IMobileObject interface, everything magically started working.

I think the signals got crossed because at runtime Csla is looking at the type information of the objects actually contained in the list (which is always the Library.ReadOnlyObject) but the list itself didn't technically require objects to be of IMobileObject.

ajj7060 commented 7 years ago

@rockfordlhotka I actually have a working repo project now, if interested. The issue is specifically the MobileList<T> if you use an interface for T which does not force T to be Csla.Serialization.Mobile.IMobileObject.

ajj7060 commented 7 years ago

Here's the repo: https://github.com/ajj7060/MobileListInterface

I'm not sure if this is considered an edge case or not; the reason for a list of interfaces vs concreate classes is for unit testing. The actual implementation is very difficult to work with when trying to configure it for a test otherwise.

It would be nice to have another more clear exception thrown in this case. I'd be happy to take a crack at implementing the logic if its agreed that the framework should catch this. My biased opinion is that it should as it took me quiet a while to track down the actual issue :-) I was thinking either during the serialization process or perhaps in RegisterProperty, assuming MobileList and managed properties are always expected for something that will cross the data portal.