locked
Reference type on InArgument RRS feed

  • Question

  • I have a scenario which used to work like a charm, but stopped working since RC: a custom activity with an InArgument for a custom reference type.

    So my activity looks somewhat like this (changed naming, etc for forums):

      public sealed class MyActivity: NativeActivity  
      {
        [RequiredArgument]
        public InArgument<MyCustomRefType> TheArgument { get; set; }


    I also have a custom editor which allows a workflow author to select a value for this InArgument. The value gets set as such:

    propertyValue.Value = new InArgument<MyCustomRefType>(customTypeInstance);


    When I try and execute the workflow, I get this error:


    Literal only supports value types and the immutable type System.String...

    The only info I could find on this issue was here: http://blogs.msdn.com/endpoint/archive/2010/02/10/4-0-beta2-rc-wcf-wf-breaking-changes.aspx, but this doesn't tell me how to resolve my issue. Obviously I can't use a VisualBasicValue to create a really complex structure.

    I also don't want to go my whole class hierarchy for this type (It has properties that are reference types) to structs!

    I don't fully understand how this could have been taken away when it worked like a charm! It was so simple...

    • Edited by hendrik swanepoel Thursday, February 18, 2010 8:02 AM screw up with font sizes
    Thursday, February 18, 2010 7:59 AM

Answers

  • >Perhaps I should try and use a normal property rather, and try to find a way to validate that?

    Yes, that sounds like a good approach.
    You can add your own custom validation logic fairly easily by overriding the various versions of CacheMetadata (which are variously passed NativeActivityMetadata, ActivityMetadata, etc) to output validation errors.

    (For NativeActivity or CodeActivity mostly straight forward. Just in case you want to do this validation for a XAML-authored <Activity x:Class> thats possible too since its a partial class.

        public partial class Workflow1 : Activity
        {
            protected override void CacheMetadata(ActivityMetadata metadata)
            {
                base.CacheMetadata(metadata);
                if (RequiredProperty == null) metadata.AddValidationError("CustomActivity.RequiredProperty must be set prior to execution");
            }
       })

    Tim
    Friday, February 19, 2010 8:33 AM

All replies

  • Hi Hendrik:
    I guess your code is 

    myActivityObject.TheArgument = new InArgument<MyCustomRefType>(customTypeInstance);

    Right?

    As the exception message said, the literal only supports value types and the immutable type System.String now.

    A work around is using the lambda expression, such as:

    myActivityObject.TheArgument = new InArgument<MyCustomRefType>((env) => customTypeInstance);

    But once you use it, your workflow cannot be serialized/deserialized correctly.

    Hope it is helpful.

    Thanks
    Hong
    Thursday, February 18, 2010 10:24 AM
  • No, I'm not setting it that way. If you read my post again, you'll notice that I'm using a custom editor.
    This means that I'm using it in a designer, which obviously creates Xaml. So I cannot use a lambda expression, because as you've correctly stated, it cannot be serialized!
    So that workaround is definitely not an option. This used to be supported! Why on earth has this been removed?

    Are you telling me that I can't set reference types on activities through custom editors?! Jeez, I hope not.

    Thursday, February 18, 2010 10:35 AM
  • Btw, with custom editor I mean a sub class of DialogPropertyValueEditor

     

    Thursday, February 18, 2010 1:04 PM
  • See also the breaking change notes on the endpoint blog. As well as the option of Lambda expression that don't serialize in XAML (owch), 

    There is an option for you to use VisualBasicValue, and to set a Visual Basic Expression for instantiating the custom type instance.
    There is another option to use classes from System.Activities.Expressions for instantiating the custom type instance.

    I think the last option would involve System.Activities.Expressions.New<MyCustomType>.

    e.g.

    new InArgument(new System.Activities.Expressions.New<MyCustomType>(){ Arguments = /*any needed arguments */})

    You could rephrase the change notes: 'reference types in Workflow are no longer treatable as literals, you actually have to new them'.

    Tim
    Thursday, February 18, 2010 10:16 PM
  • Hi Tim,

    Thanks for your reply.

    I don't mean to be rude, but if you read my original post, you would see that I did in fact mention the endpoint link. I also explained why I can't use a VisualBasicValue<> and I also explained that a DialogPropertyValueEditor creates an instance, and that it is a really complex structure. The dialog that creates this said object is a really complex interface which sets up relationships between several objects.

    I read the breaking changes post on endpoint before I posted on this forum.

    In the endpoint post, two options are listed when working with a reference type:

    1. Use a VisualBasic value - this is not an option - The DialogPropertyValueEditor launches a complex dialog which creates an inticate object with several references to other objects, who in turn have several references to other objects

    2. Use a LambdaExpression - cannot be serialized

    Through my own investigation, I deduced that I can also convert my object to a value type by changing it to a struct - which unfortunately immediately resulted in several issues, because my complex reference type has references to objects with inheritance and such.

    I might try and use the Expressions.New and bring in yet another object, which takes my original object as an argument so that I can construct it in this manner. I'll try and see whether that works. That said, I do not feel that this change makes any sense!

    I feel though, that Microsoft did not think this one through. In the endpoint post, it is mentioned that Microsoft reworked this, because people thought that changing a variable would change it for all instances of that workflow... Firstly, who on earth would be dumb enough to think that? Secondly, why make this horrible change to help these people who doesn't understand workflow. Thirdly, the only way you would be in a position to encounter this misunderstanding, is if you use a code-only workflow! I understand that code only workflows are useful when unit testing (in this case the misunderstanding doesn't really matter in any case), but in real world usages you would use XAML! Why would you want to use WF4 and build workflows in code! Surely you would want to expose an editor to the people that use your custom activities!

    I need to highlight this fact:

    In my experience the most common usage of WF4 is to build custom activities which encapsulates business use cases, so that these can be exposed to business users. These custom activities normally have some sort of interface which allows people to set parameters. The interface is launched and the value set on the activity using a DialogPropertyValueEditor. I do not feel that this is supported anymore since RC. I don't understand why this functionality has been removed to support inexperienced developers who build code only workflows!
    Friday, February 19, 2010 5:10 AM
  • Hi Hendrik,

    I didn't take read your post as carefully as I should, I'm very sorry. In my mind I wasn't sure if you had read the endpoint post but I wanted to link the endpoint post anyway in case other people are reading through the conversation and want to skim to the answer.

    I'm not sure if what you describe as the rationale for the change is entirely on. My understanding is that people thought that changing the value inside of a variable in one workflow will not affect other instances of the workflow, and that it does, was a surprising behavior (although I don't have Beta2 installed any more, making it hard for me to check):

            static void Main(string[] args)
            {
                Sequence s = new Sequence
                {
                    Variables =
                    {
                        new Variable<CustomType>("Fred", new CustomType(1)),
                        new Variable<int>("V1", 0),
                    },
                    Activities =
                    {
                        new Assign<int>
                        {
                            To = new OutArgument<int>(new VisualBasicReference<int>("Fred.Foo()")),
                            Value = new InArgument<int>(new VisualBasicValue<int>("V1")),
                        },
                        new WriteLine
                        {
                            Text = new InArgument<string>(new VisualBasicValue<string>("V1.ToString()"))
                        }
                    }
                };

                WorkflowInvoker.Invoke(s);
                WorkflowInvoker.Invoke(s);
                WorkflowInvoker.Invoke(s);
            }

    In Beta2 such a workflow was creatable in code, but was hard to create in designer as you say (instead usually you would give the Variable<CustomType> a value of VisualBasicValue with an expression that creates a new instance of the variable).


    Back to constructive thinking about the problem - I have the feeling New<> might ultimately work, but be as painful as the VB expression classes to use.

    I am a bit curious about one other thing which I missed on the first reading - is it necessary to use InArgument to pass this configuration instead of using a Property of MyCustomRefType? You mention configuring everything at design time, using the property editor, but are you not always doing the configuration at design time? Is the custom type basically 'write once, then read only?' 'Configure before running workflow?' Or is it something a bit more complex?

    Framework activities avoid a lot of the pains you're having because they use properties for their complex types even such as 'Endpoint', 'CorrelationInitializers' etc. The values are non-reference types, but the values don't really need to change depending on the workflow instance, so they don't need to be InArguments

    Tim

    Friday, February 19, 2010 6:20 AM
  • Is the custom type basically 'write once, then read only?' 'Configure before running workflow?' Or is it something a bit more complex


    Hi Tim.

    Thanks a lot for your response, I can see that you took the time to understand my initial post, and I really appreciate that.
    Yes, you are 100% correct, I do use it as a 'write once' scenario. I expect these values to be serialized to the workflow, and once it's serialized, it's part of the workflow definition.
    The only reason I use InArgument<>, is because I decorate them with the [RequiredArgument] attribute, which validates that a value is specified at design time.

    Perhaps I should try and use a normal property rather, and try to find a way to validate that?
    Friday, February 19, 2010 7:38 AM
  • >Perhaps I should try and use a normal property rather, and try to find a way to validate that?

    Yes, that sounds like a good approach.
    You can add your own custom validation logic fairly easily by overriding the various versions of CacheMetadata (which are variously passed NativeActivityMetadata, ActivityMetadata, etc) to output validation errors.

    (For NativeActivity or CodeActivity mostly straight forward. Just in case you want to do this validation for a XAML-authored <Activity x:Class> thats possible too since its a partial class.

        public partial class Workflow1 : Activity
        {
            protected override void CacheMetadata(ActivityMetadata metadata)
            {
                base.CacheMetadata(metadata);
                if (RequiredProperty == null) metadata.AddValidationError("CustomActivity.RequiredProperty must be set prior to execution");
            }
       })

    Tim
    Friday, February 19, 2010 8:33 AM
  • Thanks, I've already done just that!
    Friday, February 19, 2010 8:53 AM
  • Hi Tim, 

     

    What if it's not 'write once, then read only' case? 

    Let's say I want to let the user modify the value after I loaded the serialized workflow into our custom UI? 

     

    Regards,


    arinto
    Wednesday, February 9, 2011 3:25 AM
  • Hi Arinto,

    That's probably fine. To elaborate:

    There's definitely no problem with writing (configuring) many times, as long as you finished all the writing (configuring) before you run the workflow. It's also OK if you are sure to do it only when the workflow is stopped.

    The main kind of problem you'd potentially see if you change the configuration while executing the workflow is race conditions on how you use the data, not something special the workflow runtime does, as the runtime should only care about workflow instance data (variables, arguments etc) not definitions (properties).

    Tim

    Wednesday, February 9, 2011 6:45 PM
  • I found this thread, but no answer.

    But after some research, I got a solution:

    1)define a custom value class:

     public class ActivityArgumentValue<TResult> : CodeActivity<TResult>
        {      
            public TResult Value { get; set; }

            protected override TResult Execute(CodeActivityContext context)
            {
                return Value;
            }
        }

    2)use this value type to init InArgument:'

       task.TaskForm = new InArgument<TaskFormSetting>(new ActivityArgumentValue<TaskFormSetting>()
                {
                    Value = new TaskFormSetting
                    {
                        EditableFields = new string[] { "F1" }
                    }
                });

    Thursday, December 8, 2016 2:01 AM