Adding support for inheriting tag helpers from _ViewStart files
Fixes #1166
This commit is contained in:
parent
0ad959e236
commit
b2a01e7b45
|
|
@ -4,7 +4,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Microsoft.AspNet.Razor;
|
||||
using Microsoft.AspNet.Razor.Generator.Compiler;
|
||||
|
|
@ -13,63 +12,57 @@ using Microsoft.AspNet.Razor.Parser;
|
|||
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||
{
|
||||
/// <summary>
|
||||
/// A utility type for supporting inheritance of chunks into a page from _ViewStart pages that apply to it.
|
||||
/// A utility type for supporting inheritance of tag helpers and chunks into a page from applicable _ViewStart
|
||||
/// pages.
|
||||
/// </summary>
|
||||
public class ChunkInheritanceUtility
|
||||
{
|
||||
private readonly IReadOnlyList<Chunk> _defaultInheritedChunks;
|
||||
private readonly Dictionary<string, CodeTree> _parsedCodeTrees;
|
||||
private readonly MvcRazorHost _razorHost;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IEnumerable<Chunk> _defaultInheritedChunks;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of <see cref="ChunkInheritanceUtility"/>.
|
||||
/// </summary>
|
||||
/// <param name="codeTree">The <see cref="CodeTree"/> instance to add <see cref="Chunk"/>s to.</param>
|
||||
/// <param name="defaultInheritedChunks">The list of <see cref="Chunk"/>s inherited by default.</param>
|
||||
/// <param name="defaultModel">The model type used in the event no model is specified via the
|
||||
/// <c>@model</c> keyword.</param>
|
||||
public ChunkInheritanceUtility([NotNull] CodeTree codeTree,
|
||||
[NotNull] IReadOnlyList<Chunk> defaultInheritedChunks,
|
||||
[NotNull] string defaultModel)
|
||||
{
|
||||
CodeTree = codeTree;
|
||||
_defaultInheritedChunks = defaultInheritedChunks;
|
||||
ChunkMergers = GetMergerMappings(codeTree, defaultModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CodeTree to add inherited <see cref="Chunk"/> instances to.
|
||||
/// </summary>
|
||||
public CodeTree CodeTree { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a dictionary mapping <see cref="Chunk"/> type to the <see cref="IChunkMerger"/> used to merge
|
||||
/// chunks of that type.
|
||||
/// </summary>
|
||||
public IDictionary<Type, IChunkMerger> ChunkMergers { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of chunks that are to be inherited by a specified page.
|
||||
/// Chunks are inherited from _ViewStarts that are applicable to the page.
|
||||
/// Initializes a new instance of <see cref="ChunkInheritanceUtility"/>.
|
||||
/// </summary>
|
||||
/// <param name="razorHost">The <see cref="MvcRazorHost"/> used to parse _ViewStart pages.</param>
|
||||
/// <param name="fileSystem">The filesystem that represents the application.</param>
|
||||
/// <param name="defaultInheritedChunks">Sequence of <see cref="Chunk"/>s inherited by default.</param>
|
||||
public ChunkInheritanceUtility([NotNull] MvcRazorHost razorHost,
|
||||
[NotNull] IFileSystem fileSystem,
|
||||
[NotNull] IEnumerable<Chunk> defaultInheritedChunks)
|
||||
{
|
||||
_razorHost = razorHost;
|
||||
_fileSystem = fileSystem;
|
||||
_defaultInheritedChunks = defaultInheritedChunks;
|
||||
_parsedCodeTrees = new Dictionary<string, CodeTree>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IReadOnlyList{T}"/> of <see cref="Chunk"/> containing parsed results of _ViewStart files
|
||||
/// that are used for inheriting tag helpers and chunks to the page located at <paramref name="pagePath"/>.
|
||||
/// </summary>
|
||||
/// <param name="pagePath">The path of the page to locate inherited chunks for.</param>
|
||||
/// <returns>A list of chunks that are applicable to the given page.</returns>
|
||||
public List<Chunk> GetInheritedChunks([NotNull] MvcRazorHost razorHost,
|
||||
[NotNull] IFileSystem fileSystem,
|
||||
[NotNull] string pagePath)
|
||||
/// <returns>A <see cref="IReadOnlyList{T}"/> of <see cref="Chunk"/> from _ViewStart pages.</returns>
|
||||
public IReadOnlyList<Chunk> GetInheritedChunks([NotNull] string pagePath)
|
||||
{
|
||||
var inheritedChunks = new List<Chunk>();
|
||||
|
||||
var templateEngine = new RazorTemplateEngine(razorHost);
|
||||
foreach (var viewStart in ViewStartUtility.GetViewStartLocations(fileSystem, pagePath))
|
||||
var templateEngine = new RazorTemplateEngine(_razorHost);
|
||||
foreach (var viewStart in ViewStartUtility.GetViewStartLocations(_fileSystem, pagePath))
|
||||
{
|
||||
CodeTree codeTree;
|
||||
IFileInfo fileInfo;
|
||||
if (fileSystem.TryGetFileInfo(viewStart, out fileInfo))
|
||||
|
||||
if (_parsedCodeTrees.TryGetValue(viewStart, out codeTree))
|
||||
{
|
||||
var parsedTree = ParseViewFile(templateEngine, fileInfo);
|
||||
var chunksToAdd = parsedTree.Chunks
|
||||
.Where(chunk => ChunkMergers.ContainsKey(chunk.GetType()));
|
||||
inheritedChunks.AddRange(chunksToAdd);
|
||||
inheritedChunks.AddRange(codeTree.Chunks);
|
||||
}
|
||||
else if (_fileSystem.TryGetFileInfo(viewStart, out fileInfo))
|
||||
{
|
||||
codeTree = ParseViewFile(templateEngine, fileInfo);
|
||||
_parsedCodeTrees.Add(viewStart, codeTree);
|
||||
inheritedChunks.AddRange(codeTree.Chunks);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,19 +72,22 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges a list of chunks into the <see cref="CodeTree"/> instance.
|
||||
/// Merges a list of chunks into the specified <paramref name="codeTree/>.
|
||||
/// </summary>
|
||||
/// <param name="codeTree">The <see cref="CodeTree"/> to mere</param>
|
||||
/// <param name="inherited">The list of chunks to merge.</param>
|
||||
public void MergeInheritedChunks(List<Chunk> inherited)
|
||||
public void MergeInheritedChunks([NotNull] CodeTree codeTree,
|
||||
[NotNull] IReadOnlyList<Chunk> inherited,
|
||||
string defaultModel)
|
||||
{
|
||||
var current = CodeTree.Chunks;
|
||||
var mergerMappings = GetMergerMappings(codeTree, defaultModel);
|
||||
IChunkMerger merger;
|
||||
|
||||
// We merge chunks into the codeTree in two passes. In the first pass, we traverse the CodeTree visiting
|
||||
// a mapped IChunkMerger for types that are registered.
|
||||
foreach (var chunk in current)
|
||||
foreach (var chunk in codeTree.Chunks)
|
||||
{
|
||||
if (ChunkMergers.TryGetValue(chunk.GetType(), out merger))
|
||||
if (mergerMappings.TryGetValue(chunk.GetType(), out merger))
|
||||
{
|
||||
merger.VisitChunk(chunk);
|
||||
}
|
||||
|
|
@ -102,11 +98,11 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
|||
// rules.
|
||||
foreach (var chunk in inherited)
|
||||
{
|
||||
if (ChunkMergers.TryGetValue(chunk.GetType(), out merger))
|
||||
if (mergerMappings.TryGetValue(chunk.GetType(), out merger))
|
||||
{
|
||||
// TODO: When mapping chunks, we should remove mapping information since it would be incorrect
|
||||
// to generate it in the page that inherits it. Tracked by #945
|
||||
merger.Merge(CodeTree, chunk);
|
||||
merger.Merge(codeTree, chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -123,14 +119,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
|||
}
|
||||
|
||||
// TODO: This needs to be cached (#1016)
|
||||
private CodeTree ParseViewFile(RazorTemplateEngine engine,
|
||||
IFileInfo fileInfo)
|
||||
private static CodeTree ParseViewFile(RazorTemplateEngine engine,
|
||||
IFileInfo fileInfo)
|
||||
{
|
||||
using (var stream = fileInfo.CreateReadStream())
|
||||
{
|
||||
using (var streamReader = new StreamReader(stream))
|
||||
{
|
||||
var parseResults = engine.ParseTemplate(streamReader);
|
||||
var parseResults = engine.ParseTemplate(streamReader, fileInfo.PhysicalPath);
|
||||
var className = ParserHelpers.SanitizeClassName(fileInfo.Name);
|
||||
var language = engine.Host.CodeLanguage;
|
||||
var codeGenerator = language.CreateCodeGenerator(className,
|
||||
|
|
@ -138,6 +134,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
|||
fileInfo.PhysicalPath,
|
||||
engine.Host);
|
||||
codeGenerator.Visit(parseResults);
|
||||
|
||||
return codeGenerator.Context.CodeTreeBuilder.CodeTree;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
// CodeGenerationContext.DefaultBaseClass is set to MyBaseType<dynamic>.
|
||||
// This field holds the type name without the generic decoration (MyBaseType)
|
||||
private readonly string _baseType;
|
||||
private ChunkInheritanceUtility _chunkInheritanceUtility;
|
||||
|
||||
#if NET45
|
||||
/// <summary>
|
||||
|
|
@ -154,6 +155,20 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
get { return "CreateModelExpression"; }
|
||||
}
|
||||
|
||||
private ChunkInheritanceUtility ChunkInheritanceUtility
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_chunkInheritanceUtility == null)
|
||||
{
|
||||
// This needs to be lazily evaluated to support DefaultInheritedChunks being virtual.
|
||||
_chunkInheritanceUtility = new ChunkInheritanceUtility(this, _fileSystem, DefaultInheritedChunks);
|
||||
}
|
||||
|
||||
return _chunkInheritanceUtility;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream)
|
||||
{
|
||||
|
|
@ -163,6 +178,13 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
return engine.GenerateCode(inputStream, className, DefaultNamespace, rootRelativePath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override RazorParser DecorateRazorParser([NotNull] RazorParser razorParser, string sourceFileName)
|
||||
{
|
||||
var inheritedChunks = ChunkInheritanceUtility.GetInheritedChunks(sourceFileName);
|
||||
return new MvcRazorParser(razorParser, inheritedChunks);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ParserBase DecorateCodeParser([NotNull] ParserBase incomingCodeParser)
|
||||
{
|
||||
|
|
@ -173,10 +195,14 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public override CodeBuilder DecorateCodeBuilder([NotNull] CodeBuilder incomingBuilder,
|
||||
[NotNull] CodeBuilderContext context)
|
||||
{
|
||||
UpdateCodeBuilder(context);
|
||||
var inheritedChunks = ChunkInheritanceUtility.GetInheritedChunks(context.SourceFile);
|
||||
|
||||
ChunkInheritanceUtility.MergeInheritedChunks(context.CodeTreeBuilder.CodeTree,
|
||||
inheritedChunks,
|
||||
DefaultModel);
|
||||
|
||||
return new MvcCSharpCodeBuilder(context,
|
||||
DefaultModel,
|
||||
DefaultModel,
|
||||
ActivateAttribute,
|
||||
new GeneratedTagHelperAttributeContext
|
||||
{
|
||||
|
|
@ -184,14 +210,5 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
CreateModelExpressionMethodName = CreateModelExpressionMethod
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateCodeBuilder(CodeGeneratorContext context)
|
||||
{
|
||||
var chunkUtility = new ChunkInheritanceUtility(context.CodeTreeBuilder.CodeTree,
|
||||
DefaultInheritedChunks,
|
||||
DefaultModel);
|
||||
var inheritedChunks = chunkUtility.GetInheritedChunks(this, _fileSystem, context.SourceFile);
|
||||
chunkUtility.MergeInheritedChunks(inheritedChunks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// A subtype of <see cref="RazorParser"/> that <see cref="MvcRazorHost"/> uses to support inheritance of tag
|
||||
/// helpers from _viewstart files.
|
||||
/// </summary>
|
||||
public class MvcRazorParser : RazorParser
|
||||
{
|
||||
private readonly IReadOnlyList<Chunk> _viewStartChunks;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="MvcRazorParser"/>.
|
||||
/// </summary>
|
||||
/// <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
|
||||
/// by parsed pages from _ViewStart files.</param>
|
||||
public MvcRazorParser(RazorParser parser, IReadOnlyList<Chunk> viewStartChunks)
|
||||
: base(parser)
|
||||
{
|
||||
_viewStartChunks = viewStartChunks;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IEnumerable<TagHelperDescriptor> GetTagHelperDescriptors([NotNull] Block documentRoot)
|
||||
{
|
||||
var descriptors = base.GetTagHelperDescriptors(documentRoot);
|
||||
|
||||
// TODO: https://github.com/aspnet/Razor/issues/112 Needs to support RemvoeHelperChunks too.
|
||||
|
||||
// Grab all the @addTagHelper chunks from view starts
|
||||
var viewStartDescriptors = _viewStartChunks.OfType<AddTagHelperChunk>()
|
||||
.Select(c => c.LookupText)
|
||||
.SelectMany(TagHelperDescriptorResolver.Resolve);
|
||||
|
||||
return descriptors.Concat(viewStartDescriptors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,5 +51,26 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.Equal(expectedMediaType, response.Content.Headers.ContentType);
|
||||
Assert.Equal(expectedContent, responseContent);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("NestedViewStartTagHelper")]
|
||||
[InlineData("ViewWithLayoutAndNestedTagHelper")]
|
||||
public async Task TagHelpersAreInheritedFromViewStartPages(string action)
|
||||
{
|
||||
// Arrange
|
||||
var expected = string.Join(Environment.NewLine,
|
||||
"<root>root-content</root>",
|
||||
"",
|
||||
"",
|
||||
"<nested>nested-content</nested>");
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var result = await client.GetStringAsync("http://localhost/Home/" + action);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result.Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@
|
|||
|
||||
|
||||
|
||||
|
||||
<div>
|
||||
<p>Hello, you've reached the about page.</p>
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
|
||||
|
||||
|
||||
|
||||
<div>
|
||||
<p>Hello, you've reached the help page. If you're having troubles try visiting our <a href="/?approved=true">My Approved Home Page</a></p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
|
||||
|
||||
|
||||
|
||||
<div>
|
||||
<p>This website has <strong style="font-size: 1.25em;
|
||||
text-decoration: underline;">not</strong> been approved yet. Visit <strong><a target="_blank" href="http://www.contoso.com">www.contoso.com</a></strong> for <strong>more</strong> information.</p>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
// 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;
|
||||
using Microsoft.AspNet.Razor.Generator.Compiler;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Directives
|
||||
|
|
@ -29,24 +26,30 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
|||
|
||||
");
|
||||
var host = new MvcRazorHost(fileSystem);
|
||||
var utility = new ChunkInheritanceUtility(new CodeTree(), new Chunk[0], "dynamic");
|
||||
var utility = new ChunkInheritanceUtility(host, fileSystem, new Chunk[0]);
|
||||
|
||||
// Act
|
||||
var chunks = utility.GetInheritedChunks(host,
|
||||
fileSystem,
|
||||
@"x:\myapproot\views\home\Index.cshtml");
|
||||
var chunks = utility.GetInheritedChunks(@"x:\myapproot\views\home\Index.cshtml");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3, chunks.Count);
|
||||
var usingChunk = Assert.IsType<UsingChunk>(chunks[0]);
|
||||
Assert.Equal(8, chunks.Count);
|
||||
Assert.IsType<LiteralChunk>(chunks[0]);
|
||||
|
||||
var usingChunk = Assert.IsType<UsingChunk>(chunks[1]);
|
||||
Assert.Equal("MyNamespace", usingChunk.Namespace);
|
||||
|
||||
var injectChunk = Assert.IsType<InjectChunk>(chunks[1]);
|
||||
Assert.IsType<LiteralChunk>(chunks[2]);
|
||||
Assert.IsType<LiteralChunk>(chunks[3]);
|
||||
|
||||
var injectChunk = Assert.IsType<InjectChunk>(chunks[4]);
|
||||
Assert.Equal("MyHelper<TModel>", injectChunk.TypeName);
|
||||
Assert.Equal("Helper", injectChunk.MemberName);
|
||||
|
||||
var setBaseTypeChunk = Assert.IsType<SetBaseTypeChunk>(chunks[2]);
|
||||
var setBaseTypeChunk = Assert.IsType<SetBaseTypeChunk>(chunks[5]);
|
||||
Assert.Equal("MyBaseType", setBaseTypeChunk.TypeName);
|
||||
|
||||
Assert.IsType<StatementChunk>(chunks[6]);
|
||||
Assert.IsType<LiteralChunk>(chunks[7]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -58,12 +61,10 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
|||
fileSystem.AddFile(@"x:\myapproot\views\_Layout.cshtml", string.Empty);
|
||||
fileSystem.AddFile(@"x:\myapproot\views\home\_not-viewstart.cshtml", string.Empty);
|
||||
var host = new MvcRazorHost(fileSystem);
|
||||
var utility = new ChunkInheritanceUtility(new CodeTree(), new Chunk[0], "dynamic");
|
||||
var utility = new ChunkInheritanceUtility(host, fileSystem, new Chunk[0]);
|
||||
|
||||
// Act
|
||||
var chunks = utility.GetInheritedChunks(host,
|
||||
fileSystem,
|
||||
@"x:\myapproot\views\home\Index.cshtml");
|
||||
var chunks = utility.GetInheritedChunks(@"x:\myapproot\views\home\Index.cshtml");
|
||||
|
||||
// Assert
|
||||
Assert.Empty(chunks);
|
||||
|
|
@ -75,73 +76,26 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
|
|||
// Arrange
|
||||
var fileSystem = new TestFileSystem();
|
||||
fileSystem.AddFile(@"x:\myapproot\views\_viewstart.cshtml",
|
||||
@"@inject DifferentHelper<TModel> Html
|
||||
@using AppNamespace.Models
|
||||
@{
|
||||
Layout = ""test.cshtml"";
|
||||
}
|
||||
|
||||
");
|
||||
"@inject DifferentHelper<TModel> Html");
|
||||
var host = new MvcRazorHost(fileSystem);
|
||||
var defaultChunks = new Chunk[]
|
||||
{
|
||||
new InjectChunk("MyTestHtmlHelper", "Html"),
|
||||
new UsingChunk { Namespace = "AppNamespace.Model" },
|
||||
};
|
||||
var utility = new ChunkInheritanceUtility(new CodeTree(), defaultChunks, "dynamic");
|
||||
var utility = new ChunkInheritanceUtility(host, fileSystem, defaultChunks);
|
||||
|
||||
// Act
|
||||
var chunks = utility.GetInheritedChunks(host,
|
||||
fileSystem,
|
||||
@"x:\myapproot\views\home\Index.cshtml");
|
||||
var chunks = utility.GetInheritedChunks(@"x:\myapproot\views\home\Index.cshtml");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(4, chunks.Count);
|
||||
var injectChunk = Assert.IsType<InjectChunk>(chunks[0]);
|
||||
var injectChunk = Assert.IsType<InjectChunk>(chunks[1]);
|
||||
Assert.Equal("DifferentHelper<TModel>", injectChunk.TypeName);
|
||||
Assert.Equal("Html", injectChunk.MemberName);
|
||||
|
||||
var usingChunk = Assert.IsType<UsingChunk>(chunks[1]);
|
||||
Assert.Equal("AppNamespace.Models", usingChunk.Namespace);
|
||||
|
||||
injectChunk = Assert.IsType<InjectChunk>(chunks[2]);
|
||||
Assert.Equal("MyTestHtmlHelper", injectChunk.TypeName);
|
||||
Assert.Equal("Html", injectChunk.MemberName);
|
||||
|
||||
usingChunk = Assert.IsType<UsingChunk>(chunks[3]);
|
||||
Assert.Equal("AppNamespace.Model", usingChunk.Namespace);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MergeChunks_VisitsChunksPriorToMerging()
|
||||
{
|
||||
// Arrange
|
||||
var codeTree = new CodeTree();
|
||||
codeTree.Chunks.Add(new LiteralChunk());
|
||||
codeTree.Chunks.Add(new ExpressionBlockChunk());
|
||||
codeTree.Chunks.Add(new ExpressionBlockChunk());
|
||||
|
||||
var merger = new Mock<IChunkMerger>();
|
||||
var mockSequence = new MockSequence();
|
||||
merger.InSequence(mockSequence)
|
||||
.Setup(m => m.VisitChunk(It.IsAny<LiteralChunk>()))
|
||||
.Verifiable();
|
||||
merger.InSequence(mockSequence)
|
||||
.Setup(m => m.Merge(codeTree, It.IsAny<LiteralChunk>()))
|
||||
.Verifiable();
|
||||
var inheritedChunks = new List<Chunk>
|
||||
{
|
||||
new CodeAttributeChunk(),
|
||||
new LiteralChunk()
|
||||
};
|
||||
var utility = new ChunkInheritanceUtility(codeTree, inheritedChunks, "dynamic");
|
||||
|
||||
// Act
|
||||
utility.ChunkMergers[typeof(LiteralChunk)] = merger.Object;
|
||||
utility.MergeInheritedChunks(inheritedChunks);
|
||||
|
||||
// Assert
|
||||
merger.Verify();
|
||||
Assert.Same(defaultChunks[0], chunks[2]);
|
||||
Assert.Same(defaultChunks[1], chunks[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
var fileInfo = new Mock<IFileInfo>();
|
||||
fileInfo.Setup(f => f.CreateReadStream())
|
||||
.Returns(new MemoryStream(Encoding.UTF8.GetBytes(contents)));
|
||||
.Returns(() => new MemoryStream(Encoding.UTF8.GetBytes(contents)));
|
||||
fileInfo.SetupGet(f => f.PhysicalPath)
|
||||
.Returns(path);
|
||||
fileInfo.SetupGet(f => f.Name)
|
||||
|
|
|
|||
|
|
@ -29,5 +29,15 @@ namespace TagHelpersWebSite.Controllers
|
|||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
public ViewResult NestedViewStartTagHelper()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
public ViewResult ViewWithLayoutAndNestedTagHelper()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// 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 Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
|
||||
namespace TagHelpersWebSite.TagHelpers
|
||||
{
|
||||
[TagName("nested")]
|
||||
[ContentBehavior(ContentBehavior.Modify)]
|
||||
public class NestedViewStartTagHelper : TagHelper
|
||||
{
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
output.Content = "nested-content";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// 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 Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
|
||||
namespace TagHelpersWebSite.TagHelpers
|
||||
{
|
||||
[TagName("root")]
|
||||
[ContentBehavior(ContentBehavior.Replace)]
|
||||
public class RootViewStartTagHelper : TagHelper
|
||||
{
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
output.Content = "root-content";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
@{
|
||||
Layout = null;
|
||||
}
|
||||
<root />
|
||||
|
||||
|
||||
<nested>some-content</nested>
|
||||
|
|
@ -0,0 +1 @@
|
|||
@addtaghelper "TagHelpersWebSite.TagHelpers.NestedViewStartTagHelper, TagHelpersWebSite"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
@{
|
||||
Layout = "~/Views/Shared/_LayoutWithRootTagHelper.cshtml";
|
||||
}
|
||||
@addtaghelper "TagHelpersWebSite.TagHelpers.NestedViewStartTagHelper, TagHelpersWebSite"
|
||||
<nested>some-content</nested>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<root />
|
||||
@RenderBody()
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
@{
|
||||
Layout = "/Views/Shared/_Layout.cshtml";
|
||||
}
|
||||
}
|
||||
@addtaghelper "TagHelpersWebSite.TagHelpers.RootViewStartTagHelper, TagHelpersWebSite"
|
||||
Loading…
Reference in New Issue