[Edit 2/5/12: fixed spelling of 'ignorable' everywhere].


Or “What is this weird XAML I get from workflow designer that doesn’t look like ‘normal’ XAML?”

(No ‘news’ today, just some WF4 olds in article format, and mostly implementation level trivia stuff at that, but I hope it’s interesting. Smile)

Here’s something I whipped up today while trying to do some XAML related testing. One problem with XAML generated by WorkflowDesigner.Save() is that it’s actually very special XAML, and different from what you would get from just calling good old ‘XamlServices.Save()’. But have you ever looked at all the differences and wondered what they are? Well, let’s take a look! Here is some actual XAML generated by the actual Visual Studio 2010 workflow designer.

/// Yep, this is what WF 4.0 XAML files created with Dev10 ACTUALLY look like.

/// <Activity

mc:Ignorable="sap"

x:Class="WorkflowConsoleApplication3.Workflow1" sap:VirtualizedContainerService.HintSize="240,240"

mva:VisualBasic.Settings="Assembly references and imported namespaces for internal implementation"

xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

xmlns:mv="clr-namespace:Microsoft.VisualBasic;assembly=System"

xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.Activities"

xmlns:s="clr-namespace:System;assembly=mscorlib"

xmlns:s1="clr-namespace:System;assembly=System"

xmlns:s2="clr-namespace:System;assembly=System.Xml"

xmlns:s3="clr-namespace:System;assembly=System.Core"

xmlns:sad="clr-namespace:System.Activities.Debugger;assembly=System.Activities"

xmlns:sap="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation"

xmlns:scg="clr-namespace:System.Collections.Generic;assembly=System"

xmlns:scg1="clr-namespace:System.Collections.Generic;assembly=System.ServiceModel"

xmlns:scg2="clr-namespace:System.Collections.Generic;assembly=System.Core"

xmlns:scg3="clr-namespace:System.Collections.Generic;assembly=mscorlib"

xmlns:sd="clr-namespace:System.Data;assembly=System.Data"

xmlns:sl="clr-namespace:System.Linq;assembly=System.Core"

xmlns:st="clr-namespace:System.Text;assembly=mscorlib"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

///   <Sequence sad:XamlDebuggerXmlReader.FileName="c:\users\tilovell\documents\visual studio 2010\Projects\WorkflowConsoleApplication3\WorkflowConsoleApplication3\Workflow1.xaml" sap:VirtualizedContainerService.HintSize="200,200">

///     <sap:WorkflowViewStateService.ViewState>

///       <scg3:Dictionary x:TypeArguments="x:String, x:Object">

///         <x:Boolean x:Key="IsExpanded">True</x:Boolean>

///       </scg3:Dictionary>

///     </sap:WorkflowViewStateService.ViewState>

///   </Sequence>

/// </Activity>

 

Wow, so much weird-looking XML.

Interesting looking thing #1. x:Class. What is that? If you know what the runtime type of the basic object you’re usually working with in ActivityDesigner is, it’s ActivityBuilder. So why is the root element <Activity x:Class> and not ActivityBuilder? Well, Matt Winkler discussed this one a long time ago, so I’m not going into that deeply, but it’s probably interesting to know that it’s because it was written using a special XamlWriter, one from ActivityXamlServices.CreateBuilderWriter().

Interesting looking thing #2. sap:VirtualizedContainerService.HintSize=”240,240”. What is that? Actually it’s not that weird. This is a XAML AttachedProperty. Through regular XAML rules, we can figure out that it’s referring to the VirtualizedContainerService.HintSize property. This will actually be written out by a regular XamlXmlWriter, or XamlServices.Save(), if you have already set the attached property on an object (in this case it’s set on the ActivityBuilder while WorkflowDesigner is runing).

Interesting looking thing #3. mva:VisualBasic.Settings=”Assembly references and imported namespaces for internal implementation". This is also a XAML AttachedProperty. In this case it’s indicating something kind of special. But hang on one second!

