From e5a21520e5b4c4764bce52528a6735208ab1eedc Mon Sep 17 00:00:00 2001 From: NTaylorMullen Date: Sat, 11 Oct 2014 13:30:53 -0700 Subject: [PATCH] Add HtmlAttributeNameAttribute for TagHelpers. - Added the ability to override the HTML attribute name for TagHelper properties. - Tested functionality of overriding the attribute name target and inheriting the attribute name. #121 --- .../Properties/Resources.Designer.cs | 16 +++ .../Resources.resx | 3 + .../TagHelpers/HtmlAttributeNameAttribute.cs | 33 +++++ .../TagHelpers/TagHelperDescriptorFactory.cs | 9 +- .../CompleteTagHelperDescriptorComparer.cs | 6 +- .../TagHelperDescriptorFactoryTest.cs | 121 ++++++++++++++++-- 6 files changed, 170 insertions(+), 18 deletions(-) create mode 100644 src/Microsoft.AspNet.Razor.Runtime/TagHelpers/HtmlAttributeNameAttribute.cs diff --git a/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs index f701d0a217..3da4706236 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs @@ -94,6 +94,22 @@ namespace Microsoft.AspNet.Razor.Runtime return string.Format(CultureInfo.CurrentCulture, GetString("TagNameAttribute_AdditionalTagsCannotContainNull"), p0); } + /// + /// The value cannot be null or empty. + /// + internal static string ArgumentCannotBeNullOrEmpty + { + get { return GetString("ArgumentCannotBeNullOrEmpty"); } + } + + /// + /// The value cannot be null or empty. + /// + internal static string FormatArgumentCannotBeNullOrEmpty() + { + return GetString("ArgumentCannotBeNullOrEmpty"); + } + 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 8e0b4485bb..563b72d5a7 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/Resources.resx +++ b/src/Microsoft.AspNet.Razor.Runtime/Resources.resx @@ -134,4 +134,7 @@ Parameter {0} must not contain null tag names. + + The value cannot be null or empty. + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/HtmlAttributeNameAttribute.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/HtmlAttributeNameAttribute.cs new file mode 100644 index 0000000000..ccd2948a61 --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/HtmlAttributeNameAttribute.cs @@ -0,0 +1,33 @@ +// 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 +{ + /// + /// Used to override an property's HTML attribute name. + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] + public sealed class HtmlAttributeNameAttribute : Attribute + { + /// + /// Instantiates a new instance of the class. + /// + /// HTML attribute name for the associated property. + public HtmlAttributeNameAttribute(string name) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(name)); + } + + Name = name; + } + + /// + /// HTML attribute name of the associated property. + /// + public string Name { get; private set; } + } +} \ 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 558a835211..b3a0482f87 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs @@ -68,11 +68,14 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers return attributeDescriptors; } - // TODO: Make the HTML attribute name support names from a AttributeNameAttribute: - // https://github.com/aspnet/Razor/issues/121 private static TagHelperAttributeDescriptor ToAttributeDescriptor(PropertyInfo property) { - return new TagHelperAttributeDescriptor(property.Name, property); + var attributeNameAttribute = property.GetCustomAttribute(inherit: false); + var attributeName = attributeNameAttribute != null ? + attributeNameAttribute.Name : + property.Name; + + return new TagHelperAttributeDescriptor(attributeName, property); } private static ContentBehavior GetContentBehavior(Type type) diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CompleteTagHelperDescriptorComparer.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CompleteTagHelperDescriptorComparer.cs index fbbc3cf4f6..cae296645c 100644 --- a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CompleteTagHelperDescriptorComparer.cs +++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CompleteTagHelperDescriptorComparer.cs @@ -8,7 +8,7 @@ using Microsoft.Internal.Web.Utils; namespace Microsoft.AspNet.Razor.Runtime.TagHelpers { - public class CompleteTagHelperDescriptorComparer : TagHelperDescriptorComparer + public class CompleteTagHelperDescriptorComparer : TagHelperDescriptorComparer, IEqualityComparer { public new static readonly CompleteTagHelperDescriptorComparer Default = new CompleteTagHelperDescriptorComparer(); @@ -17,14 +17,14 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers { } - public new bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY) + bool IEqualityComparer.Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY) { return base.Equals(descriptorX, descriptorY) && descriptorX.Attributes.SequenceEqual(descriptorY.Attributes, CompleteTagHelperAttributeDescriptorComparer.Default); } - public new int GetHashCode(TagHelperDescriptor descriptor) + int IEqualityComparer.GetHashCode(TagHelperDescriptor descriptor) { return HashCodeCombiner.Start() .Add(base.GetHashCode()) diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorFactoryTest.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorFactoryTest.cs index 0338609ece..eee3922e88 100644 --- a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorFactoryTest.cs +++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorFactoryTest.cs @@ -8,6 +8,85 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers { public class TagHelperDescriptorFactoryTest { + [Fact] + public void CreateDescriptor_OverridesAttributeNameFromAttribute() + { + // Arrange + var validProperty1 = typeof(OverriddenAttributeTagHelper).GetProperty( + nameof(OverriddenAttributeTagHelper.ValidAttribute1)); + var validProperty2 = typeof(OverriddenAttributeTagHelper).GetProperty( + nameof(OverriddenAttributeTagHelper.ValidAttribute2)); + var expectedDescriptors = new[] { + new TagHelperDescriptor( + "OverriddenAttribute", + typeof(OverriddenAttributeTagHelper).FullName, + ContentBehavior.None, + new[] { + new TagHelperAttributeDescriptor("SomethingElse", validProperty1), + new TagHelperAttributeDescriptor("Something-Else", validProperty2) + }) + }; + + // Act + var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(OverriddenAttributeTagHelper)); + + // Assert + Assert.Equal(expectedDescriptors, descriptors, CompleteTagHelperDescriptorComparer.Default); + } + + [Fact] + public void CreateDescriptor_DoesNotInheritOverridenAttributeName() + { + // Arrange + var validProperty1 = typeof(InheritedOverriddenAttributeTagHelper).GetProperty( + nameof(InheritedOverriddenAttributeTagHelper.ValidAttribute1)); + var validProperty2 = typeof(InheritedOverriddenAttributeTagHelper).GetProperty( + nameof(InheritedOverriddenAttributeTagHelper.ValidAttribute2)); + var expectedDescriptors = new[] { + new TagHelperDescriptor( + "InheritedOverriddenAttribute", + typeof(InheritedOverriddenAttributeTagHelper).FullName, + ContentBehavior.None, + new[] { + new TagHelperAttributeDescriptor(nameof(InheritedOverriddenAttributeTagHelper.ValidAttribute1), + validProperty1), + new TagHelperAttributeDescriptor("Something-Else", validProperty2) + }) + }; + + // Act + var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(InheritedOverriddenAttributeTagHelper)); + + // Assert + Assert.Equal(expectedDescriptors, descriptors, CompleteTagHelperDescriptorComparer.Default); + } + + [Fact] + public void CreateDescriptor_AllowsOverridenAttributeNameOnUnimplementedVirtual() + { + // Arrange + var validProperty1 = typeof(InheritedNotOverriddenAttributeTagHelper).GetProperty( + nameof(InheritedNotOverriddenAttributeTagHelper.ValidAttribute1)); + var validProperty2 = typeof(InheritedNotOverriddenAttributeTagHelper).GetProperty( + nameof(InheritedNotOverriddenAttributeTagHelper.ValidAttribute2)); + var expectedDescriptors = new[] { + new TagHelperDescriptor( + "InheritedNotOverriddenAttribute", + typeof(InheritedNotOverriddenAttributeTagHelper).FullName, + ContentBehavior.None, + new[] { + new TagHelperAttributeDescriptor("SomethingElse", validProperty1), + new TagHelperAttributeDescriptor("Something-Else", validProperty2) + }) + }; + + // Act + var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(InheritedNotOverriddenAttributeTagHelper)); + + // Assert + Assert.Equal(expectedDescriptors, descriptors, CompleteTagHelperDescriptorComparer.Default); + } + [Fact] public void CreateDescriptor_BuildsDescriptorsFromSimpleTypes() { @@ -19,7 +98,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers // Assert var descriptor = Assert.Single(descriptors); - Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, CompleteTagHelperDescriptorComparer.Default); } [Fact] @@ -41,7 +120,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers // Assert var descriptor = Assert.Single(descriptors); - Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, CompleteTagHelperDescriptorComparer.Default); } [Fact] @@ -62,7 +141,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers // Assert var descriptor = Assert.Single(descriptors); - Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, CompleteTagHelperDescriptorComparer.Default); } [Fact] @@ -84,7 +163,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers // Assert var descriptor = Assert.Single(descriptors); - Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, CompleteTagHelperDescriptorComparer.Default); } [Fact] @@ -107,7 +186,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers // Assert var descriptor = Assert.Single(descriptors); - Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, CompleteTagHelperDescriptorComparer.Default); } [Fact] @@ -124,7 +203,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers // Assert var descriptor = Assert.Single(descriptors); - Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, CompleteTagHelperDescriptorComparer.Default); } [Fact] @@ -142,7 +221,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers // Assert var descriptor = Assert.Single(descriptors); - Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, CompleteTagHelperDescriptorComparer.Default); } [Fact] @@ -171,7 +250,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(MultiTagTagHelper)); // Assert - Assert.Equal(descriptors, expectedDescriptors, CompleteTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptors, descriptors, CompleteTagHelperDescriptorComparer.Default); } [Fact] @@ -192,7 +271,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers // Assert var descriptor = Assert.Single(descriptors); - Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, CompleteTagHelperDescriptorComparer.Default); } [Fact] @@ -208,7 +287,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(DuplicateTagNameTagHelper)); // Assert - Assert.Equal(descriptors, expectedDescriptors, CompleteTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptors, descriptors, CompleteTagHelperDescriptorComparer.Default); } [Fact] @@ -225,7 +304,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(OverrideNameTagHelper)); // Assert - Assert.Equal(descriptors, expectedDescriptors, CompleteTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptors, descriptors, CompleteTagHelperDescriptorComparer.Default); } [Fact] @@ -242,7 +321,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(MultipleAttributeTagHelper)); // Assert - Assert.Equal(descriptors, expectedDescriptors, CompleteTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptors, descriptors, CompleteTagHelperDescriptorComparer.Default); } [ContentBehavior(ContentBehavior.Append)] @@ -283,5 +362,23 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers private class InheritedSingleAttributeTagHelper : SingleAttributeTagHelper { } + + private class OverriddenAttributeTagHelper + { + [HtmlAttributeName("SomethingElse")] + public virtual string ValidAttribute1 { get; set; } + + [HtmlAttributeName("Something-Else")] + public string ValidAttribute2 { get; set; } + } + + private class InheritedOverriddenAttributeTagHelper : OverriddenAttributeTagHelper + { + public override string ValidAttribute1 { get; set; } + } + + private class InheritedNotOverriddenAttributeTagHelper : OverriddenAttributeTagHelper + { + } } } \ No newline at end of file