Search code examples
c#workflow-foundation-4custom-activity

Workflow Foundation 4 - Results of ActivityFunc<bool> always false even though Execute Method shows true


Ello, I'm having an issue with a custom activity that preforms an evaluation of a `ActivityFunc ` and returns false even though it is evaluated in Execute to be true. Here is my activity


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.ComponentModel;
using System.Activities.Presentation;

namespace SomeActivities
{
    /// 
    /// Root of any number of activities used to check for a specific set of conditions to be true (Trigger Conditions) 
    /// 
    public sealed class Trigger : NativeActivity, IActivityTemplateFactory
    {
        /// 
        /// The initial Condition that determines if the trigger should be scheduled
        /// 
        /// The condition.
        [RequiredArgument]
        public ActivityFunc <bool> Condition { get; set; }

        /// 
        /// The resulting action that is scheduled if the Condition is true
        /// 
        /// The child.
        [RequiredArgument]
        public ActivityAction Child { get; set; }

        /// 
        /// Gets or sets the value holding whether or not the trigger matches the condition
        /// 
        /// The type of the match.
        public MatchType MatchType{ get; set; }

        private CompletionCallback<bool> OnExecutionCompleteCallBack;

        protected override void Execute(NativeActivityContext context)
        {
            this.OnExecutionCompleteCallBack = this.OnConditionComplete;
            context.ScheduleFunc<bool>(this.Condition, this.OnExecutionCompleteCallBack);
        }

        public void OnConditionComplete(NativeActivityContext context, ActivityInstance instance, bool result)
        {
            if (instance.State == ActivityInstanceState.Canceled)
            {
                context.Abort();
                return;
            }

            //check if Condition evaluation returns true
            //Always show as false
            if (result)
            {
                //If so then schedule child Activity
                context.ScheduleAction(Child);
            }
        }

        Activity IActivityTemplateFactory.Create(System.Windows.DependencyObject target)
        {
            return new Trigger()
            {
                Child = new ActivityAction()
                {
                    DisplayName = "Trigger Child Action"
                },

                Condition = new ActivityFunc<bool>()
                {
                    DisplayName = "Trigger Conditionals",
                    Result = new DelegateOutArgument<bool>()
                },
                DisplayName = "Trigger",
                MatchType = MatchType.Matches,

            };
        }
    }
}

So when my condition is evaluated in the Execute Method it calls OnConditionComplete with the result (which is what is always false) even though if I print the result of the condition to be true. So is there something obviously wrong here that I don't see?

Update

Okay I think Marice talking about having the callback in the class and just having the OnConditionComplete method point to the callback. I did change that but haven't seen a change. If I could somehow retrieve the value from the ActivityFunc<bool> child condition when its actually executing or save its value afterward, that would work great. I've played around with CacheMetadata's metadata to see if there was anything that I could find that would allow me to do so but haven't found anything as of yet.

Update 2

The problem apparently is coming from the ActivityFunc <bool> Condition. I'm going to have to go through and check what the issue(s) with the Condition might be. Not sure if this should go to a new question or not since its technically not solved but I'll see about putting together a test Condition to go off of and if nothing else show where I'm at.

Update 3

Okay this is a bare bones example of what I use as a child activity that always returns false even though it evaluates to true in the execution


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.Activities.Presentation;
using System.ComponentModel;
using System.Drawing;

namespace SomeActivities
{
    public sealed class DataHandlerTypeName : NativeActivity,IActivityTemplateFactory
    {
        // Define an activity input argument of type string
        [RequiredArgument]
        public InArgument ImportContext { get; set; }

        /// 
        /// Gets or sets the handler type name to check.
        /// 
        /// The handler type name to check.
        [RequiredArgument]
        public string HandlerTypeNameToCheck { get; set; }


        /// 
        /// Performs the trigger check for the matching Data Type Handler Names
        /// 
        /// The context.
        protected override void Execute(NativeActivityContext context)
        {
            var ic = this.ImportContext.Get(context);

            if (1==1)
            {
                //context.SetValue(base.Result, true);
                Result.Set(context, true);
            }
            else 
            {
                //context.SetValue(base.Result, true);
                Result.Set(context, false);
            }
        }

        #region IActivityTemplateFactory Members


        Activity IActivityTemplateFactory.Create(System.Windows.DependencyObject target)
        {
            return new DataHandlerTypeName()
            {
                ImportContext = this.ImportContext,
                HandlerTypeNameToCheck = "Default"
            };
        }

        #endregion
    }
}



