locked
DesignerSerializationVisibility and read-only properties RRS feed

  • Question

  • Hello all,

     

    According to the MSDN documentation, and if I'm understanding this right, the DesignerSerializationVisibility attribute, used with the DesignerSerializationVisibility.Content setting, allows you to specify that the value of a property is not serialized per se, and instead, only its contents are serialized.

    This is used for example for collection properties, which are typically read-only, so that the serializer doesn't serialize the list itself, serializing each item instead. During deserialization, items are deserialized and added to the list via the ICollection interface.

    But the documentation says that it should work also for any other read-only property: the property's contents (i.e. the property's properties) will be serialized and deserialized. An example of this behaviour is given.

    However, when using the XamlWriter/XamlReader classes, this doesn't seem to work....

     

    Here's my Foo class:

     

    public class Foo

    {

      private string mName;

      public string Name

      {

        get { return mName; }

        set { mName = value; }

      }

      private int mValue;

      public int Value

      {

        get { return mValue; }

        set { mValue = value; }

      }

    }

     

    Here's my FooContainer class:

     

    public class FooContainer

    {

      private Foo mMyFoo;

      [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]

      public Foo MyFoo

      {

        get { return mMyFoo; }

      }

      public FooContainer()

      {

        mMyFoo = new Foo();

      }

    }

     

    Here's my program code:

     

    static void Main(string[] args)

    {

      FooContainer f1 = new FooContainer();

      f1.MyFoo.Name = "my foo";

      f1.MyFoo.Value = 42;

      XmlWriterSettings settings = new XmlWriterSettings();

      settings.Indent = true;

      settings.NewLineOnAttributes = true;

      using (XmlWriter writer = XmlWriter.Create("foo.xml", settings))

      {

        XamlWriter.Save(f1, writer);

      }

      using (XmlReader reader = XmlReader.Create("foo.xml"))

      {

        FooContainer f2 = (FooContainer)XamlReader.Load(reader);

        Debug.Assert(f2.MyFoo.Name == f1.MyFoo.Name);

        Debug.Assert(f2.MyFoo.Value == f1.MyFoo.Value);

      }

    }

     

    If I don't have the DesignerSerializationVisibility attribute on FooContainer.MyFoo, here's the XAML I get:

     

    <?xml version="1.0" encoding="utf-8"?>

    <FooContainer xmlns="clr-namespace:XamlTests;assembly=XamlTests" />

     

    (obviously, the Asserts fail)

     

    If I do have the attribute, here's the XAML I get:

     

    <?xml version="1.0" encoding="utf-8"?>

    <FooContainer xmlns="clr-namespace:XamlTests;assembly=XamlTests">

      <FooContainer.MyFoo>

        <Foo

          Name="my foo"

          Value="42" />

      </FooContainer.MyFoo>

    </FooContainer>

     

    ...so it seems the attribute works for serialization.... but I get a XamlParseException in the XamlReader.Load(), saying that the XamlReader can't deserialize FooContainer.MyFoo because it's read-only.

     

    What am I missing? Did I forget to initialize or setup something? Otherwise, what's the point of the DesignerSerializationVisibility attribute in this case if you can serialize something to Xaml, but not deserialize it? (which, as far as I can tell, means that piece of Xaml code is useless, except for staring at in notepad, and posting on the MSDN forums).

     

    Any help would be appreciated!

    Monday, March 31, 2008 3:56 PM

