Add the ability to specify invariant and format

Adds the ability to specify that an `<input type="..." />` 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 60f2d563a6
This commit is contained in:
Ryan Nowak 2019-06-30 14:15:47 -07:00 committed by Ryan Nowak
parent 2c6e1dacac
commit 3c84c57c61
5 changed files with 115 additions and 1 deletions

View File

@ -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

View File

@ -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;
}
/// <summary>
/// Gets a value that indicates where the tag helper is a bind tag helper with a default
/// culture value of <see cref="CultureInfo.InvariantCulture"/>.
/// </summary>
/// <param name="tagHelper">The <see cref="TagHelperDescriptor"/>.</param>
/// <returns>
/// <c>true</c> if this tag helper is a bind tag helper and defaults in <see cref="CultureInfo.InvariantCulture"/>
/// </returns>
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;
}
/// <summary>
/// Gets the default format value for a bind tag helper.
/// </summary>
/// <param name="tagHelper">The <see cref="TagHelperDescriptor"/>.</param>
/// <returns>The format, or <c>null</c>.</returns>
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)

View File

@ -72,6 +72,12 @@ namespace Microsoft.CodeAnalysis.Razor
// These mappings are also provided by attributes. Primarily these are used by <input />
// and so we have a special case for input elements and their type attributes.
//
// Additionally, our mappings tell us about cases like <input type="number" ... /> 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
// <input type="text" ... /> 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

View File

@ -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()
{

View File

@ -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
{