diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorIntermediateNodeWriter.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorIntermediateNodeWriter.cs
index 65ed3cd485..5f921bcfbe 100644
--- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorIntermediateNodeWriter.cs
+++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorIntermediateNodeWriter.cs
@@ -12,6 +12,7 @@ using AngleSharp.Parser.Html;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
+using Microsoft.CodeAnalysis.CSharp;
namespace Microsoft.AspNetCore.Blazor.Razor
{
@@ -27,6 +28,8 @@ namespace Microsoft.AspNetCore.Blazor.Razor
new[] { "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr" },
StringComparer.OrdinalIgnoreCase);
private readonly static Regex bindExpressionRegex = new Regex(@"^bind\((.+)\)$");
+ private readonly static CSharpParseOptions bindArgsParseOptions
+ = CSharpParseOptions.Default.WithKind(CodeAnalysis.SourceCodeKind.Script);
private readonly ScopeStack _scopeStack = new ScopeStack();
private string _unconsumedHtml;
@@ -323,24 +326,31 @@ namespace Microsoft.AspNetCore.Blazor.Razor
var bindMatch = bindExpressionRegex.Match(token.AttributeValue.Content);
if (bindMatch.Success)
{
- // The @bind(X) syntax is special. We convert it to a pair of attributes:
- // [1] value=@X
- var valueExpression = bindMatch.Groups[1].Value;
- WriteAttribute(context.CodeWriter, "value",
- new IntermediateToken { Kind = TokenKind.CSharp, Content = valueExpression });
+ // TODO: Consider alternatives to the @bind syntax. The following is very strange.
- // [2] @onchange(newValue => { X = newValue; })
- var isCheckbox = tag.Data.Equals("input", StringComparison.OrdinalIgnoreCase)
- && tag.Attributes.Any(a =>
- a.Key.Equals("type", StringComparison.Ordinal)
- && a.Value.Equals("checkbox", StringComparison.Ordinal));
- var castToType = isCheckbox ? "bool" : "string";
+ // The @bind(X, Y, Z, ...) syntax is special. We convert it to a pair of attributes:
+ // [1] value=@BindMethods.GetValue(X, Y, Z, ...)
+ var valueParams = bindMatch.Groups[1].Value;
+ WriteAttribute(context.CodeWriter, "value", new IntermediateToken
+ {
+ Kind = TokenKind.CSharp,
+ Content = $"{RenderTreeBuilder.BindMethodsGetValue}({valueParams})"
+ });
+
+ // [2] @onchange(BindSetValue(parsed => { X = parsed; }, X, Y, Z, ...))
+ var parsedArgs = CSharpSyntaxTree.ParseText(valueParams, bindArgsParseOptions);
+ var parsedArgsSplit = parsedArgs.GetRoot().ChildNodes().Select(x => x.ToString()).ToList();
+ if (parsedArgsSplit.Count > 0)
+ {
+ parsedArgsSplit.Insert(0, $"_parsedValue_ => {{ {parsedArgsSplit[0]} = _parsedValue_; }}");
+ }
+ var parsedArgsJoined = string.Join(", ", parsedArgsSplit);
var onChangeAttributeToken = new PendingAttributeToken
{
AttributeValue = new IntermediateToken
{
Kind = TokenKind.CSharp,
- Content = $"onchange(_newValue_ => {{ {valueExpression} = ({castToType})_newValue_; }})"
+ Content = $"onchange({RenderTreeBuilder.BindMethodsSetValue}({parsedArgsJoined}))"
}
};
WriteElementAttributeToken(context, tag, onChangeAttributeToken);
diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/RenderTreeBuilder.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/RenderTreeBuilder.cs
index 00742c7f34..398c5ad064 100644
--- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/RenderTreeBuilder.cs
+++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/RenderTreeBuilder.cs
@@ -24,5 +24,9 @@ namespace Microsoft.AspNetCore.Blazor.Razor
public static readonly string GetFrames = nameof(GetFrames);
public static readonly string ChildContent = nameof(ChildContent);
+
+ public static readonly string BindMethodsGetValue = "Microsoft.AspNetCore.Blazor.Components.BindMethods.GetValue";
+
+ public static readonly string BindMethodsSetValue = "Microsoft.AspNetCore.Blazor.Components.BindMethods.SetValue";
}
}
diff --git a/src/Microsoft.AspNetCore.Blazor/Components/BindMethods.cs b/src/Microsoft.AspNetCore.Blazor/Components/BindMethods.cs
new file mode 100644
index 0000000000..e90d6e84fc
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Blazor/Components/BindMethods.cs
@@ -0,0 +1,75 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Globalization;
+
+namespace Microsoft.AspNetCore.Blazor.Components
+{
+ ///
+ /// Methods used internally by @bind syntax. Not intended to be used directly.
+ ///
+ public static class BindMethods
+ {
+ ///
+ /// Not intended to be used directly.
+ ///
+ public static T GetValue(T value) => value;
+
+ ///
+ /// Not intended to be used directly.
+ ///
+ public static string GetValue(DateTime value, string format) =>
+ value == default ? null
+ : (format == null ? value.ToString() : value.ToString(format));
+
+ ///
+ /// Not intended to be used directly.
+ ///
+ public static Action