From b871172dd07bc0a65bfabd5cb0a76b97a463ca73 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Tue, 18 Aug 2015 19:39:28 -0700 Subject: [PATCH] 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 --- .../Directives/ChunkInheritanceUtility.cs | 20 ++++++----- .../Directives/ChunkTreeResult.cs | 36 +++++++++++++++++++ .../Directives/DefaultChunkTreeCache.cs | 3 +- .../MvcRazorHost.cs | 35 ++++++++++++++---- .../Directives/ChunkInheritanceUtilityTest.cs | 22 ++++++------ .../MvcRazorHostTest.cs | 6 ++-- 6 files changed, 90 insertions(+), 32 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkTreeResult.cs diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkInheritanceUtility.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkInheritanceUtility.cs index 679dd5e7e8..53f3aba753 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkInheritanceUtility.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkInheritanceUtility.cs @@ -40,17 +40,18 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives } /// - /// Gets an ordered of parsed for each - /// _ViewImports that is applicable to the page located at . The list is - /// ordered so that the for the _ViewImports closest to the + /// Gets an ordered of parsed s and + /// file paths for each _ViewImports that is applicable to the page located at + /// . The list is ordered so that the 's + /// for the _ViewImports closest to the /// in the file system appears first. /// /// The path of the page to locate inherited chunks for. - /// A of parsed _ViewImports - /// s. - public virtual IReadOnlyList GetInheritedChunkTrees([NotNull] string pagePath) + /// A of parsed _ViewImports + /// s and their file paths. + public virtual IReadOnlyList GetInheritedChunkTreeResults([NotNull] string pagePath) { - var inheritedChunkTrees = new List(); + var inheritedChunkTreeResults = new List(); var templateEngine = new RazorTemplateEngine(_razorHost); foreach (var viewImportsPath in ViewHierarchyUtility.GetViewImportsLocations(pagePath)) { @@ -67,11 +68,12 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives if (chunkTree != null) { - inheritedChunkTrees.Add(chunkTree); + var result = new ChunkTreeResult(chunkTree, viewImportsPath); + inheritedChunkTreeResults.Add(result); } } - return inheritedChunkTrees; + return inheritedChunkTreeResults; } /// diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkTreeResult.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkTreeResult.cs new file mode 100644 index 0000000000..f139d06947 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkTreeResult.cs @@ -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 +{ + /// + /// Contains information. + /// + public class ChunkTreeResult + { + /// + /// Initializes a new instance of . + /// + /// The generated from the file at the + /// given . + /// The path to the file that generated the given . + public ChunkTreeResult([NotNull] ChunkTree chunkTree, [NotNull] string filePath) + { + ChunkTree = chunkTree; + FilePath = filePath; + } + + /// + /// The generated from the file at . + /// + public ChunkTree ChunkTree { get; } + + /// + /// The path to the file that generated the . + /// + public string FilePath { get; } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/DefaultChunkTreeCache.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/DefaultChunkTreeCache.cs index 56a91c4af8..681031c01b 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/DefaultChunkTreeCache.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/DefaultChunkTreeCache.cs @@ -47,7 +47,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives ChunkTree 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 // negative results and adding a Watch for that file. @@ -58,7 +58,6 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives var file = _fileProvider.GetFileInfo(pagePath); chunkTree = file.Exists ? getChunkTree(file) : null; - _chunkTreeCache.Set(pagePath, chunkTree, options); } diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs index 8a27fe01a1..f60d151046 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; +using System.Linq; #if NET45 using Microsoft.AspNet.FileProviders; #endif @@ -224,6 +225,21 @@ namespace Microsoft.AspNet.Mvc.Razor } } + /// + /// Locates and parses _ViewImports.cshtml files applying to the given to + /// create s. + /// + /// The path to a Razor file to locate _ViewImports.cshtml for. + /// Inherited s. + public IReadOnlyList 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); + } + /// public GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream) { @@ -236,9 +252,8 @@ namespace Microsoft.AspNet.Mvc.Razor /// 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); } @@ -253,14 +268,11 @@ namespace Microsoft.AspNet.Mvc.Razor [NotNull] CodeGenerator incomingGenerator, [NotNull] CodeGeneratorContext context) { - // 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(context.SourceFile); - var inheritedChunks = ChunkInheritanceUtility.GetInheritedChunkTrees(normalizedPath); + var inheritedChunkTrees = GetInheritedChunkTrees(context.SourceFile); ChunkInheritanceUtility.MergeInheritedChunkTrees( context.ChunkTreeBuilder.ChunkTree, - inheritedChunks, + inheritedChunkTrees, DefaultModel); return new MvcCSharpCodeGenerator( @@ -273,5 +285,14 @@ namespace Microsoft.AspNet.Mvc.Razor CreateModelExpressionMethodName = CreateModelExpressionMethod }); } + + private IReadOnlyList GetInheritedChunkTrees(string sourceFileName) + { + var inheritedChunkTrees = GetInheritedChunkTreeResults(sourceFileName) + .Select(result => result.ChunkTree) + .ToList(); + + return inheritedChunkTrees; + } } } diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/ChunkInheritanceUtilityTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/ChunkInheritanceUtilityTest.cs index 87116f6ddf..7a43d8690b 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/ChunkInheritanceUtilityTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/ChunkInheritanceUtilityTest.cs @@ -2,7 +2,6 @@ // 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.Testing; using Xunit; namespace Microsoft.AspNet.Mvc.Razor.Directives @@ -36,14 +35,15 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives var utility = new ChunkInheritanceUtility(host, cache, defaultChunks); // Act - var chunkTrees = utility.GetInheritedChunkTrees(PlatformNormalizer.NormalizePath(@"Views\home\Index.cshtml")); + var chunkTreeResults = utility.GetInheritedChunkTreeResults( + PlatformNormalizer.NormalizePath(@"Views\home\Index.cshtml")); // Assert - Assert.Collection(chunkTrees, - chunkTree => + Assert.Collection(chunkTreeResults, + chunkTreeResult => { var viewImportsPath = PlatformNormalizer.NormalizePath(@"Views\home\_ViewImports.cshtml"); - Assert.Collection(chunkTree.Chunks, + Assert.Collection(chunkTreeResult.ChunkTree.Chunks, chunk => { Assert.IsType(chunk); @@ -60,11 +60,12 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives Assert.IsType(chunk); Assert.Equal(viewImportsPath, chunk.Start.FilePath); }); + Assert.Equal(viewImportsPath, chunkTreeResult.FilePath); }, - chunkTree => + chunkTreeResult => { var viewImportsPath = PlatformNormalizer.NormalizePath(@"Views\_ViewImports.cshtml"); - Assert.Collection(chunkTree.Chunks, + Assert.Collection(chunkTreeResult.ChunkTree.Chunks, chunk => { Assert.IsType(chunk); @@ -104,6 +105,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives Assert.IsType(chunk); 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); // Act - var chunkTrees = utility.GetInheritedChunkTrees(PlatformNormalizer.NormalizePath(@"Views\home\Index.cshtml")); + var chunkTrees = utility.GetInheritedChunkTreeResults(PlatformNormalizer.NormalizePath(@"Views\home\Index.cshtml")); // Assert Assert.Empty(chunkTrees); @@ -168,9 +170,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives var chunkTree = new ChunkTree(); // Act - utility.MergeInheritedChunkTrees(chunkTree, - inheritedChunkTrees, - "dynamic"); + utility.MergeInheritedChunkTrees(chunkTree, inheritedChunkTrees, "dynamic"); // Assert Assert.Equal(3, chunkTree.Chunks.Count); diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs index 507fc6ea98..edb55b4daa 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs @@ -486,11 +486,11 @@ namespace Microsoft.AspNet.Mvc.Razor public string InheritedChunkTreePagePath { get; private set; } - public override IReadOnlyList GetInheritedChunkTrees([NotNull] string pagePath) + public override IReadOnlyList GetInheritedChunkTreeResults([NotNull] string pagePath) { InheritedChunkTreePagePath = pagePath; - return new ChunkTree[0]; + return new ChunkTreeResult[0]; } } @@ -533,7 +533,7 @@ namespace Microsoft.AspNet.Mvc.Razor 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). var codeWriter = base.CreateCodeWriter(); codeWriter.NewLine = "\r\n";