For some experimentation (more on that in another post) I needed to convert an arbitrary object graph to Baml. So with a very simple API equivalent to XamlReader and XamlWriter here's my interpretation of BamlReader and BamlWriter.
The only way I know to write Baml is to interact with the MSBuild tasks in PresentationBuildTasks. I couldn't actually work out if it's possible to use these without MSBuild. Fortunately, automating MSBuild turns out to be quite easy.
BamlReader on the other hand was difficult. It would appear there aren't any public APIs we can use to read an arbitrary stream. One possible solution is to emit the Baml as a resource in an assembly then invoke Application.LoadComponent (the way Baml is normally loaded). For now I've taken the easy way out and resorted to evil private reflection.
using System.IO; using System.Windows.Markup; using System.CodeDom.Compiler; using System.Reflection; using System.Runtime.InteropServices; using Microsoft.Build.BuildEngine; public static class BamlWriter { public static void Save(object obj, Stream stream) { string path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(path); try { string xamlFile = Path.Combine(path, "input.xaml"); string projFile = Path.Combine(path, "project.proj"); using (FileStream fs = File.Create(xamlFile)) { XamlWriter.Save(obj, fs); } Engine engine = new Engine(); engine.BinPath = RuntimeEnvironment.GetRuntimeDirectory(); Project project = engine.CreateNewProject(); BuildPropertyGroup pgroup = project.AddNewPropertyGroup(false); pgroup.AddNewProperty("AssemblyName", "temp"); pgroup.AddNewProperty("OutputType", "Library"); pgroup.AddNewProperty("IntermediateOutputPath", "."); pgroup.AddNewProperty("MarkupCompilePass1DependsOn", "ResolveReferences"); BuildItemGroup igroup = project.AddNewItemGroup(); igroup.AddNewItem("Page", "input.xaml"); igroup.AddNewItem("Reference", "WindowsBase"); igroup.AddNewItem("Reference", "PresentationCore"); igroup.AddNewItem("Reference", "PresentationFramework"); project.AddNewImport(@"$(MSBuildBinPath)\Microsoft.CSharp.targets", null); project.AddNewImport(@"$(MSBuildBinPath)\Microsoft.WinFX.targets", null); project.FullFileName = projFile; if (engine.BuildProject(project, "MarkupCompilePass1")) { byte[] buffer = new byte[1024]; using (FileStream fs = File.OpenRead(Path.Combine(path, "input.baml"))) { int read = 0; while (0 < (read = fs.Read(buffer, 0, buffer.Length))) { stream.Write(buffer, 0, read); } } } else { // attach a logger to the Engine if you need better errors throw new System.Exception("Baml compilation failed."); } } finally { Directory.Delete(path, true); } } } public static class BamlReader { public static object Load(Stream stream) { ParserContext pc = new ParserContext(); return typeof(XamlReader) .GetMethod("LoadBaml", BindingFlags.NonPublic | BindingFlags.Static) .Invoke(null, new object[] { stream, pc, null, false }); } }
