none
Argument validation in custom activity designer

    Question

  • I am having problems getting validation to work properly in the designer for my custom activity. The simplest sample to reproduce the behavior is as follows:

    I have a custom WF4 activity with a dynamic collection of arguments stored in a dictionary:

    [Designer(typeof(DictionaryActivityDesigner))]
    public class DictionaryActivity : NativeActivity
    {
      [Browsable(false)]
      public Dictionary<string, InArgument> Arguments { get; set; }
      public InArgument<string> StringArg { get; set; }
    
      public DictionaryActivity()
      {
        Arguments = new Dictionary<string, InArgument>();
      }
    
      protected override void Execute(NativeActivityContext context)
      { }
    }
    
    

    In the designer I dinamically create expression text boxes for editing these arguments. The user has the possibility to define the arguments and their types in a separate modal window, but for the sake of simplicity I have fixed the arguments in this sample:

    public partial class DictionaryActivityDesigner
    {
      private Dictionary<string, Type> definition;
    
      public DictionaryActivityDesigner()
      {
        definition = new Dictionary<string, Type>
        {
          { "String Arg", typeof(string) },
          { "Int Arg", typeof(int) }
        };
    
        InitializeComponent();
      }
    
      public void InitializeGrid(Dictionary<string, Type> arguments)
      {
        ArgumentsGrid.RowDefinitions.Clear();
        ArgumentsGrid.Children.Clear();
    
        int gridRow = 0;
        foreach (var arg in arguments)
        {
          ArgumentsGrid.RowDefinitions.Add(new RowDefinition());
    
          var label = new Label()
          {
            Content = arg.Key + ":"
          };
          Grid.SetRow(label, gridRow);
          Grid.SetColumn(label, 0);
          ArgumentsGrid.Children.Add(label);
    
          var textbox = new ExpressionTextBox()
          {
            ExpressionType = arg.Value,
            OwnerActivity = ModelItem,
            UseLocationExpression = false
          };
          var binding = new Binding()
          {
            Mode = BindingMode.TwoWay,
            Converter = new ArgumentToExpressionConverter(),
            ConverterParameter = "In",
            Path = new PropertyPath("ModelItem.Arguments[(0)]", arg.Key)
          };
          textbox.SetBinding(ExpressionTextBox.ExpressionProperty, binding);
          Grid.SetRow(textbox, gridRow);
          Grid.SetColumn(textbox, 1);
          ArgumentsGrid.Children.Add(textbox);
    
          gridRow++;
        }
      }
    
      private void ActivityDesigner_Loaded(object sender, RoutedEventArgs e)
      {
        InitializeGrid(definition);
      }
    }
    
    

    Below is the XAML for the designer:

    <sap:ActivityDesigner x:Class="ActivityValidation.DictionaryActivityDesigner"
               xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
               xmlns:s="clr-namespace:System;assembly=mscorlib"
               xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
               xmlns:sapc="clr-namespace:System.Activities.Presentation.Converters;assembly=System.Activities.Presentation"
               xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation"
               Loaded="ActivityDesigner_Loaded">
      <sap:ActivityDesigner.Resources>
        <ResourceDictionary>
          <sapc:ArgumentToExpressionConverter x:Key="ArgumentToExpressionConverter" />
        </ResourceDictionary>
      </sap:ActivityDesigner.Resources>
      <StackPanel Orientation="Vertical">
        <Grid Name="ArgumentsGrid">
          <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition MinWidth="250" />
          </Grid.ColumnDefinitions>
        </Grid>
        <sapv:ExpressionTextBox ExpressionType="s:String" 
                    OwnerActivity="{Binding ModelItem}" 
                    Expression="{Binding ModelItem.StringArg, Mode=TwoWay, Converter={StaticResource ArgumentToExpressionConverter}, ConverterParameter=In}" />
      </StackPanel>
    </sap:ActivityDesigner>
    
    

    The InitializeGrid method adds the expression text boxes for the arguments to the ArgumentGrid. Under it I have a separate statically defined expression text box for a fixed argument in the activity to demonstrate the (almost) desired behavior.

    Now for the problems:

    1. Invalid expressions for the dynamic arguments only cause the error icon to appear beside the text box but it doesn't propagate to the top bar of the designer as it does if there is an error in the statically defined text box.

    2. If I close the designer in such invalid state (and save the definition), the eror icon correctly propagates to the top bar even if the error is only in the dynamic text box. Though the behavior gets even more strange afterwards. After changing the values for the arguments, now even the error icon beside the text box doesn't work consistently any more.

    3. If I delete the contents of a dynamic text box completely, the value in the dictionary gets set to null which manifests in the workflow definition as <x:Null x:Key="String Arg" /> instead of <InArgument x:TypeArguments="x:String" x:Key="String Arg">["a"]</InArgument> or just ommiting the entry as is the case before editing the expression for the first time. If I reopen such a workflow even the statically created text box doesn't work properly any more (the error icon is only visible when text box is focused and it doesn't propagate to the top any more).

    It seems obvious that I am doing something wrong when creating the dynamic text boxes. What would be the correct way of doing it? Is there any example available for creating a designer for a custom activity with dynamic number of arguments?

    Saturday, May 14, 2011 12:14 PM

