// 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;
}
}
}
}
}