// 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 System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.Chunks; using Microsoft.AspNetCore.Razor.Parser; using Microsoft.Extensions.FileProviders; namespace Microsoft.AspNetCore.Mvc.Razor.Directives { /// /// A utility type for supporting inheritance of directives into a page from applicable _ViewImports pages. /// public class ChunkInheritanceUtility { private readonly MvcRazorHost _razorHost; private readonly IReadOnlyList _defaultInheritedChunks; private readonly IChunkTreeCache _chunkTreeCache; /// /// Initializes a new instance of . /// /// The used to parse _ViewImports pages. /// that caches instances. /// /// Sequence of s inherited by default. public ChunkInheritanceUtility( MvcRazorHost razorHost, IChunkTreeCache chunkTreeCache, IReadOnlyList defaultInheritedChunks) { if (razorHost == null) { throw new ArgumentNullException(nameof(razorHost)); } if (chunkTreeCache == null) { throw new ArgumentNullException(nameof(chunkTreeCache)); } if (defaultInheritedChunks == null) { throw new ArgumentNullException(nameof(defaultInheritedChunks)); } _razorHost = razorHost; _defaultInheritedChunks = defaultInheritedChunks; _chunkTreeCache = chunkTreeCache; } /// /// 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 and their file paths. /// /// The resulting is ordered so that the result /// for a _ViewImport closest to the application root appears first and the _ViewImport /// closest to the page appears last i.e. /// [ /_ViewImport, /Views/_ViewImport, /Views/Home/_ViewImport ] /// public virtual IReadOnlyList GetInheritedChunkTreeResults(string pagePath) { if (pagePath == null) { throw new ArgumentNullException(nameof(pagePath)); } var inheritedChunkTreeResults = new List(); var templateEngine = new RazorTemplateEngine(_razorHost); foreach (var viewImportsPath in ViewHierarchyUtility.GetViewImportsLocations(pagePath)) { // viewImportsPath contains the app-relative path of the _ViewImports. // Since the parsing of a _ViewImports would cause parent _ViewImports to be parsed // we need to ensure the paths are app-relative to allow the GetGlobalFileLocations // for the current _ViewImports to succeed. var chunkTree = _chunkTreeCache.GetOrAdd( viewImportsPath, fileInfo => ParseViewFile( templateEngine, fileInfo, viewImportsPath)); if (chunkTree != null) { var result = new ChunkTreeResult(chunkTree, viewImportsPath); inheritedChunkTreeResults.Insert(0, result); } } return inheritedChunkTreeResults; } /// /// Merges inherited by default and instances produced by parsing /// _ViewImports files into the specified . /// /// The to merge in to. /// inherited from _ViewImports /// files. /// The default model name. public void MergeInheritedChunkTrees( ChunkTree chunkTree, IReadOnlyList inheritedChunkTrees, string defaultModel) { if (chunkTree == null) { throw new ArgumentNullException(nameof(chunkTree)); } if (inheritedChunkTrees == null) { throw new ArgumentNullException(nameof(inheritedChunkTrees)); } var chunkMergers = GetChunkMergers(chunkTree, defaultModel); // We merge chunks into the ChunkTree in two passes. In the first pass, we traverse the ChunkTree visiting // a mapped IChunkMerger for types that are registered. foreach (var chunk in chunkTree.Children) { foreach (var merger in chunkMergers) { merger.VisitChunk(chunk); } } var inheritedChunks = _defaultInheritedChunks.Concat( inheritedChunkTrees.SelectMany(tree => tree.Children)).ToArray(); foreach (var merger in chunkMergers) { merger.MergeInheritedChunks(chunkTree, inheritedChunks); } } private static IChunkMerger[] GetChunkMergers(ChunkTree chunkTree, string defaultModel) { var modelType = ChunkHelper.GetModelTypeName(chunkTree, defaultModel); return new IChunkMerger[] { new UsingChunkMerger(), new InjectChunkMerger(modelType), new SetBaseTypeChunkMerger(modelType) }; } private static ChunkTree ParseViewFile( RazorTemplateEngine engine, IFileInfo fileInfo, string viewImportsPath) { using (var stream = fileInfo.CreateReadStream()) { using (var streamReader = new StreamReader(stream)) { var parseResults = engine.ParseTemplate(streamReader, viewImportsPath); var className = ParserHelpers.SanitizeClassName(fileInfo.Name); var language = engine.Host.CodeLanguage; var chunkGenerator = language.CreateChunkGenerator( className, engine.Host.DefaultNamespace, viewImportsPath, engine.Host); chunkGenerator.Visit(parseResults); // Rewrite the location of inherited chunks so they point to the global import file. var chunkTree = chunkGenerator.Context.ChunkTreeBuilder.Root; foreach (var chunk in chunkTree.Children) { chunk.Start = new SourceLocation( viewImportsPath, chunk.Start.AbsoluteIndex, chunk.Start.LineIndex, chunk.Start.CharacterIndex); } return chunkTree; } } } } }