// 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.FileProviders;
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 directives into a page from applicable _GlobalImport pages.
///
public class ChunkInheritanceUtility
{
private readonly MvcRazorHost _razorHost;
private readonly IReadOnlyList _defaultInheritedChunks;
private readonly ICodeTreeCache _codeTreeCache;
///
/// Initializes a new instance of .
///
/// The used to parse _GlobalImport pages.
/// that caches instances.
///
/// Sequence of s inherited by default.
public ChunkInheritanceUtility([NotNull] MvcRazorHost razorHost,
[NotNull] ICodeTreeCache codeTreeCache,
[NotNull] IReadOnlyList defaultInheritedChunks)
{
_razorHost = razorHost;
_defaultInheritedChunks = defaultInheritedChunks;
_codeTreeCache = codeTreeCache;
}
///
/// Gets an ordered of parsed for each
/// _GlobalImport that is applicable to the page located at . The list is
/// ordered so that the for the _GlobalImport closest to the
/// in the file system appears first.
///
/// The path of the page to locate inherited chunks for.
/// A of parsed _GlobalImport
/// s.
public IReadOnlyList GetInheritedCodeTrees([NotNull] string pagePath)
{
var inheritedCodeTrees = new List();
var templateEngine = new RazorTemplateEngine(_razorHost);
foreach (var globalImportPath in ViewHierarchyUtility.GetGlobalImportLocations(pagePath))
{
// globalImportPath contains the app-relative path of the _GlobalImport.
// Since the parsing of a _GlobalImport would cause parent _GlobalImports to be parsed
// we need to ensure the paths are app-relative to allow the GetGlobalFileLocations
// for the current _GlobalImport to succeed.
var codeTree = _codeTreeCache.GetOrAdd(globalImportPath,
fileInfo => ParseViewFile(templateEngine,
fileInfo,
globalImportPath));
if (codeTree != null)
{
inheritedCodeTrees.Add(codeTree);
}
}
return inheritedCodeTrees;
}
///
/// Merges inherited by default and instances produced by parsing
/// _GlobalImport files into the specified .
///
/// The to merge in to.
/// inherited from _GlobalImport
/// files.
/// The list of chunks to merge.
public void MergeInheritedCodeTrees([NotNull] CodeTree codeTree,
[NotNull] IReadOnlyList inheritedCodeTrees,
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.
// Read the chunks outside in - that is chunks from the _GlobalImport closest to the page get merged in first
// and the furthest one last. This allows the merger to ignore a directive like @model that was previously
// seen.
var chunksToMerge = inheritedCodeTrees.SelectMany(tree => tree.Chunks)
.Concat(_defaultInheritedChunks);
foreach (var chunk in chunksToMerge)
{
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;
}
}
}
}
}