All replies

  • You should try marking with DesignerSerializationVisibility attribute your private field that actually contains the value instead of the public getter.

    Monday, March 31, 2008 4:43 PM
  • Nope.... If I set it only on the private field, I get the first version of the Xaml code (Foo isn't serialized, and my Asserts fail).

    If I set it on both the private field and the public property, it does the same as when I set it only on the public property (Foo is serialized, but deserialization fails).

    So I guess it shows that the XamlWriter is really looking for that attribute on public properties, and nothing else.

    Thanks for the suggestion though.
    Monday, March 31, 2008 5:18 PM
  • By the way have you read Mike Hillberg's articles towards XamlWriter?

     

    Here's the links

     

    Being written by XamlWriter

    http://blogs.msdn.com/mikehillberg/archive/2006/09/16/XamlWriter.aspx

     

     

    Monday, March 31, 2008 5:32 PM
  • Yep, I read it. According to Mike, only collection-type read-only properties are serialized:

     

    There's usually no point in writing to Xaml the value of a read-only property, because it won't be possible to load it back from the Xaml, since Xaml can't set read-only properties.  There is one case, however, where Xaml can set to a read-only property, and that's with collection type properties.  In that case, it’s not setting the property itself, but adding to the collection.

     

    ...but this is inconsistent with what the MSDN documentation says, and the example that is given for it... maybe other designer serializers support this attribute completely, but the Xaml one doesn't? That sounds wrong.

     

    Then, there's the problem that I can serialize a read-only property, contrary to what Mike Hillberg says (unless by "no point", he really means "you can do it, but you shouldn't").... so why is the framework allowing that, if it's not going to allow deserializing it back? Being able to create data you can't use sounds like a huge scenario issue to me, which is why I'm thinking I'm doing something wrong - it could be that the WPF team didn't catch that bug before RTM, but I doubt it.

     

    IMHO, there is point in writing the Xaml value of a read-only property: just as, when you deserialize a collection, you get that collection (expecting the object to return a non-null value) and add each item, you could also deserialize a read-only property by getting that property's value from the object (expecting also a non-null value), and then set each of that value's properties.

    Having a read-only property is a valid OO design decision, and since it looks to me that it's technically possible to implement serialization that works with it, I was hoping that the DesignerSerializationVisibility attribute would give me that feature...

    Monday, March 31, 2008 6:05 PM
  • So I've been digging a bit, and I found that some types in the WPF framework do use read-only properties.

    For example, System.Windows.Documents.PageContent has a read-only property called "Child", which is exposed to Xaml serialization using DesignerSerializationVisibility.Content, and is also set as the content property (via the ContentProperty attribute), meaning no wrapping tag will be created in the generated Xaml.

     

    So I set my FooContainer class to have its "MyFoo" property be its content property, hoping this would solve my problem.... but I get a different XamlParseException:

     

    "Cannot set content property 'MyFoo' on element 'FooContainer'. 'MyFoo' has incorrect access level or its assembly does not allow access. Line '2' Position '66'."

     

    I don't really know why MyFoo could have an incorrect access level, considering everything is public in this code sample, and I don't know what the second part means, about the assembly not allowing access.

     

    Interestingly enough, it looks like what's really happening for the PageContent class is that the XamlReader understands the "Child" property as a customized list property behind the scenes. There's an IAddChild interface that PageContent implements, and which, among other things, is supposed to allow you to serialize and deserialize collections that don't implement the ICollection (or is it IList?) interface. That sounds a bit weird but it makes sense when you expose a collection only as an IEnumerable. To support this, you can implement the IAddChild interface, through which the XamlReader will feed you the collection's items.

     

    Now, this is funny because I did the test of exposing a read-only IEnumerable property, and implementing IAddChild, and I get the following exception when deserializing my object:

     

    "'ChildFoos' is a read-only IEnumerable property, so 'XamlTests.FooContainer' must implement IAddChild. Line '3' Position '4'."

     

    It seems that the IAddChild interface was deprecated during the betas, but is still around (it was replaced by the ContentProperty attribute, but this doesn't really cover all scenarios for the original IAddChild interface). The way they deprecated it is by creating an IAddChildInternal interface which is, well, internal, and the XamlReader is actually checking for that instead of IAddChild. Because the error message was not changed, you get that confusing feedback where the framework tells you that you need to implement an interface you're already implementing... I hope this will be fixed in upcoming patches.

     

    Anyway.... it looks like the guys in the WPF framework can serialize/deserialize read-only properties because they're using a half-deprecated/half-internal feature of the SDK... but what about the rest of us? Any information on this from somebody on the WPF team would be very much appreciated.
    Monday, March 31, 2008 11:20 PM
  • Been awhile, but your IAddChildInternal observation is valuable to me, as I was trying to figure out why the Baml reader didn't recognize my custom collection as implementing the interface.

    Long story short, there are four ways the writer will add content to your content object:  If it implements IDictionary, IList, IAddChild(Internal?!?), or is an ArrayExtension.  So if you're brewing up a custom collection and you want to serialize it to xaml and back, you'd better either implement IDictionary or IList. 

    The good thing is that, if your custom collection is strongly typed, you can implement these explicitly and throw NSE's where appropriate, since all you really care about is the Add method.
    Tuesday, July 29, 2008 4:44 PM