Monday, June 28, 2010

Xaml Serialization and Blend Sample Data

Background
For a project I am currently working on, we are using specialised UI Designers and they are using Blend to import, draw and arrange the graphic assets.  One of the coolest features in Blend 4 is the very nice Sample Data features.  Our process involves delivering partially constructed MVVM controllers so the Designers can use Blend to generate random sample data.  This is all well and good, but it still means the application is totally blank when it runs, hence the generated sample data is called "Design-time Sample Data".  However, the sample data is actually a serialised format of instances of objects:

Blend's Design-time Sample Data
Blend offers 3 ways of creating sample data:
  1. From an XML file you have handcrafted yourself. 
  2. From a code class.
  3. Or manually creating it using a designer within Blend. (Not terribly useful in my opinion as the developers generally set the underlying class structure and names).
I have been using the code class option.
This generates a sample data item in the Data tab.
From this we can now be more specific on what kind of data each field is.  For example string fields can use the following templates to generate sample data:
Blend has created an xaml file to store in serialised form the object instances it has created.
Having sample data in the application makes the designer's job much easier as the see the data in the fields. It also means its easier to create custom templates for collection based items.

Xaml Serialised Data
The Xaml markup is actually just a form of XML serialisation. Here's an example of the file format generated by Blend:

<WpfApplication:MainWindowController 
    xmlns:WpfApplication="clr-namespace:WpfApplication" 
    State="Active">

    <WpfApplication:MainWindowController.CurrentForm>
        <WpfApplication:FormController State="Active">
            <WpfApplication:FormController.CurrentContact>
                <WpfApplication:Contact 
                    Company="A. Datum Corporation" 
                    Email="someone@example.com" 
                    FirstName="Aaberg, Jesper" 
                    MainLine="(111) 555-0100" 
                    Mobile="(111) 555-0100" 
                    State="Active" 
                    Surname="Aaberg, Jesper" />
            </WpfApplication:FormController.CurrentContact>
        </WpfApplication:FormController>
    </WpfApplication:MainWindowController.CurrentForm>

    <WpfApplication:MainWindowController.Dashboard>
        <WpfApplication:DashboardController 
            Capacity="33" 
            Online="True" 
            State="Invalid" 
            Status="Amet dictumst curae donec eleifend" 
            StatusEnum="Online" />
    </WpfApplication:MainWindowController.Dashboard>

</WpfApplication:MainWindowController>
This syntax is significantly better than standard Xml serialisation because the xml type elements are namespaced back to the assembly they are declared in.  Also it follows the same serialisation process as WCF Service contract serialisation in that it serialises all public properties. Standard Xml serialisation only serialises fields and those must be decorated with Xml Attributes! Way too much hassle.  Xaml serialisation is definitely the way of the future.

The Problem
Having design time data is a very tidy solution as it guarantees the design time data doesn't make it into your compiled executable.  The only drawback is that the designers can't run the application to do final visual testing.  One solution to this problem is to use the Xaml Serialisation classes to deserialise the Xaml back into objects and bind to those at runtime.

I feel like I need to come up with a safer more elegant solution so the design time cannot be accidentally left in the executable code, but for now I use a compilation switch #SAMPLEDATA. But hopefully the likelihood of someone releasing the product with the SAMPLEDATA switch intact is low.

public partial class App
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="App"/> class.
        /// </summary>
        public App()
        {
#if (SAMPLEDATA)
            // Sample data used at runtime.  Sometimes useful for visual testing in addition to design time testing in Blend.
            SampleData.SampleDataRepository.MainWindowController =
                SetUpSampleData<MainWindowController>(@".\..\..\SampleData\MainWindowControllerSampleData.xaml");
#endif
        }

#if (SAMPLEDATA)
        private static T SetUpSampleData<T>(string sampleDataFile) where T : class, new()
        {
            // Using Xaml
            if (File.Exists(sampleDataFile))
            {
                var deserialisedObject = XamlServices.Load(sampleDataFile);
                return deserialisedObject as T;
            }
            
            return null;
        }
#endif
    }

You can see I am using a static class with static properties to store the pre-made sample data controllers ready for use.  The sample controllers replace runtime controllers when the SAMPLEDATA switch is present.  .Net 4 includes a new class designed to simplify Xaml serialisation System.Xaml.XamlServices.

public partial class Sample {
        public Sample() {
            InitializeComponent();

#if (SAMPLEDATA)
            this.DataContext = SampleData.SampleDataRepository.MainWindowController;
#endif
        }
    }
Ordinarily when running in "Production" mode the DataContext is set in Xaml (my personal preference), or if it is set in code behind then it would need to be above the #if.

For more complicated scenarios the controllers might need to be interfaced if the are tightly bound to other data sources. This would allow the sample controllers to bypass any data service calls etc.

Serialisation
It doesn't apply to this scenario but it is equally as easy as deserialisation.

string serialised = XamlServices.Save(myObject);
No need to tell it the type or anything else. There are other overloads that take streams and writers and parameters.

For more information:

No comments:

Post a Comment