diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs index 0610e40ce6..97b54512b2 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs @@ -38,23 +38,30 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers /// /// The assembly name that contains . /// The type to create a from. + /// Indicates if the returned s should include + /// design time specific information. + /// The used to collect s encountered + /// when creating s for the given . /// /// A collection of s that describe the given . /// public static IEnumerable 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 attributeDescriptors, - IEnumerable targetElementAttributes) + IEnumerable 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()) + requiredAttributes: Enumerable.Empty(), + 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 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 attributeDescriptors, - IEnumerable requiredAttributes) + IEnumerable 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); } /// @@ -225,6 +253,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers private static IEnumerable 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(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) diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorResolver.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorResolver.cs index da7fc974a3..fa1835b421 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorResolver.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorResolver.cs @@ -26,14 +26,16 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers }; private readonly TagHelperTypeResolver _typeResolver; + private readonly bool _designTime; /// /// Instantiates a new instance of the class. /// - public TagHelperDescriptorResolver() - : this(new TagHelperTypeResolver()) + /// Indicates whether resolved s should include + /// design time specific information. + public TagHelperDescriptorResolver(bool designTime) + : this(new TagHelperTypeResolver(), designTime) { - } /// @@ -41,9 +43,12 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers /// specified . /// /// The . - public TagHelperDescriptorResolver(TagHelperTypeResolver typeResolver) + /// Indicates whether resolved s should include + /// design time specific information. + public TagHelperDescriptorResolver(TagHelperTypeResolver typeResolver, bool designTime) { _typeResolver = typeResolver; + _designTime = designTime; } /// @@ -113,7 +118,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers /// The name of the assembly to resolve s from. /// /// The of the directive. - /// Used to record errors found when resolving s + /// Used to record errors found when resolving s /// within the given . /// s for s from the given /// . @@ -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(@"\*", ".*?") + "$"; diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperUsageDescriptorFactory.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperUsageDescriptorFactory.cs new file mode 100644 index 0000000000..ee7a191894 --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperUsageDescriptorFactory.cs @@ -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 +{ + /// + /// Factory for providing s from s and + /// s. + /// + public static class TagHelperUsageDescriptorFactory + { + /// + /// Creates a from the given . + /// + /// The to create a from. + /// A that describes the summary and remarks XML documentation + /// for the given . + public static TagHelperUsageDescriptor CreateDescriptor([NotNull] Type type) + { + var id = XmlDocumentationProvider.GetId(type); + + return CreateDescriptorCore(type.Assembly, id); + } + + /// + /// Creates a from the given . + /// + /// The to create a + /// from. + /// A that describes the summary and remarks XML documentation + /// for the given . + 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 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 \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime/XMLDocumentationProvider.cs b/src/Microsoft.AspNet.Razor.Runtime/XMLDocumentationProvider.cs new file mode 100644 index 0000000000..7132cc1f80 --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime/XMLDocumentationProvider.cs @@ -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 +{ + /// + /// Extracts summary and remarks XML documentation from an XML documentation file. + /// + public class XmlDocumentationProvider + { + private readonly IEnumerable _members; + + /// + /// Instantiates a new instance of the . + /// + /// Path to the XML documentation file to read. + 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"); + } + + /// + /// Retrieves the <summary> documentation for the given . + /// + /// The id to lookup. + /// <summary> documentation for the given . + 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; + } + + /// + /// Retrieves the <remarks> documentation for the given . + /// + /// The id to lookup. + /// <remarks> documentation for the given . + 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; + } + + /// + /// Generates the identifier for the given . + /// + /// The to get the identifier for. + /// The identifier for the given . + public static string GetId([NotNull] Type type) + { + return $"T:{type.FullName}"; + } + + /// + /// Generates the identifier for the given . + /// + /// The to get the identifier for. + /// The identifier for the given . + 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 \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime/project.json b/src/Microsoft.AspNet.Razor.Runtime/project.json index e37620525c..f596d4f4c9 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/project.json +++ b/src/Microsoft.AspNet.Razor.Runtime/project.json @@ -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-*", diff --git a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperAttributeDescriptor.cs b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperAttributeDescriptor.cs index a4c370ae7b..b994136e1d 100644 --- a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperAttributeDescriptor.cs +++ b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperAttributeDescriptor.cs @@ -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 true this is used for dictionary indexer assignments. /// Otherwise this is used for property assignment. /// + /// The that contains information about + /// use of this attribute. /// /// HTML attribute names are matched case-insensitively, regardless of . /// @@ -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; } /// @@ -111,6 +118,11 @@ namespace Microsoft.AspNet.Razor.TagHelpers /// public string TypeName { get; } + /// + /// The that contains information about use of this attribute. + /// + public TagHelperUsageDescriptor UsageDescriptor { get; } + /// /// Determines whether HTML attribute matches this /// . diff --git a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperAttributeDescriptorComparer.cs b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperAttributeDescriptorComparer.cs deleted file mode 100644 index c118af1406..0000000000 --- a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperAttributeDescriptorComparer.cs +++ /dev/null @@ -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 -{ - /// - /// An used to check equality between - /// two s. - /// - public class TagHelperAttributeDescriptorComparer : IEqualityComparer - { - /// - /// A default instance of the . - /// - public static readonly TagHelperAttributeDescriptorComparer Default = - new TagHelperAttributeDescriptorComparer(); - - /// - /// Initializes a new instance. - /// - protected TagHelperAttributeDescriptorComparer() - { - } - - /// - /// - /// Determines equality based on , - /// , , - /// and . Ignores - /// because it can be inferred directly from - /// . - /// - 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); - } - - /// - 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; - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptor.cs b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptor.cs index 80d07b2028..cc0e4c6808 100644 --- a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptor.cs +++ b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptor.cs @@ -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 /// /// The attribute names required for the tag helper to target the HTML tag. /// + /// The that contains information about + /// how to use the tag helper at design time. public TagHelperDescriptor( string prefix, [NotNull] string tagName, [NotNull] string typeName, [NotNull] string assemblyName, [NotNull] IEnumerable attributes, - [NotNull] IEnumerable requiredAttributes) + [NotNull] IEnumerable requiredAttributes, + TagHelperUsageDescriptor usageDescriptor) { Prefix = prefix ?? string.Empty; TagName = tagName; @@ -96,39 +100,40 @@ namespace Microsoft.AspNet.Razor.TagHelpers AssemblyName = assemblyName; Attributes = new List(attributes); RequiredAttributes = new List(requiredAttributes); + UsageDescriptor = usageDescriptor; } /// /// Text used as a required prefix when matching HTML start and end tags in the Razor source to available /// tag helpers. /// - public string Prefix { get; private set; } + public string Prefix { get; } /// /// The tag name that the tag helper should target. /// - public string TagName { get; private set; } + public string TagName { get; } /// /// The full tag name that is required for the tag helper to target an HTML element. /// /// This is equivalent to and concatenated. - public string FullTagName { get; private set; } + public string FullTagName { get; } /// /// The full name of the tag helper class. /// - public string TypeName { get; private set; } + public string TypeName { get; } /// /// The name of the assembly containing the tag helper class. /// - public string AssemblyName { get; private set; } + public string AssemblyName { get; } /// /// The list of attributes the tag helper expects. /// - public IReadOnlyList Attributes { get; private set; } + public IReadOnlyList Attributes { get; } /// /// The list of required attribute names the tag helper expects to target an element. @@ -136,6 +141,12 @@ namespace Microsoft.AspNet.Razor.TagHelpers /// /// * at the end of an attribute name acts as a prefix match. /// - public IReadOnlyList RequiredAttributes { get; private set; } + public IReadOnlyList RequiredAttributes { get; } + + /// + /// The that contains information about how to use the tag helper at + /// design time. + /// + public TagHelperUsageDescriptor UsageDescriptor { get; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorComparer.cs b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorComparer.cs index 8eb961b0c4..2ea1b9dbee 100644 --- a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorComparer.cs +++ b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorComparer.cs @@ -31,7 +31,9 @@ namespace Microsoft.AspNet.Razor.TagHelpers /// /// Determines equality based on , /// , , - /// and . + /// and . Ignores + /// because it can be inferred directly from + /// and . /// public virtual bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY) { diff --git a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperUsageDescriptor.cs b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperUsageDescriptor.cs new file mode 100644 index 0000000000..5a3918f4e9 --- /dev/null +++ b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperUsageDescriptor.cs @@ -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 +{ + /// + /// A metadata class containing information about tag helper use. + /// + public class TagHelperUsageDescriptor + { + /// + /// Instantiates a new instance of . + /// + /// A summary on how to use a tag helper. + /// Remarks on how to use a tag helper. + public TagHelperUsageDescriptor(string summary, string remarks) + { + Summary = summary; + Remarks = remarks; + } + + /// + /// A summary of how to use a tag helper. + /// + public string Summary { get; } + + /// + /// Remarks about how to use a tag helper. + /// + public string Remarks { get; } + } +} \ No newline at end of file