2

It's a known issue that XamlWriter.Save is very slow (just Google "xamlwriter slow"). The application I wrote generates XPS documents, and has a limited scope of elements that it uses. Thus, I'd like to replace the XamlWriter with a more efficient one. Is it possible to replace the default XamlWriter that XpsDocumentWriter uses?


Research

Below is how XpsDocumentWriter serializes to Xaml.

This is how I write to an XpsDocument:

var writer = XpsDocument.CreateXpsDocumentWriter(xpsDocument);
var vc = writer.CreateVisualsCollator();
vc.BeginBatchWrite();
foreach (var pfe in pageFrameworkElements) {
    vc.Write(pfe);
}
vc.EndBatchWrite();

So what I want to do is alter the serializer that the VisualsCollator uses when serializing to Xaml.

The VisualsCollator is actually a VisualsToXpsDocument:

// Generated by .NET Reflector from C:\Windows\Microsoft.Net\assembly\GAC_64\System.Printing\v4.0_4.0.0.0__31bf3856ad364e35\System.Printing.dll
namespace System.Windows.Xps
{
    /* Removed Code */

    public class VisualsToXpsDocument : SerializerWriterCollator
    {
        /* Removed Code */

        [SecurityCritical]
        private PackageSerializationManager _manager;

        /* Removed Code */

        [return: MarshalAs(UnmanagedType.U1)]
        [SecuritySafeCritical]
        private unsafe bool WriteVisual([MarshalAs(UnmanagedType.U1)] bool asyncMode, PrintTicket printTicket, PrintTicketLevel printTicketLevel, Visual visual)
        {
            /* Removed Code */

            if (!asyncMode)
            {
                try
                {
                    try
                    {
                        this._manager.SaveAsXaml(visual); /* This line is called to serialize to Xaml */
                        return flag;
                    }
                    catch (PrintingCanceledException exception)
                    {
                        this.parentWriter.OnWritingCanceled(this, exception);
                    }
                    return flag;
                }
                finally
                {
                    flag = true;
                }
            }
            XpsWriterException.ThrowException("XPSWriter.BatchSync");
            return flag;
        }
    }
}

And the PackageSerializationManager _manager is actually a XpsSerializationManager:

// Generated by .NET Reflector from C:\Windows\Microsoft.Net\assembly\GAC_MSIL\ReachFramework\v4.0_4.0.0.0__31bf3856ad364e35\ReachFramework.dll
namespace System.Windows.Xps.Serialization
{
    /* Removed Code */

    public class XpsSerializationManager : PackageSerializationManager
    {
        /* Removed Code */

        private ReachHierarchySimulator _simulator;

        /* Removed Code */

        internal override ReachSerializer GetSerializer(object serializedObject)
        {
            ReachSerializer serializer = null;
            serializer = base.GetSerializer(serializedObject);
            if (serializer == null)
            {
                serializer = base.SerializersCacheManager.GetSerializer(serializedObject);
            }
            return serializer;
        }

        /* Removed Code */

        public override void SaveAsXaml(object serializedObject)
        {
            /* Removed Code */

            ReachSerializer serializer = this.GetSerializer(serializedObject);
            if (serializer == null)
            {
                throw new XpsSerializationException(System.Windows.Xps.SR.Get("ReachSerialization_NoSerializer"));
            }
            serializer.SerializeObject(serializedObject);

            /* Removed Code */
        }

        /* Removed Code */
    }
}

And here's the PackageSerializationManager that it inherits:

// Generated by .NET Reflector from C:\Windows\Microsoft.Net\assembly\GAC_MSIL\ReachFramework\v4.0_4.0.0.0__31bf3856ad364e35\ReachFramework.dll
namespace System.Windows.Xps.Serialization
{
    /* Removed Code */

    public abstract class PackageSerializationManager : IDisposable
    {
        /* Removed Code */

        private System.Windows.Xps.Serialization.SerializersCacheManager _serializersCacheManager;

        /* Removed Code */

        internal virtual ReachSerializer GetSerializer(object serializedObject)
        {
            return this._serializersCacheManager.GetSerializer(serializedObject);
        }

        /* Removed Code */

        public abstract void SaveAsXaml(object serializedObject);

        /* Removed Code */

        internal System.Windows.Xps.Serialization.SerializersCacheManager CacheManager
        {
            get
            {
                return this._serializersCacheManager;
            }
        }

