Читатель XAML, эквивалентный IDeserializationCallback.OnDeserialization - PullRequest
0 голосов
/ 11 марта 2019

Я добавил новые поля в постоянный класс и должен убедиться, что для них установлены разумные значения по умолчанию при загрузке более старых версий сериализованного файла XAML с диска. Ранее с помощью BinaryFormatter я использовал метод OnDeserialization, чтобы определить, какие значения по умолчанию следует установить, если новые поля были добавлены в постоянный класс (с использованием атрибута OptionalField). E.g.:

    /// <summary>
    /// Runs when the entire object graph has been deserialized.
    /// </summary>
    /// <param name="sender">The object that initiated the callback. The functionality for this parameter is not currently implemented.</param>
    public override void
    OnDeserialization
        (object sender)
    {

Я не могу найти ничего эквивалентного при записи в файл XAML, например:

            using (TextReader reader = File.OpenText(filePath))
            {
                protocol = (Protocol)XamlServices.Load(reader);
            }

Я хотел бы убедиться, что более старые файлы, которые не содержат новых необязательных полей в типе протокола (в приведенном выше примере кода), имеют разумные значения по умолчанию. Я охотился вокруг, но не могу найти ничего очевидного (например, https://ludovic.chabant.com/devblog/2008/06/25/almost-everything-you-need-to-know-about-xaml-serialization-part-2/) Есть ли какой-нибудь эквивалент?

1 Ответ

1 голос
/ 11 марта 2019

XamlServices внутренне использует XamlObjectWriter.Этот тип имеет параметр XamlObjectWriterSettings, который включает в себя различные обратные вызовы.Они не отображаются XamlServices, но его функциональность легко реплицируется.

Я не тестировал это подробно, но, похоже, это работает:

public static object LoadXaml(TextReader textReader)
{
    var settings = new XamlObjectWriterSettings
    {
        AfterBeginInitHandler = (s, e) => Debug.Print($"Before deserializing {e.Instance}"),
        AfterEndInitHandler = (s, e) => Debug.Print($"After deserializing {e.Instance}")
    };

    using (var xmlReader = XmlReader.Create(textReader))
    using (var xamlReader = new XamlXmlReader(xmlReader))
    using (var xamlWriter = new XamlObjectWriter(xamlReader.SchemaContext, settings))
    {
        XamlServices.Transform(xamlReader, xamlWriter);
        return xamlWriter.Result;
    }
}

e.Instance содержитобъект десериализован.Не уверен, какой обратный вызов лучше всего подходит для ваших целей.Они более эквивалентны атрибутам [OnDeserializing] / [OnDeserialized], потому что они вызываются при десериализации отдельного объекта, а не после того, как весь граф завершен, как IDeserializationCallback.OnDeserialization.

Вот ещеполная реализация класса, обеспечивающего события во время сериализации.XamlObjectReader не поддерживает обратные вызовы, как XamlObjectWriter, поэтому используется обходной путь.Он вызывает событие только до, но не после сериализации объекта по причинам, объясненным в комментариях.

public class CallbackXamlService
{
    // Default settings that XamlService uses
    public XmlWriterSettings XmlWriterSettings { get; set; }
        = new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true };

    public event EventHandler<XamlObjectEventArgs> BeforeDeserializing;
    public event EventHandler<XamlObjectEventArgs> AfterDeserializing;
    public event EventHandler<XamlObjectEventArgs> BeforeSerializing;
    // AfterSerializing event doesn't seem to be easily possible, see below

    public object LoadXaml(TextReader textReader)
    {
        var settings = new XamlObjectWriterSettings
        {
            BeforePropertiesHandler = (s, e) => BeforeDeserializing?.Invoke(this, e),
            AfterPropertiesHandler = (s, e) => AfterDeserializing?.Invoke(this, e)
        };

        using (var xmlReader = XmlReader.Create(textReader))
        using (var xamlReader = new XamlXmlReader(xmlReader))
        using (var xamlWriter = new XamlObjectWriter(xamlReader.SchemaContext, settings))
        {
            XamlServices.Transform(xamlReader, xamlWriter);
            return xamlWriter.Result;
        }
    }

    public string SaveXaml(object instance)
    {
        var stringBuilder = new StringBuilder();
        using (var textWriter = new StringWriter(stringBuilder))
            SaveXaml(textWriter, instance);
        return stringBuilder.ToString();
    }

    public void SaveXaml(TextWriter textWriter, object instance)
    {
        Action<object> beforeSerializing = (obj) => BeforeSerializing?.Invoke(this, new XamlObjectEventArgs(obj));

        // There are no equivalent callbacks on XamlObjectReaderSettings
        // Using a derived XamlObjectReader to track processed objects instead
        using (var xmlWriter = XmlWriter.Create(textWriter, XmlWriterSettings))
        using (var xamlXmlWriter = new XamlXmlWriter(xmlWriter, new XamlSchemaContext()))
        using (var xamlObjectReader = new CallbackXamlObjectReader(instance, xamlXmlWriter.SchemaContext, null, beforeSerializing))
        {
            XamlServices.Transform(xamlObjectReader, xamlXmlWriter);
            xmlWriter.Flush();
        }
    }

    private class CallbackXamlObjectReader : XamlObjectReader
    {
        public Action<object> BeforeSerializing { get; }

        //private Stack<object> instanceStack = new Stack<object>();

        public CallbackXamlObjectReader(object instance, XamlSchemaContext schemaContext, XamlObjectReaderSettings settings, Action<object> beforeSerializing)
            : base(instance, schemaContext, settings)
        {
            BeforeSerializing = beforeSerializing;
        }

        public override bool Read()
        {
            if (base.Read())
            {
                if (NodeType == XamlNodeType.StartObject)
                {
                    //instanceStack.Push(Instance);
                    BeforeSerializing?.Invoke(Instance);
                }
                // XamlObjectReader.Instance is not set on EndObject nodes
                // EndObject nodes do not line up with StartObject nodes when types like arrays and dictionaries
                // are involved, so using a stack to track the current instance doesn't always work.
                // Don't know if there is a reliable way to fix this without possibly fragile special-casing,
                // the XamlObjectReader internals are horrendously complex.
                //else if (NodeType == XamlNodeType.EndObject)
                //{
                //    object instance = instanceStack.Pop();
                //    AfterSerializing(instance);
                //}
                return true;
            }
            return false;
        }
    }
}
...