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:
parent
dc4ee8b915
commit
de95f67400
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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")]
|
||||
|
|
|
|||
Loading…
Reference in New Issue