{"record":{"id":"fe54ceddc1103b3e","repo":"dotnet/yarp","slug":"string-join-transformvalues-keys","errorCode":null,"errorMessage":"{string.Join(\";\", transformValues.Keys)}","messagePattern":"\\{string\\.Join\\(\";\", transformValues\\.Keys\\)\\}","errorType":"validation","errorClass":"NotSupportedException","httpStatus":null,"severity":"error","filePath":"src/ReverseProxy/Transforms/QueryTransformFactory.cs","lineNumber":63,"sourceCode":"        return true;\n    }\n\n    public bool Build(TransformBuilderContext context, IReadOnlyDictionary<string, string> transformValues)\n    {\n        if (transformValues.TryGetValue(QueryValueParameterKey, out var queryValueParameter))\n        {\n            TransformHelpers.CheckTooManyParameters(transformValues, expected: 2);\n            if (transformValues.TryGetValue(AppendKey, out var appendValue))\n            {\n                context.AddQueryValue(queryValueParameter, appendValue, append: true);\n            }\n            else if (transformValues.TryGetValue(SetKey, out var setValue))\n            {\n                context.AddQueryValue(queryValueParameter, setValue, append: false);\n            }\n            else\n            {\n                throw new NotSupportedException(string.Join(\";\", transformValues.Keys));\n            }\n        }\n        else if (transformValues.TryGetValue(QueryRouteParameterKey, out var queryRouteParameter))\n        {\n            TransformHelpers.CheckTooManyParameters(transformValues, expected: 2);\n            if (transformValues.TryGetValue(AppendKey, out var routeValueKeyAppend))\n            {\n                context.AddQueryRouteValue(queryRouteParameter, routeValueKeyAppend, append: true);\n            }\n            else if (transformValues.TryGetValue(SetKey, out var routeValueKeySet))\n            {\n                context.AddQueryRouteValue(queryRouteParameter, routeValueKeySet, append: false);\n            }\n            else\n            {\n                throw new NotSupportedException(string.Join(\";\", transformValues.Keys));\n            }\n        }","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/dotnet/yarp/blob/bd11867bee7df522e7fd3effb08a9c85fd616908/src/ReverseProxy/Transforms/QueryTransformFactory.cs#L45-L81","documentation":"Thrown by QueryTransformFactory.Build when a query-string transform entry declares a 'QueryValueParameter' key but includes neither an 'Append' nor a 'Set' sub-key. The factory can only produce a meaningful transform when one of those two value keys is present, so it hard-stops with a NotSupportedException listing the actual keys found. This is the Build-time guard; the sibling Validate method reports the same condition non-throwing during config loading.","triggerScenarios":"A transform dictionary passed to Build that contains { \"QueryValueParameter\": \"foo\" } with no \"Append\" or \"Set\" entry. Happens when calling TransformBuilderContext/ITransformFactory.Build directly in code, or when a malformed transform slips past startup config validation (e.g. a custom config provider, or config validation disabled).","commonSituations":"Typo'd key in appsettings.json such as 'Appendd' or 'Sett' instead of 'Append'/'Set'; a copy-pasted transform block where the Set/Append line was accidentally deleted; a dynamic/config-database-driven transform store that emits a partial entry.","solutions":["Add either an \"Append\" or \"Set\" key to the transform entry, e.g. { \"QueryValueParameter\": \"foo\", \"Set\": \"bar\" }.","If the key name was a typo, correct it to exactly \"Append\" or \"Set\" (case-sensitive) and rebuild.","If you are building transforms programmatically, double-check the IReadOnlyDictionary<string,string> you hand to Build contains exactly the expected keys.","Ensure startup config validation is enabled so this is caught as a validation error (non-throwing) rather than reaching Build at runtime."],"exampleFix":"// before (appsettings.json)\n{ \"Transforms\": [ { \"QueryValueParameter\": \"q\" } ] }\n// after\n{ \"Transforms\": [ { \"QueryValueParameter\": \"q\", \"Set\": \"value\" } ] }","handlingStrategy":"validation","validationCode":"// Before building, ensure a QueryValueParameter transform has Append or Set\nif (transformValues.ContainsKey(\"QueryValueParameter\")\n    && !transformValues.ContainsKey(\"Append\")\n    && !transformValues.ContainsKey(\"Set\"))\n{\n    throw new InvalidOperationException(\n        \"QueryValueParameter transform requires an 'Append' or 'Set' key.\");\n}","typeGuard":"// Validate a transform entry before passing it to the factory\nstatic bool IsValidQueryValueTransform(IReadOnlyDictionary<string, string> t)\n    => t.ContainsKey(\"QueryValueParameter\")\n       && (t.ContainsKey(\"Append\") || t.ContainsKey(\"Set\"));","tryCatchPattern":null,"preventionTips":["Keep YARP startup config validation enabled so malformed transforms fail at startup, not at runtime Build.","When generating transforms from a database/config store, unit-test the generator against expected key sets.","Use the documented transform schema (QueryValueParameter + Append/Set) as the single source of truth."],"tags":["yarp","config","transforms","query-string"],"backgroundTag":null,"analyzedSha":"bd11867bee7df522e7fd3effb08a9c85fd616908","analyzedAt":"2026-08-13T21:29:49.359Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}