Add TagHelper documentation resolution to TagHelperDescriptorResolver.

- Added TagHelperUseageDescriptor and associated factory for the TagHelperDescriptorFactory to utilize.
- TagHelperUseageDescriptors are only created during design time.
- CoreCLR is not supported for XML documentation resolution for now. Can revisit this later when we have better tooling integration with CoreCLR.

#352
This commit is contained in:
N. Taylor Mullen 2015-06-08 12:20:35 -07:00
parent 361a53ba3c
commit ccf8433f27
10 changed files with 455 additions and 110 deletions

View File

@ -38,23 +38,30 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
/// </summary>
/// <param name="assemblyName">The assembly name that contains <paramref name="type"/>.</param>
/// <param name="type">The type to create a <see cref="TagHelperDescriptor"/> from.</param>
/// <param name="designTime">Indicates if the returned <see cref="TagHelperDescriptor"/>s should include
/// design time specific information.</param>
/// <param name="errorSink">The <see cref="ErrorSink"/> used to collect <see cref="RazorError"/>s encountered
/// when creating <see cref="TagHelperDescriptor"/>s for the given <paramref name="type"/>.</param>
/// <returns>
/// A collection of <see cref="TagHelperDescriptor"/>s that describe the given <paramref name="type"/>.
/// </returns>
public static IEnumerable<TagHelperDescriptor> CreateDescriptors(
string assemblyName,
[NotNull] Type type,
bool designTime,
[NotNull] ErrorSink errorSink)
{
var typeInfo = type.GetTypeInfo();
var attributeDescriptors = GetAttributeDescriptors(type, errorSink);
var attributeDescriptors = GetAttributeDescriptors(type, designTime, errorSink);
var targetElementAttributes = GetValidTargetElementAttributes(typeInfo, errorSink);
var tagHelperDescriptors =
BuildTagHelperDescriptors(
typeInfo,
assemblyName,
attributeDescriptors,
targetElementAttributes);
targetElementAttributes,
designTime);
return tagHelperDescriptors.Distinct(TagHelperDescriptorComparer.Default);
}
@ -72,8 +79,18 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
TypeInfo typeInfo,
string assemblyName,
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
IEnumerable<TargetElementAttribute> targetElementAttributes)
IEnumerable<TargetElementAttribute> targetElementAttributes,
bool designTime)
{
TagHelperUsageDescriptor typeUsageDescriptor = null;
#if !DNXCORE50
if (designTime)
{
typeUsageDescriptor = TagHelperUsageDescriptorFactory.CreateDescriptor(typeInfo.GetType());
}
#endif
var typeName = typeInfo.FullName;
// If there isn't an attribute specifying the tag name derive it from the name
@ -93,19 +110,27 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
typeName,
assemblyName,
attributeDescriptors,
requiredAttributes: Enumerable.Empty<string>())
requiredAttributes: Enumerable.Empty<string>(),
usageDescriptor: typeUsageDescriptor)
};
}
return targetElementAttributes.Select(
attribute => BuildTagHelperDescriptor(typeName, assemblyName, attributeDescriptors, attribute));
attribute =>
BuildTagHelperDescriptor(
typeName,
assemblyName,
attributeDescriptors,
attribute,
typeUsageDescriptor));
}
private static TagHelperDescriptor BuildTagHelperDescriptor(
string typeName,
string assemblyName,
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
TargetElementAttribute targetElementAttribute)
TargetElementAttribute targetElementAttribute,
TagHelperUsageDescriptor usageDescriptor)
{
var requiredAttributes = GetCommaSeparatedValues(targetElementAttribute.Attributes);
@ -114,7 +139,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
typeName,
assemblyName,
attributeDescriptors,
requiredAttributes);
requiredAttributes,
usageDescriptor);
}
private static TagHelperDescriptor BuildTagHelperDescriptor(
@ -122,7 +148,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
string typeName,
string assemblyName,
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
IEnumerable<string> requiredAttributes)
IEnumerable<string> requiredAttributes,
TagHelperUsageDescriptor usageDescriptor)
{
return new TagHelperDescriptor(
prefix: string.Empty,
@ -130,7 +157,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
typeName: typeName,
assemblyName: assemblyName,
attributes: attributeDescriptors,
requiredAttributes: requiredAttributes);
requiredAttributes: requiredAttributes,
usageDescriptor: usageDescriptor);
}
/// <summary>
@ -225,6 +253,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
private static IEnumerable<TagHelperAttributeDescriptor> GetAttributeDescriptors(
Type type,
bool designTime,
ErrorSink errorSink)
{
var accessibleProperties = type.GetRuntimeProperties().Where(IsAccessibleProperty);
@ -236,7 +265,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
foreach (var property in accessibleProperties)
{
var attributeNameAttribute = property.GetCustomAttribute<HtmlAttributeNameAttribute>(inherit: false);
var descriptor = ToAttributeDescriptor(property, attributeNameAttribute);
var descriptor = ToAttributeDescriptor(property, attributeNameAttribute, designTime);
if (ValidateTagHelperAttributeDescriptor(descriptor, type, errorSink))
{
bool isInvalid;
@ -246,6 +275,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
parentType: type,
errorSink: errorSink,
defaultPrefix: descriptor.Name + "-",
designTime: designTime,
isInvalid: out isInvalid);
if (indexerDescriptor != null &&
@ -358,17 +388,19 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
private static TagHelperAttributeDescriptor ToAttributeDescriptor(
PropertyInfo property,
HtmlAttributeNameAttribute attributeNameAttribute)
HtmlAttributeNameAttribute attributeNameAttribute,
bool designTime)
{
var attributeName = attributeNameAttribute != null ?
attributeNameAttribute.Name :
ToHtmlCase(property.Name);
return new TagHelperAttributeDescriptor(
return ToAttributeDescriptor(
property,
attributeName,
property.Name,
property.PropertyType.FullName,
isIndexer: false);
isIndexer: false,
designTime: designTime);
}
private static TagHelperAttributeDescriptor ToIndexerAttributeDescriptor(
@ -377,6 +409,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
Type parentType,
ErrorSink errorSink,
string defaultPrefix,
bool designTime,
out bool isInvalid)
{
isInvalid = false;
@ -414,11 +447,36 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
return null;
}
return new TagHelperAttributeDescriptor(
name: prefix,
propertyName: property.Name,
return ToAttributeDescriptor(
property,
attributeName: prefix,
typeName: dictionaryTypeArguments[1].FullName,
isIndexer: true);
isIndexer: true,
designTime: designTime);
}
private static TagHelperAttributeDescriptor ToAttributeDescriptor(
PropertyInfo property,
string attributeName,
string typeName,
bool isIndexer,
bool designTime)
{
TagHelperUsageDescriptor propertyUsageDescriptor = null;
#if !DNXCORE50
if (designTime)
{
propertyUsageDescriptor = TagHelperUsageDescriptorFactory.CreateDescriptor(property);
}
#endif
return new TagHelperAttributeDescriptor(
attributeName,
property.Name,
typeName,
isIndexer,
propertyUsageDescriptor);
}
private static bool IsAccessibleProperty(PropertyInfo property)

View File

@ -26,14 +26,16 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
};
private readonly TagHelperTypeResolver _typeResolver;
private readonly bool _designTime;
/// <summary>
/// Instantiates a new instance of the <see cref="TagHelperDescriptorResolver"/> class.
/// </summary>
public TagHelperDescriptorResolver()
: this(new TagHelperTypeResolver())
/// <param name="designTime">Indicates whether resolved <see cref="TagHelperDescriptor"/>s should include
/// design time specific information.</param>
public TagHelperDescriptorResolver(bool designTime)
: this(new TagHelperTypeResolver(), designTime)
{
}
/// <summary>
@ -41,9 +43,12 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
/// specified <paramref name="typeResolver"/>.
/// </summary>
/// <param name="typeResolver">The <see cref="TagHelperTypeResolver"/>.</param>
public TagHelperDescriptorResolver(TagHelperTypeResolver typeResolver)
/// <param name="designTime">Indicates whether resolved <see cref="TagHelperDescriptor"/>s should include
/// design time specific information.</param>
public TagHelperDescriptorResolver(TagHelperTypeResolver typeResolver, bool designTime)
{
_typeResolver = typeResolver;
_designTime = designTime;
}
/// <inheritdoc />
@ -113,7 +118,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
/// The name of the assembly to resolve <see cref="TagHelperDescriptor"/>s from.
/// </param>
/// <param name="documentLocation">The <see cref="SourceLocation"/> of the directive.</param>
/// <param name="errorSink">Used to record errors found when resolving <see cref="TagHelperDescriptor"/>s
/// <param name="errorSink">Used to record errors found when resolving <see cref="TagHelperDescriptor"/>s
/// within the given <paramref name="assemblyName"/>.</param>
/// <returns><see cref="TagHelperDescriptor"/>s for <see cref="ITagHelper"/>s from the given
/// <paramref name="assemblyName"/>.</returns>
@ -128,7 +133,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Convert types to TagHelperDescriptors
var descriptors = tagHelperTypes.SelectMany(
type => TagHelperDescriptorFactory.CreateDescriptors(assemblyName, type, errorSink));
type => TagHelperDescriptorFactory.CreateDescriptors(assemblyName, type, _designTime, errorSink));
return descriptors;
}
@ -148,7 +153,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
descriptor.TypeName,
descriptor.AssemblyName,
descriptor.Attributes,
descriptor.RequiredAttributes));
descriptor.RequiredAttributes,
descriptor.UsageDescriptor));
}
return descriptors;
@ -222,7 +228,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// We need to escape the TypePattern so we can choose to only allow specific regex.
var escaped = Regex.Escape(lookupInfo.TypePattern);
// We surround the escaped with ^ and $ in order ot ensure a regex match matches the entire
// We surround the escaped with ^ and $ in order ot ensure a regex match matches the entire
// string. We also replace any '*' or '?' characters with regex to match appropriate content.
// '*' matches 0 or more characters lazily and '?' matches 1 character.
var pattern = "^" + escaped.Replace(@"\?", ".").Replace(@"\*", ".*?") + "$";

