Expose locations of `_ViewImports.cshtml` that affect a given Razor file.

- Added `ChunkTreeResult` to associate inherited chunks with a specific source file.
- Updated existing tests to validate file path.

#2256
This commit is contained in:
N. Taylor Mullen 2015-08-18 19:39:28 -07:00
parent 91c0081939
commit b871172dd0
6 changed files with 90 additions and 32 deletions

View File

@ -40,17 +40,18 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
} }
/// <summary> /// <summary>
/// Gets an ordered <see cref="IReadOnlyList{T}"/> of parsed <see cref="ChunkTree"/> for each /// Gets an ordered <see cref="IReadOnlyList{ChunkTreeResult}"/> of parsed <see cref="ChunkTree"/>s and
/// <c>_ViewImports</c> that is applicable to the page located at <paramref name="pagePath"/>. The list is /// file paths for each <c>_ViewImports</c> that is applicable to the page located at
/// ordered so that the <see cref="ChunkTree"/> for the <c>_ViewImports</c> closest to the /// <paramref name="pagePath"/>. The list is ordered so that the <see cref="ChunkTreeResult"/>'s
/// <see cref="ChunkTreeResult.ChunkTree"/> for the <c>_ViewImports</c> closest to the
/// <paramref name="pagePath"/> in the file system appears first. /// <paramref name="pagePath"/> in the file system 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{ChunkTree}"/> of parsed <c>_ViewImports</c> /// <returns>A <see cref="IReadOnlyList{ChunkTreeResult}"/> of parsed <c>_ViewImports</c>
/// <see cref="ChunkTree"/>s.</returns> /// <see cref="ChunkTree"/>s and their file paths.</returns>
public virtual IReadOnlyList<ChunkTree> GetInheritedChunkTrees([NotNull] string pagePath) public virtual IReadOnlyList<ChunkTreeResult> GetInheritedChunkTreeResults([NotNull] string pagePath)
{ {
var inheritedChunkTrees = new List<ChunkTree>(); var inheritedChunkTreeResults = new List<ChunkTreeResult>();
var templateEngine = new RazorTemplateEngine(_razorHost); var templateEngine = new RazorTemplateEngine(_razorHost);
foreach (var viewImportsPath in ViewHierarchyUtility.GetViewImportsLocations(pagePath)) foreach (var viewImportsPath in ViewHierarchyUtility.GetViewImportsLocations(pagePath))
{ {
@ -67,11 +68,12 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
if (chunkTree != null) if (chunkTree != null)
{ {
inheritedChunkTrees.Add(chunkTree); var result = new ChunkTreeResult(chunkTree, viewImportsPath);
inheritedChunkTreeResults.Add(result);
} }
} }
return inheritedChunkTrees; return inheritedChunkTreeResults;
} }
/// <summary> /// <summary>

View File

@ -0,0 +1,36 @@
// Copyright (c) .NET Foundation. 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.Chunks;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.Razor.Directives
{
/// <summary>
/// Contains <see cref="AspNet.Razor.Chunks.ChunkTree"/> information.
/// </summary>
public class ChunkTreeResult
{
/// <summary>
/// Initializes a new instance of <see cref="ChunkTreeResult"/>.
/// </summary>
/// <param name="chunkTree">The <see cref="AspNet.Razor.Chunks.ChunkTree"/> generated from the file at the
/// given <paramref name="filePath"/>.</param>
/// <param name="filePath">The path to the file that generated the given <paramref name="chunkTree"/>.</param>
public ChunkTreeResult([NotNull] ChunkTree chunkTree, [NotNull] string filePath)
{
ChunkTree = chunkTree;
FilePath = filePath;
}
/// <summary>
/// The <see cref="AspNet.Razor.Chunks.ChunkTree"/> generated from the file at <see cref="FilePath"/>.
/// </summary>
public ChunkTree ChunkTree { get; }
/// <summary>
/// The path to the file that generated the <see cref="ChunkTree"/>.
/// </summary>
public string FilePath { get; }
}
}

View File