Answers

  • OK, thanks for the updated sample code. I think one thing you should probably be doing different, but that doesn't make a difference, is making your changes through the model tree:

    //this commented code doesn't go through the model item tree, so designer won't know about the changes you are making
    //if (!modelArguments.ContainsKey(arg.Key))
    //{
    //    modelArguments[arg.Key] = (InArgument)Activator.CreateInstance(typeof(InArgument<>).MakeGenericType(arg.Value));
    //}

    if (!ModelItem.Properties["Arguments"].Dictionary.ContainsKey(arg.Key))
    {
        object x = Activator.CreateInstance(typeof(InArgument<>).MakeGenericType(arg.Value));
        ModelItem.Properties["Arguments"].Dictionary.Add(arg.Key, x);
    }

    Anyway even with this I still see the interesting behavior similar to what you described in original post part 2), where validation stops working once you edit an expression in the ETB. Also, I am seeing some other really weird behavior, where your edit into the ETB doesn't always commit - it depends what you click on outside of the ETB in order to lose focus.

    To be honest, I think this behavior is not a problem of your code but a bug in the designer. A good next thing to do might be file it on connect.microsoft.com (Product = Visual Studio & .Net framework), or contact MS support to ask if there is a hotfix.
    Tim

    Tuesday, May 31, 2011 5:13 AM
  • I encountered the problem I described here while trying to create a designer for a dynamic collection of arguments in an activity. I managed to work around the problem by using the built-in DynamicArgumentDialog window. I had to restructure my activity to accept a single collection of both input and output arguments:

    public Dictionary<string, Argument> Arguments { get; set; }
    

    instead of two separate collections I was using before:

    public Dictionary<string, InArgument> InArguments { get; set; }
    public Dictionary<string, OutArgument> OutArguments { get; set; }
    

    I found the Custom Activity to Invoke XAML Based Child Workflows very helpful when making this work.

     





    Tuesday, August 02, 2011 1:37 PM

