XML deserialisation

Topics: Issues / bugs
Sep 11, 2012 at 5:52 PM

Maybe I'm missing something obvious but I can't get the XML deserialiser to read back my saved model. The model is ChartData, a SavableDataObjectBase.

When the model is saved ChartData.Save creates the file like this:

<?xml version="1.0" encoding="utf-8"?>
<ChartData xmlns:ctl="http://catel.codeplex.com">
  <MaxSampleCount>20</MaxSampleCount>
  <Samples xmlns:d1p1="http://schemas.datacontract.org/2004/07/ControlChart.Models" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <d1p1:ChartData.Sample>
      <Id>165706</Id>
      <Timestamp>2012-03-20T12:11:38</Timestamp>
      <Value>999.9306640625</Value>
    </d1p1:ChartData.Sample>
    <d1p1:ChartData.Sample>
      <Id>165707</Id>
      <Timestamp>2012-03-20T12:11:40</Timestamp>
      <Value>999.67559814453125</Value>
    </d1p1:ChartData.Sample>
  </Samples>
  <SpecificationLimit xmlns:d1p1="http://schemas.datacontract.org/2004/07/ControlChart.Models" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <d1p1:Lower>999</d1p1:Lower>
    <d1p1:Upper>1001</d1p1:Upper>
  </SpecificationLimit>
  <VerticalScale xmlns:d1p1="http://schemas.datacontract.org/2004/07/ControlChart.Models" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <d1p1:Lower>999</d1p1:Lower>
    <d1p1:Upper>1001</d1p1:Upper>
  </VerticalScale>
  <UpdateCount>26</UpdateCount>
  <VariableName>Length</VariableName>
  <VariableUnit>mm</VariableUnit>
</ChartData>
When I try to Load a file like that the deserialiser stops at the first EndElement (</Samples> in this case) and doesn't read any more from the file. If I change the order of elements in the file it will stop at </SpecificationLimit> or </VerticalScale>.

Coordinator
Sep 12, 2012 at 7:37 PM

Can you provide the model classes as well?

Sep 13, 2012 at 11:41 AM

Here's a stripped-down version of the ChartData model class (with most of the code removed).