@ -47,7 +47,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
ChunkTree chunkTree; ChunkTree chunkTree;
if (!_chunkTreeCache.TryGetValue(pagePath, out chunkTree)) if (!_chunkTreeCache.TryGetValue(pagePath, out chunkTree))
{ {
// GetOrAdd is invoked for each _GlobalImport that might potentially exist in the path. // GetOrAdd is invoked for each _ViewImport that might potentially exist in the path.
// We can avoid performing file system lookups for files that do not exist by caching // We can avoid performing file system lookups for files that do not exist by caching
// negative results and adding a Watch for that file. // negative results and adding a Watch for that file.
@ -58,7 +58,6 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
var file = _fileProvider.GetFileInfo(pagePath); var file = _fileProvider.GetFileInfo(pagePath);
chunkTree = file.Exists ? getChunkTree(file) : null; chunkTree = file.Exists ? getChunkTree(file) : null;
_chunkTreeCache.Set(pagePath, chunkTree, options); _chunkTreeCache.Set(pagePath, chunkTree, options);
} }

View File

@ -3,6 +3,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
#if NET45 #if NET45
using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.FileProviders;
#endif #endif
@ -224,6 +225,21 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
} }
/// <summary>
/// Locates and parses _ViewImports.cshtml files applying to the given <paramref name="sourceFileName"/> to
/// create <see cref="ChunkTreeResult"/>s.
/// </summary>
/// <param name="sourceFileName">The path to a Razor file to locate _ViewImports.cshtml for.</param>
/// <returns>Inherited <see cref="ChunkTreeResult"/>s.</returns>
public IReadOnlyList<ChunkTreeResult> GetInheritedChunkTreeResults([NotNull] string sourceFileName)
{
// Need the normalized path to resolve inherited chunks only. Full paths are needed for generated Razor
// files checksum and line pragmas to enable DesignTime debugging.
var normalizedPath = _pathNormalizer.NormalizePath(sourceFileName);
return ChunkInheritanceUtility.GetInheritedChunkTreeResults(normalizedPath);
}
/// <inheritdoc /> /// <inheritdoc />
public GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream) public GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream)
{ {
@ -236,9 +252,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)
{ {
sourceFileName = _pathNormalizer.NormalizePath(sourceFileName); var inheritedChunkTrees = GetInheritedChunkTrees(sourceFileName);
var inheritedChunkTrees = ChunkInheritanceUtility.GetInheritedChunkTrees(sourceFileName);
return new MvcRazorParser(razorParser, inheritedChunkTrees, DefaultInheritedChunks, ModelExpressionType); return new MvcRazorParser(razorParser, inheritedChunkTrees, DefaultInheritedChunks, ModelExpressionType);
} }
@ -253,14 +268,11 @@ namespace Microsoft.AspNet.Mvc.Razor
[NotNull] CodeGenerator incomingGenerator, [NotNull] CodeGenerator incomingGenerator,
[NotNull] CodeGeneratorContext context) [NotNull] CodeGeneratorContext context)
{ {
// Need the normalized path to resolve inherited chunks only. Full paths are needed for generated Razor var inheritedChunkTrees = GetInheritedChunkTrees(context.SourceFile);
// files checksum and line pragmas to enable DesignTime debugging.
var normalizedPath = _pathNormalizer.NormalizePath(context.SourceFile);
var inheritedChunks = ChunkInheritanceUtility.GetInheritedChunkTrees(normalizedPath);
ChunkInheritanceUtility.MergeInheritedChunkTrees( ChunkInheritanceUtility.MergeInheritedChunkTrees(
context.ChunkTreeBuilder.ChunkTree, context.ChunkTreeBuilder.ChunkTree,
inheritedChunks, inheritedChunkTrees,
DefaultModel); DefaultModel);
return new MvcCSharpCodeGenerator( return new MvcCSharpCodeGenerator(
@ -273,5 +285,14 @@ namespace Microsoft.AspNet.Mvc.Razor
CreateModelExpressionMethodName = CreateModelExpressionMethod CreateModelExpressionMethodName = CreateModelExpressionMethod
}); });
} }
private IReadOnlyList<ChunkTree> GetInheritedChunkTrees(string sourceFileName)
{
var inheritedChunkTrees = GetInheritedChunkTreeResults(sourceFileName)
.Select(result => result.ChunkTree)
.ToList();
return inheritedChunkTrees;
}
} }
} }

View File

