4

I have data that is best described as "onion-like" in that each outer layer builds on the one below it. Below you will see a vastly simplified version (mine is several layers deeper but exhibits the same behavior at each level).

[CollectionDataContract]
public abstract class AbstractTestGroup : ObservableCollection<AbstractTest>
{
    [DataMember]
    public abstract string Name { get; set; }
}

[CollectionDataContract]
[KnownType(typeof(Test))]
public class TestGroup : AbstractTestGroup
{
    public override string Name
    {
        get { return "TestGroupName"; }
        set { }
    }

    [DataMember]
    public string Why { get { return "Why"; } set { } }
}

[DataContract]
public abstract class AbstractTest
{
    [DataMember]
    public abstract string SayHello { get; set; }
}


[DataContract]
public class Test : AbstractTest
{
    //Concrete class - members in this class get serialized
    [DataMember]
    public string Month { get { return "June"; } set { } }

    public override string SayHello { get { return "HELLO"; } set { } }
}

I create an instance of TestGroup and add Test objects to it using the .Add that comes with the ObservableCollection.

When I serialize and de-serialize this structure I get the following

<TestGroup xmlns="http://schemas.datacontract.org/2004/07/WpfApplication2" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <AbstractTest i:type="Test">
        <SayHello>HELLO</SayHello>
        <Month>June</Month>
    </AbstractTest>
</TestGroup>

The output has left off the DataMembers in TestGroup. As I get deeper in my onion, no DataMembers that are higher are included (even from the abstract classes). I have tried adding [KnownType(typeof(TestGroup))] to both TestGroup and AbstractTestGroup without success.

The question: Why am I not able to serialize the DataMember Why in the TestGroup class?

Follow up question: Is there an alternative way to serialize and de-serialize a structure of this shape? I am planning on using the output locally to "load" the configuration the user specifies. I would prefer to not have to specify my own Serialization scheme if I can avoid it.


For those interested here is how I am generating the class, serializing, and de-serializing it.

TestGroup tg = new TestGroup();
tg.Add(new Test());

DataContractSerializer ser = new DataContractSerializer(typeof(TestGroup));
MemoryStream memoryStream = new MemoryStream();
ser.WriteObject(memoryStream, tg);

memoryStream.Seek(0, SeekOrigin.Begin);
string str;
using (StreamReader sr = new StreamReader(memoryStream))
    str = sr.ReadToEnd();

Edit: For what it's worth I tried changing to using Serializable instead and have the same issue.