View File

@ -0,0 +1,162 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if !DNXCORE50 // Cannot accurately resolve the location of the documentation XML file in coreclr.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
/// <summary>
/// Factory for providing <see cref="TagHelperUsageDescriptor"/>s from <see cref="Type"/>s and
/// <see cref="PropertyInfo"/>s.
/// </summary>
public static class TagHelperUsageDescriptorFactory
{
/// <summary>
/// Creates a <see cref="TagHelperUsageDescriptor"/> from the given <paramref name="type"/>.
/// </summary>
/// <param name="type">The <see cref="Type"/> to create a <see cref="TagHelperUsageDescriptor"/> from.</param>
/// <returns>A <see cref="TagHelperUsageDescriptor"/> that describes the summary and remarks XML documentation
/// for the given <paramref name="type"/>.</returns>
public static TagHelperUsageDescriptor CreateDescriptor([NotNull] Type type)
{
var id = XmlDocumentationProvider.GetId(type);
return CreateDescriptorCore(type.Assembly, id);
}
/// <summary>
/// Creates a <see cref="TagHelperUsageDescriptor"/> from the given <paramref name="propertyInfo"/>.
/// </summary>
/// <param name="propertyInfo">The <see cref="PropertyInfo"/> to create a
/// <see cref="TagHelperUsageDescriptor"/> from.</param>
/// <returns>A <see cref="TagHelperUsageDescriptor"/> that describes the summary and remarks XML documentation
/// for the given <paramref name="propertyInfo"/>.</returns>
public static TagHelperUsageDescriptor CreateDescriptor([NotNull] PropertyInfo propertyInfo)
{
var id = XmlDocumentationProvider.GetId(propertyInfo);
var declaringAssembly = propertyInfo.DeclaringType.Assembly;
return CreateDescriptorCore(declaringAssembly, id);
}
private static TagHelperUsageDescriptor CreateDescriptorCore(Assembly assembly, string id)
{
var assemblyLocation = assembly.Location;
if (string.IsNullOrEmpty(assemblyLocation) && !string.IsNullOrEmpty(assembly.CodeBase))
{
var uri = new UriBuilder(assembly.CodeBase);
// Normalize the path to a UNC path. This will remove things like file:// from start of the uri.Path.
assemblyLocation = Uri.UnescapeDataString(uri.Path);
}
// Couldn't resolve a valid assemblyLocation.
if (string.IsNullOrEmpty(assemblyLocation))
{
return null;
}
var xmlDocumentationFile = GetXmlDocumentationFile(assembly, assemblyLocation);
// Only want to process the file if it exists.
if (xmlDocumentationFile != null)
{
var documentationProvider = new XmlDocumentationProvider(xmlDocumentationFile.FullName);
var summary = documentationProvider.GetSummary(id);
var remarks = documentationProvider.GetRemarks(id);
if (!string.IsNullOrEmpty(summary) || !string.IsNullOrEmpty(remarks))
{
return new TagHelperUsageDescriptor(summary, remarks);
}
}
return null;
}
private static FileInfo GetXmlDocumentationFile(Assembly assembly, string assemblyLocation)
{
try
{
var assemblyDirectory = Path.GetDirectoryName(assemblyLocation);
var assemblyName = Path.GetFileName(assemblyLocation);
var assemblyXmlDocumentationName = Path.ChangeExtension(assemblyName, ".xml");
// Check for a localized XML file for the current culture.
var xmlDocumentationFile = GetLocalizedXmlDocumentationFile(
CultureInfo.CurrentCulture,
assemblyDirectory,
assemblyXmlDocumentationName);
if (xmlDocumentationFile == null)
{
// Check for a culture-neutral XML file next to the assembly
xmlDocumentationFile = new FileInfo(
Path.Combine(assemblyDirectory, assemblyXmlDocumentationName));
if (!xmlDocumentationFile.Exists)
{
xmlDocumentationFile = null;
}
}
return xmlDocumentationFile;
}
catch (ArgumentException)
{
// Could not resolve XML file.
return null;
}
}
private static IEnumerable<string> ExpandPaths(
CultureInfo culture,
string assemblyDirectory,
string assemblyXmlDocumentationName)
{
// Following the fall-back process defined by:
// https://msdn.microsoft.com/en-us/library/sb6a8618.aspx#cpconpackagingdeployingresourcesanchor1
do
{
var cultureName = culture.Name;
var cultureSpecificFileName =
Path.ChangeExtension(assemblyXmlDocumentationName, cultureName + ".xml");
// Look for a culture specific XML file next to the assembly.
yield return Path.Combine(assemblyDirectory, cultureSpecificFileName);
// Look for an XML file with the same name as the assembly in a culture specific directory.
yield return Path.Combine(assemblyDirectory, cultureName, assemblyXmlDocumentationName);
// Look for a culture specific XML file in a culture specific directory.
yield return Path.Combine(assemblyDirectory, cultureName, cultureSpecificFileName);
culture = culture.Parent;
} while (culture != null && culture != CultureInfo.InvariantCulture);
}
private static FileInfo GetLocalizedXmlDocumentationFile(
CultureInfo culture,
string assemblyDirectory,
string assemblyXmlDocumentationName)
{
var localizedXmlPaths = ExpandPaths(culture, assemblyDirectory, assemblyXmlDocumentationName);
var xmlDocumentationFile = localizedXmlPaths
.Select(path => new FileInfo(path))
.FirstOrDefault(file => file.Exists);
return xmlDocumentationFile;
}
}
}
#endif

