Skip to content
This repository was archived by the owner on Feb 25, 2021. It is now read-only.

Commit 4407de1

Browse files
committed
Replace @Bind with bind-...
This change introduces a 'tag helper' that replaces @Bind with custom code generation that accomplishes roughly the same thing. This feature lights up by dynamically generating tag helpers that are visible to tooling and affect the code generation based on: - pattern recognition of component properties - attributes that create definitions for elements - a 'fallback' case for elements 'bind' also supports format strings (currently only for DateTime) via a separate attribute. This change introduces the basic framework for bind and tooling support. We know that we'll have to do more work to define the set of default 'bind' cases for the DOM and to flesh out the conversion/formatting infrastructure. This change gets us far enough to replace all of the cases we currently have tests for :) with the new features. The old @Bind technique still works for now. Examples: @* bind an input element to an expression *@ <input bind="@SelectedDate" format="mm/dd/yyyy" /> @functions { public DateTime SelectedDate { get; set; } } @* bind an arbitrary expression to an arbitrary set of attributes *@ <div bind-myvalue-myevent="@SomeExpression">...</div> @* write a component that supports bind *@ @* in Counter.cshtml *@ <div>...html omitted for brevity...</div> @functions { public int Value { get; set; } = 1; public Action<int> ValueChanged { get; set; } } @* in another file *@ <Counter bind-Value="@CurrentValue" /> @functions { public int CurrentValue { get; set; } }
1 parent e4cd94d commit 4407de1

26 files changed

+2879
-67
lines changed

src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BindLoweringPass.cs

Lines changed: 418 additions & 0 deletions
Large diffs are not rendered by default.

src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BindTagHelperDescriptorProvider.cs

Lines changed: 491 additions & 0 deletions
Large diffs are not rendered by default.

src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorApi.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ namespace Microsoft.AspNetCore.Blazor.Razor
77
// Keep these in sync with the actual definitions
88
internal static class BlazorApi
99
{
10+
public static readonly string AssemblyName = "Microsoft.AspNetCore.Blazor";
11+
1012
public static class BlazorComponent
1113
{
1214
public static readonly string FullTypeName = "Microsoft.AspNetCore.Blazor.Components.BlazorComponent";
@@ -64,13 +66,27 @@ public static class RouteAttribute
6466
public static readonly string FullTypeName = "Microsoft.AspNetCore.Blazor.Components.RouteAttribute";
6567
}
6668

69+
public static class BindElementAttribute
70+
{
71+
public static readonly string FullTypeName = "Microsoft.AspNetCore.Blazor.Components.BindElementAttribute";
72+
}
73+
74+
public static class BindInputElementAttribute
75+
{
76+
public static readonly string FullTypeName = "Microsoft.AspNetCore.Blazor.Components.BindInputElementAttribute";
77+
}
78+
6779
public static class BindMethods
6880
{
81+
public static readonly string FullTypeName = "Microsoft.AspNetCore.Blazor.Components.BindMethods";
82+
6983
public static readonly string GetValue = "Microsoft.AspNetCore.Blazor.Components.BindMethods.GetValue";
7084

7185
public static readonly string SetValue = "Microsoft.AspNetCore.Blazor.Components.BindMethods.SetValue";
86+
87+
public static readonly string SetValueHandler = "Microsoft.AspNetCore.Blazor.Components.BindMethods.SetValueHandler";
7288
}
73-
89+
7490
public static class UIEventHandler
7591
{
7692
public static readonly string FullTypeName = "Microsoft.AspNetCore.Blazor.UIEventHandler";

src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDiagnosticFactory.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Collections.Generic;
56
using System.IO;
67
using System.Linq;
@@ -88,5 +89,21 @@ public static RazorDiagnostic CreatePageDirective_MustSpecifyRoute(SourceSpan? s
8889
var diagnostic = RazorDiagnostic.Create(PageDirective_MustSpecifyRoute, source ?? SourceSpan.Undefined);
8990
return diagnostic;
9091
}
92+
93+
public static readonly RazorDiagnosticDescriptor BindAttribute_Duplicates =
94+
new RazorDiagnosticDescriptor(
95+
"BL9989",
96+
() => "The attribute '{0}' was matched by multiple bind attributes. Duplicates:{1}",
97+
RazorDiagnosticSeverity.Error);
98+
99+
public static RazorDiagnostic CreateBindAttribute_Duplicates(SourceSpan? source, string attribute, ComponentAttributeExtensionNode[] attributes)
100+
{
101+
var diagnostic = RazorDiagnostic.Create(
102+
BindAttribute_Duplicates,
103+
source ?? SourceSpan.Undefined,
104+
attribute,
105+
Environment.NewLine + string.Join(Environment.NewLine, attributes.Select(p => p.TagHelper.DisplayName)));
106+
return diagnostic;
107+
}
91108
}
92109
}

