Add TagHelper registration system.

- This registration system accepts tag descriptors that it then uses to feed requests for tag helpers.
- Added some infrastructure pieces that are used to build up valid tag helper descriptors.

#70
This commit is contained in:
N. Taylor Mullen 2014-09-08 17:13:12 -07:00
parent a9f6a63e7f
commit 555615f60e
5 changed files with 265 additions and 0 deletions

View File

@ -0,0 +1,12 @@
// 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
{
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
internal sealed class NotNullAttribute : Attribute
{
}
}

View File

@ -0,0 +1,46 @@
// 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.
namespace Microsoft.AspNet.Razor.TagHelpers
{
/// <summary>
/// Defines how a tag helper will utilize its inner HTML.
/// </summary>
public enum ContentBehavior
{
/// <summary>
/// Indicates that the tag helper will not modify its inner HTML in any way. This is the default
/// <see cref="ContentBehavior"/>.
/// </summary>
/// <remarks>Children of the current tag helper will execute after the current tag helper.</remarks>
None,
/// <summary>
/// Indicates that the tag helper wants anything within its tag builder's inner HTML to be
/// appended to content its children generate.
/// </summary>
/// <remarks>Children of the current tag helper will execute before the current tag helper.</remarks>
Append,
/// <summary>
/// Indicates that the tag helper will modify its HTML content. Therefore this <see cref="ContentBehavior"/>
/// enables the tag helper to examine the content its children generate.
/// </summary>
/// <remarks>Children of the current tag helper will execute before the current tag helper.</remarks>
Modify,
/// <summary>
/// Indicates that the tag helper wants anything within its tag builder's inner HTML to be
/// prepended to the content its children generate.
/// </summary>
/// <remarks>Children of the current tag helper will execute after the current tag helper.</remarks>
Prepend,
/// <summary>
/// Indicates that the tag helper wants anything within its tag builder's inner HTML to
/// replace any HTML inside of it.
/// </summary>
/// <remarks>Children of the current tag helper will not execute.</remarks>
Replace,
}
}

View File

@ -0,0 +1,47 @@
// 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.Reflection;
namespace Microsoft.AspNet.Razor.TagHelpers
{
/// <summary>
/// A metadata class describing a tag helper attribute.
/// </summary>
public class TagHelperAttributeDescriptor
{
/// <summary>
/// Instantiates a new <see cref="TagHelperAttributeDescriptor"/> class.
/// </summary>
/// <param name="attributeName">The HTML attribute name.</param>
/// <param name="propertyInfo">The <see cref="System.Reflection.PropertyInfo"/> for the tag
/// helper attribute</param>
public TagHelperAttributeDescriptor(string attributeName,
PropertyInfo propertyInfo)
{
AttributeName = attributeName;
PropertyInfo = propertyInfo;
}
/// <summary>
/// The HTML attribute name.
/// </summary>
public string AttributeName { get; private set; }
/// <summary>
/// The name of the CLR property name that corresponds to the HTML attribute name.
/// </summary>
public string AttributePropertyName
{
get
{
return PropertyInfo.Name;
}
}
/// <summary>
/// The <see cref="System.Reflection.PropertyInfo"/> for the tag helper attribute
/// </summary>
public PropertyInfo PropertyInfo { get; private set; }
}
}

View File

@ -0,0 +1,53 @@
// 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 Microsoft.Internal.Web.Utils;
namespace Microsoft.AspNet.Razor.TagHelpers
{
/// <summary>
/// A metadata class describing a tag helper.
/// </summary>
public class TagHelperDescriptor
{
/// <summary>
/// Instantiates a new instance of the <see cref="TagHelperDescriptor"/> class.
/// </summary>
/// <param name="tagName">The tag name that the tag helper targets. '*' indicates a catch-all
/// <see cref="TagHelperDescriptor"/> which applies to every HTML tag.</param>
/// <param name="tagHelperName">The full name of the tag helper class.</param>
/// <param name="contentBehavior">The <see cref="TagHelpers.ContentBehavior"/>
/// of the tag helper.</param>
public TagHelperDescriptor(string tagName,
string tagHelperName,
ContentBehavior contentBehavior)
{
TagName = tagName;
TagHelperName = tagHelperName;
ContentBehavior = contentBehavior;
Attributes = new List<TagHelperAttributeDescriptor>();
}
/// <summary>
/// The tag name that the tag helper should target.
/// </summary>
public string TagName { get; private set; }
/// <summary>
/// The full name of the tag helper class.
/// </summary>
public string TagHelperName { get; private set; }
/// <summary>
/// The <see cref="TagHelpers.ContentBehavior"/> of the tag helper.
/// </summary>
public ContentBehavior ContentBehavior { get; private set; }
/// <summary>
/// The list of attributes the tag helper expects.
/// </summary>
public virtual List<TagHelperAttributeDescriptor> Attributes { get; private set; }
}
}