View File

@ -0,0 +1,120 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if !DNXCORE50
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml.Linq;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Razor.Runtime
{
/// <summary>
/// Extracts summary and remarks XML documentation from an XML documentation file.
/// </summary>
public class XmlDocumentationProvider
{
private readonly IEnumerable<XElement> _members;
/// <summary>
/// Instantiates a new instance of the <see cref="XmlDocumentationProvider"/>.
/// </summary>
/// <param name="xmlFileLocation">Path to the XML documentation file to read.</param>
public XmlDocumentationProvider(string xmlFileLocation)
{
// XML file processing is defined by: https://msdn.microsoft.com/en-us/library/fsbx0t7x.aspx
var xmlDocumentation = XDocument.Load(xmlFileLocation);
var documentationRootMembers = xmlDocumentation.Root.Element("members");
_members = documentationRootMembers.Elements("member");
}
/// <summary>
/// Retrieves the <c>&lt;summary&gt;</c> documentation for the given <paramref name="id"/>.
/// </summary>
/// <param name="id">The id to lookup.</param>
/// <returns><c>&lt;summary&gt;</c> documentation for the given <paramref name="id"/>.</returns>
public string GetSummary(string id)
{
var associatedMemeber = GetMember(id);
var summaryElement = associatedMemeber?.Element("summary");
if (summaryElement != null)
{
var summaryValue = GetElementValue(summaryElement);
return summaryValue;
}
return null;
}
/// <summary>
/// Retrieves the <c>&lt;remarks&gt;</c> documentation for the given <paramref name="id"/>.
/// </summary>
/// <param name="id">The id to lookup.</param>
/// <returns><c>&lt;remarks&gt;</c> documentation for the given <paramref name="id"/>.</returns>
public string GetRemarks(string id)
{
var associatedMemeber = GetMember(id);
var remarksElement = associatedMemeber?.Element("remarks");
if (remarksElement != null)
{
var remarksValue = GetElementValue(remarksElement);
return remarksValue;
}
return null;
}
/// <summary>
/// Generates the <see cref="string"/> identifier for the given <paramref name="type"/>.
/// </summary>
/// <param name="type">The <see cref="Type"/> to get the identifier for.</param>
/// <returns>The <see cref="string"/> identifier for the given <paramref name="type"/>.</returns>
public static string GetId([NotNull] Type type)
{
return $"T:{type.FullName}";
}
/// <summary>
/// Generates the <see cref="string"/> identifier for the given <paramref name="propertyInfo"/>.
/// </summary>
/// <param name="propertyInfo">The <see cref="PropertyInfo"/> to get the identifier for.</param>
/// <returns>The <see cref="string"/> identifier for the given <paramref name="propertyInfo"/>.</returns>
public static string GetId([NotNull] PropertyInfo propertyInfo)
{
var declaringTypeInfo = propertyInfo.DeclaringType;
return $"P:{declaringTypeInfo.FullName}.{propertyInfo.Name}";
}
private XElement GetMember(string id)
{
var associatedMemeber = _members
.FirstOrDefault(element =>
string.Equals(element.Attribute("name").Value, id, StringComparison.Ordinal));
return associatedMemeber;
}
private static string GetElementValue(XElement element)
{
var stringBuilder = new StringBuilder();
var node = element.FirstNode;
while (node != null)
{
stringBuilder.Append(node.ToString(SaveOptions.DisableFormatting));
node = node.NextNode;
}
return stringBuilder.ToString().Trim();
}
}
}
#endif