src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorExtensionInitializer.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,17 @@ public static void Register(RazorProjectEngineBuilder builder)
6565

6666
builder.Features.Add(new ConfigureBlazorCodeGenerationOptions());
6767

68+
// Implementation of components
6869
builder.Features.Add(new ComponentDocumentClassifierPass());
6970
builder.Features.Add(new ComplexAttributeContentPass());
7071
builder.Features.Add(new ComponentLoweringPass());
71-
7272
builder.Features.Add(new ComponentTagHelperDescriptorProvider());
7373

74+
// Implementation of bind
75+
builder.Features.Add(new BindLoweringPass());
76+
builder.Features.Add(new BindTagHelperDescriptorProvider());
77+
builder.Features.Add(new OrphanTagHelperLoweringPass());
78+
7479
if (builder.Configuration.ConfigurationName == DeclarationConfiguration.ConfigurationName)
7580
{
7681
// This is for 'declaration only' processing. We don't want to try and emit any method bodies during
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.AspNetCore.Blazor.Razor
5+
{
6+
// Metadata used for Blazor's interations with the tag helper system
7+
internal static class BlazorMetadata
8+
{
9+
// There's a bug in the 15.7 preview 1 Razor that prevents 'Kind' from being serialized
10+
// this affects both tooling and build. For now our workaround is to ignore 'Kind' and
11+
// use our own metadata entry to denote non-Component tag helpers.
12+
public static readonly string SpecialKindKey = "Blazor.IsSpecialKind";
13+
14+
public static class Bind
15+
{
16+
public static readonly string RuntimeName = "Blazor.None";
17+
18+
public readonly static string TagHelperKind = "Blazor.Bind-0.1";
19+
20+
public readonly static string FallbackKey = "Blazor.Bind.Fallback";
21+
22+
public readonly static string TypeAttribute = "Blazor.Bind.TypeAttribute";
23+
24+
public readonly static string ValueAttribute = "Blazor.Bind.ValueAttribute";
25+
26+
public readonly static string ChangeAttribute = "Blazor.Bind.ChangeAttribute";
27+
}
28+
29+
public static class Component
30+
{
31+
public static readonly string DelegateSignatureKey = "Blazor.DelegateSignature";
32+
33+
public static readonly string RuntimeName = "Blazor.IComponent";
34+
35+
public readonly static string TagHelperKind = "Blazor.Component-0.1";
36+
37+
}
38+
}
39+
}

src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentAttributeExtensionNode.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,31 @@ public ComponentAttributeExtensionNode(TagHelperPropertyIntermediateNode propert
6161
}
6262
}
6363

64+
public ComponentAttributeExtensionNode(ComponentAttributeExtensionNode attributeNode)
65+
{
66+
if (attributeNode == null)
67+
{
68+
throw new ArgumentNullException(nameof(attributeNode));
69+
}
70+
71+
AttributeName = attributeNode.AttributeName;
72+
AttributeStructure = attributeNode.AttributeStructure;
73+
BoundAttribute = attributeNode.BoundAttribute;
74+
PropertyName = attributeNode.BoundAttribute.GetPropertyName();
75+
Source = attributeNode.Source;
76+
TagHelper = attributeNode.TagHelper;
77+
78+
for (var i = 0; i < attributeNode.Children.Count; i++)
79+
{
80+
Children.Add(attributeNode.Children[i]);
81+
}
82+
83+
for (var i = 0; i < attributeNode.Diagnostics.Count; i++)
84+
{
85+
Diagnostics.Add(attributeNode.Diagnostics[i]);
86+
}
87+
}
88+
6489
public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();
6590

6691
public string AttributeName { get; set; }

src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentLoweringPass.cs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using System.Collections.Generic;
54
using System.Linq;
65
using Microsoft.AspNetCore.Razor.Language;
7-
using Microsoft.AspNetCore.Razor.Language.Extensions;
86
using Microsoft.AspNetCore.Razor.Language.Intermediate;
97

