From 3c84c57c61f46a527a526c7a118fbc2f44cd3ab8 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Sun, 30 Jun 2019 14:15:47 -0700 Subject: [PATCH] Add the ability to specify invariant and format Adds the ability to specify that an `` for some specific value of `type` maps to the invariant culture and/or provides a default format. The primary examples of usage would be the number field which wants to use invariant culture, or datetime which wants to use invariant culture and a format string. \n\nCommit migrated from https://github.com/dotnet/aspnetcore-tooling/commit/60f2d563a681beb70eb0ffb9dae8c3bfc247c002 --- .../src/Components/ComponentMetadata.cs | 4 ++ .../TagHelperDescriptorExtensions.cs | 38 +++++++++++++++++ .../src/BindTagHelperDescriptorProvider.cs | 29 ++++++++++++- .../BindTagHelperDescriptorProviderTest.cs | 42 +++++++++++++++++++ ...ft.AspNetCore.Components.netstandard2.0.cs | 3 ++ 5 files changed, 115 insertions(+), 1 deletion(-) diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentMetadata.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentMetadata.cs index 4c6db3048e..fdf26952a6 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentMetadata.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentMetadata.cs @@ -59,6 +59,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Components public readonly static string ChangeAttribute = "Components.Bind.ChangeAttribute"; public readonly static string ExpressionAttribute = "Components.Bind.ExpressionAttribute"; + + public readonly static string IsInvariantCulture = "Components.Bind.IsInvariantCulture"; + + public readonly static string Format = "Components.Bind.Format"; } public static class ChildContent diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/TagHelperDescriptorExtensions.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/TagHelperDescriptorExtensions.cs index 6ea32c7171..2a732e612c 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/TagHelperDescriptorExtensions.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/TagHelperDescriptorExtensions.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Globalization; namespace Microsoft.AspNetCore.Razor.Language.Components { @@ -115,6 +116,43 @@ namespace Microsoft.AspNetCore.Razor.Language.Components return result; } + /// + /// Gets a value that indicates where the tag helper is a bind tag helper with a default + /// culture value of . + /// + /// The . + /// + /// true if this tag helper is a bind tag helper and defaults in + /// + public static bool IsInvariantCultureBindTagHelper(this TagHelperDescriptor tagHelper) + { + if (tagHelper == null) + { + throw new ArgumentNullException(nameof(tagHelper)); + } + + return + tagHelper.Metadata.TryGetValue(ComponentMetadata.Bind.IsInvariantCulture, out var text) && + bool.TryParse(text, out var result) && + result; + } + + /// + /// Gets the default format value for a bind tag helper. + /// + /// The . + /// The format, or null. + public static string GetFormat(this TagHelperDescriptor tagHelper) + { + if (tagHelper == null) + { + throw new ArgumentNullException(nameof(tagHelper)); + } + + tagHelper.Metadata.TryGetValue(ComponentMetadata.Bind.Format, out var result); + return result; + } + public static bool IsChildContentTagHelper(this TagHelperDescriptor tagHelper) { if (tagHelper == null) diff --git a/src/Razor/Microsoft.CodeAnalysis.Razor/src/BindTagHelperDescriptorProvider.cs b/src/Razor/Microsoft.CodeAnalysis.Razor/src/BindTagHelperDescriptorProvider.cs index 6a9d72377b..8ad405c6b5 100644 --- a/src/Razor/Microsoft.CodeAnalysis.Razor/src/BindTagHelperDescriptorProvider.cs +++ b/src/Razor/Microsoft.CodeAnalysis.Razor/src/BindTagHelperDescriptorProvider.cs @@ -72,6 +72,12 @@ namespace Microsoft.CodeAnalysis.Razor // These mappings are also provided by attributes. Primarily these are used by // and so we have a special case for input elements and their type attributes. // + // Additionally, our mappings tell us about cases like where + // we need to treat the value as an invariant culture value. In general the HTML5 field + // types use invariant culture values when interacting with the DOM, in contrast to + // which is free-form text and is most likely to be + // culture-sensitive. + // // 4. For components, we have a bit of a special case. We can infer a syntax that matches // case #2 based on property names. So if a component provides both 'Value' and 'ValueChanged' // we will turn that into an instance of bind. @@ -254,6 +260,19 @@ namespace Microsoft.CodeAnalysis.Razor (string)attribute.ConstructorArguments[2].Value, (string)attribute.ConstructorArguments[3].Value)); } + else if (attribute.AttributeClass == bindInputElement && attribute.ConstructorArguments.Length == 6) + { + results.Add(new ElementBindData( + type.ContainingAssembly.Name, + type.ToDisplayString(), + "input", + (string)attribute.ConstructorArguments[0].Value, + (string)attribute.ConstructorArguments[1].Value, + (string)attribute.ConstructorArguments[2].Value, + (string)attribute.ConstructorArguments[3].Value, + (bool)attribute.ConstructorArguments[4].Value, + (string)attribute.ConstructorArguments[5].Value)); + } } } @@ -287,6 +306,8 @@ namespace Microsoft.CodeAnalysis.Razor builder.Metadata[TagHelperMetadata.Runtime.Name] = ComponentMetadata.Bind.RuntimeName; builder.Metadata[ComponentMetadata.Bind.ValueAttribute] = entry.ValueAttribute; builder.Metadata[ComponentMetadata.Bind.ChangeAttribute] = entry.ChangeAttribute; + builder.Metadata[ComponentMetadata.Bind.IsInvariantCulture] = entry.IsInvariantCulture ? bool.TrueString : bool.FalseString; + builder.Metadata[ComponentMetadata.Bind.Format] = entry.Format; if (entry.TypeAttribute != null) { @@ -512,7 +533,9 @@ namespace Microsoft.CodeAnalysis.Razor string typeAttribute, string suffix, string valueAttribute, - string changeAttribute) + string changeAttribute, + bool isInvariantCulture = false, + string format = null) { Assembly = assembly; TypeName = typeName; @@ -521,6 +544,8 @@ namespace Microsoft.CodeAnalysis.Razor Suffix = suffix; ValueAttribute = valueAttribute; ChangeAttribute = changeAttribute; + IsInvariantCulture = isInvariantCulture; + Format = format; } public string Assembly { get; } @@ -530,6 +555,8 @@ namespace Microsoft.CodeAnalysis.Razor public string Suffix { get; } public string ValueAttribute { get; } public string ChangeAttribute { get; } + public bool IsInvariantCulture { get; } + public string Format { get; } } private class BindElementDataVisitor : SymbolVisitor diff --git a/src/Razor/Microsoft.CodeAnalysis.Razor/test/BindTagHelperDescriptorProviderTest.cs b/src/Razor/Microsoft.CodeAnalysis.Razor/test/BindTagHelperDescriptorProviderTest.cs index 328808dd40..e037ab2fd1 100644 --- a/src/Razor/Microsoft.CodeAnalysis.Razor/test/BindTagHelperDescriptorProviderTest.cs +++ b/src/Razor/Microsoft.CodeAnalysis.Razor/test/BindTagHelperDescriptorProviderTest.cs @@ -649,6 +649,8 @@ namespace Test Assert.Equal("checkbox", bind.Metadata[ComponentMetadata.Bind.TypeAttribute]); Assert.True(bind.IsInputElementBindTagHelper()); Assert.False(bind.IsInputElementFallbackBindTagHelper()); + Assert.False(bind.IsInvariantCultureBindTagHelper()); + Assert.Null(bind.GetFormat()); var rule = Assert.Single(bind.TagMatchingRules); Assert.Equal("input", rule.TagName); @@ -681,6 +683,46 @@ namespace Test Assert.Equal(":format", parameter.DisplayName); } + [Fact] + public void Execute_BindOnInputElementWithTypeAttributeAndSuffixAndInvariantCultureAndFormat_CreatesDescriptor() + { + // Arrange + var compilation = BaseCompilation.AddSyntaxTrees(Parse(@" +using Microsoft.AspNetCore.Components; + +namespace Test +{ + [BindInputElement(""number"", null, ""value"", ""onchange"", isInvariantCulture: true, format: ""0.00"")] + public class BindAttributes + { + } +} +")); + + Assert.Empty(compilation.GetDiagnostics()); + + var context = TagHelperDescriptorProviderContext.Create(); + context.SetCompilation(compilation); + + var provider = new BindTagHelperDescriptorProvider(); + + // Act + provider.Execute(context); + + // Assert + var matches = GetBindTagHelpers(context); + matches = AssertAndExcludeFullyQualifiedNameMatchComponents(matches, expectedCount: 0); + var bind = Assert.Single(matches); + + Assert.Equal("value", bind.Metadata[ComponentMetadata.Bind.ValueAttribute]); + Assert.Equal("onchange", bind.Metadata[ComponentMetadata.Bind.ChangeAttribute]); + Assert.Equal("number", bind.Metadata[ComponentMetadata.Bind.TypeAttribute]); + Assert.True(bind.IsInputElementBindTagHelper()); + Assert.False(bind.IsInputElementFallbackBindTagHelper()); + Assert.True(bind.IsInvariantCultureBindTagHelper()); + Assert.Equal("0.00", bind.GetFormat()); + } + [Fact] public void Execute_BindFallback_CreatesDescriptor() { diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components.netstandard2.0.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components.netstandard2.0.cs index 92b9f1f55d..caa5697b2b 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components.netstandard2.0.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components.netstandard2.0.cs @@ -38,10 +38,13 @@ namespace Microsoft.AspNetCore.Components public sealed partial class BindInputElementAttribute : System.Attribute { public BindInputElementAttribute(string type, string suffix, string valueAttribute, string changeAttribute) { } + public BindInputElementAttribute(string type, string suffix, string valueAttribute, string changeAttribute, bool isInvariantCulture, string format) { } public string ChangeAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public string Suffix { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public string Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public string ValueAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool IsInvariantCulture { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Format { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } } public static partial class BindMethods {