From c35d19142c3219932cbd6e8a1796b1ca075c613f Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Fri, 31 Oct 2014 15:02:08 -0700 Subject: [PATCH] Add ability to resolve all TagHelperDescriptors with one method call. - Modified the AddOrRemoveTagHelperSpanVisitor to no longer manage TagHelperDescriptors found in the system. Instead it now manages TagHelperDirectiveDescriptors which are then used to resolve TagHelperDescriptors. - Changed the signature of ITagHelperDescriptorResolver to take in a TagHelperResolutionContext which will allow us to pass more information without breaking tooling. - TagHelperDescriptorResolver now resolves all TagHelperDescriptors at once and manages descriptors found in the system based on values on the provided TagHelperDirectiveDescriptors. #214 --- .../TagHelpers/TagHelperDescriptorResolver.cs | 99 ++++++++++++++----- .../AddOrRemoveTagHelperSpanVisitor.cs | 42 +++----- .../Properties/RazorResources.Designer.cs | 16 --- .../RazorResources.resx | 3 - .../ITagHelperDescriptorResolver.cs | 12 +-- .../TagHelperDescriptorResolutionContext.cs | 30 ++++++ .../TagHelperDirectiveDescriptor.cs | 32 ++++++ .../TagHelpers/TagHelperDirectiveType.cs | 21 ++++ 8 files changed, 174 insertions(+), 81 deletions(-) create mode 100644 src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorResolutionContext.cs create mode 100644 src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDirectiveDescriptor.cs create mode 100644 src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDirectiveType.cs diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorResolver.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorResolver.cs index 9978fff71f..ec1ade272a 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorResolver.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorResolver.cs @@ -30,37 +30,30 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers } /// - public IEnumerable Resolve(string lookupText) + public IEnumerable Resolve([NotNull] TagHelperDescriptorResolutionContext context) { - var lookupStrings = lookupText?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var resolvedDescriptors = new HashSet(TagHelperDescriptorComparer.Default); - // Ensure that we have valid lookupStrings to work with. Valid formats are: - // "assemblyName" - // "typeName, assemblyName" - if (string.IsNullOrEmpty(lookupText) || - (lookupStrings.Length != 1 && lookupStrings.Length != 2)) + foreach (var directiveDescriptor in context.DirectiveDescriptors) { - throw new ArgumentException( - Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(lookupText), - nameof(lookupText)); + var lookupInfo = GetLookupInfo(directiveDescriptor); + + if (directiveDescriptor.DirectiveType == TagHelperDirectiveType.RemoveTagHelper) + { + resolvedDescriptors.RemoveWhere(descriptor => MatchesLookupInfo(descriptor, lookupInfo)); + } + else if (directiveDescriptor.DirectiveType == TagHelperDirectiveType.AddTagHelper) + { + var descriptors = ResolveDescriptorsInAssembly(lookupInfo.AssemblyName); + + // Only use descriptors that match our lookup info + descriptors = descriptors.Where(descriptor => MatchesLookupInfo(descriptor, lookupInfo)); + + resolvedDescriptors.UnionWith(descriptors); + } } - // Grab the assembly name from the lookup text strings. Due to our supported lookupText formats it will - // always be the last element provided. - var assemblyName = lookupStrings.Last().Trim(); - var descriptors = ResolveDescriptorsInAssembly(assemblyName); - - // Check if the lookupText specifies a type to search for. - if (lookupStrings.Length == 2) - { - // The user provided a type name. Retrieve it so we can prune our descriptors. - var typeName = lookupStrings[0].Trim(); - - descriptors = descriptors.Where(descriptor => - string.Equals(descriptor.TypeName, typeName, StringComparison.Ordinal)); - } - - return descriptors; + return resolvedDescriptors; } /// @@ -83,5 +76,59 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers return descriptors; } + + private static bool MatchesLookupInfo(TagHelperDescriptor descriptor, LookupInfo lookupInfo) + { + if (!string.Equals(descriptor.AssemblyName, lookupInfo.AssemblyName, StringComparison.Ordinal)) + { + return false; + } + + return string.IsNullOrEmpty(lookupInfo.TypeName) || + string.Equals(descriptor.TypeName, lookupInfo.TypeName, StringComparison.Ordinal); + } + + private static LookupInfo GetLookupInfo(TagHelperDirectiveDescriptor directiveDescriptor) + { + var lookupText = directiveDescriptor.LookupText; + var lookupStrings = lookupText?.Split(new[] { ',' }); + + // Ensure that we have valid lookupStrings to work with. Valid formats are: + // "assemblyName" + // "typeName, assemblyName" + if (lookupStrings == null || + lookupStrings.Any(string.IsNullOrWhiteSpace) || + (lookupStrings.Length != 1 && lookupStrings.Length != 2)) + { + throw new ArgumentException( + Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(lookupText), + nameof(lookupText)); + } + + // Grab the assembly name from the lookup text strings. Due to our supported lookupText formats it will + // always be the last element provided. + var assemblyName = lookupStrings.Last().Trim(); + string typeName = null; + + // Check if the lookupText specifies a type to search for. + if (lookupStrings.Length == 2) + { + // The user provided a type name. Retrieve it so we can prune our descriptors. + typeName = lookupStrings[0].Trim(); + } + + return new LookupInfo + { + AssemblyName = assemblyName, + TypeName = typeName + }; + } + + private class LookupInfo + { + public string AssemblyName { get; set; } + + public string TypeName { get; set; } + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/AddOrRemoveTagHelperSpanVisitor.cs b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/AddOrRemoveTagHelperSpanVisitor.cs index fa5cc139bf..98283ee077 100644 --- a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/AddOrRemoveTagHelperSpanVisitor.cs +++ b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/AddOrRemoveTagHelperSpanVisitor.cs @@ -18,21 +18,24 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers { private readonly ITagHelperDescriptorResolver _descriptorResolver; - private List _descriptors; + private List _directiveDescriptors; - public AddOrRemoveTagHelperSpanVisitor(ITagHelperDescriptorResolver descriptorResolver) + public AddOrRemoveTagHelperSpanVisitor([NotNull] ITagHelperDescriptorResolver descriptorResolver) { _descriptorResolver = descriptorResolver; } public IEnumerable GetDescriptors([NotNull] Block root) { - _descriptors = new List(); + _directiveDescriptors = new List(); // This will recurse through the syntax tree. VisitBlock(root); - return _descriptors; + var resolutionContext = new TagHelperDescriptorResolutionContext(_directiveDescriptors); + var descriptors = _descriptorResolver.Resolve(resolutionContext); + + return descriptors; } public override void VisitSpan(Span span) @@ -43,34 +46,13 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers { var codeGenerator = (AddOrRemoveTagHelperCodeGenerator)span.CodeGenerator; - if (_descriptorResolver == null) - { - var directive = codeGenerator.RemoveTagHelperDescriptors ? - SyntaxConstants.CSharp.RemoveTagHelperKeyword : - SyntaxConstants.CSharp.AddTagHelperKeyword; + var directive = codeGenerator.RemoveTagHelperDescriptors ? + TagHelperDirectiveType.RemoveTagHelper : + TagHelperDirectiveType.AddTagHelper; - throw new InvalidOperationException( - RazorResources.FormatTagHelpers_CannotUseDirectiveWithNoTagHelperDescriptorResolver( - directive, typeof(ITagHelperDescriptorResolver).FullName, typeof(RazorParser).FullName)); - } + var directiveDescriptor = new TagHelperDirectiveDescriptor(codeGenerator.LookupText, directive); - // Look up all the descriptors associated with the "LookupText". - var descriptors = _descriptorResolver.Resolve(codeGenerator.LookupText); - - if (codeGenerator.RemoveTagHelperDescriptors) - { - var evaluatedDescriptors = - new HashSet(descriptors, TagHelperDescriptorComparer.Default); - - // We remove all found descriptors from the descriptor list to ignore the associated TagHelpers on the - // Razor page. - _descriptors.RemoveAll(descriptor => evaluatedDescriptors.Contains(descriptor)); - } - else - { - // Add all the found descriptors to our list. - _descriptors.AddRange(descriptors); - } + _directiveDescriptors.Add(directiveDescriptor); } } } diff --git a/src/Microsoft.AspNet.Razor/Properties/RazorResources.Designer.cs b/src/Microsoft.AspNet.Razor/Properties/RazorResources.Designer.cs index cd468b4d71..0bab77f769 100644 --- a/src/Microsoft.AspNet.Razor/Properties/RazorResources.Designer.cs +++ b/src/Microsoft.AspNet.Razor/Properties/RazorResources.Designer.cs @@ -1526,22 +1526,6 @@ namespace Microsoft.AspNet.Razor return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_DirectiveMustBeSurroundedByQuotes"), p0); } - /// - /// Cannot use directive '{0}' when a {1} has not been provided to the {2}. - /// - internal static string TagHelpers_CannotUseDirectiveWithNoTagHelperDescriptorResolver - { - get { return GetString("TagHelpers_CannotUseDirectiveWithNoTagHelperDescriptorResolver"); } - } - - /// - /// Cannot use directive '{0}' when a {1} has not been provided to the {2}. - /// - internal static string FormatTagHelpers_CannotUseDirectiveWithNoTagHelperDescriptorResolver(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelpers_CannotUseDirectiveWithNoTagHelperDescriptorResolver"), p0, p1, p2); - } - /// /// Found a malformed '{0}' tag helper. Tag helpers must have a start and end tag or be self closing. /// diff --git a/src/Microsoft.AspNet.Razor/RazorResources.resx b/src/Microsoft.AspNet.Razor/RazorResources.resx index 476f47f4ce..cdca65f247 100644 --- a/src/Microsoft.AspNet.Razor/RazorResources.resx +++ b/src/Microsoft.AspNet.Razor/RazorResources.resx @@ -421,9 +421,6 @@ Instead, wrap the contents of the block in "{{}}": Directive '{0}'s value must be surrounded in double quotes. - - Cannot use directive '{0}' when a {1} has not been provided to the {2}. - Found a malformed '{0}' tag helper. Tag helpers must have a start and end tag or be self closing. diff --git a/src/Microsoft.AspNet.Razor/TagHelpers/ITagHelperDescriptorResolver.cs b/src/Microsoft.AspNet.Razor/TagHelpers/ITagHelperDescriptorResolver.cs index 747242a8a0..be9fb00209 100644 --- a/src/Microsoft.AspNet.Razor/TagHelpers/ITagHelperDescriptorResolver.cs +++ b/src/Microsoft.AspNet.Razor/TagHelpers/ITagHelperDescriptorResolver.cs @@ -11,13 +11,13 @@ namespace Microsoft.AspNet.Razor.TagHelpers public interface ITagHelperDescriptorResolver { /// - /// Resolves s matching the given . + /// Resolves s based on the given . /// - /// - /// A used to find tag helper s. + /// + /// used to resolve descriptors for the Razor page. /// - /// An of s matching - /// the given . - IEnumerable Resolve(string lookupText); + /// An of s based + /// on the given . + IEnumerable Resolve(TagHelperDescriptorResolutionContext resolutionContext); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorResolutionContext.cs b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorResolutionContext.cs new file mode 100644 index 0000000000..9a127edf55 --- /dev/null +++ b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorResolutionContext.cs @@ -0,0 +1,30 @@ +// 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; + +namespace Microsoft.AspNet.Razor.TagHelpers +{ + /// + /// Contains information needed to resolve s. + /// + public class TagHelperDescriptorResolutionContext + { + /// + /// Instantiates a new instance of . + /// + /// s used to resolve + /// s. + public TagHelperDescriptorResolutionContext( + [NotNull] IEnumerable directiveDescriptors) + { + DirectiveDescriptors = new List(directiveDescriptors); + } + + /// + /// s used to resolve s. + /// + public IList DirectiveDescriptors { get; private set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDirectiveDescriptor.cs b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDirectiveDescriptor.cs new file mode 100644 index 0000000000..efb9148592 --- /dev/null +++ b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDirectiveDescriptor.cs @@ -0,0 +1,32 @@ +// 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 +{ + /// + /// Contains information needed to resolve s. + /// + public class TagHelperDirectiveDescriptor + { + /// + /// Instantiates a new instance of . + /// + /// A used to find tag helper s. + /// The of this directive. + public TagHelperDirectiveDescriptor([NotNull] string lookupText, TagHelperDirectiveType directiveType) + { + LookupText = lookupText; + DirectiveType = directiveType; + } + + /// + /// A used to find tag helper s. + /// + public string LookupText { get; private set; } + + /// + /// The of this directive. + /// + public TagHelperDirectiveType DirectiveType { get; private set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDirectiveType.cs b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDirectiveType.cs new file mode 100644 index 0000000000..88eb9eb9e5 --- /dev/null +++ b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDirectiveType.cs @@ -0,0 +1,21 @@ +// 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 +{ + /// + /// The type of tag helper directive. + /// + public enum TagHelperDirectiveType + { + /// + /// An @addtaghelper directive. + /// + AddTagHelper, + + /// + /// A @removetaghelper directive. + /// + RemoveTagHelper + } +} \ No newline at end of file