locked
Serialize namespace as URI rather than assembly/type RRS feed

  • Question

  • Hi,

    Looking at the XAML for a workflow, I see a number of references to namespaces using a fully qualified type name and an assembly name.  For example:

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

    There are other cases, however, where a URI is used rather than an assembly and type name.  For example:

      xmlns:av="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    When I add a custom activity to a workflow, it uses the assembly name and type name approach to define my namespace.  I'd like to know:

    1) how I can change what the designer inserts such that inserts a custom URI,

    2) which somehow resolve to my type, outside of the workflow designer.  That is, is the XAML file doesn't specifically resolve the URI.

    I imagine I need to do something with the Xaml reader to map my custom URI, but I'm not sure where to begin.  (I'm rehosting the workflow designer, so I imagine that makes the scenario a bit more reasonable than if I were trying to do this with the vanilla WF designer in VS).

    Thanks,

    Notre

    Tuesday, April 20, 2010 10:35 PM

Answers

  • I think the normal way this mapping works is the same as for WPF XAML.

    XAML Namespaces and Namespace Mapping for WPF XAML (incremental search this page for 'XmlnsDefinitionAttribute').

    Of course the assembly has to be 'primed' somehow for the attribute to be known to the Xaml reader, I forget how this works... I think it was by setting ReferenceAssemblies on the XamlSchemaContext.

    The XamlSchemaContext is the key object that figures out type mappings between Xaml names and CLR types, so you can also have a look at overriding that to provide your own custom type mapping, overriding AssemblyResolve, etc.

    [+Matt has this entry about XAML rewriting on his blog, it doesn't actually show what you are trying to do, but it does show in very general how you could pass a custom XamlSchemaContext. Emitting the mc:Ignorable Instruction In Your WF4 XAML]

    Tim

    • Marked as answer by Notre Wednesday, April 21, 2010 7:22 PM
    Wednesday, April 21, 2010 12:14 AM

All replies

  • I think the normal way this mapping works is the same as for WPF XAML.

    XAML Namespaces and Namespace Mapping for WPF XAML (incremental search this page for 'XmlnsDefinitionAttribute').

    Of course the assembly has to be 'primed' somehow for the attribute to be known to the Xaml reader, I forget how this works... I think it was by setting ReferenceAssemblies on the XamlSchemaContext.

    The XamlSchemaContext is the key object that figures out type mappings between Xaml names and CLR types, so you can also have a look at overriding that to provide your own custom type mapping, overriding AssemblyResolve, etc.

    [+Matt has this entry about XAML rewriting on his blog, it doesn't actually show what you are trying to do, but it does show in very general how you could pass a custom XamlSchemaContext. Emitting the mc:Ignorable Instruction In Your WF4 XAML]

    Tim

    • Marked as answer by Notre Wednesday, April 21, 2010 7:22 PM
    Wednesday, April 21, 2010 12:14 AM
  • Hi Tim,

    Thanks for the information.  What I've done so far is to modify the AssemblyInfo.cs file in the assembly that contains my custom activites.  I added an attribute like this:

    [assembly: XmlnsDefinition("http://schemas.myCompany.com/activities", "ActivityLibrary1")]

     

    ActivityLibrary1 is the namespace that contains my custom activity classes.  Then I went into the generated XAML for a workflow that VS created and modified the namespace attribute to this:

    xmlns:p="http://schemas.myCompany.com/activities"

    Later in the XAML, the designer had (earlier, before manually changing the namespace to use the URI rather than assembly and type name) generated XAML like this in my designer:

    <p:PromptActivity sap:VirtualizedContainerService.HintSize="200,22" Text="Hello, world" Title="Prompt title" />

    (PromptActivity is a custom activity in my activity library).  The XmlnsDefinition attribute did the trick for resolving the URI to the assembly and type within the designer in VS.  Good start!  I still have to work to generate the namespace to use a URI automatically, rather than writing the assembly and type name.  I'm hoping Matt's post will help there (and I'll look into that shortly).

    However, before I go into that, I have a related question.  So, I've now got the URI based custom namespace working in the designer (to a point).  Next, I want to take that XAML and run it.  So, I created a simple little WPF app with a big textbox in which I can paste the generated XAML, and a button on the window that takes the XAML text and attempts to run it.  The WPF app has an explicit reference to the assembly (and is set to Copy Local this assembly) that contains the custom activities, and the XmlnsDefinition attribute, as shown above.  The code to run the workflow XAML looks like this:

            private void Button_Click(object sender, RoutedEventArgs e)
            {
                StringReader stringReader = new StringReader(xamlTextBox.Text);
                Activity workflowRootActivity = ActivityXamlServices.Load(stringReader);
                WorkflowInvoker.Invoke(workflowRootActivity);
            }

    When I run the WPF app and click the button, it fails with the exception (stack trace truncated for brevity):

     

    System.Xaml.XamlObjectWriterException was unhandled
      Message=Cannot create unknown type '{http://schemas.myCompany.com/activities}PromptActivity'.
      Source=System.Xaml
      LineNumber=0
      LinePosition=0
      StackTrace:
           at System.Xaml.XamlObjectWriter.WriteStartObject(XamlType xamlType)
           at System.Xaml.XamlWriter.WriteNode(XamlReader reader)
           at System.Xaml.XamlServices.Transform(XamlReader xamlReader, XamlWriter xamlWriter, Boolean closeWriter)
           at System.Activities.XamlIntegration.FuncDeferringLoader.FuncFactory`1.Evaluate()
           at System.Activities.DynamicActivity.OnInternalCacheMetadata(Boolean createEmptyBindings)
           at System.Activities.Activity.InternalCacheMetadata(Boolean createEmptyBindings, IList`1& validationErrors)
           at System.Activities.ActivityUtilities.ProcessActivity(ChildActivity childActivity, ChildActivity& nextActivity, Stack`1& activitiesRemaining, ActivityCallStack parentChain, IList`1& validationErrors, ProcessActivityTreeOptions options, ProcessActivityCallback callback)
           at System.Activities.ActivityUtilities.ProcessActivityTreeCore(ChildActivity currentActivity, ActivityCallStack parentChain, ProcessActivityTreeOptions options, ProcessActivityCallback callback, IList`1& validationErrors)
           at System.Activities.ActivityUtilities.CacheRootMetadata(Activity activity, LocationReferenceEnvironment hostEnvironment, ProcessActivityTreeOptions options, ProcessActivityCallback callback, IList`1& validationErrors)
           at System.Activities.Hosting.WorkflowInstance.ValidateWorkflow(WorkflowInstanceExtensionManager extensionManager)
           at System.Activities.Hosting.WorkflowInstance.RegisterExtensionManager(WorkflowInstanceExtensionManager extensionManager)
           at System.Activities.WorkflowApplication.EnsureInitialized()
           at System.Activities.WorkflowApplication.RunInstance(WorkflowApplication instance)
           at System.Activities.WorkflowApplication.Invoke(Activity activity, IDictionary`2 inputs, WorkflowInstanceExtensionManager extensions, TimeSpan timeout)
           at System.Activities.WorkflowInvoker.Invoke(Activity workflow, TimeSpan timeout, WorkflowInstanceExtensionManager extensions)
           at System.Activities.WorkflowInvoker.Invoke(Activity workflow)
           at WFRunnerApp.MainWindow.Button_Click(Object sender, RoutedEventArgs e) in C:\Users\tstich.SWG\Documents\Visual Studio 2010

    If I change the XAML I pasted in such that the custom namepace uses the assembly/type format rather than URI, e.g.

        xmlns:p="clr-namespace:ActivityLibrary1;assembly=ActivityLibrary1"

    Then the workflow runs successfully, and my custom activity is executed.

    So, my new question is, how to do I provide the URI to type/assembly mapping, from the runtime perspective?  I imagine it goes back to XamlSchemaContext, but the XamlSchemaContext.ReferenceAssemblies property is read only, and I'm not sure how Matt's tip can apply in this context.

    Thanks,

    Notre

    Wednesday, April 21, 2010 5:43 PM
  • Hm, sorry, you're right, it is read-only.
    Looks like the way to set it is probably via the XamlSchemaContext(IEnumerable<Assembly>) constructor.

    As for generating http namespaces - My recollection is that VS (or probably its actually XamlSchemaContext) should be doing that for you automatically as long as it saw your XmlnsDefinitionAttributes. For VS, the assembly should only need to be referenced/the local assembly.

    Tim

    Wednesday, April 21, 2010 5:48 PM
  • Success!  I modified my code to run the workflow to look like this:

            private void Button_Click(object sender, RoutedEventArgs e)
            {
                //Define a xml schema context with the referenced activity library and interface assemblies
                Assembly activityInterfacesAssembly = Assembly.Load(new AssemblyName("ActivityInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=77e3577bf486b28b"));
                Assembly activityLibraryAssembly = Assembly.Load(new AssemblyName("ActivityLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e638fa7f776c63c5"));
                List<Assembly> referencedAssemblies = new List<Assembly>();
                referencedAssemblies.Add(activityInterfacesAssembly);
                referencedAssemblies.Add(activityLibraryAssembly);
                XamlSchemaContext xsc = new XamlSchemaContext((referencedAssemblies));

                //Load XAML into an activity and run it
                StringReader stringReader = new StringReader(xamlTextBox.Text);
                XamlXmlReader xamlXmlReader = new XamlXmlReader(stringReader);
                Activity workflowRootActivity = ActivityXamlServices.Load(xamlXmlReader);
                WorkflowInvoker.Invoke(workflowRootActivity);
            }

    Yes, I tried VS and it does automatically insert the http namespace for me.  Nice. 

     

    Now, I have to see if the rehosted designer is that smart. I'm going to keep the forum question open for a while longer, until I can verify it either works automatically or verify I know how to do what is needed.

    Thanks,

    Notre

    Wednesday, April 21, 2010 6:31 PM
  • Sweet - the re-hosted designer also automatically serializes using the http/URI namespace rather than the assembly / type format.

    I think this answers my questions for now.

    Thanks,

    Notre

    Wednesday, April 21, 2010 7:21 PM