View File

@ -9,8 +9,18 @@
"Microsoft.Framework.NotNullAttribute.Sources": { "type": "build", "version": "1.0.0-*" }
},
"frameworks": {
"net45": { },
"dnx451": { },
"net45": {
"frameworkAssemblies": {
"System.Xml": "4.0.0.0",
"System.Xml.Linq": "4.0.0.0"
}
},
"dnx451": {
"frameworkAssemblies": {
"System.Xml": "4.0.0.0",
"System.Xml.Linq": "4.0.0.0"
}
},
"dnxcore50": {
"dependencies": {
"System.Reflection.Extensions": "4.0.0-beta-*",

View File

@ -19,7 +19,8 @@ namespace Microsoft.AspNet.Razor.TagHelpers
propertyInfo.Name,
propertyInfo.PropertyType.FullName,
isIndexer: false,
isStringProperty: propertyInfo.PropertyType == typeof(string))
isStringProperty: propertyInfo.PropertyType == typeof(string),
usageDescriptor: null)
{
}
@ -39,6 +40,8 @@ namespace Microsoft.AspNet.Razor.TagHelpers
/// If <c>true</c> this <see cref="TagHelperAttributeDescriptor"/> is used for dictionary indexer assignments.
/// Otherwise this <see cref="TagHelperAttributeDescriptor"/> is used for property assignment.
/// </param>
/// <param name="usageDescriptor">The <see cref="TagHelperUsageDescriptor"/> that contains information about
/// use of this attribute.</param>
/// <remarks>
/// HTML attribute names are matched case-insensitively, regardless of <paramref name="isIndexer"/>.
/// </remarks>
@ -46,13 +49,15 @@ namespace Microsoft.AspNet.Razor.TagHelpers
[NotNull] string name,
[NotNull] string propertyName,
[NotNull] string typeName,
bool isIndexer)
bool isIndexer,
TagHelperUsageDescriptor usageDescriptor)
: this(
name,
propertyName,
typeName,
isIndexer,
isStringProperty: string.Equals(typeName, typeof(string).FullName, StringComparison.Ordinal))
isStringProperty: string.Equals(typeName, typeof(string).FullName, StringComparison.Ordinal),
usageDescriptor: usageDescriptor)
{
}
@ -62,13 +67,15 @@ namespace Microsoft.AspNet.Razor.TagHelpers
[NotNull] string propertyName,
[NotNull] string typeName,
bool isIndexer,
bool isStringProperty)
bool isStringProperty,
TagHelperUsageDescriptor usageDescriptor)
{
Name = name;
PropertyName = propertyName;
TypeName = typeName;
IsIndexer = isIndexer;
IsStringProperty = isStringProperty;
UsageDescriptor = usageDescriptor;
}
/// <summary>
@ -111,6 +118,11 @@ namespace Microsoft.AspNet.Razor.TagHelpers
/// </summary>
public string TypeName { get; }
/// <summary>
/// The <see cref="TagHelperUsageDescriptor"/> that contains information about use of this attribute.
/// </summary>
public TagHelperUsageDescriptor UsageDescriptor { get; }
/// <summary>
/// Determines whether HTML attribute <paramref name="name"/> matches this
/// <see cref="TagHelperAttributeDescriptor"/>.