Jon Peterson
  • 723
  • 7
  • 21
  • Also see [c-sharp-xml-serializing-listt-descendant-with-xml-attribute](http://stackoverflow.com/questions/3416426/c-sharp-xml-serializing-listt-descendant-with-xml-attribute) – nawfal Jul 16 '14 at 20:50

2 Answers2

2

The reason why the property Why is not serialized is because TestGroup is a collection. And DataContract treats collections specially. The end result is that only the data in the collection is stored and none of the properties are stored.

Lists are stored in a way that any other list could read them in. The only differentiation is between collections and dictionaries. A good reference is http://msdn.microsoft.com/en-us/library/aa347850%28v=vs.110%29.aspx

Ed_S
  • 88
  • 1
  • 6
  • 1
    Could you elaborate on this answer a bit? The reference you pointed to is a good one, but a bit long winded. Is there a way to do what I need done, based on the example given? – Jon Peterson Jun 04 '14 at 18:02
  • This answer correct, but not complete. Applying `[DataContract]` to a custom collection type will cause its properties to be serialized instead of its items. From [Advanced Collection Rules](https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/collection-types-in-data-contracts#advanced-collection-rules): *If the DataContractAttribute attribute is applied to a collection type, the type is treated as a regular data contract type, not as a collection.* – dbc Nov 13 '19 at 23:01
-1

UPDATE: I've seen some things online that may help you. In particular, change the abstract class attribute declarations to the following:

[DataContract]
[KnownTypes(typeof(Test))]
public abstract class AbstractTest { /* ... */ }

You could have a look at the documentation at MSDN on the KnownTypesAttribute. Apparently, there's also a constructor overload that takes a string that resolves to a method name that would be found via reflection and would be called by the DataContractSerializer to determine the known types for a base class (if you had multiple known types and/or possibly needed to dynamically return known types that may not be known at compile time). There's also web.config XML configurations for setting up known types.

UPDATE: I noticed that the KnownTypesAttribute attribute seems to be misused in the code examples in the OP. So, I wanted to elaborate the above with the full code that should work.

[CollectionDataContract]
[KnownTypes(typeof(TestGroup))] // Need to tell DCS that this class's metadata will be included with members from this abstract base class.
public abstract class AbstractTestGroup : ObservableCollection<AbstractTest>
{
    [DataMember]
    public abstract string Name { get; set; }
}

[CollectionDataContract]
//[KnownTypes(typeof(Test))] -- You don't need this here....
public class TestGroup : AbstractTestGroup
{
    [DataMember] // Even though this is a derived class, you still need to tell DCS to serialize this overridden property when serializing this type
    public override string Name
    {
        get { return "TestGroupName"; }
        set { }
    }

    [DataMember]
    public string Why { get { return "Why"; } set { } }
}

[DataContract]
[KnownTypes(typeof(Test))] // Again, you need to inform DCS
public abstract class AbstractTest
{
    [DataMember]
    public abstract string SayHello { get; set; }
}


[DataContract]
public class Test : AbstractTest
{
    //Concrete class - members in this class get serialized
    [DataMember]
    public string Month { get { return "June"; } set { } }

    [DataMember] // Even though this is a derived class, you still need to tell DCS to serialize this overridden property when serializing this type
    public override string SayHello { get { return "HELLO"; } set { } }
}

See the comments next to the KnownTypesAttribute attributes in the example above.

UPDATE: Added the DataMemberAttribute attribute to the derived class' overridden properties.

UPDATE: OK, there may be an added dimension to this that is causing the behavior you're referencing. Do you have an interface or a class that is decorated with the ServiceContractAttribute attribute, where the service contains a method which returns one of these abstract types above? If so, then you also need to decorate said interface or class method that returns the abstract type with the ServiceKnownTypesAttribute attribute. A quick and dirty example follows:

[ServiceContract]
//[ServiceKnownTypes(typeof(TestGroup))] -- You could also place the attribute here...not sure what the difference is, though.
public interface ITestGroupService
{
    [OperationContract]
    [ServiceKnownTypes(typeof(TestGroup))]
    AbstractTestGroup GetTestGroup();
}

HTH.

fourpastmidnight
  • 4,032
  • 1
  • 35
  • 48
  • Unfortunately that did not work either. With the attributes on the abstract class it generates the same output. Without the attributes I get an error message when I try to serialize: "Type '.Test' cannot inherit from a type that is not marked with DataContractAttribute or SerializableAttribute. Consider marking the base type '.AbstractTest' with DataContractAttribute or SerializableAttribute, or removing them from the derived type." – Jon Peterson Jun 13 '13 at 17:00
  • Thanks for the update. Something else for me to file away when I use DataContractSerializer. Hmm, I'll try to do more research on possible solutions. – fourpastmidnight Jun 13 '13 at 17:39
  • Thanks for continuing to look into this - but adding `KnownTypes` (to anything / everything) hasn't changed the result. I agree that there seems to be something missing in the conversion but I don't understand why the serializer seems to be missing the concrete implementations. – Jon Peterson Jun 13 '13 at 19:13
  • Looking at your code examples above, it looks like you may have misused the `KnownTypesAttribute` attribute. I've expanded my answer to include full code of your code. This should work, from what I've read. I'm really trying! :) I'm really interested to know what the solution will be in the end. – fourpastmidnight Jun 14 '13 at 13:16
  • It seems that last change resulted in a minor difference, I now get the following opening tag with no other changes to content: I think I've provided enough to recreate the entire scenario on your end - maybe whip up a quick project to test it out? Thanks for sticking with it! It is quite vexing! – Jon Peterson Jun 14 '13 at 14:43
  • OK, I updated the code example above. Try adding the `DataMemberAttribute` attribute to the derived class's overridden properties--I think you still need to tell the DataContractSerializer that you want to serialize those methods. What the existing example has done is notify the DCS that abstract base classes may have other properties from its derived types that need to be included in serialization. – fourpastmidnight Jun 14 '13 at 14:57
  • Sorry, but there is still no change in the output. Note that the `SayHello` property is in the output without the `DataMemberAttribute`, but neither `Name` nor `Why` are ending up in the output. – Jon Peterson Jun 14 '13 at 16:02
  • Check out my latest update. **N.B.:** I'm not sure if you'll need to add the `Test` type as a ServiceKnownType as well. I think we're really close to figuring this out. – fourpastmidnight Jun 14 '13 at 16:39
  • I'm not using an interface. The extent of the code is what I originally posted. – Jon Peterson Jun 14 '13 at 19:45
  • There is no 'KnownTypes Attribute: there is a 'KnownType Attribute. – BillW Mar 08 '15 at 13:13