Building read-only objects in Xaml


We often use Xaml to instantiate and initialize objects.  For example, given “<Foo Bar=’1’/>”, a Xaml loader creates a Foo object, and sets the Bar property to 1.  That works when the Bar property is settable, but what can you do if it isn’t?


 


An example of this scenario in .Net today shows up with strings; the String type is immutable, it doesn’t have any settable properties.  But sometimes you want to build a string with formats, concatenation, etc.  Thus the invention of the StringBuilder class, where the properties have setters (and the methods can mutate an instance).


 


You can follow the same idea in Xaml by creating builder types, using markup extensions.


 


Here’s an example of a type whose properties can’t be set except internally, since it has read-only properties:


 


<Assembly: XmlnsDefinition(“http://blogs.msdn.com/mikehillberg/BuilderPattern”, “PeopleNamespace”)>


 


Namespace PeopleNamespace


 


    Public Class Person


 


        Private _name As String


        Public ReadOnly Property Name() As String


            Get


                Return _name


            End Get


        End Property


 


        Private _age As Integer


        Public ReadOnly Property Age() As Integer


            Get


                Return _age


            End Get


        End Property


 


        Public Shared Function Create(ByVal Name As String, ByVal Age As Integer) As Person


            Dim person As New Person


            person._name = Name


            person._age = Age


            Return person


        End Function


 


    End Class


 


End Namespace


 


(I’m using the XmlnsDefinition assembly attribute here so that I can have a normal-looking Uri in my Xaml, but you can also make references directly to an assembly using the “clr-namespace” syntax, as described here.)


 


 


For demonstration, also define a List(Of Person) named “People”:


 


Namespace PeopleNamespace


 


    Public Class People


        Inherits List(Of Person)


    End Class


 


End Namespace


 


 


… and then, for example, you can’t load this Xaml, because of the read-only properties:


 


<People xmlns=http://blogs.msdn.com/mikehillberg/BuilderPattern>


 


  <Person Name=Mike Age=5 />


  <Person Name=Dwayne Age=7 />


 


</People>


 


(This gives an error of “’Name’ property is read-only and cannot be set from markup”).


 


But we can create a markup extension that follows the builder pattern to create the object. 


 


First, define the markup extension, named PersonExtension.  It looks just like the Person type, except it has setters for the properties, and overrides the ProvideValue function:


 


<Assembly: XmlnsDefinition(“http://blogs.msdn.com/mikehillberg/BuilderPatternBuilder, PeopleBuilderNamespace)>


 


Namespace PeopleBuilderNamespace


 


    Public Class PersonExtension


        Inherits MarkupExtension


 


        Private _name As String


        Public Property Name() As String


            Get


                Return _name


            End Get


            Set(ByVal value As String)


                _name = value


            End Set


        End Property


 


 


        Private _age As Integer


        Public Property Age() As Integer


            Get


                Return _age


            End Get


            Set(ByVal value As Integer)


                _age = value


            End Set


        End Property


 


 


        Public Overrides Function ProvideValue(ByVal serviceProvider As IServiceProvider) As Object


            Return Person.Create(Name, Age)


        End Function


 


    End Class


 


End Namespace


 


And now we can modify the markup slightly so that it successfully loads:


 


<People


  xmlns=http://blogs.msdn.com/mikehillberg/BuilderPattern


  xmlns:builder=http://blogs.msdn.com/mikehillberg/BuilderPatternBuilder >


 


  <builder:Person Name=Mike Age=5 />


  <builder:Person Name=Dwayne Age=7 />


 


</People>


 


To test it out, run this code:


 


Sub Main()


 


    Dim people As People


    people = XamlReader.Load(File.Open(“..\..\Test.xaml”, FileMode.Open))


 


    For Each person As Person In people


        Console.WriteLine(“Name = “ + person.Name + “, Age = “ + person.Age.ToString())


    Next


 


End Sub


 


… and we get this output:


 


Name = Mike, Age = 5


Name = Dwayne, Age = 7


 


 


One last note about markup extensions … Notice that I named the builder class “PersonExtension”, but I referred to it from Xaml as “Person”.  That’s possible because when Xaml doesn’t find “Person”, it automatically appends the “Extension” suffix, and looks for “PersonExtension”.  If Person and PersonExtension had been in the same Clr namespace, that would have caused confusion, so I put them in different namespaces (“PeopleNamespace” and “PeopleBuilderNamespace”).  Alternatively, I could have put them both in the “PeopleNamespace”, and used the specific <PersonExtension> tag (or maybe I would have named it “PersonBuilder”).