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:
NTaylorMullen 2014-09-28 19:41:14 -07:00 committed by N. Taylor Mullen
parent ec638b147a
commit 72c449bf86
6 changed files with 230 additions and 20 deletions

View File

@ -78,6 +78,22 @@ namespace Microsoft.AspNet.Razor.Runtime
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)
{
var value = _resourceManager.GetString(name);

View File

@ -131,4 +131,7 @@
<data name="ScopeManager_EndCannotBeCalledWithoutACallToBegin" xml:space="preserve">
<value>Must call '{2}.{1}' before calling '{2}.{0}'.</value>
</data>
<data name="TagNameAttribute_AdditionalTagsCannotContainNull" xml:space="preserve">
<value>Parameter {0} must not contain null tag names.</value>
</data>
</root>

View File

@ -24,30 +24,40 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
/// </summary>
/// <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>
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 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<string> GetTagNames(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)

View File

@ -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;
}

View File

@ -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; }
}
}

View File

@ -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
{
}
}
}