Create error when TagHelper binds to HTML attribute starting w/ data-.
- Added TagHelperDescriptorFactory tests to validate TagHelperAttributeDescriptor creation. #342
This commit is contained in:
parent
96dc7ea6c5
commit
5bcda94b2c
|
|
@ -202,6 +202,22 @@ namespace Microsoft.AspNet.Razor.Runtime
|
||||||
return GetString("TagHelperDescriptorFactory_Tag");
|
return GetString("TagHelperDescriptorFactory_Tag");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes beginning with '{2}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelperDescriptorFactory_InvalidBoundAttributeName
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelperDescriptorFactory_InvalidBoundAttributeName"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes beginning with '{2}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelperDescriptorFactory_InvalidBoundAttributeName(object p0, object p1, object p2)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidBoundAttributeName"), p0, p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -153,4 +153,7 @@
|
||||||
<data name="TagHelperDescriptorFactory_Tag" xml:space="preserve">
|
<data name="TagHelperDescriptorFactory_Tag" xml:space="preserve">
|
||||||
<value>Tag</value>
|
<value>Tag</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_InvalidBoundAttributeName" xml:space="preserve">
|
||||||
|
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes beginning with '{2}'.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -16,6 +16,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class TagHelperDescriptorFactory
|
public static class TagHelperDescriptorFactory
|
||||||
{
|
{
|
||||||
|
private const string DataDashPrefix = "data-";
|
||||||
private const string TagHelperNameEnding = "TagHelper";
|
private const string TagHelperNameEnding = "TagHelper";
|
||||||
private const string HtmlCaseRegexReplacement = "-$1$2";
|
private const string HtmlCaseRegexReplacement = "-$1$2";
|
||||||
|
|
||||||
|
|
@ -44,7 +45,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
[NotNull] ErrorSink errorSink)
|
[NotNull] ErrorSink errorSink)
|
||||||
{
|
{
|
||||||
var typeInfo = type.GetTypeInfo();
|
var typeInfo = type.GetTypeInfo();
|
||||||
var attributeDescriptors = GetAttributeDescriptors(type);
|
var attributeDescriptors = GetAttributeDescriptors(type, errorSink);
|
||||||
var targetElementAttributes = GetValidTargetElementAttributes(typeInfo, errorSink);
|
var targetElementAttributes = GetValidTargetElementAttributes(typeInfo, errorSink);
|
||||||
var tagHelperDescriptors =
|
var tagHelperDescriptors =
|
||||||
BuildTagHelperDescriptors(
|
BuildTagHelperDescriptors(
|
||||||
|
|
@ -201,14 +202,47 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
return validName;
|
return validName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<TagHelperAttributeDescriptor> GetAttributeDescriptors(Type type)
|
private static IEnumerable<TagHelperAttributeDescriptor> GetAttributeDescriptors(
|
||||||
|
Type type,
|
||||||
|
ErrorSink errorSink)
|
||||||
{
|
{
|
||||||
var properties = type.GetRuntimeProperties().Where(IsAccessibleProperty);
|
var accessibleProperties = type.GetRuntimeProperties().Where(IsAccessibleProperty);
|
||||||
var attributeDescriptors = properties.Select(ToAttributeDescriptor);
|
var attributeDescriptors = new List<TagHelperAttributeDescriptor>();
|
||||||
|
|
||||||
|
foreach (var property in accessibleProperties)
|
||||||
|
{
|
||||||
|
var descriptor = ToAttributeDescriptor(property);
|
||||||
|
if (ValidateTagHelperAttributeDescriptor(descriptor, type, errorSink))
|
||||||
|
{
|
||||||
|
attributeDescriptors.Add(descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return attributeDescriptors;
|
return attributeDescriptors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool ValidateTagHelperAttributeDescriptor(
|
||||||
|
TagHelperAttributeDescriptor attributeDescriptor,
|
||||||
|
Type parentType,
|
||||||
|
ErrorSink errorSink)
|
||||||
|
{
|
||||||
|
// data-* attributes are explicitly not implemented by user agents and are not intended for use on
|
||||||
|
// the server; therefore it's invalid for TagHelpers to bind to them.
|
||||||
|
if (attributeDescriptor.Name.StartsWith(DataDashPrefix, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
errorSink.OnError(
|
||||||
|
SourceLocation.Zero,
|
||||||
|
Resources.FormatTagHelperDescriptorFactory_InvalidBoundAttributeName(
|
||||||
|
attributeDescriptor.PropertyName,
|
||||||
|
parentType.FullName,
|
||||||
|
DataDashPrefix));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static TagHelperAttributeDescriptor ToAttributeDescriptor(PropertyInfo property)
|
private static TagHelperAttributeDescriptor ToAttributeDescriptor(PropertyInfo property)
|
||||||
{
|
{
|
||||||
var attributeNameAttribute = property.GetCustomAttribute<HtmlAttributeNameAttribute>(inherit: false);
|
var attributeNameAttribute = property.GetCustomAttribute<HtmlAttributeNameAttribute>(inherit: false);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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.Collections.Generic;
|
||||||
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
using Microsoft.Internal.Web.Utils;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
{
|
||||||
|
public class CaseSensitiveTagHelperAttributeDescriptorComparer : IEqualityComparer<TagHelperAttributeDescriptor>
|
||||||
|
{
|
||||||
|
public static readonly CaseSensitiveTagHelperAttributeDescriptorComparer Default =
|
||||||
|
new CaseSensitiveTagHelperAttributeDescriptorComparer();
|
||||||
|
|
||||||
|
private CaseSensitiveTagHelperAttributeDescriptorComparer()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(TagHelperAttributeDescriptor descriptorX, TagHelperAttributeDescriptor descriptorY)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
// Normal comparer doesn't care about case, in tests we do.
|
||||||
|
string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.Ordinal) &&
|
||||||
|
string.Equals(descriptorX.PropertyName, descriptorY.PropertyName, StringComparison.Ordinal) &&
|
||||||
|
string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode(TagHelperAttributeDescriptor descriptor)
|
||||||
|
{
|
||||||
|
return HashCodeCombiner
|
||||||
|
.Start()
|
||||||
|
.Add(descriptor.Name, StringComparer.Ordinal)
|
||||||
|
.Add(descriptor.PropertyName, StringComparer.Ordinal)
|
||||||
|
.Add(descriptor.TypeName, StringComparer.Ordinal)
|
||||||
|
.CombinedHash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IEqualityComparer<TagHelperDescriptor>.Equals(
|
bool IEqualityComparer<TagHelperDescriptor>.Equals(
|
||||||
TagHelperDescriptor descriptorX,
|
TagHelperDescriptor descriptorX,
|
||||||
TagHelperDescriptor descriptorY)
|
TagHelperDescriptor descriptorY)
|
||||||
{
|
{
|
||||||
return base.Equals(descriptorX, descriptorY) &&
|
return base.Equals(descriptorX, descriptorY) &&
|
||||||
|
|
@ -33,7 +33,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
StringComparer.Ordinal) &&
|
StringComparer.Ordinal) &&
|
||||||
descriptorX.Attributes.SequenceEqual(
|
descriptorX.Attributes.SequenceEqual(
|
||||||
descriptorY.Attributes,
|
descriptorY.Attributes,
|
||||||
CaseSensitiveAttributeDescriptorComparer.Default);
|
CaseSensitiveTagHelperAttributeDescriptorComparer.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
int IEqualityComparer<TagHelperDescriptor>.GetHashCode(TagHelperDescriptor descriptor)
|
int IEqualityComparer<TagHelperDescriptor>.GetHashCode(TagHelperDescriptor descriptor)
|
||||||
|
|
@ -51,39 +51,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
|
||||||
foreach (var attribute in descriptor.Attributes)
|
foreach (var attribute in descriptor.Attributes)
|
||||||
{
|
{
|
||||||
hashCodeCombiner.Add(CaseSensitiveAttributeDescriptorComparer.Default.GetHashCode(attribute));
|
hashCodeCombiner.Add(CaseSensitiveTagHelperAttributeDescriptorComparer.Default.GetHashCode(attribute));
|
||||||
}
|
}
|
||||||
|
|
||||||
return hashCodeCombiner.CombinedHash;
|
return hashCodeCombiner.CombinedHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CaseSensitiveAttributeDescriptorComparer : IEqualityComparer<TagHelperAttributeDescriptor>
|
|
||||||
{
|
|
||||||
public static readonly CaseSensitiveAttributeDescriptorComparer Default =
|
|
||||||
new CaseSensitiveAttributeDescriptorComparer();
|
|
||||||
|
|
||||||
private CaseSensitiveAttributeDescriptorComparer()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(TagHelperAttributeDescriptor descriptorX, TagHelperAttributeDescriptor descriptorY)
|
|
||||||
{
|
|
||||||
return
|
|
||||||
// Normal comparer doesn't care about case, in tests we do.
|
|
||||||
string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.Ordinal) &&
|
|
||||||
string.Equals(descriptorX.PropertyName, descriptorY.PropertyName, StringComparison.Ordinal) &&
|
|
||||||
string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetHashCode(TagHelperAttributeDescriptor descriptor)
|
|
||||||
{
|
|
||||||
return HashCodeCombiner
|
|
||||||
.Start()
|
|
||||||
.Add(descriptor.Name, StringComparer.Ordinal)
|
|
||||||
.Add(descriptor.PropertyName, StringComparer.Ordinal)
|
|
||||||
.Add(descriptor.TypeName, StringComparer.Ordinal)
|
|
||||||
.CombinedHash;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -861,6 +861,107 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
Assert.Empty(result);
|
Assert.Empty(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TheoryData InvalidTagHelperAttributeDescriptorData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var errorFormat = "Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML " +
|
||||||
|
"attributes beginning with 'data-'.";
|
||||||
|
|
||||||
|
// type, expectedAttributeDescriptors, expectedErrors
|
||||||
|
return new TheoryData<Type, IEnumerable<TagHelperAttributeDescriptor>, string[]>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
typeof(InvalidBoundAttribute),
|
||||||
|
Enumerable.Empty<TagHelperAttributeDescriptor>(),
|
||||||
|
new[] {
|
||||||
|
string.Format(
|
||||||
|
errorFormat,
|
||||||
|
nameof(InvalidBoundAttribute.DataSomething),
|
||||||
|
typeof(InvalidBoundAttribute).FullName)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
typeof(InvalidBoundAttributeWithValid),
|
||||||
|
new[] {
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
"int-attribute",
|
||||||
|
typeof(InvalidBoundAttributeWithValid)
|
||||||
|
.GetProperty(nameof(InvalidBoundAttributeWithValid.IntAttribute)))
|
||||||
|
},
|
||||||
|
new[] {
|
||||||
|
string.Format(
|
||||||
|
errorFormat,
|
||||||
|
nameof(InvalidBoundAttributeWithValid.DataSomething),
|
||||||
|
typeof(InvalidBoundAttributeWithValid).FullName)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
typeof(OverriddenInvalidBoundAttributeWithValid),
|
||||||
|
new[] {
|
||||||
|
new TagHelperAttributeDescriptor(
|
||||||
|
"valid-something",
|
||||||
|
typeof(OverriddenInvalidBoundAttributeWithValid)
|
||||||
|
.GetProperty(nameof(OverriddenInvalidBoundAttributeWithValid.DataSomething)))
|
||||||
|
},
|
||||||
|
new string[0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
typeof(OverriddenValidBoundAttributeWithInvalid),
|
||||||
|
Enumerable.Empty<TagHelperAttributeDescriptor>(),
|
||||||
|
new[] {
|
||||||
|
string.Format(
|
||||||
|
errorFormat,
|
||||||
|
nameof(OverriddenValidBoundAttributeWithInvalid.ValidSomething),
|
||||||
|
typeof(OverriddenValidBoundAttributeWithInvalid).FullName)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
typeof(OverriddenValidBoundAttributeWithInvalidUpperCase),
|
||||||
|
Enumerable.Empty<TagHelperAttributeDescriptor>(),
|
||||||
|
new[] {
|
||||||
|
string.Format(
|
||||||
|
errorFormat,
|
||||||
|
nameof(OverriddenValidBoundAttributeWithInvalidUpperCase.ValidSomething),
|
||||||
|
typeof(OverriddenValidBoundAttributeWithInvalidUpperCase).FullName)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(InvalidTagHelperAttributeDescriptorData))]
|
||||||
|
public void CreateDescriptor_DoesNotAllowDataDashAttributes(
|
||||||
|
Type type,
|
||||||
|
IEnumerable<TagHelperAttributeDescriptor> expectedAttributeDescriptors,
|
||||||
|
string[] expectedErrors)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var errorSink = new ErrorSink();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(AssemblyName, type, errorSink);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var actualErrors = errorSink.Errors.ToArray();
|
||||||
|
Assert.Equal(expectedErrors.Length, actualErrors.Length);
|
||||||
|
|
||||||
|
for (var i = 0; i < actualErrors.Length; i++)
|
||||||
|
{
|
||||||
|
var actualError = actualErrors[i];
|
||||||
|
Assert.Equal(1, actualError.Length);
|
||||||
|
Assert.Equal(SourceLocation.Zero, actualError.Location);
|
||||||
|
Assert.Equal(expectedErrors[i], actualError.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
var actualDescriptor = Assert.Single(descriptors);
|
||||||
|
Assert.Equal(
|
||||||
|
expectedAttributeDescriptors,
|
||||||
|
actualDescriptor.Attributes,
|
||||||
|
CaseSensitiveTagHelperAttributeDescriptorComparer.Default);
|
||||||
|
}
|
||||||
|
|
||||||
[TargetElement(Attributes = "class")]
|
[TargetElement(Attributes = "class")]
|
||||||
private class AttributeTargetingTagHelper : TagHelper
|
private class AttributeTargetingTagHelper : TagHelper
|
||||||
{
|
{
|
||||||
|
|
@ -999,7 +1100,34 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
private class UNSuffixedCLASS : TagHelper
|
private class UNSuffixedCLASS : TagHelper
|
||||||
{
|
{
|
||||||
public int UNSuffixedATTRIBUTE { get; set; }
|
public int UNSuffixedATTRIBUTE { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InvalidBoundAttribute : TagHelper
|
||||||
|
{
|
||||||
|
public string DataSomething { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InvalidBoundAttributeWithValid : SingleAttributeTagHelper
|
||||||
|
{
|
||||||
|
public string DataSomething { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OverriddenInvalidBoundAttributeWithValid : TagHelper
|
||||||
|
{
|
||||||
|
[HtmlAttributeName("valid-something")]
|
||||||
|
public string DataSomething { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OverriddenValidBoundAttributeWithInvalid : TagHelper
|
||||||
|
{
|
||||||
|
[HtmlAttributeName("data-something")]
|
||||||
|
public string ValidSomething { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OverriddenValidBoundAttributeWithInvalidUpperCase : TagHelper
|
||||||
|
{
|
||||||
|
[HtmlAttributeName("DATA-SOMETHING")]
|
||||||
|
public string ValidSomething { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue