// 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 System.IO; using Microsoft.AspNet.FileSystems; using Microsoft.AspNet.Razor; using Microsoft.AspNet.Razor.Generator.Compiler; using Microsoft.AspNet.Razor.Parser; namespace Microsoft.AspNet.Mvc.Razor.Directives { /// /// A utility type for supporting inheritance of tag helpers and chunks into a page from applicable _ViewStart /// pages. /// public class ChunkInheritanceUtility { private readonly Dictionary _parsedCodeTrees; private readonly MvcRazorHost _razorHost; private readonly IFileSystem _fileSystem; private readonly IEnumerable _defaultInheritedChunks; /// /// Initializes a new instance of . /// /// The used to parse _ViewStart pages. /// The filesystem that represents the application. /// Sequence of s inherited by default. public ChunkInheritanceUtility([NotNull] MvcRazorHost razorHost, [NotNull] IFileSystem fileSystem, [NotNull] IEnumerable defaultInheritedChunks) { _razorHost = razorHost; _fileSystem = fileSystem; _defaultInheritedChunks = defaultInheritedChunks; _parsedCodeTrees = new Dictionary(StringComparer.Ordinal); } /// /// Gets a of containing parsed results of _ViewStart files /// that are used for inheriting tag helpers and chunks to the page located at . /// /// The path of the page to locate inherited chunks for. /// A of from _ViewStart pages. public IReadOnlyList GetInheritedChunks([NotNull] string pagePath) { var inheritedChunks = new List(); var templateEngine = new RazorTemplateEngine(_razorHost); foreach (var viewStartPath in ViewStartUtility.GetViewStartLocations(pagePath)) { CodeTree codeTree; if (_parsedCodeTrees.TryGetValue(viewStartPath, out codeTree)) { inheritedChunks.AddRange(codeTree.Chunks); } else { var fileInfo = _fileSystem.GetFileInfo(viewStartPath); if (fileInfo.Exists) { // viewStartPath contains the app-relative path of the ViewStart. // Since the parsing of a _ViewStart would cause parent _ViewStarts to be parsed // we need to ensure the paths are app-relative to allow the GetViewStartLocations // for the current _ViewStart to succeed. codeTree = ParseViewFile(templateEngine, fileInfo, viewStartPath); _parsedCodeTrees.Add(viewStartPath, codeTree); inheritedChunks.AddRange(codeTree.Chunks); } } } inheritedChunks.AddRange(_defaultInheritedChunks); return inheritedChunks; } /// /// Merges a list of chunks into the specified . /// /// The to merge. /// The of to merge. /// The list of chunks to merge. public void MergeInheritedChunks([NotNull] CodeTree codeTree, [NotNull] IReadOnlyList inherited, string defaultModel) { 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 codeTree.Chunks) { if (mergerMappings.TryGetValue(chunk.GetType(), out merger)) { merger.VisitChunk(chunk); } } // 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 // rules. foreach (var chunk in inherited) { if (mergerMappings.TryGetValue(chunk.GetType(), out merger)) { merger.Merge(codeTree, chunk); } } } private static Dictionary GetMergerMappings(CodeTree codeTree, string defaultModel) { var modelType = ChunkHelper.GetModelTypeName(codeTree, defaultModel); return new Dictionary { { typeof(UsingChunk), new UsingChunkMerger() }, { typeof(InjectChunk), new InjectChunkMerger(modelType) }, { typeof(SetBaseTypeChunk), new SetBaseTypeChunkMerger(modelType) } }; } private static CodeTree ParseViewFile(RazorTemplateEngine engine, IFileInfo fileInfo, string viewStartPath) { using (var stream = fileInfo.CreateReadStream()) { using (var streamReader = new StreamReader(stream)) { var parseResults = engine.ParseTemplate(streamReader, viewStartPath); var className = ParserHelpers.SanitizeClassName(fileInfo.Name); var language = engine.Host.CodeLanguage; var codeGenerator = language.CreateCodeGenerator(className, engine.Host.DefaultNamespace, viewStartPath, engine.Host); codeGenerator.Visit(parseResults); return codeGenerator.Context.CodeTreeBuilder.CodeTree; } } } } }