108
namespace Microsoft.AspNetCore.Blazor.Razor
119
{
1210
internal class ComponentLoweringPass : IntermediateNodePassBase, IRazorOptimizationPass
1311
{
12+
// Run after our *special* tag helpers get lowered.
13+
public override int Order => 1000;
14+
1415
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
1516
{
1617
var @namespace = documentNode.FindPrimaryNamespace();
@@ -26,20 +27,28 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentInte
2627
var nodes = documentNode.FindDescendantNodes<TagHelperIntermediateNode>();
2728
for (var i = 0; i < nodes.Count; i++)
2829
{
30+
var count = 0;
2931
var node = nodes[i];
30-
if (node.TagHelpers.Count > 1)
32+
for (var j = 0; j < node.TagHelpers.Count; j++)
3133
{
32-
node.Diagnostics.Add(BlazorDiagnosticFactory.Create_MultipleComponents(node.Source, node.TagName, node.TagHelpers));
33-
}
34+
if (node.TagHelpers[j].IsComponentTagHelper())
35+
{
36+
// Only allow a single component tag helper per element. We also have some *special* tag helpers
37+
// and they should have already been processed by now.
38+
if (count++ > 1)
39+
{
40+
node.Diagnostics.Add(BlazorDiagnosticFactory.Create_MultipleComponents(node.Source, node.TagName, node.TagHelpers));
41+
break;
42+
}
3443

35-
RewriteUsage(node, node.TagHelpers[0]);
44+
RewriteUsage(node, node.TagHelpers[j]);
45+
}
46+
}
3647
}
3748
}
3849

3950
private void RewriteUsage(TagHelperIntermediateNode node, TagHelperDescriptor tagHelper)
4051
{
41-
// Ignore Kind here. Some versions of Razor have a bug in the serializer that ignores it.
42-
4352
// We need to surround the contents of the node with open and close nodes to ensure the component
4453
// is scoped correctly.
4554
node.Children.Insert(0, new ComponentOpenExtensionNode()

src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentTagHelperDescriptorProvider.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ namespace Microsoft.AspNetCore.Blazor.Razor
1212
{
1313
internal class ComponentTagHelperDescriptorProvider : RazorEngineFeatureBase, ITagHelperDescriptorProvider
1414
{
15-
public static readonly string DelegateSignatureMetadata = "Blazor.DelegateSignature";
16-
17-
public readonly static string ComponentTagHelperKind = ComponentDocumentClassifierPass.ComponentDocumentKind;
18-
1915
private static readonly SymbolDisplayFormat FullNameTypeDisplayFormat =
2016
SymbolDisplayFormat.FullyQualifiedFormat
2117
.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted)
@@ -78,12 +74,12 @@ private TagHelperDescriptor CreateDescriptor(INamedTypeSymbol type)
7874
var typeName = type.ToDisplayString(FullNameTypeDisplayFormat);
7975
var assemblyName = type.ContainingAssembly.Identity.Name;
8076

81-
var builder = TagHelperDescriptorBuilder.Create(ComponentTagHelperKind, typeName, assemblyName);
77+
var builder = TagHelperDescriptorBuilder.Create(BlazorMetadata.Component.TagHelperKind, typeName, assemblyName);
8278
builder.SetTypeName(typeName);
8379

8480
// This opts out this 'component' tag helper for any processing that's specific to the default
8581
// Razor ITagHelper runtime.
86-
builder.Metadata[TagHelperMetadata.Runtime.Name] = "Blazor.IComponent";
82+
builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.Component.RuntimeName;
8783

8884
var xml = type.GetDocumentationCommentXml();
8985
if (!string.IsNullOrEmpty(xml))
@@ -114,7 +110,7 @@ private TagHelperDescriptor CreateDescriptor(INamedTypeSymbol type)
114110

115111
if (property.kind == PropertyKind.Delegate)
116112
{
117-
pb.Metadata.Add(DelegateSignatureMetadata, bool.TrueString);
113+
pb.Metadata.Add(BlazorMetadata.Component.DelegateSignatureKey, bool.TrueString);
118114
}
119115

120116
xml = property.property.GetDocumentationCommentXml();

0 commit comments

Comments
 (0)