// 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 System.Linq; 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 chunks into a page from _ViewStart pages that apply to it. /// public class ChunkInheritanceUtility { private readonly IReadOnlyList _defaultInheritedChunks; /// /// Instantiates a new instance of . /// /// The instance to add s to. /// The list of s inherited by default. /// The model type used in the event no model is specified via the /// @model keyword. public ChunkInheritanceUtility([NotNull] CodeTree codeTree, [NotNull] IReadOnlyList defaultInheritedChunks, [NotNull] string defaultModel) { CodeTree = codeTree; _defaultInheritedChunks = defaultInheritedChunks; ChunkMergers = GetMergerMappings(codeTree, defaultModel); } /// /// Gets the CodeTree to add inherited instances to. /// public CodeTree CodeTree { get; private set; } /// /// Gets a dictionary mapping type to the used to merge /// chunks of that type. /// public IDictionary ChunkMergers { get; private set; } /// /// 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. /// /// The used to parse _ViewStart pages. /// The filesystem that represents the application. /// The path of the page to locate inherited chunks for. /// A list of chunks that are applicable to the given page. public List GetInheritedChunks([NotNull] MvcRazorHost razorHost, [NotNull] IFileSystem fileSystem, [NotNull] string pagePath) { var inheritedChunks = new List(); var templateEngine = new RazorTemplateEngine(razorHost); foreach (var viewStart in ViewStartUtility.GetViewStartLocations(fileSystem, pagePath)) { IFileInfo fileInfo; if (fileSystem.TryGetFileInfo(viewStart, out fileInfo)) { var parsedTree = ParseViewFile(templateEngine, fileInfo); var chunksToAdd = parsedTree.Chunks .Where(chunk => ChunkMergers.ContainsKey(chunk.GetType())); inheritedChunks.AddRange(chunksToAdd); } } inheritedChunks.AddRange(_defaultInheritedChunks); return inheritedChunks; } /// /// Merges a list of chunks into the instance. /// /// The list of chunks to merge. public void MergeInheritedChunks(List inherited) { var current = CodeTree.Chunks; // 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) { if (ChunkMergers.TryGetValue(chunk.GetType(), out var 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 (ChunkMergers.TryGetValue(chunk.GetType(), out var 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); } } } 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) } }; } // TODO: This needs to be cached (#1016) private CodeTree ParseViewFile(RazorTemplateEngine engine, IFileInfo fileInfo) { using (var stream = fileInfo.CreateReadStream()) { using (var streamReader = new StreamReader(stream)) { var parseResults = engine.ParseTemplate(streamReader); var className = ParserHelpers.SanitizeClassName(fileInfo.Name); var language = engine.Host.CodeLanguage; var codeGenerator = language.CreateCodeGenerator(className, engine.Host.DefaultNamespace, fileInfo.PhysicalPath, engine.Host); codeGenerator.Visit(parseResults); return codeGenerator.Context.CodeTreeBuilder.CodeTree; } } } } }