Solution

  • Hello someone who I have never met before and just happens to share my IP.

    You're doing something wrong. Elsewhere, that is.

    Either the DLL with the activities you're using isn't fresh, or the workflow definition is out of date and doesn't hold what you believe it does. Or its something completely different.

    I've stripped down your code and compressed it into a sample project. Like to see it here we go:

    This is the simple condition:

    public sealed class AnTrigger : NativeActivity<bool>
    {
        public bool ResultToSet { get; set; }
    
        protected override void Execute(NativeActivityContext context)
        {
            Result.Set(context, ResultToSet);
        }
    }
    

    Simple, no? Here's the host that evaluates this condition and, if it returns true, runs a single child activity. Note, I'm constructing the activity in the Create method so I don't have to create an editor.

    public sealed class AnTriggerHost : NativeActivity, IActivityTemplateFactory
    {
        public ActivityFunc<bool> Condition { get; set; }
        public ActivityAction Child { get; set; }
    
        protected override void Execute(NativeActivityContext context)
        {
            context.ScheduleFunc(Condition, OnConditionComplete);
        }
    
        private void OnConditionComplete(
            NativeActivityContext context, 
            ActivityInstance completedInstance, 
            bool result)
        {
            if (result)
                context.ScheduleAction(Child);
        }
    
        Activity IActivityTemplateFactory.Create(System.Windows.DependencyObject target)
        {
            // so I don't have to create UI for these, here's a couple samples
            // seq is the first child and will run as the first AnTrigger is configured to return true
            // so the first trigger evals to true and the first child runs, which
            var seq = new Sequence
            {
                DisplayName = "Chief Runs After First Trigger Evals True"
            };
            // prints this message to the console and
            seq.Activities.Add(new WriteLine { Text = "See this?  It worked." });
            // runs this second trigger host, which 
            seq.Activities.Add(
                new AnTriggerHost
                {
                    DisplayName = "This is the SECOND host",
                    Condition = new ActivityFunc<bool>
                    {
                        // will NOT be triggered, so you will never see
                        Handler = new AnTrigger
                        {
                            ResultToSet = false,
                            DisplayName = "I return false guize"
                        }
                    },
                    Child = new ActivityAction
                    {
                        // this activity write to the console.
                        Handler = new WriteLine
                        {
                            Text = "you won't see me"
                        }
                    }
                });
    
            return new AnTriggerHost
            {
                DisplayName = "This is the FIRST host",
                Condition = new ActivityFunc<bool>
                {
                    Handler = new AnTrigger
                    {
                        ResultToSet = true,
                        DisplayName = "I return true!"
                    }
                },
                Child = new ActivityAction
                {
                    Handler = seq
                }
            };
        }
    }
    

    Drop these two in a Workflow Console app and drop the AnTriggerHost on the workflow. Set ye a couple breakpoints and watch it fly. Here's the workflow xaml:

      <local:AnTriggerHost DisplayName="This is the FIRST host" >
        <local:AnTriggerHost.Child>
          <ActivityAction>
            <Sequence DisplayName="Chief Runs After First Trigger Evals True">
              <WriteLine Text="See this?  It worked." />
              <local:AnTriggerHost DisplayName="This is the SECOND host">
                <local:AnTriggerHost.Child>
                  <ActivityAction>
                    <WriteLine Text="you won't see me" />
                  </ActivityAction>
                </local:AnTriggerHost.Child>
                <local:AnTriggerHost.Condition>
                  <ActivityFunc x:TypeArguments="x:Boolean">
                    <local:AnTrigger DisplayName="I return false guize" ResultToSet="False" />
                  </ActivityFunc>
                </local:AnTriggerHost.Condition>
              </local:AnTriggerHost>
            </Sequence>
          </ActivityAction>
        </local:AnTriggerHost.Child>
        <local:AnTriggerHost.Condition>
          <ActivityFunc x:TypeArguments="x:Boolean">
            <local:AnTrigger DisplayName="I return true!" ResultToSet="True" />
          </ActivityFunc>
        </local:AnTriggerHost.Condition>
      </local:AnTriggerHost>
    

    Your issue doesn't lie in the activities, it lies in how you're using them. You're assuming your test rig is correct when it isn't.


    For future reference... When this happens to me, the cause is that I set Result within the Create method of the IActivityTemplateFactory

    Activity IActivityTemplateFactory.Create(System.Windows.DependencyObject target)
    {
        return new Child
        {
            InputText = new InArgument<string>(
                new VisualBasicValue<string>(Parent.InputTextVariable)),
            // the following demonstrates what NOT to do in the Create method. 
            // this BREAKS your ActivityFunc, which will ALWAYS return default(T)
            // DO NOT SET Result AT ANY TIME OR IN ANY PLACE
            // BEGIN ERROR
            Result = new OutArgument<string>()
            // END ERROR
        };
    }
    

    This results in the result set within the workflow definition, which breaks the ActivityFunc pattern.

    <!--If you see ActivityFunc.Result in your workflow, DELETE IT -->
    <ActivityFunc.Result>
      <DelegateOutArgument x:TypeArguments="x:String" />
    </ActivityFunc.Result>