locked
Using an argument in an attached property RRS feed

  • Question

  • Hi,

    I've written a workflow level ErrorMessage as an attached property on the root workflow activity, at design time.  I made the attached property for ErrorMessage written as an InArgument (to support some custom control that works well with InArguments).  Here's some of the design time code:

                AttachedProperty<InArgument<string>> ErrorMessage = new AttachedProperty<InArgument<string>>
                {
                    OwnerType = typeof(ActivityBuilder),
                    IsBrowsable = true,
                    Name = "ErrorMessage",
                    Getter = (mi =>
                    {
                        InArgument<string> temp;
                        AttachablePropertyServices.TryGetProperty<InArgument<string>>(mi.GetCurrentValue(), ActivityError.ErrorMessage, out temp);
                        return temp;
                    }),
                    Setter = ((mi, val) => AttachablePropertyServices.SetProperty(mi.GetCurrentValue(), ActivityError.ErrorMessage, val))

                };
                aps.AddProperty(ErrorMessage);

    which result in XAML that looks similar to this (edited for brevity), when I set the ErrorMessage to "default message":

    <Activity mc:Ignorable="sap" x:Class="MyWorkflow" sap:VirtualizedContainerService.HintSize="1613,853" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces" xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:av="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.Activities" xmlns:p="http://myURI" xmlns:sap="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
      <p:ActivityError.ErrorMessage>
        <InArgument x:TypeArguments="x:String">"default message"</InArgument>
      </p:ActivityError.ErrorMessage>
      <Flowchart DisplayName="&lt;new workflow&gt;" sap:VirtualizedContainerService.HintSize="1573,813" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces">
        <sap:WorkflowViewStateService.ViewState>
          <scg:Dictionary x:TypeArguments="x:String, x:Object">
            <av:Point x:Key="ShapeLocation">10,30</av:Point>
            <av:Size x:Key="ShapeSize">60,75</av:Size>
            <x:Double x:Key="Width">1558.8</x:Double>
            <x:Double x:Key="Height">776.772</x:Double>
          </scg:Dictionary>
        </sap:WorkflowViewStateService.ViewState>
        <Flowchart.StartNode>
          <x:Null />
        </Flowchart.StartNode>
      </Flowchart>
    </Activity>

    This all works fine at design time.  The problem I'm running into is how to retrieve and evaluate this InArgument and, at runtime.

    The first problem I'm running into is just trying to retrieve the attached property on the root node, at runtime.  Here's some of my runtime code, within the NativeActivity derived Execute method:

                WorkflowInstanceProxy proxy = context.GetExtension<WorkflowInstanceInfo>().GetProxy();
                object errorMessageString;
                bool result = AttachablePropertyServices.TryGetProperty(proxy.WorkflowDefinition.DisplayName, ActivityError.ErrorMessage, out errorMessageString);


    The proxy returns the root node at runtime, but the call to TryGetProperty returns false.  I thought the problem might be the InArgument<string>, so I tried a simpler ErrorAction attached property (not shown in XAML, but a boolean type):

                object errorActionString;
                result = AttachablePropertyServices.TryGetProperty(proxy.WorkflowDefinition, ActivityError.ErrorAction, out errorActionString);

    That worked okay.  I then discovered the CopyPropertiesToMethod method, and decided to try to use it to get the various attached properties:

                KeyValuePair<AttachableMemberIdentifier, object>[] workflowProperties = new KeyValuePair<AttachableMemberIdentifier, object>[AttachablePropertyServices.GetAttachedPropertyCount(proxy.WorkflowDefinition)];
                AttachablePropertyServices.CopyPropertiesTo(proxy.WorkflowDefinition, workflowProperties, 0);

    Looking at a few watch values,

    -                              workflowProperties                {System.Collections.Generic.KeyValuePair<System.Xaml.AttachableMemberIdentifier,object>[4]}                System.Collections.Generic.KeyValuePair<System.Xaml.AttachableMemberIdentifier,object>[]

    -                              [3]          {[MyNamespace.ActivityError.ErrorMessage, System.Activities.InArgument`1[System.String]]}                System.Collections.Generic.KeyValuePair<System.Xaml.AttachableMemberIdentifier,object>

    I can see that there's an attached property that has an argument, as I defined it as design time.

    So, the questions are:

    1) How to get the ErrorMessage property via TryGetProperty?  (CopyPropertiesToMethod will work, as a fallback).
    2) How to get the value of the ErrorMessage property/argument?  Normally, I'd do something like context.GetValue<string>(theArgument), but when I tried this:

    context.GetValue<string>((System.Activities.InArgument<string>)(workflowProperties[3].Value))

    I get an exception thrown:

                    'context.GetValue<string>((System.Activities.InArgument<string>)(workflowProperties[3].Value))' threw an exception of type 'System.InvalidOperationException'   string {System.InvalidOperationException}

    Thanks,
    Notre

    Tuesday, April 5, 2011 5:31 PM

Answers

  • Hmm..
    OK, that confirms my suspicion that the context belongs to another one other than the one the arguments are declared on.

    If the argument isn't in the workflow tree, since nobody added it using CacheMetadata, then GetValue will fail.

    If the argument isn't in scope due to public/private implementation scoping rules, GetValue should definitely fail. I am not sure if that applies in this case, and of course it depends on CacheMetadata behavior.

    If it is actually in scope, from being in chain of public children, it might possibly work, but even then I am not sure if this is a supported use of ActivityContext. And actually I kind of doubt that it is.

    So next question - what are you really trying to do?
    Tim

    • Marked as answer by Notre Friday, April 8, 2011 8:04 PM
    Wednesday, April 6, 2011 2:22 AM

All replies

  • >AttachablePropertyServices.TryGetProperty(proxy.WorkflowDefinition.DisplayName, ActivityError.ErrorMessage, out errorMessageString);

    I wonder why are you passing in WorkflowDefinition.DisplayName here? It looks to me like the property is attached the root activity, not the display name.

    For Part 2, how did you get context? Maybe it's not legal to evaluate the variable in that context. Otherwise I don't see any problem.

    Tim

    Tuesday, April 5, 2011 9:08 PM
  • Doh - thank you!  WorkflowDefinition.DisplayName is a typo; it should be attached to the root activity (WorkflowDefinition), not the DisplayName, as you guessed.  So, you've answered my first question.

    For part 2, the context I'm using is for a child (of the root) activity, not the root activity.  That's probably where I'm hitting a wall.   I don't know how to get the context from the root activity, which is not my own activity but the parent of a FlowChart activity, I think.

    Thanks,

    Notre

    P.S. The better exception information is as follows:

    System.InvalidOperationException occurred
      Message=The argument of type 'System.String' cannot be used.  Make sure that it is declared on an activity.
      Source=System.Activities
      StackTrace:
           at System.Activities.Argument.ThrowIfNotInTree()
      InnerException:

      System.Activities.dll!System.Activities.Argument.ThrowIfNotInTree() + 0x73 bytes 
      System.Activities.dll!System.Activities.ActivityContext.GetValue<string>(System.Activities.InArgument<string> argument) + 0x31 bytes 
      Evaluation of: context.GetValue<string>((System.Activities.InArgument<string>)(errorMessageString)) 

    • Edited by Notre Tuesday, April 5, 2011 9:46 PM Edited exception information
    Tuesday, April 5, 2011 9:42 PM
  • Hmm..
    OK, that confirms my suspicion that the context belongs to another one other than the one the arguments are declared on.

    If the argument isn't in the workflow tree, since nobody added it using CacheMetadata, then GetValue will fail.

    If the argument isn't in scope due to public/private implementation scoping rules, GetValue should definitely fail. I am not sure if that applies in this case, and of course it depends on CacheMetadata behavior.

    If it is actually in scope, from being in chain of public children, it might possibly work, but even then I am not sure if this is a supported use of ActivityContext. And actually I kind of doubt that it is.

    So next question - what are you really trying to do?
    Tim

    • Marked as answer by Notre Friday, April 8, 2011 8:04 PM
    Wednesday, April 6, 2011 2:22 AM
  • What I'm trying to do is define some values for workflow level default settings, related to custom error handling (when similar settings on a given child activity of the workflow specify 'use the default' workflow error handling settings).  At design time, I have a design experience that results in some attached properties being created on the ActivityBuilder (root node at design time).  At runtime, I get the root node from the executing child elements, and examine the default settings made on the root element.

    In most cases, the attached properties I've placed on the root activity are 'simple' properties.  In the case of this ErrorMessage attached property, I wish to have the ability to have an expression, something I can do on a given activity's InArguments, but not on properties.  So, I had the 'bright' idea of making the attached property on the root element actually be an InArgument, rather than a vanilla property.  This works fine at design time...

    As noted earlier, all the other 'vanilla' properties can be accessed just fine from the child activity, at runtime.  It's just the special InArgument attached property that's causing me grief.  Undoubtably, for the reason's you outlined :)

    Notre

    Wednesday, April 6, 2011 4:21 AM
  • The best alternative I can think of to enable expressions is to implement this settings initializer concept as a custom activity.
    Tim

    Wednesday, April 6, 2011 4:12 PM
  • Hi Tim,

    What do you mean by "implement this settings initializer concept as a custom activity"?  Do you mean abandon the use of the Flowchart and create my own top level, custom activity?

    Thanks,

    Trevor

    Wednesday, April 6, 2011 5:11 PM
  • I was thinking of having the custom activity be one of the regular children of flowchart.
    Tim
    Wednesday, April 6, 2011 5:55 PM
  • All my activities that try to access the attached properties on the root element are custom activities, that are regular children of the flowchart. Can you please expand on your idea?

    Thanks!

    Notre

    Wednesday, April 6, 2011 6:54 PM
  • I need to work on my powers of expression huh. :p

    But it seems like the first problem you have is there is no way to schedule custom logic based upon your attached properties. Adding a child custom activity would allow you to get custom logic scheduled, and in a scope where it can definitely see variables etc.

    Having solved that the second problem is how do you pass data from that child activity down to the other activities you want to pass it to... actually I don't see how the workflow data model allows this either, unless your child is an ancestor of those activities... so either you're redesigning the activity tree, or going outside the WF data model somehow.

    Tim

    Wednesday, April 6, 2011 7:17 PM
  • I think I also need to work on my power of expression (the pun was nice, BTW).

    My custom activities will get scheduled just like any other activity, as children of the Flowchart.   The question is, how do I get information from a commonly accessible area in the workflow, from any child activity?  I chose to put some workflow level state as attached properties on the root node, and then use an extension to get access to that root node, and grab the attached properties off it.  For 'regular' attached properties, this works just fine.  For more 'interesting' attached properties, where I try to stuff an InArgument into the attached property, I can get the InArgument attached property from the custom child activity okay (thanks to you catching my typo), but can't get it evaluated.

    Is my goal clear now, or is my explanation still muddled?

    Thanks,

    Notre

    Wednesday, April 6, 2011 8:05 PM
  • I'm now thinking (accepting?) this approach might be a dead end, so I'm prepared to go with plan B.  Rather than using an attached property for the workflow-level ErrorMessage, I could create a variable at design time and access it from the activity.  In order to access the variable from the child activity's execution (Execute method), I think we need to associate the variable with an input argument on the activity

    Is there a way to programmatically gain access to the variable without doing it this way (maybe through explicit creation of a runtime argument and binding it to the variable somehow during CacheMetadata)?  Otherwise I guess this would mean I'd need to have a common, well known InArgument on any error handling capable custom activity, which the designer could link to the ErrorMessage variable at design time.

    Thanks,

    Notre

    Thursday, April 7, 2011 8:07 PM
  • >(maybe through explicit creation of a runtime argument and binding it to the variable somehow during CacheMetadata)?

    Hm. For public child activities, that sounds like it just might actually work - you might be able to figure out whether the variable exists from ActivityMetadata.Environment then bind the argument whenever it does.

    Tim

    Thursday, April 7, 2011 9:03 PM
  • So, ActivityMetadata.Environment might be able to tell me whether the variables exists (I'm not sure how at this point), but I can't create an InArgument on a public child activity at runtime, can I? That is, I need to create my public child activity at design time such that it has an InArgument, right?

    Thanks,

    Notre

    Thursday, April 7, 2011 9:18 PM
  • I don't think that rule exists. You can create activities in code and run them after all. The only hard rule should be that you can't change your arguments once you've started running.
    Tim

    Friday, April 8, 2011 12:18 AM
  • I guess changing arguments (creating them) once I start running is what I was thinking of, so I guess I'll go the simple route or creating and bindng them at runtime.

    Thanks for your help.

    Notre

    Friday, April 8, 2011 8:02 PM