diff --git a/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs
index e9d4cc8127..f701d0a217 100644
--- a/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs
@@ -78,6 +78,22 @@ namespace Microsoft.AspNet.Razor.Runtime
return string.Format(CultureInfo.CurrentCulture, GetString("ScopeManager_EndCannotBeCalledWithoutACallToBegin"), p0, p1, p2);
}
+ ///
+ /// Parameter {0} must not contain null tag names.
+ ///
+ internal static string TagNameAttribute_AdditionalTagsCannotContainNull
+ {
+ get { return GetString("TagNameAttribute_AdditionalTagsCannotContainNull"); }
+ }
+
+ ///
+ /// Parameter {0} must not contain null tag names.
+ ///
+ internal static string FormatTagNameAttribute_AdditionalTagsCannotContainNull(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("TagNameAttribute_AdditionalTagsCannotContainNull"), p0);
+ }
+
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 89259d17d4..8e0b4485bb 100644
--- a/src/Microsoft.AspNet.Razor.Runtime/Resources.resx
+++ b/src/Microsoft.AspNet.Razor.Runtime/Resources.resx
@@ -131,4 +131,7 @@
Must call '{2}.{1}' before calling '{2}.{0}'.
+
+ Parameter {0} must not contain null tag names.
+
\ 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 fd1b02343c..6113eed120 100644
--- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs
+++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs
@@ -24,30 +24,40 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
///
/// The type to create a from.
/// A that describes the given .
- public static TagHelperDescriptor CreateDescriptor(Type type)
+ public static IEnumerable CreateDescriptors(Type type)
{
- var tagName = GetTagName(type);
+ var tagNames = GetTagNames(type);
var typeName = type.FullName;
var attributeDescriptors = GetAttributeDescriptors(type);
var contentBehavior = GetContentBehavior(type);
- return new TagHelperDescriptor(tagName,
- typeName,
- contentBehavior,
- attributeDescriptors);
+ return tagNames.Select(tagName =>
+ new TagHelperDescriptor(tagName,
+ typeName,
+ contentBehavior,
+ attributeDescriptors));
}
- // TODO: Make this method support TagNameAttribute tag names: https://github.com/aspnet/Razor/issues/120
- private static string GetTagName(Type tagHelperType)
+ private static IEnumerable GetTagNames(Type tagHelperType)
{
- var name = tagHelperType.Name;
+ var typeInfo = tagHelperType.GetTypeInfo();
+ var attributes = typeInfo.GetCustomAttributes(inherit: false);
- if (name.EndsWith(TagHelperNameEnding, StringComparison.OrdinalIgnoreCase))
+ // If there isn't an attribute specifying the tag name derive it from the name
+ if (!attributes.Any())
{
- name = name.Substring(0, name.Length - TagHelperNameEnding.Length);
+ var name = typeInfo.Name;
+
+ if (name.EndsWith(TagHelperNameEnding, StringComparison.OrdinalIgnoreCase))
+ {
+ name = name.Substring(0, name.Length - TagHelperNameEnding.Length);
+ }
+
+ return new[] { name };
}
- return name;
+ // Remove duplicate tag names.
+ return attributes.SelectMany(attribute => attribute.Tags).Distinct();
}
private static IEnumerable GetAttributeDescriptors(Type type)
diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorResolver.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorResolver.cs
index 3cd8a5c2c9..fd9cff000b 100644
--- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorResolver.cs
+++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorResolver.cs
@@ -48,7 +48,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
var tagHelperTypes = ResolveTagHelperTypes(lookupStrings);
// Convert types to TagHelperDescriptors
- var descriptors = tagHelperTypes.Select(TagHelperDescriptorFactory.CreateDescriptor);
+ var descriptors = tagHelperTypes.SelectMany(TagHelperDescriptorFactory.CreateDescriptors);
return descriptors;
}
diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagNameAttribute.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagNameAttribute.cs
new file mode 100644
index 0000000000..91d06d193f
--- /dev/null
+++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagNameAttribute.cs
@@ -0,0 +1,50 @@
+// 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 System.Linq;
+
+namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
+{
+ ///
+ /// Used to override a 's default tag name target.
+ ///
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
+ public sealed class TagNameAttribute : Attribute
+ {
+ ///
+ /// Instantiates a new instance of the class.
+ ///
+ /// The HTML tag name for the to target.
+ public TagNameAttribute([NotNull] string tag)
+ {
+ Tags = new[] { tag };
+ }
+
+ ///
+ /// Instantiates a new instance of the class.
+ ///
+ /// The HTML tag name for the to target.
+ /// Additional HTML tag names for the to target.
+ public TagNameAttribute([NotNull] string tag, [NotNull] params string[] additionalTags)
+ {
+ if (additionalTags.Contains(null))
+ {
+ throw new ArgumentNullException(
+ nameof(additionalTags),
+ Resources.FormatTagNameAttribute_AdditionalTagsCannotContainNull(nameof(additionalTags)));
+ };
+
+ var allTags = new List(additionalTags);
+ allTags.Add(tag);
+
+ Tags = allTags;
+ }
+
+ ///
+ /// An of tag names for the to target.
+ ///
+ public IEnumerable Tags { get; private set; }
+ }
+}
\ 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 6fd0813131..8aee286fd6 100644
--- a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorFactoryTest.cs
+++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorFactoryTest.cs
@@ -15,9 +15,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
var expectedDescriptor = new TagHelperDescriptor("Object", "System.Object", ContentBehavior.None);
// Act
- var descriptor = TagHelperDescriptorFactory.CreateDescriptor(typeof(object));
+ var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(object));
// Assert
+ var descriptor = Assert.Single(descriptors);
Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default);
}
@@ -35,9 +36,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
});
// Act
- var descriptor = TagHelperDescriptorFactory.CreateDescriptor(typeof(SingleAttributeTagHelper));
+ var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(SingleAttributeTagHelper));
// Assert
+ var descriptor = Assert.Single(descriptors);
Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default);
}
@@ -56,9 +58,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
});
// Act
- var descriptor = TagHelperDescriptorFactory.CreateDescriptor(typeof(MissingAccessorTagHelper));
+ var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(MissingAccessorTagHelper));
// Assert
+ var descriptor = Assert.Single(descriptors);
Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default);
}
@@ -78,13 +81,13 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
});
// Act
- var descriptor = TagHelperDescriptorFactory.CreateDescriptor(typeof(PrivateAccessorTagHelper));
+ var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(PrivateAccessorTagHelper));
// Assert
+ var descriptor = Assert.Single(descriptors);
Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default);
}
-
[Fact]
public void CreateDescriptor_ResolvesCustomContentBehavior()
{
@@ -95,9 +98,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
ContentBehavior.Append);
// Act
- var descriptor = TagHelperDescriptorFactory.CreateDescriptor(typeof(CustomContentBehaviorTagHelper));
+ var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(CustomContentBehaviorTagHelper));
// Assert
+ var descriptor = Assert.Single(descriptors);
Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default);
}
@@ -111,13 +115,114 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
ContentBehavior.None);
// Act
- var descriptor = TagHelperDescriptorFactory.CreateDescriptor(
+ var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
typeof(InheritedCustomContentBehaviorTagHelper));
// Assert
+ var descriptor = Assert.Single(descriptors);
Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default);
}
+ [Fact]
+ public void CreateDescriptor_ResolvesMultipleTagHelperDescriptorsFromSingleType()
+ {
+ // Arrange
+ var validProp = typeof(MultiTagTagHelper).GetProperty(nameof(MultiTagTagHelper.ValidAttribute));
+ var expectedDescriptors = new[] {
+ new TagHelperDescriptor(
+ "div",
+ typeof(MultiTagTagHelper).FullName,
+ ContentBehavior.None,
+ new[] {
+ new TagHelperAttributeDescriptor(nameof(MultiTagTagHelper.ValidAttribute), validProp)
+ }),
+ new TagHelperDescriptor(
+ "p",
+ typeof(MultiTagTagHelper).FullName,
+ ContentBehavior.None,
+ new[] {
+ new TagHelperAttributeDescriptor(nameof(MultiTagTagHelper.ValidAttribute), validProp)
+ })
+ };
+
+ // Act
+ var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(MultiTagTagHelper));
+
+ // Assert
+ Assert.Equal(descriptors, expectedDescriptors, CompleteTagHelperDescriptorComparer.Default);
+ }
+
+ [Fact]
+ public void CreateDescriptor_DoesntResolveInheritedTagNames()
+ {
+ // Arrange
+ var validProp = typeof(InheritedMultiTagTagHelper).GetProperty(nameof(InheritedMultiTagTagHelper.ValidAttribute));
+ var expectedDescriptor = new TagHelperDescriptor(
+ "InheritedMultiTag",
+ typeof(InheritedMultiTagTagHelper).FullName,
+ ContentBehavior.None,
+ new[] {
+ new TagHelperAttributeDescriptor(nameof(InheritedMultiTagTagHelper.ValidAttribute), validProp)
+ });
+
+ // Act
+ var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(InheritedMultiTagTagHelper));
+
+ // Assert
+ var descriptor = Assert.Single(descriptors);
+ Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default);
+ }
+
+ [Fact]
+ public void CreateDescriptor_IgnoresDuplicateTagNamesFromAttribute()
+ {
+ // Arrange
+ var expectedDescriptors = new[] {
+ new TagHelperDescriptor("p", typeof(DuplicateTagNameTagHelper).FullName, ContentBehavior.None),
+ new TagHelperDescriptor("div", typeof(DuplicateTagNameTagHelper).FullName, ContentBehavior.None)
+ };
+
+ // Act
+ var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(DuplicateTagNameTagHelper));
+
+ // Assert
+ Assert.Equal(descriptors, expectedDescriptors, CompleteTagHelperDescriptorComparer.Default);
+ }
+
+ [Fact]
+ public void CreateDescriptor_OverridesTagNameFromAttribute()
+ {
+ // Arrange
+ var expectedDescriptors = new[] {
+ new TagHelperDescriptor("data-condition",
+ typeof(OverrideNameTagHelper).FullName,
+ ContentBehavior.None),
+ };
+
+ // Act
+ var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(OverrideNameTagHelper));
+
+ // Assert
+ Assert.Equal(descriptors, expectedDescriptors, CompleteTagHelperDescriptorComparer.Default);
+ }
+
+ [Fact]
+ public void CreateDescriptor_GetsTagNamesFromMultipleAttributes()
+ {
+ // Arrange
+ var expectedDescriptors = new[] {
+ new TagHelperDescriptor("span", typeof(MultipleAttributeTagHelper).FullName, ContentBehavior.None),
+ new TagHelperDescriptor("p", typeof(MultipleAttributeTagHelper).FullName, ContentBehavior.None),
+ new TagHelperDescriptor("div", typeof(MultipleAttributeTagHelper).FullName, ContentBehavior.None)
+ };
+
+ // Act
+ var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(MultipleAttributeTagHelper));
+
+ // Assert
+ Assert.Equal(descriptors, expectedDescriptors, CompleteTagHelperDescriptorComparer.Default);
+ }
+
[ContentBehavior(ContentBehavior.Append)]
private class CustomContentBehaviorTagHelper
{
@@ -126,5 +231,31 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
private class InheritedCustomContentBehaviorTagHelper : CustomContentBehaviorTagHelper
{
}
+
+ [TagName("p", "div")]
+ private class MultiTagTagHelper
+ {
+ public string ValidAttribute { get; set; }
+ }
+
+ private class InheritedMultiTagTagHelper : MultiTagTagHelper
+ {
+ }
+
+ [TagName("p", "p", "div", "div")]
+ private class DuplicateTagNameTagHelper
+ {
+ }
+
+ [TagName("data-condition")]
+ private class OverrideNameTagHelper
+ {
+ }
+
+ [TagName("span")]
+ [TagName("div", "p")]
+ private class MultipleAttributeTagHelper
+ {
+ }
}
}
\ No newline at end of file