dotnet/maui · error · BuildException

XC0060

XC0060

Error message

Markup expression not closed.

What it means

Thrown by ExpandMarkupsVisitor.ParseExpression when a markup expression string's last character is not '}'. Every XAML markup extension must be wrapped in { ... }; the compiler checks the closing brace first before attempting to parse.

Source

Thrown at src/Controls/src/Build.Tasks/ExpandMarkupsVisitor.cs:88

				return false;
			foreach (var kvp in parentElement.Properties)
			{
				if (kvp.Value != node)
					continue;
				name = kvp.Key;
				return true;
			}
			return false;
		}

		static INode ParseExpression(ref string expression, ILContext context, IXmlNamespaceResolver nsResolver,
			IXmlLineInfo xmlLineInfo)
		{
			if (expression.StartsWith("{}", StringComparison.Ordinal))
				return new ValueNode(expression.Substring(2), null);

			if (expression[expression.Length - 1] != '}')
				throw new BuildException(BuildExceptionCode.MarkupNotClosed, xmlLineInfo, null);

			if (!MarkupExpressionParser.MatchMarkup(out var match, expression, out var len))
				throw new BuildException(BuildExceptionCode.MarkupParsingFailed, xmlLineInfo, null);
			expression = expression.Substring(len).TrimStart();
			if (expression.Length == 0)
				throw new BuildException(BuildExceptionCode.MarkupNotClosed, xmlLineInfo, null);

			var provider = new XamlServiceProvider(null, null);
			provider.Add(typeof(ILContextProvider), new ILContextProvider(context));
			provider.Add(typeof(IXmlNamespaceResolver), nsResolver);
			provider.Add(typeof(IXmlLineInfoProvider), new XmlLineInfoProvider(xmlLineInfo));

			return new MarkupExpansionParser().Parse(match, ref expression, provider);
		}

		class ILContextProvider(ILContext context)
		{
			public ILContext Context { get; } = context;

View on GitHub (pinned to f377ff1c5e)

Solutions

  1. Ensure the expression ends with '}', e.g. "{Binding Path=Foo}".
  2. Use the XAML editor's validation/intellisense to auto-close braces.
  3. Search the file for '{' without a matching '}' to locate the broken expression.

Example fix

// before
<Label Text="{Binding Title" />
// after
<Label Text="{Binding Title}" />
Defensive patterns

Strategy: validation

Validate before calling

// Lint: every '{' markup expression must end with '}'
static bool MarkupIsClosed(string value) {
    if (!value.Contains("{" + "") || value.StartsWith("{}")) return true;
    return value.TrimEnd().EndsWith("}");
}

Prevention

When it happens

Trigger: Writing {Binding Path=Foo (missing closing brace); a stray truncation like Text="{x:Static"; an unterminated expression from a merge conflict or partial edit.

Common situations: Manual editing that deletes the trailing brace; templated/string-generated XAML that omits the brace; copy-paste that cut off the end.

Related errors


AI-assisted analysis of dotnet/maui@f377ff1c5e (2026-08-13). Data as JSON: /api/errors/b4d7c5881825aa67. Report an issue: GitHub.