View File

@ -1,68 +0,0 @@
// Copyright (c) .NET Foundation. 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.Framework.Internal;
using Microsoft.Internal.Web.Utils;
namespace Microsoft.AspNet.Razor.TagHelpers
{
/// <summary>
/// An <see cref="IEqualityComparer{TagHelperAttributeDescriptor}"/> used to check equality between
/// two <see cref="TagHelperAttributeDescriptor"/>s.
/// </summary>
public class TagHelperAttributeDescriptorComparer : IEqualityComparer<TagHelperAttributeDescriptor>
{
/// <summary>
/// A default instance of the <see cref="TagHelperAttributeDescriptorComparer"/>.
/// </summary>
public static readonly TagHelperAttributeDescriptorComparer Default =
new TagHelperAttributeDescriptorComparer();
/// <summary>
/// Initializes a new <see cref="TagHelperAttributeDescriptorComparer"/> instance.
/// </summary>
protected TagHelperAttributeDescriptorComparer()
{
}
/// <inheritdoc />
/// <remarks>
/// Determines equality based on <see cref="TagHelperAttributeDescriptor.IsIndexer"/>,
/// <see cref="TagHelperAttributeDescriptor.Name"/>, <see cref="TagHelperAttributeDescriptor.PropertyName"/>,
/// and <see cref="TagHelperAttributeDescriptor.TypeName"/>. Ignores
/// <see cref="TagHelperAttributeDescriptor.IsStringProperty"/> because it can be inferred directly from
/// <see cref="TagHelperAttributeDescriptor.TypeName"/>.
/// </remarks>
public virtual bool Equals(TagHelperAttributeDescriptor descriptorX, TagHelperAttributeDescriptor descriptorY)
{
if (descriptorX == descriptorY)
{
return true;
}
// Check Name and TypeName though each property in a particular tag helper has at most two
// TagHelperAttributeDescriptors (one for the indexer and one not). May be comparing attributes between
// tag helpers and should be as specific as we can.
return descriptorX != null &&
descriptorX.IsIndexer == descriptorY.IsIndexer &&
string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.OrdinalIgnoreCase) &&
string.Equals(descriptorX.PropertyName, descriptorY.PropertyName, StringComparison.Ordinal) &&
string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal);
}
/// <inheritdoc />
public virtual int GetHashCode([NotNull] TagHelperAttributeDescriptor descriptor)
{
// Rarely if ever hash TagHelperAttributeDescriptor. If we do, include the Name and TypeName since context
// information is not available in the hash.
return HashCodeCombiner.Start()
.Add(descriptor.IsIndexer)
.Add(descriptor.Name, StringComparer.OrdinalIgnoreCase)
.Add(descriptor.PropertyName, StringComparer.Ordinal)
.Add(descriptor.TypeName, StringComparer.Ordinal)
.CombinedHash;
}
}
}

