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
This commit is contained in:
N. Taylor Mullen 2014-10-31 15:02:08 -07:00
parent 64a5b8ee22
commit c35d19142c
8 changed files with 174 additions and 81 deletions

View File

@ -30,37 +30,30 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
}
/// <inheritdoc />
public IEnumerable<TagHelperDescriptor> Resolve(string lookupText)
public IEnumerable<TagHelperDescriptor> Resolve([NotNull] TagHelperDescriptorResolutionContext context)
{
var lookupStrings = lookupText?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var resolvedDescriptors = new HashSet<TagHelperDescriptor>(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;
}
/// <summary>
@ -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; }
}
}
}

View File

@ -18,21 +18,24 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
{
private readonly ITagHelperDescriptorResolver _descriptorResolver;
private List<TagHelperDescriptor> _descriptors;
private List<TagHelperDirectiveDescriptor> _directiveDescriptors;
public AddOrRemoveTagHelperSpanVisitor(ITagHelperDescriptorResolver descriptorResolver)
public AddOrRemoveTagHelperSpanVisitor([NotNull] ITagHelperDescriptorResolver descriptorResolver)
{
_descriptorResolver = descriptorResolver;
}
public IEnumerable<TagHelperDescriptor> GetDescriptors([NotNull] Block root)
{
_descriptors = new List<TagHelperDescriptor>();
_directiveDescriptors = new List<TagHelperDirectiveDescriptor>();
// 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<TagHelperDescriptor>(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);
}
}
}

View File

@ -1526,22 +1526,6 @@ namespace Microsoft.AspNet.Razor
return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_DirectiveMustBeSurroundedByQuotes"), p0);
}
/// <summary>
/// Cannot use directive '{0}' when a {1} has not been provided to the {2}.
/// </summary>
internal static string TagHelpers_CannotUseDirectiveWithNoTagHelperDescriptorResolver
{
get { return GetString("TagHelpers_CannotUseDirectiveWithNoTagHelperDescriptorResolver"); }
}
/// <summary>
/// Cannot use directive '{0}' when a {1} has not been provided to the {2}.
/// </summary>
internal static string FormatTagHelpers_CannotUseDirectiveWithNoTagHelperDescriptorResolver(object p0, object p1, object p2)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelpers_CannotUseDirectiveWithNoTagHelperDescriptorResolver"), p0, p1, p2);
}
/// <summary>
/// Found a malformed '{0}' tag helper. Tag helpers must have a start and end tag or be self closing.
/// </summary>

View File

@ -421,9 +421,6 @@ Instead, wrap the contents of the block in "{{}}":
<data name="ParseError_DirectiveMustBeSurroundedByQuotes" xml:space="preserve">
<value>Directive '{0}'s value must be surrounded in double quotes.</value>
</data>
<data name="TagHelpers_CannotUseDirectiveWithNoTagHelperDescriptorResolver" xml:space="preserve">
<value>Cannot use directive '{0}' when a {1} has not been provided to the {2}.</value>
</data>
<data name="TagHelpersParseTreeRewriter_FoundMalformedTagHelper" xml:space="preserve">
<value>Found a malformed '{0}' tag helper. Tag helpers must have a start and end tag or be self closing.</value>
</data>

View File

@ -11,13 +11,13 @@ namespace Microsoft.AspNet.Razor.TagHelpers
public interface ITagHelperDescriptorResolver
{
/// <summary>
/// Resolves <see cref="TagHelperDescriptor"/>s matching the given <paramref name="lookupText"/>.
/// Resolves <see cref="TagHelperDescriptor"/>s based on the given <paramref name="resolutionContext"/>.
/// </summary>
/// <param name="lookupText">
/// A <see cref="string"/> used to find tag helper <see cref="Type"/>s.
/// <param name="resolutionContext">
/// <see cref="TagHelperDescriptorResolutionContext"/> used to resolve descriptors for the Razor page.
/// </param>
/// <returns>An <see cref="IEnumerable{TagHelperDescriptor}"/> of <see cref="TagHelperDescriptor"/>s matching
/// the given <paramref name="lookupText"/>.</returns>
IEnumerable<TagHelperDescriptor> Resolve(string lookupText);
/// <returns>An <see cref="IEnumerable{TagHelperDescriptor}"/> of <see cref="TagHelperDescriptor"/>s based
/// on the given <paramref name="resolutionContext"/>.</returns>
IEnumerable<TagHelperDescriptor> Resolve(TagHelperDescriptorResolutionContext resolutionContext);
}
}

View File

@ -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
{
/// <summary>
/// Contains information needed to resolve <see cref="TagHelperDescriptor"/>s.
/// </summary>
public class TagHelperDescriptorResolutionContext
{
/// <summary>
/// Instantiates a new instance of <see cref="TagHelperDescriptorResolutionContext"/>.
/// </summary>
/// <param name="directiveDescriptors"><see cref="TagHelperDirectiveDescriptor"/>s used to resolve
/// <see cref="TagHelperDescriptor"/>s.</param>
public TagHelperDescriptorResolutionContext(
[NotNull] IEnumerable<TagHelperDirectiveDescriptor> directiveDescriptors)
{
DirectiveDescriptors = new List<TagHelperDirectiveDescriptor>(directiveDescriptors);
}
/// <summary>
/// <see cref="TagHelperDirectiveDescriptor"/>s used to resolve <see cref="TagHelperDescriptor"/>s.
/// </summary>
public IList<TagHelperDirectiveDescriptor> DirectiveDescriptors { get; private set; }
}
}

View File

@ -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
{
/// <summary>
/// Contains information needed to resolve <see cref="TagHelperDescriptor"/>s.
/// </summary>
public class TagHelperDirectiveDescriptor
{
/// <summary>
/// Instantiates a new instance of <see cref="TagHelperDirectiveDescriptor"/>.
/// </summary>
/// <param name="lookupText">A <see cref="string"/> used to find tag helper <see cref="System.Type"/>s.</param>
/// <param name="directiveType">The <see cref="TagHelperDirectiveType"/> of this directive.</param>
public TagHelperDirectiveDescriptor([NotNull] string lookupText, TagHelperDirectiveType directiveType)
{
LookupText = lookupText;
DirectiveType = directiveType;
}
/// <summary>
/// A <see cref="string"/> used to find tag helper <see cref="System.Type"/>s.
/// </summary>
public string LookupText { get; private set; }
/// <summary>
/// The <see cref="TagHelperDirectiveType"/> of this directive.
/// </summary>
public TagHelperDirectiveType DirectiveType { get; private set; }
}
}

View File

@ -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
{
/// <summary>
/// The type of tag helper directive.
/// </summary>
public enum TagHelperDirectiveType
{
/// <summary>
/// An @addtaghelper directive.
/// </summary>
AddTagHelper,
/// <summary>
/// A @removetaghelper directive.
/// </summary>
RemoveTagHelper
}
}