View File

@ -0,0 +1,107 @@
// 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;
using Microsoft.Internal.Web.Utils;
namespace Microsoft.AspNet.Razor.TagHelpers
{
/// <summary>
/// Enables retrieval of <see cref="TagHelperDescriptor"/>'s.
/// </summary>
public class TagHelperDescriptorProvider
{
private const string CatchAllDescriptorTarget = "*";
private static readonly TagHelperDescriptorComparer DefaultTagHelperDescriptorComparer =
new TagHelperDescriptorComparer();
private IDictionary<string, HashSet<TagHelperDescriptor>> _registrations;
/// <summary>
/// Instantiates a new instance of the <see cref="TagHelperDescriptorProvider"/>.
/// </summary>
/// <param name="descriptors">The descriptors that the <see cref="TagHelperDescriptorProvider"/> will pull from.</param>
public TagHelperDescriptorProvider(IEnumerable<TagHelperDescriptor> descriptors)
{
_registrations = new Dictionary<string, HashSet<TagHelperDescriptor>>(StringComparer.OrdinalIgnoreCase);
// Populate our registrations
foreach (var descriptor in descriptors)
{
Register(descriptor);
}
}
/// <summary>
/// Gets all tag helpers that match the given <paramref name="tagName"/>.
/// </summary>
/// <param name="tagName">The name of the HTML tag to match. Providing a '*' tag name
/// retrieves catch-all <see cref="TagHelperDescriptor"/>s (descriptors that target every tag).</param>
/// <returns><see cref="TagHelperDescriptor"/>s that apply to the given <paramref name="tagName"/>.
/// Will return an empty <see cref="Enumerable" /> if no <see cref="TagHelperDescriptor"/>s are
/// found.</returns>
public IEnumerable<TagHelperDescriptor> GetTagHelpers(string tagName)
{
HashSet<TagHelperDescriptor> descriptors;
// Ensure there's an ISet to use.
if (!_registrations.TryGetValue(CatchAllDescriptorTarget, out descriptors))
{
descriptors = new HashSet<TagHelperDescriptor>();
}
// If the requested tag name is the catch-all target, we should short circuit.
if (tagName.Equals(CatchAllDescriptorTarget, StringComparison.OrdinalIgnoreCase))
{
return descriptors;
}
// If we have a tag name associated with the requested name, return the descriptors +
// all of the catch-all descriptors.
if (_registrations.TryGetValue(tagName, out var matchingDescriptors))
{
return matchingDescriptors.Concat(descriptors);
}
// We couldn't any descriptors associated with the requested tag name, return all
// of the "catch-all" tag descriptors (there may not be any).
return descriptors;
}
private void Register(TagHelperDescriptor descriptor)
{
HashSet<TagHelperDescriptor> descriptorSet;
// Ensure there's a List to add the descriptor to.
if (!_registrations.TryGetValue(descriptor.TagName, out descriptorSet))
{
descriptorSet = new HashSet<TagHelperDescriptor>(DefaultTagHelperDescriptorComparer);
_registrations[descriptor.TagName] = descriptorSet;
}
descriptorSet.Add(descriptor);
}
private class TagHelperDescriptorComparer : IEqualityComparer<TagHelperDescriptor>
{
public bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY)
{
return descriptorX.TagHelperName == descriptorY.TagHelperName &&
descriptorX.TagName == descriptorY.TagName &&
descriptorX.ContentBehavior == descriptorY.ContentBehavior;
}
public int GetHashCode(TagHelperDescriptor descriptor)
{
return HashCodeCombiner.Start()
.Add(descriptor.TagName)
.Add(descriptor.TagHelperName)
.Add(descriptor.ContentBehavior)
.CombinedHash;
}
}
}
}