All replies

  • Hi,

    Would you mind send me(xhinker[at]hotmail.com) a sample project that produce the problem

    Regards
    MSDN Community Support
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    This posting is provided "AS IS" with no warranties, and confers no rights. My Blog: http://xhinker.com
    Microsoft Windows Workflow Foundation 4.0 Cookbook
    Wednesday, May 18, 2011 5:41 AM
  • >1. Invalid expressions for the dynamic arguments only cause the error icon to appear beside the text box but it doesn't propagate to the top bar of the designer as it does if there is an error in the statically defined text box.

    This should be based on whether you declared the arguments properly inside your activity's CacheMetadata(), using e.g. ActivityMetadata.Bind, Metadata.SetArgumentsCollection, and RuntimeArgument.

    Tim


    Wednesday, May 18, 2011 7:39 AM
  • Thanks, Tim.

    In the above sample I haven't overriden the CacheMetadata method since it didn't seem to make any difference. I did some more testing regarding that today and it seems that this might be related to my problems. I have now overriden the CacheMetadata method as follows (I already use a similar approach in my project):

    protected override void CacheMetadata(NativeActivityMetadata metadata)
    {
    	var runtimeArgument = new RuntimeArgument("StringArg", typeof(string), ArgumentDirection.In);
    	metadata.Bind(StringArg, runtimeArgument);
    	metadata.AddArgument(runtimeArgument);
    
    	foreach (string inputName in Arguments.Keys.ToArray())
    	{
    		InArgument argument = Arguments[inputName];
    		runtimeArgument = new RuntimeArgument(String.Format("Argument-{0}", inputName), argument.ArgumentType, ArgumentDirection.In);
    		metadata.Bind(argument, runtimeArgument);
    		metadata.AddArgument(runtimeArgument);
    	}
    }
    

    The problem persists as I described it before. I have tried not to bind the "static" StringArg argument (I commented out the first 3 lines in the method) and now the same problem manifested itself with this argument as well. This got me thinking that I might be doing something wrong with the metadata binding for the arguments in the dictionary, though I'm not sure what.

    The builtin InvokeMethod activity offers a simiar functionality for editing the Parameters collection so it must be possible.

    Thursday, May 19, 2011 8:29 AM
  • Andrew,

    I've just sent you the project reproducing the problem.

    I also put it online in case anyone else wants to take a look: download link.

    Thursday, May 19, 2011 8:43 AM
  • Hi, Arh

    You can try code inside method CacheMetadata:

         metadata.AddValidationError("your error message");
    Here is a sample could be helpful to you:
    http://xhinker.com/post/WF4-Activity-Validation.aspx

    Regards


    MSDN Community Support
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    This posting is provided "AS IS" with no warranties, and confers no rights. My Blog: http://xhinker.com
    Microsoft Windows Workflow Foundation 4.0 Cookbook
    Friday, May 20, 2011 6:33 AM
  • Andrew,

    I know about

    metadata.AddValidationError("your error message");
    

    but I'm not sure how this could help me in my case. To add validation errors myself, I would need to be able to check from code whether the entered expressions for arguments are valid are not. I have no idea have to do that (short of implementing my own expression validation). It might not even be possible.

    I would be glad to be proven wrong with a working sample, though.

    Friday, May 20, 2011 12:36 PM
  • Hi Arh,
    I tried running your code.
     
    The main problem I can find with the code that you posted is that the dictionary of arguments you are creating UI for dynamically is not ever actually set as part of your activity's CLR properties (nor did you go through ModelItem to do so).
    This means that the designer, which does validation by traversing the Activity tree, will never find the arguments, since they are stored in a dictionary it doesn't know about.

    Now I suspect this is maybe because you simplified your code sample in order for the post, and is not the actual cause of the issue in your real code. If so, maybe you could do another iteration of code posting.
    Tim

    Saturday, May 21, 2011 8:07 PM
  • Tim,

    I apologize for the late reply but I've been out of the office last week.

    If I understand correctly, you're suggesting that I should also be manually adding the arguments to the Arguments collection of the Activity object itself. That's something I've been doing at first, but I've abandoned it because of the problems it caused. I've now added the code I was using to my sample:

    // get reference to the arguments collection from the ModelItem at the beginning of InitializeGrid
    Dictionary<string, InArgument> modelArguments = (Dictionary<string, InArgument>)ModelItem.Properties["Arguments"].ComputedValue;
    
    // add each argument to the collection if it's not already there
    if (!modelArguments.ContainsKey(arg.Key))
      modelArguments[arg.Key] = (InArgument)Activator.CreateInstance(typeof(InArgument<>).MakeGenericType(arg.Value));
    
    

    After doing that an ArgumentException is raised when I first enter something into the ExpressionTextBox:

    System.ArgumentException: An item with the same key has already been added.
      at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
      at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
      at System.Collections.Generic.Dictionary`2.System.Collections.IDictionary.Add(Object key, Object value)
      at System.Activities.Presentation.Model.ModelItemDictionaryImpl.DictionaryWrapper.Add(Object key, Object value)
      at System.Activities.Presentation.Model.ModelItemDictionaryImpl.AddCore(ModelItem key, ModelItem value)
      at System.Activities.Presentation.Model.DictionaryChange.ApplyInsert()
      at System.Activities.Presentation.Model.DictionaryChange.Apply()
      at System.Activities.Presentation.Model.EditingScope.OnComplete()
      at System.Activities.Presentation.Model.ModelEditingScope.Complete()
      at System.Activities.Presentation.View.ExpressionTextBox.GenerateExpression()
      at System.Activities.Presentation.View.ExpressionTextBox.Commit(Boolean isExplicitCommit)
      at System.Activities.Presentation.View.ExpressionTextBox.DoLostFocus()
      at System.Activities.Presentation.View.ExpressionTextBox.OnEditorLostAggregateFocus(Object sender, EventArgs e)
      at Microsoft.VisualStudio.Activities.AddIn.ExpressionEditor.OnTextViewLostAggregateFocus(Object sender, EventArgs e)
      at Microsoft.VisualStudio.Text.Utilities.GuardedOperations.RaiseEvent(Object sender, EventHandler eventHandlers)

    It looks as if the designer wasn't aware that the argument is already in the collection and was trying to add it again. I couldn't figure out how to prevent it, therefore I decided to just stop manually creating the arguments. They seemed to be created by the designer anyway.

    Any help is appreciated.

    Monday, May 30, 2011 7:25 AM
  • Yes, that is the code I felt was missing from your sample. I will have another look.
    Tim

    Monday, May 30, 2011 9:10 PM
  • OK, thanks for the updated sample code. I think one thing you should probably be doing different, but that doesn't make a difference, is making your changes through the model tree:

    //this commented code doesn't go through the model item tree, so designer won't know about the changes you are making
    //if (!modelArguments.ContainsKey(arg.Key))
    //{
    //    modelArguments[arg.Key] = (InArgument)Activator.CreateInstance(typeof(InArgument<>).MakeGenericType(arg.Value));
    //}

    if (!ModelItem.Properties["Arguments"].Dictionary.ContainsKey(arg.Key))
    {
        object x = Activator.CreateInstance(typeof(InArgument<>).MakeGenericType(arg.Value));
        ModelItem.Properties["Arguments"].Dictionary.Add(arg.Key, x);
    }

    Anyway even with this I still see the interesting behavior similar to what you described in original post part 2), where validation stops working once you edit an expression in the ETB. Also, I am seeing some other really weird behavior, where your edit into the ETB doesn't always commit - it depends what you click on outside of the ETB in order to lose focus.

    To be honest, I think this behavior is not a problem of your code but a bug in the designer. A good next thing to do might be file it on connect.microsoft.com (Product = Visual Studio & .Net framework), or contact MS support to ask if there is a hotfix.
    Tim

    Tuesday, May 31, 2011 5:13 AM
  • Tim,

    Thanks for all the help.

    I followed your advice and filed a report on Microsoft Connect.

    Tuesday, May 31, 2011 7:48 AM
  • I encountered the problem I described here while trying to create a designer for a dynamic collection of arguments in an activity. I managed to work around the problem by using the built-in DynamicArgumentDialog window. I had to restructure my activity to accept a single collection of both input and output arguments:

    public Dictionary<string, Argument> Arguments { get; set; }
    

    instead of two separate collections I was using before:

    public Dictionary<string, InArgument> InArguments { get; set; }
    public Dictionary<string, OutArgument> OutArguments { get; set; }
    

    I found the Custom Activity to Invoke XAML Based Child Workflows very helpful when making this work.

     





    Tuesday, August 02, 2011 1:37 PM
  • Hai anderw we are displaying the error message of age in workflow thats fine but How can we display the same error message age must larger than 0 in the properties window beside the Age.Can we able to do it
    • Edited by Naga bushan Tuesday, September 06, 2011 1:59 PM
    Tuesday, September 06, 2011 1:57 PM