diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorParser.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorParser.cs index 4d87d4a1ff..94a0faf16d 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorParser.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorParser.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.Razor { /// /// A subtype of that uses to support inheritance of tag - /// helpers from _ViewStart files. + /// helpers from _GlobalImport files. /// public class MvcRazorParser : RazorParser { @@ -27,16 +27,19 @@ namespace Microsoft.AspNet.Mvc.Razor /// /// The to copy properties from. /// The s that are inherited - /// from parsed pages from _ViewStart files. + /// from parsed pages from _GlobalImport files. /// The inherited by /// default by all Razor pages in the application. - public MvcRazorParser([NotNull] RazorParser parser, - [NotNull] IReadOnlyList inheritedCodeTrees, - [NotNull] IReadOnlyList defaultInheritedChunks) + public MvcRazorParser( + [NotNull] RazorParser parser, + [NotNull] IReadOnlyList inheritedCodeTrees, + [NotNull] IReadOnlyList defaultInheritedChunks) : base(parser) { - // Construct tag helper descriptors from @addTagHelper and @removeTagHelper chunks - _globalImportDirectiveDescriptors = GetTagHelperDirectiveDescriptors(inheritedCodeTrees, defaultInheritedChunks); + // Construct tag helper descriptors from @addTagHelper, @removeTagHelper and @tagHelperPrefix chunks + _globalImportDirectiveDescriptors = GetTagHelperDirectiveDescriptors( + inheritedCodeTrees, + defaultInheritedChunks); } /// @@ -44,7 +47,7 @@ namespace Microsoft.AspNet.Mvc.Razor [NotNull] Block documentRoot, [NotNull] ParserErrorSink errorSink) { - var visitor = new GlobalImportAddRemoveTagHelperVisitor( + var visitor = new GlobalImportTagHelperDirectiveSpanVisitor( TagHelperDescriptorResolver, _globalImportDirectiveDescriptors, errorSink); @@ -58,43 +61,63 @@ namespace Microsoft.AspNet.Mvc.Razor var descriptors = new List(); // For tag helpers, the @removeTagHelper only applies tag helpers that were added prior to it. - // Consequently we must visit tag helpers outside-in - furthest _ViewStart first and nearest one last. This - // is different from the behavior of chunk merging where we visit the nearest one first and ignore chunks - // that were previously visited. - var chunksFromViewStarts = inheritedCodeTrees.Reverse() - .SelectMany(tree => tree.Chunks); - var chunksInOrder = defaultInheritedChunks.Concat(chunksFromViewStarts); + // Consequently we must visit tag helpers outside-in - furthest _GlobalImport first and nearest one last. + // This is different from the behavior of chunk merging where we visit the nearest one first and ignore + // chunks that were previously visited. + var chunksFromGlobalImports = inheritedCodeTrees + .Reverse() + .SelectMany(tree => tree.Chunks); + var chunksInOrder = defaultInheritedChunks.Concat(chunksFromGlobalImports); foreach (var chunk in chunksInOrder) { - var addHelperChunk = chunk as AddTagHelperChunk; - if (addHelperChunk != null) + // All TagHelperDirectiveDescriptors created here have undefined source locations because the source + // that created them is not in the same file. + var addTagHelperChunk = chunk as AddTagHelperChunk; + if (addTagHelperChunk != null) { - var descriptor = new TagHelperDirectiveDescriptor(addHelperChunk.LookupText, - SourceLocation.Undefined, - TagHelperDirectiveType.AddTagHelper); + var descriptor = new TagHelperDirectiveDescriptor( + addTagHelperChunk.LookupText, + SourceLocation.Undefined, + TagHelperDirectiveType.AddTagHelper); + descriptors.Add(descriptor); + + continue; } - else + + var removeTagHelperChunk = chunk as RemoveTagHelperChunk; + if (removeTagHelperChunk != null) { - var removeHelperChunk = chunk as RemoveTagHelperChunk; - if (removeHelperChunk != null) - { - var descriptor = new TagHelperDirectiveDescriptor(removeHelperChunk.LookupText, - SourceLocation.Undefined, - TagHelperDirectiveType.RemoveTagHelper); - descriptors.Add(descriptor); - } + var descriptor = new TagHelperDirectiveDescriptor( + removeTagHelperChunk.LookupText, + SourceLocation.Undefined, + TagHelperDirectiveType.RemoveTagHelper); + + descriptors.Add(descriptor); + + continue; + } + + var tagHelperPrefixDirectiveChunk = chunk as TagHelperPrefixDirectiveChunk; + if (tagHelperPrefixDirectiveChunk != null) + { + var descriptor = new TagHelperDirectiveDescriptor( + tagHelperPrefixDirectiveChunk.Prefix, + SourceLocation.Undefined, + TagHelperDirectiveType.TagHelperPrefix); + + descriptors.Add(descriptor); } } return descriptors; } - private class GlobalImportAddRemoveTagHelperVisitor : TagHelperDirectiveSpanVisitor + private class GlobalImportTagHelperDirectiveSpanVisitor : TagHelperDirectiveSpanVisitor { private readonly IEnumerable _globalImportDirectiveDescriptors; - public GlobalImportAddRemoveTagHelperVisitor( + public GlobalImportTagHelperDirectiveSpanVisitor( ITagHelperDescriptorResolver descriptorResolver, IEnumerable globalImportDirectiveDescriptors, ParserErrorSink errorSink) @@ -107,9 +130,43 @@ namespace Microsoft.AspNet.Mvc.Razor IEnumerable descriptors, ParserErrorSink errorSink) { - return base.GetTagHelperDescriptorResolutionContext( - _globalImportDirectiveDescriptors.Concat(descriptors), - errorSink); + var directivesToImport = MergeDirectiveDescriptors(descriptors, _globalImportDirectiveDescriptors); + + return base.GetTagHelperDescriptorResolutionContext(directivesToImport, errorSink); + } + + private static IEnumerable MergeDirectiveDescriptors( + IEnumerable descriptors, + IEnumerable inheritedDescriptors) + { + var mergedDescriptors = new List(); + TagHelperDirectiveDescriptor prefixDirectiveDescriptor = null; + + foreach (var descriptor in inheritedDescriptors) + { + if (descriptor.DirectiveType == TagHelperDirectiveType.TagHelperPrefix) + { + // Always take the latest @tagHelperPrefix descriptor. Can only have 1 per page. + prefixDirectiveDescriptor = descriptor; + } + else + { + mergedDescriptors.Add(descriptor); + } + } + + // We need to see if the provided descriptors contain a @tagHelperPrefix directive. If so, it + // takes precedence and overrides any provided by the inheritedDescriptors. If not we need to add the + // inherited @tagHelperPrefix directive back into the merged list. + if (prefixDirectiveDescriptor != null && + !descriptors.Any(descriptor => descriptor.DirectiveType == TagHelperDirectiveType.TagHelperPrefix)) + { + mergedDescriptors.Add(prefixDirectiveDescriptor); + } + + mergedDescriptors.AddRange(descriptors); + + return mergedDescriptors; } } }