Add TagName attribute for tag helpers.
- Made it so the TagHelperDescriptorFactory could pull tag name targets from TagNameAttributes on types. - Tested the behavior between TagNameAttribute and TagHelperDescriptorFactory (the only consumer of the attribute). - Made sure to test duplicate and inherited class scenarios. #120
This commit is contained in:
parent
ec638b147a
commit
72c449bf86
|
|
@ -78,6 +78,22 @@ namespace Microsoft.AspNet.Razor.Runtime
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("ScopeManager_EndCannotBeCalledWithoutACallToBegin"), p0, p1, p2);
|
return string.Format(CultureInfo.CurrentCulture, GetString("ScopeManager_EndCannotBeCalledWithoutACallToBegin"), p0, p1, p2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parameter {0} must not contain null tag names.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagNameAttribute_AdditionalTagsCannotContainNull
|
||||||
|
{
|
||||||
|
get { return GetString("TagNameAttribute_AdditionalTagsCannotContainNull"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parameter {0} must not contain null tag names.
|
||||||
|
/// </summary>
|
||||||
|
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)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -131,4 +131,7 @@
|
||||||
<data name="ScopeManager_EndCannotBeCalledWithoutACallToBegin" xml:space="preserve">
|
<data name="ScopeManager_EndCannotBeCalledWithoutACallToBegin" xml:space="preserve">
|
||||||
<value>Must call '{2}.{1}' before calling '{2}.{0}'.</value>
|
<value>Must call '{2}.{1}' before calling '{2}.{0}'.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TagNameAttribute_AdditionalTagsCannotContainNull" xml:space="preserve">
|
||||||
|
<value>Parameter {0} must not contain null tag names.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -24,30 +24,40 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="type">The type to create a <see cref="TagHelperDescriptor"/> from.</param>
|
/// <param name="type">The type to create a <see cref="TagHelperDescriptor"/> from.</param>
|
||||||
/// <returns>A <see cref="TagHelperDescriptor"/> that describes the given <paramref name="type"/>.</returns>
|
/// <returns>A <see cref="TagHelperDescriptor"/> that describes the given <paramref name="type"/>.</returns>
|
||||||
public static TagHelperDescriptor CreateDescriptor(Type type)
|
public static IEnumerable<TagHelperDescriptor> CreateDescriptors(Type type)
|
||||||
{
|
{
|
||||||
var tagName = GetTagName(type);
|
var tagNames = GetTagNames(type);
|
||||||
var typeName = type.FullName;
|
var typeName = type.FullName;
|
||||||
var attributeDescriptors = GetAttributeDescriptors(type);
|
var attributeDescriptors = GetAttributeDescriptors(type);
|
||||||
var contentBehavior = GetContentBehavior(type);
|
var contentBehavior = GetContentBehavior(type);
|
||||||
|
|
||||||
return new TagHelperDescriptor(tagName,
|
return tagNames.Select(tagName =>
|
||||||
typeName,
|
new TagHelperDescriptor(tagName,
|
||||||
contentBehavior,
|
typeName,
|
||||||
attributeDescriptors);
|
contentBehavior,
|
||||||
|
attributeDescriptors));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make this method support TagNameAttribute tag names: https://github.com/aspnet/Razor/issues/120
|
private static IEnumerable<string> GetTagNames(Type tagHelperType)
|
||||||
private static string GetTagName(Type tagHelperType)
|
|
||||||
{
|
{
|
||||||
var name = tagHelperType.Name;
|
var typeInfo = tagHelperType.GetTypeInfo();
|
||||||
|
var attributes = typeInfo.GetCustomAttributes<TagNameAttribute>(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<TagHelperAttributeDescriptor> GetAttributeDescriptors(Type type)
|
private static IEnumerable<TagHelperAttributeDescriptor> GetAttributeDescriptors(Type type)
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
var tagHelperTypes = ResolveTagHelperTypes(lookupStrings);
|
var tagHelperTypes = ResolveTagHelperTypes(lookupStrings);
|
||||||
|
|
||||||
// Convert types to TagHelperDescriptors
|
// Convert types to TagHelperDescriptors
|
||||||
var descriptors = tagHelperTypes.Select(TagHelperDescriptorFactory.CreateDescriptor);
|
var descriptors = tagHelperTypes.SelectMany(TagHelperDescriptorFactory.CreateDescriptors);
|
||||||
|
|
||||||
return descriptors;
|
return descriptors;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to override a <see cref="ITagHelper"/>'s default tag name target.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||||
|
public sealed class TagNameAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of the <see cref="TagNameAttribute"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tag">The HTML tag name for the <see cref="TagHelper"/> to target.</param>
|
||||||
|
public TagNameAttribute([NotNull] string tag)
|
||||||
|
{
|
||||||
|
Tags = new[] { tag };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of the <see cref="TagNameAttribute"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tag">The HTML tag name for the <see cref="TagHelper"/> to target.</param>
|
||||||
|
/// <param name="additionalTags">Additional HTML tag names for the <see cref="TagHelper"/> to target.</param>
|
||||||
|
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<string>(additionalTags);
|
||||||
|
allTags.Add(tag);
|
||||||
|
|
||||||
|
Tags = allTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="IEnumerable{string}"/> of tag names for the <see cref="TagHelper"/> to target.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<string> Tags { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,9 +15,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
var expectedDescriptor = new TagHelperDescriptor("Object", "System.Object", ContentBehavior.None);
|
var expectedDescriptor = new TagHelperDescriptor("Object", "System.Object", ContentBehavior.None);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var descriptor = TagHelperDescriptorFactory.CreateDescriptor(typeof(object));
|
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(object));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
var descriptor = Assert.Single(descriptors);
|
||||||
Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default);
|
Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,9 +36,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var descriptor = TagHelperDescriptorFactory.CreateDescriptor(typeof(SingleAttributeTagHelper));
|
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(SingleAttributeTagHelper));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
var descriptor = Assert.Single(descriptors);
|
||||||
Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default);
|
Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,9 +58,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var descriptor = TagHelperDescriptorFactory.CreateDescriptor(typeof(MissingAccessorTagHelper));
|
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(MissingAccessorTagHelper));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
var descriptor = Assert.Single(descriptors);
|
||||||
Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default);
|
Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,13 +81,13 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var descriptor = TagHelperDescriptorFactory.CreateDescriptor(typeof(PrivateAccessorTagHelper));
|
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(PrivateAccessorTagHelper));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
var descriptor = Assert.Single(descriptors);
|
||||||
Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default);
|
Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CreateDescriptor_ResolvesCustomContentBehavior()
|
public void CreateDescriptor_ResolvesCustomContentBehavior()
|
||||||
{
|
{
|
||||||
|
|
@ -95,9 +98,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
ContentBehavior.Append);
|
ContentBehavior.Append);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var descriptor = TagHelperDescriptorFactory.CreateDescriptor(typeof(CustomContentBehaviorTagHelper));
|
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(typeof(CustomContentBehaviorTagHelper));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
var descriptor = Assert.Single(descriptors);
|
||||||
Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default);
|
Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,13 +115,114 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
ContentBehavior.None);
|
ContentBehavior.None);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var descriptor = TagHelperDescriptorFactory.CreateDescriptor(
|
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
|
||||||
typeof(InheritedCustomContentBehaviorTagHelper));
|
typeof(InheritedCustomContentBehaviorTagHelper));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
var descriptor = Assert.Single(descriptors);
|
||||||
Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default);
|
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)]
|
[ContentBehavior(ContentBehavior.Append)]
|
||||||
private class CustomContentBehaviorTagHelper
|
private class CustomContentBehaviorTagHelper
|
||||||
{
|
{
|
||||||
|
|
@ -126,5 +231,31 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
private class InheritedCustomContentBehaviorTagHelper : CustomContentBehaviorTagHelper
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue