diff --git a/src/Aspire.Hosting/ApplicationModel/ReferenceExpression.cs b/src/Aspire.Hosting/ApplicationModel/ReferenceExpression.cs
index dcf032ddef..c9ffc41ba6 100644
--- a/src/Aspire.Hosting/ApplicationModel/ReferenceExpression.cs
+++ b/src/Aspire.Hosting/ApplicationModel/ReferenceExpression.cs
@@ -112,7 +112,9 @@ public readonly void AppendLiteral(string value)
/// The formatted string to be appended to the interpolated string.
public readonly void AppendFormatted(string? value)
{
- _builder.Append(value);
+ // The value that comes in is a literal string that is not meant to be interpreted.
+ // But the _builder later gets treated as a format string, so we just need to escape the braces.
+ _builder.Append(value?.Replace("{", "{{").Replace("}", "}}"));
}
///
diff --git a/tests/Aspire.Hosting.Tests/ReferenceExpressionTests.cs b/tests/Aspire.Hosting.Tests/ReferenceExpressionTests.cs
new file mode 100644
index 0000000000..1d2e84d93a
--- /dev/null
+++ b/tests/Aspire.Hosting.Tests/ReferenceExpressionTests.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Xunit;
+
+namespace Aspire.Hosting.Tests;
+public class ReferenceExpressionTests
+{
+ [Theory]
+ [InlineData("world", "Hello world", "Hello world")]
+ [InlineData("{", "Hello {{", "Hello {")]
+ [InlineData("}", "Hello }}", "Hello }")]
+ [InlineData("{1}", "Hello {{1}}", "Hello {1}")]
+ [InlineData("{x}", "Hello {{x}}", "Hello {x}")]
+ [InlineData("{{x}}", "Hello {{{{x}}}}", "Hello {{x}}")]
+ public void TestReferenceExpressionCreateInputStringTreatedAsLiteral(string input, string expectedFormat, string expectedExpression)
+ {
+ var refExpression = ReferenceExpression.Create($"Hello {input}");
+ Assert.Equal(expectedFormat, refExpression.Format);
+
+ // Generally, the input string should end up unchanged in the expression, since it's a literal
+ var expr = refExpression.ValueExpression;
+ Assert.Equal(expectedExpression, expr);
+ }
+}