Fix #624, #704, #711, #714, #717: action exception propagation, list schema merging, OutputExpression error hint, global-param dedup, object-return diagnostics#728
Merged
Conversation
added 2 commits
May 27, 2026 16:17
…soft#624: bug bash batch microsoft#624: ActionBase.ExecuteAndReturnResultAsync silently captured every exception into result.Exception regardless of EnableExceptionAsErrorMessage, so an action throwing inside Run() never propagated when the setting was false. Add ThrowIfActionExceptionShouldPropagate in RulesEngine to honor the contract documented on ReSettings. microsoft#711: OutputExpressionAction emitted a cryptic "Expression is missing an 'as' clause" when users wrote C#-style anonymous objects (`new { X = ... } as Result`). Detect the pattern and wrap the parse exception with a clear hint pointing at the Dynamic.Core syntax (`new (value as Name, ...)`). microsoft#714: Global params were re-evaluated for every rule in the workflow, so `Utils.FromDb(myInput)` ran N times per ExecuteAllRulesAsync call. Move the global-params compilation+evaluation to workflow scope: compile a single delegate at RegisterRule time, store it in RulesCache, evaluate once in ExecuteAllRuleByWorkflow and append the result as RuleParameters to each compiled rule. Preserve the existing per-rule error messages when global compilation or evaluation fails. ExecuteActionWorkflowAsync (which bypasses RulesCache) evaluates globals ad-hoc. microsoft#704: Utils.CreateAbstractClassType used only list[0] when generating the CLR type for a heterogeneous IList of ExpandoObject/Dictionary, so any property appearing only in later elements was dropped. Walk every element and union the schema; recursively merge nested dictionaries. microsoft#717: Methods declared to return `object` cannot have their result members accessed in Dynamic.Core expressions — the parser errors with "exists in type 'Object'" or "is not defined for the types 'System.Object'". We can't unbox at parse time, but the error message gave no clue what was wrong. Detect those patterns in LambdaExpressionBuilder and append a hint to change the method's return type to the concrete class. All 144 tests pass on net6.0 / net8.0 / net9.0 / net10.0.
- RulesEngine.cs: ApplyGlobalParams and EvaluateGlobalsAdHoc both built RuleParameters from a globals delegate and concatenated them with the user's inputs. Extracted AppendGlobals (delegate-invoke + concat tail) and CompileGlobalParamsDelegate (GetRuleExpressionParameters + CompileScopedParams). - Utils.cs: MergeListElementSchemas and MergeTwoDictionaries shared the same pair-merge logic. Replaced both with MergeDictionaries (n-ary fold) + MergeValues (handles dict/dict, list/list, and first-non-null fallback). The latter also makes nested list-concatenation consistent across all call sites. No behavior change. All 144 tests still pass on net6/8/9/10.
sagarambilpure
approved these changes
May 27, 2026
YogeshPraj
added a commit
that referenced
this pull request
May 27, 2026
Bump <Version> from 6.0.0 to 6.0.1-preview.1 and add a CHANGELOG entry covering the three PRs landed since 6.0.0: #727 (perf cache restore), #728 (action-exception propagation, list schema union, OutputExpression hint, global-param dedup, object-return diagnostics), and #729 (ExecuteActionWorkflowAsync FormatErrorMessages, ActionContext null guard, deep ErrorMessage interpolation, plus regression guards for #581, #590, #606, #608). Co-authored-by: Yogesh Prajapati <yogeshcprajapati@outlook.com>
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Bundles five independent bug fixes that came out of the issue triage following #727. Each fix has a dedicated regression test file; details below.
Fixes #624 —
EnableExceptionAsErrorMessage = falsesilently suppressed action exceptionsActionBase.ExecuteAndReturnResultAsynccatches every exception thrown fromRun()and stuffs it intoActionRuleResult.Exception, which the engine then quietly stores on the result tree. WithEnableExceptionAsErrorMessage = false, the docs say the engine should throw — but action-level exceptions never propagated.Added
ThrowIfActionExceptionShouldPropagateinRulesEngine.cs. After the action result is captured, ifEnableExceptionAsErrorMessage == false && IgnoreException == falseand the action produced an exception, we re-throw. BothExecuteAllRulesAsyncandExecuteActionWorkflowAsyncpaths now honor the contract.Fixes #711 — Cryptic error for C#-style anonymous objects in
OutputExpressionUsers frequently write
"new { State = input.state, ... } as Result"(C# syntax), and Dynamic.Core responds with"Expression is missing an 'as' clause". The correct Dynamic.Core form is"new (input.state as State, ...)".OutputExpressionAction.Runnow catchesParseExceptionwhen the expression matches\bnew\s*\{and wraps it with a clear hint about the required syntax.Fixes #714 — Global params evaluated once per rule instead of once per workflow execution
RuleCompiler.GetWrappedRuleFuncwrapped each rule's compiled delegate with a per-invocation re-evaluation of all global+local scoped params. AUtils.FromDb(...)call inGlobalParamstherefore ran once for every rule in the workflow.Refactor:
RuleCompiler.CompileRuleno longer wraps with globals — it returns the inner delegate that expects globals as part ofRuleParameter[].RulesEngine.RegisterRuleeagerly evaluates global expressions, compiles a singleFunc<object[], Dictionary<string, object>>, and stores it in a new per-workflow slot onRulesCache.ExecuteAllRuleByWorkflowinvokes that delegate once with the user inputs, appends the result as extraRuleParameters, and passes the extended array to each compiled rule."Error while compiling rule …"/"Error while executing scoped params for rule …"), preserving the contract that existing tests rely on.ExecuteActionWorkflowAsync(which bypassesRulesCache) evaluates globals ad-hoc.Fixes #704 —
Utils.CreateAbstractClassTypelost properties that only appeared in later list elementsCreateAbstractClassTypeandCreateAbstractClassTypeFromDictionaryboth inferred the CLR element type for anIListfromlist[0]only, so any property that appeared first in a later element was silently dropped duringCreateObject/CreateObjectFromDictionary.Added a
BuildListTypehelper that walks every element and unions their schemas viaMergeListElementSchemas+MergeTwoDictionaries. Nested dictionaries are recursively merged. Non-schema-like elements (primitives, strings) keep the previous first-element behavior.Fixes #717 — No diagnostic when a custom method returns
objectDynamic.Core fails with
"No property or field 'X' exists in type 'Object'"or"is not defined for the types 'System.Object' and ..."when a custom/static method's declared return type isobject, because the parser only has the static signature. We can't unbox at parse time, but we can tell the user what's wrong.LambdaExpressionBuilder.BuildDelegateForRuledetects these patterns inex.Messageand appends a hint telling the user to change the method's return type to the concrete class.Test plan
Issue624Test,Issue711Test,Issue714Test,Issue704Test,Issue717Test) — 13 new tests total, all covering both the bug repro and the regression guard.Note on what's deliberately out of scope