Search code examples
wpfbamlbaml2006reader

loading ResourceDictionary from baml using Baml2006Reader


How do can I read through a baml stream that contains a ResourceDictionaory using the Baml2006Reader and without acually instantiating the the ResourceDictionary?

I can ready through regular baml that just contains a UserControl just fine and I can examine the XAML tree using Baml2006Reader.NodeType etc.

But once the reader hits a ResourceDictionary, Baml2006Reader.Member.Name is "DeferrableContent" and Baml2006Reader.Value contains a MemoryStream that can not be parsed by another instance of Baml2006Reader. I can't event instantiate the reader:

System.IO.EndOfStreamException occurred HResult=-2147024858
Message=Unable to read beyond the end of the stream. Source=mscorlib StackTrace: at System.IO.MemoryStream.InternalReadInt32() at System.Windows.Baml2006.Baml2006Reader.Process_Header() at WpfApplication10.AssemblyExtensions.Read(Stream stream, List`1 result) in d:\Documents\Visual Studio 2012\Projects\WpfApplication10\WpfApplication10\AssemblyExtensions.cs:line 84 InnerException:


Solution

  • It seems that whenever the Baml2006Reader encounters an element where Baml2006Reader.Member.Name is "DeferrableContent" it is followed by another node where BamlReader.Value is a MemoryStream. It seems that this stream only contains a baml fragment and does not have a header (that's why System.Windows.Baml2006.Baml2006Reader.Process_Header() fails.)

    So we need to tell the baml reader to read a baml fragment. This can be done be giving the reader an instance of System.Windows.Baml2006.Baml2006ReaderSettings where the IsBamlFragment property istrue.

    Unfortunately both the Baml2006ReaderSettings class and the appropriate constructor of Baml2006Reader are internal. So we need to resort to reflection:

    private static string PresentationFrameworkAssemblyName = "PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35";
    
    private static Baml2006Reader CreateBamlFragmentReader(MemoryStream substream, XamlSchemaContext schemaContext)
    {
        var bamlSettingsType =
            Type.GetType(
                "System.Windows.Baml2006.Baml2006ReaderSettings, " + PresentationFrameworkAssemblyName);
        var settingsCtor =
            bamlSettingsType.GetConstructor(Type.EmptyTypes);
        var bamlSettings = settingsCtor.Invoke(null);
        var isBamlFragmentProp = bamlSettingsType.GetProperty("IsBamlFragment",
                                                                  BindingFlags.NonPublic |
                                                                  BindingFlags.Instance);
        isBamlFragmentProp.SetValue(bamlSettings, true, null);
    
        var ctor = typeof (Baml2006Reader).GetConstructor(
            BindingFlags.Instance | BindingFlags.NonPublic,
            null,
            new[]
            {
                typeof (Stream),
                Type.GetType(
                    "System.Windows.Baml2006.Baml2006SchemaContext, " + PresentationFrameworkAssemblyName),
                    bamlSettingsType
                },
                null);
    
            return (Baml2006Reader)ctor.Invoke(new[] { substream, schemaContext, bamlSettings });
        }
    

    usage:

    var substream = reader.Value as MemoryStream;
    if (substream != null)
    {
        using (var subReader = CreateBamlFragmentReader(substream, reader.SchemaContext))
        {
            // continue reading with subReader
        }
    }
    

    I know this is rather fragile code and very hackish, but what the heck - it works (for me, currently)!