abpframework/abp · error · FormatException
Incorrect syntax at char {i}! format string can not contain
Error message
Incorrect syntax at char {i}! format string can not contain nested dynamic value expression! What it means
FormatStringTokenizer.Tokenize splits a format string into ConstantText and DynamicValue tokens, where dynamic segments are delimited by { and }. When a '{' character is encountered while already inside a dynamic segment (inDynamicValue == true), the tokenizer throws FormatException because nested dynamic placeholders are unsupported. This tokenizer powers FormattedStringValueExtracter.Extract, which reverses string.Format to pull named values out of a target string (e.g. format "My name is {name}." matched against "My name is Neo.").
Source
Thrown at framework/src/Volo.Abp.Core/Volo/Abp/Text/Formatting/FormatStringTokenizer.cs:24
internal class FormatStringTokenizer
{
public List<FormatStringToken> Tokenize(string format, bool includeBracketsForDynamicValues = false)
{
var tokens = new List<FormatStringToken>();
var currentText = new StringBuilder();
var inDynamicValue = false;
for (var i = 0; i < format.Length; i++)
{
var c = format[i];
switch (c)
{
case '{':
if (inDynamicValue)
{
throw new FormatException("Incorrect syntax at char " + i + "! format string can not contain nested dynamic value expression!");
}
inDynamicValue = true;
if (currentText.Length > 0)
{
tokens.Add(new FormatStringToken(currentText.ToString(), FormatStringTokenType.ConstantText));
currentText.Clear();
}
break;
case '}':
if (!inDynamicValue)
{
throw new FormatException("Incorrect syntax at char " + i + "! These is no opening brackets for the closing bracket }.");
}
inDynamicValue = false;View on GitHub (pinned to 7ed43b1931)
Solutions
- Flatten the format so each dynamic segment contains only its name and no inner braces: "a{b}" instead of "a{b{c}}".
- If a literal brace is needed in constant text, that is unsupported by this tokenizer — keep braces out of constant text and restructure the template.
- Validate the format string with a brace-balance/nesting check before calling Extract, and reject malformed templates at config load time.
- Wrap Extract in a try/catch(FormatException) to degrade gracefully when the format comes from an untrusted source.
Example fix
// before: nested braces inside a dynamic segment
var r = FormattedStringValueExtracter.Extract(input, "doc-{id{rev}}.pdf");
// throws FormatException at the inner '{'
// after: single, non-nested dynamic segment
var r = FormattedStringValueExtracter.Extract(input, "doc-{id}.pdf"); Defensive patterns
Strategy: try-catch
Validate before calling
static bool IsValidFormatString(string format)
{
if (format.IsNullOrEmpty()) return true;
var depth = 0;
foreach (var c in format)
{
if (c == '{') { if (depth > 0) return false; depth++; }
else if (c == '}') { if (depth == 0) return false; depth--; }
}
return depth == 0;
}
// usage:
if (!IsValidFormatString(format)) { /* reject or sanitize */ } Try / catch
ExtractionResult result;
try
{
result = FormattedStringValueExtracter.Extract(str, format, ignoreCase);
}
catch (FormatException ex)
{
logger.LogDebug(ex, "Malformed format string: {Format}", format);
result = new ExtractionResult(false);
} Prevention
- Treat format templates as code: validate them at load time, not at first user request.
- Remember braces are always dynamic delimiters in this tokenizer — there is no escape; keep literal braces out of templates.
- Prefer named single-segment placeholders ({name}) and avoid any nesting.
- Keep formats in one place (config/constants) so they can be unit-tested with representative inputs.
When it happens
Trigger: Calling FormattedStringValueExtracter.Extract(str, format) (or IsMatch) with a format containing an opening brace inside an already-open dynamic segment: "a{b{c}}", "{x{y}}", or "{0{1}}" (the test case at FormattedStringTokenizer_Test.cs:14). Any second '{' before the matching '}' triggers it.
Common situations: User-supplied or config-driven format templates that accidentally nest placeholders; copying a string.Format numeric-index style ("{0}") into a template and then embedding another token; feeding route-like patterns containing literal braces without realizing braces are always interpreted as dynamic delimiters (there is no escape sequence).
Related errors
- Incorrect syntax at char {i}! These is no opening brackets f
- Incorrect syntax at char {i}! Brackets does not containt any
- There is no closing } char for an opened { char.
- Should specify an option name after '--' prefix!
- Should specify an option name after '-' prefix!
AI-assisted analysis of abpframework/abp@7ed43b1931 (2026-08-13).
Data as JSON: /api/errors/0a8961081b942ad8.
Report an issue: GitHub.