View File

@ -59,7 +59,8 @@ namespace Microsoft.AspNet.Razor.TagHelpers
typeName: typeName,
assemblyName: assemblyName,
attributes: attributes,
requiredAttributes: requiredAttributes)
requiredAttributes: requiredAttributes,
usageDescriptor: null)
{
}
@ -81,13 +82,16 @@ namespace Microsoft.AspNet.Razor.TagHelpers
/// <param name="requiredAttributes">
/// The attribute names required for the tag helper to target the HTML tag.
/// </param>
/// <param name="usageDescriptor">The <see cref="TagHelperUsageDescriptor"/> that contains information about
/// how to use the tag helper at design time.</param>
public TagHelperDescriptor(
string prefix,
[NotNull] string tagName,
[NotNull] string typeName,
[NotNull] string assemblyName,
[NotNull] IEnumerable<TagHelperAttributeDescriptor> attributes,
[NotNull] IEnumerable<string> requiredAttributes)
[NotNull] IEnumerable<string> requiredAttributes,
TagHelperUsageDescriptor usageDescriptor)
{
Prefix = prefix ?? string.Empty;
TagName = tagName;
@ -96,39 +100,40 @@ namespace Microsoft.AspNet.Razor.TagHelpers
AssemblyName = assemblyName;
Attributes = new List<TagHelperAttributeDescriptor>(attributes);
RequiredAttributes = new List<string>(requiredAttributes);
UsageDescriptor = usageDescriptor;
}
/// <summary>
/// Text used as a required prefix when matching HTML start and end tags in the Razor source to available
/// tag helpers.
/// </summary>
public string Prefix { get; private set; }
public string Prefix { get; }
/// <summary>
/// The tag name that the tag helper should target.
/// </summary>
public string TagName { get; private set; }
public string TagName { get; }
/// <summary>
/// The full tag name that is required for the tag helper to target an HTML element.
/// </summary>
/// <remarks>This is equivalent to <see cref="Prefix"/> and <see cref="TagName"/> concatenated.</remarks>
public string FullTagName { get; private set; }
public string FullTagName { get; }
/// <summary>
/// The full name of the tag helper class.
/// </summary>
public string TypeName { get; private set; }
public string TypeName { get; }
/// <summary>
/// The name of the assembly containing the tag helper class.
/// </summary>
public string AssemblyName { get; private set; }
public string AssemblyName { get; }
/// <summary>
/// The list of attributes the tag helper expects.
/// </summary>
public IReadOnlyList<TagHelperAttributeDescriptor> Attributes { get; private set; }
public IReadOnlyList<TagHelperAttributeDescriptor> Attributes { get; }
/// <summary>
/// The list of required attribute names the tag helper expects to target an element.
@ -136,6 +141,12 @@ namespace Microsoft.AspNet.Razor.TagHelpers
/// <remarks>
/// <c>*</c> at the end of an attribute name acts as a prefix match.
/// </remarks>
public IReadOnlyList<string> RequiredAttributes { get; private set; }
public IReadOnlyList<string> RequiredAttributes { get; }
/// <summary>
/// The <see cref="TagHelperUsageDescriptor"/> that contains information about how to use the tag helper at
/// design time.
/// </summary>
public TagHelperUsageDescriptor UsageDescriptor { get; }
}
}