        /* Removed Code */

        internal System.Windows.Xps.Serialization.SerializersCacheManager SerializersCacheManager
        {
            get
            {
                return this._serializersCacheManager;
            }
        }
    }
}

The closest non-solution I have so far

I was thinking of inheritting from XpsSerializationManager and overriding the SaveAsXaml method, but I'm not sure how to actually override it since it doesn't return a value or offer a stream or any object to write to. Even if I did that, I'd have to use reflection to change the PackageSerializationManager _manager field in the VisualsToXpsDocument class, so it's a bit hacky.

So thus far, I do not have a solution.


An unfinished potential XamlWriter

If it is possible to replace the serializer, then here's an example of a custom XamlWriter that I found here:

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows.Markup;
using System.Windows.Markup.Primitives;
using System.Xml;

// Summary:
// Provides a single static Overload:SXamlWriter.Save method
// that can be used for limited Extensible Application
// Markup Language (XAML) serialization of provided runtime objects. This class
// cannot be inherited, and only has static methods.
public static class XamlWriter {
    // Summary:
    // Returns a Extensible Application Markup Language (XAML) string that serializes
    // the provided object.
    //
    // Parameters:
    // obj:
    // The element to be serialized. Typically, this is the root element of a page
    // or application.
    //
    // Returns:
    // Extensible Application Markup Language (XAML) string that can be written
    // to a stream or file. The logical tree of all elements that fall under the
    // provided obj element will be serialized.
    //
    // Exceptions:
    // System.Security.SecurityException:
    // the application is not running in full trust.
    //
    // System.ArgumentNullException:
    // obj is null.
    public static string Save(object obj) {
        var sb = new StringBuilder();
        WriteObject(obj, sb, true);
        return sb.ToString();
    }

    //WriteObject - 3 params (used primarily when isRoot is true or by the 2 param version)
    private static void WriteObject(object obj, StringBuilder sb, bool isRoot) {
        WriteObjectWithKey(null, obj, sb, isRoot);
    }

    //WriteObject - 2 param version
    private static void WriteObject(object obj, StringBuilder sb) {
        WriteObjectWithKey(null, obj, sb, false);
    }

    //WriteObject - 3 param version
    private static void WriteObjectWithKey(object key, object obj, StringBuilder sb) {
        WriteObjectWithKey(key, obj, sb, false);
    }

    private static Dictionary<Type, string> contentProperties = new Dictionary<Type, string>();

