3

Resolved

DecisionActivity does not supply tokens to C# scripting

description

Orchard.Scripting.CSharp.Activities.DecisionActivity.Execute() does not supply tokens to C# scripting.
        public override IEnumerable<LocalizedString> Execute(WorkflowContext workflowContext, ActivityContext activityContext) {
            var script = activityContext.GetState<string>("Script");
            object outcome = null;

            // TODO: Commit to Orchard.
            foreach (var token in workflowContext.Tokens) {
                _csharpService.SetParameter(token.Key, (dynamic)token.Value);
            }

            _csharpService.SetParameter("Services", _orchardServices);
            _csharpService.SetParameter("ContentItem", (dynamic)workflowContext.Content.ContentItem);
            _csharpService.SetParameter("WorkContext", _workContextAccessor.GetContext());
            _csharpService.SetFunction("T", (Func<string, string>)(x => T(x).Text));
            _csharpService.SetFunction("SetOutcome", (Action<object>)(x => outcome = x));

            _csharpService.Run(script);

            yield return T(Convert.ToString(outcome));
        }
We are trying to create a custom form that will send an e-mail to the administrator. We want (or actually our client wants) to include a checkbox-field on the form. If the checkbox is checked, we want to send an e-mail to the person who filled in the form.

So we created:
  • Custom Form Submitted event (chose our custom form)
  • Decision activity (pasted the script below)
  • Configured 2 outcomes: True/False.
  • Script:
// #{}
if (Content.InformatieForm.EmailCopy.Value == true) {
SetOutcome("True");
} else {
SetOutcome("False");
}
However, without the foreach-loop, we cannot use "Content" in the script.

If we do not use the foreach-loop, there is no way we can get to the just-filled-in field.

Also, there's a problem with {} and #{}. For some reason, the code checks wether it should use #{} or {} as tokens. If we do not place // #{} on top, we see the bodies of the if and else statement choked. It treats it as a token, for which it has no value, so it replaces it with "" (empty string).

Then, the script to execute becomes:
if (Content.InformatieForm.EmailCopy.Value == true) else

comments

mjy78 wrote Mar 2 at 6:14 AM

I believe tokens do get passed to the script without the need for the above core modification.

There's a few things to consider here....
  1. Is it possible that the "ContentItem" parameter is only populated if your workflow is based one of the "Content Created" or "Content Published", etc style activities? If you've used the "Form Submitted" activity, then maybe this parameter isn't necessarily populated. If you have your form configured to save a content item and your activity was based off the "Content Created" activity, then I would expect that you should be able to access your field via the Content parameter.
  2. If you are using the "Form Submitted" activity, then in your decision script you can access the tokens via the #{Request.Form:FormName.FieldName.Value} token syntax. This also has the benefit of ensuring your {} conditional brackets are not treated as tokens, because the tokeniser looks for the existence of "#{" and if found it expects all tokens to be prefixed with # otherwise it expects all tokens to be simply surrounded by {}. This is done for backwards compatibility, but does mean that if you wish to use {} brackets in your script that you need to ensure "#{" exists somewhere in your script.
The following works fine for me....
if ("#{Request.Form:InformatieForm.EmailCopy.Value}".Contains("true")) {
  SetOutcome("True");
}
else {
  SetOutcome("False");
}
  1. The thing that you will notice about the above code, is that to check if the EmailCopy Field is set to true, I check if it contains the string "true". This is done because the Request.Form values are all strings and also the MVC checkbox helper (in case your field is a boolean checkbox field) will actually send "true,false" when the checkbox is checked and "false" when the checkbox is not checked, because it generates a hidden field with the same name as the checkbox and value false. More background on that is available here: http://stackoverflow.com/questions/5936048/why-html-checkboxvisible-returns-true-false-in-asp-net-mvc-2

sebastienros wrote Mar 22 at 1:57 AM

Fixed in changeset 24f9225361f42de6914c13ce59ff5438b2f7438d

sebastienros wrote Mar 22 at 1:59 AM

Please use this syntax now:
if(ContentItem.InformatieForm.EmailCopy.Value == true) {
  SetOutcome("Checked");
}
The tokens are still not included, for this use the tokens syntax, but ContentItem represents the submitted content item in the case of a submitted form.

Also there was a bug in the BooleanField which prevented its value from being assigned in this context.

hkui wrote Mar 25 at 8:11 AM

Sebastienros: There's something wrong in your fix:
             public override IEnumerable<LocalizedString> Execute(WorkflowContext workflowContext, ActivityContext activityContext) {
                 var script = activityContext.GetState<string>("Script");
      
                 script = "// #{ }" + System.Environment.NewLine;
      
                 object outcome = null;
      
                 _csharpService.SetParameter("Services", _orchardServices);
You're overwriting the script with a comment. I guess it should be:
                 script = "// #{ }" + System.Environment.NewLine + script;

mjy78 wrote Mar 25 at 12:34 PM

It may not be a huge concern, but have you considered that your change could break backwards compatibility for any users that still have decision workflows in use with the old token syntax?

For example someone may have a decision in place that is something like...
SetOutcome("{SomeKindOfToken}" == "SomeValue" ? "This" : "That")
Will you be making a point in release documentation somewhere that the deprecated {token} (without the #p prefix) syntax no longer works at all with decision activities, while it remains in place in other areas of the code base that tokens are used?

hkui wrote Mar 25 at 1:28 PM

Also, the fix is quite nasty. Why not just a parameter to Run()?

mjy78 wrote Mar 25 at 2:17 PM

I am thinking this problem would go away if the token parser outputted the unchanged token when it didn't match a valid token, instead of outputting nothing.

If this was the case, when the token parser detected brackets within a decision activity that were actually part of a script, it would fail to match a token, and leave that section of script unchanged.

This behaviour might also be advantageous in other places where tokens are used. For example in an email notification, when testing, invalid tokens would be more obvious if left in the output instead of being cleared.

hkui wrote Mar 25 at 2:39 PM

I agree with that.

But I do think there may be cases where it's actually advantageous to have it replace unknown tokens with string.empty. but I think we could have a workaround for those cases.

sebastienros wrote Mar 26 at 12:01 AM

Thanks for pointing the issue, fixing it