Add support for `@removeTagHelpers` inheritance from _ViewStart files

Fixes #1657
This commit is contained in:
Pranav K 2014-12-01 15:24:10 -08:00
parent 106b9fc30c
commit 6641997836
10 changed files with 275 additions and 69 deletions

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using Microsoft.AspNet.FileSystems; using Microsoft.AspNet.FileSystems;
using Microsoft.AspNet.Razor; using Microsoft.AspNet.Razor;
using Microsoft.AspNet.Razor.Generator.Compiler; using Microsoft.AspNet.Razor.Generator.Compiler;
@ -20,7 +21,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
private readonly Dictionary<string, CodeTree> _parsedCodeTrees; private readonly Dictionary<string, CodeTree> _parsedCodeTrees;
private readonly MvcRazorHost _razorHost; private readonly MvcRazorHost _razorHost;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IEnumerable<Chunk> _defaultInheritedChunks; private readonly IReadOnlyList<Chunk> _defaultInheritedChunks;
/// <summary> /// <summary>
/// Initializes a new instance of <see cref="ChunkInheritanceUtility"/>. /// Initializes a new instance of <see cref="ChunkInheritanceUtility"/>.
@ -30,7 +31,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
/// <param name="defaultInheritedChunks">Sequence of <see cref="Chunk"/>s inherited by default.</param> /// <param name="defaultInheritedChunks">Sequence of <see cref="Chunk"/>s inherited by default.</param>
public ChunkInheritanceUtility([NotNull] MvcRazorHost razorHost, public ChunkInheritanceUtility([NotNull] MvcRazorHost razorHost,
[NotNull] IFileSystem fileSystem, [NotNull] IFileSystem fileSystem,
[NotNull] IEnumerable<Chunk> defaultInheritedChunks) [NotNull] IReadOnlyList<Chunk> defaultInheritedChunks)
{ {
_razorHost = razorHost; _razorHost = razorHost;
_fileSystem = fileSystem; _fileSystem = fileSystem;
@ -39,14 +40,16 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
} }
/// <summary> /// <summary>
/// Gets a <see cref="IReadOnlyList{T}"/> of <see cref="Chunk"/> containing parsed results of _ViewStart files /// Gets an ordered <see cref="IReadOnlyList{T}"/> of parsed <see cref="CodeTree"/> for each _ViewStart that
/// that are used for inheriting tag helpers and chunks to the page located at <paramref name="pagePath"/>. /// is applicable to the page located at <paramref name="pagePath"/>. The list is ordered so that the
/// <see cref="CodeTree"/> for the _ViewStart closest to the <paramref name="pagePath"/> in the filesystem
/// appears first.
/// </summary> /// </summary>
/// <param name="pagePath">The path of the page to locate inherited chunks for.</param> /// <param name="pagePath">The path of the page to locate inherited chunks for.</param>
/// <returns>A <see cref="IReadOnlyList{T}"/> of <see cref="Chunk"/> from _ViewStart pages.</returns> /// <returns>A <see cref="IReadOnlyList{CodeTree}"/> of parsed _ViewStart <see cref="CodeTree"/>s.</returns>
public IReadOnlyList<Chunk> GetInheritedChunks([NotNull] string pagePath) public IReadOnlyList<CodeTree> GetInheritedCodeTrees([NotNull] string pagePath)
{ {
var inheritedChunks = new List<Chunk>(); var inheritedCodeTrees = new List<CodeTree>();
var templateEngine = new RazorTemplateEngine(_razorHost); var templateEngine = new RazorTemplateEngine(_razorHost);
foreach (var viewStartPath in ViewStartUtility.GetViewStartLocations(pagePath)) foreach (var viewStartPath in ViewStartUtility.GetViewStartLocations(pagePath))
@ -55,7 +58,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
if (_parsedCodeTrees.TryGetValue(viewStartPath, out codeTree)) if (_parsedCodeTrees.TryGetValue(viewStartPath, out codeTree))
{ {
inheritedChunks.AddRange(codeTree.Chunks); inheritedCodeTrees.Add(codeTree);
} }
else else
{ {
@ -68,25 +71,26 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
// for the current _ViewStart to succeed. // for the current _ViewStart to succeed.
codeTree = ParseViewFile(templateEngine, fileInfo, viewStartPath); codeTree = ParseViewFile(templateEngine, fileInfo, viewStartPath);
_parsedCodeTrees.Add(viewStartPath, codeTree); _parsedCodeTrees.Add(viewStartPath, codeTree);
inheritedChunks.AddRange(codeTree.Chunks);
inheritedCodeTrees.Add(codeTree);
} }
} }
} }
inheritedChunks.AddRange(_defaultInheritedChunks); return inheritedCodeTrees;
return inheritedChunks;
} }
/// <summary> /// <summary>
/// Merges a list of chunks into the specified <paramref name="codeTree"/>. /// Merges <see cref="Chunk"/> inherited by default and <see cref="CodeTree"/> instances produced by parsing
/// _ViewStart files into the specified <paramref name="codeTree"/>.
/// </summary> /// </summary>
/// <param name="codeTree">The <see cref="CodeTree"/> to merge.</param> /// <param name="codeTree">The <see cref="CodeTree"/> to merge in to.</param>
/// <param name="inherited">The <see credit="IReadOnlyList{T}"/> of <see cref="Chunk"/> to merge.</param> /// <param name="inheritedCodeTrees"><see cref="IReadOnlyList{CodeTree}"/> inherited from _ViewStart
/// files.</param>
/// <param name="defaultModel">The list of chunks to merge.</param> /// <param name="defaultModel">The list of chunks to merge.</param>
public void MergeInheritedChunks([NotNull] CodeTree codeTree, public void MergeInheritedCodeTrees([NotNull] CodeTree codeTree,
[NotNull] IReadOnlyList<Chunk> inherited, [NotNull] IReadOnlyList<CodeTree> inheritedCodeTrees,
string defaultModel) string defaultModel)
{ {
var mergerMappings = GetMergerMappings(codeTree, defaultModel); var mergerMappings = GetMergerMappings(codeTree, defaultModel);
IChunkMerger merger; IChunkMerger merger;
@ -104,7 +108,12 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
// In the second phase we invoke IChunkMerger.Merge for each chunk that has a mapped merger. // In the second phase we invoke IChunkMerger.Merge for each chunk that has a mapped merger.
// During this phase, the merger can either add to the CodeTree or ignore the chunk based on the merging // During this phase, the merger can either add to the CodeTree or ignore the chunk based on the merging
// rules. // rules.
foreach (var chunk in inherited) // Read the chunks outside in - that is chunks from the _ViewStart closest to the page get merged in first
// and the furthest one last. This allows the merger to ignore a directive like @model that was previously
// seen.
var chunksToMerge = inheritedCodeTrees.SelectMany(tree => tree.Chunks)
.Concat(_defaultInheritedChunks);
foreach (var chunk in chunksToMerge)
{ {
if (mergerMappings.TryGetValue(chunk.GetType(), out merger)) if (mergerMappings.TryGetValue(chunk.GetType(), out merger))
{ {

View File

@ -183,8 +183,8 @@ namespace Microsoft.AspNet.Mvc.Razor
/// <inheritdoc /> /// <inheritdoc />
public override RazorParser DecorateRazorParser([NotNull] RazorParser razorParser, string sourceFileName) public override RazorParser DecorateRazorParser([NotNull] RazorParser razorParser, string sourceFileName)
{ {
var inheritedChunks = ChunkInheritanceUtility.GetInheritedChunks(sourceFileName); var inheritedCodeTrees = ChunkInheritanceUtility.GetInheritedCodeTrees(sourceFileName);
return new MvcRazorParser(razorParser, inheritedChunks); return new MvcRazorParser(razorParser, inheritedCodeTrees, DefaultInheritedChunks);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -197,9 +197,9 @@ namespace Microsoft.AspNet.Mvc.Razor
public override CodeBuilder DecorateCodeBuilder([NotNull] CodeBuilder incomingBuilder, public override CodeBuilder DecorateCodeBuilder([NotNull] CodeBuilder incomingBuilder,
[NotNull] CodeBuilderContext context) [NotNull] CodeBuilderContext context)
{ {
var inheritedChunks = ChunkInheritanceUtility.GetInheritedChunks(context.SourceFile); var inheritedChunks = ChunkInheritanceUtility.GetInheritedCodeTrees(context.SourceFile);
ChunkInheritanceUtility.MergeInheritedChunks(context.CodeTreeBuilder.CodeTree, ChunkInheritanceUtility.MergeInheritedCodeTrees(context.CodeTreeBuilder.CodeTree,
inheritedChunks, inheritedChunks,
DefaultModel); DefaultModel);

View File

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // 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. // 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 System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.AspNet.Razor.Generator.Compiler; using Microsoft.AspNet.Razor.Generator.Compiler;
@ -8,6 +9,7 @@ using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.SyntaxTree; using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Parser.TagHelpers; using Microsoft.AspNet.Razor.Parser.TagHelpers;
using Microsoft.AspNet.Razor.TagHelpers; using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.AspNet.Razor.Text;
namespace Microsoft.AspNet.Mvc.Razor namespace Microsoft.AspNet.Mvc.Razor
{ {
@ -17,18 +19,23 @@ namespace Microsoft.AspNet.Mvc.Razor
/// </summary> /// </summary>
public class MvcRazorParser : RazorParser public class MvcRazorParser : RazorParser
{ {
private readonly IReadOnlyList<Chunk> _viewStartChunks; private readonly IEnumerable<TagHelperDirectiveDescriptor> _viewStartDirectiveDescriptors;
/// <summary> /// <summary>
/// Initializes a new instance of <see cref="MvcRazorParser"/>. /// Initializes a new instance of <see cref="MvcRazorParser"/>.
/// </summary> /// </summary>
/// <param name="parser">The <see cref="RazorParser"/> to copy properties from.</param> /// <param name="parser">The <see cref="RazorParser"/> to copy properties from.</param>
/// <param name="viewStartChunks">The <see cref="IReadOnlyList{T}"/> of <see cref="Chunk"/>s that are inherited /// <param name="inheritedCodeTrees">The <see cref="IReadOnlyList{CodeTree}"/>s that are inherited
/// by parsed pages from _ViewStart files.</param> /// from parsed pages from _ViewStart files.</param>
public MvcRazorParser(RazorParser parser, IReadOnlyList<Chunk> viewStartChunks) /// <param name="defaultInheritedChunks">The <see cref="IReadOnlyList{Chunk}"/> inherited by
/// default by all Razor pages in the application.</param>
public MvcRazorParser([NotNull] RazorParser parser,
[NotNull] IReadOnlyList<CodeTree> inheritedCodeTrees,
[NotNull] IReadOnlyList<Chunk> defaultInheritedChunks)
: base(parser) : base(parser)
{ {
_viewStartChunks = viewStartChunks; // Construct tag helper descriptors from @addTagHelper and @removeTagHelper chunks
_viewStartDirectiveDescriptors = GetTagHelperDescriptors(inheritedCodeTrees, defaultInheritedChunks);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -36,17 +43,47 @@ namespace Microsoft.AspNet.Mvc.Razor
[NotNull] Block documentRoot, [NotNull] Block documentRoot,
[NotNull] ParserErrorSink errorSink) [NotNull] ParserErrorSink errorSink)
{ {
// Grab all the @addtaghelper chunks from view starts and construct TagHelperDirectiveDescriptors
var directiveDescriptors = _viewStartChunks.OfType<AddTagHelperChunk>()
.Select(chunk => new TagHelperDirectiveDescriptor(
chunk.LookupText,
chunk.Start,
TagHelperDirectiveType.AddTagHelper));
var visitor = new ViewStartAddRemoveTagHelperVisitor(TagHelperDescriptorResolver, var visitor = new ViewStartAddRemoveTagHelperVisitor(TagHelperDescriptorResolver,
directiveDescriptors, _viewStartDirectiveDescriptors,
errorSink); errorSink);
var descriptors = visitor.GetDescriptors(documentRoot); return visitor.GetDescriptors(documentRoot);
}
private static IEnumerable<TagHelperDirectiveDescriptor> GetTagHelperDescriptors(
IReadOnlyList<CodeTree> inheritedCodeTrees,
IReadOnlyList<Chunk> defaultInheritedChunks)
{
var descriptors = new List<TagHelperDirectiveDescriptor>();
// 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);
foreach (var chunk in chunksInOrder)
{
var addHelperChunk = chunk as AddTagHelperChunk;
if (addHelperChunk != null)
{
var descriptor = new TagHelperDirectiveDescriptor(addHelperChunk.LookupText,
SourceLocation.Undefined,
TagHelperDirectiveType.AddTagHelper);
descriptors.Add(descriptor);
}
else
{
var removeHelperChunk = chunk as RemoveTagHelperChunk;
if (removeHelperChunk != null)
{
var descriptor = new TagHelperDirectiveDescriptor(removeHelperChunk.LookupText,
SourceLocation.Undefined,
TagHelperDirectiveType.RemoveTagHelper);
descriptors.Add(descriptor);
}
}
}
return descriptors; return descriptors;
} }

View File

@ -54,17 +54,40 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal(expectedContent, responseContent); Assert.Equal(expectedContent, responseContent);
} }
public static IEnumerable<object[]> TagHelpersAreInheritedFromViewStartPagesData
{
get
{
var expected1 =
@"<root>root-content</root>
<nested>nested-content</nested>";
yield return new[] { "NestedViewStartTagHelper", expected1 };
var expected2 =
@"layout:<root>root-content</root>
<nested>nested-content</nested>";
yield return new[] { "ViewWithLayoutAndNestedTagHelper", expected2 };
var expected3 =
@"layout:<root>root-content</root>
page:<root/>
<nested>nested-content</nested>";
yield return new[] { "ViewWithInheritedRemoveTagHelper", expected3 };
}
}
[Theory] [Theory]
[InlineData("NestedViewStartTagHelper")] [MemberData(nameof(TagHelpersAreInheritedFromViewStartPagesData))]
[InlineData("ViewWithLayoutAndNestedTagHelper")] public async Task TagHelpersAreInheritedFromViewStartPages(string action, string expected)
public async Task TagHelpersAreInheritedFromViewStartPages(string action)
{ {
// Arrange // Arrange
var expected = string.Join(Environment.NewLine,
"<root>root-content</root>",
"",
"",
"<nested>nested-content</nested>");
var server = TestServer.Create(_provider, _app); var server = TestServer.Create(_provider, _app);
var client = server.CreateClient(); var client = server.CreateClient();

View File

@ -25,31 +25,41 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
} }
"); ");
var defaultChunks = new Chunk[]
{
new InjectChunk("MyTestHtmlHelper", "Html"),
new UsingChunk { Namespace = "AppNamespace.Model" },
};
var host = new MvcRazorHost(fileSystem); var host = new MvcRazorHost(fileSystem);
var utility = new ChunkInheritanceUtility(host, fileSystem, new Chunk[0]); var utility = new ChunkInheritanceUtility(host, fileSystem, defaultChunks);
// Act // Act
var chunks = utility.GetInheritedChunks(@"Views\home\Index.cshtml"); var codeTrees = utility.GetInheritedCodeTrees(@"Views\home\Index.cshtml");
// Assert // Assert
Assert.Equal(8, chunks.Count); Assert.Equal(2, codeTrees.Count);
Assert.IsType<LiteralChunk>(chunks[0]); var viewStartChunks = codeTrees[0].Chunks;
Assert.Equal(3, viewStartChunks.Count);
var usingChunk = Assert.IsType<UsingChunk>(chunks[1]); Assert.IsType<LiteralChunk>(viewStartChunks[0]);
var usingChunk = Assert.IsType<UsingChunk>(viewStartChunks[1]);
Assert.Equal("MyNamespace", usingChunk.Namespace); Assert.Equal("MyNamespace", usingChunk.Namespace);
Assert.IsType<LiteralChunk>(viewStartChunks[2]);
Assert.IsType<LiteralChunk>(chunks[2]); viewStartChunks = codeTrees[1].Chunks;
Assert.IsType<LiteralChunk>(chunks[3]); Assert.Equal(5, viewStartChunks.Count);
var injectChunk = Assert.IsType<InjectChunk>(chunks[4]); Assert.IsType<LiteralChunk>(viewStartChunks[0]);
var injectChunk = Assert.IsType<InjectChunk>(viewStartChunks[1]);
Assert.Equal("MyHelper<TModel>", injectChunk.TypeName); Assert.Equal("MyHelper<TModel>", injectChunk.TypeName);
Assert.Equal("Helper", injectChunk.MemberName); Assert.Equal("Helper", injectChunk.MemberName);
var setBaseTypeChunk = Assert.IsType<SetBaseTypeChunk>(chunks[5]); var setBaseTypeChunk = Assert.IsType<SetBaseTypeChunk>(viewStartChunks[2]);
Assert.Equal("MyBaseType", setBaseTypeChunk.TypeName); Assert.Equal("MyBaseType", setBaseTypeChunk.TypeName);
Assert.IsType<StatementChunk>(chunks[6]); Assert.IsType<StatementChunk>(viewStartChunks[3]);
Assert.IsType<LiteralChunk>(chunks[7]); Assert.IsType<LiteralChunk>(viewStartChunks[4]);
} }
[Fact] [Fact]
@ -61,17 +71,22 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
fileSystem.AddFile(@"Views\_Layout.cshtml", string.Empty); fileSystem.AddFile(@"Views\_Layout.cshtml", string.Empty);
fileSystem.AddFile(@"Views\home\_not-viewstart.cshtml", string.Empty); fileSystem.AddFile(@"Views\home\_not-viewstart.cshtml", string.Empty);
var host = new MvcRazorHost(fileSystem); var host = new MvcRazorHost(fileSystem);
var utility = new ChunkInheritanceUtility(host, fileSystem, new Chunk[0]); var defaultChunks = new Chunk[]
{
new InjectChunk("MyTestHtmlHelper", "Html"),
new UsingChunk { Namespace = "AppNamespace.Model" },
};
var utility = new ChunkInheritanceUtility(host, fileSystem, defaultChunks);
// Act // Act
var chunks = utility.GetInheritedChunks(@"Views\home\Index.cshtml"); var codeTrees = utility.GetInheritedCodeTrees(@"Views\home\Index.cshtml");
// Assert // Assert
Assert.Empty(chunks); Assert.Empty(codeTrees);
} }
[Fact] [Fact]
public void GetInheritedChunks_ReturnsDefaultInheritedChunks() public void MergeInheritedChunks_MergesDefaultInheritedChunks()
{ {
// Arrange // Arrange
var fileSystem = new TestFileSystem(); var fileSystem = new TestFileSystem();
@ -83,19 +98,38 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
new InjectChunk("MyTestHtmlHelper", "Html"), new InjectChunk("MyTestHtmlHelper", "Html"),
new UsingChunk { Namespace = "AppNamespace.Model" }, new UsingChunk { Namespace = "AppNamespace.Model" },
}; };
var inheritedCodeTrees = new CodeTree[]
{
new CodeTree
{
Chunks = new Chunk[]
{
new UsingChunk { Namespace = "InheritedNamespace" },
new LiteralChunk { Text = "some text" }
}
},
new CodeTree
{
Chunks = new Chunk[]
{
new UsingChunk { Namespace = "AppNamespace.Model" },
}
}
};
var utility = new ChunkInheritanceUtility(host, fileSystem, defaultChunks); var utility = new ChunkInheritanceUtility(host, fileSystem, defaultChunks);
var codeTree = new CodeTree();
// Act // Act
var chunks = utility.GetInheritedChunks(@"Views\Home\Index.cshtml"); utility.MergeInheritedCodeTrees(codeTree,
inheritedCodeTrees,
"dynamic");
// Assert // Assert
Assert.Equal(4, chunks.Count); Assert.Equal(3, codeTree.Chunks.Count);
var injectChunk = Assert.IsType<InjectChunk>(chunks[1]); Assert.Same(inheritedCodeTrees[0].Chunks[0], codeTree.Chunks[0]);
Assert.Equal("DifferentHelper<TModel>", injectChunk.TypeName); Assert.Same(inheritedCodeTrees[1].Chunks[0], codeTree.Chunks[1]);
Assert.Equal("Html", injectChunk.MemberName); Assert.Same(defaultChunks[0], codeTree.Chunks[2]);
Assert.Same(defaultChunks[0], chunks[2]);
Assert.Same(defaultChunks[1], chunks[3]);
} }
} }
} }

View File

@ -0,0 +1,91 @@
// 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.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Razor.Generator.Compiler;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.AspNet.Razor.Text;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.Razor
{
public class MvcRazorCodeParserTest
{
[Fact]
public void GetTagHelperDescriptors_ReturnsDescriptorsFromViewStart()
{
// Arrange
var builder = new BlockBuilder { Type = BlockType.Comment };
var block = new Block(builder);
var codeTrees = new[]
{
new CodeTree
{
Chunks = new Chunk[]
{
new LiteralChunk { Text = "Hello world" },
new AddTagHelperChunk { LookupText = "Add Tag Helper" },
}
},
new CodeTree
{
Chunks = new[]
{
new RemoveTagHelperChunk { LookupText = "Remove Tag Helper" },
}
}
};
IList<TagHelperDirectiveDescriptor> descriptors = null;
var resolver = new Mock<ITagHelperDescriptorResolver>();
resolver.Setup(r => r.Resolve(It.IsAny<TagHelperDescriptorResolutionContext>()))
.Callback((TagHelperDescriptorResolutionContext context) =>
{
descriptors = context.DirectiveDescriptors;
})
.Returns(Enumerable.Empty<TagHelperDescriptor>())
.Verifiable();
var baseParser = new RazorParser(new CSharpCodeParser(),
new HtmlMarkupParser(),
resolver.Object);
var parser = new TestableMvcRazorParser(baseParser, codeTrees, new Chunk[0]);
var sink = new ParserErrorSink();
// Act
var result = parser.GetTagHelperDescriptorsPublic(block, sink).ToArray();
// Assert
Assert.NotNull(descriptors);
Assert.Equal(2, descriptors.Count);
Assert.Equal("Remove Tag Helper", descriptors[0].LookupText);
Assert.Equal(SourceLocation.Undefined, descriptors[0].Location);
Assert.Equal("Add Tag Helper", descriptors[1].LookupText);
Assert.Equal(TagHelperDirectiveType.AddTagHelper, descriptors[1].DirectiveType);
Assert.Equal(SourceLocation.Undefined, descriptors[1].Location);
}
private class TestableMvcRazorParser : MvcRazorParser
{
public TestableMvcRazorParser(RazorParser parser,
IReadOnlyList<CodeTree> codeTrees,
IReadOnlyList<Chunk> defaultInheritedChunks)
: base(parser, codeTrees, defaultInheritedChunks)
{
}
public IEnumerable<TagHelperDescriptor> GetTagHelperDescriptorsPublic(
Block documentRoot,
ParserErrorSink errorSink)
{
return GetTagHelperDescriptors(documentRoot, errorSink);
}
}
}
}

View File

@ -39,5 +39,10 @@ namespace TagHelpersWebSite.Controllers
{ {
return View(); return View();
} }
public ViewResult ViewWithInheritedRemoveTagHelper()
{
return View("/Views/RemoveTagHelperViewStart/ViewWithInheritedRemoveTagHelper.cshtml");
}
} }
} }

View File

@ -0,0 +1,2 @@
page:<root/>
<nested>some-content</nested>

View File

@ -0,0 +1,5 @@
@{
Layout = "~/Views/Shared/_LayoutWithRootTagHelper.cshtml";
}
@removetaghelper "TagHelpersWebSite.TagHelpers.RootViewStartTagHelper, TagHelpersWebSite"
@addtaghelper "TagHelpersWebSite.TagHelpers.NestedViewStartTagHelper, TagHelpersWebSite"

View File

@ -1,2 +1,2 @@
<root /> layout:<root/>
@RenderBody() @RenderBody()