namespace ControlChart.Models
	{
	/// <summary>
	/// ChartData Data object class which fully supports serialization, property changed notifications,
	/// backwards compatibility and error checking.
	/// </summary>
#if !SILVERLIGHT
	[Serializable]
#endif
	public class ChartData : SavableDataObjectBase<ChartData>
		{
		[Serializable]
		public class Limits
			{
			public double Upper { get; set; }
			public double Lower { get; set; }
			}

#if !SILVERLIGHT
		[Serializable]
#endif
		public class Sample : DataObjectBase<Sample>
			{
			public Sample()
				: this(0, DateTime.Now, 0.0)
				{
				}

			public Sample(int id, DateTime timeStamp, double value)
				{
				Id = id;
				Timestamp = timeStamp;
				Value = value;
				}

#if !SILVERLIGHT
		/// <summary>
		/// Initializes a new object based on <see cref="SerializationInfo"/>.
		/// </summary>
		/// <param name="info"><see cref="SerializationInfo"/> that contains the information.</param>
		/// <param name="context"><see cref="StreamingContext"/>.</param>
			protected Sample(SerializationInfo info, StreamingContext context)
				: base(info, context)
			{
			}
#endif

		/// <summary>
			/// Gets or sets the property value.
			/// </summary>
			public int Id
				{
				get { return GetValue<int>(IdProperty); }
				set { SetValue(IdProperty, value); }
				}

			/// <summary>
			/// Register the Id property so it is known in the class.
			/// </summary>
			public static readonly PropertyData IdProperty = RegisterProperty("Id", typeof(int));

			/// <summary>
			/// Gets or sets the property value.
			/// </summary>
			public DateTime Timestamp
				{
				get { return GetValue<DateTime>(TimestampProperty); }
				set { SetValue(TimestampProperty, value); }
				}

			/// <summary>
			/// Register the Timestamp property so it is known in the class.
			/// </summary>
			public static readonly PropertyData TimestampProperty = RegisterProperty("Timestamp", typeof(DateTime));

			/// <summary>
			/// Gets or sets the property value.
			/// </summary>
			public double Value
				{
				get { return GetValue<double>(ValueProperty); }
				set { SetValue(ValueProperty, value); }
				}

			/// <summary>
			/// Register the Value property so it is known in the class.
			/// </summary>
			public static readonly PropertyData ValueProperty = RegisterProperty("Value", typeof(double));
			}

		#region Variables
		public Limits ControlLimit { get; private set; }
		public double Mean { get; private set; }
		public double StdDev { get; private set; }
		public int SampleCount { get { return (Samples != null) ? Samples.Count : 0; } }

		private bool initialised = false;
		private const int MaxSampleCountDefault = 20;
		private PlcInterface plcInterface;

		#endregion

		#region Constructor & destructor

		// this one gets called directly for xml deserialisation
		private ChartData()
		  {
		  ControlLimit = new Limits() { Lower = double.NaN, Upper = double.NaN };
		  }

		/// <summary>
		/// Initializes a new object from scratch.
		/// </summary>
		/// <remarks>The parameter is mainly to distinguish this constructor from the private one to be called by deserialisation</remarks>
		public ChartData(bool initialise)
			: this()
			{
			if (initialise)
				{
				UpdateSettings();
				Reset();
				if (plcInterface != null)
					plcInterface.PropertyChanged -= plcInterface_PropertyChanged;
				plcInterface = new PlcInterface(this);
				plcInterface.PropertyChanged += plcInterface_PropertyChanged;
				plcInterface.Initialise();
				}
			mainWindow = Application.Current.MainWindow; // cache the main window for cross-thread invoking
			Calculate();
			}

		void plcInterface_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
			{
			if (e.PropertyName == "ErrorString")
				ErrorString = plcInterface.ErrorString;
			}

#if !SILVERLIGHT
		/// <summary>
		/// Initializes a new object based on <see cref="SerializationInfo"/>.
		/// </summary>
		/// <param name="info"><see cref="SerializationInfo"/> that contains the information.</param>
		/// <param name="context"><see cref="StreamingContext"/>.</param>
		protected ChartData(SerializationInfo info, StreamingContext context)
			: base(info, context)
			{
			ControlLimit = new Limits() { Lower = double.NaN, Upper = double.NaN };
			mainWindow = Application.Current.MainWindow; // cache the main window for cross-thread invoking
			// Don't do any other initialisation. This is only called by Catel internals creating a DataObject backup and we
			// don't want it to have a comms thread or anything.
			}
#endif
		#endregion

		#region Properties
		// TODO: Define your custom properties here using the dataprop code snippet
		/// <summary>
		/// Gets or sets the property value.
		/// </summary>
		public int MaxSampleCount
			{
			get { return GetValue<int>(MaxSampleCountProperty); }
			set { SetValue(MaxSampleCountProperty, value); }
			}

		/// <summary>
		/// Register the MaxSampleCount property so it is known in the class.
		/// </summary>
		public static readonly PropertyData MaxSampleCountProperty = RegisterProperty("MaxSampleCount", typeof(int), MaxSampleCountDefault, new EventHandler<AdvancedPropertyChangedEventArgs>(OnPropertyChanged));

		/// <summary>
		/// Gets or sets the property value.
		/// </summary>
		public ObservableCollectionEx<Sample> Samples
			{
			get { return GetValue<ObservableCollectionEx<Sample>>(SamplesProperty); }
			set { SetValue(SamplesProperty, value); }
			}

		/// <summary>
		/// Register the Samples property so it is known in the class.
		/// </summary>
		public static readonly PropertyData SamplesProperty = RegisterProperty("Samples", typeof(ObservableCollectionEx<Sample>), null);

		/// <summary>
		/// Gets or sets the property value.
		/// </summary>
		public Limits SpecificationLimit
			{
			get { return GetValue<Limits>(SpecificationLimitProperty); }
			set { SetValue(SpecificationLimitProperty, value); }
			}

		/// <summary>
		/// Register the SpecificationLimit property so it is known in the class.
		/// </summary>
		public static readonly PropertyData SpecificationLimitProperty = RegisterProperty("SpecificationLimit", typeof(Limits), new Limits { Lower = double.NaN, Upper = double.NaN }, new EventHandler<AdvancedPropertyChangedEventArgs>(OnPropertyChanged));

		/// <summary>
		/// Gets or sets the property value.
		/// </summary>
		public Limits VerticalScale
			{
			get { return GetValue<Limits>(VerticalScaleProperty); }
			set { SetValue(VerticalScaleProperty, value); }
			}

		/// <summary>
		/// Register the VerticalScale property so it is known in the class.
		/// </summary>
		public static readonly PropertyData VerticalScaleProperty = RegisterProperty("VerticalScale", typeof(Limits), new Limits { Lower = 0, Upper = 0 }, new EventHandler<AdvancedPropertyChangedEventArgs>(OnPropertyChanged));

		private static void OnPropertyChanged(object sender, AdvancedPropertyChangedEventArgs ea)
			{
			ChartData cd = sender as ChartData;
			if (cd != null)
				cd.Calculate();
			}

		/// <summary>
		/// Gets or sets the property value.
		/// </summary>
		public int UpdateCount
			{
			get { return GetValue<int>(UpdateCountProperty); }
			set { SetValue(UpdateCountProperty, value); }
			}

		/// <summary>
		/// Register the UpdateCount property so it is known in the class.
		/// </summary>
		public static readonly PropertyData UpdateCountProperty = RegisterProperty("UpdateCount", typeof(int), 0);

		/// <summary>
		/// Gets or sets the property value.
		/// </summary>
		public string VariableName
			{
			get { return GetValue<string>(VariableNameProperty); }
			set { SetValue(VariableNameProperty, value); }
			}

		/// <summary>
		/// Register the VariableName property so it is known in the class.
		/// </summary>
		public static readonly PropertyData VariableNameProperty = RegisterProperty("VariableName", typeof(string));

		/// <summary>
		/// Gets or sets the property value.
		/// </summary>
		public string VariableUnit
			{
			get { return GetValue<string>(VariableUnitProperty); }
			set { SetValue(VariableUnitProperty, value); }
			}

		/// <summary>
		/// Register the VariableUnit property so it is known in the class.
		/// </summary>
		public static readonly PropertyData VariableUnitProperty = RegisterProperty("VariableUnit", typeof(string));

		/// <summary>
		/// Gets or sets the property value.
		/// </summary>
		[XmlIgnore]
		public string ErrorString
			{
			get { return GetValue<string>(ErrorStringProperty); }
			set { SetValue(ErrorStringProperty, value); }
			}

		/// <summary>
		/// Register the ErrorString property so it is known in the class.
		/// </summary>
		public static readonly PropertyData ErrorStringProperty = RegisterProperty("ErrorString", typeof(string));
		#endregion
		}
	}

Coordinator
Sep 17, 2012 at 8:53 PM

I have tested it, but I cannot find any errors. You can download my test project here.