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
|
// In the default scenario the following services are singleton by virtue of being initialized as part of
|
||||||
// creating the singleton RazorViewEngine instance.
|
// creating the singleton RazorViewEngine instance.
|
||||||
services.TryAddTransient<IRazorPageFactoryProvider, DefaultRazorPageFactoryProvider>();
|
services.TryAddTransient<IRazorPageFactoryProvider, DefaultRazorPageFactoryProvider>();
|
||||||
services.TryAddTransient<IRazorCompilationService, RazorCompilationService>();
|
|
||||||
|
|
||||||
services.TryAddSingleton<RazorProject>(s =>
|
services.TryAddSingleton<RazorProject, DefaultRazorProject>();
|
||||||
{
|
|
||||||
return new DefaultRazorProject(s.GetRequiredService<IRazorViewEngineFileProviderAccessor>().FileProvider);
|
|
||||||
});
|
|
||||||
|
|
||||||
services.TryAddSingleton<RazorEngine>(s =>
|
services.TryAddSingleton<RazorEngine>(s =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -67,16 +67,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public CompilerCacheResult GetOrAdd(
|
public CompilerCacheResult GetOrAdd(
|
||||||
string relativePath,
|
string relativePath,
|
||||||
Func<RelativeFileInfo, CompilationResult> compile)
|
Func<string, CompilerCacheContext> cacheContextFactory)
|
||||||
{
|
{
|
||||||
if (relativePath == null)
|
if (relativePath == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(relativePath));
|
throw new ArgumentNullException(nameof(relativePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compile == null)
|
if (cacheContextFactory == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(compile));
|
throw new ArgumentNullException(nameof(cacheContextFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
Task<CompilerCacheResult> cacheEntry;
|
Task<CompilerCacheResult> cacheEntry;
|
||||||
|
|
@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var normalizedPath = GetNormalizedPath(relativePath);
|
var normalizedPath = GetNormalizedPath(relativePath);
|
||||||
if (!_cache.TryGetValue(normalizedPath, out cacheEntry))
|
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(
|
private Task<CompilerCacheResult> CreateCacheEntry(
|
||||||
string relativePath,
|
|
||||||
string normalizedPath,
|
string normalizedPath,
|
||||||
Func<RelativeFileInfo, CompilationResult> compile)
|
Func<string, CompilerCacheContext> cacheContextFactory)
|
||||||
{
|
{
|
||||||
TaskCompletionSource<CompilerCacheResult> compilationTaskSource = null;
|
TaskCompletionSource<CompilerCacheResult> compilationTaskSource = null;
|
||||||
MemoryCacheEntryOptions cacheEntryOptions = null;
|
MemoryCacheEntryOptions cacheEntryOptions;
|
||||||
IFileInfo fileInfo = null;
|
|
||||||
Task<CompilerCacheResult> cacheEntry;
|
Task<CompilerCacheResult> cacheEntry;
|
||||||
|
CompilerCacheContext compilerCacheContext;
|
||||||
|
|
||||||
// Safe races cannot be allowed when compiling Razor pages. To ensure only one compilation request succeeds
|
// 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
|
// 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);
|
throw new InvalidOperationException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
fileInfo = _fileProvider.GetFileInfo(normalizedPath);
|
cacheEntryOptions = new MemoryCacheEntryOptions();
|
||||||
if (!fileInfo.Exists)
|
|
||||||
{
|
|
||||||
var expirationToken = _fileProvider.Watch(normalizedPath);
|
|
||||||
cacheEntry = Task.FromResult(new CompilerCacheResult(new[] { expirationToken }));
|
|
||||||
|
|
||||||
cacheEntryOptions = new MemoryCacheEntryOptions();
|
compilerCacheContext = cacheContextFactory(normalizedPath);
|
||||||
cacheEntryOptions.AddExpirationToken(expirationToken);
|
cacheEntryOptions.ExpirationTokens.Add(_fileProvider.Watch(compilerCacheContext.ProjectItem.Path));
|
||||||
|
if (!compilerCacheContext.ProjectItem.Exists)
|
||||||
|
{
|
||||||
|
cacheEntry = Task.FromResult(new CompilerCacheResult(normalizedPath, cacheEntryOptions.ExpirationTokens));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cacheEntryOptions = GetMemoryCacheEntryOptions(normalizedPath);
|
|
||||||
|
|
||||||
// A file exists and needs to be compiled.
|
// A file exists and needs to be compiled.
|
||||||
compilationTaskSource = new TaskCompletionSource<CompilerCacheResult>();
|
compilationTaskSource = new TaskCompletionSource<CompilerCacheResult>();
|
||||||
|
foreach (var projectItem in compilerCacheContext.AdditionalCompilationItems)
|
||||||
|
{
|
||||||
|
cacheEntryOptions.ExpirationTokens.Add(_fileProvider.Watch(projectItem.Path));
|
||||||
|
}
|
||||||
cacheEntry = compilationTaskSource.Task;
|
cacheEntry = compilationTaskSource.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheEntry = _cache.Set<Task<CompilerCacheResult>>(normalizedPath, cacheEntry, cacheEntryOptions);
|
cacheEntry = _cache.Set(normalizedPath, cacheEntry, cacheEntryOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compilationTaskSource != null)
|
if (compilationTaskSource != null)
|
||||||
{
|
{
|
||||||
// Indicates that the file was found and needs to be compiled.
|
// Indicates that a file was found and needs to be compiled.
|
||||||
Debug.Assert(fileInfo != null && fileInfo.Exists);
|
|
||||||
Debug.Assert(cacheEntryOptions != null);
|
Debug.Assert(cacheEntryOptions != null);
|
||||||
var relativeFileInfo = new RelativeFileInfo(fileInfo, normalizedPath);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var compilationResult = compile(relativeFileInfo);
|
var compilationResult = compilerCacheContext.Compile(compilerCacheContext);
|
||||||
compilationResult.EnsureSuccessful();
|
compilationResult.EnsureSuccessful();
|
||||||
compilationTaskSource.SetResult(
|
compilationTaskSource.SetResult(
|
||||||
new CompilerCacheResult(relativePath, compilationResult, cacheEntryOptions.ExpirationTokens));
|
new CompilerCacheResult(normalizedPath, compilationResult, cacheEntryOptions.ExpirationTokens));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -169,20 +167,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
return cacheEntry;
|
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)
|
private string GetNormalizedPath(string relativePath)
|
||||||
{
|
{
|
||||||
Debug.Assert(relativePath != null);
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq.Expressions;
|
|
||||||
using System.Reflection;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
@ -18,21 +16,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of <see cref="CompilerCacheResult"/> with the specified
|
/// Initializes a new instance of <see cref="CompilerCacheResult"/> with the specified
|
||||||
/// <see cref="Compilation.CompilationResult"/>.
|
/// <see cref="CompilationResult"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="relativePath">Path of the view file relative to the application base.</param>
|
/// <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>
|
||||||
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="isPrecompiled"><c>true</c> if the view is precompiled, <c>false</c> otherwise.</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)
|
public CompilerCacheResult(string relativePath, CompilationResult compilationResult, bool isPrecompiled)
|
||||||
: this(relativePath, compilationResult, EmptyArray<IChangeToken>.Instance)
|
: this(relativePath, compilationResult, EmptyArray<IChangeToken>.Instance)
|
||||||
|
|
@ -42,10 +29,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of <see cref="CompilerCacheResult"/> with the specified
|
/// Initializes a new instance of <see cref="CompilerCacheResult"/> with the specified
|
||||||
/// <see cref="Compilation.CompilationResult"/>.
|
/// <see cref="CompilationResult"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="relativePath">Path of the view file relative to the application base.</param>
|
/// <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
|
/// <param name="expirationTokens">One or more <see cref="IChangeToken"/> instances that indicate when
|
||||||
/// this result has expired.</param>
|
/// this result has expired.</param>
|
||||||
public CompilerCacheResult(string relativePath, CompilationResult compilationResult, IList<IChangeToken> expirationTokens)
|
public CompilerCacheResult(string relativePath, CompilationResult compilationResult, IList<IChangeToken> expirationTokens)
|
||||||
|
|
@ -55,18 +42,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
throw new ArgumentNullException(nameof(expirationTokens));
|
throw new ArgumentNullException(nameof(expirationTokens));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RelativePath = relativePath;
|
||||||
|
CompiledType = compilationResult.CompiledType;
|
||||||
ExpirationTokens = expirationTokens;
|
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;
|
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
|
/// Initializes a new instance of <see cref="CompilerCacheResult"/> for a file that could not be
|
||||||
/// found in the file system.
|
/// found in the file system.
|
||||||
/// </summary>
|
/// </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
|
/// <param name="expirationTokens">One or more <see cref="IChangeToken"/> instances that indicate when
|
||||||
/// this result has expired.</param>
|
/// this result has expired.</param>
|
||||||
public CompilerCacheResult(IList<IChangeToken> expirationTokens)
|
public CompilerCacheResult(string relativePath, IList<IChangeToken> expirationTokens)
|
||||||
{
|
{
|
||||||
if (expirationTokens == null)
|
if (expirationTokens == null)
|
||||||
{
|
{
|
||||||
|
|
@ -84,7 +63,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
ExpirationTokens = expirationTokens;
|
ExpirationTokens = expirationTokens;
|
||||||
PageFactory = null;
|
RelativePath = null;
|
||||||
|
CompiledType = null;
|
||||||
IsPrecompiled = false;
|
IsPrecompiled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,12 +76,17 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value that determines if the view was successfully found and compiled.
|
/// Gets a value that determines if the view was successfully found and compiled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Success => PageFactory != null;
|
public bool Success => CompiledType != null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a delegate that creates an instance of the <see cref="IRazorPage"/>.
|
/// Normalized relative path of the file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<IRazorPage> PageFactory { get; }
|
public string RelativePath { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The compiled <see cref="Type"/>.
|
||||||
|
/// </summary>
|
||||||
|
public Type CompiledType { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value that determines if the view is precompiled.
|
/// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
{
|
{
|
||||||
|
|
@ -13,37 +15,26 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DefaultRazorPageFactoryProvider : IRazorPageFactoryProvider
|
public class DefaultRazorPageFactoryProvider : IRazorPageFactoryProvider
|
||||||
{
|
{
|
||||||
/// <remarks>
|
private const string ViewImportsFileName = "_ViewImports.cshtml";
|
||||||
/// This delegate holds on to an instance of <see cref="IRazorCompilationService"/>.
|
private readonly RazorCompiler _razorCompiler;
|
||||||
/// </remarks>
|
|
||||||
private readonly Func<RelativeFileInfo, CompilationResult> _compileDelegate;
|
|
||||||
private readonly ICompilerCacheProvider _compilerCacheProvider;
|
|
||||||
private ICompilerCache _compilerCache;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of <see cref="DefaultRazorPageFactoryProvider"/>.
|
/// Initializes a new instance of <see cref="DefaultRazorPageFactoryProvider"/>.
|
||||||
/// </summary>
|
/// </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>
|
/// <param name="compilerCacheProvider">The <see cref="ICompilerCacheProvider"/>.</param>
|
||||||
public DefaultRazorPageFactoryProvider(
|
public DefaultRazorPageFactoryProvider(
|
||||||
IRazorCompilationService razorCompilationService,
|
RazorEngine razorEngine,
|
||||||
|
RazorProject razorProject,
|
||||||
|
ICompilationService compilationService,
|
||||||
ICompilerCacheProvider compilerCacheProvider)
|
ICompilerCacheProvider compilerCacheProvider)
|
||||||
{
|
{
|
||||||
_compileDelegate = razorCompilationService.Compile;
|
var templateEngine = new MvcRazorTemplateEngine(razorEngine, razorProject);
|
||||||
_compilerCacheProvider = compilerCacheProvider;
|
templateEngine.Options.ImportsFileName = ViewImportsFileName;
|
||||||
}
|
|
||||||
|
|
||||||
private ICompilerCache CompilerCache
|
_razorCompiler = new RazorCompiler(compilationService, compilerCacheProvider, templateEngine);
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_compilerCache == null)
|
|
||||||
{
|
|
||||||
_compilerCache = _compilerCacheProvider.Cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _compilerCache;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <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.
|
// For tilde slash paths, drop the leading ~ to make it work with the underlying IFileProvider.
|
||||||
relativePath = relativePath.Substring(1);
|
relativePath = relativePath.Substring(1);
|
||||||
}
|
}
|
||||||
var result = CompilerCache.GetOrAdd(relativePath, _compileDelegate);
|
|
||||||
|
var result = _razorCompiler.Compile(relativePath);
|
||||||
if (result.Success)
|
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
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
private const string RazorFileExtension = ".cshtml";
|
private const string RazorFileExtension = ".cshtml";
|
||||||
private readonly IFileProvider _provider;
|
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;
|
_provider = provider;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
/// <returns>A cached <see cref="CompilationResult"/>.</returns>
|
/// <returns>A cached <see cref="CompilationResult"/>.</returns>
|
||||||
CompilerCacheResult GetOrAdd(
|
CompilerCacheResult GetOrAdd(
|
||||||
string relativePath,
|
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);
|
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>
|
/// <summary>
|
||||||
/// The layout view '{0}' could not be located. The following locations were searched:{1}
|
/// The layout view '{0}' could not be located. The following locations were searched:{1}
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -314,70 +298,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("ViewContextMustBeSet"), p0, p1);
|
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>
|
/// <summary>
|
||||||
/// Generated Code
|
/// Generated Code
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -526,6 +446,22 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
return GetString("RazorProject_PathMustStartWithForwardSlash");
|
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)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||||
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
@ -30,6 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
public class RazorViewEngine : IRazorViewEngine
|
public class RazorViewEngine : IRazorViewEngine
|
||||||
{
|
{
|
||||||
public static readonly string ViewExtension = ".cshtml";
|
public static readonly string ViewExtension = ".cshtml";
|
||||||
|
private const string ViewStartFileName = "_ViewStart.cshtml";
|
||||||
|
|
||||||
private const string ControllerKey = "controller";
|
private const string ControllerKey = "controller";
|
||||||
private const string AreaKey = "area";
|
private const string AreaKey = "area";
|
||||||
|
|
@ -42,6 +44,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
private readonly HtmlEncoder _htmlEncoder;
|
private readonly HtmlEncoder _htmlEncoder;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly RazorViewEngineOptions _options;
|
private readonly RazorViewEngineOptions _options;
|
||||||
|
private readonly RazorProject _razorProject;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RazorViewEngine" />.
|
/// Initializes a new instance of the <see cref="RazorViewEngine" />.
|
||||||
|
|
@ -51,6 +54,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
IRazorPageActivator pageActivator,
|
IRazorPageActivator pageActivator,
|
||||||
HtmlEncoder htmlEncoder,
|
HtmlEncoder htmlEncoder,
|
||||||
IOptions<RazorViewEngineOptions> optionsAccessor,
|
IOptions<RazorViewEngineOptions> optionsAccessor,
|
||||||
|
RazorProject razorProject,
|
||||||
ILoggerFactory loggerFactory)
|
ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
_options = optionsAccessor.Value;
|
_options = optionsAccessor.Value;
|
||||||
|
|
@ -73,6 +77,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
_pageActivator = pageActivator;
|
_pageActivator = pageActivator;
|
||||||
_htmlEncoder = htmlEncoder;
|
_htmlEncoder = htmlEncoder;
|
||||||
_logger = loggerFactory.CreateLogger<RazorViewEngine>();
|
_logger = loggerFactory.CreateLogger<RazorViewEngine>();
|
||||||
|
_razorProject = razorProject;
|
||||||
ViewLookupCache = new MemoryCache(new MemoryCacheOptions
|
ViewLookupCache = new MemoryCache(new MemoryCacheOptions
|
||||||
{
|
{
|
||||||
CompactOnMemoryPressure = false
|
CompactOnMemoryPressure = false
|
||||||
|
|
@ -483,10 +488,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
string path,
|
string path,
|
||||||
HashSet<IChangeToken> expirationTokens)
|
HashSet<IChangeToken> expirationTokens)
|
||||||
{
|
{
|
||||||
|
var applicationRelativePath = MakePathApplicationRelative(path);
|
||||||
var viewStartPages = new List<ViewLocationCacheItem>();
|
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)
|
if (result.ExpirationTokens != null)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < result.ExpirationTokens.Count; i++)
|
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
|
// 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
|
// executed (closest last, furthest first). This is the reverse order in which
|
||||||
// ViewHierarchyUtility.GetViewStartLocations returns _ViewStarts.
|
// 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] == '/';
|
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)
|
private static bool IsRelativePath(string name)
|
||||||
{
|
{
|
||||||
Debug.Assert(!string.IsNullOrEmpty(name));
|
Debug.Assert(!string.IsNullOrEmpty(name));
|
||||||
|
|
|
||||||
|
|
@ -126,9 +126,6 @@
|
||||||
<data name="FlushPointCannotBeInvoked" xml:space="preserve">
|
<data name="FlushPointCannotBeInvoked" xml:space="preserve">
|
||||||
<value>'{0}' cannot be invoked when a Layout page is set to be executed.</value>
|
<value>'{0}' cannot be invoked when a Layout page is set to be executed.</value>
|
||||||
</data>
|
</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">
|
<data name="LayoutCannotBeLocated" xml:space="preserve">
|
||||||
<value>The layout view '{0}' could not be located. The following locations were searched:{1}</value>
|
<value>The layout view '{0}' could not be located. The following locations were searched:{1}</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
@ -174,18 +171,6 @@
|
||||||
<data name="ViewContextMustBeSet" xml:space="preserve">
|
<data name="ViewContextMustBeSet" xml:space="preserve">
|
||||||
<value>'{0} must be set to access '{1}'.</value>
|
<value>'{0} must be set to access '{1}'.</value>
|
||||||
</data>
|
</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">
|
<data name="GeneratedCodeFileName" xml:space="preserve">
|
||||||
<value>Generated Code</value>
|
<value>Generated Code</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
@ -215,4 +200,7 @@
|
||||||
<data name="RazorProject_PathMustStartWithForwardSlash" xml:space="preserve">
|
<data name="RazorProject_PathMustStartWithForwardSlash" xml:space="preserve">
|
||||||
<value>Path must begin with a forward slash '/'.</value>
|
<value>Path must begin with a forward slash '/'.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="PropertyMustBeSet" xml:space="preserve">
|
||||||
|
<value>The property '{0}' of '{1}' must not be null.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</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
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="CompiledPageActionDescriptor"/> from a <see cref="PageActionDescriptor"/>.
|
||||||
|
/// </summary>
|
||||||
public interface IPageLoader
|
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);
|
CompiledPageActionDescriptor Load(PageActionDescriptor actionDescriptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,123 +1,51 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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 System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Razor.Evolution;
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
{
|
{
|
||||||
public class DefaultPageLoader : IPageLoader
|
public class DefaultPageLoader : IPageLoader
|
||||||
{
|
{
|
||||||
|
private const string PageImportsFileName = "_PageImports.cshtml";
|
||||||
private const string ModelPropertyName = "Model";
|
private const string ModelPropertyName = "Model";
|
||||||
private readonly RazorCompilationService _razorCompilationService;
|
|
||||||
private readonly ICompilationService _compilationService;
|
private readonly MvcRazorTemplateEngine _templateEngine;
|
||||||
private readonly RazorProject _project;
|
private readonly RazorCompiler _razorCompiler;
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
public DefaultPageLoader(
|
public DefaultPageLoader(
|
||||||
IRazorCompilationService razorCompilationService,
|
RazorEngine razorEngine,
|
||||||
ICompilationService compilationService,
|
|
||||||
RazorProject razorProject,
|
RazorProject razorProject,
|
||||||
ILogger<DefaultPageLoader> logger)
|
ICompilationService compilationService,
|
||||||
|
ICompilerCacheProvider compilerCacheProvider)
|
||||||
{
|
{
|
||||||
_razorCompilationService = (RazorCompilationService)razorCompilationService;
|
_templateEngine = new MvcRazorTemplateEngine(razorEngine, razorProject);
|
||||||
_compilationService = compilationService;
|
_templateEngine.Options.ImportsFileName = PageImportsFileName;
|
||||||
_project = razorProject;
|
_razorCompiler = new RazorCompiler(compilationService, compilerCacheProvider, _templateEngine);
|
||||||
_logger = logger;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompiledPageActionDescriptor Load(PageActionDescriptor actionDescriptor)
|
public CompiledPageActionDescriptor Load(PageActionDescriptor actionDescriptor)
|
||||||
{
|
{
|
||||||
var item = _project.GetItem(actionDescriptor.RelativePath);
|
var compilationResult = _razorCompiler.Compile(actionDescriptor.RelativePath);
|
||||||
if (!item.Exists)
|
var compiledTypeInfo = compilationResult.CompiledType.GetTypeInfo();
|
||||||
{
|
|
||||||
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();
|
|
||||||
|
|
||||||
// If a model type wasn't set in code then the model property's type will be the same
|
// If a model type wasn't set in code then the model property's type will be the same
|
||||||
// as the compiled type.
|
// as the compiled type.
|
||||||
var pageType = compilationResult.CompiledType.GetTypeInfo();
|
var modelTypeInfo = compiledTypeInfo.GetProperty(ModelPropertyName)?.PropertyType.GetTypeInfo();
|
||||||
var modelType = pageType.GetProperty(ModelPropertyName)?.PropertyType.GetTypeInfo();
|
if (modelTypeInfo == compiledTypeInfo)
|
||||||
if (modelType == pageType)
|
|
||||||
{
|
{
|
||||||
modelType = null;
|
modelTypeInfo = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CompiledPageActionDescriptor(actionDescriptor)
|
return new CompiledPageActionDescriptor(actionDescriptor)
|
||||||
{
|
{
|
||||||
ModelTypeInfo = modelType,
|
PageTypeInfo = compiledTypeInfo,
|
||||||
PageTypeInfo = pageType,
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Diagnostics;
|
using Microsoft.AspNetCore.Diagnostics;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -17,18 +19,30 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
{
|
{
|
||||||
private const string ViewPath = "/Views/Home/Index.cshtml";
|
private const string ViewPath = "/Views/Home/Index.cshtml";
|
||||||
private const string PrecompiledViewsPath = "/Views/Home/Precompiled.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>
|
private readonly IDictionary<string, Type> _precompiledViews = new Dictionary<string, Type>
|
||||||
{
|
{
|
||||||
{ PrecompiledViewsPath, typeof(PreCompile) }
|
{ PrecompiledViewsPath, typeof(PreCompile) }
|
||||||
};
|
};
|
||||||
|
|
||||||
public static TheoryData ViewImportsPaths =>
|
public static TheoryData ViewImportsPaths
|
||||||
new TheoryData<string>
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
"/Views/Home/_ViewImports.cshtml",
|
var theoryData = new TheoryData<string>();
|
||||||
"/Views/_ViewImports.cshtml",
|
foreach (var path in _viewImportsPath)
|
||||||
"/_ViewImports.cshtml",
|
{
|
||||||
};
|
theoryData.Add(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return theoryData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetOrAdd_ReturnsFileNotFoundResult_IfFileIsNotFoundInFileSystem()
|
public void GetOrAdd_ReturnsFileNotFoundResult_IfFileIsNotFoundInFileSystem()
|
||||||
|
|
@ -36,9 +50,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
// Arrange
|
// Arrange
|
||||||
var fileProvider = new TestFileProvider();
|
var fileProvider = new TestFileProvider();
|
||||||
var cache = new CompilerCache(fileProvider);
|
var cache = new CompilerCache(fileProvider);
|
||||||
|
var compilerCacheContext = new CompilerCacheContext(
|
||||||
|
new NotFoundProjectItem("", "/path"),
|
||||||
|
Enumerable.Empty<RazorProjectItem>(),
|
||||||
|
_ => throw new Exception("Shouldn't be called."));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = cache.GetOrAdd("/some/path", ThrowsIfCalled);
|
var result = cache.GetOrAdd("/some/path", _ => compilerCacheContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result.Success);
|
Assert.False(result.Success);
|
||||||
|
|
@ -54,12 +72,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var expected = new CompilationResult(typeof(TestView));
|
var expected = new CompilationResult(typeof(TestView));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = cache.GetOrAdd(ViewPath, _ => expected);
|
var result = cache.GetOrAdd(ViewPath, CreateContextFactory(expected));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result.Success);
|
Assert.True(result.Success);
|
||||||
Assert.IsType<TestView>(result.PageFactory());
|
Assert.Equal(typeof(TestView), result.CompiledType);
|
||||||
Assert.Same(ViewPath, result.PageFactory().Path);
|
Assert.Equal(ViewPath, result.RelativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -77,17 +95,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var expected = new CompilationResult(typeof(TestView));
|
var expected = new CompilationResult(typeof(TestView));
|
||||||
|
|
||||||
// Act - 1
|
// 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 - 1
|
||||||
Assert.IsType<TestView>(result1.PageFactory());
|
Assert.Equal(typeof(TestView), result1.CompiledType);
|
||||||
|
|
||||||
// Act - 2
|
// Act - 2
|
||||||
var result2 = cache.GetOrAdd(relativePath, ThrowsIfCalled);
|
var result2 = cache.GetOrAdd(relativePath, ThrowsIfCalled);
|
||||||
|
|
||||||
// Assert - 2
|
// Assert - 2
|
||||||
Assert.IsType<TestView>(result2.PageFactory());
|
Assert.Equal(typeof(TestView), result2.CompiledType);
|
||||||
Assert.Same(result1.PageFactory, result2.PageFactory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -95,22 +112,27 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var fileProvider = new TestFileProvider();
|
var fileProvider = new TestFileProvider();
|
||||||
fileProvider.AddFile(ViewPath, "some content");
|
var fileInfo = fileProvider.AddFile(ViewPath, "some content");
|
||||||
var cache = new CompilerCache(fileProvider);
|
var cache = new CompilerCache(fileProvider);
|
||||||
var expected = new CompilationResult(typeof(TestView));
|
var expected = new CompilationResult(typeof(TestView));
|
||||||
|
var projectItem = new DefaultRazorProjectItem(fileInfo, "", ViewPath);
|
||||||
|
var cacheContext = new CompilerCacheContext(projectItem, Enumerable.Empty<RazorProjectItem>(), _ => expected);
|
||||||
|
|
||||||
// Act 1
|
// Act 1
|
||||||
var result1 = cache.GetOrAdd(ViewPath, _ => expected);
|
var result1 = cache.GetOrAdd(ViewPath, _ => cacheContext);
|
||||||
|
|
||||||
// Assert 1
|
// Assert 1
|
||||||
Assert.True(result1.Success);
|
Assert.True(result1.Success);
|
||||||
Assert.IsType<TestView>(result1.PageFactory());
|
Assert.Equal(typeof(TestView), result1.CompiledType);
|
||||||
|
|
||||||
// Act 2
|
// Act 2
|
||||||
// Delete the file from the file system and set it's expiration token.
|
// 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;
|
fileProvider.GetChangeToken(ViewPath).HasChanged = true;
|
||||||
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
|
var result2 = cache.GetOrAdd(ViewPath, _ => cacheContext);
|
||||||
|
|
||||||
// Assert 2
|
// Assert 2
|
||||||
Assert.False(result2.Success);
|
Assert.False(result2.Success);
|
||||||
|
|
@ -127,11 +149,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var expected2 = new CompilationResult(typeof(DifferentView));
|
var expected2 = new CompilationResult(typeof(DifferentView));
|
||||||
|
|
||||||
// Act 1
|
// Act 1
|
||||||
var result1 = cache.GetOrAdd(ViewPath, _ => expected1);
|
var result1 = cache.GetOrAdd(ViewPath, CreateContextFactory(expected1));
|
||||||
|
|
||||||
// Assert 1
|
// Assert 1
|
||||||
Assert.True(result1.Success);
|
Assert.True(result1.Success);
|
||||||
Assert.IsType<TestView>(result1.PageFactory());
|
Assert.Equal(typeof(TestView), result1.CompiledType);
|
||||||
|
|
||||||
// Act 2
|
// Act 2
|
||||||
// Verify we're getting cached results.
|
// Verify we're getting cached results.
|
||||||
|
|
@ -139,15 +161,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
|
|
||||||
// Assert 2
|
// Assert 2
|
||||||
Assert.True(result2.Success);
|
Assert.True(result2.Success);
|
||||||
Assert.IsType<TestView>(result2.PageFactory());
|
Assert.Equal(typeof(TestView), result1.CompiledType);
|
||||||
|
|
||||||
// Act 3
|
// Act 3
|
||||||
fileProvider.GetChangeToken(ViewPath).HasChanged = true;
|
fileProvider.GetChangeToken(ViewPath).HasChanged = true;
|
||||||
var result3 = cache.GetOrAdd(ViewPath, _ => expected2);
|
var result3 = cache.GetOrAdd(ViewPath, CreateContextFactory(expected2));
|
||||||
|
|
||||||
// Assert 3
|
// Assert 3
|
||||||
Assert.True(result3.Success);
|
Assert.True(result3.Success);
|
||||||
Assert.IsType<DifferentView>(result3.PageFactory());
|
Assert.Equal(typeof(DifferentView), result3.CompiledType);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -162,11 +184,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var expected2 = new CompilationResult(typeof(DifferentView));
|
var expected2 = new CompilationResult(typeof(DifferentView));
|
||||||
|
|
||||||
// Act 1
|
// Act 1
|
||||||
var result1 = cache.GetOrAdd(ViewPath, _ => expected1);
|
var result1 = cache.GetOrAdd(ViewPath, CreateContextFactory(expected1));
|
||||||
|
|
||||||
// Assert 1
|
// Assert 1
|
||||||
Assert.True(result1.Success);
|
Assert.True(result1.Success);
|
||||||
Assert.IsType<TestView>(result1.PageFactory());
|
Assert.Equal(typeof(TestView), result1.CompiledType);
|
||||||
|
|
||||||
// Act 2
|
// Act 2
|
||||||
// Verify we're getting cached results.
|
// Verify we're getting cached results.
|
||||||
|
|
@ -174,15 +196,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
|
|
||||||
// Assert 2
|
// Assert 2
|
||||||
Assert.True(result2.Success);
|
Assert.True(result2.Success);
|
||||||
Assert.IsType<TestView>(result2.PageFactory());
|
Assert.Equal(typeof(TestView), result1.CompiledType);
|
||||||
|
|
||||||
// Act 3
|
// Act 3
|
||||||
fileProvider.GetChangeToken(globalImportPath).HasChanged = true;
|
fileProvider.GetChangeToken(globalImportPath).HasChanged = true;
|
||||||
var result3 = cache.GetOrAdd(ViewPath, _ => expected2);
|
var result3 = cache.GetOrAdd(ViewPath, CreateContextFactory(expected2));
|
||||||
|
|
||||||
// Assert 2
|
// Assert 2
|
||||||
Assert.True(result3.Success);
|
Assert.True(result3.Success);
|
||||||
Assert.IsType<DifferentView>(result3.PageFactory());
|
Assert.Equal(typeof(DifferentView), result3.CompiledType);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -196,19 +218,17 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var expected = new CompilationResult(typeof(TestView));
|
var expected = new CompilationResult(typeof(TestView));
|
||||||
|
|
||||||
// Act 1
|
// Act 1
|
||||||
var result1 = cache.GetOrAdd(ViewPath, _ => expected);
|
var result1 = cache.GetOrAdd(ViewPath, CreateContextFactory(expected));
|
||||||
|
|
||||||
// Assert 1
|
// Assert 1
|
||||||
Assert.True(result1.Success);
|
Assert.True(result1.Success);
|
||||||
Assert.IsType<TestView>(result1.PageFactory());
|
Assert.Equal(typeof(TestView), result1.CompiledType);
|
||||||
|
|
||||||
// Act 2
|
// Act 2
|
||||||
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
|
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
|
||||||
|
|
||||||
// Assert 2
|
// Assert 2
|
||||||
Assert.True(result2.Success);
|
Assert.True(result2.Success);
|
||||||
Assert.IsType<TestView>(result2.PageFactory());
|
|
||||||
mockFileProvider.Verify(v => v.GetFileInfo(ViewPath), Times.Once());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -223,8 +243,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result.Success);
|
Assert.True(result.Success);
|
||||||
Assert.IsType<PreCompile>(result.PageFactory());
|
Assert.Equal(typeof(PreCompile), result.CompiledType);
|
||||||
Assert.Same(PrecompiledViewsPath, result.PageFactory().Path);
|
Assert.Same(PrecompiledViewsPath, result.RelativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -242,7 +262,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result.Success);
|
Assert.True(result.Success);
|
||||||
Assert.True(result.IsPrecompiled);
|
Assert.True(result.IsPrecompiled);
|
||||||
Assert.IsType<PreCompile>(result.PageFactory());
|
Assert.Equal(typeof(PreCompile), result.CompiledType);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -260,11 +280,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result.Success);
|
Assert.True(result.Success);
|
||||||
Assert.IsType<PreCompile>(result.PageFactory());
|
Assert.Equal(typeof(PreCompile), result.CompiledType);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetOrAdd_ReturnsRuntimeCompiledAndPrecompiledViews()
|
public void GetOrAdd_ReturnsRuntimeCompiled()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var fileProvider = new TestFileProvider();
|
var fileProvider = new TestFileProvider();
|
||||||
|
|
@ -273,24 +293,32 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var expected = new CompilationResult(typeof(TestView));
|
var expected = new CompilationResult(typeof(TestView));
|
||||||
|
|
||||||
// Act 1
|
// Act 1
|
||||||
var result1 = cache.GetOrAdd(ViewPath, _ => expected);
|
var result1 = cache.GetOrAdd(ViewPath, CreateContextFactory(expected));
|
||||||
|
|
||||||
// Assert 1
|
// Assert 1
|
||||||
Assert.IsType<TestView>(result1.PageFactory());
|
Assert.Equal(typeof(TestView), result1.CompiledType);
|
||||||
|
|
||||||
// Act 2
|
// Act 2
|
||||||
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
|
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
|
||||||
|
|
||||||
// Assert 2
|
// Assert 2
|
||||||
Assert.True(result2.Success);
|
Assert.True(result2.Success);
|
||||||
Assert.IsType<TestView>(result2.PageFactory());
|
Assert.Equal(typeof(TestView), result2.CompiledType);
|
||||||
|
}
|
||||||
|
|
||||||
// Act 3
|
[Fact]
|
||||||
var result3 = cache.GetOrAdd(PrecompiledViewsPath, ThrowsIfCalled);
|
public void GetOrAdd_ReturnsPrecompiledViews()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var fileProvider = new TestFileProvider();
|
||||||
|
var cache = new CompilerCache(fileProvider, _precompiledViews);
|
||||||
|
var expected = new CompilationResult(typeof(TestView));
|
||||||
|
|
||||||
// Assert 3
|
// Act
|
||||||
Assert.True(result2.Success);
|
var result1 = cache.GetOrAdd(PrecompiledViewsPath, ThrowsIfCalled);
|
||||||
Assert.IsType<PreCompile>(result3.PageFactory());
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(typeof(PreCompile), result1.CompiledType);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -314,8 +342,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var result = cache.GetOrAdd(relativePath, ThrowsIfCalled);
|
var result = cache.GetOrAdd(relativePath, ThrowsIfCalled);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsType<PreCompile>(result.PageFactory());
|
Assert.Equal(typeof(PreCompile), result.CompiledType);
|
||||||
Assert.Same(viewPath, result.PageFactory().Path);
|
Assert.Equal(viewPath, result.RelativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -337,7 +365,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var result = cache.GetOrAdd("/Areas/Finances/Views/Home/Index.cshtml", ThrowsIfCalled);
|
var result = cache.GetOrAdd("/Areas/Finances/Views/Home/Index.cshtml", ThrowsIfCalled);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsType<PreCompile>(result.PageFactory());
|
Assert.Equal(typeof(PreCompile), result.CompiledType);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -354,42 +382,55 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var compilingOne = false;
|
var compilingOne = false;
|
||||||
var compilingTwo = 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
|
// Act
|
||||||
var task1 = Task.Run(() =>
|
var task1 = Task.Run(() =>
|
||||||
{
|
{
|
||||||
return cache.GetOrAdd("/Views/Home/Index.cshtml", file =>
|
return cache.GetOrAdd("/Views/Home/Index.cshtml", path =>
|
||||||
{
|
{
|
||||||
compilingOne = true;
|
var projectItem = new DefaultRazorProjectItem(new TestFileInfo(), "", path);
|
||||||
|
return new CompilerCacheContext(projectItem, Enumerable.Empty<RazorProjectItem>(), compile1);
|
||||||
// Event 2
|
|
||||||
resetEvent1.WaitOne(waitDuration);
|
|
||||||
|
|
||||||
// Event 3
|
|
||||||
resetEvent2.Set();
|
|
||||||
|
|
||||||
// Event 6
|
|
||||||
resetEvent1.WaitOne(waitDuration);
|
|
||||||
|
|
||||||
Assert.True(compilingTwo);
|
|
||||||
return new CompilationResult(typeof(TestView));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var task2 = Task.Run(() =>
|
var task2 = Task.Run(() =>
|
||||||
{
|
{
|
||||||
// Event 4
|
// Event 4
|
||||||
return cache.GetOrAdd("/Views/Home/About.cshtml", file =>
|
return cache.GetOrAdd("/Views/Home/About.cshtml", path =>
|
||||||
{
|
{
|
||||||
compilingTwo = true;
|
var projectItem = new DefaultRazorProjectItem(new TestFileInfo(), "", path);
|
||||||
|
return new CompilerCacheContext(projectItem, Enumerable.Empty<RazorProjectItem>(), compile2);
|
||||||
// Event 4
|
|
||||||
resetEvent2.WaitOne(waitDuration);
|
|
||||||
|
|
||||||
// Event 5
|
|
||||||
resetEvent1.Set();
|
|
||||||
|
|
||||||
Assert.True(compilingOne);
|
|
||||||
return new CompilationResult(typeof(DifferentView));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -416,24 +457,30 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var resetEvent2 = new ManualResetEvent(initialState: false);
|
var resetEvent2 = new ManualResetEvent(initialState: false);
|
||||||
var cache = new CompilerCache(fileProvider);
|
var cache = new CompilerCache(fileProvider);
|
||||||
|
|
||||||
|
Func<CompilerCacheContext, CompilationResult> compile = _ =>
|
||||||
|
{
|
||||||
|
// Event 2
|
||||||
|
resetEvent1.WaitOne(waitDuration);
|
||||||
|
|
||||||
|
// Event 3
|
||||||
|
resetEvent2.Set();
|
||||||
|
return new CompilationResult(typeof(TestView));
|
||||||
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var task1 = Task.Run(() =>
|
var task1 = Task.Run(() =>
|
||||||
{
|
{
|
||||||
return cache.GetOrAdd(ViewPath, file =>
|
return cache.GetOrAdd(ViewPath, path =>
|
||||||
{
|
{
|
||||||
// Event 2
|
var projectItem = new DefaultRazorProjectItem(new TestFileInfo(), "", path);
|
||||||
resetEvent1.WaitOne(waitDuration);
|
return new CompilerCacheContext(projectItem, Enumerable.Empty<RazorProjectItem>(), compile);
|
||||||
|
|
||||||
// Event 3
|
|
||||||
resetEvent2.Set();
|
|
||||||
return new CompilationResult(typeof(TestView));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var task2 = Task.Run(() =>
|
var task2 = Task.Run(() =>
|
||||||
{
|
{
|
||||||
// Event 4
|
// Event 4
|
||||||
resetEvent2.WaitOne(waitDuration);
|
Assert.True(resetEvent2.WaitOne(waitDuration));
|
||||||
return cache.GetOrAdd(ViewPath, ThrowsIfCalled);
|
return cache.GetOrAdd(ViewPath, ThrowsIfCalled);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -444,7 +491,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
// Assert
|
// Assert
|
||||||
var result1 = task1.Result;
|
var result1 = task1.Result;
|
||||||
var result2 = task2.Result;
|
var result2 = task2.Result;
|
||||||
Assert.Same(result1.PageFactory, result2.PageFactory);
|
Assert.Same(result1.CompiledType, result2.CompiledType);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -475,7 +522,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
|
|
||||||
// Act and Assert - 1
|
// Act and Assert - 1
|
||||||
var actual = Assert.Throws<InvalidTimeZoneException>(() =>
|
var actual = Assert.Throws<InvalidTimeZoneException>(() =>
|
||||||
cache.GetOrAdd(ViewPath, _ => { throw exception; }));
|
cache.GetOrAdd(ViewPath, _ => ThrowsIfCalled(ViewPath, exception)));
|
||||||
Assert.Same(exception, actual);
|
Assert.Same(exception, actual);
|
||||||
|
|
||||||
// Act and Assert - 2
|
// Act and Assert - 2
|
||||||
|
|
@ -489,6 +536,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
// Arrange
|
// Arrange
|
||||||
var fileProvider = new TestFileProvider();
|
var fileProvider = new TestFileProvider();
|
||||||
fileProvider.AddFile(ViewPath, "some content");
|
fileProvider.AddFile(ViewPath, "some content");
|
||||||
|
var changeToken = fileProvider.AddChangeToken(ViewPath);
|
||||||
var cache = new CompilerCache(fileProvider);
|
var cache = new CompilerCache(fileProvider);
|
||||||
|
|
||||||
// Act and Assert - 1
|
// Act and Assert - 1
|
||||||
|
|
@ -496,11 +544,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
cache.GetOrAdd(ViewPath, _ => { throw new InvalidTimeZoneException(); }));
|
cache.GetOrAdd(ViewPath, _ => { throw new InvalidTimeZoneException(); }));
|
||||||
|
|
||||||
// Act - 2
|
// Act - 2
|
||||||
fileProvider.GetChangeToken(ViewPath).HasChanged = true;
|
changeToken.HasChanged = true;
|
||||||
var result = cache.GetOrAdd(ViewPath, _ => new CompilationResult(typeof(TestView)));
|
var result = cache.GetOrAdd(ViewPath, CreateContextFactory(new CompilationResult(typeof(TestView))));
|
||||||
|
|
||||||
// Assert - 2
|
// Assert - 2
|
||||||
Assert.IsType<TestView>(result.PageFactory());
|
Assert.Same(typeof(TestView), result.CompiledType);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -512,15 +560,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var cache = new CompilerCache(fileProvider);
|
var cache = new CompilerCache(fileProvider);
|
||||||
var diagnosticMessages = new[]
|
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[]
|
var compilationResult = new CompilationResult(new[]
|
||||||
{
|
{
|
||||||
new CompilationFailure(ViewPath, "some content", "compiled content", diagnosticMessages)
|
new CompilationFailure(ViewPath, "some content", "compiled content", diagnosticMessages)
|
||||||
});
|
});
|
||||||
|
var context = CreateContextFactory(compilationResult);
|
||||||
|
|
||||||
// Act and Assert - 1
|
// 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);
|
Assert.Same(compilationResult.CompilationFailures, ex.CompilationFailures);
|
||||||
|
|
||||||
// Act and Assert - 2
|
// 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;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -16,6 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
public void CreateFactory_ReturnsExpirationTokensFromCompilerCache_ForUnsuccessfulResults()
|
public void CreateFactory_ReturnsExpirationTokensFromCompilerCache_ForUnsuccessfulResults()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
var path = "/file-does-not-exist";
|
||||||
var expirationTokens = new[]
|
var expirationTokens = new[]
|
||||||
{
|
{
|
||||||
Mock.Of<IChangeToken>(),
|
Mock.Of<IChangeToken>(),
|
||||||
|
|
@ -23,18 +25,20 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
};
|
};
|
||||||
var compilerCache = new Mock<ICompilerCache>();
|
var compilerCache = new Mock<ICompilerCache>();
|
||||||
compilerCache
|
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(expirationTokens));
|
.Returns(new CompilerCacheResult(path, expirationTokens));
|
||||||
var compilerCacheProvider = new Mock<ICompilerCacheProvider>();
|
var compilerCacheProvider = new Mock<ICompilerCacheProvider>();
|
||||||
compilerCacheProvider
|
compilerCacheProvider
|
||||||
.SetupGet(c => c.Cache)
|
.SetupGet(c => c.Cache)
|
||||||
.Returns(compilerCache.Object);
|
.Returns(compilerCache.Object);
|
||||||
var factoryProvider = new DefaultRazorPageFactoryProvider(
|
var factoryProvider = new DefaultRazorPageFactoryProvider(
|
||||||
Mock.Of<IRazorCompilationService>(),
|
RazorEngine.Create(),
|
||||||
|
new DefaultRazorProject(new TestFileProvider()),
|
||||||
|
Mock.Of<ICompilationService>(),
|
||||||
compilerCacheProvider.Object);
|
compilerCacheProvider.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = factoryProvider.CreateFactory("/file-does-not-exist");
|
var result = factoryProvider.CreateFactory(path);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result.Success);
|
Assert.False(result.Success);
|
||||||
|
|
@ -53,14 +57,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
};
|
};
|
||||||
var compilerCache = new Mock<ICompilerCache>();
|
var compilerCache = new Mock<ICompilerCache>();
|
||||||
compilerCache
|
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));
|
.Returns(new CompilerCacheResult(relativePath, new CompilationResult(typeof(TestRazorPage)), expirationTokens));
|
||||||
var compilerCacheProvider = new Mock<ICompilerCacheProvider>();
|
var compilerCacheProvider = new Mock<ICompilerCacheProvider>();
|
||||||
compilerCacheProvider
|
compilerCacheProvider
|
||||||
.SetupGet(c => c.Cache)
|
.SetupGet(c => c.Cache)
|
||||||
.Returns(compilerCache.Object);
|
.Returns(compilerCache.Object);
|
||||||
var factoryProvider = new DefaultRazorPageFactoryProvider(
|
var factoryProvider = new DefaultRazorPageFactoryProvider(
|
||||||
Mock.Of<IRazorCompilationService>(),
|
RazorEngine.Create(),
|
||||||
|
new DefaultRazorProject(new TestFileProvider()),
|
||||||
|
Mock.Of<ICompilationService>(),
|
||||||
compilerCacheProvider.Object);
|
compilerCacheProvider.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -78,14 +84,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var relativePath = "/file-exists";
|
var relativePath = "/file-exists";
|
||||||
var compilerCache = new Mock<ICompilerCache>();
|
var compilerCache = new Mock<ICompilerCache>();
|
||||||
compilerCache
|
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]));
|
.Returns(new CompilerCacheResult(relativePath, new CompilationResult(typeof(TestRazorPage)), new IChangeToken[0]));
|
||||||
var compilerCacheProvider = new Mock<ICompilerCacheProvider>();
|
var compilerCacheProvider = new Mock<ICompilerCacheProvider>();
|
||||||
compilerCacheProvider
|
compilerCacheProvider
|
||||||
.SetupGet(c => c.Cache)
|
.SetupGet(c => c.Cache)
|
||||||
.Returns(compilerCache.Object);
|
.Returns(compilerCache.Object);
|
||||||
var factoryProvider = new DefaultRazorPageFactoryProvider(
|
var factoryProvider = new DefaultRazorPageFactoryProvider(
|
||||||
Mock.Of<IRazorCompilationService>(),
|
RazorEngine.Create(),
|
||||||
|
new DefaultRazorProject(new TestFileProvider()),
|
||||||
|
Mock.Of<ICompilationService>(),
|
||||||
compilerCacheProvider.Object);
|
compilerCacheProvider.Object);
|
||||||
|
|
||||||
// Act
|
// 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.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||||
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
|
@ -855,7 +857,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
|
||||||
.Setup(p => p.CreateFactory("/Views/_ViewStart.cshtml"))
|
.Setup(p => p.CreateFactory("/Views/_ViewStart.cshtml"))
|
||||||
.Returns(new RazorPageFactoryResult(() => viewStart, new IChangeToken[0]));
|
.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);
|
var context = GetActionContext(_controllerTestContext);
|
||||||
|
|
||||||
// Act 1
|
// Act 1
|
||||||
|
|
@ -1302,6 +1306,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
|
||||||
Mock.Of<IRazorPageActivator>(),
|
Mock.Of<IRazorPageActivator>(),
|
||||||
new HtmlTestEncoder(),
|
new HtmlTestEncoder(),
|
||||||
GetOptionsAccessor(expanders: null),
|
GetOptionsAccessor(expanders: null),
|
||||||
|
new DefaultRazorProject(new TestFileProvider()),
|
||||||
loggerFactory);
|
loggerFactory);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -1615,10 +1620,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
|
||||||
|
|
||||||
private TestableRazorViewEngine CreateViewEngine(
|
private TestableRazorViewEngine CreateViewEngine(
|
||||||
IRazorPageFactoryProvider pageFactory = null,
|
IRazorPageFactoryProvider pageFactory = null,
|
||||||
IEnumerable<IViewLocationExpander> expanders = null)
|
IEnumerable<IViewLocationExpander> expanders = null,
|
||||||
|
RazorProject razorProject = null)
|
||||||
{
|
{
|
||||||
pageFactory = pageFactory ?? Mock.Of<IRazorPageFactoryProvider>();
|
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(
|
private static IOptions<RazorViewEngineOptions> GetOptionsAccessor(
|
||||||
|
|
@ -1707,7 +1717,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
|
||||||
public TestableRazorViewEngine(
|
public TestableRazorViewEngine(
|
||||||
IRazorPageFactoryProvider pageFactory,
|
IRazorPageFactoryProvider pageFactory,
|
||||||
IOptions<RazorViewEngineOptions> optionsAccessor)
|
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();
|
var fileProvider = new TestFileProvider();
|
||||||
fileProvider.AddFile("/Home/Path1/_PageStart.cshtml", "content1");
|
fileProvider.AddFile("/Home/Path1/_PageStart.cshtml", "content1");
|
||||||
fileProvider.AddFile("/_PageStart.cshtml", "content2");
|
fileProvider.AddFile("/_PageStart.cshtml", "content2");
|
||||||
var defaultRazorProject = new DefaultRazorProject(fileProvider);
|
var defaultRazorProject = new TestRazorProject(fileProvider);
|
||||||
|
|
||||||
var invokerProvider = CreateInvokerProvider(
|
var invokerProvider = CreateInvokerProvider(
|
||||||
loader.Object,
|
loader.Object,
|
||||||
|
|
@ -249,7 +249,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
|
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
|
||||||
var razorPageFactoryProvider = new Mock<IRazorPageFactoryProvider>();
|
var razorPageFactoryProvider = new Mock<IRazorPageFactoryProvider>();
|
||||||
var fileProvider = new TestFileProvider();
|
var fileProvider = new TestFileProvider();
|
||||||
var defaultRazorProject = new DefaultRazorProject(fileProvider);
|
var defaultRazorProject = new TestRazorProject(fileProvider);
|
||||||
|
|
||||||
var invokerProvider = CreateInvokerProvider(
|
var invokerProvider = CreateInvokerProvider(
|
||||||
loader.Object,
|
loader.Object,
|
||||||
|
|
@ -592,7 +592,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
fileProvider.AddFile("/Views/_PageStart.cshtml", "@page starts!");
|
fileProvider.AddFile("/Views/_PageStart.cshtml", "@page starts!");
|
||||||
fileProvider.AddFile("/Views/Deeper/_PageStart.cshtml", "page content");
|
fileProvider.AddFile("/Views/Deeper/_PageStart.cshtml", "page content");
|
||||||
|
|
||||||
var razorProject = CreateRazorProject(fileProvider);
|
var razorProject = new TestRazorProject(fileProvider);
|
||||||
|
|
||||||
var mock = new Mock<IRazorPageFactoryProvider>();
|
var mock = new Mock<IRazorPageFactoryProvider>();
|
||||||
mock.Setup(p => p.CreateFactory("/Views/Deeper/_PageStart.cshtml"))
|
mock.Setup(p => p.CreateFactory("/Views/Deeper/_PageStart.cshtml"))
|
||||||
|
|
@ -642,8 +642,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
|
||||||
// No files
|
// No files
|
||||||
var fileProvider = new TestFileProvider();
|
var fileProvider = new TestFileProvider();
|
||||||
|
var razorProject = new TestRazorProject(fileProvider);
|
||||||
var razorProject = CreateRazorProject(fileProvider);
|
|
||||||
|
|
||||||
var invokerProvider = CreateInvokerProvider(
|
var invokerProvider = CreateInvokerProvider(
|
||||||
loader.Object,
|
loader.Object,
|
||||||
|
|
@ -670,11 +669,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
return mock.Object;
|
return mock.Object;
|
||||||
}
|
}
|
||||||
|
|
||||||
private RazorProject CreateRazorProject(IFileProvider fileProvider)
|
|
||||||
{
|
|
||||||
return new DefaultRazorProject(fileProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CompiledPageActionDescriptor CreateCompiledPageActionDescriptor(
|
private static CompiledPageActionDescriptor CreateCompiledPageActionDescriptor(
|
||||||
PageActionDescriptor descriptor,
|
PageActionDescriptor descriptor,
|
||||||
Type pageType = null)
|
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)
|
public virtual IChangeToken Watch(string filter)
|
||||||
{
|
{
|
||||||
TestFileChangeToken changeToken;
|
TestFileChangeToken changeToken;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace RazorPageExecutionInstrumentationWebSite
|
namespace RazorPageExecutionInstrumentationWebSite
|
||||||
|
|
@ -16,7 +16,7 @@ namespace RazorPageExecutionInstrumentationWebSite
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
// Normalize line endings to avoid changes in instrumentation locations between systems.
|
// 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.
|
// Add MVC services to the services container.
|
||||||
services.AddMvc();
|
services.AddMvc();
|
||||||
|
|
|
||||||
|
|
@ -3,40 +3,45 @@
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||||
using Microsoft.AspNetCore.Razor.Evolution;
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace RazorPageExecutionInstrumentationWebSite
|
namespace RazorPageExecutionInstrumentationWebSite
|
||||||
{
|
{
|
||||||
public class TestRazorCompilationService : RazorCompilationService
|
public class TestRazorProject : DefaultRazorProject
|
||||||
{
|
{
|
||||||
public TestRazorCompilationService(
|
public TestRazorProject(IRazorViewEngineFileProviderAccessor fileProviderAccessor)
|
||||||
ICompilationService compilationService,
|
: base(fileProviderAccessor)
|
||||||
RazorEngine engine,
|
|
||||||
RazorProject project,
|
|
||||||
IRazorViewEngineFileProviderAccessor fileProviderAccessor,
|
|
||||||
ILoggerFactory loggerFactory)
|
|
||||||
: base(compilationService, engine, project, fileProviderAccessor, loggerFactory)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
var item = (DefaultRazorProjectItem)base.GetItem(path);
|
||||||
// .gitattributes from the equation and treats "\r\n" and "\n" as equivalent. Does not handle
|
return new TestRazorProjectItem(item);
|
||||||
// some line endings like "\r" but otherwise ensures checksums and line mappings are consistent.
|
}
|
||||||
string text;
|
|
||||||
using (var streamReader = new StreamReader(inputStream))
|
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);
|
public override Stream Read()
|
||||||
inputStream = new MemoryStream(bytes);
|
{
|
||||||
|
// 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