View File

@ -31,7 +31,9 @@ namespace Microsoft.AspNet.Razor.TagHelpers
/// <remarks>
/// Determines equality based on <see cref="TagHelperDescriptor.TypeName"/>,
/// <see cref="TagHelperDescriptor.AssemblyName"/>, <see cref="TagHelperDescriptor.TagName"/>,
/// and <see cref="TagHelperDescriptor.RequiredAttributes"/>.
/// and <see cref="TagHelperDescriptor.RequiredAttributes"/>. Ignores
/// <see cref="TagHelperDescriptor.UsageDescriptor"/> because it can be inferred directly from
/// <see cref="TagHelperDescriptor.TypeName"/> and <see cref="TagHelperDescriptor.AssemblyName"/>.
/// </remarks>
public virtual bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY)
{

View File

@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation. 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>
/// A metadata class containing information about tag helper use.
/// </summary>
public class TagHelperUsageDescriptor
{
/// <summary>
/// Instantiates a new instance of <see cref="TagHelperUsageDescriptor"/>.
/// </summary>
/// <param name="summary">A summary on how to use a tag helper.</param>
/// <param name="remarks">Remarks on how to use a tag helper.</param>
public TagHelperUsageDescriptor(string summary, string remarks)
{
Summary = summary;
Remarks = remarks;
}
/// <summary>
/// A summary of how to use a tag helper.
/// </summary>
public string Summary { get; }
/// <summary>
/// Remarks about how to use a tag helper.
/// </summary>
public string Remarks { get; }
}
}