workflow-foundation-4

How do I use System.Activities.Validation.GetParentChain?


I've got an Outer activity which has a Body onto which you drag other activities. I then have several Inner activities, which MUST be a descendent of an Outer activity. I'd like to add design-time validation to ensure the Inner is placed within an Outer.

I've heard that I "can use System.Activities.Validation.GetParentChain to enumerate all of the parents of an activity during the validation step". But even after reading the documentation for the class, I have no idea how to use it.

I would think I use it inside CacheMetadata in my Inner class. I'd like to use have a foreach(var ancestor in parentChain) { ... }, and make sure at least one ancestor is of type Outer. Not sure how to do that.

Can anyone explain how to validate at design time that an Inner activity is a descendant of an Outer activity?


Solution

  • As you can see through the documentation GetParentChain is a regular CodeActivity. You can use it in conjunction with Activity.Constraints.

    Constraints are executed at design time, just like CacheMetadata(), but you have the ability to access the context (the ValidationContext at this point, of course). Otherwise you wouldn't be able to known the upper level activities.

    Lets see if I understood your case right and if this example covers it. Basically it loops through IEnumerable<Activity> returned by GetParentChain and checks if any of Inner's parents is an Outer. This way it ensures that Inner is always inside an Outer.

    public sealed class Inner : CodeActivity
    {
    
        public Inner()
        {
            Constraints.Add(MustBeInsideOuterActivityConstraint());
        }
    
        protected override void Execute(CodeActivityContext context)
        {
            // Execution logic here
        }
    
        private Constraint<Inner> MustBeInsideOuterActivityConstraint()
        {
            var activityBeingValidated = new DelegateInArgument<Inner>();
            var validationContext = new DelegateInArgument<ValidationContext>();
            var parent = new DelegateInArgument<Activity>();
            var parentIsOuter = new Variable<bool>();
    
            return new Constraint<Inner>
            {
                Body = new ActivityAction<Inner, ValidationContext>
                {
                    Argument1 = activityBeingValidated,
                    Argument2 = validationContext,
    
                    Handler = new Sequence
                    {
                        Variables = 
                        {
                            parentIsOuter
                        },
                        Activities =
                        {
                            new ForEach<Activity>
                            {
                                Values = new GetParentChain 
                                { 
                                    ValidationContext = validationContext 
                                },
    
                                Body = new ActivityAction<Activity>
                                {
                                    Argument = parent,
    
                                    Handler = new If
                                    {
                                        Condition = new InArgument<bool>(env => 
                                            object.Equals(parent.Get(env).GetType(), typeof(Outer))),
    
                                        Then = new Assign<bool> 
                                        { 
                                            To = parentIsOuter, 
                                            Value = true 
                                        }
                                    }    
                                }
                            },
    
                            new AssertValidation
                            {
                                Assertion = parentIsOuter,
                                Message = "Inner must be inside Outer"
                            }
                        }
                    }
                }
            };
        }
    }
    

    If you want to allow multiple Outers, you have to check them, one by one, wither with a loop through an array (using ForEach) or multiple nested Ifs.

    For example, with multiple ifs, and continuing with the code above:

    Handler = new If
    {
        Condition = new InArgument<bool>(env => 
            object.Equals(parent.Get(env).GetType(), typeof(OuterONE))),
    
        Then = new Assign<bool> 
        { 
            To = parentIsOuter, 
            Value = true 
        }
        Else = new If
        {
             Condition = new InArgument<bool>(env => 
                 object.Equals(parent.Get(env).GetType(), typeof(OuterTWO))),
    
             Then = new Assign<bool> 
             { 
                To = parentIsOuter, 
                Value = true 
             },
    
             Else = new If 
             {
                 // and continue with other Outers
             }
        }
    } 
    

    In short, an If-then-else statement using activities.

    Other option that I've never tested but that it seems pretty plausible, and because you can use activities inside constraints, is throw all this logic inside an activity which its only job is to check if type if an Outer:

    public sealed CheckIfTypeIsOuter<T> : CodeActivity<bool>
    {
        protected override bool Execute() 
        {
            if (typeof(T) == typeof(Outer1))
                return true;
            if (typeof(T) == typeof(Outer2))
                return true;
            if (typeof(T) == typeof(Outer3))
                return true;
            if (typeof(T) == typeof(Outer4))
                return true;
    
            return false;
        }
    }
    

    This way you can do it through code.

    Well, I guess you get the idea!