abpframework/abp · error · FormatException

There is no closing } char for an opened { char.

Error message

There is no closing } char for an opened { char.

What it means

After the tokenizer loop finishes scanning every character, it checks whether inDynamicValue is still true. If so, an opening '{' was never closed by a '}', and it throws FormatException for the unterminated dynamic segment. Used by FormattedStringValueExtracter.Extract/IsMatch.

Source

Thrown at framework/src/Volo.Abp.Core/Volo/Abp/Text/Formatting/FormatStringTokenizer.cs:67

                    var dynamicValue = currentText.ToString();
                    if (includeBracketsForDynamicValues)
                    {
                        dynamicValue = "{" + dynamicValue + "}";
                    }

                    tokens.Add(new FormatStringToken(dynamicValue, FormatStringTokenType.DynamicValue));
                    currentText.Clear();

                    break;
                default:
                    currentText.Append(c);
                    break;
            }
        }

        if (inDynamicValue)
        {
            throw new FormatException("There is no closing } char for an opened { char.");
        }

        if (currentText.Length > 0)
        {
            tokens.Add(new FormatStringToken(currentText.ToString(), FormatStringTokenType.ConstantText));
        }

        return tokens;
    }
}

View on GitHub (pinned to 7ed43b1931)

Solutions

  1. Close the dynamic segment: "a{b}" instead of "a{b".
  2. Validate brace balance (equal counts and correct open/close ordering) before invoking Extract.
  3. Catch FormatException around Extract for untrusted format input and treat it as a non-match.

Example fix

// before: opening brace never closed
var r = FormattedStringValueExtracter.Extract(input, "user-{id");
// throws FormatException: no closing } for the opened {

// after: close the dynamic segment
var r = FormattedStringValueExtracter.Extract(input, "user-{id}");
Defensive patterns

Strategy: try-catch

Validate before calling

static bool IsBraceBalanced(string format)
{
    if (format.IsNullOrEmpty()) return true;
    var depth = 0;
    foreach (var c in format)
    {
        if (c == '{') depth++;
        else if (c == '}') { if (depth == 0) return false; depth--; }
    }
    return depth == 0; // false when an opening brace was never closed
}

if (!IsBraceBalanced(format)) { /* reject template */ }

Try / catch

try
{
    result = FormattedStringValueExtracter.Extract(str, format);
}
catch (FormatException)
{
    result = new ExtractionResult(false);
}

Prevention

When it happens

Trigger: Calling FormattedStringValueExtracter.Extract(str, format) with a format whose last dynamic segment has no closing brace: "a{b", "name-{id", or "{unclosed" (test case at FormattedStringTokenizer_Test.cs:13 tokenizes "a sample { wrong format").

Common situations: Truncated template strings; formats assembled by string concatenation that dropped the trailing '}; user-edited config that accidentally deleted a brace; copy-paste that cut off the end of a template.

Related errors


AI-assisted analysis of abpframework/abp@7ed43b1931 (2026-08-13). Data as JSON: /api/errors/d4bd4dd12f5a922b. Report an issue: GitHub.