Add `HtmlAttributeNotBoundAttribute`

- #182
- ignore otherwise-bound (i.e. `public`) properties in tag helpers

nits:
- add more `TagHelperDescriptorFactory` tests e.g. of `internal` properties
- remove `private` setter from `HtmlAttributeNameAttribute`
- use `null` propagation to shorten `IsAccessibleProperty` expression
- clean up some trailing whitespace
This commit is contained in:
Doug Bunting 2015-04-24 22:57:41 -07:00
parent dc4ee8b915
commit de95f67400
5 changed files with 104 additions and 15 deletions

View File

@ -28,6 +28,6 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
/// <summary>
/// HTML attribute name of the associated property.
/// </summary>
public string Name { get; private set; }
public string Name { get; }
}
}

View File

@ -0,0 +1,21 @@
// 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;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
/// <summary>
/// Indicates the associated <see cref="ITagHelper"/> property should not be bound to HTML attributes.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class HtmlAttributeNotBoundAttribute : Attribute
{
/// <summary>
/// Instantiates a new instance of the <see cref="HtmlAttributeNotBoundAttribute"/> class.
/// </summary>
public HtmlAttributeNotBoundAttribute()
{
}
}
}

View File

@ -203,7 +203,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
}
private static IEnumerable<TagHelperAttributeDescriptor> GetAttributeDescriptors(
Type type,
Type type,
ErrorSink errorSink)
{
var accessibleProperties = type.GetRuntimeProperties().Where(IsAccessibleProperty);
@ -226,12 +226,12 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
Type parentType,
ErrorSink errorSink)
{
// data-* attributes are explicitly not implemented by user agents and are not intended for use on
// 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,
SourceLocation.Zero,
Resources.FormatTagHelperDescriptorFactory_InvalidBoundAttributeName(
attributeDescriptor.PropertyName,
parentType.FullName,
@ -255,10 +255,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
private static bool IsAccessibleProperty(PropertyInfo property)
{
return property.GetMethod != null &&
property.GetMethod.IsPublic &&
property.SetMethod != null &&
property.SetMethod.IsPublic;
// Accessible properties are those with public getters and setters and without [HtmlAttributeNotBound].
return property.GetMethod?.IsPublic == true &&
property.SetMethod?.IsPublic == true &&
property.GetCustomAttribute<HtmlAttributeNotBoundAttribute>(inherit: false) == null;
}
/// <summary>

View File

@ -23,10 +23,13 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
public string InvalidNoSetAttribute { get { return string.Empty; } }
}
public class PrivateAccessorTagHelper : TagHelper
public class NonPublicAccessorTagHelper : TagHelper
{
public string ValidAttribute { get; set; }
public string InvalidPrivateSetAttribute { get; private set; }
public string InvalidPrivateGetAttribute { private get; set; }
protected string InvalidProtectedAttribute { get; set; }
internal string InvalidInternalAttribute { get; set; }
protected internal string InvalidProtectedInternalAttribute { get; set; }
}
}

View File

@ -443,11 +443,11 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
// Arrange
var errorSink = new ErrorSink();
var validProperty = typeof(PrivateAccessorTagHelper).GetProperty(
nameof(PrivateAccessorTagHelper.ValidAttribute));
var validProperty = typeof(NonPublicAccessorTagHelper).GetProperty(
nameof(NonPublicAccessorTagHelper.ValidAttribute));
var expectedDescriptor = new TagHelperDescriptor(
"private-accessor",
typeof(PrivateAccessorTagHelper).FullName,
"non-public-accessor",
typeof(NonPublicAccessorTagHelper).FullName,
AssemblyName,
new[] {
new TagHelperAttributeDescriptor(
@ -457,7 +457,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
typeof(PrivateAccessorTagHelper),
typeof(NonPublicAccessorTagHelper),
errorSink);
// Assert
@ -466,6 +466,51 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
Assert.Equal(expectedDescriptor, descriptor, CaseSensitiveTagHelperDescriptorComparer.Default);
}
[Fact]
public void CreateDescriptor_DoesNotIncludePropertiesWithNotBound()
{
// Arrange
var errorSink = new ErrorSink();
var boundProperty = typeof(NotBoundAttributeTagHelper).GetProperty(
nameof(NotBoundAttributeTagHelper.BoundProperty));
var expectedDescriptor = new TagHelperDescriptor(
"not-bound-attribute",
typeof(NotBoundAttributeTagHelper).FullName,
AssemblyName,
new[]
{
new TagHelperAttributeDescriptor("bound-property", boundProperty)
});
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
typeof(NotBoundAttributeTagHelper),
errorSink);
// Assert
Assert.Empty(errorSink.Errors);
var descriptor = Assert.Single(descriptors);
Assert.Equal(expectedDescriptor, descriptor, CaseSensitiveTagHelperDescriptorComparer.Default);
}
[Fact(Skip = "#364")]
public void CreateDescriptor_AddsErrorForTagHelperWithDuplicateAttributeNames()
{
// Arrange
var errorSink = new ErrorSink();
// Act
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
AssemblyName,
typeof(DuplicateAttributeNameTagHelper),
errorSink);
// Assert
Assert.Empty(descriptors);
var error = Assert.Single(errorSink.Errors);
}
[Fact]
public void CreateDescriptor_ResolvesMultipleTagHelperDescriptorsFromSingleType()
{
@ -867,7 +912,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
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[]>
{
@ -1049,6 +1094,26 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
}
private class DuplicateAttributeNameTagHelper
{
public string MyNameIsLegion { get; set; }
[HtmlAttributeName("my-name-is-legion")]
public string Fred { get; set; }
}
private class NotBoundAttributeTagHelper
{
public string BoundProperty { get; set; }
[HtmlAttributeNotBound]
public string NotBoundProperty { get; set; }
[HtmlAttributeName("unused")]
[HtmlAttributeNotBound]
public string NamedNotBoundProperty { get; set; }
}
private class OverriddenAttributeTagHelper
{
[HtmlAttributeName("SomethingElse")]