locked
What is the best practice to debug pure XAML workflows? RRS feed

  • Question

  • Our system run only full XAML workflows, so we can customize some of them for some customers. But for debugging, it's a lot better to use the breakpoints in the designer with the strongly typed classes. So for unittestings, for exemple, we wan't to be able to use the strongly typed classes. But before going too far, we already have some problems while trying to do so.

    Here is an exemple to reproduce our problems :

          private static void TestDebug2 ()
          {
    // XAML of the Workflow created by the designer string originalXml = @"<Activity mc:Ignorable=""sap"" x:Class=""ConsoleApplication1.Test8"" mva:VisualBasic.Settings=""Assembly references and imported namespaces serialized as XML namespaces"" xmlns=""http://schemas.microsoft.com/netfx/2009/xaml/activities"" xmlns:local=""clr-namespace:ConsoleApplication1"" 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:sd1=""clr-namespace:System.Data;assembly=System.Data.DataSetExtensions"" 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\emonda\Documents\Visual Studio 10\Projects\ConsoleApplication1\ConsoleApplication1\Test8.xaml"" sap:VirtualizedContainerService.HintSize=""222,208""> <sap:WorkflowViewStateService.ViewState> <scg3:Dictionary x:TypeArguments=""x:String, x:Object""> <x:Boolean x:Key=""IsExpanded"">True</x:Boolean> </scg3:Dictionary> </sap:WorkflowViewStateService.ViewState> <local:TestElement sap:VirtualizedContainerService.HintSize=""200,22"" /> <local:TestElement sap:VirtualizedContainerService.HintSize=""200,22"" /> </Sequence> </Activity>";


    // Workflow with the modified "local" (done by hand-editing)
    string patchXml = @"<Activity mc:Ignorable=""sap"" x:Class=""ConsoleApplication1.Test8"" mva:VisualBasic.Settings=""Assembly references and imported namespaces serialized as XML namespaces"" xmlns=""http://schemas.microsoft.com/netfx/2009/xaml/activities"" xmlns:local=""clr-namespace:ConsoleApplication1;assembly=ConsoleApplication1"" 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:sd1=""clr-namespace:System.Data;assembly=System.Data.DataSetExtensions"" 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\emonda\Documents\Visual Studio 10\Projects\ConsoleApplication1\ConsoleApplication1\Test8.xaml"" sap:VirtualizedContainerService.HintSize=""222,208""> <sap:WorkflowViewStateService.ViewState> <scg3:Dictionary x:TypeArguments=""x:String, x:Object""> <x:Boolean x:Key=""IsExpanded"">True</x:Boolean> </scg3:Dictionary> </sap:WorkflowViewStateService.ViewState> <local:TestElement sap:VirtualizedContainerService.HintSize=""200,22"" /> <local:TestElement sap:VirtualizedContainerService.HintSize=""200,22"" /> </Sequence> </Activity>"; // Workflow auto-generated
    string generatedXml;
    System.IO.Stream generatedXmlStream = System.Reflection.Assembly.GetExecutingAssembly ().GetManifestResourceStream ("ConsoleApplication1.obj.x86.Debug.Test8.g.xaml"); using (var reader = new StreamReader (generatedXmlStream)) { generatedXml = reader.ReadToEnd (); } Activity workflow; // With original : Cannot create unknown type '{clr-namespace:ConsoleApplication1}TestElement'. using (StringReader reader = new StringReader (originalXml)) { workflow = ActivityXamlServices.Load (reader); } WorkflowInvoker.Invoke (workflow); // With patch : Ok using (StringReader reader = new StringReader (patchXml)) { workflow = ActivityXamlServices.Load (reader); } WorkflowInvoker.Invoke (workflow); // With generated : Cannot set unknown member 'Implementation'. using (StringReader reader = new StringReader (generatedXml)) { workflow = ActivityXamlServices.Load (reader); } WorkflowInvoker.Invoke (workflow); }

    And this is the custom activity for the sample :

       public class TestElement : System.Activities.CodeActivity
       {
          public static int ExecuteCount = 0;
          protected override void Execute (CodeActivityContext context)
          {
             ExecuteCount++;
          }
       }
    
    - So, as we can see, a copy / paste of the .xaml of the content generated by the desginer doesn't always works right out of the box. We, most of the time, need to add the assembly name for the local prefix to make it work else we get this error : Cannot create unknown type '{clr-namespace:ConsoleApplication1}TestElement'. A little tiring when we are doing a lot of workflows, especially for debugging / unit testing.

    - So, for debugging and since our API is based on XAML workflows, we tough to use the ActivityXamlServices.Load with the embedded ressources xaml. This way we could debug easily using breakpoints. But it doesn't seem to works. : Cannot set unknown member 'Implementation'.

    So, what would be the recommanded way to use the pure XAML workflows combine with the ease of debugging of the strongly typed classes without too much troubles?
    • Edited by Instriker Monday, October 26, 2009 2:39 PM Bad formatting
    Monday, October 26, 2009 2:37 PM

Answers

  • Rather than copy/pasting the XAML into your unit tests you should be loading the XAML from the XAML file you created in the designer.  This will have a couple of benefits:

    * The designer created file acts as your workflow definition and you don't have to worry about it getting out of sync
    * The debugger will continue to work

    For example, I would expect your tests to look something like:

    Activity workflow = ActivityXamlServices.Load(@"c:\MyWorkflowDefinitionStorageLocation\Workflow1.xaml");
    WorkflowInvoker.Invoke(workflow);
    Tuesday, October 27, 2009 8:42 PM

All replies

  • You should not mix compiled activities (such as TestElement) with activities that you do not intend to compile (like ConsoleApplication1.Test8) to avoid exactly the problems that you are seeing above.  The designer emits namespaces as though the XAML will be compiled into an activity in that project's assembly.  Pulling the XAML out of the assembly and loading it separately breaks some of those namespaces references.

    I'm assuming that the XAML you are trying to pull out of the manifest resources is the XAML generated by compiling the workflow.  I'm not familiar with how this is different from the XAML produced by the designer, but it wouldn't surprise me if this is some special format or if it has some of the more pertinent parts stripped from it.  When a XAML activity is compiled, a class representing the public surface area is created and the rest of the activity is loaded from the XAML stream on demand.  You could use a tool like reflector to see exactly how ConsoleApplication1.Test8 is loaded that XAML for execution if you wanted more details.

    The way I would suggest doing this is the following:
    * Keep your compiled activities in a separate project from the activities you aren't going to compile.
    * Use ActivityXamlServices.Load to load the XAML document produced by the designer.


    Nate
    Monday, October 26, 2009 9:39 PM
  • Yes using the non compiled workflows in other project generated the "assembly=" in the generated xmlns, so I don't have the probleme anymore.

    But doing so, I lose the power of the debugger with breakpoints in the workflow. Also, it give some difficulties in unit testings, since each time I change my workflow, I need to open as xml... and copy paste in my unit tests... Is there any plan to better support XAML workflows for tests purposes?

    Tuesday, October 27, 2009 1:34 PM
  • Rather than copy/pasting the XAML into your unit tests you should be loading the XAML from the XAML file you created in the designer.  This will have a couple of benefits:

    * The designer created file acts as your workflow definition and you don't have to worry about it getting out of sync
    * The debugger will continue to work

    For example, I would expect your tests to look something like:

    Activity workflow = ActivityXamlServices.Load(@"c:\MyWorkflowDefinitionStorageLocation\Workflow1.xaml");
    WorkflowInvoker.Invoke(workflow);
    Tuesday, October 27, 2009 8:42 PM
  • Adding non-compiled workflows in an other project combined with the Load goes well.

    Thanks.
    Wednesday, October 28, 2009 2:31 PM