Interesting looking thing #4. There are so many unused XAML namespaces. Why?? Specifically, nearly ALL of the xmlns:blahblah=”clr-namespace:blahblahblah” namespaces in your documents are never referened by XML elements or attributes. Why? Well it’s because of the mva:VisualBasic.Settings. What mva:VisualBasic.Settings is indicating is that all of your xmlns namespaces are going to be treated as imported namespaces in your workflow’s VB expressions. What’s an imported namespace? It’s a namespace which will be searched by the hosted Visual Basic compiler, i.e. it’s like a C# using statement.
So basically XAML has all your workflow’s ‘using’ statements hidden as xmlns namespaces. This actually creates an annoying bug or two in the workflow designer experience. You can’t, for instance, add a variable of type X to your workflow without adding the namespace of that type to your namespace imports. But adding namespaces occasionally has an unwanted effect of creating namespace resolution ambiguities! So you end up having to use a fully qualified namespace in your Visual Basic expression to resolve that. Which is just like not having imported the namespace you wanted to import in the first place! (Meh) Anyway, since it’s just another attached property, again to set this one again all you need to do is set the attached property on the object before you serialize.

Interesting looking thing #5. Looking at xmlns:x=”http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation” we realize not every xmlns namespace is a clr-namespace in XAML. Even though, in this case, it really does often refer, in its XAMLy-way (like in the HintSize attached property) to CLR types! Actually you can define your own XAML Http namespaces as well, if you want to. It’s harder to describe just when it is a good idea to do this (define an http namespace to use), in fact I don’t claim to understand that fully yet.

Interesting looking thing #6. mc:Ignorable attribute. I purposely left this one to last! What does it mean? Well, if you research a little on your favorite search engine, you’ll find that mc:Ignorable is not a XAML-ism, it’s actually an XML-ism/XAML-ism for ‘markup compatibility’. What’s markup compatibility? It’s a way of saying ‘you can ignore this stuff if you don’t understand it.’

Anyway the real subject which prompted me to write this blog is the question how do I add mc:Ignorable to a XAML file.

As far as I know there isn’t actually a XAML-based way of adding this attribute. So I had to do it through XML… and on the way I added a couple attached properties and stuff for fun, so you can see how to do some of this stuff manually - just in case the need should ever arise!

    ActivityBuilder ab = new ActivityBuilder

    {

        Name = "MyProject.Workflow1",

        Implementation = new Sequence

        {

        }

    };

           

    AttachablePropertyServices.SetProperty(

        ab.Implementation,

        XamlDebuggerXmlReader.FileNameName,

@"c:\users\tilovell\documents\visual studio 2010\Projects\WorkflowConsoleApplication3\WorkflowConsoleApplication3\Workflow1.xaml");

 

    // Minor hack. Actually hint size is not a string value... but whatever type it really was, will serialize the same to XAML

    AttachablePropertyServices.SetProperty(ab.Implementation, VirtualizedContainerService.HintSizeName, new Size(200, 200));

 

    var isExpandedState = new Dictionary<string, object>

    {

        { "IsExpanded", true },

    };

    AttachablePropertyServices.SetProperty(ab.Implementation, WorkflowViewStateService.ViewStateName, isExpandedState);

           

    MemoryStream xamlStream = ab.AsXamlBytes();

    string oldXaml = xamlStream.AsXmlString();

    string newXaml = AddMcIgnorableAttributeForSAP(oldXaml);

}

 

private string AddMcIgnorableAttributeForSAP(string oldXaml)

{

    Stream xamlStream = new MemoryStream(Encoding.UTF8.GetBytes(oldXaml));

 

    // Serialize to XElement, then add the mc:Ignorable Attribute via XML API, since it's not representing a XAML object

    // xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

    XName activityName = XName.Get("Activity", "http://schemas.microsoft.com/netfx/2009/xaml/activities");

    XName mcIgnorableName = XName.Get("Ignorable", "http://schemas.openxmlformats.org/markup-compatibility/2006");

 

    XElement xamlXml = XElement.Load(xamlStream, LoadOptions.PreserveWhitespace);

    Assert.AreEqual(activityName, xamlXml.Name);

    Assert.IsNull(xamlXml.Attribute(mcIgnorableName));

    string sapPrefix = xamlXml.GetPrefixOfNamespace(XNamespace.Get("http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation"));

    xamlXml.Add(new XAttribute(XNamespace.Xmlns + "mc", mcIgnorableName.NamespaceName));

    List<XAttribute> EventAttributes = xamlXml.Attributes().ToList();

    EventAttributes.Insert(0, new XAttribute(mcIgnorableName, sapPrefix));

    xamlXml.ReplaceAttributes(EventAttributes.ToArray());

 

    MemoryStream newXamlStream = new MemoryStream();

    xamlXml.Save(newXamlStream, SaveOptions.DisableFormatting);

    newXamlStream.Position = 0;

    return new StreamReader(newXamlStream).ReadToEnd();