    //WriteObject - 4 params (used primarily when isRoot is true or by the 3 param version)
    private static void WriteObjectWithKey(object key, object obj, StringBuilder sb, bool isRoot) {
        var propertyElements = new List<MarkupProperty>();
        //If the value is a string
        var s = obj as string;
        if (s != null) {
            //TODO: in a dictionary, this should be serialized as a <s:String />
            sb.Append(s);
            return;
        }
        MarkupProperty contentProperty = null;
        string contentPropertyName = null;
        var markupObj = MarkupWriter.GetMarkupObjectFor(obj);
        var objectType = obj.GetType();
        sb.Append("<" + markupObj.ObjectType.Name);
        if (isRoot) {
            sb.Append(
                " xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"");
        }

        if (key != null) {
            var keyString = key.ToString();
            if (keyString.Length > 0)
                sb.Append(" x:Key=\"" + keyString + "\"");
            else
                //TODO: key may not be a string, what about x:Type...
                throw new NotImplementedException("Sample XamlWriter cannot yet handle keys that aren't strings");
        }

        //Look for CPA info in our cache that keeps contentProperty names per Type
        //If it doesn't have an entry, go get the info and store it.
        if (!contentProperties.ContainsKey(objectType)) {
            var lookedUpContentProperty = string.Empty;
            foreach (Attribute attr in markupObj.Attributes) {
                var cpa = attr as ContentPropertyAttribute;
                if (cpa != null)
                    lookedUpContentProperty = cpa.Name;
            }
            contentProperties.Add(objectType, lookedUpContentProperty);
        }
        contentPropertyName = contentProperties[objectType];

        var contentString = string.Empty;
        foreach (var markupProperty in markupObj.Properties) {
            if (markupProperty.Name != contentPropertyName) {
                if (markupProperty.IsValueAsString)
                    contentString = markupProperty.Value as string;
                else if (!markupProperty.IsComposite)
                    sb.Append(" " + markupProperty.Name + "=\"" + markupProperty.Value + "\"");
                else if (markupProperty.Value.GetType() == typeof (NullExtension))
                    sb.Append(" " + markupProperty.Name + "=\"{x:Null}\"");
                else {
                    propertyElements.Add(markupProperty);
                }
            }
            else
                contentProperty = markupProperty;
        }

        if (contentProperty != null || propertyElements.Count > 0 || contentString != string.Empty) {
            sb.Append(">");
            foreach (var markupProp in propertyElements) {
                var propElementName = markupObj.ObjectType.Name + "." + markupProp.Name;
                sb.Append("<" + propElementName + ">");
                WriteChildren(sb, markupProp);
                sb.Append("</" + propElementName + ">");
            }
            if (contentString != string.Empty)
                sb.Append(contentString);
            else if (contentProperty != null)
                WriteChildren(sb, contentProperty);
            sb.Append("</" + markupObj.ObjectType.Name + ">");
        }
        else {
            sb.Append("/>");
        }
    }

    private static void WriteChildren(StringBuilder sb, MarkupProperty markupProp) {
        if (!markupProp.IsComposite) {
            WriteObject(markupProp.Value, sb);
        }
        else {
            var collection = markupProp.Value as IList;
            var dictionary = markupProp.Value as IDictionary;
            if (collection != null) {
                foreach (var o in collection)
                    WriteObject(o, sb);
            }
            else if (dictionary != null) {
                foreach (var key in dictionary.Keys) {
                    WriteObjectWithKey(key, dictionary[key], sb);
                }
            }
            else
                WriteObject(markupProp.Value, sb);
        }
    }

    //
    // Summary:
    // Saves Extensible Application Markup Language (XAML) information into a provided
    // stream to serialize the provided object.
    //
    // Parameters:
    // obj:
    // The element to be serialized. Typically, this is the root element of a page
    // or application.
    //
    // stream:
    // Destination stream for the serialized XAML information.
    //
    // Exceptions:
    // System.Security.SecurityException:
    // the application is not running in full trust.
    //
    // System.ArgumentNullException:
    // obj is null -or- stream is null.
    public static void Save(object obj, Stream stream) {
        var writer = new StreamWriter(stream);
        stream.Seek(0, SeekOrigin.Begin); //this line may not be needed.
        writer.Write(Save(obj));
        writer.Flush();
    }

    //
    // Summary:
    // Saves Extensible Application Markup Language (XAML) information as the source
    // for a provided text writer object. The output of the text writer can then
    // be used to serialize the provided object.
    //
    // Parameters:
    // writer:
    // TextWriter instance to use to write the serialized XAML information.
    //
    // obj:
    // The element to be serialized. Typically, this is the root element of a page
    // or application.
    //
    // Exceptions:
    // System.ArgumentNullException:
    // obj is null -or- writer is null.
    //
    // System.Security.SecurityException:
    // the application is not running in full trust.

    public static void Save(object obj, TextWriter writer) {}
    //
    // Summary:
    // Saves Extensible Application Markup Language (XAML) information as the source
    // for a provided XML writer object. The output of the XML writer can then be
    // used to serialize the provided object.
    //
    // Parameters:
    // obj:
    // The element to be serialized. Typically, this is the root element of a page
    // or application.
    //
    // xmlWriter:
    // Writer to use to write the serialized XAML information.
    //
    // Exceptions:
    // System.ArgumentNullException:
    // obj is null -or- manager is null.
    //
    // System.Security.SecurityException:
    // the application is not running in full trust.

    public static void Save(object obj, XmlWriter xmlWriter) {}
}
Charlie
  • 846
  • 7
  • 21
  • This question is basically, "Is XYZ possible?" and "If so, how do it do it?" Which basically seems like you are asking the community to research this for you. You should make a stab at doing this research yourself, and if you still come to a dead end, I'd re-present this question as "How do I do XYZ? I tried found this thing and that thing, but they resulted in this result and that result." – rodamn Sep 08 '15 at 23:28
  • 2
    Fair enough. I have researched it though, so I'll add the failures that I tried. I just didn't see any way doing what I wanted. – Charlie Sep 08 '15 at 23:31

0 Answers0