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");
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -153,4 +153,7 @@
|
|||
<data name="TagHelperDescriptorFactory_Tag" xml:space="preserve">
|
||||
<value>Tag</value>
|
||||
</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>
|
||||
|
|
@ -16,6 +16,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
/// </summary>
|
||||
public static class TagHelperDescriptorFactory
|
||||
{
|
||||
private const string DataDashPrefix = "data-";
|
||||
private const string TagHelperNameEnding = "TagHelper";
|
||||
private const string HtmlCaseRegexReplacement = "-$1$2";
|
||||
|
||||
|
|
@ -44,7 +45,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
[NotNull] ErrorSink errorSink)
|
||||
{
|
||||
var typeInfo = type.GetTypeInfo();
|
||||
var attributeDescriptors = GetAttributeDescriptors(type);
|
||||
var attributeDescriptors = GetAttributeDescriptors(type, errorSink);
|
||||
var targetElementAttributes = GetValidTargetElementAttributes(typeInfo, errorSink);
|
||||
var tagHelperDescriptors =
|
||||
BuildTagHelperDescriptors(
|
||||
|
|
@ -201,14 +202,47 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
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 attributeDescriptors = properties.Select(ToAttributeDescriptor);
|
||||
var accessibleProperties = type.GetRuntimeProperties().Where(IsAccessibleProperty);
|
||||
var attributeDescriptors = new List<TagHelperAttributeDescriptor>();
|
||||
|
||||
foreach (var property in accessibleProperties)
|
||||
{
|
||||
var descriptor = ToAttributeDescriptor(property);
|
||||
if (ValidateTagHelperAttributeDescriptor(descriptor, type, errorSink))
|
||||
{
|
||||
attributeDescriptors.Add(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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(
|
||||
TagHelperDescriptor descriptorX,
|
||||
TagHelperDescriptor descriptorX,
|
||||
TagHelperDescriptor descriptorY)
|
||||
{
|
||||
return base.Equals(descriptorX, descriptorY) &&
|
||||
|
|
@ -33,7 +33,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
StringComparer.Ordinal) &&
|
||||
descriptorX.Attributes.SequenceEqual(
|
||||
descriptorY.Attributes,
|
||||
CaseSensitiveAttributeDescriptorComparer.Default);
|
||||
CaseSensitiveTagHelperAttributeDescriptorComparer.Default);
|
||||
}
|
||||
|
||||
int IEqualityComparer<TagHelperDescriptor>.GetHashCode(TagHelperDescriptor descriptor)
|
||||
|
|
@ -51,39 +51,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
|
||||
foreach (var attribute in descriptor.Attributes)
|
||||
{
|
||||
hashCodeCombiner.Add(CaseSensitiveAttributeDescriptorComparer.Default.GetHashCode(attribute));
|
||||
hashCodeCombiner.Add(CaseSensitiveTagHelperAttributeDescriptorComparer.Default.GetHashCode(attribute));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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")]
|
||||
private class AttributeTargetingTagHelper : TagHelper
|
||||
{
|
||||
|
|
@ -999,7 +1100,34 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
private class UNSuffixedCLASS : TagHelper
|
||||
{
|
||||
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