diff --git a/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs
index 445a265592..e5b254e1e1 100644
--- a/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs
@@ -202,6 +202,22 @@ namespace Microsoft.AspNet.Razor.Runtime
return GetString("TagHelperDescriptorFactory_Tag");
}
+ ///
+ /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes beginning with '{2}'.
+ ///
+ internal static string TagHelperDescriptorFactory_InvalidBoundAttributeName
+ {
+ get { return GetString("TagHelperDescriptorFactory_InvalidBoundAttributeName"); }
+ }
+
+ ///
+ /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes beginning with '{2}'.
+ ///
+ 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);
diff --git a/src/Microsoft.AspNet.Razor.Runtime/Resources.resx b/src/Microsoft.AspNet.Razor.Runtime/Resources.resx
index bb3fdbd8ae..19695614ab 100644
--- a/src/Microsoft.AspNet.Razor.Runtime/Resources.resx
+++ b/src/Microsoft.AspNet.Razor.Runtime/Resources.resx
@@ -153,4 +153,7 @@
Tag
+
+ Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes beginning with '{2}'.
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs
index 627d63f7a2..6eb7ddeae8 100644
--- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs
+++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs
@@ -16,6 +16,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
///
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 GetAttributeDescriptors(Type type)
+ private static IEnumerable 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();
+
+ 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(inherit: false);
diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CaseSensitiveTagHelperAttributeDescriptorComparer.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CaseSensitiveTagHelperAttributeDescriptorComparer.cs
new file mode 100644
index 0000000000..e27730e084
--- /dev/null
+++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CaseSensitiveTagHelperAttributeDescriptorComparer.cs
@@ -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
+ {
+ 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CaseSensitiveTagHelperDescriptorComparer.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CaseSensitiveTagHelperDescriptorComparer.cs
index 6bc5acdbab..1096ef93c5 100644
--- a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CaseSensitiveTagHelperDescriptorComparer.cs
+++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CaseSensitiveTagHelperDescriptorComparer.cs
@@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
}
bool IEqualityComparer.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.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
- {
- 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;
- }
- }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorFactoryTest.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorFactoryTest.cs
index 859f134902..f6b29b5947 100644
--- a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorFactoryTest.cs
+++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorFactoryTest.cs
@@ -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, string[]>
+ {
+ {
+ typeof(InvalidBoundAttribute),
+ Enumerable.Empty(),
+ 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(),
+ new[] {
+ string.Format(
+ errorFormat,
+ nameof(OverriddenValidBoundAttributeWithInvalid.ValidSomething),
+ typeof(OverriddenValidBoundAttributeWithInvalid).FullName)
+ }
+ },
+ {
+ typeof(OverriddenValidBoundAttributeWithInvalidUpperCase),
+ Enumerable.Empty(),
+ new[] {
+ string.Format(
+ errorFormat,
+ nameof(OverriddenValidBoundAttributeWithInvalidUpperCase.ValidSomething),
+ typeof(OverriddenValidBoundAttributeWithInvalidUpperCase).FullName)
+ }
+ },
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(InvalidTagHelperAttributeDescriptorData))]
+ public void CreateDescriptor_DoesNotAllowDataDashAttributes(
+ Type type,
+ IEnumerable 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; }
}
}
}
\ No newline at end of file