@ -2,7 +2,6 @@
// 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 Microsoft.AspNet.Razor.Chunks; using Microsoft.AspNet.Razor.Chunks;
using Microsoft.AspNet.Testing;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Mvc.Razor.Directives namespace Microsoft.AspNet.Mvc.Razor.Directives
@ -36,14 +35,15 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
var utility = new ChunkInheritanceUtility(host, cache, defaultChunks); var utility = new ChunkInheritanceUtility(host, cache, defaultChunks);
// Act // Act
var chunkTrees = utility.GetInheritedChunkTrees(PlatformNormalizer.NormalizePath(@"Views\home\Index.cshtml")); var chunkTreeResults = utility.GetInheritedChunkTreeResults(
PlatformNormalizer.NormalizePath(@"Views\home\Index.cshtml"));
// Assert // Assert
Assert.Collection(chunkTrees, Assert.Collection(chunkTreeResults,
chunkTree => chunkTreeResult =>
{ {
var viewImportsPath = PlatformNormalizer.NormalizePath(@"Views\home\_ViewImports.cshtml"); var viewImportsPath = PlatformNormalizer.NormalizePath(@"Views\home\_ViewImports.cshtml");
Assert.Collection(chunkTree.Chunks, Assert.Collection(chunkTreeResult.ChunkTree.Chunks,
chunk => chunk =>
{ {
Assert.IsType<LiteralChunk>(chunk); Assert.IsType<LiteralChunk>(chunk);
@ -60,11 +60,12 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
Assert.IsType<LiteralChunk>(chunk); Assert.IsType<LiteralChunk>(chunk);
Assert.Equal(viewImportsPath, chunk.Start.FilePath); Assert.Equal(viewImportsPath, chunk.Start.FilePath);
}); });
Assert.Equal(viewImportsPath, chunkTreeResult.FilePath);
}, },
chunkTree => chunkTreeResult =>
{ {
var viewImportsPath = PlatformNormalizer.NormalizePath(@"Views\_ViewImports.cshtml"); var viewImportsPath = PlatformNormalizer.NormalizePath(@"Views\_ViewImports.cshtml");
Assert.Collection(chunkTree.Chunks, Assert.Collection(chunkTreeResult.ChunkTree.Chunks,
chunk => chunk =>
{ {
Assert.IsType<LiteralChunk>(chunk); Assert.IsType<LiteralChunk>(chunk);
@ -104,6 +105,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
Assert.IsType<LiteralChunk>(chunk); Assert.IsType<LiteralChunk>(chunk);
Assert.Equal(viewImportsPath, chunk.Start.FilePath); Assert.Equal(viewImportsPath, chunk.Start.FilePath);
}); });
Assert.Equal(viewImportsPath, chunkTreeResult.FilePath);
}); });
} }
@ -125,7 +127,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
var utility = new ChunkInheritanceUtility(host, cache, defaultChunks); var utility = new ChunkInheritanceUtility(host, cache, defaultChunks);
// Act // Act
var chunkTrees = utility.GetInheritedChunkTrees(PlatformNormalizer.NormalizePath(@"Views\home\Index.cshtml")); var chunkTrees = utility.GetInheritedChunkTreeResults(PlatformNormalizer.NormalizePath(@"Views\home\Index.cshtml"));
// Assert // Assert
Assert.Empty(chunkTrees); Assert.Empty(chunkTrees);
@ -168,9 +170,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
var chunkTree = new ChunkTree(); var chunkTree = new ChunkTree();
// Act // Act
utility.MergeInheritedChunkTrees(chunkTree, utility.MergeInheritedChunkTrees(chunkTree, inheritedChunkTrees, "dynamic");
inheritedChunkTrees,
"dynamic");
// Assert // Assert
Assert.Equal(3, chunkTree.Chunks.Count); Assert.Equal(3, chunkTree.Chunks.Count);

View File

@ -486,11 +486,11 @@ namespace Microsoft.AspNet.Mvc.Razor
public string InheritedChunkTreePagePath { get; private set; } public string InheritedChunkTreePagePath { get; private set; }
public override IReadOnlyList<ChunkTree> GetInheritedChunkTrees([NotNull] string pagePath) public override IReadOnlyList<ChunkTreeResult> GetInheritedChunkTreeResults([NotNull] string pagePath)
{ {
InheritedChunkTreePagePath = pagePath; InheritedChunkTreePagePath = pagePath;
return new ChunkTree[0]; return new ChunkTreeResult[0];
} }
} }
@ -533,7 +533,7 @@ namespace Microsoft.AspNet.Mvc.Razor
protected override CSharpCodeWriter CreateCodeWriter() protected override CSharpCodeWriter CreateCodeWriter()
{ {
// We normalize newlines so no matter what platform we're on // We normalize newlines so no matter what platform we're on
// they're consistent (for code generation tests). // they're consistent (for code generation tests).
var codeWriter = base.CreateCodeWriter(); var codeWriter = base.CreateCodeWriter();
codeWriter.NewLine = "\r\n"; codeWriter.NewLine = "\r\n";