Introduce MvcRazorTemplateEngine
This commit is contained in:
parent
4faef7afaf
commit
f7fd5114b3
|
|
@ -0,0 +1,62 @@
|
|||
// 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.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="RazorTemplateEngine"/> for Mvc Razor views.
|
||||
/// </summary>
|
||||
public class MvcRazorTemplateEngine : RazorTemplateEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="MvcRazorTemplateEngine"/>.
|
||||
/// </summary>
|
||||
/// <param name="engine">The <see cref="RazorEngine"/>.</param>
|
||||
/// <param name="project">The <see cref="RazorProject"/>.</param>
|
||||
public MvcRazorTemplateEngine(
|
||||
RazorEngine engine,
|
||||
RazorProject project)
|
||||
: base(engine, project)
|
||||
{
|
||||
Options.DefaultImports = GetDefaultImports();
|
||||
}
|
||||
|
||||
/// <inheritsdoc />
|
||||
public override RazorCodeDocument CreateCodeDocument(RazorProjectItem projectItem)
|
||||
{
|
||||
var codeDocument = base.CreateCodeDocument(projectItem);
|
||||
codeDocument.SetRelativePath(projectItem.Path);
|
||||
|
||||
return codeDocument;
|
||||
}
|
||||
|
||||
private static RazorSourceDocument GetDefaultImports()
|
||||
{
|
||||
using (var stream = new MemoryStream())
|
||||
using (var writer = new StreamWriter(stream, Encoding.UTF8))
|
||||
{
|
||||
writer.WriteLine("@using System");
|
||||
writer.WriteLine("@using System.Linq");
|
||||
writer.WriteLine("@using System.Collections.Generic");
|
||||
writer.WriteLine("@using Microsoft.AspNetCore.Mvc");
|
||||
writer.WriteLine("@using Microsoft.AspNetCore.Mvc.Rendering");
|
||||
writer.WriteLine("@using Microsoft.AspNetCore.Mvc.ViewFeatures");
|
||||
writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel> Html");
|
||||
writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json");
|
||||
writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component");
|
||||
writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.IUrlHelper Url");
|
||||
writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider");
|
||||
writer.WriteLine("@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor");
|
||||
writer.Flush();
|
||||
|
||||
stream.Position = 0;
|
||||
return RazorSourceDocument.ReadFrom(stream, fileName: null, encoding: Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
// 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 System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains methods to locate <c>_ViewStart.cshtml</c> and <c>_ViewImports.cshtml</c>
|
||||
/// </summary>
|
||||
public static class ViewHierarchyUtility
|
||||
{
|
||||
private const string ViewStartFileName = "_ViewStart.cshtml";
|
||||
|
||||
/// <summary>
|
||||
/// File name of <c>_ViewImports.cshtml</c> file
|
||||
/// </summary>
|
||||
public static readonly string ViewImportsFileName = "_ViewImports.cshtml";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the view start locations that are applicable to the specified path.
|
||||
/// </summary>
|
||||
/// <param name="applicationRelativePath">The application relative path of the file to locate
|
||||
/// <c>_ViewStart</c>s for.</param>
|
||||
/// <returns>A sequence of paths that represent potential view start locations.</returns>
|
||||
/// <remarks>
|
||||
/// This method returns paths starting from the directory of <paramref name="applicationRelativePath"/> and
|
||||
/// moves upwards until it hits the application root.
|
||||
/// e.g.
|
||||
/// /Views/Home/View.cshtml -> [ /Views/Home/_ViewStart.cshtml, /Views/_ViewStart.cshtml, /_ViewStart.cshtml ]
|
||||
/// </remarks>
|
||||
public static IEnumerable<string> GetViewStartLocations(string applicationRelativePath)
|
||||
{
|
||||
return GetHierarchicalPath(applicationRelativePath, ViewStartFileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the locations for <c>_ViewImports</c>s that are applicable to the specified path.
|
||||
/// </summary>
|
||||
/// <param name="applicationRelativePath">The application relative path of the file to locate
|
||||
/// <c>_ViewImports</c>s for.</param>
|
||||
/// <returns>A sequence of paths that represent potential <c>_ViewImports</c> locations.</returns>
|
||||
/// <remarks>
|
||||
/// This method returns paths starting from the directory of <paramref name="applicationRelativePath"/> and
|
||||
/// moves upwards until it hits the application root.
|
||||
/// e.g.
|
||||
/// /Views/Home/View.cshtml -> [ /Views/Home/_ViewImports.cshtml, /Views/_ViewImports.cshtml,
|
||||
/// /_ViewImports.cshtml ]
|
||||
/// </remarks>
|
||||
public static IEnumerable<string> GetViewImportsLocations(string applicationRelativePath)
|
||||
{
|
||||
return GetHierarchicalPath(applicationRelativePath, ViewImportsFileName);
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetHierarchicalPath(string relativePath, string fileName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(relativePath))
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
if (relativePath.StartsWith("~/", StringComparison.Ordinal))
|
||||
{
|
||||
relativePath = relativePath.Substring(2);
|
||||
}
|
||||
|
||||
if (relativePath.StartsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
relativePath = relativePath.Substring(1);
|
||||
}
|
||||
|
||||
if (string.Equals(Path.GetFileName(relativePath), fileName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// If the specified path is for the file hierarchy being constructed, then the first file that applies
|
||||
// to it is in a parent directory.
|
||||
relativePath = Path.GetDirectoryName(relativePath);
|
||||
|
||||
if (string.IsNullOrEmpty(relativePath))
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
var builder = new StringBuilder(relativePath);
|
||||
builder.Replace('\\', '/');
|
||||
|
||||
if (builder.Length > 0 && builder[0] != '/')
|
||||
{
|
||||
builder.Insert(0, '/');
|
||||
}
|
||||
|
||||
var locations = new List<string>();
|
||||
for (var index = builder.Length - 1; index >= 0; index--)
|
||||
{
|
||||
if (builder[index] == '/')
|
||||
{
|
||||
builder.Length = index + 1;
|
||||
builder.Append(fileName);
|
||||
|
||||
locations.Add(builder.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return locations;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the contracts for a service that compiles Razor files.
|
||||
/// </summary>
|
||||
public interface IRazorCompilationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Compiles the razor file located at <paramref name="fileInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="fileInfo">A <see cref="RelativeFileInfo"/> instance that represents the file to compile.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A <see cref="CompilationResult"/> that represents the results of parsing and compiling the file.
|
||||
/// </returns>
|
||||
CompilationResult Compile(RelativeFileInfo fileInfo);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
// 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 Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
/// <summary>
|
||||
/// A container type that represents <see cref="IFileInfo"/> along with the application base relative path
|
||||
/// for a file in the file system.
|
||||
/// </summary>
|
||||
public class RelativeFileInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="RelativeFileInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="fileInfo"><see cref="IFileInfo"/> for the file.</param>
|
||||
/// <param name="relativePath">Path of the file relative to the application base.</param>
|
||||
public RelativeFileInfo(IFileInfo fileInfo, string relativePath)
|
||||
{
|
||||
if (fileInfo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(fileInfo));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(relativePath))
|
||||
{
|
||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(relativePath));
|
||||
}
|
||||
|
||||
FileInfo = fileInfo;
|
||||
RelativePath = relativePath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IFileInfo"/> associated with this instance of <see cref="RelativeFileInfo"/>.
|
||||
/// </summary>
|
||||
public IFileInfo FileInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path of the file relative to the application base.
|
||||
/// </summary>
|
||||
public string RelativePath { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -170,12 +170,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
// In the default scenario the following services are singleton by virtue of being initialized as part of
|
||||
// creating the singleton RazorViewEngine instance.
|
||||
services.TryAddTransient<IRazorPageFactoryProvider, DefaultRazorPageFactoryProvider>();
|
||||
services.TryAddTransient<IRazorCompilationService, RazorCompilationService>();
|
||||
|
||||
services.TryAddSingleton<RazorProject>(s =>
|
||||
{
|
||||
return new DefaultRazorProject(s.GetRequiredService<IRazorViewEngineFileProviderAccessor>().FileProvider);
|
||||
});
|
||||
services.TryAddSingleton<RazorProject, DefaultRazorProject>();
|
||||
|
||||
services.TryAddSingleton<RazorEngine>(s =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -67,16 +67,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
/// <inheritdoc />
|
||||
public CompilerCacheResult GetOrAdd(
|
||||
string relativePath,
|
||||
Func<RelativeFileInfo, CompilationResult> compile)
|
||||
Func<string, CompilerCacheContext> cacheContextFactory)
|
||||
{
|
||||
if (relativePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(relativePath));
|
||||
}
|
||||
|
||||
if (compile == null)
|
||||
if (cacheContextFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(compile));
|
||||
throw new ArgumentNullException(nameof(cacheContextFactory));
|
||||
}
|
||||
|
||||
Task<CompilerCacheResult> cacheEntry;
|
||||
|
|
@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
var normalizedPath = GetNormalizedPath(relativePath);
|
||||
if (!_cache.TryGetValue(normalizedPath, out cacheEntry))
|
||||
{
|
||||
cacheEntry = CreateCacheEntry(relativePath, normalizedPath, compile);
|
||||
cacheEntry = CreateCacheEntry(normalizedPath, cacheContextFactory);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -97,14 +97,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
}
|
||||
|
||||
private Task<CompilerCacheResult> CreateCacheEntry(
|
||||
string relativePath,
|
||||
string normalizedPath,
|
||||
Func<RelativeFileInfo, CompilationResult> compile)
|
||||
Func<string, CompilerCacheContext> cacheContextFactory)
|
||||
{
|
||||
TaskCompletionSource<CompilerCacheResult> compilationTaskSource = null;
|
||||
MemoryCacheEntryOptions cacheEntryOptions = null;
|
||||
IFileInfo fileInfo = null;
|
||||
MemoryCacheEntryOptions cacheEntryOptions;
|
||||
Task<CompilerCacheResult> cacheEntry;
|
||||
CompilerCacheContext compilerCacheContext;
|
||||
|
||||
// Safe races cannot be allowed when compiling Razor pages. To ensure only one compilation request succeeds
|
||||
// per file, we'll lock the creation of a cache entry. Creating the cache entry should be very quick. The
|
||||
|
|
@ -125,40 +124,39 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
fileInfo = _fileProvider.GetFileInfo(normalizedPath);
|
||||
if (!fileInfo.Exists)
|
||||
{
|
||||
var expirationToken = _fileProvider.Watch(normalizedPath);
|
||||
cacheEntry = Task.FromResult(new CompilerCacheResult(new[] { expirationToken }));
|
||||
cacheEntryOptions = new MemoryCacheEntryOptions();
|
||||
|
||||
cacheEntryOptions = new MemoryCacheEntryOptions();
|
||||
cacheEntryOptions.AddExpirationToken(expirationToken);
|
||||
compilerCacheContext = cacheContextFactory(normalizedPath);
|
||||
cacheEntryOptions.ExpirationTokens.Add(_fileProvider.Watch(compilerCacheContext.ProjectItem.Path));
|
||||
if (!compilerCacheContext.ProjectItem.Exists)
|
||||
{
|
||||
cacheEntry = Task.FromResult(new CompilerCacheResult(normalizedPath, cacheEntryOptions.ExpirationTokens));
|
||||
}
|
||||
else
|
||||
{
|
||||
cacheEntryOptions = GetMemoryCacheEntryOptions(normalizedPath);
|
||||
|
||||
// A file exists and needs to be compiled.
|
||||
compilationTaskSource = new TaskCompletionSource<CompilerCacheResult>();
|
||||
foreach (var projectItem in compilerCacheContext.AdditionalCompilationItems)
|
||||
{
|
||||
cacheEntryOptions.ExpirationTokens.Add(_fileProvider.Watch(projectItem.Path));
|
||||
}
|
||||
cacheEntry = compilationTaskSource.Task;
|
||||
}
|
||||
|
||||
cacheEntry = _cache.Set<Task<CompilerCacheResult>>(normalizedPath, cacheEntry, cacheEntryOptions);
|
||||
cacheEntry = _cache.Set(normalizedPath, cacheEntry, cacheEntryOptions);
|
||||
}
|
||||
|
||||
if (compilationTaskSource != null)
|
||||
{
|
||||
// Indicates that the file was found and needs to be compiled.
|
||||
Debug.Assert(fileInfo != null && fileInfo.Exists);
|
||||
// Indicates that a file was found and needs to be compiled.
|
||||
Debug.Assert(cacheEntryOptions != null);
|
||||
var relativeFileInfo = new RelativeFileInfo(fileInfo, normalizedPath);
|
||||
|
||||
try
|
||||
{
|
||||
var compilationResult = compile(relativeFileInfo);
|
||||
var compilationResult = compilerCacheContext.Compile(compilerCacheContext);
|
||||
compilationResult.EnsureSuccessful();
|
||||
compilationTaskSource.SetResult(
|
||||
new CompilerCacheResult(relativePath, compilationResult, cacheEntryOptions.ExpirationTokens));
|
||||
new CompilerCacheResult(normalizedPath, compilationResult, cacheEntryOptions.ExpirationTokens));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -169,20 +167,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
return cacheEntry;
|
||||
}
|
||||
|
||||
private MemoryCacheEntryOptions GetMemoryCacheEntryOptions(string relativePath)
|
||||
{
|
||||
var options = new MemoryCacheEntryOptions();
|
||||
options.AddExpirationToken(_fileProvider.Watch(relativePath));
|
||||
|
||||
var viewImportsPaths = ViewHierarchyUtility.GetViewImportsLocations(relativePath);
|
||||
foreach (var location in viewImportsPaths)
|
||||
{
|
||||
options.AddExpirationToken(_fileProvider.Watch(location));
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private string GetNormalizedPath(string relativePath)
|
||||
{
|
||||
Debug.Assert(relativePath != null);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
// 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 Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||
{
|
||||
public struct CompilerCacheContext
|
||||
{
|
||||
public CompilerCacheContext(
|
||||
RazorProjectItem projectItem,
|
||||
IEnumerable<RazorProjectItem> additionalCompilationItems,
|
||||
Func<CompilerCacheContext, CompilationResult> compile)
|
||||
{
|
||||
ProjectItem = projectItem;
|
||||
AdditionalCompilationItems = additionalCompilationItems;
|
||||
Compile = compile;
|
||||
}
|
||||
|
||||
public RazorProjectItem ProjectItem { get; }
|
||||
|
||||
public IEnumerable<RazorProjectItem> AdditionalCompilationItems { get; }
|
||||
|
||||
public Func<CompilerCacheContext, CompilationResult> Compile { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
|
@ -18,21 +16,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="CompilerCacheResult"/> with the specified
|
||||
/// <see cref="Compilation.CompilationResult"/>.
|
||||
/// <see cref="CompilationResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="relativePath">Path of the view file relative to the application base.</param>
|
||||
/// <param name="compilationResult">The <see cref="Compilation.CompilationResult"/>.</param>
|
||||
public CompilerCacheResult(string relativePath, CompilationResult compilationResult)
|
||||
: this(relativePath, compilationResult, EmptyArray<IChangeToken>.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="CompilerCacheResult"/> with the specified
|
||||
/// <see cref="Compilation.CompilationResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="relativePath">Path of the view file relative to the application base.</param>
|
||||
/// <param name="compilationResult">The <see cref="Compilation.CompilationResult"/>.</param>
|
||||
/// <param name="compilationResult">The <see cref="CompilationResult"/>.</param>
|
||||
/// <param name="isPrecompiled"><c>true</c> if the view is precompiled, <c>false</c> otherwise.</param>
|
||||
public CompilerCacheResult(string relativePath, CompilationResult compilationResult, bool isPrecompiled)
|
||||
: this(relativePath, compilationResult, EmptyArray<IChangeToken>.Instance)
|
||||
|
|
@ -42,10 +29,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="CompilerCacheResult"/> with the specified
|
||||
/// <see cref="Compilation.CompilationResult"/>.
|
||||
/// <see cref="CompilationResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="relativePath">Path of the view file relative to the application base.</param>
|
||||
/// <param name="compilationResult">The <see cref="Compilation.CompilationResult"/>.</param>
|
||||
/// <param name="compilationResult">The <see cref="CompilationResult"/>.</param>
|
||||
/// <param name="expirationTokens">One or more <see cref="IChangeToken"/> instances that indicate when
|
||||
/// this result has expired.</param>
|
||||
public CompilerCacheResult(string relativePath, CompilationResult compilationResult, IList<IChangeToken> expirationTokens)
|
||||
|
|
@ -55,18 +42,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
throw new ArgumentNullException(nameof(expirationTokens));
|
||||
}
|
||||
|
||||
RelativePath = relativePath;
|
||||
CompiledType = compilationResult.CompiledType;
|
||||
ExpirationTokens = expirationTokens;
|
||||
var compiledType = compilationResult.CompiledType;
|
||||
|
||||
var newExpression = Expression.New(compiledType);
|
||||
|
||||
var pathProperty = compiledType.GetProperty(nameof(IRazorPage.Path));
|
||||
|
||||
var propertyBindExpression = Expression.Bind(pathProperty, Expression.Constant(relativePath));
|
||||
var objectInitializeExpression = Expression.MemberInit(newExpression, propertyBindExpression);
|
||||
PageFactory = Expression
|
||||
.Lambda<Func<IRazorPage>>(objectInitializeExpression)
|
||||
.Compile();
|
||||
IsPrecompiled = false;
|
||||
}
|
||||
|
||||
|
|
@ -74,9 +52,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
/// Initializes a new instance of <see cref="CompilerCacheResult"/> for a file that could not be
|
||||
/// found in the file system.
|
||||
/// </summary>
|
||||
/// <param name="relativePath">Path of the view file relative to the application base.</param>
|
||||
/// <param name="expirationTokens">One or more <see cref="IChangeToken"/> instances that indicate when
|
||||
/// this result has expired.</param>
|
||||
public CompilerCacheResult(IList<IChangeToken> expirationTokens)
|
||||
public CompilerCacheResult(string relativePath, IList<IChangeToken> expirationTokens)
|
||||
{
|
||||
if (expirationTokens == null)
|
||||
{
|
||||
|
|
@ -84,7 +63,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
}
|
||||
|
||||
ExpirationTokens = expirationTokens;
|
||||
PageFactory = null;
|
||||
RelativePath = null;
|
||||
CompiledType = null;
|
||||
IsPrecompiled = false;
|
||||
}
|
||||
|
||||
|
|
@ -96,12 +76,17 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
/// <summary>
|
||||
/// Gets a value that determines if the view was successfully found and compiled.
|
||||
/// </summary>
|
||||
public bool Success => PageFactory != null;
|
||||
public bool Success => CompiledType != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a delegate that creates an instance of the <see cref="IRazorPage"/>.
|
||||
/// Normalized relative path of the file.
|
||||
/// </summary>
|
||||
public Func<IRazorPage> PageFactory { get; }
|
||||
public string RelativePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The compiled <see cref="Type"/>.
|
||||
/// </summary>
|
||||
public Type CompiledType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that determines if the view is precompiled.
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||
{
|
||||
|
|
@ -13,37 +15,26 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
/// </summary>
|
||||
public class DefaultRazorPageFactoryProvider : IRazorPageFactoryProvider
|
||||
{
|
||||
/// <remarks>
|
||||
/// This delegate holds on to an instance of <see cref="IRazorCompilationService"/>.
|
||||
/// </remarks>
|
||||
private readonly Func<RelativeFileInfo, CompilationResult> _compileDelegate;
|
||||
private readonly ICompilerCacheProvider _compilerCacheProvider;
|
||||
private ICompilerCache _compilerCache;
|
||||
private const string ViewImportsFileName = "_ViewImports.cshtml";
|
||||
private readonly RazorCompiler _razorCompiler;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="DefaultRazorPageFactoryProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="razorCompilationService">The <see cref="IRazorCompilationService"/>.</param>
|
||||
/// <param name="razorEngine">The <see cref="RazorEngine"/>.</param>
|
||||
/// <param name="razorProject">The <see cref="RazorProject" />.</param>
|
||||
/// <param name="compilationService">The <see cref="ICompilationService"/>.</param>
|
||||
/// <param name="compilerCacheProvider">The <see cref="ICompilerCacheProvider"/>.</param>
|
||||
public DefaultRazorPageFactoryProvider(
|
||||
IRazorCompilationService razorCompilationService,
|
||||
RazorEngine razorEngine,
|
||||
RazorProject razorProject,
|
||||
ICompilationService compilationService,
|
||||
ICompilerCacheProvider compilerCacheProvider)
|
||||
{
|
||||
_compileDelegate = razorCompilationService.Compile;
|
||||
_compilerCacheProvider = compilerCacheProvider;
|
||||
}
|
||||
var templateEngine = new MvcRazorTemplateEngine(razorEngine, razorProject);
|
||||
templateEngine.Options.ImportsFileName = ViewImportsFileName;
|
||||
|
||||
private ICompilerCache CompilerCache
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_compilerCache == null)
|
||||
{
|
||||
_compilerCache = _compilerCacheProvider.Cache;
|
||||
}
|
||||
|
||||
return _compilerCache;
|
||||
}
|
||||
_razorCompiler = new RazorCompiler(compilationService, compilerCacheProvider, templateEngine);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -59,10 +50,23 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
// For tilde slash paths, drop the leading ~ to make it work with the underlying IFileProvider.
|
||||
relativePath = relativePath.Substring(1);
|
||||
}
|
||||
var result = CompilerCache.GetOrAdd(relativePath, _compileDelegate);
|
||||
|
||||
var result = _razorCompiler.Compile(relativePath);
|
||||
if (result.Success)
|
||||
{
|
||||
return new RazorPageFactoryResult(result.PageFactory, result.ExpirationTokens, result.IsPrecompiled);
|
||||
var compiledType = result.CompiledType;
|
||||
|
||||
var newExpression = Expression.New(compiledType);
|
||||
var pathProperty = compiledType.GetTypeInfo().GetProperty(nameof(IRazorPage.Path));
|
||||
|
||||
// Generate: page.Path = relativePath;
|
||||
// Use the normalized path specified from the result.
|
||||
var propertyBindExpression = Expression.Bind(pathProperty, Expression.Constant(result.RelativePath));
|
||||
var objectInitializeExpression = Expression.MemberInit(newExpression, propertyBindExpression);
|
||||
var pageFactory = Expression
|
||||
.Lambda<Func<IRazorPage>>(objectInitializeExpression)
|
||||
.Compile();
|
||||
return new RazorPageFactoryResult(pageFactory, result.ExpirationTokens, result.IsPrecompiled);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,7 +14,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
private const string RazorFileExtension = ".cshtml";
|
||||
private readonly IFileProvider _provider;
|
||||
|
||||
public DefaultRazorProject(IFileProvider provider)
|
||||
public DefaultRazorProject(IRazorViewEngineFileProviderAccessor accessor)
|
||||
: this(accessor.FileProvider)
|
||||
{
|
||||
}
|
||||
|
||||
// Internal for unit testing
|
||||
internal DefaultRazorProject(IFileProvider provider)
|
||||
{
|
||||
_provider = provider;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
/// <returns>A cached <see cref="CompilationResult"/>.</returns>
|
||||
CompilerCacheResult GetOrAdd(
|
||||
string relativePath,
|
||||
Func<RelativeFileInfo, CompilationResult> compile);
|
||||
Func<string, CompilerCacheContext> compile);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,203 +0,0 @@
|
|||
// 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.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of <see cref="IRazorCompilationService"/>.
|
||||
/// </summary>
|
||||
public class RazorCompilationService : IRazorCompilationService
|
||||
{
|
||||
private readonly ICompilationService _compilationService;
|
||||
private readonly RazorEngine _engine;
|
||||
private readonly RazorProject _project;
|
||||
private readonly IFileProvider _fileProvider;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of the <see cref="RazorCompilationService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="compilationService">The <see cref="ICompilationService"/> to compile generated code.</param>
|
||||
/// <param name="engine">The <see cref="RazorEngine"/> to generate code from Razor files.</param>
|
||||
/// <param name="project">The <see cref="RazorProject"/> implementation for locating files.</param>
|
||||
/// <param name="fileProviderAccessor">The <see cref="IRazorViewEngineFileProviderAccessor"/>.</param>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
||||
public RazorCompilationService(
|
||||
ICompilationService compilationService,
|
||||
RazorEngine engine,
|
||||
RazorProject project,
|
||||
IRazorViewEngineFileProviderAccessor fileProviderAccessor,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
_compilationService = compilationService;
|
||||
_engine = engine;
|
||||
_fileProvider = fileProviderAccessor.FileProvider;
|
||||
_logger = loggerFactory.CreateLogger<RazorCompilationService>();
|
||||
|
||||
_project = project;
|
||||
|
||||
var stream = new MemoryStream();
|
||||
var writer = new StreamWriter(stream, Encoding.UTF8);
|
||||
writer.WriteLine("@using System");
|
||||
writer.WriteLine("@using System.Linq");
|
||||
writer.WriteLine("@using System.Collections.Generic");
|
||||
writer.WriteLine("@using Microsoft.AspNetCore.Mvc");
|
||||
writer.WriteLine("@using Microsoft.AspNetCore.Mvc.Rendering");
|
||||
writer.WriteLine("@using Microsoft.AspNetCore.Mvc.ViewFeatures");
|
||||
writer.WriteLine("@inject Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel> Html");
|
||||
writer.WriteLine("@inject Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json");
|
||||
writer.WriteLine("@inject Microsoft.AspNetCore.Mvc.IViewComponentHelper Component");
|
||||
writer.WriteLine("@inject Microsoft.AspNetCore.Mvc.IUrlHelper Url");
|
||||
writer.WriteLine("@inject Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider");
|
||||
writer.WriteLine("@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor");
|
||||
writer.Flush();
|
||||
|
||||
stream.Seek(0L, SeekOrigin.Begin);
|
||||
GlobalImports = RazorSourceDocument.ReadFrom(stream, fileName: null, encoding: Encoding.UTF8);
|
||||
}
|
||||
|
||||
public RazorSourceDocument GlobalImports { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public CompilationResult Compile(RelativeFileInfo file)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(file));
|
||||
}
|
||||
|
||||
RazorCodeDocument codeDocument;
|
||||
RazorCSharpDocument cSharpDocument;
|
||||
using (var inputStream = file.FileInfo.CreateReadStream())
|
||||
{
|
||||
_logger.RazorFileToCodeCompilationStart(file.RelativePath);
|
||||
|
||||
var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0;
|
||||
|
||||
codeDocument = CreateCodeDocument(file.RelativePath, inputStream);
|
||||
cSharpDocument = ProcessCodeDocument(codeDocument);
|
||||
|
||||
_logger.RazorFileToCodeCompilationEnd(file.RelativePath, startTimestamp);
|
||||
}
|
||||
|
||||
if (cSharpDocument.Diagnostics.Count > 0)
|
||||
{
|
||||
return GetCompilationFailedResult(file.RelativePath, cSharpDocument.Diagnostics);
|
||||
}
|
||||
|
||||
return _compilationService.Compile(codeDocument, cSharpDocument);
|
||||
}
|
||||
|
||||
public virtual RazorCodeDocument CreateCodeDocument(string relativePath, Stream inputStream)
|
||||
{
|
||||
var absolutePath = _fileProvider.GetFileInfo(relativePath)?.PhysicalPath ?? relativePath;
|
||||
|
||||
var source = RazorSourceDocument.ReadFrom(inputStream, absolutePath);
|
||||
|
||||
var imports = new List<RazorSourceDocument>()
|
||||
{
|
||||
GlobalImports,
|
||||
};
|
||||
|
||||
var paths = ViewHierarchyUtility.GetViewImportsLocations(relativePath);
|
||||
foreach (var path in paths.Reverse())
|
||||
{
|
||||
var file = _fileProvider.GetFileInfo(path);
|
||||
if (file.Exists)
|
||||
{
|
||||
using (var stream = file.CreateReadStream())
|
||||
{
|
||||
imports.Add(RazorSourceDocument.ReadFrom(stream, file.PhysicalPath ?? path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var codeDocument = RazorCodeDocument.Create(source, imports);
|
||||
codeDocument.SetRelativePath(relativePath);
|
||||
return codeDocument;
|
||||
}
|
||||
|
||||
public virtual RazorCSharpDocument ProcessCodeDocument(RazorCodeDocument codeDocument)
|
||||
{
|
||||
_engine.Process(codeDocument);
|
||||
|
||||
return codeDocument.GetCSharpDocument();
|
||||
}
|
||||
|
||||
// Internal for unit testing
|
||||
public CompilationResult GetCompilationFailedResult(
|
||||
string relativePath,
|
||||
IEnumerable<RazorDiagnostic> errors)
|
||||
{
|
||||
// If a SourceLocation does not specify a file path, assume it is produced
|
||||
// from parsing the current file.
|
||||
var messageGroups = errors
|
||||
.GroupBy(razorError =>
|
||||
razorError.Span.FilePath ?? relativePath,
|
||||
StringComparer.Ordinal);
|
||||
|
||||
var failures = new List<CompilationFailure>();
|
||||
foreach (var group in messageGroups)
|
||||
{
|
||||
var filePath = group.Key;
|
||||
var fileContent = ReadFileContentsSafely(filePath);
|
||||
var compilationFailure = new CompilationFailure(
|
||||
filePath,
|
||||
fileContent,
|
||||
compiledContent: string.Empty,
|
||||
messages: group.Select(parserError => CreateDiagnosticMessage(parserError, filePath)));
|
||||
failures.Add(compilationFailure);
|
||||
}
|
||||
|
||||
return new CompilationResult(failures);
|
||||
}
|
||||
|
||||
private DiagnosticMessage CreateDiagnosticMessage(
|
||||
RazorDiagnostic error,
|
||||
string filePath)
|
||||
{
|
||||
var sourceSpan = error.Span;
|
||||
return new DiagnosticMessage(
|
||||
message: error.GetMessage(),
|
||||
formattedMessage: $"{error} ({sourceSpan.LineIndex},{sourceSpan.CharacterIndex}) {error.GetMessage()}",
|
||||
filePath: filePath,
|
||||
startLine: sourceSpan.LineIndex + 1,
|
||||
startColumn: sourceSpan.CharacterIndex,
|
||||
endLine: sourceSpan.LineIndex + 1,
|
||||
endColumn: sourceSpan.CharacterIndex + sourceSpan.Length);
|
||||
}
|
||||
|
||||
private string ReadFileContentsSafely(string relativePath)
|
||||
{
|
||||
var fileInfo = _fileProvider.GetFileInfo(relativePath);
|
||||
if (fileInfo.Exists)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var reader = new StreamReader(fileInfo.CreateReadStream()))
|
||||
{
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore any failures
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
|
||||
// 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.Linq;
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||
{
|
||||
public class RazorCompiler
|
||||
{
|
||||
private readonly ICompilationService _compilationService;
|
||||
private readonly ICompilerCacheProvider _compilerCacheProvider;
|
||||
private readonly MvcRazorTemplateEngine _templateEngine;
|
||||
private readonly Func<string, CompilerCacheContext> _getCacheContext;
|
||||
private readonly Func<CompilerCacheContext, CompilationResult> _getCompilationResultDelegate;
|
||||
|
||||
public RazorCompiler(
|
||||
ICompilationService compilationService,
|
||||
ICompilerCacheProvider compilerCacheProvider,
|
||||
MvcRazorTemplateEngine templateEngine)
|
||||
{
|
||||
_compilationService = compilationService;
|
||||
_compilerCacheProvider = compilerCacheProvider;
|
||||
_templateEngine = templateEngine;
|
||||
_getCacheContext = GetCacheContext;
|
||||
_getCompilationResultDelegate = GetCompilationResult;
|
||||
}
|
||||
|
||||
private ICompilerCache CompilerCache => _compilerCacheProvider.Cache;
|
||||
|
||||
public CompilerCacheResult Compile(string relativePath)
|
||||
{
|
||||
return CompilerCache.GetOrAdd(relativePath, _getCacheContext);
|
||||
}
|
||||
|
||||
private CompilerCacheContext GetCacheContext(string path)
|
||||
{
|
||||
var item = _templateEngine.Project.GetItem(path);
|
||||
var imports = _templateEngine.Project.FindHierarchicalItems(path, _templateEngine.Options.ImportsFileName);
|
||||
return new CompilerCacheContext(item, imports, GetCompilationResult);
|
||||
}
|
||||
|
||||
private CompilationResult GetCompilationResult(CompilerCacheContext cacheContext)
|
||||
{
|
||||
var projectItem = cacheContext.ProjectItem;
|
||||
var codeDocument = _templateEngine.CreateCodeDocument(projectItem.Path);
|
||||
var cSharpDocument = _templateEngine.GenerateCode(codeDocument);
|
||||
|
||||
CompilationResult compilationResult;
|
||||
if (cSharpDocument.Diagnostics.Count > 0)
|
||||
{
|
||||
compilationResult = GetCompilationFailedResult(
|
||||
codeDocument,
|
||||
cSharpDocument.Diagnostics);
|
||||
}
|
||||
else
|
||||
{
|
||||
compilationResult = _compilationService.Compile(codeDocument, cSharpDocument);
|
||||
}
|
||||
|
||||
return compilationResult;
|
||||
}
|
||||
|
||||
internal CompilationResult GetCompilationFailedResult(
|
||||
RazorCodeDocument codeDocument,
|
||||
IEnumerable<RazorDiagnostic> diagnostics)
|
||||
{
|
||||
// If a SourceLocation does not specify a file path, assume it is produced from parsing the current file.
|
||||
var messageGroups = diagnostics.GroupBy(
|
||||
razorError => razorError.Span.FilePath ?? codeDocument.Source.FileName,
|
||||
StringComparer.Ordinal);
|
||||
|
||||
var failures = new List<CompilationFailure>();
|
||||
foreach (var group in messageGroups)
|
||||
{
|
||||
var filePath = group.Key;
|
||||
var fileContent = ReadContent(codeDocument, filePath);
|
||||
var compilationFailure = new CompilationFailure(
|
||||
filePath,
|
||||
fileContent,
|
||||
compiledContent: string.Empty,
|
||||
messages: group.Select(parserError => CreateDiagnosticMessage(parserError, filePath)));
|
||||
failures.Add(compilationFailure);
|
||||
}
|
||||
|
||||
return new CompilationResult(failures);
|
||||
}
|
||||
|
||||
private static string ReadContent(RazorCodeDocument codeDocument, string filePath)
|
||||
{
|
||||
RazorSourceDocument sourceDocument = null;
|
||||
if (string.IsNullOrEmpty(filePath) || string.Equals(codeDocument.Source.FileName, filePath, StringComparison.Ordinal))
|
||||
{
|
||||
sourceDocument = codeDocument.Source;
|
||||
}
|
||||
else
|
||||
{
|
||||
sourceDocument = codeDocument.Imports.FirstOrDefault(f => string.Equals(f.FileName, filePath, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
if (sourceDocument != null)
|
||||
{
|
||||
var contentChars = new char[sourceDocument.Length];
|
||||
sourceDocument.CopyTo(0, contentChars, 0, sourceDocument.Length);
|
||||
return new string(contentChars);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static DiagnosticMessage CreateDiagnosticMessage(
|
||||
RazorDiagnostic razorDiagnostic,
|
||||
string filePath)
|
||||
{
|
||||
var sourceSpan = razorDiagnostic.Span;
|
||||
var message = razorDiagnostic.GetMessage();
|
||||
return new DiagnosticMessage(
|
||||
message: message,
|
||||
formattedMessage: razorDiagnostic.ToString(),
|
||||
filePath: filePath,
|
||||
startLine: sourceSpan.LineIndex + 1,
|
||||
startColumn: sourceSpan.CharacterIndex,
|
||||
endLine: sourceSpan.LineIndex + 1,
|
||||
endColumn: sourceSpan.CharacterIndex + sourceSpan.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -58,22 +58,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("FlushPointCannotBeInvoked"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The {0} returned by '{1}' must be an instance of '{2}'.
|
||||
/// </summary>
|
||||
internal static string Instrumentation_WriterMustBeBufferedTextWriter
|
||||
{
|
||||
get { return GetString("Instrumentation_WriterMustBeBufferedTextWriter"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The {0} returned by '{1}' must be an instance of '{2}'.
|
||||
/// </summary>
|
||||
internal static string FormatInstrumentation_WriterMustBeBufferedTextWriter(object p0, object p1, object p2)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("Instrumentation_WriterMustBeBufferedTextWriter"), p0, p1, p2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The layout view '{0}' could not be located. The following locations were searched:{1}
|
||||
/// </summary>
|
||||
|
|
@ -314,70 +298,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("ViewContextMustBeSet"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// '{0}' must be a {1} that is generated as result of the call to '{2}'.
|
||||
/// </summary>
|
||||
internal static string ViewLocationCache_KeyMustBeString
|
||||
{
|
||||
get { return GetString("ViewLocationCache_KeyMustBeString"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// '{0}' must be a {1} that is generated as result of the call to '{2}'.
|
||||
/// </summary>
|
||||
internal static string FormatViewLocationCache_KeyMustBeString(object p0, object p1, object p2)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("ViewLocationCache_KeyMustBeString"), p0, p1, p2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' method must be called before '{1}' can be invoked.
|
||||
/// </summary>
|
||||
internal static string ViewMustBeContextualized
|
||||
{
|
||||
get { return GetString("ViewMustBeContextualized"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' method must be called before '{1}' can be invoked.
|
||||
/// </summary>
|
||||
internal static string FormatViewMustBeContextualized(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("ViewMustBeContextualized"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsupported hash algorithm.
|
||||
/// </summary>
|
||||
internal static string RazorHash_UnsupportedHashAlgorithm
|
||||
{
|
||||
get { return GetString("RazorHash_UnsupportedHashAlgorithm"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsupported hash algorithm.
|
||||
/// </summary>
|
||||
internal static string FormatRazorHash_UnsupportedHashAlgorithm()
|
||||
{
|
||||
return GetString("RazorHash_UnsupportedHashAlgorithm");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The resource '{0}' specified by '{1}' could not be found.
|
||||
/// </summary>
|
||||
internal static string RazorFileInfoCollection_ResourceCouldNotBeFound
|
||||
{
|
||||
get { return GetString("RazorFileInfoCollection_ResourceCouldNotBeFound"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The resource '{0}' specified by '{1}' could not be found.
|
||||
/// </summary>
|
||||
internal static string FormatRazorFileInfoCollection_ResourceCouldNotBeFound(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("RazorFileInfoCollection_ResourceCouldNotBeFound"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generated Code
|
||||
/// </summary>
|
||||
|
|
@ -526,6 +446,22 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
return GetString("RazorProject_PathMustStartWithForwardSlash");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The property '{0}' of '{1}' must not be null.
|
||||
/// </summary>
|
||||
internal static string PropertyMustBeSet
|
||||
{
|
||||
get { return GetString("PropertyMustBeSet"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The property '{0}' of '{1}' must not be null.
|
||||
/// </summary>
|
||||
internal static string FormatPropertyMustBeSet(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("PropertyMustBeSet"), p0, p1);
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using System.Text.Encodings.Web;
|
|||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
|
@ -30,6 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
public class RazorViewEngine : IRazorViewEngine
|
||||
{
|
||||
public static readonly string ViewExtension = ".cshtml";
|
||||
private const string ViewStartFileName = "_ViewStart.cshtml";
|
||||
|
||||
private const string ControllerKey = "controller";
|
||||
private const string AreaKey = "area";
|
||||
|
|
@ -42,6 +44,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
private readonly HtmlEncoder _htmlEncoder;
|
||||
private readonly ILogger _logger;
|
||||
private readonly RazorViewEngineOptions _options;
|
||||
private readonly RazorProject _razorProject;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RazorViewEngine" />.
|
||||
|
|
@ -51,6 +54,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
IRazorPageActivator pageActivator,
|
||||
HtmlEncoder htmlEncoder,
|
||||
IOptions<RazorViewEngineOptions> optionsAccessor,
|
||||
RazorProject razorProject,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
_options = optionsAccessor.Value;
|
||||
|
|
@ -73,6 +77,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
_pageActivator = pageActivator;
|
||||
_htmlEncoder = htmlEncoder;
|
||||
_logger = loggerFactory.CreateLogger<RazorViewEngine>();
|
||||
_razorProject = razorProject;
|
||||
ViewLookupCache = new MemoryCache(new MemoryCacheOptions
|
||||
{
|
||||
CompactOnMemoryPressure = false
|
||||
|
|
@ -483,10 +488,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
string path,
|
||||
HashSet<IChangeToken> expirationTokens)
|
||||
{
|
||||
var applicationRelativePath = MakePathApplicationRelative(path);
|
||||
var viewStartPages = new List<ViewLocationCacheItem>();
|
||||
foreach (var viewStartPath in ViewHierarchyUtility.GetViewStartLocations(path))
|
||||
|
||||
foreach (var viewStartProjectItem in _razorProject.FindHierarchicalItems(applicationRelativePath, ViewStartFileName))
|
||||
{
|
||||
var result = _pageFactory.CreateFactory(viewStartPath);
|
||||
var result = _pageFactory.CreateFactory(viewStartProjectItem.Path);
|
||||
if (result.ExpirationTokens != null)
|
||||
{
|
||||
for (var i = 0; i < result.ExpirationTokens.Count; i++)
|
||||
|
|
@ -500,7 +507,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
// Populate the viewStartPages list so that _ViewStarts appear in the order the need to be
|
||||
// executed (closest last, furthest first). This is the reverse order in which
|
||||
// ViewHierarchyUtility.GetViewStartLocations returns _ViewStarts.
|
||||
viewStartPages.Insert(0, new ViewLocationCacheItem(result.RazorPageFactory, viewStartPath));
|
||||
viewStartPages.Insert(0, new ViewLocationCacheItem(result.RazorPageFactory, viewStartProjectItem.Path));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -533,6 +540,22 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
return name[0] == '~' || name[0] == '/';
|
||||
}
|
||||
|
||||
private string MakePathApplicationRelative(string path)
|
||||
{
|
||||
Debug.Assert(!string.IsNullOrEmpty(path));
|
||||
if (path[0] == '~')
|
||||
{
|
||||
path = path.Substring(1);
|
||||
}
|
||||
|
||||
if (path[0] != '/')
|
||||
{
|
||||
path = '/' + path;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private static bool IsRelativePath(string name)
|
||||
{
|
||||
Debug.Assert(!string.IsNullOrEmpty(name));
|
||||
|
|
|
|||
|
|
@ -126,9 +126,6 @@
|
|||
<data name="FlushPointCannotBeInvoked" xml:space="preserve">
|
||||
<value>'{0}' cannot be invoked when a Layout page is set to be executed.</value>
|
||||
</data>
|
||||
<data name="Instrumentation_WriterMustBeBufferedTextWriter" xml:space="preserve">
|
||||
<value>The {0} returned by '{1}' must be an instance of '{2}'.</value>
|
||||
</data>
|
||||
<data name="LayoutCannotBeLocated" xml:space="preserve">
|
||||
<value>The layout view '{0}' could not be located. The following locations were searched:{1}</value>
|
||||
</data>
|
||||
|
|
@ -174,18 +171,6 @@
|
|||
<data name="ViewContextMustBeSet" xml:space="preserve">
|
||||
<value>'{0} must be set to access '{1}'.</value>
|
||||
</data>
|
||||
<data name="ViewLocationCache_KeyMustBeString" xml:space="preserve">
|
||||
<value>'{0}' must be a {1} that is generated as result of the call to '{2}'.</value>
|
||||
</data>
|
||||
<data name="ViewMustBeContextualized" xml:space="preserve">
|
||||
<value>The '{0}' method must be called before '{1}' can be invoked.</value>
|
||||
</data>
|
||||
<data name="RazorHash_UnsupportedHashAlgorithm" xml:space="preserve">
|
||||
<value>Unsupported hash algorithm.</value>
|
||||
</data>
|
||||
<data name="RazorFileInfoCollection_ResourceCouldNotBeFound" xml:space="preserve">
|
||||
<value>The resource '{0}' specified by '{1}' could not be found.</value>
|
||||
</data>
|
||||
<data name="GeneratedCodeFileName" xml:space="preserve">
|
||||
<value>Generated Code</value>
|
||||
</data>
|
||||
|
|
@ -215,4 +200,7 @@
|
|||
<data name="RazorProject_PathMustStartWithForwardSlash" xml:space="preserve">
|
||||
<value>Path must begin with a forward slash '/'.</value>
|
||||
</data>
|
||||
<data name="PropertyMustBeSet" xml:space="preserve">
|
||||
<value>The property '{0}' of '{1}' must not be null.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -1,9 +1,18 @@
|
|||
using System;
|
||||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a <see cref="CompiledPageActionDescriptor"/> from a <see cref="PageActionDescriptor"/>.
|
||||
/// </summary>
|
||||
public interface IPageLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Produces a <see cref="CompiledPageActionDescriptor"/> given a <see cref="PageActionDescriptor"/>.
|
||||
/// </summary>
|
||||
/// <param name="actionDescriptor">The <see cref="PageActionDescriptor"/>.</param>
|
||||
/// <returns>The <see cref="CompiledPageActionDescriptor"/>.</returns>
|
||||
CompiledPageActionDescriptor Load(PageActionDescriptor actionDescriptor);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,123 +1,51 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||
{
|
||||
public class DefaultPageLoader : IPageLoader
|
||||
{
|
||||
private const string PageImportsFileName = "_PageImports.cshtml";
|
||||
private const string ModelPropertyName = "Model";
|
||||
private readonly RazorCompilationService _razorCompilationService;
|
||||
private readonly ICompilationService _compilationService;
|
||||
private readonly RazorProject _project;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly MvcRazorTemplateEngine _templateEngine;
|
||||
private readonly RazorCompiler _razorCompiler;
|
||||
|
||||
public DefaultPageLoader(
|
||||
IRazorCompilationService razorCompilationService,
|
||||
ICompilationService compilationService,
|
||||
RazorEngine razorEngine,
|
||||
RazorProject razorProject,
|
||||
ILogger<DefaultPageLoader> logger)
|
||||
ICompilationService compilationService,
|
||||
ICompilerCacheProvider compilerCacheProvider)
|
||||
{
|
||||
_razorCompilationService = (RazorCompilationService)razorCompilationService;
|
||||
_compilationService = compilationService;
|
||||
_project = razorProject;
|
||||
_logger = logger;
|
||||
_templateEngine = new MvcRazorTemplateEngine(razorEngine, razorProject);
|
||||
_templateEngine.Options.ImportsFileName = PageImportsFileName;
|
||||
_razorCompiler = new RazorCompiler(compilationService, compilerCacheProvider, _templateEngine);
|
||||
}
|
||||
|
||||
public CompiledPageActionDescriptor Load(PageActionDescriptor actionDescriptor)
|
||||
{
|
||||
var item = _project.GetItem(actionDescriptor.RelativePath);
|
||||
if (!item.Exists)
|
||||
{
|
||||
throw new InvalidOperationException($"File {actionDescriptor.RelativePath} was not found.");
|
||||
}
|
||||
|
||||
_logger.RazorFileToCodeCompilationStart(item.Path);
|
||||
|
||||
var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0;
|
||||
|
||||
var codeDocument = CreateCodeDocument(item);
|
||||
var cSharpDocument = _razorCompilationService.ProcessCodeDocument(codeDocument);
|
||||
|
||||
_logger.RazorFileToCodeCompilationEnd(item.Path, startTimestamp);
|
||||
|
||||
CompilationResult compilationResult;
|
||||
if (cSharpDocument.Diagnostics.Count > 0)
|
||||
{
|
||||
compilationResult = _razorCompilationService.GetCompilationFailedResult(item.Path, cSharpDocument.Diagnostics);
|
||||
}
|
||||
else
|
||||
{
|
||||
compilationResult = _compilationService.Compile(codeDocument, cSharpDocument);
|
||||
}
|
||||
|
||||
compilationResult.EnsureSuccessful();
|
||||
|
||||
var compilationResult = _razorCompiler.Compile(actionDescriptor.RelativePath);
|
||||
var compiledTypeInfo = compilationResult.CompiledType.GetTypeInfo();
|
||||
// If a model type wasn't set in code then the model property's type will be the same
|
||||
// as the compiled type.
|
||||
var pageType = compilationResult.CompiledType.GetTypeInfo();
|
||||
var modelType = pageType.GetProperty(ModelPropertyName)?.PropertyType.GetTypeInfo();
|
||||
if (modelType == pageType)
|
||||
var modelTypeInfo = compiledTypeInfo.GetProperty(ModelPropertyName)?.PropertyType.GetTypeInfo();
|
||||
if (modelTypeInfo == compiledTypeInfo)
|
||||
{
|
||||
modelType = null;
|
||||
modelTypeInfo = null;
|
||||
}
|
||||
|
||||
return new CompiledPageActionDescriptor(actionDescriptor)
|
||||
{
|
||||
ModelTypeInfo = modelType,
|
||||
PageTypeInfo = pageType,
|
||||
PageTypeInfo = compiledTypeInfo,
|
||||
ModelTypeInfo = modelTypeInfo,
|
||||
};
|
||||
}
|
||||
|
||||
private RazorCodeDocument CreateCodeDocument(RazorProjectItem item)
|
||||
{
|
||||
var absolutePath = GetItemPath(item);
|
||||
|
||||
RazorSourceDocument source;
|
||||
using (var inputStream = item.Read())
|
||||
{
|
||||
source = RazorSourceDocument.ReadFrom(inputStream, absolutePath);
|
||||
}
|
||||
|
||||
var imports = new List<RazorSourceDocument>()
|
||||
{
|
||||
_razorCompilationService.GlobalImports,
|
||||
};
|
||||
|
||||
var pageImports = _project.FindHierarchicalItems(item.Path, "_PageImports.cshtml");
|
||||
foreach (var pageImport in pageImports.Reverse())
|
||||
{
|
||||
if (pageImport.Exists)
|
||||
{
|
||||
using (var stream = pageImport.Read())
|
||||
{
|
||||
imports.Add(RazorSourceDocument.ReadFrom(stream, GetItemPath(item)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RazorCodeDocument.Create(source, imports);
|
||||
}
|
||||
|
||||
private static string GetItemPath(RazorProjectItem item)
|
||||
{
|
||||
var absolutePath = item.Path;
|
||||
if (item.Exists && string.IsNullOrEmpty(item.PhysicalPath))
|
||||
{
|
||||
absolutePath = item.PhysicalPath;
|
||||
}
|
||||
|
||||
return absolutePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
// 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.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||
{
|
||||
public class MvcRazorTemplateEngineTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetDefaultImports_IncludesDefaultImports()
|
||||
{
|
||||
// Arrange
|
||||
var expectedImports = new[]
|
||||
{
|
||||
"@using System",
|
||||
"@using System.Linq",
|
||||
"@using System.Collections.Generic",
|
||||
"@using Microsoft.AspNetCore.Mvc",
|
||||
"@using Microsoft.AspNetCore.Mvc.Rendering",
|
||||
"@using Microsoft.AspNetCore.Mvc.ViewFeatures",
|
||||
};
|
||||
var mvcRazorTemplateEngine = new MvcRazorTemplateEngine(
|
||||
RazorEngine.Create(),
|
||||
GetRazorProject(new TestFileProvider()));
|
||||
|
||||
// Act
|
||||
var imports = mvcRazorTemplateEngine.Options.DefaultImports;
|
||||
|
||||
// Assert
|
||||
var importContent = GetContent(imports)
|
||||
.Split(new[] { Environment.NewLine }, StringSplitOptions.None)
|
||||
.Where(line => line.StartsWith("@using"));
|
||||
Assert.Equal(expectedImports, importContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDefaultImports_IncludesDefaulInjects()
|
||||
{
|
||||
// Arrange
|
||||
var expectedImports = new[]
|
||||
{
|
||||
"@inject global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel> Html",
|
||||
"@inject global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json",
|
||||
"@inject global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component",
|
||||
"@inject global::Microsoft.AspNetCore.Mvc.IUrlHelper Url",
|
||||
"@inject global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider",
|
||||
};
|
||||
var mvcRazorTemplateEngine = new MvcRazorTemplateEngine(
|
||||
RazorEngine.Create(),
|
||||
GetRazorProject(new TestFileProvider()));
|
||||
|
||||
// Act
|
||||
var imports = mvcRazorTemplateEngine.Options.DefaultImports;
|
||||
|
||||
// Assert
|
||||
var importContent = GetContent(imports)
|
||||
.Split(new[] { Environment.NewLine }, StringSplitOptions.None)
|
||||
.Where(line => line.StartsWith("@inject"));
|
||||
Assert.Equal(expectedImports, importContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDefaultImports_IncludesUrlTagHelper()
|
||||
{
|
||||
// Arrange
|
||||
var mvcRazorTemplateEngine = new MvcRazorTemplateEngine(
|
||||
RazorEngine.Create(),
|
||||
GetRazorProject(new TestFileProvider()));
|
||||
|
||||
// Act
|
||||
var imports = mvcRazorTemplateEngine.Options.DefaultImports;
|
||||
|
||||
// Assert
|
||||
var importContent = GetContent(imports)
|
||||
.Split(new[] { Environment.NewLine }, StringSplitOptions.None)
|
||||
.Where(line => line.StartsWith("@addTagHelper"));
|
||||
var addTagHelper = Assert.Single(importContent);
|
||||
Assert.Equal("@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor",
|
||||
addTagHelper);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateCodeDocument_SetsRelativePathOnOutput()
|
||||
{
|
||||
// Arrange
|
||||
var path = "/Views/Home/Index.cshtml";
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(path, "Hello world");
|
||||
var mvcRazorTemplateEngine = new MvcRazorTemplateEngine(
|
||||
RazorEngine.Create(),
|
||||
GetRazorProject(fileProvider));
|
||||
|
||||
// Act
|
||||
var codeDocument = mvcRazorTemplateEngine.CreateCodeDocument(path);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(path, codeDocument.GetRelativePath());
|
||||
}
|
||||
|
||||
private string GetContent(RazorSourceDocument imports)
|
||||
{
|
||||
var contentChars = new char[imports.Length];
|
||||
imports.CopyTo(0, contentChars, 0, imports.Length);
|
||||
return new string(contentChars);
|
||||
}
|
||||
|
||||
private static DefaultRazorProject GetRazorProject(IFileProvider fileProvider)
|
||||
{
|
||||
var fileProviderAccessor = new Mock<IRazorViewEngineFileProviderAccessor>();
|
||||
fileProviderAccessor.SetupGet(f => f.FileProvider)
|
||||
.Returns(fileProvider);
|
||||
|
||||
return new DefaultRazorProject(fileProviderAccessor.Object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,277 +0,0 @@
|
|||
// 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.IO;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
public class ViewHierarchyUtilityTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
public void GetViewStartLocations_ReturnsEmptySequenceIfViewPathIsEmpty(string viewPath)
|
||||
{
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewStartLocations(viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
public void GetViewImportsLocations_ReturnsEmptySequenceIfViewPathIsEmpty(string viewPath)
|
||||
{
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewImportsLocations(viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Views/Home/MyView.cshtml")]
|
||||
[InlineData("~/Views/Home/MyView.cshtml")]
|
||||
[InlineData("Views/Home/MyView.cshtml")]
|
||||
public void GetViewStartLocations_ReturnsPotentialViewStartLocations_PathStartswithSlash(string inputPath)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
"/Views/Home/_ViewStart.cshtml",
|
||||
"/Views/_ViewStart.cshtml",
|
||||
"/_ViewStart.cshtml"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewStartLocations(inputPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[ConditionalTheory]
|
||||
[OSSkipCondition(OperatingSystems.Linux,
|
||||
SkipReason = "Back slashes only work as path separators on Windows")]
|
||||
[OSSkipCondition(OperatingSystems.MacOSX,
|
||||
SkipReason = "Back slashes only work as path separators on Windows")]
|
||||
[InlineData(@"~/Views\Home\MyView.cshtml")]
|
||||
[InlineData(@"Views\Home\MyView.cshtml")]
|
||||
public void GetViewStartLocations_ReturnsPotentialViewStartLocations_PathsContainBackSlash(
|
||||
string inputPath)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
"/Views/Home/_ViewStart.cshtml",
|
||||
"/Views/_ViewStart.cshtml",
|
||||
"/_ViewStart.cshtml"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewStartLocations(inputPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Views/Home/MyView.cshtml")]
|
||||
[InlineData("~/Views/Home/MyView.cshtml")]
|
||||
[InlineData("Views/Home/MyView.cshtml")]
|
||||
public void GetViewImportsLocations_ReturnsPotentialViewStartLocations_PathStartswithSlash(string inputPath)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
"/Views/Home/_ViewImports.cshtml",
|
||||
"/Views/_ViewImports.cshtml",
|
||||
"/_ViewImports.cshtml"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewImportsLocations(inputPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Views/Home/_ViewStart.cshtml")]
|
||||
[InlineData("~/Views/Home/_ViewStart.cshtml")]
|
||||
[InlineData("Views/Home/_ViewStart.cshtml")]
|
||||
public void GetViewStartLocations_SkipsCurrentPath_IfCurrentIsViewStart(string inputPath)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
"/Views/_ViewStart.cshtml",
|
||||
"/_ViewStart.cshtml"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewStartLocations(inputPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Views/Home/_ViewStart.cshtml")]
|
||||
[InlineData("~/Views/Home/_ViewStart.cshtml")]
|
||||
[InlineData("Views/Home/_ViewStart.cshtml")]
|
||||
public void GetViewImportsLocations_WhenCurrentIsViewStart(string inputPath)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
"/Views/Home/_ViewImports.cshtml",
|
||||
"/Views/_ViewImports.cshtml",
|
||||
"/_ViewImports.cshtml"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewImportsLocations(inputPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Views/Home/_ViewImports.cshtml")]
|
||||
[InlineData("~/Views/Home/_ViewImports.cshtml")]
|
||||
[InlineData("Views/Home/_ViewImports.cshtml")]
|
||||
public void GetViewImportsLocations_SkipsCurrentPath_IfCurrentIsViewImports(string inputPath)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
"/Views/_ViewImports.cshtml",
|
||||
"/_ViewImports.cshtml"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewImportsLocations(inputPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Test.cshtml")]
|
||||
[InlineData("ViewStart.cshtml")]
|
||||
public void GetViewStartLocations_ReturnsPotentialViewStartLocations(string fileName)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
"/Areas/MyArea/Sub/Views/Admin/_ViewStart.cshtml",
|
||||
"/Areas/MyArea/Sub/Views/_ViewStart.cshtml",
|
||||
"/Areas/MyArea/Sub/_ViewStart.cshtml",
|
||||
"/Areas/MyArea/_ViewStart.cshtml",
|
||||
"/Areas/_ViewStart.cshtml",
|
||||
"/_ViewStart.cshtml",
|
||||
};
|
||||
var viewPath = $"Areas/MyArea/Sub/Views/Admin/{fileName}";
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewStartLocations(viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[ConditionalTheory]
|
||||
[OSSkipCondition(OperatingSystems.Linux,
|
||||
SkipReason = "Back slashes only work as path separators on Windows")]
|
||||
[OSSkipCondition(OperatingSystems.MacOSX,
|
||||
SkipReason = "Back slashes only work as path separators on Windows")]
|
||||
[InlineData("Test.cshtml")]
|
||||
[InlineData("ViewStart.cshtml")]
|
||||
public void GetViewStartLocations_ReturnsPotentialViewStartLocations_ForPathsWithBackSlashes(string fileName)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
"/Areas/MyArea/Sub/Views/Admin/_ViewStart.cshtml",
|
||||
"/Areas/MyArea/Sub/Views/_ViewStart.cshtml",
|
||||
"/Areas/MyArea/Sub/_ViewStart.cshtml",
|
||||
"/Areas/MyArea/_ViewStart.cshtml",
|
||||
"/Areas/_ViewStart.cshtml",
|
||||
"/_ViewStart.cshtml",
|
||||
};
|
||||
var viewPath = $"Areas\\MyArea\\Sub\\Views\\Admin/{fileName}";
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewStartLocations(viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Test.cshtml")]
|
||||
[InlineData("Global.cshtml")]
|
||||
[InlineData("_ViewStart.cshtml")]
|
||||
public void GetViewImportsLocations_ReturnsPotentialGlobalLocations(string fileName)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
"/Areas/MyArea/Sub/Views/Admin/_ViewImports.cshtml",
|
||||
"/Areas/MyArea/Sub/Views/_ViewImports.cshtml",
|
||||
"/Areas/MyArea/Sub/_ViewImports.cshtml",
|
||||
"/Areas/MyArea/_ViewImports.cshtml",
|
||||
"/Areas/_ViewImports.cshtml",
|
||||
"/_ViewImports.cshtml",
|
||||
};
|
||||
var viewPath = $"Areas/MyArea/Sub/Views/Admin/{fileName}";
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewImportsLocations(viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("_ViewStart.cshtml")]
|
||||
[InlineData("_viewstart.cshtml")]
|
||||
public void GetViewStartLocations_SkipsCurrentPath_IfPathIsAViewStartFile(string fileName)
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
"/Areas/MyArea/Sub/Views/_ViewStart.cshtml",
|
||||
"/Areas/MyArea/Sub/_ViewStart.cshtml",
|
||||
"/Areas/MyArea/_ViewStart.cshtml",
|
||||
"/Areas/_ViewStart.cshtml",
|
||||
"/_ViewStart.cshtml",
|
||||
};
|
||||
var viewPath = $"Areas/MyArea/Sub/Views/Admin/{fileName}";
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewStartLocations(viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetViewStartLocations_ReturnsEmptySequence_IfViewStartIsAtRoot()
|
||||
{
|
||||
// Arrange
|
||||
var viewPath = "_ViewStart.cshtml";
|
||||
|
||||
// Act
|
||||
var result = ViewHierarchyUtility.GetViewStartLocations(viewPath);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,10 +3,12 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
@ -17,18 +19,30 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
{
|
||||
private const string ViewPath = "/Views/Home/Index.cshtml";
|
||||
private const string PrecompiledViewsPath = "/Views/Home/Precompiled.cshtml";
|
||||
private static readonly string[] _viewImportsPath = new[]
|
||||
{
|
||||
"/Views/Home/_ViewImports.cshtml",
|
||||
"/Views/_ViewImports.cshtml",
|
||||
"/_ViewImports.cshtml",
|
||||
};
|
||||
private readonly IDictionary<string, Type> _precompiledViews = new Dictionary<string, Type>
|
||||
{
|
||||
{ PrecompiledViewsPath, typeof(PreCompile) }
|
||||
};
|
||||
|
||||
public static TheoryData ViewImportsPaths =>
|
||||
new TheoryData<string>
|
||||
public static TheoryData ViewImportsPaths
|
||||
{
|
||||
get
|
||||
{
|
||||
"/Views/Home/_ViewImports.cshtml",
|
||||
"/Views/_ViewImports.cshtml",
|
||||
"/_ViewImports.cshtml",
|
||||
};
|
||||
var theoryData = new TheoryData<string>();
|
||||
foreach (var path in _viewImportsPath)
|
||||
{
|
||||
theoryData.Add(path);
|
||||
}
|
||||
|
||||
return theoryData;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrAdd_ReturnsFileNotFoundResult_IfFileIsNotFoundInFileSystem()
|
||||
|
|
@ -36,9 +50,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
var cache = new CompilerCache(fileProvider);
|
||||
var compilerCacheContext = new CompilerCacheContext(
|
||||
new NotFoundProjectItem("", "/path"),
|
||||
Enumerable.Empty<RazorProjectItem>(),
|
||||
_ => throw new Exception("Shouldn't be called."));
|
||||
|
||||
// Act
|
||||
var result = cache.GetOrAdd("/some/path", ThrowsIfCalled);
|
||||
var result = cache.GetOrAdd("/some/path", _ => compilerCacheContext);
|
||||
|
||||
// Assert
|
||||
Assert.False(result.Success);
|
||||
|
|
@ -54,12 +72,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
var expected = new CompilationResult(typeof(TestView));
|
||||
|
||||
// Act
|
||||
var result = cache.GetOrAdd(ViewPath, _ => expected);
|
||||
var result = cache.GetOrAdd(ViewPath, CreateContextFactory(expected));
|
||||
|
||||
// Assert
|
||||
Assert.True(result.Success);
|
||||
Assert.IsType<TestView>(result.PageFactory());
|
||||
Assert.Same(ViewPath, result.PageFactory().Path);
|
||||
Assert.Equal(typeof(TestView), result.CompiledType);
|
||||
Assert.Equal(ViewPath, result.RelativePath);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -77,17 +95,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
var expected = new CompilationResult(typeof(TestView));
|
||||
|
||||
// Act - 1
|
||||
var result1 = cache.GetOrAdd(@"Areas\Finances\Views\Home\Index.cshtml", _ => expected);
|
||||
var result1 = cache.GetOrAdd(@"Areas\Finances\Views\Home\Index.cshtml", CreateContextFactory(expected));
|
||||
|
||||
// Assert - 1
|
||||
Assert.IsType<TestView>(result1.PageFactory());
|
||||
Assert.Equal(typeof(TestView), result1.CompiledType);
|
||||
|
||||
// Act - 2
|
||||
var result2 = cache.GetOrAdd(relativePath, ThrowsIfCalled);
|
||||
|
||||
// Assert - 2
|
||||
Assert.IsType<TestView>(result2.PageFactory());
|
||||
Assert.Same(result1.PageFactory, result2.PageFactory);
|
||||
Assert.Equal(typeof(TestView), result2.CompiledType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -95,22 +112,27 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(ViewPath, "some content");
|
||||
var fileInfo = fileProvider.AddFile(ViewPath, "some content");
|
||||
var cache = new CompilerCache(fileProvider);
|
||||
var expected = new CompilationResult(typeof(TestView));
|
||||
var projectItem = new DefaultRazorProjectItem(fileInfo, "", ViewPath);
|
||||
var cacheContext = new CompilerCacheContext(projectItem, Enumerable.Empty<RazorProjectItem>(), _ => expected);
|
||||
|
||||
// Act 1
|
||||
var result1 = cache.GetOrAdd(ViewPath, _ => expected);
|
||||
var result1 = cache.GetOrAdd(ViewPath, _ => cacheContext);
|
||||
|
||||
// Assert 1
|
||||
Assert.True(result1.Success);
|
||||
Assert.IsType<TestView>(result1.PageFactory());
|
||||
Assert.Equal(typeof(TestView), result1.CompiledType);
|
||||
|
||||
// Act 2
|
||||
// Delete the file from the file system and set it's expiration token.
|
||||
fileProvider.DeleteFile(ViewPath);
|
||||
cacheContext = new CompilerCacheContext(
|
||||
new NotFoundProjectItem("", ViewPath),
|
||||
Enumerable.Empty<RazorProjectItem>(),
|
||||
_ => throw new Exception("Shouldn't be called."));
|
||||
fileProvider.GetChangeToken(ViewPath).HasChanged = true;
|
||||
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
|
||||
var result2 = cache.GetOrAdd(ViewPath, _ => cacheContext);
|
||||
|
||||
// Assert 2
|
||||
Assert.False(result2.Success);
|
||||
|
|
@ -127,11 +149,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
var expected2 = new CompilationResult(typeof(DifferentView));
|
||||
|
||||
// Act 1
|
||||
var result1 = cache.GetOrAdd(ViewPath, _ => expected1);
|
||||
var result1 = cache.GetOrAdd(ViewPath, CreateContextFactory(expected1));
|
||||
|
||||
// Assert 1
|
||||
Assert.True(result1.Success);
|
||||
Assert.IsType<TestView>(result1.PageFactory());
|
||||
Assert.Equal(typeof(TestView), result1.CompiledType);
|
||||
|
||||
// Act 2
|
||||
// Verify we're getting cached results.
|
||||
|
|
@ -139,15 +161,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
|
||||
// Assert 2
|
||||
Assert.True(result2.Success);
|
||||
Assert.IsType<TestView>(result2.PageFactory());
|
||||
Assert.Equal(typeof(TestView), result1.CompiledType);
|
||||
|
||||
// Act 3
|
||||
fileProvider.GetChangeToken(ViewPath).HasChanged = true;
|
||||
var result3 = cache.GetOrAdd(ViewPath, _ => expected2);
|
||||
var result3 = cache.GetOrAdd(ViewPath, CreateContextFactory(expected2));
|
||||
|
||||
// Assert 3
|
||||
Assert.True(result3.Success);
|
||||
Assert.IsType<DifferentView>(result3.PageFactory());
|
||||
Assert.Equal(typeof(DifferentView), result3.CompiledType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -162,11 +184,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
var expected2 = new CompilationResult(typeof(DifferentView));
|
||||
|
||||
// Act 1
|
||||
var result1 = cache.GetOrAdd(ViewPath, _ => expected1);
|
||||
var result1 = cache.GetOrAdd(ViewPath, CreateContextFactory(expected1));
|
||||
|
||||
// Assert 1
|
||||
Assert.True(result1.Success);
|
||||
Assert.IsType<TestView>(result1.PageFactory());
|
||||
Assert.Equal(typeof(TestView), result1.CompiledType);
|
||||
|
||||
// Act 2
|
||||
// Verify we're getting cached results.
|
||||
|
|
@ -174,15 +196,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
|
||||
// Assert 2
|
||||
Assert.True(result2.Success);
|
||||
Assert.IsType<TestView>(result2.PageFactory());
|
||||
Assert.Equal(typeof(TestView), result1.CompiledType);
|
||||
|
||||
// Act 3
|
||||
fileProvider.GetChangeToken(globalImportPath).HasChanged = true;
|
||||
var result3 = cache.GetOrAdd(ViewPath, _ => expected2);
|
||||
var result3 = cache.GetOrAdd(ViewPath, CreateContextFactory(expected2));
|
||||
|
||||
// Assert 2
|
||||
Assert.True(result3.Success);
|
||||
Assert.IsType<DifferentView>(result3.PageFactory());
|
||||
Assert.Equal(typeof(DifferentView), result3.CompiledType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -196,19 +218,17 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
var expected = new CompilationResult(typeof(TestView));
|
||||
|
||||
// Act 1
|
||||
var result1 = cache.GetOrAdd(ViewPath, _ => expected);
|
||||
var result1 = cache.GetOrAdd(ViewPath, CreateContextFactory(expected));
|
||||
|
||||
// Assert 1
|
||||
Assert.True(result1.Success);
|
||||
Assert.IsType<TestView>(result1.PageFactory());
|
||||
Assert.Equal(typeof(TestView), result1.CompiledType);
|
||||
|
||||
// Act 2
|
||||
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
|
||||
|
||||
// Assert 2
|
||||
Assert.True(result2.Success);
|
||||
Assert.IsType<TestView>(result2.PageFactory());
|
||||
mockFileProvider.Verify(v => v.GetFileInfo(ViewPath), Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -223,8 +243,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
|
||||
// Assert
|
||||
Assert.True(result.Success);
|
||||
Assert.IsType<PreCompile>(result.PageFactory());
|
||||
Assert.Same(PrecompiledViewsPath, result.PageFactory().Path);
|
||||
Assert.Equal(typeof(PreCompile), result.CompiledType);
|
||||
Assert.Same(PrecompiledViewsPath, result.RelativePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -242,7 +262,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
// Assert
|
||||
Assert.True(result.Success);
|
||||
Assert.True(result.IsPrecompiled);
|
||||
Assert.IsType<PreCompile>(result.PageFactory());
|
||||
Assert.Equal(typeof(PreCompile), result.CompiledType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -260,11 +280,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
|
||||
// Assert
|
||||
Assert.True(result.Success);
|
||||
Assert.IsType<PreCompile>(result.PageFactory());
|
||||
Assert.Equal(typeof(PreCompile), result.CompiledType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrAdd_ReturnsRuntimeCompiledAndPrecompiledViews()
|
||||
public void GetOrAdd_ReturnsRuntimeCompiled()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
|
|
@ -273,24 +293,32 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
var expected = new CompilationResult(typeof(TestView));
|
||||
|
||||
// Act 1
|
||||
var result1 = cache.GetOrAdd(ViewPath, _ => expected);
|
||||
var result1 = cache.GetOrAdd(ViewPath, CreateContextFactory(expected));
|
||||
|
||||
// Assert 1
|
||||
Assert.IsType<TestView>(result1.PageFactory());
|
||||
Assert.Equal(typeof(TestView), result1.CompiledType);
|
||||
|
||||
// Act 2
|
||||
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
|
||||
|
||||
// Assert 2
|
||||
Assert.True(result2.Success);
|
||||
Assert.IsType<TestView>(result2.PageFactory());
|
||||
Assert.Equal(typeof(TestView), result2.CompiledType);
|
||||
}
|
||||
|
||||
// Act 3
|
||||
var result3 = cache.GetOrAdd(PrecompiledViewsPath, ThrowsIfCalled);
|
||||
[Fact]
|
||||
public void GetOrAdd_ReturnsPrecompiledViews()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
var cache = new CompilerCache(fileProvider, _precompiledViews);
|
||||
var expected = new CompilationResult(typeof(TestView));
|
||||
|
||||
// Assert 3
|
||||
Assert.True(result2.Success);
|
||||
Assert.IsType<PreCompile>(result3.PageFactory());
|
||||
// Act
|
||||
var result1 = cache.GetOrAdd(PrecompiledViewsPath, ThrowsIfCalled);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(typeof(PreCompile), result1.CompiledType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -314,8 +342,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
var result = cache.GetOrAdd(relativePath, ThrowsIfCalled);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<PreCompile>(result.PageFactory());
|
||||
Assert.Same(viewPath, result.PageFactory().Path);
|
||||
Assert.Equal(typeof(PreCompile), result.CompiledType);
|
||||
Assert.Equal(viewPath, result.RelativePath);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -337,7 +365,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
var result = cache.GetOrAdd("/Areas/Finances/Views/Home/Index.cshtml", ThrowsIfCalled);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<PreCompile>(result.PageFactory());
|
||||
Assert.Equal(typeof(PreCompile), result.CompiledType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -354,42 +382,55 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
var compilingOne = false;
|
||||
var compilingTwo = false;
|
||||
|
||||
Func<CompilerCacheContext, CompilationResult> compile1 = _ =>
|
||||
{
|
||||
compilingOne = true;
|
||||
|
||||
// Event 2
|
||||
Assert.True(resetEvent1.WaitOne(waitDuration));
|
||||
|
||||
// Event 3
|
||||
Assert.True(resetEvent2.Set());
|
||||
|
||||
// Event 6
|
||||
Assert.True(resetEvent1.WaitOne(waitDuration));
|
||||
|
||||
Assert.True(compilingTwo);
|
||||
return new CompilationResult(typeof(TestView));
|
||||
};
|
||||
|
||||
Func<CompilerCacheContext, CompilationResult> compile2 = _ =>
|
||||
{
|
||||
compilingTwo = true;
|
||||
|
||||
// Event 4
|
||||
Assert.True(resetEvent2.WaitOne(waitDuration));
|
||||
|
||||
// Event 5
|
||||
Assert.True(resetEvent1.Set());
|
||||
|
||||
Assert.True(compilingOne);
|
||||
return new CompilationResult(typeof(DifferentView));
|
||||
};
|
||||
|
||||
|
||||
// Act
|
||||
var task1 = Task.Run(() =>
|
||||
{
|
||||
return cache.GetOrAdd("/Views/Home/Index.cshtml", file =>
|
||||
return cache.GetOrAdd("/Views/Home/Index.cshtml", path =>
|
||||
{
|
||||
compilingOne = true;
|
||||
|
||||
// Event 2
|
||||
resetEvent1.WaitOne(waitDuration);
|
||||
|
||||
// Event 3
|
||||
resetEvent2.Set();
|
||||
|
||||
// Event 6
|
||||
resetEvent1.WaitOne(waitDuration);
|
||||
|
||||
Assert.True(compilingTwo);
|
||||
return new CompilationResult(typeof(TestView));
|
||||
var projectItem = new DefaultRazorProjectItem(new TestFileInfo(), "", path);
|
||||
return new CompilerCacheContext(projectItem, Enumerable.Empty<RazorProjectItem>(), compile1);
|
||||
});
|
||||
});
|
||||
|
||||
var task2 = Task.Run(() =>
|
||||
{
|
||||
// Event 4
|
||||
return cache.GetOrAdd("/Views/Home/About.cshtml", file =>
|
||||
return cache.GetOrAdd("/Views/Home/About.cshtml", path =>
|
||||
{
|
||||
compilingTwo = true;
|
||||
|
||||
// Event 4
|
||||
resetEvent2.WaitOne(waitDuration);
|
||||
|
||||
// Event 5
|
||||
resetEvent1.Set();
|
||||
|
||||
Assert.True(compilingOne);
|
||||
return new CompilationResult(typeof(DifferentView));
|
||||
var projectItem = new DefaultRazorProjectItem(new TestFileInfo(), "", path);
|
||||
return new CompilerCacheContext(projectItem, Enumerable.Empty<RazorProjectItem>(), compile2);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -416,24 +457,30 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
var resetEvent2 = new ManualResetEvent(initialState: false);
|
||||
var cache = new CompilerCache(fileProvider);
|
||||
|
||||
Func<CompilerCacheContext, CompilationResult> compile = _ =>
|
||||
{
|
||||
// Event 2
|
||||
resetEvent1.WaitOne(waitDuration);
|
||||
|
||||
// Event 3
|
||||
resetEvent2.Set();
|
||||
return new CompilationResult(typeof(TestView));
|
||||
};
|
||||
|
||||
// Act
|
||||
var task1 = Task.Run(() =>
|
||||
{
|
||||
return cache.GetOrAdd(ViewPath, file =>
|
||||
return cache.GetOrAdd(ViewPath, path =>
|
||||
{
|
||||
// Event 2
|
||||
resetEvent1.WaitOne(waitDuration);
|
||||
|
||||
// Event 3
|
||||
resetEvent2.Set();
|
||||
return new CompilationResult(typeof(TestView));
|
||||
var projectItem = new DefaultRazorProjectItem(new TestFileInfo(), "", path);
|
||||
return new CompilerCacheContext(projectItem, Enumerable.Empty<RazorProjectItem>(), compile);
|
||||
});
|
||||
});
|
||||
|
||||
var task2 = Task.Run(() =>
|
||||
{
|
||||
// Event 4
|
||||
resetEvent2.WaitOne(waitDuration);
|
||||
Assert.True(resetEvent2.WaitOne(waitDuration));
|
||||
return cache.GetOrAdd(ViewPath, ThrowsIfCalled);
|
||||
});
|
||||
|
||||
|
|
@ -444,7 +491,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
// Assert
|
||||
var result1 = task1.Result;
|
||||
var result2 = task2.Result;
|
||||
Assert.Same(result1.PageFactory, result2.PageFactory);
|
||||
Assert.Same(result1.CompiledType, result2.CompiledType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -475,7 +522,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
|
||||
// Act and Assert - 1
|
||||
var actual = Assert.Throws<InvalidTimeZoneException>(() =>
|
||||
cache.GetOrAdd(ViewPath, _ => { throw exception; }));
|
||||
cache.GetOrAdd(ViewPath, _ => ThrowsIfCalled(ViewPath, exception)));
|
||||
Assert.Same(exception, actual);
|
||||
|
||||
// Act and Assert - 2
|
||||
|
|
@ -489,6 +536,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(ViewPath, "some content");
|
||||
var changeToken = fileProvider.AddChangeToken(ViewPath);
|
||||
var cache = new CompilerCache(fileProvider);
|
||||
|
||||
// Act and Assert - 1
|
||||
|
|
@ -496,11 +544,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
cache.GetOrAdd(ViewPath, _ => { throw new InvalidTimeZoneException(); }));
|
||||
|
||||
// Act - 2
|
||||
fileProvider.GetChangeToken(ViewPath).HasChanged = true;
|
||||
var result = cache.GetOrAdd(ViewPath, _ => new CompilationResult(typeof(TestView)));
|
||||
changeToken.HasChanged = true;
|
||||
var result = cache.GetOrAdd(ViewPath, CreateContextFactory(new CompilationResult(typeof(TestView))));
|
||||
|
||||
// Assert - 2
|
||||
Assert.IsType<TestView>(result.PageFactory());
|
||||
Assert.Same(typeof(TestView), result.CompiledType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -512,15 +560,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
var cache = new CompilerCache(fileProvider);
|
||||
var diagnosticMessages = new[]
|
||||
{
|
||||
new AspNetCore.Diagnostics.DiagnosticMessage("message", "message", ViewPath, 1, 1, 1, 1)
|
||||
new DiagnosticMessage("message", "message", ViewPath, 1, 1, 1, 1)
|
||||
};
|
||||
var compilationResult = new CompilationResult(new[]
|
||||
{
|
||||
new CompilationFailure(ViewPath, "some content", "compiled content", diagnosticMessages)
|
||||
});
|
||||
var context = CreateContextFactory(compilationResult);
|
||||
|
||||
// Act and Assert - 1
|
||||
var ex = Assert.Throws<CompilationFailedException>(() => cache.GetOrAdd(ViewPath, _ => compilationResult));
|
||||
var ex = Assert.Throws<CompilationFailedException>(() => cache.GetOrAdd(ViewPath, context));
|
||||
Assert.Same(compilationResult.CompilationFailures, ex.CompilationFailures);
|
||||
|
||||
// Act and Assert - 2
|
||||
|
|
@ -552,9 +601,38 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
}
|
||||
}
|
||||
|
||||
private CompilationResult ThrowsIfCalled(RelativeFileInfo file)
|
||||
private CompilerCacheContext ThrowsIfCalled(string path) =>
|
||||
ThrowsIfCalled(path, new Exception("Shouldn't be called"));
|
||||
|
||||
private CompilerCacheContext ThrowsIfCalled(string path, Exception exception)
|
||||
{
|
||||
throw new Exception("Shouldn't be called");
|
||||
exception = exception ?? new Exception("Shouldn't be called");
|
||||
var projectItem = new DefaultRazorProjectItem(new TestFileInfo(), "", path);
|
||||
|
||||
return new CompilerCacheContext(
|
||||
projectItem,
|
||||
Enumerable.Empty<RazorProjectItem>(),
|
||||
_ => throw exception);
|
||||
}
|
||||
|
||||
private Func<string, CompilerCacheContext> CreateContextFactory(CompilationResult compile)
|
||||
{
|
||||
return path => CreateCacheContext(compile, path);
|
||||
}
|
||||
|
||||
private CompilerCacheContext CreateCacheContext(CompilationResult compile, string path = ViewPath)
|
||||
{
|
||||
var projectItem = new DefaultRazorProjectItem(new TestFileInfo(), "", path);
|
||||
|
||||
var imports = new List<RazorProjectItem>();
|
||||
foreach (var importFilePath in _viewImportsPath)
|
||||
{
|
||||
var importProjectItem = new DefaultRazorProjectItem(new TestFileInfo(), "", importFilePath);
|
||||
|
||||
imports.Add(importProjectItem);
|
||||
}
|
||||
|
||||
return new CompilerCacheContext(projectItem, imports, _ => compile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
@ -16,6 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
public void CreateFactory_ReturnsExpirationTokensFromCompilerCache_ForUnsuccessfulResults()
|
||||
{
|
||||
// Arrange
|
||||
var path = "/file-does-not-exist";
|
||||
var expirationTokens = new[]
|
||||
{
|
||||
Mock.Of<IChangeToken>(),
|
||||
|
|
@ -23,18 +25,20 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
};
|
||||
var compilerCache = new Mock<ICompilerCache>();
|
||||
compilerCache
|
||||
.Setup(f => f.GetOrAdd(It.IsAny<string>(), It.IsAny<Func<RelativeFileInfo, CompilationResult>>()))
|
||||
.Returns(new CompilerCacheResult(expirationTokens));
|
||||
.Setup(f => f.GetOrAdd(It.IsAny<string>(), It.IsAny<Func<string, CompilerCacheContext>>()))
|
||||
.Returns(new CompilerCacheResult(path, expirationTokens));
|
||||
var compilerCacheProvider = new Mock<ICompilerCacheProvider>();
|
||||
compilerCacheProvider
|
||||
.SetupGet(c => c.Cache)
|
||||
.Returns(compilerCache.Object);
|
||||
var factoryProvider = new DefaultRazorPageFactoryProvider(
|
||||
Mock.Of<IRazorCompilationService>(),
|
||||
RazorEngine.Create(),
|
||||
new DefaultRazorProject(new TestFileProvider()),
|
||||
Mock.Of<ICompilationService>(),
|
||||
compilerCacheProvider.Object);
|
||||
|
||||
// Act
|
||||
var result = factoryProvider.CreateFactory("/file-does-not-exist");
|
||||
var result = factoryProvider.CreateFactory(path);
|
||||
|
||||
// Assert
|
||||
Assert.False(result.Success);
|
||||
|
|
@ -53,14 +57,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
};
|
||||
var compilerCache = new Mock<ICompilerCache>();
|
||||
compilerCache
|
||||
.Setup(f => f.GetOrAdd(It.IsAny<string>(), It.IsAny<Func<RelativeFileInfo, CompilationResult>>()))
|
||||
.Setup(f => f.GetOrAdd(It.IsAny<string>(), It.IsAny<Func<string, CompilerCacheContext>>()))
|
||||
.Returns(new CompilerCacheResult(relativePath, new CompilationResult(typeof(TestRazorPage)), expirationTokens));
|
||||
var compilerCacheProvider = new Mock<ICompilerCacheProvider>();
|
||||
compilerCacheProvider
|
||||
.SetupGet(c => c.Cache)
|
||||
.Returns(compilerCache.Object);
|
||||
var factoryProvider = new DefaultRazorPageFactoryProvider(
|
||||
Mock.Of<IRazorCompilationService>(),
|
||||
RazorEngine.Create(),
|
||||
new DefaultRazorProject(new TestFileProvider()),
|
||||
Mock.Of<ICompilationService>(),
|
||||
compilerCacheProvider.Object);
|
||||
|
||||
// Act
|
||||
|
|
@ -78,14 +84,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
var relativePath = "/file-exists";
|
||||
var compilerCache = new Mock<ICompilerCache>();
|
||||
compilerCache
|
||||
.Setup(f => f.GetOrAdd(It.IsAny<string>(), It.IsAny<Func<RelativeFileInfo, CompilationResult>>()))
|
||||
.Setup(f => f.GetOrAdd(It.IsAny<string>(), It.IsAny<Func<string, CompilerCacheContext>>()))
|
||||
.Returns(new CompilerCacheResult(relativePath, new CompilationResult(typeof(TestRazorPage)), new IChangeToken[0]));
|
||||
var compilerCacheProvider = new Mock<ICompilerCacheProvider>();
|
||||
compilerCacheProvider
|
||||
.SetupGet(c => c.Cache)
|
||||
.Returns(compilerCache.Object);
|
||||
var factoryProvider = new DefaultRazorPageFactoryProvider(
|
||||
Mock.Of<IRazorCompilationService>(),
|
||||
RazorEngine.Create(),
|
||||
new DefaultRazorProject(new TestFileProvider()),
|
||||
Mock.Of<ICompilationService>(),
|
||||
compilerCacheProvider.Object);
|
||||
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -1,246 +0,0 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||
{
|
||||
public class RazorCompilationServiceTest
|
||||
{
|
||||
[Fact]
|
||||
public void CompileCalculatesRootRelativePath()
|
||||
{
|
||||
// Arrange
|
||||
var viewPath = @"src\work\myapp\Views\index\home.cshtml";
|
||||
var relativePath = @"Views\index\home.cshtml";
|
||||
|
||||
var fileInfo = new Mock<IFileInfo>();
|
||||
fileInfo.Setup(f => f.PhysicalPath).Returns(viewPath);
|
||||
fileInfo.Setup(f => f.CreateReadStream()).Returns(new MemoryStream(new byte[] { 0 }));
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(relativePath, fileInfo.Object);
|
||||
var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, relativePath);
|
||||
|
||||
var compiler = new Mock<ICompilationService>();
|
||||
compiler.Setup(c => c.Compile(It.IsAny<RazorCodeDocument>(), It.IsAny<RazorCSharpDocument>()))
|
||||
.Returns(new CompilationResult(typeof(RazorCompilationServiceTest)));
|
||||
|
||||
var engine = new Mock<RazorEngine>();
|
||||
engine.Setup(e => e.Process(It.IsAny<RazorCodeDocument>()))
|
||||
.Callback<RazorCodeDocument>(document =>
|
||||
{
|
||||
document.SetCSharpDocument(new RazorCSharpDocument()
|
||||
{
|
||||
Diagnostics = new List<RazorDiagnostic>()
|
||||
});
|
||||
|
||||
Assert.Equal(viewPath, document.Source.FileName); // Assert if source file name is the root relative path
|
||||
}).Verifiable();
|
||||
|
||||
var razorService = new RazorCompilationService(
|
||||
compiler.Object,
|
||||
engine.Object,
|
||||
new DefaultRazorProject(fileProvider),
|
||||
GetFileProviderAccessor(fileProvider),
|
||||
NullLoggerFactory.Instance);
|
||||
|
||||
// Act
|
||||
razorService.Compile(relativeFileInfo);
|
||||
|
||||
// Assert
|
||||
engine.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Compile_ReturnsFailedResultIfParseFails()
|
||||
{
|
||||
// Arrange
|
||||
var relativePath = @"Views\index\home.cshtml";
|
||||
var fileInfo = new Mock<IFileInfo>();
|
||||
fileInfo.Setup(f => f.CreateReadStream()).Returns(new MemoryStream(new byte[] { 0 }));
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(relativePath, fileInfo.Object);
|
||||
var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, relativePath);
|
||||
|
||||
var compiler = new Mock<ICompilationService>(MockBehavior.Strict);
|
||||
|
||||
var engine = new Mock<RazorEngine>();
|
||||
engine.Setup(e => e.Process(It.IsAny<RazorCodeDocument>()))
|
||||
.Callback<RazorCodeDocument>(document =>
|
||||
{
|
||||
document.SetCSharpDocument(new RazorCSharpDocument()
|
||||
{
|
||||
Diagnostics = new List<RazorDiagnostic>()
|
||||
{
|
||||
GetRazorDiagnostic("some message", new SourceLocation(1, 1, 1), length: 1)
|
||||
}
|
||||
});
|
||||
}).Verifiable();
|
||||
|
||||
var razorService = new RazorCompilationService(
|
||||
compiler.Object,
|
||||
engine.Object,
|
||||
new DefaultRazorProject(fileProvider),
|
||||
GetFileProviderAccessor(fileProvider),
|
||||
NullLoggerFactory.Instance);
|
||||
|
||||
// Act
|
||||
var result = razorService.Compile(relativeFileInfo);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result.CompilationFailures);
|
||||
Assert.Collection(result.CompilationFailures,
|
||||
failure =>
|
||||
{
|
||||
var message = Assert.Single(failure.Messages);
|
||||
Assert.Equal("some message", message.Message);
|
||||
});
|
||||
engine.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Compile_ReturnsResultFromCompilationServiceIfParseSucceeds()
|
||||
{
|
||||
var relativePath = @"Views\index\home.cshtml";
|
||||
var fileInfo = new Mock<IFileInfo>();
|
||||
fileInfo.Setup(f => f.CreateReadStream()).Returns(new MemoryStream(new byte[] { 0 }));
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(relativePath, fileInfo.Object);
|
||||
var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, relativePath);
|
||||
|
||||
var compilationResult = new CompilationResult(typeof(object));
|
||||
var compiler = new Mock<ICompilationService>();
|
||||
compiler.Setup(c => c.Compile(It.IsAny<RazorCodeDocument>(), It.IsAny<RazorCSharpDocument>()))
|
||||
.Returns(compilationResult)
|
||||
.Verifiable();
|
||||
|
||||
var engine = new Mock<RazorEngine>();
|
||||
engine.Setup(e => e.Process(It.IsAny<RazorCodeDocument>()))
|
||||
.Callback<RazorCodeDocument>(document =>
|
||||
{
|
||||
document.SetCSharpDocument(new RazorCSharpDocument()
|
||||
{
|
||||
Diagnostics = new List<RazorDiagnostic>()
|
||||
});
|
||||
});
|
||||
|
||||
var razorService = new RazorCompilationService(
|
||||
compiler.Object,
|
||||
engine.Object,
|
||||
new DefaultRazorProject(fileProvider),
|
||||
GetFileProviderAccessor(fileProvider),
|
||||
NullLoggerFactory.Instance);
|
||||
|
||||
// Act
|
||||
var result = razorService.Compile(relativeFileInfo);
|
||||
|
||||
// Assert
|
||||
Assert.Same(compilationResult.CompiledType, result.CompiledType);
|
||||
compiler.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCompilationFailedResult_ReturnsCompilationResult_WithGroupedMessages()
|
||||
{
|
||||
// Arrange
|
||||
var viewPath = @"views/index.razor";
|
||||
var viewImportsPath = @"views/global.import.cshtml";
|
||||
|
||||
var fileProvider = new TestFileProvider();
|
||||
var file = fileProvider.AddFile(viewPath, "View Content");
|
||||
fileProvider.AddFile(viewImportsPath, "Global Import Content");
|
||||
var razorService = new RazorCompilationService(
|
||||
Mock.Of<ICompilationService>(),
|
||||
Mock.Of<RazorEngine>(),
|
||||
new DefaultRazorProject(fileProvider),
|
||||
GetFileProviderAccessor(fileProvider),
|
||||
NullLoggerFactory.Instance);
|
||||
var errors = new[]
|
||||
{
|
||||
GetRazorDiagnostic("message-1", new SourceLocation(1, 2, 17), length: 1),
|
||||
GetRazorDiagnostic("message-2", new SourceLocation(viewPath, 1, 4, 6), length: 7),
|
||||
GetRazorDiagnostic("message-3", SourceLocation.Undefined, length: -1),
|
||||
GetRazorDiagnostic("message-4", new SourceLocation(viewImportsPath, 1, 3, 8), length: 4),
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = razorService.GetCompilationFailedResult(viewPath, errors);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result.CompilationFailures);
|
||||
Assert.Collection(result.CompilationFailures,
|
||||
failure =>
|
||||
{
|
||||
Assert.Equal(viewPath, failure.SourceFilePath);
|
||||
Assert.Equal("View Content", failure.SourceFileContent);
|
||||
Assert.Collection(failure.Messages,
|
||||
message =>
|
||||
{
|
||||
Assert.Equal(errors[0].GetMessage(), message.Message);
|
||||
Assert.Equal(viewPath, message.SourceFilePath);
|
||||
Assert.Equal(3, message.StartLine);
|
||||
Assert.Equal(17, message.StartColumn);
|
||||
Assert.Equal(3, message.EndLine);
|
||||
Assert.Equal(18, message.EndColumn);
|
||||
},
|
||||
message =>
|
||||
{
|
||||
Assert.Equal(errors[1].GetMessage(), message.Message);
|
||||
Assert.Equal(viewPath, message.SourceFilePath);
|
||||
Assert.Equal(5, message.StartLine);
|
||||
Assert.Equal(6, message.StartColumn);
|
||||
Assert.Equal(5, message.EndLine);
|
||||
Assert.Equal(13, message.EndColumn);
|
||||
},
|
||||
message =>
|
||||
{
|
||||
Assert.Equal(errors[2].GetMessage(), message.Message);
|
||||
Assert.Equal(viewPath, message.SourceFilePath);
|
||||
Assert.Equal(0, message.StartLine);
|
||||
Assert.Equal(-1, message.StartColumn);
|
||||
Assert.Equal(0, message.EndLine);
|
||||
Assert.Equal(-2, message.EndColumn);
|
||||
});
|
||||
},
|
||||
failure =>
|
||||
{
|
||||
Assert.Equal(viewImportsPath, failure.SourceFilePath);
|
||||
Assert.Equal("Global Import Content", failure.SourceFileContent);
|
||||
Assert.Collection(failure.Messages,
|
||||
message =>
|
||||
{
|
||||
Assert.Equal(errors[3].GetMessage(), message.Message);
|
||||
Assert.Equal(viewImportsPath, message.SourceFilePath);
|
||||
Assert.Equal(4, message.StartLine);
|
||||
Assert.Equal(8, message.StartColumn);
|
||||
Assert.Equal(4, message.EndLine);
|
||||
Assert.Equal(12, message.EndColumn);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static IRazorViewEngineFileProviderAccessor GetFileProviderAccessor(IFileProvider fileProvider = null)
|
||||
{
|
||||
var options = new Mock<IRazorViewEngineFileProviderAccessor>();
|
||||
options.SetupGet(o => o.FileProvider)
|
||||
.Returns(fileProvider ?? new TestFileProvider());
|
||||
|
||||
return options.Object;
|
||||
}
|
||||
|
||||
private static RazorDiagnostic GetRazorDiagnostic(string message, SourceLocation sourceLocation, int length)
|
||||
{
|
||||
var diagnosticDescriptor = new RazorDiagnosticDescriptor("test-id", () => message, RazorDiagnosticSeverity.Error);
|
||||
var sourceSpan = new SourceSpan(sourceLocation, length);
|
||||
|
||||
return RazorDiagnostic.Create(diagnosticDescriptor, sourceSpan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,262 @@
|
|||
// 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.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||
{
|
||||
public class RazorCompilerTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetCompilationFailedResult_ReadsRazorErrorsFromPage()
|
||||
{
|
||||
// Arrange
|
||||
var viewPath = "/Views/Home/Index.cshtml";
|
||||
var razorEngine = RazorEngine.Create();
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(viewPath, "<span name=\"@(User.Id\">");
|
||||
var razorProject = new DefaultRazorProject(fileProvider);
|
||||
|
||||
var templateEngine = new MvcRazorTemplateEngine(razorEngine, razorProject);
|
||||
var compiler = new RazorCompiler(
|
||||
Mock.Of<ICompilationService>(),
|
||||
GetCompilerCacheProvider(fileProvider),
|
||||
templateEngine);
|
||||
var codeDocument = templateEngine.CreateCodeDocument(viewPath);
|
||||
|
||||
// Act
|
||||
var csharpDocument = templateEngine.GenerateCode(codeDocument);
|
||||
var compilationResult = compiler.GetCompilationFailedResult(codeDocument, csharpDocument.Diagnostics);
|
||||
|
||||
// Assert
|
||||
var failure = Assert.Single(compilationResult.CompilationFailures);
|
||||
Assert.Equal(viewPath, failure.SourceFilePath);
|
||||
Assert.Collection(failure.Messages,
|
||||
message => Assert.StartsWith(
|
||||
@"(1,22): Error RZ9999: Unterminated string literal.",
|
||||
message.FormattedMessage),
|
||||
message => Assert.StartsWith(
|
||||
@"(1,14): Error RZ9999: The explicit expression block is missing a closing "")"" character.",
|
||||
message.FormattedMessage));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCompilationFailedResult_UsesPhysicalPath()
|
||||
{
|
||||
// Arrange
|
||||
var viewPath = "/Views/Home/Index.cshtml";
|
||||
var physicalPath = @"x:\myapp\views\home\index.cshtml";
|
||||
var razorEngine = RazorEngine.Create();
|
||||
var fileProvider = new TestFileProvider();
|
||||
var file = fileProvider.AddFile(viewPath, "<span name=\"@(User.Id\">");
|
||||
file.PhysicalPath = physicalPath;
|
||||
var razorProject = new DefaultRazorProject(fileProvider);
|
||||
|
||||
var templateEngine = new MvcRazorTemplateEngine(razorEngine, razorProject);
|
||||
var compiler = new RazorCompiler(
|
||||
Mock.Of<ICompilationService>(),
|
||||
GetCompilerCacheProvider(fileProvider),
|
||||
templateEngine);
|
||||
var codeDocument = templateEngine.CreateCodeDocument(viewPath);
|
||||
|
||||
// Act
|
||||
var csharpDocument = templateEngine.GenerateCode(codeDocument);
|
||||
var compilationResult = compiler.GetCompilationFailedResult(codeDocument, csharpDocument.Diagnostics);
|
||||
|
||||
// Assert
|
||||
var failure = Assert.Single(compilationResult.CompilationFailures);
|
||||
Assert.Equal(physicalPath, failure.SourceFilePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCompilationFailedResult_ReadsContentFromSourceDocuments()
|
||||
{
|
||||
// Arrange
|
||||
var viewPath = "/Views/Home/Index.cshtml";
|
||||
var fileContent =
|
||||
@"
|
||||
@if (User.IsAdmin)
|
||||
{
|
||||
<span>
|
||||
}
|
||||
</span>";
|
||||
|
||||
var razorEngine = RazorEngine.Create();
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(viewPath, fileContent);
|
||||
var razorProject = new DefaultRazorProject(fileProvider);
|
||||
|
||||
var templateEngine = new MvcRazorTemplateEngine(razorEngine, razorProject);
|
||||
var compiler = new RazorCompiler(
|
||||
Mock.Of<ICompilationService>(),
|
||||
GetCompilerCacheProvider(fileProvider),
|
||||
templateEngine);
|
||||
var codeDocument = templateEngine.CreateCodeDocument(viewPath);
|
||||
|
||||
// Act
|
||||
var csharpDocument = templateEngine.GenerateCode(codeDocument);
|
||||
var compilationResult = compiler.GetCompilationFailedResult(codeDocument, csharpDocument.Diagnostics);
|
||||
|
||||
// Assert
|
||||
var failure = Assert.Single(compilationResult.CompilationFailures);
|
||||
Assert.Equal(fileContent, failure.SourceFileContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCompilationFailedResult_ReadsContentFromImports()
|
||||
{
|
||||
// Arrange
|
||||
var viewPath = "/Views/Home/Index.cshtml";
|
||||
var importsFilePath = @"x:\views\_MyImports.cshtml";
|
||||
var fileContent = "@ ";
|
||||
var importsContent = "@(abc";
|
||||
|
||||
var razorEngine = RazorEngine.Create();
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(viewPath, fileContent);
|
||||
var importsFile = fileProvider.AddFile("/Views/_MyImports.cshtml", importsContent);
|
||||
importsFile.PhysicalPath = importsFilePath;
|
||||
var razorProject = new DefaultRazorProject(fileProvider);
|
||||
|
||||
var templateEngine = new MvcRazorTemplateEngine(razorEngine, razorProject)
|
||||
{
|
||||
Options =
|
||||
{
|
||||
ImportsFileName = "_MyImports.cshtml",
|
||||
}
|
||||
};
|
||||
var compiler = new RazorCompiler(
|
||||
Mock.Of<ICompilationService>(),
|
||||
GetCompilerCacheProvider(fileProvider),
|
||||
templateEngine);
|
||||
var codeDocument = templateEngine.CreateCodeDocument(viewPath);
|
||||
|
||||
// Act
|
||||
var csharpDocument = templateEngine.GenerateCode(codeDocument);
|
||||
var compilationResult = compiler.GetCompilationFailedResult(codeDocument, csharpDocument.Diagnostics);
|
||||
|
||||
// Assert
|
||||
// This expectation is incorrect. https://github.com/aspnet/Razor/issues/1069 needs to be fixed,
|
||||
// which should cause this test to fail.
|
||||
var failure = Assert.Single(compilationResult.CompilationFailures);
|
||||
Assert.Equal(viewPath, failure.SourceFilePath);
|
||||
Assert.Collection(failure.Messages,
|
||||
message =>
|
||||
{
|
||||
Assert.Equal(@"A space or line break was encountered after the ""@"" character. Only valid identifiers, keywords, comments, ""("" and ""{"" are valid at the start of a code block and they must occur immediately following ""@"" with no space in between.",
|
||||
message.Message);
|
||||
},
|
||||
message =>
|
||||
{
|
||||
Assert.Equal(@"The explicit expression block is missing a closing "")"" character. Make sure you have a matching "")"" character for all the ""("" characters within this block, and that none of the "")"" characters are being interpreted as markup.",
|
||||
message.Message);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCompilationFailedResult_GroupsMessages()
|
||||
{
|
||||
// Arrange
|
||||
var viewPath = "views/index.razor";
|
||||
var viewImportsPath = "views/global.import.cshtml";
|
||||
var codeDocument = RazorCodeDocument.Create(
|
||||
Create(viewPath, "View Content"),
|
||||
new[] { Create(viewImportsPath, "Global Import Content") });
|
||||
var diagnostics = new[]
|
||||
{
|
||||
GetRazorDiagnostic("message-1", new SourceLocation(1, 2, 17), length: 1),
|
||||
GetRazorDiagnostic("message-2", new SourceLocation(viewPath, 1, 4, 6), length: 7),
|
||||
GetRazorDiagnostic("message-3", SourceLocation.Undefined, length: -1),
|
||||
GetRazorDiagnostic("message-4", new SourceLocation(viewImportsPath, 1, 3, 8), length: 4),
|
||||
};
|
||||
var fileProvider = new TestFileProvider();
|
||||
var compiler = new RazorCompiler(
|
||||
Mock.Of<ICompilationService>(),
|
||||
GetCompilerCacheProvider(fileProvider),
|
||||
new MvcRazorTemplateEngine(RazorEngine.Create(), new DefaultRazorProject(fileProvider)));
|
||||
|
||||
// Act
|
||||
var result = compiler.GetCompilationFailedResult(codeDocument, diagnostics);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.CompilationFailures,
|
||||
failure =>
|
||||
{
|
||||
Assert.Equal(viewPath, failure.SourceFilePath);
|
||||
Assert.Equal("View Content", failure.SourceFileContent);
|
||||
Assert.Collection(failure.Messages,
|
||||
message =>
|
||||
{
|
||||
Assert.Equal(diagnostics[0].GetMessage(), message.Message);
|
||||
Assert.Equal(viewPath, message.SourceFilePath);
|
||||
Assert.Equal(3, message.StartLine);
|
||||
Assert.Equal(17, message.StartColumn);
|
||||
Assert.Equal(3, message.EndLine);
|
||||
Assert.Equal(18, message.EndColumn);
|
||||
},
|
||||
message =>
|
||||
{
|
||||
Assert.Equal(diagnostics[1].GetMessage(), message.Message);
|
||||
Assert.Equal(viewPath, message.SourceFilePath);
|
||||
Assert.Equal(5, message.StartLine);
|
||||
Assert.Equal(6, message.StartColumn);
|
||||
Assert.Equal(5, message.EndLine);
|
||||
Assert.Equal(13, message.EndColumn);
|
||||
},
|
||||
message =>
|
||||
{
|
||||
Assert.Equal(diagnostics[2].GetMessage(), message.Message);
|
||||
Assert.Equal(viewPath, message.SourceFilePath);
|
||||
Assert.Equal(0, message.StartLine);
|
||||
Assert.Equal(-1, message.StartColumn);
|
||||
Assert.Equal(0, message.EndLine);
|
||||
Assert.Equal(-2, message.EndColumn);
|
||||
});
|
||||
},
|
||||
failure =>
|
||||
{
|
||||
Assert.Equal(viewImportsPath, failure.SourceFilePath);
|
||||
Assert.Equal("Global Import Content", failure.SourceFileContent);
|
||||
Assert.Collection(failure.Messages,
|
||||
message =>
|
||||
{
|
||||
Assert.Equal(diagnostics[3].GetMessage(), message.Message);
|
||||
Assert.Equal(viewImportsPath, message.SourceFilePath);
|
||||
Assert.Equal(4, message.StartLine);
|
||||
Assert.Equal(8, message.StartColumn);
|
||||
Assert.Equal(4, message.EndLine);
|
||||
Assert.Equal(12, message.EndColumn);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private ICompilerCacheProvider GetCompilerCacheProvider(TestFileProvider fileProvider)
|
||||
{
|
||||
var compilerCache = new CompilerCache(fileProvider);
|
||||
var compilerCacheProvider = new Mock<ICompilerCacheProvider>();
|
||||
compilerCacheProvider.SetupGet(p => p.Cache).Returns(compilerCache);
|
||||
|
||||
return compilerCacheProvider.Object;
|
||||
}
|
||||
|
||||
private static RazorSourceDocument Create(string path, string template)
|
||||
{
|
||||
var stream = new MemoryStream(Encoding.UTF8.GetBytes(template));
|
||||
return RazorSourceDocument.ReadFrom(stream, path);
|
||||
}
|
||||
|
||||
private static RazorDiagnostic GetRazorDiagnostic(string message, SourceLocation sourceLocation, int length)
|
||||
{
|
||||
var diagnosticDescriptor = new RazorDiagnosticDescriptor("test-id", () => message, RazorDiagnosticSeverity.Error);
|
||||
var sourceSpan = new SourceSpan(sourceLocation, length);
|
||||
|
||||
return RazorDiagnostic.Create(diagnosticDescriptor, sourceSpan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,10 @@ using System.Threading;
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
|
@ -855,7 +857,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
|
|||
.Setup(p => p.CreateFactory("/Views/_ViewStart.cshtml"))
|
||||
.Returns(new RazorPageFactoryResult(() => viewStart, new IChangeToken[0]));
|
||||
|
||||
var viewEngine = CreateViewEngine(pageFactory.Object);
|
||||
var fileProvider = new TestFileProvider();
|
||||
var razorProject = new DefaultRazorProject(fileProvider);
|
||||
var viewEngine = CreateViewEngine(pageFactory.Object, razorProject: razorProject);
|
||||
var context = GetActionContext(_controllerTestContext);
|
||||
|
||||
// Act 1
|
||||
|
|
@ -1302,6 +1306,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
|
|||
Mock.Of<IRazorPageActivator>(),
|
||||
new HtmlTestEncoder(),
|
||||
GetOptionsAccessor(expanders: null),
|
||||
new DefaultRazorProject(new TestFileProvider()),
|
||||
loggerFactory);
|
||||
|
||||
// Act
|
||||
|
|
@ -1615,10 +1620,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
|
|||
|
||||
private TestableRazorViewEngine CreateViewEngine(
|
||||
IRazorPageFactoryProvider pageFactory = null,
|
||||
IEnumerable<IViewLocationExpander> expanders = null)
|
||||
IEnumerable<IViewLocationExpander> expanders = null,
|
||||
RazorProject razorProject = null)
|
||||
{
|
||||
pageFactory = pageFactory ?? Mock.Of<IRazorPageFactoryProvider>();
|
||||
return new TestableRazorViewEngine(pageFactory, GetOptionsAccessor(expanders));
|
||||
if (razorProject == null)
|
||||
{
|
||||
razorProject = new DefaultRazorProject(new TestFileProvider());
|
||||
}
|
||||
return new TestableRazorViewEngine(pageFactory, GetOptionsAccessor(expanders), razorProject);
|
||||
}
|
||||
|
||||
private static IOptions<RazorViewEngineOptions> GetOptionsAccessor(
|
||||
|
|
@ -1707,7 +1717,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
|
|||
public TestableRazorViewEngine(
|
||||
IRazorPageFactoryProvider pageFactory,
|
||||
IOptions<RazorViewEngineOptions> optionsAccessor)
|
||||
: base(pageFactory, Mock.Of<IRazorPageActivator>(), new HtmlTestEncoder(), optionsAccessor, NullLoggerFactory.Instance)
|
||||
: this(pageFactory, optionsAccessor, new DefaultRazorProject(new TestFileProvider()))
|
||||
{
|
||||
}
|
||||
|
||||
public TestableRazorViewEngine(
|
||||
IRazorPageFactoryProvider pageFactory,
|
||||
IOptions<RazorViewEngineOptions> optionsAccessor,
|
||||
RazorProject razorProject)
|
||||
: base(pageFactory, Mock.Of<IRazorPageActivator>(), new HtmlTestEncoder(), optionsAccessor, razorProject, NullLoggerFactory.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile("/Home/Path1/_PageStart.cshtml", "content1");
|
||||
fileProvider.AddFile("/_PageStart.cshtml", "content2");
|
||||
var defaultRazorProject = new DefaultRazorProject(fileProvider);
|
||||
var defaultRazorProject = new TestRazorProject(fileProvider);
|
||||
|
||||
var invokerProvider = CreateInvokerProvider(
|
||||
loader.Object,
|
||||
|
|
@ -249,7 +249,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
|
||||
var razorPageFactoryProvider = new Mock<IRazorPageFactoryProvider>();
|
||||
var fileProvider = new TestFileProvider();
|
||||
var defaultRazorProject = new DefaultRazorProject(fileProvider);
|
||||
var defaultRazorProject = new TestRazorProject(fileProvider);
|
||||
|
||||
var invokerProvider = CreateInvokerProvider(
|
||||
loader.Object,
|
||||
|
|
@ -592,7 +592,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
fileProvider.AddFile("/Views/_PageStart.cshtml", "@page starts!");
|
||||
fileProvider.AddFile("/Views/Deeper/_PageStart.cshtml", "page content");
|
||||
|
||||
var razorProject = CreateRazorProject(fileProvider);
|
||||
var razorProject = new TestRazorProject(fileProvider);
|
||||
|
||||
var mock = new Mock<IRazorPageFactoryProvider>();
|
||||
mock.Setup(p => p.CreateFactory("/Views/Deeper/_PageStart.cshtml"))
|
||||
|
|
@ -642,8 +642,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
|
||||
// No files
|
||||
var fileProvider = new TestFileProvider();
|
||||
|
||||
var razorProject = CreateRazorProject(fileProvider);
|
||||
var razorProject = new TestRazorProject(fileProvider);
|
||||
|
||||
var invokerProvider = CreateInvokerProvider(
|
||||
loader.Object,
|
||||
|
|
@ -670,11 +669,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
return mock.Object;
|
||||
}
|
||||
|
||||
private RazorProject CreateRazorProject(IFileProvider fileProvider)
|
||||
{
|
||||
return new DefaultRazorProject(fileProvider);
|
||||
}
|
||||
|
||||
private static CompiledPageActionDescriptor CreateCompiledPageActionDescriptor(
|
||||
PageActionDescriptor descriptor,
|
||||
Type pageType = null)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
// 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 Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Moq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||
{
|
||||
public class TestRazorProject : DefaultRazorProject
|
||||
{
|
||||
public TestRazorProject(IFileProvider fileProvider)
|
||||
:base(GetAccessor(fileProvider))
|
||||
{
|
||||
}
|
||||
|
||||
private static IRazorViewEngineFileProviderAccessor GetAccessor(IFileProvider fileProvider)
|
||||
{
|
||||
var fileProviderAccessor = new Mock<IRazorViewEngineFileProviderAccessor>();
|
||||
fileProviderAccessor.SetupGet(f => f.FileProvider)
|
||||
.Returns(fileProvider);
|
||||
|
||||
return fileProviderAccessor.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -74,6 +74,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
}
|
||||
}
|
||||
|
||||
public virtual TestFileChangeToken AddChangeToken(string filter)
|
||||
{
|
||||
var changeToken = new TestFileChangeToken();
|
||||
_fileTriggers[filter] = changeToken;
|
||||
|
||||
return changeToken;
|
||||
}
|
||||
|
||||
public virtual IChangeToken Watch(string filter)
|
||||
{
|
||||
TestFileChangeToken changeToken;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System.Diagnostics;
|
|||
using System.IO;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace RazorPageExecutionInstrumentationWebSite
|
||||
|
|
@ -16,7 +16,7 @@ namespace RazorPageExecutionInstrumentationWebSite
|
|||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// Normalize line endings to avoid changes in instrumentation locations between systems.
|
||||
services.AddTransient<IRazorCompilationService, TestRazorCompilationService>();
|
||||
services.AddTransient<RazorProject, TestRazorProject>();
|
||||
|
||||
// Add MVC services to the services container.
|
||||
services.AddMvc();
|
||||
|
|
|
|||
|
|
@ -3,40 +3,45 @@
|
|||
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace RazorPageExecutionInstrumentationWebSite
|
||||
{
|
||||
public class TestRazorCompilationService : RazorCompilationService
|
||||
public class TestRazorProject : DefaultRazorProject
|
||||
{
|
||||
public TestRazorCompilationService(
|
||||
ICompilationService compilationService,
|
||||
RazorEngine engine,
|
||||
RazorProject project,
|
||||
IRazorViewEngineFileProviderAccessor fileProviderAccessor,
|
||||
ILoggerFactory loggerFactory)
|
||||
: base(compilationService, engine, project, fileProviderAccessor, loggerFactory)
|
||||
public TestRazorProject(IRazorViewEngineFileProviderAccessor fileProviderAccessor)
|
||||
: base(fileProviderAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
public override RazorCodeDocument CreateCodeDocument(string relativePath, Stream inputStream)
|
||||
public override RazorProjectItem GetItem(string path)
|
||||
{
|
||||
// Normalize line endings to '\r\n' (CRLF). This removes core.autocrlf, core.eol, core.safecrlf, and
|
||||
// .gitattributes from the equation and treats "\r\n" and "\n" as equivalent. Does not handle
|
||||
// some line endings like "\r" but otherwise ensures checksums and line mappings are consistent.
|
||||
string text;
|
||||
using (var streamReader = new StreamReader(inputStream))
|
||||
var item = (DefaultRazorProjectItem)base.GetItem(path);
|
||||
return new TestRazorProjectItem(item);
|
||||
}
|
||||
|
||||
private class TestRazorProjectItem : DefaultRazorProjectItem
|
||||
{
|
||||
public TestRazorProjectItem(DefaultRazorProjectItem projectItem)
|
||||
: base(projectItem.FileInfo, projectItem.BasePath, projectItem.Path)
|
||||
{
|
||||
text = streamReader.ReadToEnd().Replace("\r", "").Replace("\n", "\r\n");
|
||||
}
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(text);
|
||||
inputStream = new MemoryStream(bytes);
|
||||
public override Stream Read()
|
||||
{
|
||||
// Normalize line endings to '\r\n' (CRLF). This removes core.autocrlf, core.eol, core.safecrlf, and
|
||||
// .gitattributes from the equation and treats "\r\n" and "\n" as equivalent. Does not handle
|
||||
// some line endings like "\r" but otherwise ensures checksums and line mappings are consistent.
|
||||
string text;
|
||||
using (var streamReader = new StreamReader(base.Read()))
|
||||
{
|
||||
text = streamReader.ReadToEnd().Replace("\r", "").Replace("\n", "\r\n");
|
||||
}
|
||||
|
||||
return base.CreateCodeDocument(relativePath, inputStream);
|
||||
var bytes = Encoding.UTF8.GetBytes(text);
|
||||
return new MemoryStream(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue