зеркало из https://github.com/aspnet/Mvc.git
Remove runtime compilation from Mvc.Razor (#8755)
* Remove runtime compilation from Mvc.Razor * Remove RazorViewEngineOptions.FileProviders
This commit is contained in:
Родитель
bfdecc8f5e
Коммит
f80490f99d
|
@ -40,9 +40,7 @@
|
|||
<MicrosoftAspNetCoreJsonPatchPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreJsonPatchPackageVersion>
|
||||
<MicrosoftAspNetCoreLocalizationPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreLocalizationPackageVersion>
|
||||
<MicrosoftAspNetCoreLocalizationRoutingPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreLocalizationRoutingPackageVersion>
|
||||
<MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion>
|
||||
<MicrosoftAspNetCoreRangeHelperSourcesPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreRangeHelperSourcesPackageVersion>
|
||||
<MicrosoftAspNetCoreRazorLanguagePackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreRazorLanguagePackageVersion>
|
||||
<MicrosoftAspNetCoreRazorRuntimePackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreRazorRuntimePackageVersion>
|
||||
<MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>
|
||||
<MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>3.0.0-alpha1-10742</MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>
|
||||
|
|
|
@ -28,7 +28,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
|
|||
"Microsoft.AspNetCore.Mvc.Formatters.Xml",
|
||||
"Microsoft.AspNetCore.Mvc.Localization",
|
||||
"Microsoft.AspNetCore.Mvc.Razor",
|
||||
"Microsoft.AspNetCore.Mvc.Razor.Extensions",
|
||||
"Microsoft.AspNetCore.Mvc.RazorPages",
|
||||
"Microsoft.AspNetCore.Mvc.TagHelpers",
|
||||
"Microsoft.AspNetCore.Mvc.ViewFeatures",
|
||||
|
|
|
@ -1,234 +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.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Emit;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.Extensions.DependencyModel;
|
||||
using DependencyContextCompilationOptions = Microsoft.Extensions.DependencyModel.CompilationOptions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
internal class CSharpCompiler
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
private readonly RazorReferenceManager _referenceManager;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private bool _optionsInitialized;
|
||||
private CSharpParseOptions _parseOptions;
|
||||
private CSharpCompilationOptions _compilationOptions;
|
||||
private EmitOptions _emitOptions;
|
||||
private bool _emitPdb;
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
public CSharpCompiler(RazorReferenceManager manager, IHostingEnvironment hostingEnvironment)
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
_referenceManager = manager ?? throw new ArgumentNullException(nameof(manager));
|
||||
_hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
|
||||
}
|
||||
|
||||
public virtual CSharpParseOptions ParseOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureOptions();
|
||||
return _parseOptions;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual CSharpCompilationOptions CSharpCompilationOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureOptions();
|
||||
return _compilationOptions;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool EmitPdb
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureOptions();
|
||||
return _emitPdb;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual EmitOptions EmitOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureOptions();
|
||||
return _emitOptions;
|
||||
}
|
||||
}
|
||||
|
||||
public SyntaxTree CreateSyntaxTree(SourceText sourceText)
|
||||
{
|
||||
return CSharpSyntaxTree.ParseText(
|
||||
sourceText,
|
||||
options: ParseOptions);
|
||||
}
|
||||
|
||||
public CSharpCompilation CreateCompilation(string assemblyName)
|
||||
{
|
||||
return CSharpCompilation.Create(
|
||||
assemblyName,
|
||||
options: CSharpCompilationOptions,
|
||||
references: _referenceManager.CompilationReferences);
|
||||
}
|
||||
|
||||
// Internal for unit testing.
|
||||
protected internal virtual DependencyContextCompilationOptions GetDependencyContextCompilationOptions()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_hostingEnvironment.ApplicationName))
|
||||
{
|
||||
var applicationAssembly = Assembly.Load(new AssemblyName(_hostingEnvironment.ApplicationName));
|
||||
var dependencyContext = DependencyContext.Load(applicationAssembly);
|
||||
if (dependencyContext?.CompilationOptions != null)
|
||||
{
|
||||
return dependencyContext.CompilationOptions;
|
||||
}
|
||||
}
|
||||
|
||||
return DependencyContextCompilationOptions.Default;
|
||||
}
|
||||
|
||||
private void EnsureOptions()
|
||||
{
|
||||
if (!_optionsInitialized)
|
||||
{
|
||||
var dependencyContextOptions = GetDependencyContextCompilationOptions();
|
||||
_parseOptions = GetParseOptions(_hostingEnvironment, dependencyContextOptions);
|
||||
_compilationOptions = GetCompilationOptions(_hostingEnvironment, dependencyContextOptions);
|
||||
_emitOptions = GetEmitOptions(dependencyContextOptions);
|
||||
|
||||
_optionsInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
private EmitOptions GetEmitOptions(DependencyContextCompilationOptions dependencyContextOptions)
|
||||
{
|
||||
// Assume we're always producing pdbs unless DebugType = none
|
||||
_emitPdb = true;
|
||||
DebugInformationFormat debugInformationFormat;
|
||||
if (string.IsNullOrEmpty(dependencyContextOptions.DebugType))
|
||||
{
|
||||
debugInformationFormat = SymbolsUtility.SupportsFullPdbGeneration() ?
|
||||
DebugInformationFormat.Pdb :
|
||||
DebugInformationFormat.PortablePdb;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Based on https://github.com/dotnet/roslyn/blob/1d28ff9ba248b332de3c84d23194a1d7bde07e4d/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs#L624-L640
|
||||
switch (dependencyContextOptions.DebugType.ToLower())
|
||||
{
|
||||
case "none":
|
||||
// There isn't a way to represent none in DebugInformationFormat.
|
||||
// We'll set EmitPdb to false and let callers handle it by setting a null pdb-stream.
|
||||
_emitPdb = false;
|
||||
return new EmitOptions();
|
||||
case "portable":
|
||||
debugInformationFormat = DebugInformationFormat.PortablePdb;
|
||||
break;
|
||||
case "embedded":
|
||||
// Roslyn does not expose enough public APIs to produce a binary with embedded pdbs.
|
||||
// We'll produce PortablePdb instead to continue providing a reasonable user experience.
|
||||
debugInformationFormat = DebugInformationFormat.PortablePdb;
|
||||
break;
|
||||
case "full":
|
||||
case "pdbonly":
|
||||
debugInformationFormat = SymbolsUtility.SupportsFullPdbGeneration() ?
|
||||
DebugInformationFormat.Pdb :
|
||||
DebugInformationFormat.PortablePdb;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException(Resources.FormatUnsupportedDebugInformationFormat(dependencyContextOptions.DebugType));
|
||||
}
|
||||
}
|
||||
|
||||
var emitOptions = new EmitOptions(debugInformationFormat: debugInformationFormat);
|
||||
return emitOptions;
|
||||
}
|
||||
|
||||
private static CSharpCompilationOptions GetCompilationOptions(
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
DependencyContextCompilationOptions dependencyContextOptions)
|
||||
{
|
||||
var csharpCompilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
|
||||
|
||||
// Disable 1702 until roslyn turns this off by default
|
||||
csharpCompilationOptions = csharpCompilationOptions.WithSpecificDiagnosticOptions(
|
||||
new Dictionary<string, ReportDiagnostic>
|
||||
{
|
||||
{"CS1701", ReportDiagnostic.Suppress}, // Binding redirects
|
||||
{"CS1702", ReportDiagnostic.Suppress},
|
||||
{"CS1705", ReportDiagnostic.Suppress}
|
||||
});
|
||||
|
||||
if (dependencyContextOptions.AllowUnsafe.HasValue)
|
||||
{
|
||||
csharpCompilationOptions = csharpCompilationOptions.WithAllowUnsafe(
|
||||
dependencyContextOptions.AllowUnsafe.Value);
|
||||
}
|
||||
|
||||
OptimizationLevel optimizationLevel;
|
||||
if (dependencyContextOptions.Optimize.HasValue)
|
||||
{
|
||||
optimizationLevel = dependencyContextOptions.Optimize.Value ?
|
||||
OptimizationLevel.Release :
|
||||
OptimizationLevel.Debug;
|
||||
}
|
||||
else
|
||||
{
|
||||
optimizationLevel = hostingEnvironment.IsDevelopment() ?
|
||||
OptimizationLevel.Debug :
|
||||
OptimizationLevel.Release;
|
||||
}
|
||||
csharpCompilationOptions = csharpCompilationOptions.WithOptimizationLevel(optimizationLevel);
|
||||
|
||||
if (dependencyContextOptions.WarningsAsErrors.HasValue)
|
||||
{
|
||||
var reportDiagnostic = dependencyContextOptions.WarningsAsErrors.Value ?
|
||||
ReportDiagnostic.Error :
|
||||
ReportDiagnostic.Default;
|
||||
csharpCompilationOptions = csharpCompilationOptions.WithGeneralDiagnosticOption(reportDiagnostic);
|
||||
}
|
||||
|
||||
return csharpCompilationOptions;
|
||||
}
|
||||
|
||||
private static CSharpParseOptions GetParseOptions(
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
DependencyContextCompilationOptions dependencyContextOptions)
|
||||
{
|
||||
var configurationSymbol = hostingEnvironment.IsDevelopment() ? "DEBUG" : "RELEASE";
|
||||
var defines = dependencyContextOptions.Defines.Concat(new[] { configurationSymbol });
|
||||
|
||||
var parseOptions = new CSharpParseOptions(preprocessorSymbols: defines);
|
||||
|
||||
if (!string.IsNullOrEmpty(dependencyContextOptions.LanguageVersion))
|
||||
{
|
||||
if (LanguageVersionFacts.TryParse(dependencyContextOptions.LanguageVersion, out var languageVersion))
|
||||
{
|
||||
parseOptions = parseOptions.WithLanguageVersion(languageVersion);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Fail($"LanguageVersion {languageVersion} specified in the deps file could not be parsed.");
|
||||
}
|
||||
}
|
||||
|
||||
return parseOptions;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,122 +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.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Hosting;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
internal static class ChecksumValidator
|
||||
{
|
||||
public static bool IsRecompilationSupported(RazorCompiledItem item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
// A Razor item only supports recompilation if its primary source file has a checksum.
|
||||
//
|
||||
// Other files (view imports) may or may not have existed at the time of compilation,
|
||||
// so we may not have checksums for them.
|
||||
var checksums = item.GetChecksumMetadata();
|
||||
return checksums.Any(c => string.Equals(item.Identifier, c.Identifier, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
// Validates that we can use an existing precompiled view by comparing checksums with files on
|
||||
// disk.
|
||||
public static bool IsItemValid(RazorProjectFileSystem fileSystem, RazorCompiledItem item)
|
||||
{
|
||||
if (fileSystem == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(fileSystem));
|
||||
}
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
var checksums = item.GetChecksumMetadata();
|
||||
|
||||
// The checksum that matches 'Item.Identity' in this list is significant. That represents the main file.
|
||||
//
|
||||
// We don't really care about the validation unless the main file exists. This is because we expect
|
||||
// most sites to have some _ViewImports in common location. That means that in the case you're
|
||||
// using views from a 3rd party library, you'll always have **some** conflicts.
|
||||
//
|
||||
// The presence of the main file with the same content is a very strong signal that you're in a
|
||||
// development scenario.
|
||||
var primaryChecksum = checksums
|
||||
.FirstOrDefault(c => string.Equals(item.Identifier, c.Identifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (primaryChecksum == null)
|
||||
{
|
||||
// No primary checksum, assume valid.
|
||||
return true;
|
||||
}
|
||||
|
||||
var projectItem = fileSystem.GetItem(primaryChecksum.Identifier);
|
||||
if (!projectItem.Exists)
|
||||
{
|
||||
// Main file doesn't exist - assume valid.
|
||||
return true;
|
||||
}
|
||||
|
||||
var sourceDocument = RazorSourceDocument.ReadFrom(projectItem);
|
||||
if (!string.Equals(sourceDocument.GetChecksumAlgorithm(), primaryChecksum.ChecksumAlgorithm) ||
|
||||
!ChecksumsEqual(primaryChecksum.Checksum, sourceDocument.GetChecksum()))
|
||||
{
|
||||
// Main file exists, but checksums not equal.
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < checksums.Count; i++)
|
||||
{
|
||||
var checksum = checksums[i];
|
||||
if (string.Equals(item.Identifier, checksum.Identifier, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Ignore primary checksum on this pass.
|
||||
continue;
|
||||
}
|
||||
|
||||
var importItem = fileSystem.GetItem(checksum.Identifier);
|
||||
if (!importItem.Exists)
|
||||
{
|
||||
// Import file doesn't exist - assume invalid.
|
||||
return false;
|
||||
}
|
||||
|
||||
sourceDocument = RazorSourceDocument.ReadFrom(importItem);
|
||||
if (!string.Equals(sourceDocument.GetChecksumAlgorithm(), checksum.ChecksumAlgorithm) ||
|
||||
!ChecksumsEqual(checksum.Checksum, sourceDocument.GetChecksum()))
|
||||
{
|
||||
// Import file exists, but checksums not equal.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool ChecksumsEqual(string checksum, byte[] bytes)
|
||||
{
|
||||
if (bytes.Length * 2 != checksum.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
var text = bytes[i].ToString("x2");
|
||||
if (checksum[i * 2] != text[0] || checksum[i * 2 + 1] != text[1])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,44 +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.Linq;
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="Exception"/> thrown when accessing the result of a failed compilation.
|
||||
/// </summary>
|
||||
public class CompilationFailedException : Exception, ICompilationException
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of <see cref="CompilationFailedException"/>.
|
||||
/// </summary>
|
||||
/// <param name="compilationFailures"><see cref="CompilationFailure"/>s containing
|
||||
/// details of the compilation failure.</param>
|
||||
public CompilationFailedException(
|
||||
IEnumerable<CompilationFailure> compilationFailures)
|
||||
: base(FormatMessage(compilationFailures))
|
||||
{
|
||||
if (compilationFailures == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(compilationFailures));
|
||||
}
|
||||
|
||||
CompilationFailures = compilationFailures;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<CompilationFailure> CompilationFailures { get; }
|
||||
|
||||
private static string FormatMessage(IEnumerable<CompilationFailure> compilationFailures)
|
||||
{
|
||||
return Resources.CompilationFailed + Environment.NewLine +
|
||||
string.Join(
|
||||
Environment.NewLine,
|
||||
compilationFailures.SelectMany(f => f.Messages).Select(message => message.FormattedMessage));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,157 +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.Linq;
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
internal static class CompilationFailedExceptionFactory
|
||||
{
|
||||
// error CS0234: The type or namespace name 'C' does not exist in the namespace 'N' (are you missing
|
||||
// an assembly reference?)
|
||||
private const string CS0234 = nameof(CS0234);
|
||||
// error CS0246: The type or namespace name 'T' could not be found (are you missing a using directive
|
||||
// or an assembly reference?)
|
||||
private const string CS0246 = nameof(CS0246);
|
||||
|
||||
public static CompilationFailedException Create(
|
||||
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.FilePath,
|
||||
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 CompilationFailedException(failures);
|
||||
}
|
||||
|
||||
public static CompilationFailedException Create(
|
||||
RazorCodeDocument codeDocument,
|
||||
string compilationContent,
|
||||
string assemblyName,
|
||||
IEnumerable<Diagnostic> diagnostics)
|
||||
{
|
||||
var diagnosticGroups = diagnostics
|
||||
.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error)
|
||||
.GroupBy(diagnostic => GetFilePath(codeDocument, diagnostic), StringComparer.Ordinal);
|
||||
|
||||
var failures = new List<CompilationFailure>();
|
||||
foreach (var group in diagnosticGroups)
|
||||
{
|
||||
var sourceFilePath = group.Key;
|
||||
string sourceFileContent;
|
||||
if (string.Equals(assemblyName, sourceFilePath, StringComparison.Ordinal))
|
||||
{
|
||||
// The error is in the generated code and does not have a mapping line pragma
|
||||
sourceFileContent = compilationContent;
|
||||
sourceFilePath = Resources.GeneratedCodeFileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
sourceFileContent = ReadContent(codeDocument, sourceFilePath);
|
||||
}
|
||||
|
||||
string additionalMessage = null;
|
||||
if (group.Any(g =>
|
||||
string.Equals(CS0234, g.Id, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(CS0246, g.Id, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
additionalMessage = Resources.FormatCompilation_MissingReferences(
|
||||
"CopyRefAssembliesToPublishDirectory");
|
||||
}
|
||||
|
||||
var compilationFailure = new CompilationFailure(
|
||||
sourceFilePath,
|
||||
sourceFileContent,
|
||||
compilationContent,
|
||||
group.Select(GetDiagnosticMessage),
|
||||
additionalMessage);
|
||||
|
||||
failures.Add(compilationFailure);
|
||||
}
|
||||
|
||||
return new CompilationFailedException(failures);
|
||||
}
|
||||
|
||||
private static string ReadContent(RazorCodeDocument codeDocument, string filePath)
|
||||
{
|
||||
RazorSourceDocument sourceDocument;
|
||||
if (string.IsNullOrEmpty(filePath) || string.Equals(codeDocument.Source.FilePath, filePath, StringComparison.Ordinal))
|
||||
{
|
||||
sourceDocument = codeDocument.Source;
|
||||
}
|
||||
else
|
||||
{
|
||||
sourceDocument = codeDocument.Imports.FirstOrDefault(f => string.Equals(f.FilePath, 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 GetDiagnosticMessage(Diagnostic diagnostic)
|
||||
{
|
||||
var mappedLineSpan = diagnostic.Location.GetMappedLineSpan();
|
||||
return new DiagnosticMessage(
|
||||
diagnostic.GetMessage(),
|
||||
CSharpDiagnosticFormatter.Instance.Format(diagnostic),
|
||||
mappedLineSpan.Path,
|
||||
mappedLineSpan.StartLinePosition.Line + 1,
|
||||
mappedLineSpan.StartLinePosition.Character + 1,
|
||||
mappedLineSpan.EndLinePosition.Line + 1,
|
||||
mappedLineSpan.EndLinePosition.Character + 1);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private static string GetFilePath(RazorCodeDocument codeDocument, Diagnostic diagnostic)
|
||||
{
|
||||
if (diagnostic.Location == Location.None)
|
||||
{
|
||||
return codeDocument.Source.FilePath;
|
||||
}
|
||||
|
||||
return diagnostic.Location.GetMappedLineSpan().Path;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -51,7 +51,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
// later.
|
||||
ExpirationTokens = Array.Empty<IChangeToken>();
|
||||
RelativePath = ViewPath.NormalizePath(item?.Identifier ?? attribute.Path);
|
||||
IsPrecompiled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -72,11 +71,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
/// </summary>
|
||||
public IList<IChangeToken> ExpirationTokens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that determines if the view is precompiled.
|
||||
/// </summary>
|
||||
public bool IsPrecompiled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="RazorCompiledItem"/> descriptor for this view.
|
||||
/// </summary>
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
|
|
|
@ -1,65 +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.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
internal class DefaultRazorReferenceManager : RazorReferenceManager
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
private readonly ApplicationPartManager _partManager;
|
||||
private readonly IList<MetadataReference> _additionalMetadataReferences;
|
||||
private object _compilationReferencesLock = new object();
|
||||
private bool _compilationReferencesInitialized;
|
||||
private IReadOnlyList<MetadataReference> _compilationReferences;
|
||||
|
||||
public DefaultRazorReferenceManager(
|
||||
ApplicationPartManager partManager,
|
||||
IOptions<RazorViewEngineOptions> optionsAccessor)
|
||||
{
|
||||
_partManager = partManager;
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
_additionalMetadataReferences = optionsAccessor.Value.AdditionalCompilationReferences;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
public override IReadOnlyList<MetadataReference> CompilationReferences
|
||||
{
|
||||
get
|
||||
{
|
||||
return LazyInitializer.EnsureInitialized(
|
||||
ref _compilationReferences,
|
||||
ref _compilationReferencesInitialized,
|
||||
ref _compilationReferencesLock,
|
||||
GetCompilationReferences);
|
||||
}
|
||||
}
|
||||
|
||||
private IReadOnlyList<MetadataReference> GetCompilationReferences()
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var feature = new MetadataReferenceFeature();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
_partManager.PopulateFeature(feature);
|
||||
var applicationReferences = feature.MetadataReferences;
|
||||
|
||||
if (_additionalMetadataReferences.Count == 0)
|
||||
{
|
||||
return applicationReferences.ToArray();
|
||||
}
|
||||
|
||||
var compilationReferences = new List<MetadataReference>(applicationReferences.Count + _additionalMetadataReferences.Count);
|
||||
compilationReferences.AddRange(applicationReferences);
|
||||
compilationReferences.AddRange(_additionalMetadataReferences);
|
||||
|
||||
return compilationReferences;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,215 +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.Diagnostics;
|
||||
using System.Linq.Expressions;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
/// <summary>
|
||||
/// An expression rewriter which can hoist a simple expression lambda into a private field.
|
||||
/// </summary>
|
||||
internal class ExpressionRewriter : CSharpSyntaxRewriter
|
||||
{
|
||||
private static readonly string FieldNameTemplate = "__h{0}";
|
||||
|
||||
public ExpressionRewriter(SemanticModel semanticModel)
|
||||
{
|
||||
SemanticModel = semanticModel;
|
||||
|
||||
Expressions = new List<KeyValuePair<SimpleLambdaExpressionSyntax, IdentifierNameSyntax>>();
|
||||
}
|
||||
|
||||
// We only want to rewrite expressions for the top-level class definition.
|
||||
private bool IsInsideClass { get; set; }
|
||||
|
||||
private SemanticModel SemanticModel { get; }
|
||||
|
||||
private List<KeyValuePair<SimpleLambdaExpressionSyntax, IdentifierNameSyntax>> Expressions { get; }
|
||||
|
||||
public static CSharpCompilation Rewrite(CSharpCompilation compilation)
|
||||
{
|
||||
var rewrittenTrees = new List<SyntaxTree>();
|
||||
foreach (var tree in compilation.SyntaxTrees)
|
||||
{
|
||||
var semanticModel = compilation.GetSemanticModel(tree, ignoreAccessibility: true);
|
||||
var rewriter = new ExpressionRewriter(semanticModel);
|
||||
|
||||
var rewrittenTree = tree.WithRootAndOptions(rewriter.Visit(tree.GetRoot()), tree.Options);
|
||||
rewrittenTrees.Add(rewrittenTree);
|
||||
}
|
||||
|
||||
return compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(rewrittenTrees);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
|
||||
{
|
||||
if (IsInsideClass)
|
||||
{
|
||||
// Avoid recursing into nested classes.
|
||||
return node;
|
||||
}
|
||||
|
||||
Expressions.Clear();
|
||||
|
||||
IsInsideClass = true;
|
||||
|
||||
// Call base first to visit all the children and populate Expressions.
|
||||
var classDeclaration = (ClassDeclarationSyntax)base.VisitClassDeclaration(node);
|
||||
|
||||
IsInsideClass = false;
|
||||
|
||||
var memberDeclarations = new List<MemberDeclarationSyntax>();
|
||||
foreach (var kvp in Expressions)
|
||||
{
|
||||
var expression = kvp.Key;
|
||||
var memberName = kvp.Value.GetFirstToken();
|
||||
|
||||
var expressionType = SemanticModel.GetTypeInfo(expression).ConvertedType;
|
||||
var declaration = SyntaxFactory.FieldDeclaration(
|
||||
SyntaxFactory.List<AttributeListSyntax>(),
|
||||
SyntaxFactory.TokenList(
|
||||
SyntaxFactory.Token(SyntaxKind.PrivateKeyword),
|
||||
SyntaxFactory.Token(SyntaxKind.StaticKeyword),
|
||||
SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)),
|
||||
SyntaxFactory.VariableDeclaration(
|
||||
SyntaxFactory.ParseTypeName(expressionType.ToDisplayString(
|
||||
SymbolDisplayFormat.FullyQualifiedFormat)),
|
||||
SyntaxFactory.SingletonSeparatedList(
|
||||
SyntaxFactory.VariableDeclarator(
|
||||
memberName,
|
||||
SyntaxFactory.BracketedArgumentList(),
|
||||
SyntaxFactory.EqualsValueClause(expression)))))
|
||||
.WithTriviaFrom(expression);
|
||||
memberDeclarations.Add(declaration);
|
||||
}
|
||||
|
||||
return classDeclaration.AddMembers(memberDeclarations.ToArray());
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node)
|
||||
{
|
||||
Debug.Assert(IsInsideClass);
|
||||
|
||||
// If this lambda is an Expression and is suitable for hoisting, we rewrite this into a field access.
|
||||
//
|
||||
// Before:
|
||||
// public Task ExecuteAsync(...)
|
||||
// {
|
||||
// ...
|
||||
// Html.EditorFor(m => m.Price);
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
//
|
||||
// After:
|
||||
// private static readonly Expression<Func<Product, decimal>> __h0 = m => m.Price;
|
||||
// public Task ExecuteAsync(...)
|
||||
// {
|
||||
// ...
|
||||
// Html.EditorFor(__h0);
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
var type = SemanticModel.GetTypeInfo(node);
|
||||
|
||||
// Due to an anomaly where Roslyn (depending on code sample) may finish compilation without diagnostic
|
||||
// errors (this code path does not execute when diagnostic errors are present) we need to validate that
|
||||
// the ConvertedType was determined/is not null.
|
||||
if (type.ConvertedType == null ||
|
||||
type.ConvertedType.Name != typeof(Expression).Name &&
|
||||
type.ConvertedType.ContainingNamespace.Name != typeof(Expression).Namespace)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
if (!node.Parent.IsKind(SyntaxKind.Argument))
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
var parameter = node.Parameter;
|
||||
if (IsValidForHoisting(parameter, node.Body))
|
||||
{
|
||||
// Replace with a MemberAccess
|
||||
var memberName = string.Format(FieldNameTemplate, Expressions.Count);
|
||||
var memberAccess = PadMemberAccess(node, SyntaxFactory.IdentifierName(memberName));
|
||||
Expressions.Add(new KeyValuePair<SimpleLambdaExpressionSyntax, IdentifierNameSyntax>(node, memberAccess));
|
||||
return memberAccess;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private static IdentifierNameSyntax PadMemberAccess(
|
||||
SimpleLambdaExpressionSyntax node,
|
||||
IdentifierNameSyntax memberAccess)
|
||||
{
|
||||
var charactersToExclude = memberAccess.Identifier.Text.Length;
|
||||
var triviaList = new SyntaxTriviaList();
|
||||
|
||||
// Go through each token and
|
||||
// 1. Append leading trivia
|
||||
// 2. Append the same number of whitespace as the length of the token text
|
||||
// 3. Append trailing trivia
|
||||
foreach (var token in node.DescendantTokens())
|
||||
{
|
||||
if (token.HasLeadingTrivia)
|
||||
{
|
||||
triviaList = triviaList.AddRange(token.LeadingTrivia);
|
||||
}
|
||||
|
||||
// Need to exclude the length of the member name from the padding.
|
||||
var padding = token.Text.Length;
|
||||
if (padding > charactersToExclude)
|
||||
{
|
||||
padding -= charactersToExclude;
|
||||
charactersToExclude = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
charactersToExclude -= padding;
|
||||
padding = 0;
|
||||
}
|
||||
|
||||
if (padding > 0)
|
||||
{
|
||||
triviaList = triviaList.Add(SyntaxFactory.Whitespace(new string(' ', padding)));
|
||||
}
|
||||
|
||||
if (token.HasTrailingTrivia)
|
||||
{
|
||||
triviaList = triviaList.AddRange(token.TrailingTrivia);
|
||||
}
|
||||
}
|
||||
|
||||
return memberAccess
|
||||
.WithLeadingTrivia(node.GetLeadingTrivia())
|
||||
.WithTrailingTrivia(triviaList);
|
||||
}
|
||||
|
||||
private static bool IsValidForHoisting(ParameterSyntax parameter, CSharpSyntaxNode node)
|
||||
{
|
||||
if (node.IsKind(SyntaxKind.IdentifierName))
|
||||
{
|
||||
var identifier = (IdentifierNameSyntax)node;
|
||||
if (identifier.Identifier.Text == parameter.Identifier.Text)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (node.IsKind(SyntaxKind.SimpleMemberAccessExpression))
|
||||
{
|
||||
var memberAccess = (MemberAccessExpressionSyntax)node;
|
||||
var lhs = memberAccess.Expression;
|
||||
return IsValidForHoisting(parameter, lhs);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +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 Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides an instance of <see cref="IMemoryCache"/> that is used to store compiled Razor views.
|
||||
/// </summary>
|
||||
public interface IViewCompilationMemoryCacheProvider
|
||||
{
|
||||
IMemoryCache CompilationMemoryCache { get; }
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the list of <see cref="MetadataReference"/> used in Razor compilation.
|
||||
/// </summary>
|
||||
[Obsolete("This type is obsolete and will be removed in a future version. See https://aka.ms/AA1x4gg for details.")]
|
||||
public class MetadataReferenceFeature
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="MetadataReference"/> instances.
|
||||
/// </summary>
|
||||
public IList<MetadataReference> MetadataReferences { get; } = new List<MetadataReference>();
|
||||
}
|
||||
}
|
|
@ -1,62 +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.Reflection.PortableExecutable;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.Extensions.DependencyModel;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IApplicationFeatureProvider{TFeature}"/> for <see cref="MetadataReferenceFeature"/> that
|
||||
/// uses <see cref="DependencyContext"/> for registered <see cref="AssemblyPart"/> instances to create
|
||||
/// <see cref="MetadataReference"/>.
|
||||
/// </summary>
|
||||
[Obsolete("This type is obsolete and will be removed in a future version. See https://aka.ms/AA1x4gg for details.")]
|
||||
public class MetadataReferenceFeatureProvider : IApplicationFeatureProvider<MetadataReferenceFeature>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void PopulateFeature(IEnumerable<ApplicationPart> parts, MetadataReferenceFeature feature)
|
||||
{
|
||||
if (parts == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(parts));
|
||||
}
|
||||
|
||||
if (feature == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(feature));
|
||||
}
|
||||
|
||||
var libraryPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var providerPart in parts.OfType<ICompilationReferencesProvider>())
|
||||
{
|
||||
var referencePaths = providerPart.GetReferencePaths();
|
||||
foreach (var path in referencePaths)
|
||||
{
|
||||
if (libraryPaths.Add(path))
|
||||
{
|
||||
var metadataReference = CreateMetadataReference(path);
|
||||
feature.MetadataReferences.Add(metadataReference);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static MetadataReference CreateMetadataReference(string path)
|
||||
{
|
||||
using (var stream = File.OpenRead(path))
|
||||
{
|
||||
var moduleMetadata = ModuleMetadata.CreateFromStream(stream, PEStreamOptions.PrefetchMetadata);
|
||||
var assemblyMetadata = AssemblyMetadata.Create(moduleMetadata);
|
||||
|
||||
return assemblyMetadata.GetReference(filePath: path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages compilation references for Razor compilation.
|
||||
/// </summary>
|
||||
[Obsolete("This type is obsolete and will be removed in a future version. See https://aka.ms/AA1x4gg for details.")]
|
||||
public abstract class RazorReferenceManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the set of compilation references to be used for Razor compilation.
|
||||
/// </summary>
|
||||
public abstract IReadOnlyList<MetadataReference> CompilationReferences { get; }
|
||||
}
|
||||
}
|
|
@ -1,12 +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 Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
internal class RazorViewCompilationMemoryCacheProvider : IViewCompilationMemoryCacheProvider
|
||||
{
|
||||
IMemoryCache IViewCompilationMemoryCacheProvider.CompilationMemoryCache { get; } = new MemoryCache(new MemoryCacheOptions());
|
||||
}
|
||||
}
|
|
@ -5,18 +5,7 @@ using System;
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Hosting;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Emit;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
|
@ -27,48 +16,17 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
/// </summary>
|
||||
internal class RazorViewCompiler : IViewCompiler
|
||||
{
|
||||
private readonly object _cacheLock = new object();
|
||||
private readonly Dictionary<string, CompiledViewDescriptor> _precompiledViews;
|
||||
private readonly Dictionary<string, Task<CompiledViewDescriptor>> _compiledViews;
|
||||
private readonly ConcurrentDictionary<string, string> _normalizedPathCache;
|
||||
private readonly IFileProvider _fileProvider;
|
||||
private readonly RazorProjectEngine _projectEngine;
|
||||
private readonly Action<RoslynCompilationContext> _compilationCallback;
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly ILogger _logger;
|
||||
private readonly CSharpCompiler _csharpCompiler;
|
||||
|
||||
public RazorViewCompiler(
|
||||
IFileProvider fileProvider,
|
||||
RazorProjectEngine projectEngine,
|
||||
CSharpCompiler csharpCompiler,
|
||||
Action<RoslynCompilationContext> compilationCallback,
|
||||
IList<CompiledViewDescriptor> precompiledViews,
|
||||
IMemoryCache cache,
|
||||
IList<CompiledViewDescriptor> compiledViews,
|
||||
ILogger logger)
|
||||
{
|
||||
if (fileProvider == null)
|
||||
if (compiledViews == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(fileProvider));
|
||||
}
|
||||
|
||||
if (projectEngine == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectEngine));
|
||||
}
|
||||
|
||||
if (csharpCompiler == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(csharpCompiler));
|
||||
}
|
||||
|
||||
if (compilationCallback == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(compilationCallback));
|
||||
}
|
||||
|
||||
if (precompiledViews == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(precompiledViews));
|
||||
throw new ArgumentNullException(nameof(compiledViews));
|
||||
}
|
||||
|
||||
if (logger == null)
|
||||
|
@ -76,47 +34,35 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_fileProvider = fileProvider;
|
||||
_projectEngine = projectEngine;
|
||||
_csharpCompiler = csharpCompiler;
|
||||
_compilationCallback = compilationCallback;
|
||||
_logger = logger;
|
||||
|
||||
|
||||
_normalizedPathCache = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
|
||||
|
||||
// This is our L0 cache, and is a durable store. Views migrate into the cache as they are requested
|
||||
// from either the set of known precompiled views, or by being compiled.
|
||||
_cache = cache;
|
||||
|
||||
// We need to validate that the all of the precompiled views are unique by path (case-insensitive).
|
||||
// We do this because there's no good way to canonicalize paths on windows, and it will create
|
||||
// problems when deploying to linux. Rather than deal with these issues, we just don't support
|
||||
// views that differ only by case.
|
||||
_precompiledViews = new Dictionary<string, CompiledViewDescriptor>(
|
||||
precompiledViews.Count,
|
||||
_compiledViews = new Dictionary<string, Task<CompiledViewDescriptor>>(
|
||||
compiledViews.Count,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var precompiledView in precompiledViews)
|
||||
foreach (var compiledView in compiledViews)
|
||||
{
|
||||
logger.ViewCompilerLocatedCompiledView(precompiledView.RelativePath);
|
||||
logger.ViewCompilerLocatedCompiledView(compiledView.RelativePath);
|
||||
|
||||
if (!_precompiledViews.ContainsKey(precompiledView.RelativePath))
|
||||
if (!_compiledViews.ContainsKey(compiledView.RelativePath))
|
||||
{
|
||||
// View ordering has precedence semantics, a view with a higher precedence was
|
||||
// View ordering has precedence semantics, a view with a higher precedence was not
|
||||
// already added to the list.
|
||||
_precompiledViews.Add(precompiledView.RelativePath, precompiledView);
|
||||
_compiledViews.Add(compiledView.RelativePath, Task.FromResult(compiledView));
|
||||
}
|
||||
}
|
||||
|
||||
if (_precompiledViews.Count == 0)
|
||||
if (_compiledViews.Count == 0)
|
||||
{
|
||||
logger.ViewCompilerNoCompiledViewsFound();
|
||||
}
|
||||
}
|
||||
|
||||
public bool AllowRecompilingViewsOnFileChange { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<CompiledViewDescriptor> CompileAsync(string relativePath)
|
||||
{
|
||||
|
@ -127,320 +73,26 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
|
||||
// Attempt to lookup the cache entry using the passed in path. This will succeed if the path is already
|
||||
// normalized and a cache entry exists.
|
||||
if (_cache.TryGetValue(relativePath, out Task<CompiledViewDescriptor> cachedResult))
|
||||
if (_compiledViews.TryGetValue(relativePath, out var cachedResult))
|
||||
{
|
||||
_logger.ViewCompilerLocatedCompiledViewForPath(relativePath);
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
var normalizedPath = GetNormalizedPath(relativePath);
|
||||
if (_cache.TryGetValue(normalizedPath, out cachedResult))
|
||||
if (_compiledViews.TryGetValue(normalizedPath, out cachedResult))
|
||||
{
|
||||
_logger.ViewCompilerLocatedCompiledViewForPath(normalizedPath);
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
// Entry does not exist. Attempt to create one.
|
||||
cachedResult = OnCacheMiss(normalizedPath);
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
private Task<CompiledViewDescriptor> OnCacheMiss(string normalizedPath)
|
||||
{
|
||||
ViewCompilerWorkItem item;
|
||||
TaskCompletionSource<CompiledViewDescriptor> taskSource;
|
||||
MemoryCacheEntryOptions cacheEntryOptions;
|
||||
|
||||
// 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
|
||||
// actual work for compiling files happens outside the critical section.
|
||||
lock (_cacheLock)
|
||||
_logger.ViewCompilerCouldNotFindFileAtPath(relativePath);
|
||||
return Task.FromResult(new CompiledViewDescriptor
|
||||
{
|
||||
// Double-checked locking to handle a possible race.
|
||||
if (_cache.TryGetValue(normalizedPath, out Task<CompiledViewDescriptor> result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (_precompiledViews.TryGetValue(normalizedPath, out var precompiledView))
|
||||
{
|
||||
_logger.ViewCompilerLocatedCompiledViewForPath(normalizedPath);
|
||||
item = CreatePrecompiledWorkItem(normalizedPath, precompiledView);
|
||||
}
|
||||
else
|
||||
{
|
||||
item = CreateRuntimeCompilationWorkItem(normalizedPath);
|
||||
}
|
||||
|
||||
// At this point, we've decided what to do - but we should create the cache entry and
|
||||
// release the lock first.
|
||||
cacheEntryOptions = new MemoryCacheEntryOptions();
|
||||
|
||||
Debug.Assert(item.ExpirationTokens != null);
|
||||
for (var i = 0; i < item.ExpirationTokens.Count; i++)
|
||||
{
|
||||
cacheEntryOptions.ExpirationTokens.Add(item.ExpirationTokens[i]);
|
||||
}
|
||||
|
||||
taskSource = new TaskCompletionSource<CompiledViewDescriptor>(creationOptions: TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
if (item.SupportsCompilation)
|
||||
{
|
||||
// We'll compile in just a sec, be patient.
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we can't compile, we should have already created the descriptor
|
||||
Debug.Assert(item.Descriptor != null);
|
||||
taskSource.SetResult(item.Descriptor);
|
||||
}
|
||||
|
||||
_cache.Set(normalizedPath, taskSource.Task, cacheEntryOptions);
|
||||
}
|
||||
|
||||
// Now the lock has been released so we can do more expensive processing.
|
||||
if (item.SupportsCompilation)
|
||||
{
|
||||
Debug.Assert(taskSource != null);
|
||||
|
||||
if (item.Descriptor?.Item != null &&
|
||||
ChecksumValidator.IsItemValid(_projectEngine.FileSystem, item.Descriptor.Item))
|
||||
{
|
||||
// If the item has checksums to validate, we should also have a precompiled view.
|
||||
Debug.Assert(item.Descriptor != null);
|
||||
|
||||
taskSource.SetResult(item.Descriptor);
|
||||
return taskSource.Task;
|
||||
}
|
||||
|
||||
_logger.ViewCompilerInvalidingCompiledFile(item.NormalizedPath);
|
||||
try
|
||||
{
|
||||
var descriptor = CompileAndEmit(normalizedPath);
|
||||
descriptor.ExpirationTokens = cacheEntryOptions.ExpirationTokens;
|
||||
taskSource.SetResult(descriptor);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
taskSource.SetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
return taskSource.Task;
|
||||
}
|
||||
|
||||
private ViewCompilerWorkItem CreatePrecompiledWorkItem(string normalizedPath, CompiledViewDescriptor precompiledView)
|
||||
{
|
||||
// We have a precompiled view - but we're not sure that we can use it yet.
|
||||
//
|
||||
// We need to determine first if we have enough information to 'recompile' this view. If that's the case
|
||||
// we'll create change tokens for all of the files.
|
||||
//
|
||||
// Then we'll attempt to validate if any of those files have different content than the original sources
|
||||
// based on checksums.
|
||||
if (precompiledView.Item == null || !ChecksumValidator.IsRecompilationSupported(precompiledView.Item))
|
||||
{
|
||||
return new ViewCompilerWorkItem()
|
||||
{
|
||||
// If we don't have a checksum for the primary source file we can't recompile.
|
||||
SupportsCompilation = false,
|
||||
|
||||
ExpirationTokens = Array.Empty<IChangeToken>(), // Never expire because we can't recompile.
|
||||
Descriptor = precompiledView, // This will be used as-is.
|
||||
};
|
||||
}
|
||||
|
||||
var item = new ViewCompilerWorkItem()
|
||||
{
|
||||
SupportsCompilation = true,
|
||||
|
||||
Descriptor = precompiledView, // This might be used, if the checksums match.
|
||||
|
||||
// Used to validate and recompile
|
||||
NormalizedPath = normalizedPath,
|
||||
|
||||
ExpirationTokens = GetExpirationTokens(precompiledView),
|
||||
};
|
||||
|
||||
// We also need to create a new descriptor, because the original one doesn't have expiration tokens on
|
||||
// it. These will be used by the view location cache, which is like an L1 cache for views (this class is
|
||||
// the L2 cache).
|
||||
item.Descriptor = new CompiledViewDescriptor()
|
||||
{
|
||||
ExpirationTokens = item.ExpirationTokens,
|
||||
IsPrecompiled = true,
|
||||
Item = precompiledView.Item,
|
||||
RelativePath = precompiledView.RelativePath,
|
||||
ViewAttribute = precompiledView.ViewAttribute,
|
||||
};
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private ViewCompilerWorkItem CreateRuntimeCompilationWorkItem(string normalizedPath)
|
||||
{
|
||||
IList<IChangeToken> expirationTokens = Array.Empty<IChangeToken>();
|
||||
|
||||
if (AllowRecompilingViewsOnFileChange)
|
||||
{
|
||||
var changeToken = _fileProvider.Watch(normalizedPath);
|
||||
expirationTokens = new List<IChangeToken> { changeToken };
|
||||
}
|
||||
|
||||
var projectItem = _projectEngine.FileSystem.GetItem(normalizedPath);
|
||||
if (!projectItem.Exists)
|
||||
{
|
||||
_logger.ViewCompilerCouldNotFindFileAtPath(normalizedPath);
|
||||
|
||||
// If the file doesn't exist, we can't do compilation right now - we still want to cache
|
||||
// the fact that we tried. This will allow us to re-trigger compilation if the view file
|
||||
// is added.
|
||||
return new ViewCompilerWorkItem()
|
||||
{
|
||||
// We don't have enough information to compile
|
||||
SupportsCompilation = false,
|
||||
|
||||
Descriptor = new CompiledViewDescriptor()
|
||||
{
|
||||
RelativePath = normalizedPath,
|
||||
ExpirationTokens = expirationTokens,
|
||||
},
|
||||
|
||||
// We can try again if the file gets created.
|
||||
ExpirationTokens = expirationTokens,
|
||||
};
|
||||
}
|
||||
|
||||
_logger.ViewCompilerFoundFileToCompile(normalizedPath);
|
||||
|
||||
GetChangeTokensFromImports(expirationTokens, projectItem);
|
||||
|
||||
return new ViewCompilerWorkItem()
|
||||
{
|
||||
SupportsCompilation = true,
|
||||
|
||||
NormalizedPath = normalizedPath,
|
||||
ExpirationTokens = expirationTokens,
|
||||
};
|
||||
}
|
||||
|
||||
private IList<IChangeToken> GetExpirationTokens(CompiledViewDescriptor precompiledView)
|
||||
{
|
||||
if (!AllowRecompilingViewsOnFileChange)
|
||||
{
|
||||
return Array.Empty<IChangeToken>();
|
||||
}
|
||||
|
||||
var checksums = precompiledView.Item.GetChecksumMetadata();
|
||||
var expirationTokens = new List<IChangeToken>(checksums.Count);
|
||||
|
||||
for (var i = 0; i < checksums.Count; i++)
|
||||
{
|
||||
// We rely on Razor to provide the right set of checksums. Trust the compiler, it has to do a good job,
|
||||
// so it probably will.
|
||||
expirationTokens.Add(_fileProvider.Watch(checksums[i].Identifier));
|
||||
}
|
||||
|
||||
return expirationTokens;
|
||||
}
|
||||
|
||||
private void GetChangeTokensFromImports(IList<IChangeToken> expirationTokens, RazorProjectItem projectItem)
|
||||
{
|
||||
if (!AllowRecompilingViewsOnFileChange)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// OK this means we can do compilation. For now let's just identify the other files we need to watch
|
||||
// so we can create the cache entry. Compilation will happen after we release the lock.
|
||||
var importFeature = _projectEngine.ProjectFeatures.OfType<IImportProjectFeature>().FirstOrDefault();
|
||||
|
||||
// There should always be an import feature unless someone has misconfigured their RazorProjectEngine.
|
||||
// In that case once we attempt to parse the Razor file we'll explode and give the a user a decent
|
||||
// error message; for now, lets just be extra protective and assume 0 imports to not give a bad error.
|
||||
var imports = importFeature?.GetImports(projectItem) ?? Enumerable.Empty<RazorProjectItem>();
|
||||
var physicalImports = imports.Where(import => import.FilePath != null);
|
||||
|
||||
// Now that we have non-dynamic imports we need to get their RazorProjectItem equivalents so we have their
|
||||
// physical file paths (according to the FileSystem).
|
||||
foreach (var physicalImport in physicalImports)
|
||||
{
|
||||
expirationTokens.Add(_fileProvider.Watch(physicalImport.FilePath));
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual CompiledViewDescriptor CompileAndEmit(string relativePath)
|
||||
{
|
||||
var projectItem = _projectEngine.FileSystem.GetItem(relativePath);
|
||||
var codeDocument = _projectEngine.Process(projectItem);
|
||||
var cSharpDocument = codeDocument.GetCSharpDocument();
|
||||
|
||||
if (cSharpDocument.Diagnostics.Count > 0)
|
||||
{
|
||||
throw CompilationFailedExceptionFactory.Create(
|
||||
codeDocument,
|
||||
cSharpDocument.Diagnostics);
|
||||
}
|
||||
|
||||
var assembly = CompileAndEmit(codeDocument, cSharpDocument.GeneratedCode);
|
||||
|
||||
// Anything we compile from source will use Razor 2.1 and so should have the new metadata.
|
||||
var loader = new RazorCompiledItemLoader();
|
||||
var item = loader.LoadItems(assembly).SingleOrDefault();
|
||||
var attribute = assembly.GetCustomAttribute<RazorViewAttribute>();
|
||||
|
||||
return new CompiledViewDescriptor(item, attribute);
|
||||
}
|
||||
|
||||
internal Assembly CompileAndEmit(RazorCodeDocument codeDocument, string generatedCode)
|
||||
{
|
||||
_logger.GeneratedCodeToAssemblyCompilationStart(codeDocument.Source.FilePath);
|
||||
|
||||
var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0;
|
||||
|
||||
var assemblyName = Path.GetRandomFileName();
|
||||
var compilation = CreateCompilation(generatedCode, assemblyName);
|
||||
|
||||
var emitOptions = _csharpCompiler.EmitOptions;
|
||||
var emitPdbFile = _csharpCompiler.EmitPdb && emitOptions.DebugInformationFormat != DebugInformationFormat.Embedded;
|
||||
|
||||
using (var assemblyStream = new MemoryStream())
|
||||
using (var pdbStream = emitPdbFile ? new MemoryStream() : null)
|
||||
{
|
||||
var result = compilation.Emit(
|
||||
assemblyStream,
|
||||
pdbStream,
|
||||
options: emitOptions);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
throw CompilationFailedExceptionFactory.Create(
|
||||
codeDocument,
|
||||
generatedCode,
|
||||
assemblyName,
|
||||
result.Diagnostics);
|
||||
}
|
||||
|
||||
assemblyStream.Seek(0, SeekOrigin.Begin);
|
||||
pdbStream?.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var assembly = Assembly.Load(assemblyStream.ToArray(), pdbStream?.ToArray());
|
||||
_logger.GeneratedCodeToAssemblyCompilationEnd(codeDocument.Source.FilePath, startTimestamp);
|
||||
|
||||
return assembly;
|
||||
}
|
||||
}
|
||||
|
||||
private CSharpCompilation CreateCompilation(string compilationContent, string assemblyName)
|
||||
{
|
||||
var sourceText = SourceText.From(compilationContent, Encoding.UTF8);
|
||||
var syntaxTree = _csharpCompiler.CreateSyntaxTree(sourceText).WithFilePath(assemblyName);
|
||||
var compilation = _csharpCompiler
|
||||
.CreateCompilation(assemblyName)
|
||||
.AddSyntaxTrees(syntaxTree);
|
||||
compilation = ExpressionRewriter.Rewrite(compilation);
|
||||
|
||||
var compilationContext = new RoslynCompilationContext(compilation);
|
||||
_compilationCallback(compilationContext);
|
||||
compilation = compilationContext.Compilation;
|
||||
return compilation;
|
||||
RelativePath = normalizedPath,
|
||||
ExpirationTokens = Array.Empty<IChangeToken>(),
|
||||
});
|
||||
}
|
||||
|
||||
private string GetNormalizedPath(string relativePath)
|
||||
|
@ -459,16 +111,5 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
|
||||
return normalizedPath;
|
||||
}
|
||||
|
||||
private class ViewCompilerWorkItem
|
||||
{
|
||||
public bool SupportsCompilation { get; set; }
|
||||
|
||||
public string NormalizedPath { get; set; }
|
||||
|
||||
public IList<IChangeToken> ExpirationTokens { get; set; }
|
||||
|
||||
public CompiledViewDescriptor Descriptor { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,89 +1,25 @@
|
|||
// 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.Threading;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
internal class RazorViewCompilerProvider : IViewCompilerProvider
|
||||
{
|
||||
private readonly RazorProjectEngine _razorProjectEngine;
|
||||
private readonly ApplicationPartManager _applicationPartManager;
|
||||
private readonly IRazorViewEngineFileProviderAccessor _fileProviderAccessor;
|
||||
private readonly CSharpCompiler _csharpCompiler;
|
||||
private readonly IViewCompilationMemoryCacheProvider _compilationMemoryCacheProvider;
|
||||
private readonly RazorViewEngineOptions _viewEngineOptions;
|
||||
private readonly ILogger<RazorViewCompiler> _logger;
|
||||
private readonly Func<IViewCompiler> _createCompiler;
|
||||
|
||||
private object _initializeLock = new object();
|
||||
private bool _initialized;
|
||||
private IViewCompiler _compiler;
|
||||
private readonly RazorViewCompiler _compiler;
|
||||
|
||||
public RazorViewCompilerProvider(
|
||||
ApplicationPartManager applicationPartManager,
|
||||
RazorProjectEngine razorProjectEngine,
|
||||
IRazorViewEngineFileProviderAccessor fileProviderAccessor,
|
||||
CSharpCompiler csharpCompiler,
|
||||
IOptions<RazorViewEngineOptions> viewEngineOptionsAccessor,
|
||||
IViewCompilationMemoryCacheProvider compilationMemoryCacheProvider,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
_applicationPartManager = applicationPartManager;
|
||||
_razorProjectEngine = razorProjectEngine;
|
||||
_fileProviderAccessor = fileProviderAccessor;
|
||||
_csharpCompiler = csharpCompiler;
|
||||
_compilationMemoryCacheProvider = compilationMemoryCacheProvider;
|
||||
_viewEngineOptions = viewEngineOptionsAccessor.Value;
|
||||
|
||||
_logger = loggerFactory.CreateLogger<RazorViewCompiler>();
|
||||
_createCompiler = CreateCompiler;
|
||||
}
|
||||
|
||||
public IViewCompiler GetCompiler()
|
||||
{
|
||||
var fileProvider = _fileProviderAccessor.FileProvider;
|
||||
if (fileProvider is NullFileProvider)
|
||||
{
|
||||
var message = Resources.FormatFileProvidersAreRequired(
|
||||
typeof(RazorViewEngineOptions).FullName,
|
||||
nameof(RazorViewEngineOptions.FileProviders),
|
||||
typeof(IFileProvider).FullName);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
return LazyInitializer.EnsureInitialized(
|
||||
ref _compiler,
|
||||
ref _initialized,
|
||||
ref _initializeLock,
|
||||
_createCompiler);
|
||||
}
|
||||
|
||||
private IViewCompiler CreateCompiler()
|
||||
{
|
||||
var feature = new ViewsFeature();
|
||||
_applicationPartManager.PopulateFeature(feature);
|
||||
applicationPartManager.PopulateFeature(feature);
|
||||
|
||||
return new RazorViewCompiler(
|
||||
_fileProviderAccessor.FileProvider,
|
||||
_razorProjectEngine,
|
||||
_csharpCompiler,
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
_viewEngineOptions.CompilationCallback,
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
feature.ViewDescriptors,
|
||||
_compilationMemoryCacheProvider.CompilationMemoryCache,
|
||||
_logger)
|
||||
{
|
||||
AllowRecompilingViewsOnFileChange = _viewEngineOptions.AllowRecompilingViewsOnFileChange,
|
||||
};
|
||||
_compiler = new RazorViewCompiler(feature.ViewDescriptors, loggerFactory.CreateLogger<RazorViewCompiler>());
|
||||
}
|
||||
|
||||
public IViewCompiler GetCompiler() => _compiler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +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.CodeAnalysis.CSharp;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
/// <summary>
|
||||
/// Context object used to pass information about the current Razor page compilation.
|
||||
/// </summary>
|
||||
public class RoslynCompilationContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the <see cref="RoslynCompilationContext"/> type.
|
||||
/// </summary>
|
||||
/// <param name="compilation"><see cref="CSharpCompilation"/> to be set to <see cref="Compilation"/> property.</param>
|
||||
public RoslynCompilationContext(CSharpCompilation compilation)
|
||||
{
|
||||
if (compilation == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(compilation));
|
||||
}
|
||||
|
||||
Compilation = compilation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="CSharpCompilation"/> used for current source file compilation.
|
||||
/// </summary>
|
||||
public CSharpCompilation Compilation { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,52 +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.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility type for determining if a platform supports full pdb file generation.
|
||||
/// </summary>
|
||||
internal static class SymbolsUtility
|
||||
{
|
||||
// Native pdb writer's CLSID
|
||||
private const string SymWriterGuid = "0AE2DEB0-F901-478b-BB9F-881EE8066788";
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the current platform supports full pdb generation.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if full pdb generation is supported; <c>false</c> otherwise.</returns>
|
||||
public static bool SupportsFullPdbGeneration()
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
// Cross-plat always produce portable pdbs.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Type.GetType("Mono.Runtime") != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Check for the pdb writer component that roslyn uses to generate pdbs
|
||||
var type = Marshal.GetTypeFromCLSID(new Guid(SymWriterGuid));
|
||||
if (type != null)
|
||||
{
|
||||
// This line will throw if pdb generation is not supported.
|
||||
Activator.CreateInstance(type);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -50,7 +50,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
ExpirationTokens = Array.Empty<IChangeToken>(),
|
||||
RelativePath = relativePath,
|
||||
ViewAttribute = attribute,
|
||||
IsPrecompiled = true,
|
||||
};
|
||||
|
||||
feature.ViewDescriptors.Add(viewDescriptor);
|
||||
|
|
|
@ -1,40 +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 Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of <see cref="IRazorViewEngineFileProviderAccessor"/>.
|
||||
/// </summary>
|
||||
internal class DefaultRazorViewEngineFileProviderAccessor : IRazorViewEngineFileProviderAccessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="DefaultRazorViewEngineFileProviderAccessor"/>.
|
||||
/// </summary>
|
||||
/// <param name="optionsAccessor">Accessor to <see cref="RazorViewEngineOptions"/>.</param>
|
||||
public DefaultRazorViewEngineFileProviderAccessor(IOptions<RazorViewEngineOptions> optionsAccessor)
|
||||
{
|
||||
var fileProviders = optionsAccessor.Value.FileProviders;
|
||||
if (fileProviders.Count == 0)
|
||||
{
|
||||
FileProvider = new NullFileProvider();
|
||||
}
|
||||
else if (fileProviders.Count == 1)
|
||||
{
|
||||
FileProvider = fileProviders[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
FileProvider = new CompositeFileProvider(fileProviders);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IFileProvider"/> used to look up Razor files.
|
||||
/// </summary>
|
||||
public IFileProvider FileProvider { get; }
|
||||
}
|
||||
}
|
|
@ -2,26 +2,19 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using CompilationTagHelperFeature = Microsoft.CodeAnalysis.Razor.CompilationTagHelperFeature;
|
||||
using DefaultTagHelperDescriptorProvider = Microsoft.CodeAnalysis.Razor.DefaultTagHelperDescriptorProvider;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
|
@ -66,13 +59,6 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
|
||||
private static void AddRazorViewEngineFeatureProviders(IMvcCoreBuilder builder)
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (!builder.PartManager.FeatureProviders.OfType<MetadataReferenceFeatureProvider>().Any())
|
||||
{
|
||||
builder.PartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider());
|
||||
}
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
if (!builder.PartManager.FeatureProviders.OfType<TagHelperFeatureProvider>().Any())
|
||||
{
|
||||
builder.PartManager.FeatureProviders.Add(new TagHelperFeatureProvider());
|
||||
|
@ -148,74 +134,19 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
// Internal for testing.
|
||||
internal static void AddRazorViewEngineServices(IServiceCollection services)
|
||||
{
|
||||
services.TryAddSingleton<CSharpCompiler>();
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
services.TryAddSingleton<RazorReferenceManager, DefaultRazorReferenceManager>();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Transient<IConfigureOptions<MvcViewOptions>, MvcRazorMvcViewOptionsSetup>());
|
||||
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Transient<IConfigureOptions<RazorViewEngineOptions>, RazorViewEngineOptionsSetup>());
|
||||
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Transient<IPostConfigureOptions<RazorViewEngineOptions>, RazorViewEngineOptionsSetup>());
|
||||
|
||||
services.TryAddSingleton<
|
||||
IRazorViewEngineFileProviderAccessor,
|
||||
DefaultRazorViewEngineFileProviderAccessor>();
|
||||
|
||||
services.TryAddSingleton<IRazorViewEngine>(s =>
|
||||
{
|
||||
var pageFactory = s.GetRequiredService<IRazorPageFactoryProvider>();
|
||||
var pageActivator = s.GetRequiredService<IRazorPageActivator>();
|
||||
var htmlEncoder = s.GetRequiredService<HtmlEncoder>();
|
||||
var optionsAccessor = s.GetRequiredService<IOptions<RazorViewEngineOptions>>();
|
||||
var razorFileSystem = s.GetRequiredService<RazorProjectFileSystem>();
|
||||
var loggerFactory = s.GetRequiredService<ILoggerFactory>();
|
||||
var diagnosticListener = s.GetRequiredService<DiagnosticListener>();
|
||||
|
||||
var viewEngine = new RazorViewEngine(pageFactory, pageActivator, htmlEncoder, optionsAccessor, razorFileSystem, loggerFactory, diagnosticListener);
|
||||
return viewEngine;
|
||||
});
|
||||
services.TryAddSingleton<IRazorViewEngine, RazorViewEngine>();
|
||||
services.TryAddSingleton<IViewCompilerProvider, RazorViewCompilerProvider>();
|
||||
services.TryAddSingleton<IViewCompilationMemoryCacheProvider, RazorViewCompilationMemoryCacheProvider>();
|
||||
|
||||
// In the default scenario the following services are singleton by virtue of being initialized as part of
|
||||
// creating the singleton RazorViewEngine instance.
|
||||
services.TryAddTransient<IRazorPageFactoryProvider, DefaultRazorPageFactoryProvider>();
|
||||
|
||||
//
|
||||
// Razor compilation infrastructure
|
||||
//
|
||||
services.TryAddSingleton<LazyMetadataReferenceFeature>();
|
||||
services.TryAddSingleton<RazorProjectFileSystem, FileProviderRazorProjectFileSystem>();
|
||||
services.TryAddSingleton(s =>
|
||||
{
|
||||
var fileSystem = s.GetRequiredService<RazorProjectFileSystem>();
|
||||
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem, builder =>
|
||||
{
|
||||
RazorExtensions.Register(builder);
|
||||
|
||||
// Roslyn + TagHelpers infrastructure
|
||||
var metadataReferenceFeature = s.GetRequiredService<LazyMetadataReferenceFeature>();
|
||||
builder.Features.Add(metadataReferenceFeature);
|
||||
builder.Features.Add(new CompilationTagHelperFeature());
|
||||
|
||||
// TagHelperDescriptorProviders (actually do tag helper discovery)
|
||||
builder.Features.Add(new DefaultTagHelperDescriptorProvider());
|
||||
builder.Features.Add(new ViewComponentTagHelperDescriptorProvider());
|
||||
});
|
||||
|
||||
return projectEngine;
|
||||
});
|
||||
|
||||
// Legacy Razor compilation services
|
||||
services.TryAddSingleton<RazorProject>(s => s.GetRequiredService<RazorProjectEngine>().FileSystem);
|
||||
services.TryAddSingleton<RazorTemplateEngine, MvcRazorTemplateEngine>();
|
||||
services.TryAddSingleton(s => s.GetRequiredService<RazorProjectEngine>().Engine);
|
||||
|
||||
// This caches Razor page activation details that are valid for the lifetime of the application.
|
||||
services.TryAddSingleton<IRazorPageActivator, RazorPageActivator>();
|
||||
|
||||
|
|
|
@ -1,91 +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 Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
internal class FileProviderRazorProjectFileSystem : RazorProjectFileSystem
|
||||
{
|
||||
private const string RazorFileExtension = ".cshtml";
|
||||
private readonly IFileProvider _provider;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
|
||||
public FileProviderRazorProjectFileSystem(IRazorViewEngineFileProviderAccessor accessor, IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
if (accessor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(accessor));
|
||||
}
|
||||
|
||||
if (hostingEnvironment == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(hostingEnvironment));
|
||||
}
|
||||
|
||||
_provider = accessor.FileProvider;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
}
|
||||
|
||||
public override RazorProjectItem GetItem(string path)
|
||||
{
|
||||
path = NormalizeAndEnsureValidPath(path);
|
||||
var fileInfo = _provider.GetFileInfo(path);
|
||||
|
||||
return new FileProviderRazorProjectItem(fileInfo, basePath: string.Empty, filePath: path, root: _hostingEnvironment.ContentRootPath);
|
||||
}
|
||||
|
||||
public override IEnumerable<RazorProjectItem> EnumerateItems(string path)
|
||||
{
|
||||
path = NormalizeAndEnsureValidPath(path);
|
||||
return EnumerateFiles(_provider.GetDirectoryContents(path), path, prefix: string.Empty);
|
||||
}
|
||||
|
||||
private IEnumerable<RazorProjectItem> EnumerateFiles(IDirectoryContents directory, string basePath, string prefix)
|
||||
{
|
||||
if (directory.Exists)
|
||||
{
|
||||
foreach (var fileInfo in directory)
|
||||
{
|
||||
if (fileInfo.IsDirectory)
|
||||
{
|
||||
var relativePath = prefix + "/" + fileInfo.Name;
|
||||
var subDirectory = _provider.GetDirectoryContents(JoinPath(basePath, relativePath));
|
||||
var children = EnumerateFiles(subDirectory, basePath, relativePath);
|
||||
foreach (var child in children)
|
||||
{
|
||||
yield return child;
|
||||
}
|
||||
}
|
||||
else if (string.Equals(RazorFileExtension, Path.GetExtension(fileInfo.Name), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var filePath = prefix + "/" + fileInfo.Name;
|
||||
|
||||
yield return new FileProviderRazorProjectItem(fileInfo, basePath, filePath: filePath, root: _hostingEnvironment.ContentRootPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string JoinPath(string path1, string path2)
|
||||
{
|
||||
var hasTrailingSlash = path1.EndsWith("/", StringComparison.Ordinal);
|
||||
var hasLeadingSlash = path2.StartsWith("/", StringComparison.Ordinal);
|
||||
if (hasLeadingSlash && hasTrailingSlash)
|
||||
{
|
||||
return path1 + path2.Substring(1);
|
||||
}
|
||||
else if (hasLeadingSlash || hasTrailingSlash)
|
||||
{
|
||||
return path1 + path2;
|
||||
}
|
||||
|
||||
return path1 + "/" + path2;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,65 +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.IO;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
internal class FileProviderRazorProjectItem : RazorProjectItem
|
||||
{
|
||||
private string _root;
|
||||
private string _relativePhysicalPath;
|
||||
private bool _isRelativePhysicalPathSet;
|
||||
|
||||
public FileProviderRazorProjectItem(IFileInfo fileInfo, string basePath, string filePath, string root)
|
||||
{
|
||||
FileInfo = fileInfo;
|
||||
BasePath = basePath;
|
||||
FilePath = filePath;
|
||||
_root = root;
|
||||
}
|
||||
|
||||
public IFileInfo FileInfo { get; }
|
||||
|
||||
public override string BasePath { get; }
|
||||
|
||||
public override string FilePath { get; }
|
||||
|
||||
public override bool Exists => FileInfo.Exists;
|
||||
|
||||
public override string PhysicalPath => FileInfo.PhysicalPath;
|
||||
|
||||
public override string RelativePhysicalPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_isRelativePhysicalPathSet)
|
||||
{
|
||||
_isRelativePhysicalPathSet = true;
|
||||
|
||||
if (Exists)
|
||||
{
|
||||
if (_root != null &&
|
||||
!string.IsNullOrEmpty(PhysicalPath) &&
|
||||
PhysicalPath.StartsWith(_root, StringComparison.OrdinalIgnoreCase) &&
|
||||
PhysicalPath.Length > _root.Length &&
|
||||
(PhysicalPath[_root.Length] == Path.DirectorySeparatorChar || PhysicalPath[_root.Length] == Path.AltDirectorySeparatorChar))
|
||||
{
|
||||
_relativePhysicalPath = PhysicalPath.Substring(_root.Length + 1); // Include leading separator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _relativePhysicalPath;
|
||||
}
|
||||
}
|
||||
|
||||
public override Stream Read()
|
||||
{
|
||||
return FileInfo.CreateReadStream();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +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 Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Accessor to the <see cref="IFileProvider"/> used by <see cref="RazorViewEngine"/>.
|
||||
/// </summary>
|
||||
internal interface IRazorViewEngineFileProviderAccessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IFileProvider"/> used to look up Razor files.
|
||||
/// </summary>
|
||||
IFileProvider FileProvider { get; }
|
||||
}
|
||||
}
|
|
@ -1,33 +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 Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
internal class LazyMetadataReferenceFeature : IMetadataReferenceFeature
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
private readonly RazorReferenceManager _referenceManager;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
public LazyMetadataReferenceFeature(RazorReferenceManager referenceManager)
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
_referenceManager = referenceManager;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Invoking <see cref="RazorReferenceManager.CompilationReferences"/> ensures that compilation
|
||||
/// references are lazily evaluated.
|
||||
/// </remarks>
|
||||
public IReadOnlyList<MetadataReference> References => _referenceManager.CompilationReferences;
|
||||
|
||||
public RazorEngine Engine { get; set; }
|
||||
}
|
||||
}
|
|
@ -13,10 +13,7 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.ViewFeatures\Microsoft.AspNetCore.Mvc.ViewFeatures.csproj" />
|
||||
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.Extensions" Version="$(MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Runtime" Version="$(MicrosoftAspNetCoreRazorRuntimePackageVersion)" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Razor" Version="$(MicrosoftCodeAnalysisRazorPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="$(MicrosoftCodeAnalysisCSharpPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="$(MicrosoftExtensionsCachingMemoryPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Composite" Version="$(MicrosoftExtensionsFileProvidersCompositePackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
@ -44,7 +41,6 @@
|
|||
repositoryUrl=$(RepositoryUrl);
|
||||
repositoryCommit=$(RepositoryCommit);
|
||||
targetframework=$(TargetFramework);
|
||||
MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion=$(MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion);
|
||||
MicrosoftAspNetCoreRazorRuntimePackageVersion=$(MicrosoftAspNetCoreRazorRuntimePackageVersion);
|
||||
MicrosoftCodeAnalysisRazorPackageVersion=$(MicrosoftCodeAnalysisRazorPackageVersion);
|
||||
MicrosoftCodeAnalysisCSharpPackageVersion=$(MicrosoftCodeAnalysisCSharpPackageVersion);
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
<dependencies>
|
||||
<group targetFramework=".NETStandard2.0">
|
||||
<dependency id="Microsoft.AspNetCore.Mvc.ViewFeatures" version="$version$" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.AspNetCore.Mvc.Razor.Extensions" version="$MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion$" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.AspNetCore.Razor.Runtime" version="$MicrosoftAspNetCoreRazorRuntimePackageVersion$" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.CodeAnalysis.CSharp" version="$MicrosoftCodeAnalysisCSharpPackageVersion$" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.CodeAnalysis.Razor" version="$MicrosoftCodeAnalysisRazorPackageVersion$" exclude="Build,Analyzers" />
|
||||
|
@ -25,7 +24,6 @@
|
|||
</group>
|
||||
<group targetFramework=".NETFramework4.6.1">
|
||||
<dependency id="Microsoft.AspNetCore.Mvc.ViewFeatures" version="$version$" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.AspNetCore.Mvc.Razor.Extensions" version="$MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion$" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.AspNetCore.Razor.Runtime" version="$MicrosoftAspNetCoreRazorRuntimePackageVersion$" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.CodeAnalysis.CSharp" version="$MicrosoftCodeAnalysisCSharpPackageVersion$" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.CodeAnalysis.Razor" version="$MicrosoftCodeAnalysisRazorPackageVersion$" exclude="Build,Analyzers" />
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
// 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.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
internal static class RazorFileHierarchy
|
||||
{
|
||||
private const string ViewStartFileName = "_ViewStart.cshtml";
|
||||
|
||||
public static IEnumerable<string> GetViewStartPaths(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(path));
|
||||
}
|
||||
|
||||
if (path[0] != '/')
|
||||
{
|
||||
throw new ArgumentException(Resources.RazorProject_PathMustStartWithForwardSlash, nameof(path));
|
||||
}
|
||||
|
||||
if (path.Length == 1)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder(path);
|
||||
var maxIterations = 255;
|
||||
var index = path.Length;
|
||||
while (maxIterations-- > 0 && index > 1 && (index = path.LastIndexOf('/', index - 1)) != -1)
|
||||
{
|
||||
builder.Length = index + 1;
|
||||
builder.Append(ViewStartFileName);
|
||||
|
||||
var itemPath = builder.ToString();
|
||||
yield return itemPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@ using System.Linq;
|
|||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
@ -29,7 +28,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
public class RazorViewEngine : IRazorViewEngine
|
||||
{
|
||||
public static readonly string ViewExtension = ".cshtml";
|
||||
private const string ViewStartFileName = "_ViewStart.cshtml";
|
||||
|
||||
private const string AreaKey = "area";
|
||||
private const string ControllerKey = "controller";
|
||||
|
@ -42,19 +40,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
private readonly HtmlEncoder _htmlEncoder;
|
||||
private readonly ILogger _logger;
|
||||
private readonly RazorViewEngineOptions _options;
|
||||
private readonly RazorProject _razorFileSystem;
|
||||
private readonly DiagnosticListener _diagnosticListener;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RazorViewEngine" />.
|
||||
/// </summary>
|
||||
[Obsolete("This constructor is obsolete and will be removed in a future version.")]
|
||||
public RazorViewEngine(
|
||||
IRazorPageFactoryProvider pageFactory,
|
||||
IRazorPageActivator pageActivator,
|
||||
HtmlEncoder htmlEncoder,
|
||||
IOptions<RazorViewEngineOptions> optionsAccessor,
|
||||
RazorProject razorProject,
|
||||
ILoggerFactory loggerFactory,
|
||||
DiagnosticListener diagnosticListener)
|
||||
{
|
||||
|
@ -78,28 +73,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
_pageActivator = pageActivator;
|
||||
_htmlEncoder = htmlEncoder;
|
||||
_logger = loggerFactory.CreateLogger<RazorViewEngine>();
|
||||
_razorFileSystem = razorProject;
|
||||
_diagnosticListener = diagnosticListener;
|
||||
ViewLookupCache = new MemoryCache(new MemoryCacheOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the RazorViewEngine
|
||||
/// </summary>
|
||||
public RazorViewEngine(
|
||||
IRazorPageFactoryProvider pageFactory,
|
||||
IRazorPageActivator pageActivator,
|
||||
HtmlEncoder htmlEncoder,
|
||||
IOptions<RazorViewEngineOptions> optionsAccessor,
|
||||
RazorProjectFileSystem razorFileSystem,
|
||||
ILoggerFactory loggerFactory,
|
||||
DiagnosticListener diagnosticListener)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
: this (pageFactory, pageActivator, htmlEncoder, optionsAccessor, (RazorProject)razorFileSystem, loggerFactory, diagnosticListener)
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A cache for results of view lookups.
|
||||
/// </summary>
|
||||
|
@ -439,10 +416,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
var viewStartPages = isMainPage ?
|
||||
GetViewStartPages(viewDescriptor.RelativePath, expirationTokens) :
|
||||
Array.Empty<ViewLocationCacheItem>();
|
||||
if (viewDescriptor.IsPrecompiled)
|
||||
{
|
||||
_logger.PrecompiledViewFound(relativePath);
|
||||
}
|
||||
|
||||
return new ViewLocationCacheResult(
|
||||
new ViewLocationCacheItem(factoryResult.RazorPageFactory, relativePath),
|
||||
|
@ -458,9 +431,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
{
|
||||
var viewStartPages = new List<ViewLocationCacheItem>();
|
||||
|
||||
foreach (var viewStartProjectItem in _razorFileSystem.FindHierarchicalItems(path, ViewStartFileName))
|
||||
foreach (var filePath in RazorFileHierarchy.GetViewStartPaths(path))
|
||||
{
|
||||
var result = _pageFactory.CreateFactory(viewStartProjectItem.FilePath);
|
||||
var result = _pageFactory.CreateFactory(filePath);
|
||||
var viewDescriptor = result.ViewDescriptor;
|
||||
if (viewDescriptor?.ExpirationTokens != null)
|
||||
{
|
||||
|
@ -475,7 +448,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
// Populate the viewStartPages list so that _ViewStarts appear in the order the need to be
|
||||
// executed (closest last, furthest first). This is the reverse order in which
|
||||
// ViewHierarchyUtility.GetViewStartLocations returns _ViewStarts.
|
||||
viewStartPages.Insert(0, new ViewLocationCacheItem(result.RazorPageFactory, viewStartProjectItem.FilePath));
|
||||
viewStartPages.Insert(0, new ViewLocationCacheItem(result.RazorPageFactory, filePath));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,50 +1,20 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides programmatic configuration for the <see cref="RazorViewEngine"/>.
|
||||
/// </summary>
|
||||
public class RazorViewEngineOptions : IEnumerable<ICompatibilitySwitch>
|
||||
public class RazorViewEngineOptions
|
||||
{
|
||||
private readonly ICompatibilitySwitch[] _switches;
|
||||
private readonly CompatibilitySwitch<bool> _allowRecompilingViewsOnFileChange;
|
||||
private Action<RoslynCompilationContext> _compilationCallback = c => { };
|
||||
|
||||
public RazorViewEngineOptions()
|
||||
{
|
||||
_allowRecompilingViewsOnFileChange = new CompatibilitySwitch<bool>(nameof(AllowRecompilingViewsOnFileChange));
|
||||
_switches = new[]
|
||||
{
|
||||
_allowRecompilingViewsOnFileChange,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IList{IViewLocationExpander}"/> used by the <see cref="RazorViewEngine"/>.
|
||||
/// </summary>
|
||||
public IList<IViewLocationExpander> ViewLocationExpanders { get; } = new List<IViewLocationExpander>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sequence of <see cref="IFileProvider" /> instances used by <see cref="RazorViewEngine"/> to
|
||||
/// locate Razor files.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// At startup, this is initialized to include an instance of
|
||||
/// <see cref="IHostingEnvironment.ContentRootFileProvider"/> that is rooted at the application root.
|
||||
/// </remarks>
|
||||
public IList<IFileProvider> FileProviders { get; } = new List<IFileProvider>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the locations where <see cref="RazorViewEngine"/> will search for views.
|
||||
/// </summary>
|
||||
|
@ -165,81 +135,5 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
/// </para>
|
||||
/// </remarks>
|
||||
public IList<string> AreaPageViewLocationFormats { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="MetadataReference" /> instances that should be included in Razor compilation, along with
|
||||
/// those discovered by <see cref="MetadataReferenceFeatureProvider" />s.
|
||||
/// </summary>
|
||||
[Obsolete("This property is obsolete and will be removed in a future version. See https://aka.ms/AA1x4gg for details.")]
|
||||
public IList<MetadataReference> AdditionalCompilationReferences { get; } = new List<MetadataReference>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the callback that is used to customize Razor compilation
|
||||
/// to change compilation settings you can update <see cref="RoslynCompilationContext.Compilation"/> property.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Customizations made here would not reflect in tooling (Intellisense).
|
||||
/// </remarks>
|
||||
[Obsolete("This property is obsolete and will be removed in a future version. See https://aka.ms/AA1x4gg for details.")]
|
||||
public Action<RoslynCompilationContext> CompilationCallback
|
||||
{
|
||||
get => _compilationCallback;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_compilationCallback = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that determines if Razor files (Razor Views and Razor Pages) are recompiled and updated
|
||||
/// if files change on disk.
|
||||
/// <para>
|
||||
/// When <see langword="true"/>, MVC will use <see cref="IFileProvider.Watch(string)"/> to watch for changes to
|
||||
/// Razor files in configured <see cref="IFileProvider"/> instances.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The default value is <see langword="true"/> if the version is <see cref = "CompatibilityVersion.Version_2_1" />
|
||||
/// or earlier. If the version is later and <see cref= "IHostingEnvironment.EnvironmentName" /> is <c>Development</c>,
|
||||
/// the default value is <see langword="true"/>. Otherwise, the default value is <see langword="false" />.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This property is associated with a compatibility switch and can provide a different behavior depending on
|
||||
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
|
||||
/// guidance and examples of setting the application's compatibility version.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Configuring the desired value of the compatibility switch by calling this property's setter will take
|
||||
/// precedence over the value implied by the application's <see cref="CompatibilityVersion"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
|
||||
/// lower then this setting will have the value <see langword="true"/> unless explicitly configured.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_2"/> or
|
||||
/// higher then this setting will have the value <see langword="false"/> unless
|
||||
/// <see cref="IHostingEnvironment.EnvironmentName"/> is <c>Development</c> or the value is explicitly configured.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public bool AllowRecompilingViewsOnFileChange
|
||||
{
|
||||
// Note: When compatibility switches are removed in 3.0, this property should be retained as a regular boolean property.
|
||||
get => _allowRecompilingViewsOnFileChange.Value;
|
||||
set => _allowRecompilingViewsOnFileChange.Value = value;
|
||||
}
|
||||
|
||||
IEnumerator<ICompatibilitySwitch> IEnumerable<ICompatibilitySwitch>.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<ICompatibilitySwitch>)_switches).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => _switches.GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,45 +2,20 @@
|
|||
// 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.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
internal class RazorViewEngineOptionsSetup :
|
||||
ConfigureCompatibilityOptions<RazorViewEngineOptions>,
|
||||
IConfigureOptions<RazorViewEngineOptions>
|
||||
internal class RazorViewEngineOptionsSetup : IConfigureOptions<RazorViewEngineOptions>
|
||||
{
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
|
||||
public RazorViewEngineOptionsSetup(
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<MvcCompatibilityOptions> compatibilityOptions)
|
||||
: base(loggerFactory, compatibilityOptions)
|
||||
public RazorViewEngineOptionsSetup(IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
_hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
|
||||
}
|
||||
|
||||
protected override IReadOnlyDictionary<string, object> DefaultValues
|
||||
{
|
||||
get
|
||||
{
|
||||
var values = new Dictionary<string, object>();
|
||||
if (Version < CompatibilityVersion.Version_2_2)
|
||||
{
|
||||
// Default to true in 2.1 or earlier. In 2.2, we have to conditionally enable this
|
||||
// and consequently this switch has no default value.
|
||||
values[nameof(RazorViewEngineOptions.AllowRecompilingViewsOnFileChange)] = true;
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
public void Configure(RazorViewEngineOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
|
@ -48,22 +23,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (_hostingEnvironment.ContentRootFileProvider != null)
|
||||
{
|
||||
options.FileProviders.Add(_hostingEnvironment.ContentRootFileProvider);
|
||||
}
|
||||
|
||||
options.ViewLocationFormats.Add("/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
|
||||
options.ViewLocationFormats.Add("/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
|
||||
|
||||
options.AreaViewLocationFormats.Add("/Areas/{2}/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
|
||||
options.AreaViewLocationFormats.Add("/Areas/{2}/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
|
||||
options.AreaViewLocationFormats.Add("/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
|
||||
|
||||
if (_hostingEnvironment.IsDevelopment())
|
||||
{
|
||||
options.AllowRecompilingViewsOnFileChange = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,24 @@
|
|||
[
|
||||
{
|
||||
"TypeId": "public abstract class Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorReferenceManager",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"TypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Compilation.CompilationFailedException : System.Exception, Microsoft.AspNetCore.Diagnostics.ICompilationException",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"TypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Compilation.MetadataReferenceFeature",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"TypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Compilation.MetadataReferenceFeatureProvider : Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationFeatureProvider<Microsoft.AspNetCore.Mvc.Razor.Compilation.MetadataReferenceFeature>",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"TypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Compilation.RoslynCompilationContext",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"TypeId": "public class Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine : Microsoft.AspNetCore.Mvc.Razor.IRazorViewEngine",
|
||||
"MemberId": "public .ctor(Microsoft.AspNetCore.Mvc.Razor.IRazorPageFactoryProvider pageFactory, Microsoft.AspNetCore.Mvc.Razor.IRazorPageActivator pageActivator, System.Text.Encodings.Web.HtmlEncoder htmlEncoder, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Mvc.Razor.RazorViewEngineOptions> optionsAccessor, Microsoft.AspNetCore.Razor.Language.RazorProject razorProject, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, System.Diagnostics.DiagnosticSource diagnosticSource)",
|
||||
|
@ -13,5 +33,35 @@
|
|||
"TypeId": "public class Microsoft.AspNetCore.Mvc.Razor.RazorView : Microsoft.AspNetCore.Mvc.ViewEngines.IView",
|
||||
"MemberId": "public .ctor(Microsoft.AspNetCore.Mvc.Razor.IRazorViewEngine viewEngine, Microsoft.AspNetCore.Mvc.Razor.IRazorPageActivator pageActivator, System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Mvc.Razor.IRazorPage> viewStartPages, Microsoft.AspNetCore.Mvc.Razor.IRazorPage razorPage, System.Text.Encodings.Web.HtmlEncoder htmlEncoder, System.Diagnostics.DiagnosticSource diagnosticSource)",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"TypeId": "public class Microsoft.AspNetCore.Mvc.Razor.RazorViewEngineOptions",
|
||||
"MemberId": "public System.Action<Microsoft.AspNetCore.Mvc.Razor.Compilation.RoslynCompilationContext> get_CompilationCallback()",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"TypeId": "public class Microsoft.AspNetCore.Mvc.Razor.RazorViewEngineOptions",
|
||||
"MemberId": "public System.Collections.Generic.IList<Microsoft.CodeAnalysis.MetadataReference> get_AdditionalCompilationReferences()",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"TypeId": "public class Microsoft.AspNetCore.Mvc.Razor.RazorViewEngineOptions",
|
||||
"MemberId": "public System.Void set_CompilationCallback(System.Action<Microsoft.AspNetCore.Mvc.Razor.Compilation.RoslynCompilationContext> value)",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"TypeId": "public class Microsoft.AspNetCore.Mvc.Razor.RazorViewEngineOptions",
|
||||
"MemberId": "public System.Collections.Generic.IList<Microsoft.Extensions.FileProviders.IFileProvider> get_FileProviders()",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"TypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Compilation.CompiledViewDescriptor",
|
||||
"MemberId": "public System.Boolean get_IsPrecompiled()",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"TypeId": "public class Microsoft.AspNetCore.Mvc.Razor.Compilation.CompiledViewDescriptor",
|
||||
"MemberId": "public System.Void set_IsPrecompiled(System.Boolean value)",
|
||||
"Kind": "Removal"
|
||||
}
|
||||
]
|
||||
]
|
|
@ -6,11 +6,9 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.AspNetCore.Razor.Hosting;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
@ -18,23 +16,20 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
{
|
||||
internal class CompiledPageRouteModelProvider : IPageRouteModelProvider
|
||||
{
|
||||
private static readonly string RazorPageDocumentKind = "mvc.1.0.razor-page";
|
||||
private static readonly string RouteTemplateKey = "RouteTemplate";
|
||||
private readonly ApplicationPartManager _applicationManager;
|
||||
private readonly RazorPagesOptions _pagesOptions;
|
||||
private readonly RazorProjectEngine _razorProjectEngine;
|
||||
private readonly ILogger<CompiledPageRouteModelProvider> _logger;
|
||||
private readonly PageRouteModelFactory _routeModelFactory;
|
||||
|
||||
public CompiledPageRouteModelProvider(
|
||||
ApplicationPartManager applicationManager,
|
||||
IOptions<RazorPagesOptions> pagesOptionsAccessor,
|
||||
RazorProjectEngine razorProjectEngine,
|
||||
ILogger<CompiledPageRouteModelProvider> logger)
|
||||
{
|
||||
_applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
|
||||
_pagesOptions = pagesOptionsAccessor?.Value ?? throw new ArgumentNullException(nameof(pagesOptionsAccessor));
|
||||
_razorProjectEngine = razorProjectEngine ?? throw new ArgumentNullException(nameof(razorProjectEngine));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(razorProjectEngine));
|
||||
_routeModelFactory = new PageRouteModelFactory(_pagesOptions, _logger);
|
||||
_routeModelFactory = new PageRouteModelFactory(_pagesOptions, logger);
|
||||
}
|
||||
|
||||
public int Order => -1000;
|
||||
|
@ -75,11 +70,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!viewDescriptor.IsPrecompiled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsRazorPage(viewDescriptor))
|
||||
{
|
||||
yield return viewDescriptor;
|
||||
|
@ -90,7 +80,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
{
|
||||
if (viewDescriptor.Item != null)
|
||||
{
|
||||
return viewDescriptor.Item.Kind == RazorPageDocumentClassifierPass.RazorPageDocumentKind;
|
||||
return viewDescriptor.Item.Kind == RazorPageDocumentKind;
|
||||
}
|
||||
else if (viewDescriptor.ViewAttribute != null)
|
||||
{
|
||||
|
@ -119,12 +109,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
var areaRootDirectory = "/Areas/";
|
||||
foreach (var viewDescriptor in GetViewDescriptors(_applicationManager))
|
||||
{
|
||||
if (viewDescriptor.Item != null && !ChecksumValidator.IsItemValid(_razorProjectEngine.FileSystem, viewDescriptor.Item))
|
||||
{
|
||||
// If we get here, this compiled Page has different local content, so ignore it.
|
||||
continue;
|
||||
}
|
||||
|
||||
var relativePath = viewDescriptor.RelativePath;
|
||||
var routeTemplate = GetRouteTemplate(viewDescriptor);
|
||||
PageRouteModel routeModel = null;
|
||||
|
@ -158,7 +142,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
{
|
||||
return viewDescriptor.Item.Metadata
|
||||
.OfType<RazorCompiledItemMetadataAttribute>()
|
||||
.FirstOrDefault(f => f.Key == RazorPageDocumentClassifierPass.RouteTemplateKey)
|
||||
.FirstOrDefault(f => f.Key == RouteTemplateKey)
|
||||
?.Value;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,119 +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.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||
{
|
||||
internal class RazorProjectPageRouteModelProvider : IPageRouteModelProvider
|
||||
{
|
||||
private const string AreaRootDirectory = "/Areas";
|
||||
private readonly RazorProjectFileSystem _razorFileSystem;
|
||||
private readonly RazorPagesOptions _pagesOptions;
|
||||
private readonly PageRouteModelFactory _routeModelFactory;
|
||||
private readonly ILogger<RazorProjectPageRouteModelProvider> _logger;
|
||||
|
||||
public RazorProjectPageRouteModelProvider(
|
||||
RazorProjectFileSystem razorFileSystem,
|
||||
IOptions<RazorPagesOptions> pagesOptionsAccessor,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
_razorFileSystem = razorFileSystem;
|
||||
_pagesOptions = pagesOptionsAccessor.Value;
|
||||
_logger = loggerFactory.CreateLogger<RazorProjectPageRouteModelProvider>();
|
||||
_routeModelFactory = new PageRouteModelFactory(_pagesOptions, _logger);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Ordered to execute after <see cref="CompiledPageRouteModelProvider"/>.
|
||||
/// </remarks>
|
||||
public int Order => -1000 + 10;
|
||||
|
||||
public void OnProvidersExecuted(PageRouteModelProviderContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnProvidersExecuting(PageRouteModelProviderContext context)
|
||||
{
|
||||
// When RootDirectory and AreaRootDirectory overlap, e.g. RootDirectory = /, AreaRootDirectory = /Areas;
|
||||
// we need to ensure that the page is only route-able via the area route. By adding area routes first,
|
||||
// we'll ensure non area routes get skipped when it encounters an IsAlreadyRegistered check.
|
||||
|
||||
if (_pagesOptions.AllowAreas)
|
||||
{
|
||||
AddAreaPageModels(context);
|
||||
}
|
||||
|
||||
AddPageModels(context);
|
||||
}
|
||||
|
||||
private void AddPageModels(PageRouteModelProviderContext context)
|
||||
{
|
||||
foreach (var item in _razorFileSystem.EnumerateItems(_pagesOptions.RootDirectory))
|
||||
{
|
||||
var relativePath = item.CombinedPath;
|
||||
if (context.RouteModels.Any(m => string.Equals(relativePath, m.RelativePath, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
// A route for this file was already registered either by the CompiledPageRouteModel or as an area route.
|
||||
// by this provider. Skip registering an additional entry.
|
||||
|
||||
// Note: We're comparing duplicates based on root-relative paths. This eliminates a page from being discovered
|
||||
// by overlapping area and non-area routes where ViewEnginePath would be different.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!PageDirectiveFeature.TryGetPageDirective(_logger, item, out var routeTemplate))
|
||||
{
|
||||
// .cshtml pages without @page are not RazorPages.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_pagesOptions.AllowAreas && relativePath.StartsWith(AreaRootDirectory, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Ignore Razor pages that are under the area root directory when AllowAreas is enabled.
|
||||
// Conforming page paths will be added by AddAreaPageModels.
|
||||
_logger.UnsupportedAreaPath(relativePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
var routeModel = _routeModelFactory.CreateRouteModel(relativePath, routeTemplate);
|
||||
if (routeModel != null)
|
||||
{
|
||||
context.RouteModels.Add(routeModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddAreaPageModels(PageRouteModelProviderContext context)
|
||||
{
|
||||
foreach (var item in _razorFileSystem.EnumerateItems(AreaRootDirectory))
|
||||
{
|
||||
var relativePath = item.CombinedPath;
|
||||
if (context.RouteModels.Any(m => string.Equals(relativePath, m.RelativePath, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
// A route for this file was already registered either by the CompiledPageRouteModel.
|
||||
// Skip registering an additional entry.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!PageDirectiveFeature.TryGetPageDirective(_logger, item, out var routeTemplate))
|
||||
{
|
||||
// .cshtml pages without @page are not RazorPages.
|
||||
continue;
|
||||
}
|
||||
|
||||
var routeModel = _routeModelFactory.CreateAreaRouteModel(relativePath, routeTemplate);
|
||||
if (routeModel != null)
|
||||
{
|
||||
context.RouteModels.Add(routeModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -88,9 +88,6 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
// Action description and invocation
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Singleton<IActionDescriptorProvider, PageActionDescriptorProvider>());
|
||||
services.TryAddSingleton<IActionDescriptorChangeProvider, PageActionDescriptorChangeProvider>();
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Singleton<IPageRouteModelProvider, RazorProjectPageRouteModelProvider>());
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Singleton<IPageRouteModelProvider, CompiledPageRouteModelProvider>());
|
||||
|
||||
|
|
|
@ -1,116 +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.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||
{
|
||||
internal class PageActionDescriptorChangeProvider : IActionDescriptorChangeProvider
|
||||
{
|
||||
private readonly IFileProvider _fileProvider;
|
||||
private readonly string[] _searchPatterns;
|
||||
private readonly string[] _additionalFilesToTrack;
|
||||
private readonly bool _watchForChanges;
|
||||
|
||||
public PageActionDescriptorChangeProvider(
|
||||
RazorTemplateEngine templateEngine,
|
||||
IRazorViewEngineFileProviderAccessor fileProviderAccessor,
|
||||
IOptions<RazorPagesOptions> razorPagesOptions,
|
||||
IOptions<RazorViewEngineOptions> razorViewEngineOptions)
|
||||
{
|
||||
if (templateEngine == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(templateEngine));
|
||||
}
|
||||
|
||||
if (fileProviderAccessor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(fileProviderAccessor));
|
||||
}
|
||||
|
||||
if (razorPagesOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(razorPagesOptions));
|
||||
}
|
||||
|
||||
_watchForChanges = razorViewEngineOptions.Value.AllowRecompilingViewsOnFileChange;
|
||||
if (!_watchForChanges)
|
||||
{
|
||||
// No need to do any additional work if we aren't going to be watching for file changes.
|
||||
return;
|
||||
}
|
||||
|
||||
_fileProvider = fileProviderAccessor.FileProvider;
|
||||
|
||||
var rootDirectory = razorPagesOptions.Value.RootDirectory;
|
||||
Debug.Assert(!string.IsNullOrEmpty(rootDirectory));
|
||||
rootDirectory = rootDirectory.TrimEnd('/');
|
||||
|
||||
// Search pattern that matches all cshtml files under the Pages RootDirectory
|
||||
var pagesRootSearchPattern = rootDirectory + "/**/*.cshtml";
|
||||
|
||||
// pagesRootSearchPattern will miss _ViewImports outside the RootDirectory despite these influencing
|
||||
// compilation. e.g. when RootDirectory = /Dir1/Dir2, the search pattern will ignore changes to
|
||||
// [/_ViewImports.cshtml, /Dir1/_ViewImports.cshtml]. We need to additionally account for these.
|
||||
var importFileAtPagesRoot = rootDirectory + "/" + templateEngine.Options.ImportsFileName;
|
||||
var additionalImportFilePaths = templateEngine.GetImportItems(importFileAtPagesRoot)
|
||||
.Select(item => item.FilePath);
|
||||
|
||||
if (razorPagesOptions.Value.AllowAreas)
|
||||
{
|
||||
// Search pattern that matches all cshtml files under the Pages AreaRootDirectory
|
||||
var areaRootSearchPattern = "/Areas/**/*.cshtml";
|
||||
|
||||
var importFileAtAreaPagesRoot = $"/Areas/{templateEngine.Options.ImportsFileName}";
|
||||
var importPathsOutsideAreaPagesRoot = templateEngine.GetImportItems(importFileAtAreaPagesRoot)
|
||||
.Select(item => item.FilePath);
|
||||
|
||||
additionalImportFilePaths = additionalImportFilePaths
|
||||
.Concat(importPathsOutsideAreaPagesRoot)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
_searchPatterns = new[]
|
||||
{
|
||||
pagesRootSearchPattern,
|
||||
areaRootSearchPattern
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
_searchPatterns = new[] { pagesRootSearchPattern, };
|
||||
}
|
||||
|
||||
_additionalFilesToTrack = additionalImportFilePaths.ToArray();
|
||||
}
|
||||
|
||||
public IChangeToken GetChangeToken()
|
||||
{
|
||||
if (!_watchForChanges)
|
||||
{
|
||||
return NullChangeToken.Singleton;
|
||||
}
|
||||
|
||||
var changeTokens = new IChangeToken[_additionalFilesToTrack.Length + _searchPatterns.Length];
|
||||
for (var i = 0; i < _additionalFilesToTrack.Length; i++)
|
||||
{
|
||||
changeTokens[i] = _fileProvider.Watch(_additionalFilesToTrack[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < _searchPatterns.Length; i++)
|
||||
{
|
||||
var wildcardChangeToken = _fileProvider.Watch(_searchPatterns[i]);
|
||||
changeTokens[_additionalFilesToTrack.Length + i] = wildcardChangeToken;
|
||||
}
|
||||
|
||||
return new CompositeChangeToken(changeTokens);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Mvc.Infrastructure;
|
|||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
@ -36,7 +35,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
private readonly MvcOptions _mvcOptions;
|
||||
private readonly HtmlHelperOptions _htmlHelperOptions;
|
||||
private readonly IPageHandlerMethodSelector _selector;
|
||||
private readonly RazorProjectFileSystem _razorFileSystem;
|
||||
private readonly DiagnosticListener _diagnosticListener;
|
||||
private readonly ILogger<PageActionInvoker> _logger;
|
||||
private readonly IActionResultTypeMapper _mapper;
|
||||
|
@ -56,7 +54,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
IOptions<MvcOptions> mvcOptions,
|
||||
IOptions<HtmlHelperOptions> htmlHelperOptions,
|
||||
IPageHandlerMethodSelector selector,
|
||||
RazorProjectFileSystem razorFileSystem,
|
||||
DiagnosticListener diagnosticListener,
|
||||
ILoggerFactory loggerFactory,
|
||||
IActionResultTypeMapper mapper)
|
||||
|
@ -75,7 +72,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
_mvcOptions = mvcOptions.Value;
|
||||
_htmlHelperOptions = htmlHelperOptions.Value;
|
||||
_selector = selector;
|
||||
_razorFileSystem = razorFileSystem;
|
||||
_diagnosticListener = diagnosticListener;
|
||||
_logger = loggerFactory.CreateLogger<PageActionInvoker>();
|
||||
_mapper = mapper;
|
||||
|
@ -215,12 +211,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
{
|
||||
var viewStartFactories = new List<Func<IRazorPage>>();
|
||||
// Always pick up all _ViewStarts, including the ones outside the Pages root.
|
||||
var viewStartItems = _razorFileSystem.FindHierarchicalItems(
|
||||
descriptor.RelativePath,
|
||||
ViewStartFileName);
|
||||
foreach (var item in viewStartItems)
|
||||
foreach (var filePath in RazorFileHierarchy.GetViewStartPaths(descriptor.RelativePath))
|
||||
{
|
||||
var factoryResult = _razorPageFactoryProvider.CreateFactory(item.FilePath);
|
||||
var factoryResult = _razorPageFactoryProvider.CreateFactory(filePath);
|
||||
if (factoryResult.Success)
|
||||
{
|
||||
viewStartFactories.Insert(0, factoryResult.RazorPageFactory);
|
||||
|
|
|
@ -1,110 +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 Microsoft.AspNetCore.Mvc.Razor.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||
{
|
||||
public static class PageDirectiveFeature
|
||||
{
|
||||
private static readonly RazorProjectEngine PageDirectiveEngine = RazorProjectEngine.Create(RazorConfiguration.Default, new EmptyRazorProjectFileSystem(), builder =>
|
||||
{
|
||||
for (var i = builder.Phases.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var phase = builder.Phases[i];
|
||||
builder.Phases.RemoveAt(i);
|
||||
if (phase is IRazorDocumentClassifierPhase)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RazorExtensions.Register(builder);
|
||||
builder.Features.Add(new PageDirectiveParserOptionsFeature());
|
||||
});
|
||||
|
||||
public static bool TryGetPageDirective(ILogger logger, RazorProjectItem projectItem, out string template)
|
||||
{
|
||||
if (projectItem == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectItem));
|
||||
}
|
||||
|
||||
var codeDocument = PageDirectiveEngine.Process(projectItem);
|
||||
|
||||
var documentIRNode = codeDocument.GetDocumentIntermediateNode();
|
||||
if (PageDirective.TryGetPageDirective(documentIRNode, out var pageDirective))
|
||||
{
|
||||
if (pageDirective.DirectiveNode is MalformedDirectiveIntermediateNode malformedNode)
|
||||
{
|
||||
logger.MalformedPageDirective(projectItem.FilePath, malformedNode.Diagnostics);
|
||||
}
|
||||
|
||||
template = pageDirective.RouteTemplate;
|
||||
return true;
|
||||
}
|
||||
|
||||
template = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private class PageDirectiveParserOptionsFeature : RazorEngineFeatureBase, IConfigureRazorParserOptionsFeature
|
||||
{
|
||||
public int Order { get; }
|
||||
|
||||
public void Configure(RazorParserOptionsBuilder options)
|
||||
{
|
||||
options.ParseLeadingDirectives = true;
|
||||
}
|
||||
}
|
||||
|
||||
private class EmptyRazorProjectFileSystem : RazorProjectFileSystem
|
||||
{
|
||||
public override IEnumerable<RazorProjectItem> EnumerateItems(string basePath)
|
||||
{
|
||||
return Enumerable.Empty<RazorProjectItem>();
|
||||
}
|
||||
|
||||
public override IEnumerable<RazorProjectItem> FindHierarchicalItems(string basePath, string path, string fileName)
|
||||
{
|
||||
return Enumerable.Empty<RazorProjectItem>();
|
||||
}
|
||||
|
||||
public override RazorProjectItem GetItem(string path)
|
||||
{
|
||||
return new NotFoundProjectItem(string.Empty, path);
|
||||
}
|
||||
|
||||
private class NotFoundProjectItem : RazorProjectItem
|
||||
{
|
||||
public NotFoundProjectItem(string basePath, string path)
|
||||
{
|
||||
BasePath = basePath;
|
||||
FilePath = path;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string BasePath { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string FilePath { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Exists => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string PhysicalPath => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Stream Read() => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,11 +2,9 @@
|
|||
// 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.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||
|
@ -151,20 +149,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
|||
_pageFilterShortCircuit(logger, filter, null);
|
||||
}
|
||||
|
||||
public static void MalformedPageDirective(this ILogger logger, string filePath, IList<RazorDiagnostic> diagnostics)
|
||||
{
|
||||
if (logger.IsEnabled(LogLevel.Warning))
|
||||
{
|
||||
var messages = new string[diagnostics.Count];
|
||||
for (var i = 0; i < diagnostics.Count; i++)
|
||||
{
|
||||
messages[i] = diagnostics[i].GetMessage();
|
||||
}
|
||||
|
||||
_malformedPageDirective(logger, filePath, messages, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void NotMostEffectiveFilter(this ILogger logger, Type policyType)
|
||||
{
|
||||
_notMostEffectiveFilter(logger, policyType, null);
|
||||
|
|
|
@ -23,5 +23,9 @@
|
|||
"TypeId": "public class Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageResultExecutor : Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor",
|
||||
"MemberId": "public .ctor(Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory writerFactory, Microsoft.AspNetCore.Mvc.ViewEngines.ICompositeViewEngine compositeViewEngine, Microsoft.AspNetCore.Mvc.Razor.IRazorViewEngine razorViewEngine, Microsoft.AspNetCore.Mvc.Razor.IRazorPageActivator razorPageActivator, System.Diagnostics.DiagnosticSource diagnosticSource, System.Text.Encodings.Web.HtmlEncoder htmlEncoder)",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"TypeId": "public static class Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageDirectiveFeature",
|
||||
"Kind": "Removal"
|
||||
}
|
||||
]
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
|
||||
public HttpClient Client { get; }
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "https://github.com/aspnet/Mvc/issues/8753")]
|
||||
public async Task CompilationFailuresAreListedByErrorPageMiddleware()
|
||||
{
|
||||
// Arrange
|
||||
|
@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.DoesNotContain(PreserveCompilationContextMessage, content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "https://github.com/aspnet/Mvc/issues/8753")]
|
||||
public async Task ParseFailuresAreListedByErrorPageMiddleware()
|
||||
{
|
||||
// Arrange
|
||||
|
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Contains(expected, content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "https://github.com/aspnet/Mvc/issues/8753")]
|
||||
public async Task CompilationFailuresFromViewImportsAreListed()
|
||||
{
|
||||
// Arrange
|
||||
|
@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Contains(expectedCompilationContent, content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "https://github.com/aspnet/Mvc/issues/8753")]
|
||||
public async Task RuntimeErrorAreListedByErrorPageMiddleware()
|
||||
{
|
||||
// Arrange
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Equal("Hello from buildtime-compiled precompilation view!", responseBody.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "https://github.com/aspnet/Mvc/issues/8753")]
|
||||
public async Task Rzc_LocalPageWithDifferentContent_IsUsed()
|
||||
{
|
||||
// Act
|
||||
|
@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Equal("Hello from runtime-compiled rzc page!", responseBody.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "https://github.com/aspnet/Mvc/issues/8753")]
|
||||
public async Task Rzc_LocalViewWithDifferentContent_IsUsed()
|
||||
{
|
||||
// Act
|
||||
|
|
|
@ -1,131 +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.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
// Verifies that updating Razor files (views and pages) with AllowRecompilingViewsOnFileChange=true works
|
||||
public class RazorFileUpdateTests : IClassFixture<MvcTestFixture<RazorWebSite.Startup>>
|
||||
{
|
||||
public RazorFileUpdateTests(MvcTestFixture<RazorWebSite.Startup> fixture)
|
||||
{
|
||||
var factory = fixture.WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.UseStartup<RazorWebSite.Startup>();
|
||||
builder.ConfigureTestServices(services =>
|
||||
{
|
||||
services.Configure<RazorViewEngineOptions>(options => options.AllowRecompilingViewsOnFileChange = true);
|
||||
});
|
||||
});
|
||||
Client = factory.CreateDefaultClient();
|
||||
}
|
||||
|
||||
public HttpClient Client { get; }
|
||||
|
||||
[Fact]
|
||||
public async Task RazorViews_AreUpdatedOnChange()
|
||||
{
|
||||
// Arrange
|
||||
var expected1 = "Original content";
|
||||
var expected2 = "New content";
|
||||
var path = "/Views/UpdateableShared/_Partial.cshtml";
|
||||
|
||||
// Act - 1
|
||||
var body = await Client.GetStringAsync("/UpdateableFileProvider");
|
||||
|
||||
// Assert - 1
|
||||
Assert.Equal(expected1, body.Trim(), ignoreLineEndingDifferences: true);
|
||||
|
||||
// Act - 2
|
||||
await UpdateFile(path, expected2);
|
||||
body = await Client.GetStringAsync("/UpdateableFileProvider");
|
||||
|
||||
// Assert - 2
|
||||
Assert.Equal(expected2, body.Trim(), ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RazorViews_AreUpdatedWhenViewImportsChange()
|
||||
{
|
||||
// Arrange
|
||||
var content = "@GetType().Assembly.FullName";
|
||||
await UpdateFile("/Views/UpdateableIndex/Index.cshtml", content);
|
||||
var initial = await Client.GetStringAsync("/UpdateableFileProvider");
|
||||
|
||||
// Act
|
||||
// Trigger a change in ViewImports
|
||||
await UpdateFile("/Views/UpdateableIndex/_ViewImports.cshtml", string.Empty);
|
||||
var updated = await Client.GetStringAsync("/UpdateableFileProvider");
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(initial, updated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RazorPages_AreUpdatedOnChange()
|
||||
{
|
||||
// Arrange
|
||||
var expected1 = "Original content";
|
||||
var expected2 = "New content";
|
||||
|
||||
// Act - 1
|
||||
var body = await Client.GetStringAsync("/UpdateablePage");
|
||||
|
||||
// Assert - 1
|
||||
Assert.Equal(expected1, body.Trim(), ignoreLineEndingDifferences: true);
|
||||
|
||||
// Act - 2
|
||||
await UpdateRazorPages();
|
||||
await UpdateFile("/Pages/UpdateablePage.cshtml", "@page" + Environment.NewLine + expected2);
|
||||
body = await Client.GetStringAsync("/UpdateablePage");
|
||||
|
||||
// Assert - 2
|
||||
Assert.Equal(expected2, body.Trim(), ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RazorPages_AreUpdatedWhenViewImportsChange()
|
||||
{
|
||||
// Arrange
|
||||
var content = "@GetType().Assembly.FullName";
|
||||
await UpdateFile("/Pages/UpdateablePage.cshtml", "@page" + Environment.NewLine + content);
|
||||
var initial = await Client.GetStringAsync("/UpdateablePage");
|
||||
|
||||
// Act
|
||||
// Trigger a change in ViewImports
|
||||
await UpdateRazorPages();
|
||||
await UpdateFile("/Pages/UpdateablePage.cshtml", "@page" + Environment.NewLine + content);
|
||||
var updated = await Client.GetStringAsync("/UpdateablePage");
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(initial, updated);
|
||||
}
|
||||
|
||||
private async Task UpdateFile(string path, string content)
|
||||
{
|
||||
var updateContent = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
{ "path", path },
|
||||
{ "content", content },
|
||||
});
|
||||
|
||||
var response = await Client.PostAsync($"/UpdateableFileProvider/Update", updateContent);
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
private async Task UpdateRazorPages()
|
||||
{
|
||||
var response = await Client.PostAsync($"/UpdateableFileProvider/UpdateRazorPages", new StringContent(string.Empty));
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -66,9 +67,12 @@ ViewWithNestedLayout-Content
|
|||
public async Task RazorView_ExecutesPageAndLayout(string actionName, string expected)
|
||||
{
|
||||
// Arrange & Act
|
||||
var body = await Client.GetStringAsync("http://localhost/ViewEngine/" + actionName);
|
||||
var response = await Client.GetAsync("http://localhost/ViewEngine/" + actionName);
|
||||
|
||||
// Assert
|
||||
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
Assert.Equal(expected, body.Trim(), ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
|
@ -240,17 +244,19 @@ ViewWithNestedLayout-Content
|
|||
public async Task RazorViewEngine_RendersPartialViews(string actionName, string expected)
|
||||
{
|
||||
// Arrange & Act
|
||||
var body = await Client.GetStringAsync("http://localhost/PartialViewEngine/" + actionName);
|
||||
var response = await Client.GetAsync("http://localhost/PartialViewEngine/" + actionName);
|
||||
|
||||
// Assert
|
||||
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal(expected, body.Trim(), ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "https://github.com/aspnet/Mvc/issues/8754")]
|
||||
public Task RazorViewEngine_RendersViewsFromEmbeddedFileProvider_WhenLookedupByName()
|
||||
=> RazorViewEngine_RendersIndexViewsFromEmbeddedFileProvider("/EmbeddedViews/LookupByName");
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "https://github.com/aspnet/Mvc/issues/8754")]
|
||||
public Task RazorViewEngine_RendersViewsFromEmbeddedFileProvider_WhenLookedupByPath()
|
||||
=> RazorViewEngine_RendersIndexViewsFromEmbeddedFileProvider("/EmbeddedViews/LookupByPath");
|
||||
|
||||
|
@ -479,7 +485,7 @@ Partial";
|
|||
Assert.Equal(expected, responseContent, ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "https://github.com/aspnet/Mvc/issues/8754")]
|
||||
public async Task ViewEngine_ResolvesPathsWithSlashesThatDoNotHaveExtensions()
|
||||
{
|
||||
// Arrange
|
||||
|
|
|
@ -1,408 +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.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Emit;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using DependencyContextCompilationOptions = Microsoft.Extensions.DependencyModel.CompilationOptions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
public class CSharpCompilerTest
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
private readonly RazorReferenceManager ReferenceManager = Mock.Of<RazorReferenceManager>();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
public void GetCompilationOptions_ReturnsDefaultOptionsIfApplicationNameIsNullOrEmpty(string name)
|
||||
{
|
||||
// Arrange
|
||||
var hostingEnvironment = Mock.Of<IHostingEnvironment>(e => e.ApplicationName == name);
|
||||
var compiler = new CSharpCompiler(ReferenceManager, hostingEnvironment);
|
||||
|
||||
// Act
|
||||
var options = compiler.GetDependencyContextCompilationOptions();
|
||||
|
||||
// Assert
|
||||
Assert.Same(DependencyContextCompilationOptions.Default, options);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCompilationOptions_ReturnsDefaultOptionsIfApplicationDoesNotHaveDependencyContext()
|
||||
{
|
||||
// Arrange
|
||||
var hostingEnvironment = new Mock<IHostingEnvironment>();
|
||||
hostingEnvironment.SetupGet(e => e.ApplicationName)
|
||||
.Returns(typeof(Controller).GetTypeInfo().Assembly.GetName().Name);
|
||||
var compiler = new CSharpCompiler(ReferenceManager, hostingEnvironment.Object);
|
||||
|
||||
// Act
|
||||
var options = compiler.GetDependencyContextCompilationOptions();
|
||||
|
||||
// Assert
|
||||
Assert.Same(DependencyContextCompilationOptions.Default, options);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SetsCompilationOptionsFromDependencyContext()
|
||||
{
|
||||
// Arrange
|
||||
var hostingEnvironment = new Mock<IHostingEnvironment>();
|
||||
hostingEnvironment.SetupGet(e => e.ApplicationName)
|
||||
.Returns(GetType().GetTypeInfo().Assembly.GetName().Name);
|
||||
var compiler = new CSharpCompiler(ReferenceManager, hostingEnvironment.Object);
|
||||
|
||||
// Act & Assert
|
||||
var parseOptions = compiler.ParseOptions;
|
||||
Assert.Contains("SOME_TEST_DEFINE", parseOptions.PreprocessorSymbolNames);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Development", OptimizationLevel.Debug)]
|
||||
[InlineData("Staging", OptimizationLevel.Release)]
|
||||
[InlineData("Production", OptimizationLevel.Release)]
|
||||
public void Constructor_SetsOptimizationLevelBasedOnEnvironment(
|
||||
string environment,
|
||||
OptimizationLevel expected)
|
||||
{
|
||||
// Arrange
|
||||
var options = new RazorViewEngineOptions();
|
||||
var hostingEnvironment = new Mock<IHostingEnvironment>();
|
||||
hostingEnvironment.SetupGet(e => e.EnvironmentName)
|
||||
.Returns(environment);
|
||||
var compiler = new CSharpCompiler(ReferenceManager, hostingEnvironment.Object);
|
||||
|
||||
// Act & Assert
|
||||
var compilationOptions = compiler.CSharpCompilationOptions;
|
||||
Assert.Equal(expected, compilationOptions.OptimizationLevel);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Development", "DEBUG")]
|
||||
[InlineData("Staging", "RELEASE")]
|
||||
[InlineData("Production", "RELEASE")]
|
||||
public void EnsureOptions_SetsPreprocessorSymbols(string environment, string expectedConfiguration)
|
||||
{
|
||||
// Arrange
|
||||
var options = new RazorViewEngineOptions();
|
||||
var hostingEnvironment = new Mock<IHostingEnvironment>();
|
||||
hostingEnvironment.SetupGet(e => e.EnvironmentName)
|
||||
.Returns(environment);
|
||||
var compiler = new CSharpCompiler(ReferenceManager, hostingEnvironment.Object);
|
||||
|
||||
// Act & Assert
|
||||
var parseOptions = compiler.ParseOptions;
|
||||
Assert.Equal(new[] { expectedConfiguration }, parseOptions.PreprocessorSymbolNames);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureOptions_ConfiguresDefaultCompilationOptions()
|
||||
{
|
||||
// Arrange
|
||||
var hostingEnvironment = Mock.Of<IHostingEnvironment>(h => h.EnvironmentName == "Development");
|
||||
var compiler = new CSharpCompiler(ReferenceManager, hostingEnvironment);
|
||||
|
||||
// Act & Assert
|
||||
var compilationOptions = compiler.CSharpCompilationOptions;
|
||||
Assert.False(compilationOptions.AllowUnsafe);
|
||||
Assert.Equal(ReportDiagnostic.Default, compilationOptions.GeneralDiagnosticOption);
|
||||
Assert.Equal(OptimizationLevel.Debug, compilationOptions.OptimizationLevel);
|
||||
Assert.Collection(compilationOptions.SpecificDiagnosticOptions.OrderBy(d => d.Key),
|
||||
item =>
|
||||
{
|
||||
Assert.Equal("CS1701", item.Key);
|
||||
Assert.Equal(ReportDiagnostic.Suppress, item.Value);
|
||||
},
|
||||
item =>
|
||||
{
|
||||
Assert.Equal("CS1702", item.Key);
|
||||
Assert.Equal(ReportDiagnostic.Suppress, item.Value);
|
||||
},
|
||||
item =>
|
||||
{
|
||||
Assert.Equal("CS1705", item.Key);
|
||||
Assert.Equal(ReportDiagnostic.Suppress, item.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureOptions_ConfiguresDefaultParseOptions()
|
||||
{
|
||||
// Arrange
|
||||
var hostingEnvironment = Mock.Of<IHostingEnvironment>(h => h.EnvironmentName == "Development");
|
||||
var compiler = new CSharpCompiler(ReferenceManager, hostingEnvironment);
|
||||
|
||||
// Act & Assert
|
||||
var parseOptions = compiler.ParseOptions;
|
||||
Assert.Equal(LanguageVersion.CSharp7, parseOptions.LanguageVersion);
|
||||
Assert.Equal(new[] { "DEBUG" }, parseOptions.PreprocessorSymbolNames);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ConfiguresLanguageVersion()
|
||||
{
|
||||
// Arrange
|
||||
var dependencyContextOptions = new DependencyContextCompilationOptions(
|
||||
new[] { "MyDefine" },
|
||||
languageVersion: "7.1",
|
||||
platform: null,
|
||||
allowUnsafe: true,
|
||||
warningsAsErrors: null,
|
||||
optimize: null,
|
||||
keyFile: null,
|
||||
delaySign: null,
|
||||
publicSign: null,
|
||||
debugType: null,
|
||||
emitEntryPoint: null,
|
||||
generateXmlDocumentation: null);
|
||||
var hostingEnvironment = Mock.Of<IHostingEnvironment>();
|
||||
|
||||
var compiler = new TestCSharpCompiler(ReferenceManager, hostingEnvironment, dependencyContextOptions);
|
||||
|
||||
// Act & Assert
|
||||
var compilationOptions = compiler.ParseOptions;
|
||||
Assert.Equal(LanguageVersion.CSharp7_1, compilationOptions.LanguageVersion);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void EmitOptions_ReadsDebugTypeFromDependencyContext()
|
||||
{
|
||||
// Arrange
|
||||
var dependencyContextOptions = new DependencyContextCompilationOptions(
|
||||
new[] { "MyDefine" },
|
||||
languageVersion: "7.1",
|
||||
platform: null,
|
||||
allowUnsafe: true,
|
||||
warningsAsErrors: null,
|
||||
optimize: null,
|
||||
keyFile: null,
|
||||
delaySign: null,
|
||||
publicSign: null,
|
||||
debugType: "portable",
|
||||
emitEntryPoint: null,
|
||||
generateXmlDocumentation: null);
|
||||
var hostingEnvironment = Mock.Of<IHostingEnvironment>();
|
||||
|
||||
var compiler = new TestCSharpCompiler(ReferenceManager, hostingEnvironment, dependencyContextOptions);
|
||||
|
||||
// Act & Assert
|
||||
var emitOptions = compiler.EmitOptions;
|
||||
Assert.Equal(DebugInformationFormat.PortablePdb, emitOptions.DebugInformationFormat);
|
||||
Assert.True(compiler.EmitPdb);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmitOptions_SetsDebugInformationFormatToPortable_WhenDebugTypeIsEmbedded()
|
||||
{
|
||||
// Arrange
|
||||
var dependencyContextOptions = new DependencyContextCompilationOptions(
|
||||
new[] { "MyDefine" },
|
||||
languageVersion: "7.1",
|
||||
platform: null,
|
||||
allowUnsafe: true,
|
||||
warningsAsErrors: null,
|
||||
optimize: null,
|
||||
keyFile: null,
|
||||
delaySign: null,
|
||||
publicSign: null,
|
||||
debugType: "embedded",
|
||||
emitEntryPoint: null,
|
||||
generateXmlDocumentation: null);
|
||||
var hostingEnvironment = Mock.Of<IHostingEnvironment>();
|
||||
|
||||
var compiler = new TestCSharpCompiler(ReferenceManager, hostingEnvironment, dependencyContextOptions);
|
||||
|
||||
// Act & Assert
|
||||
var emitOptions = compiler.EmitOptions;
|
||||
Assert.Equal(DebugInformationFormat.PortablePdb, emitOptions.DebugInformationFormat);
|
||||
Assert.True(compiler.EmitPdb);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmitOptions_DoesNotSetEmitPdb_IfDebugTypeIsNone()
|
||||
{
|
||||
// Arrange
|
||||
var dependencyContextOptions = new DependencyContextCompilationOptions(
|
||||
new[] { "MyDefine" },
|
||||
languageVersion: "7.1",
|
||||
platform: null,
|
||||
allowUnsafe: true,
|
||||
warningsAsErrors: null,
|
||||
optimize: null,
|
||||
keyFile: null,
|
||||
delaySign: null,
|
||||
publicSign: null,
|
||||
debugType: "none",
|
||||
emitEntryPoint: null,
|
||||
generateXmlDocumentation: null);
|
||||
var hostingEnvironment = Mock.Of<IHostingEnvironment>();
|
||||
|
||||
var compiler = new TestCSharpCompiler(ReferenceManager, hostingEnvironment, dependencyContextOptions);
|
||||
|
||||
// Act & Assert
|
||||
Assert.False(compiler.EmitPdb);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ConfiguresAllowUnsafe()
|
||||
{
|
||||
// Arrange
|
||||
var dependencyContextOptions = new DependencyContextCompilationOptions(
|
||||
new[] { "MyDefine" },
|
||||
languageVersion: null,
|
||||
platform: null,
|
||||
allowUnsafe: true,
|
||||
warningsAsErrors: null,
|
||||
optimize: null,
|
||||
keyFile: null,
|
||||
delaySign: null,
|
||||
publicSign: null,
|
||||
debugType: null,
|
||||
emitEntryPoint: null,
|
||||
generateXmlDocumentation: null);
|
||||
var hostingEnvironment = Mock.Of<IHostingEnvironment>();
|
||||
|
||||
var compiler = new TestCSharpCompiler(ReferenceManager, hostingEnvironment, dependencyContextOptions);
|
||||
|
||||
// Act & Assert
|
||||
var compilationOptions = compiler.CSharpCompilationOptions;
|
||||
Assert.True(compilationOptions.AllowUnsafe);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SetsDiagnosticOption()
|
||||
{
|
||||
// Arrange
|
||||
var dependencyContextOptions = new DependencyContextCompilationOptions(
|
||||
new[] { "MyDefine" },
|
||||
languageVersion: null,
|
||||
platform: null,
|
||||
allowUnsafe: null,
|
||||
warningsAsErrors: true,
|
||||
optimize: null,
|
||||
keyFile: null,
|
||||
delaySign: null,
|
||||
publicSign: null,
|
||||
debugType: null,
|
||||
emitEntryPoint: null,
|
||||
generateXmlDocumentation: null);
|
||||
var hostingEnvironment = Mock.Of<IHostingEnvironment>();
|
||||
|
||||
var compiler = new TestCSharpCompiler(ReferenceManager, hostingEnvironment, dependencyContextOptions);
|
||||
|
||||
// Act & Assert
|
||||
var compilationOptions = compiler.CSharpCompilationOptions;
|
||||
Assert.Equal(ReportDiagnostic.Error, compilationOptions.GeneralDiagnosticOption);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SetsOptimizationLevel()
|
||||
{
|
||||
// Arrange
|
||||
var dependencyContextOptions = new DependencyContextCompilationOptions(
|
||||
new[] { "MyDefine" },
|
||||
languageVersion: null,
|
||||
platform: null,
|
||||
allowUnsafe: null,
|
||||
warningsAsErrors: null,
|
||||
optimize: true,
|
||||
keyFile: null,
|
||||
delaySign: null,
|
||||
publicSign: null,
|
||||
debugType: null,
|
||||
emitEntryPoint: null,
|
||||
generateXmlDocumentation: null);
|
||||
var hostingEnvironment = Mock.Of<IHostingEnvironment>();
|
||||
|
||||
var compiler = new TestCSharpCompiler(ReferenceManager, hostingEnvironment, dependencyContextOptions);
|
||||
|
||||
// Act & Assert
|
||||
var compilationOptions = compiler.CSharpCompilationOptions;
|
||||
Assert.Equal(OptimizationLevel.Release, compilationOptions.OptimizationLevel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SetsDefines()
|
||||
{
|
||||
// Arrange
|
||||
var dependencyContextOptions = new DependencyContextCompilationOptions(
|
||||
new[] { "MyDefine" },
|
||||
languageVersion: null,
|
||||
platform: null,
|
||||
allowUnsafe: null,
|
||||
warningsAsErrors: null,
|
||||
optimize: true,
|
||||
keyFile: null,
|
||||
delaySign: null,
|
||||
publicSign: null,
|
||||
debugType: null,
|
||||
emitEntryPoint: null,
|
||||
generateXmlDocumentation: null);
|
||||
var hostingEnvironment = Mock.Of<IHostingEnvironment>();
|
||||
|
||||
var compiler = new TestCSharpCompiler(ReferenceManager, hostingEnvironment, dependencyContextOptions);
|
||||
|
||||
// Act & Assert
|
||||
var parseOptions = compiler.ParseOptions;
|
||||
Assert.Equal(new[] { "MyDefine", "RELEASE" }, parseOptions.PreprocessorSymbolNames);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Compile_UsesApplicationsCompilationSettings_ForParsingAndCompilation()
|
||||
{
|
||||
// Arrange
|
||||
var content = "public class Test {}";
|
||||
var define = "MY_CUSTOM_DEFINE";
|
||||
var dependencyContextOptions = new DependencyContextCompilationOptions(
|
||||
new[] { define },
|
||||
languageVersion: null,
|
||||
platform: null,
|
||||
allowUnsafe: null,
|
||||
warningsAsErrors: null,
|
||||
optimize: true,
|
||||
keyFile: null,
|
||||
delaySign: null,
|
||||
publicSign: null,
|
||||
debugType: null,
|
||||
emitEntryPoint: null,
|
||||
generateXmlDocumentation: null);
|
||||
var hostingEnvironment = Mock.Of<IHostingEnvironment>();
|
||||
var compiler = new TestCSharpCompiler(ReferenceManager, hostingEnvironment, dependencyContextOptions);
|
||||
|
||||
// Act
|
||||
var syntaxTree = compiler.CreateSyntaxTree(SourceText.From(content));
|
||||
|
||||
// Assert
|
||||
Assert.Contains(define, syntaxTree.Options.PreprocessorSymbolNames);
|
||||
}
|
||||
|
||||
private class TestCSharpCompiler : CSharpCompiler
|
||||
{
|
||||
private readonly DependencyContextCompilationOptions _options;
|
||||
|
||||
public TestCSharpCompiler(
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
RazorReferenceManager referenceManager,
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
DependencyContextCompilationOptions options)
|
||||
: base(referenceManager, hostingEnvironment)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
protected internal override DependencyContextCompilationOptions GetDependencyContextCompilationOptions()
|
||||
=> _options;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,195 +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 Microsoft.AspNetCore.Razor.Hosting;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Xunit;
|
||||
using static Microsoft.AspNetCore.Razor.Hosting.TestRazorCompiledItem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
public class ChecksumValidatorTest
|
||||
{
|
||||
public ChecksumValidatorTest()
|
||||
{
|
||||
ProjectFileSystem = new VirtualRazorProjectFileSystem();
|
||||
}
|
||||
|
||||
public VirtualRazorProjectFileSystem ProjectFileSystem { get; }
|
||||
|
||||
[Fact]
|
||||
public void IsRecompilationSupported_NoChecksums_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", "/Views/Home/Index.cstml", new object[] { });
|
||||
|
||||
// Act
|
||||
var result = ChecksumValidator.IsRecompilationSupported(item);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsRecompilationSupported_NoPrimaryChecksum_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", "/Views/Home/Index.cstml", new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some import"), "/Views/Home/_ViewImports.cstml"),
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = ChecksumValidator.IsRecompilationSupported(item);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsRecompilationSupported_HasPrimaryChecksum_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", "/Views/Home/Index.cstml", new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some import"), "/Views/Home/_ViewImports.cstml"),
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Views/Home/Index.cstml"),
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = ChecksumValidator.IsRecompilationSupported(item);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsItemValid_NoChecksums_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", "/Views/Home/Index.cstml", new object[] { });
|
||||
|
||||
// Act
|
||||
var result = ChecksumValidator.IsItemValid(ProjectFileSystem, item);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsItemValid_NoPrimaryChecksum_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", "/Views/Home/Index.cstml", new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some import"), "/Views/Home/_ViewImports.cstml"),
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Views/Home/About.cstml"),
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = ChecksumValidator.IsItemValid(ProjectFileSystem, item);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsItemValid_PrimaryFileDoesNotExist_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", "/Views/Home/Index.cstml", new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some import"), "/Views/Home/_ViewImports.cstml"),
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Views/Home/Index.cstml"),
|
||||
});
|
||||
|
||||
ProjectFileSystem.Add(new TestRazorProjectItem("/Views/Home/_ViewImports.cstml", "dkdkfkdf")); // This will be ignored
|
||||
|
||||
// Act
|
||||
var result = ChecksumValidator.IsItemValid(ProjectFileSystem, item);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsItemValid_PrimaryFileExistsButDoesNotMatch_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", "/Views/Home/Index.cstml", new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some import"), "/Views/Home/_ViewImports.cstml"),
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Views/Home/Index.cstml"),
|
||||
});
|
||||
|
||||
ProjectFileSystem.Add(new TestRazorProjectItem("/Views/Home/Index.cstml", "other content"));
|
||||
|
||||
// Act
|
||||
var result = ChecksumValidator.IsItemValid(ProjectFileSystem, item);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsItemValid_ImportFileDoesNotExist_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", "/Views/Home/Index.cstml", new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some import"), "/Views/Home/_ViewImports.cstml"),
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Views/Home/Index.cstml"),
|
||||
});
|
||||
|
||||
ProjectFileSystem.Add(new TestRazorProjectItem("/Views/Home/Index.cstml", "some content"));
|
||||
|
||||
// Act
|
||||
var result = ChecksumValidator.IsItemValid(ProjectFileSystem, item);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsItemValid_ImportFileExistsButDoesNotMatch_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", "/Views/Home/Index.cstml", new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some import"), "/Views/Home/_ViewImports.cstml"),
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Views/Home/Index.cstml"),
|
||||
});
|
||||
|
||||
ProjectFileSystem.Add(new TestRazorProjectItem("/Views/Home/Index.cstml", "some content"));
|
||||
ProjectFileSystem.Add(new TestRazorProjectItem("/Views/Home/_ViewImports.cstml", "some other import"));
|
||||
|
||||
// Act
|
||||
var result = ChecksumValidator.IsItemValid(ProjectFileSystem, item);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsItemValid_AllFilesMatch_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", "/Views/Home/Index.cstml", new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some other import"), "/Views/_ViewImports.cstml"),
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some import"), "/Views/Home/_ViewImports.cstml"),
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Views/Home/Index.cstml"),
|
||||
});
|
||||
|
||||
ProjectFileSystem.Add(new TestRazorProjectItem("/Views/Home/Index.cstml", "some content"));
|
||||
ProjectFileSystem.Add(new TestRazorProjectItem("/Views/Home/_ViewImports.cstml", "some import"));
|
||||
ProjectFileSystem.Add(new TestRazorProjectItem("/Views/_ViewImports.cstml", "some other import"));
|
||||
|
||||
// Act
|
||||
var result = ChecksumValidator.IsItemValid(ProjectFileSystem, item);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,355 +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 System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
public class CompilerFailedExceptionFactoryTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetCompilationFailedResult_ReadsRazorErrorsFromPage()
|
||||
{
|
||||
// Arrange
|
||||
var viewPath = "/Views/Home/Index.cshtml";
|
||||
|
||||
var fileSystem = new VirtualRazorProjectFileSystem();
|
||||
fileSystem.Add(new TestRazorProjectItem(viewPath, "<span name=\"@(User.Id\">"));
|
||||
|
||||
var razorEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine;
|
||||
|
||||
var templateEngine = new MvcRazorTemplateEngine(razorEngine, fileSystem);
|
||||
var codeDocument = templateEngine.CreateCodeDocument(viewPath);
|
||||
|
||||
// Act
|
||||
var csharpDocument = templateEngine.GenerateCode(codeDocument);
|
||||
var compilationResult = CompilationFailedExceptionFactory.Create(codeDocument, csharpDocument.Diagnostics);
|
||||
|
||||
// Assert
|
||||
var failure = Assert.Single(compilationResult.CompilationFailures);
|
||||
Assert.Equal(viewPath, failure.SourceFilePath);
|
||||
Assert.Collection(failure.Messages,
|
||||
message => Assert.StartsWith(
|
||||
@"Unterminated string literal.",
|
||||
message.Message),
|
||||
message => Assert.StartsWith(
|
||||
@"The explicit expression block is missing a closing "")"" character.",
|
||||
message.Message));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCompilationFailedResult_WithMissingReferences()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "One or more compilation references may be missing. If you're seeing this in a published application, set 'CopyRefAssembliesToPublishDirectory' to true in your project file to ensure files in the refs directory are published.";
|
||||
var compilation = CSharpCompilation.Create("Test", options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||
var syntaxTree = CSharpSyntaxTree.ParseText("@class Test { public string Test { get; set; } }");
|
||||
compilation = compilation.AddSyntaxTrees(syntaxTree);
|
||||
var emitResult = compilation.Emit(new MemoryStream());
|
||||
|
||||
// Act
|
||||
var exception = CompilationFailedExceptionFactory.Create(
|
||||
RazorCodeDocument.Create(RazorSourceDocument.Create("Test", "Index.cshtml"), Enumerable.Empty<RazorSourceDocument>()),
|
||||
syntaxTree.ToString(),
|
||||
"Test",
|
||||
emitResult.Diagnostics);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
exception.CompilationFailures,
|
||||
failure => Assert.Equal(expected, failure.FailureSummary));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCompilationFailedResult_UsesPhysicalPath()
|
||||
{
|
||||
// Arrange
|
||||
var viewPath = "/Views/Home/Index.cshtml";
|
||||
var physicalPath = @"x:\myapp\views\home\index.cshtml";
|
||||
|
||||
var fileSystem = new VirtualRazorProjectFileSystem();
|
||||
fileSystem.Add(new TestRazorProjectItem(viewPath, "<span name=\"@(User.Id\">", physicalPath: physicalPath));
|
||||
|
||||
var razorEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine;
|
||||
var templateEngine = new MvcRazorTemplateEngine(razorEngine, fileSystem);
|
||||
|
||||
var codeDocument = templateEngine.CreateCodeDocument(viewPath);
|
||||
|
||||
// Act
|
||||
var csharpDocument = templateEngine.GenerateCode(codeDocument);
|
||||
var compilationResult = CompilationFailedExceptionFactory.Create(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 fileSystem = new VirtualRazorProjectFileSystem();
|
||||
fileSystem.Add(new TestRazorProjectItem(viewPath, fileContent));
|
||||
|
||||
var razorEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine;
|
||||
var templateEngine = new MvcRazorTemplateEngine(razorEngine, fileSystem);
|
||||
|
||||
var codeDocument = templateEngine.CreateCodeDocument(viewPath);
|
||||
|
||||
// Act
|
||||
var csharpDocument = templateEngine.GenerateCode(codeDocument);
|
||||
var compilationResult = CompilationFailedExceptionFactory.Create(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 importsPath = "/Views/_MyImports.cshtml";
|
||||
var fileContent = "@ ";
|
||||
var importsContent = "@(abc";
|
||||
|
||||
var fileSystem = new VirtualRazorProjectFileSystem();
|
||||
fileSystem.Add(new TestRazorProjectItem(viewPath, fileContent));
|
||||
fileSystem.Add(new TestRazorProjectItem("/Views/_MyImports.cshtml", importsContent));
|
||||
|
||||
var razorEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine;
|
||||
var templateEngine = new MvcRazorTemplateEngine(razorEngine, fileSystem)
|
||||
{
|
||||
Options =
|
||||
{
|
||||
ImportsFileName = "_MyImports.cshtml",
|
||||
}
|
||||
};
|
||||
var codeDocument = templateEngine.CreateCodeDocument(viewPath);
|
||||
|
||||
// Act
|
||||
var csharpDocument = templateEngine.GenerateCode(codeDocument);
|
||||
var compilationResult = CompilationFailedExceptionFactory.Create(codeDocument, csharpDocument.Diagnostics);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
compilationResult.CompilationFailures,
|
||||
failure =>
|
||||
{
|
||||
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);
|
||||
});
|
||||
},
|
||||
failure =>
|
||||
{
|
||||
Assert.Equal(importsPath, failure.SourceFilePath);
|
||||
Assert.Collection(failure.Messages,
|
||||
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),
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = CompilationFailedExceptionFactory.Create(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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCompilationFailedResult_ReturnsCompilationResult_WithGroupedMessages()
|
||||
{
|
||||
// Arrange
|
||||
var viewPath = "Views/Home/Index";
|
||||
var generatedCodeFileName = "Generated Code";
|
||||
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("view-content", viewPath));
|
||||
var assemblyName = "random-assembly-name";
|
||||
|
||||
var diagnostics = new[]
|
||||
{
|
||||
Diagnostic.Create(
|
||||
GetRoslynDiagnostic("message-1"),
|
||||
Location.Create(
|
||||
viewPath,
|
||||
new TextSpan(10, 5),
|
||||
new LinePositionSpan(new LinePosition(10, 1), new LinePosition(10, 2)))),
|
||||
Diagnostic.Create(
|
||||
GetRoslynDiagnostic("message-2"),
|
||||
Location.Create(
|
||||
assemblyName,
|
||||
new TextSpan(1, 6),
|
||||
new LinePositionSpan(new LinePosition(1, 2), new LinePosition(3, 4)))),
|
||||
Diagnostic.Create(
|
||||
GetRoslynDiagnostic("message-3"),
|
||||
Location.Create(
|
||||
viewPath,
|
||||
new TextSpan(40, 50),
|
||||
new LinePositionSpan(new LinePosition(30, 5), new LinePosition(40, 12)))),
|
||||
};
|
||||
|
||||
// Act
|
||||
var compilationResult = CompilationFailedExceptionFactory.Create(
|
||||
codeDocument,
|
||||
"compilation-content",
|
||||
assemblyName,
|
||||
diagnostics);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(compilationResult.CompilationFailures,
|
||||
failure =>
|
||||
{
|
||||
Assert.Equal(viewPath, failure.SourceFilePath);
|
||||
Assert.Equal("view-content", failure.SourceFileContent);
|
||||
Assert.Collection(failure.Messages,
|
||||
message =>
|
||||
{
|
||||
Assert.Equal("message-1", message.Message);
|
||||
Assert.Equal(viewPath, message.SourceFilePath);
|
||||
Assert.Equal(11, message.StartLine);
|
||||
Assert.Equal(2, message.StartColumn);
|
||||
Assert.Equal(11, message.EndLine);
|
||||
Assert.Equal(3, message.EndColumn);
|
||||
},
|
||||
message =>
|
||||
{
|
||||
Assert.Equal("message-3", message.Message);
|
||||
Assert.Equal(viewPath, message.SourceFilePath);
|
||||
Assert.Equal(31, message.StartLine);
|
||||
Assert.Equal(6, message.StartColumn);
|
||||
Assert.Equal(41, message.EndLine);
|
||||
Assert.Equal(13, message.EndColumn);
|
||||
});
|
||||
},
|
||||
failure =>
|
||||
{
|
||||
Assert.Equal(generatedCodeFileName, failure.SourceFilePath);
|
||||
Assert.Equal("compilation-content", failure.SourceFileContent);
|
||||
Assert.Collection(failure.Messages,
|
||||
message =>
|
||||
{
|
||||
Assert.Equal("message-2", message.Message);
|
||||
Assert.Equal(assemblyName, message.SourceFilePath);
|
||||
Assert.Equal(2, message.StartLine);
|
||||
Assert.Equal(3, message.StartColumn);
|
||||
Assert.Equal(4, message.EndLine);
|
||||
Assert.Equal(5, message.EndColumn);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private static DiagnosticDescriptor GetRoslynDiagnostic(string messageFormat)
|
||||
{
|
||||
return new DiagnosticDescriptor(
|
||||
id: "someid",
|
||||
title: "sometitle",
|
||||
messageFormat: messageFormat,
|
||||
category: "some-category",
|
||||
defaultSeverity: DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,55 +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.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
public class DefaultRazorReferenceManagerTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetCompilationReferences_CombinesApplicationPartAndOptionMetadataReferences()
|
||||
{
|
||||
// Arrange
|
||||
var options = new RazorViewEngineOptions();
|
||||
var objectAssemblyLocation = typeof(object).GetTypeInfo().Assembly.Location;
|
||||
var objectAssemblyMetadataReference = MetadataReference.CreateFromFile(objectAssemblyLocation);
|
||||
options.AdditionalCompilationReferences.Add(objectAssemblyMetadataReference);
|
||||
|
||||
var applicationPartManager = GetApplicationPartManager();
|
||||
var feature = new MetadataReferenceFeature();
|
||||
applicationPartManager.PopulateFeature(feature);
|
||||
var partReferences = feature.MetadataReferences;
|
||||
var expectedReferenceDisplays = partReferences
|
||||
.Concat(new[] { objectAssemblyMetadataReference })
|
||||
.Select(r => r.Display);
|
||||
var referenceManager = new DefaultRazorReferenceManager(
|
||||
applicationPartManager,
|
||||
Options.Create(options));
|
||||
|
||||
// Act
|
||||
var references = referenceManager.CompilationReferences;
|
||||
var referenceDisplays = references.Select(reference => reference.Display);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedReferenceDisplays, referenceDisplays);
|
||||
}
|
||||
|
||||
private static ApplicationPartManager GetApplicationPartManager()
|
||||
{
|
||||
var applicationPartManager = new ApplicationPartManager();
|
||||
var assembly = typeof(DefaultRazorReferenceManagerTest).GetTypeInfo().Assembly;
|
||||
applicationPartManager.ApplicationParts.Add(new AssemblyPart(assembly));
|
||||
applicationPartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider());
|
||||
|
||||
return applicationPartManager;
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
|
@ -1,576 +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.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
public class ExpressionRewriterTest
|
||||
{
|
||||
[Fact]
|
||||
public void ExpressionRewriter_DoesNotThrowsOnUnknownTypes()
|
||||
{
|
||||
// Arrange
|
||||
var source = @"
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
|
||||
public class ExamplePage : RazorPage
|
||||
{
|
||||
public IViewComponentHelper Component { get; set; }
|
||||
|
||||
public override async Task ExecuteAsync()
|
||||
{
|
||||
Write(
|
||||
await Component.InvokeAsync(
|
||||
""SomeComponent"",
|
||||
item => new HelperResult((__razor_template_writer) => WriteLiteralTo(__razor_template_writer, ""Hello World""))));
|
||||
}
|
||||
}
|
||||
";
|
||||
var tree = CSharpSyntaxTree.ParseText(source);
|
||||
|
||||
// Allow errors here because of an anomaly where Roslyn (depending on code sample) will finish compilation
|
||||
// without diagnostic errors. This test case replicates that scenario by allowing a semantic model with
|
||||
// errors to be visited by the expression rewriter to validate unexpected exceptions aren't thrown.
|
||||
// Error created: "Cannot convert lambda expression to type 'object' because it is not a delegate type."
|
||||
var compilation = Compile(tree, allowErrors: true);
|
||||
var semanticModel = compilation.GetSemanticModel(tree, ignoreAccessibility: true);
|
||||
var rewriter = new ExpressionRewriter(semanticModel);
|
||||
var root = tree.GetRoot();
|
||||
|
||||
// Act
|
||||
var result = rewriter.Visit(root);
|
||||
|
||||
// Assert
|
||||
Assert.True(root.IsEquivalentTo(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpressionRewriter_CanRewriteExpression_IdentityExpression()
|
||||
{
|
||||
// Arrange
|
||||
var source = @"
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
public class Program
|
||||
{
|
||||
public static void CalledWithExpression(Expression<Func<object, object>> expression)
|
||||
{
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CalledWithExpression(x => x);
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
var tree = CSharpSyntaxTree.ParseText(source);
|
||||
var compilation = Compile(tree);
|
||||
var semanticModel = compilation.GetSemanticModel(tree, ignoreAccessibility: true);
|
||||
|
||||
var rewriter = new ExpressionRewriter(semanticModel);
|
||||
|
||||
// Act
|
||||
var result = rewriter.Visit(tree.GetRoot());
|
||||
|
||||
// Assert
|
||||
var fields = FindFields(result);
|
||||
|
||||
var field = Assert.Single(fields);
|
||||
Assert.Collection(
|
||||
field.Modifiers,
|
||||
m => Assert.Equal("private", m.ToString()),
|
||||
m => Assert.Equal("static", m.ToString()),
|
||||
m => Assert.Equal("readonly", m.ToString()));
|
||||
|
||||
var declaration = field.Declaration;
|
||||
Assert.Equal(
|
||||
"global::System.Linq.Expressions.Expression<global::System.Func<object, object>>",
|
||||
declaration.Type.ToString());
|
||||
|
||||
var variable = Assert.Single(declaration.Variables);
|
||||
Assert.Equal("__h0", variable.Identifier.ToString());
|
||||
Assert.Equal("x => x", variable.Initializer.Value.ToString());
|
||||
|
||||
var arguments = FindArguments(result);
|
||||
var argument = Assert.IsType<IdentifierNameSyntax>(Assert.Single(arguments.Arguments).Expression);
|
||||
Assert.Equal("__h0", argument.Identifier.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpressionRewriter_CanRewriteExpression_MemberAccessExpression()
|
||||
{
|
||||
// Arrange
|
||||
var source = @"
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
public class Program
|
||||
{
|
||||
public static void CalledWithExpression(Expression<Func<Person, object>> expression)
|
||||
{
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CalledWithExpression(x => x.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public class Person
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
";
|
||||
|
||||
var tree = CSharpSyntaxTree.ParseText(source);
|
||||
var compilation = Compile(tree);
|
||||
var semanticModel = compilation.GetSemanticModel(tree, ignoreAccessibility: true);
|
||||
|
||||
var rewriter = new ExpressionRewriter(semanticModel);
|
||||
|
||||
// Act
|
||||
var result = rewriter.Visit(tree.GetRoot());
|
||||
|
||||
// Assert
|
||||
var fields = FindFields(result);
|
||||
|
||||
var field = Assert.Single(fields);
|
||||
Assert.Collection(
|
||||
field.Modifiers,
|
||||
m => Assert.Equal("private", m.ToString()),
|
||||
m => Assert.Equal("static", m.ToString()),
|
||||
m => Assert.Equal("readonly", m.ToString()));
|
||||
|
||||
var declaration = field.Declaration;
|
||||
Assert.Equal(
|
||||
"global::System.Linq.Expressions.Expression<global::System.Func<global::Person, object>>",
|
||||
declaration.Type.ToString());
|
||||
|
||||
var variable = Assert.Single(declaration.Variables);
|
||||
Assert.Equal("__h0", variable.Identifier.ToString());
|
||||
Assert.Equal("x => x.Name", variable.Initializer.Value.ToString());
|
||||
|
||||
var arguments = FindArguments(result);
|
||||
var argument = Assert.IsType<IdentifierNameSyntax>(Assert.Single(arguments.Arguments).Expression);
|
||||
Assert.Equal("__h0", argument.Identifier.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpressionRewriter_CanRewriteExpression_ChainedMemberAccessExpression()
|
||||
{
|
||||
// Arrange
|
||||
var source = @"
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
public class Program
|
||||
{
|
||||
public static void CalledWithExpression(Expression<Func<Person, int>> expression)
|
||||
{
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CalledWithExpression(x => x.Name.Length);
|
||||
}
|
||||
}
|
||||
|
||||
public class Person
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
";
|
||||
|
||||
var tree = CSharpSyntaxTree.ParseText(source);
|
||||
var compilation = Compile(tree);
|
||||
var semanticModel = compilation.GetSemanticModel(tree, ignoreAccessibility: true);
|
||||
|
||||
var rewriter = new ExpressionRewriter(semanticModel);
|
||||
|
||||
// Act
|
||||
var result = rewriter.Visit(tree.GetRoot());
|
||||
|
||||
// Assert
|
||||
var fields = FindFields(result);
|
||||
|
||||
var field = Assert.Single(fields);
|
||||
Assert.Collection(
|
||||
field.Modifiers,
|
||||
m => Assert.Equal("private", m.ToString()),
|
||||
m => Assert.Equal("static", m.ToString()),
|
||||
m => Assert.Equal("readonly", m.ToString()));
|
||||
|
||||
var declaration = field.Declaration;
|
||||
Assert.Equal(
|
||||
"global::System.Linq.Expressions.Expression<global::System.Func<global::Person, int>>",
|
||||
declaration.Type.ToString());
|
||||
|
||||
var variable = Assert.Single(declaration.Variables);
|
||||
Assert.Equal("__h0", variable.Identifier.ToString());
|
||||
Assert.Equal("x => x.Name.Length", variable.Initializer.Value.ToString());
|
||||
|
||||
var arguments = FindArguments(result);
|
||||
var argument = Assert.IsType<IdentifierNameSyntax>(Assert.Single(arguments.Arguments).Expression);
|
||||
Assert.Equal("__h0", argument.Identifier.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpressionRewriter_CannotRewriteExpression_MethodCall()
|
||||
{
|
||||
// Arrange
|
||||
var source = @"
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
public class Program
|
||||
{
|
||||
public static void CalledWithExpression(Expression<Func<object, int>> expression)
|
||||
{
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CalledWithExpression(x => x.GetHashCode());
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
var tree = CSharpSyntaxTree.ParseText(source);
|
||||
var compilation = Compile(tree);
|
||||
var semanticModel = compilation.GetSemanticModel(tree, ignoreAccessibility: true);
|
||||
|
||||
var rewriter = new ExpressionRewriter(semanticModel);
|
||||
|
||||
// Act
|
||||
var result = rewriter.Visit(tree.GetRoot());
|
||||
|
||||
// Assert
|
||||
Assert.Empty(FindFields(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpressionRewriter_CannotRewriteExpression_NonArgument()
|
||||
{
|
||||
// Arrange
|
||||
var source = @"
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
public class Program
|
||||
{
|
||||
public static void CalledWithExpression(Expression<Func<object, int>> expression)
|
||||
{
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Expression<Func<object, int>> expr = x => x.GetHashCode();
|
||||
CalledWithExpression(expr);
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
var tree = CSharpSyntaxTree.ParseText(source);
|
||||
var compilation = Compile(tree);
|
||||
var semanticModel = compilation.GetSemanticModel(tree, ignoreAccessibility: true);
|
||||
|
||||
var rewriter = new ExpressionRewriter(semanticModel);
|
||||
|
||||
// Act
|
||||
var result = rewriter.Visit(tree.GetRoot());
|
||||
|
||||
// Assert
|
||||
Assert.Empty(FindFields(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpressionRewriter_CannotRewriteExpression_NestedClass()
|
||||
{
|
||||
// Arrange
|
||||
var source = @"
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
public class Program
|
||||
{
|
||||
private class Nested
|
||||
{
|
||||
public static void CalledWithExpression(Expression<Func<object, int>> expression)
|
||||
{
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Expression<Func<object, int>> expr = x => x.GetHashCode();
|
||||
CalledWithExpression(expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
var tree = CSharpSyntaxTree.ParseText(source);
|
||||
var compilation = Compile(tree);
|
||||
var semanticModel = compilation.GetSemanticModel(tree, ignoreAccessibility: true);
|
||||
|
||||
var rewriter = new ExpressionRewriter(semanticModel);
|
||||
|
||||
// Act
|
||||
var result = rewriter.Visit(tree.GetRoot());
|
||||
|
||||
// Assert
|
||||
Assert.Empty(FindFields(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpressionRewriter_CanRewriteExpression_AdditionalArguments()
|
||||
{
|
||||
// Arrange
|
||||
var source = @"
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
public class Program
|
||||
{
|
||||
public static void CalledWithExpression(int x, Expression<Func<object, object>> expression, string name)
|
||||
{
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CalledWithExpression(5, x => x, ""Billy"");
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
var tree = CSharpSyntaxTree.ParseText(source);
|
||||
var compilation = Compile(tree);
|
||||
var semanticModel = compilation.GetSemanticModel(tree, ignoreAccessibility: true);
|
||||
|
||||
var rewriter = new ExpressionRewriter(semanticModel);
|
||||
|
||||
// Act
|
||||
var result = rewriter.Visit(tree.GetRoot());
|
||||
|
||||
// Assert
|
||||
var fields = FindFields(result);
|
||||
|
||||
var field = Assert.Single(fields);
|
||||
Assert.Collection(
|
||||
field.Modifiers,
|
||||
m => Assert.Equal("private", m.ToString()),
|
||||
m => Assert.Equal("static", m.ToString()),
|
||||
m => Assert.Equal("readonly", m.ToString()));
|
||||
|
||||
var declaration = field.Declaration;
|
||||
Assert.Equal(
|
||||
"global::System.Linq.Expressions.Expression<global::System.Func<object, object>>",
|
||||
declaration.Type.ToString());
|
||||
|
||||
var variable = Assert.Single(declaration.Variables);
|
||||
Assert.Equal("__h0", variable.Identifier.ToString());
|
||||
Assert.Equal("x => x", variable.Initializer.Value.ToString());
|
||||
|
||||
var arguments = FindArguments(result);
|
||||
Assert.Equal(3, arguments.Arguments.Count);
|
||||
var argument = Assert.IsType<IdentifierNameSyntax>(arguments.Arguments[1].Expression);
|
||||
Assert.Equal("__h0", argument.Identifier.ToString());
|
||||
}
|
||||
|
||||
// When we rewrite the expression, we want to maintain the original span as much as possible.
|
||||
[Fact]
|
||||
public void ExpressionRewriter_CanRewriteExpression_SimpleFormatting()
|
||||
{
|
||||
// Arrange
|
||||
var source = @"
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
public class Program
|
||||
{
|
||||
public static void CalledWithExpression(Expression<Func<Person, int>> expression)
|
||||
{
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CalledWithExpression(x => x.Name.Length);
|
||||
}
|
||||
}
|
||||
|
||||
public class Person
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
";
|
||||
|
||||
var tree = CSharpSyntaxTree.ParseText(source);
|
||||
|
||||
var originalArguments = FindArguments(tree.GetRoot());
|
||||
var originalSpan = originalArguments.GetLocation().GetMappedLineSpan();
|
||||
|
||||
var compilation = Compile(tree);
|
||||
var semanticModel = compilation.GetSemanticModel(tree, ignoreAccessibility: true);
|
||||
|
||||
var rewriter = new ExpressionRewriter(semanticModel);
|
||||
|
||||
// Act
|
||||
var result = rewriter.Visit(tree.GetRoot());
|
||||
|
||||
// Assert
|
||||
var arguments = FindArguments(result);
|
||||
Assert.Equal(originalSpan, arguments.GetLocation().GetMappedLineSpan());
|
||||
}
|
||||
|
||||
// When we rewrite the expression, we want to maintain the original span as much as possible.
|
||||
[Fact]
|
||||
public void ExpressionRewriter_CanRewriteExpression_ComplexFormatting()
|
||||
{
|
||||
// Arrange
|
||||
var source = @"
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
public class Program
|
||||
{
|
||||
public static void CalledWithExpression(int z, Expression<Func<Person, int>> expression)
|
||||
{
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CalledWithExpression(
|
||||
17,
|
||||
x =>
|
||||
x.Name.
|
||||
Length
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public class Person
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
";
|
||||
|
||||
var tree = CSharpSyntaxTree.ParseText(source);
|
||||
|
||||
var originalArguments = FindArguments(tree.GetRoot());
|
||||
var originalSpan = originalArguments.GetLocation().GetMappedLineSpan();
|
||||
|
||||
var compilation = Compile(tree);
|
||||
var semanticModel = compilation.GetSemanticModel(tree, ignoreAccessibility: true);
|
||||
|
||||
var rewriter = new ExpressionRewriter(semanticModel);
|
||||
|
||||
// Act
|
||||
var result = rewriter.Visit(tree.GetRoot());
|
||||
|
||||
// Assert
|
||||
var arguments = FindArguments(result);
|
||||
Assert.Equal(originalSpan, arguments.GetLocation().GetMappedLineSpan());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpressionRewriter_CanRewriteExpression_BadlyIndentedFormatting()
|
||||
{
|
||||
// Arrange
|
||||
var source = @"
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
public class Program
|
||||
{
|
||||
public static void CalledWithExpression(Expression<Func<Person, int>> expression)
|
||||
{
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CalledWithExpression(x =>
|
||||
x.Name.
|
||||
Length);
|
||||
}
|
||||
}
|
||||
|
||||
public class Person
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
";
|
||||
|
||||
var tree = CSharpSyntaxTree.ParseText(source);
|
||||
|
||||
var originalArguments = FindArguments(tree.GetRoot());
|
||||
var originalSpan = originalArguments.GetLocation().GetMappedLineSpan();
|
||||
|
||||
var compilation = Compile(tree);
|
||||
var semanticModel = compilation.GetSemanticModel(tree, ignoreAccessibility: true);
|
||||
|
||||
var rewriter = new ExpressionRewriter(semanticModel);
|
||||
|
||||
// Act
|
||||
var result = rewriter.Visit(tree.GetRoot());
|
||||
|
||||
// Assert
|
||||
var arguments = FindArguments(result);
|
||||
Assert.Equal(originalSpan, arguments.GetLocation().GetMappedLineSpan());
|
||||
}
|
||||
|
||||
public ArgumentListSyntax FindArguments(SyntaxNode node)
|
||||
{
|
||||
return node
|
||||
.DescendantNodes(n => true)
|
||||
.Where(n => n.IsKind(SyntaxKind.ArgumentList))
|
||||
.Cast<ArgumentListSyntax>()
|
||||
.Single();
|
||||
}
|
||||
|
||||
public IEnumerable<FieldDeclarationSyntax> FindFields(SyntaxNode node)
|
||||
{
|
||||
return node
|
||||
.DescendantNodes(n => true)
|
||||
.Where(n => n.IsKind(SyntaxKind.FieldDeclaration))
|
||||
.Cast<FieldDeclarationSyntax>();
|
||||
}
|
||||
|
||||
private CSharpCompilation Compile(SyntaxTree tree, bool allowErrors = false)
|
||||
{
|
||||
// Disable 1702 until roslyn turns this off by default
|
||||
var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
|
||||
.WithSpecificDiagnosticOptions(new Dictionary<string, ReportDiagnostic>
|
||||
{
|
||||
{ "CS1701", ReportDiagnostic.Suppress }, // Binding redirects
|
||||
{ "CS1702", ReportDiagnostic.Suppress },
|
||||
{ "CS1705", ReportDiagnostic.Suppress }
|
||||
});
|
||||
|
||||
var compilation = CSharpCompilation.Create(
|
||||
"Test.Assembly",
|
||||
new[] { tree },
|
||||
GetReferences(),
|
||||
options: options);
|
||||
|
||||
if (!allowErrors)
|
||||
{
|
||||
var diagnostics = compilation.GetDiagnostics();
|
||||
Assert.True(diagnostics.Length == 0, string.Join(Environment.NewLine, diagnostics));
|
||||
}
|
||||
|
||||
return compilation;
|
||||
}
|
||||
|
||||
private IEnumerable<MetadataReference> GetReferences()
|
||||
{
|
||||
var types = new[]
|
||||
{
|
||||
typeof(System.Linq.Expressions.Expression),
|
||||
typeof(string),
|
||||
};
|
||||
|
||||
return types.Select(t => MetadataReference.CreateFromFile(t.GetTypeInfo().Assembly.Location));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,53 +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.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
public class MetadataReferenceFeatureProviderTest
|
||||
{
|
||||
[Fact]
|
||||
public void PopulateFeature_ReturnsEmptyList_IfNoAssemblyPartsAreRegistered()
|
||||
{
|
||||
// Arrange
|
||||
var applicationPartManager = new ApplicationPartManager();
|
||||
applicationPartManager.ApplicationParts.Add(Mock.Of<ApplicationPart>());
|
||||
applicationPartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider());
|
||||
var feature = new MetadataReferenceFeature();
|
||||
|
||||
// Act
|
||||
applicationPartManager.PopulateFeature(feature);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(feature.MetadataReferences);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PopulateFeature_AddsMetadataReferenceForAssemblyPartsWithDependencyContext()
|
||||
{
|
||||
// Arrange
|
||||
var applicationPartManager = new ApplicationPartManager();
|
||||
var currentAssembly = GetType().GetTypeInfo().Assembly;
|
||||
var assemblyPart1 = new AssemblyPart(currentAssembly);
|
||||
applicationPartManager.ApplicationParts.Add(assemblyPart1);
|
||||
var assemblyPart2 = new AssemblyPart(typeof(MetadataReferenceFeatureProvider).GetTypeInfo().Assembly);
|
||||
applicationPartManager.ApplicationParts.Add(assemblyPart2);
|
||||
applicationPartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider());
|
||||
var feature = new MetadataReferenceFeature();
|
||||
|
||||
// Act
|
||||
applicationPartManager.PopulateFeature(feature);
|
||||
|
||||
// Assert
|
||||
Assert.Contains(
|
||||
feature.MetadataReferences,
|
||||
reference => reference.Display.Equals(currentAssembly.Location));
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
|
@ -1,51 +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.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
public class RazorViewCompilerProviderTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetCompiler_ThrowsIfNullFileProvider()
|
||||
{
|
||||
// Arrange
|
||||
var expected =
|
||||
$"'{typeof(RazorViewEngineOptions).FullName}.{nameof(RazorViewEngineOptions.FileProviders)}' must " +
|
||||
$"not be empty. At least one '{typeof(IFileProvider).FullName}' is required to locate a view for " +
|
||||
"rendering.";
|
||||
var fileProvider = new NullFileProvider();
|
||||
var accessor = Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == fileProvider);
|
||||
|
||||
var partManager = new ApplicationPartManager();
|
||||
var options = Options.Create(new RazorViewEngineOptions());
|
||||
|
||||
var referenceManager = new DefaultRazorReferenceManager(partManager, options);
|
||||
|
||||
var provider = new RazorViewCompilerProvider(
|
||||
partManager,
|
||||
RazorProjectEngine.Create(
|
||||
RazorConfiguration.Default,
|
||||
new FileProviderRazorProjectFileSystem(accessor, Mock.Of<IHostingEnvironment>())),
|
||||
accessor,
|
||||
new CSharpCompiler(referenceManager, Mock.Of<IHostingEnvironment>()),
|
||||
options,
|
||||
new RazorViewCompilationMemoryCacheProvider(),
|
||||
NullLoggerFactory.Instance);
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<InvalidOperationException>(
|
||||
() => provider.GetCompiler());
|
||||
Assert.Equal(expected, exception.Message);
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,65 +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 Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
public class DefaultRazorViewEngineFileProviderAccessorTest
|
||||
{
|
||||
[Fact]
|
||||
public void FileProvider_ReturnsInstance_IfExactlyOneFileProviderIsRegistered()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
var options = new RazorViewEngineOptions();
|
||||
options.FileProviders.Add(fileProvider);
|
||||
var optionsAccessor = new Mock<IOptions<RazorViewEngineOptions>>();
|
||||
optionsAccessor.SetupGet(o => o.Value).Returns(options);
|
||||
var fileProviderAccessor = new DefaultRazorViewEngineFileProviderAccessor(optionsAccessor.Object);
|
||||
|
||||
// Act
|
||||
var actual = fileProviderAccessor.FileProvider;
|
||||
|
||||
// Assert
|
||||
Assert.Same(fileProvider, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FileProvider_ReturnsNullFileProvider_IfNoInstancesAreRegistered()
|
||||
{
|
||||
// Arrange
|
||||
var options = new RazorViewEngineOptions();
|
||||
var optionsAccessor = new Mock<IOptions<RazorViewEngineOptions>>();
|
||||
optionsAccessor.SetupGet(o => o.Value).Returns(options);
|
||||
var fileProviderAccessor = new DefaultRazorViewEngineFileProviderAccessor(optionsAccessor.Object);
|
||||
|
||||
// Act
|
||||
var actual = fileProviderAccessor.FileProvider;
|
||||
|
||||
// Assert
|
||||
Assert.IsType<NullFileProvider>(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FileProvider_ReturnsCompositeFileProvider_IfMoreThanOneInstanceIsRegistered()
|
||||
{
|
||||
// Arrange
|
||||
var options = new RazorViewEngineOptions();
|
||||
options.FileProviders.Add(new TestFileProvider());
|
||||
options.FileProviders.Add(new TestFileProvider());
|
||||
var optionsAccessor = new Mock<IOptions<RazorViewEngineOptions>>();
|
||||
optionsAccessor.SetupGet(o => o.Value).Returns(options);
|
||||
var fileProviderAccessor = new DefaultRazorViewEngineFileProviderAccessor(optionsAccessor.Object);
|
||||
|
||||
// Act
|
||||
var actual = fileProviderAccessor.FileProvider;
|
||||
|
||||
// Assert
|
||||
Assert.IsType<CompositeFileProvider>(actual);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -53,63 +53,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test.DependencyInjection
|
|||
Assert.Empty(builder.PartManager.ApplicationParts);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRazorViewEngine_AddsMetadataReferenceFeatureProvider()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
var builder = services.AddMvcCore();
|
||||
|
||||
// Act
|
||||
builder.AddRazorViewEngine();
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Assert.Single(builder.PartManager.FeatureProviders.OfType<MetadataReferenceFeatureProvider>());
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRazorViewEngine_DoesNotAddMultipleMetadataReferenceFeatureProvider_OnMultipleInvocations()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
var builder = services.AddMvcCore();
|
||||
|
||||
// Act - 1
|
||||
builder.AddRazorViewEngine();
|
||||
|
||||
// Act - 2
|
||||
builder.AddRazorViewEngine();
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Assert.Single(builder.PartManager.FeatureProviders.OfType<MetadataReferenceFeatureProvider>());
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRazorViewEngine_DoesNotReplaceExistingMetadataReferenceFeatureProvider()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
var builder = services.AddMvcCore();
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var metadataReferenceFeatureProvider = new MetadataReferenceFeatureProvider();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
builder.PartManager.FeatureProviders.Add(metadataReferenceFeatureProvider);
|
||||
|
||||
// Act
|
||||
builder.AddRazorViewEngine();
|
||||
|
||||
// Assert
|
||||
var actual = Assert.Single(
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
collection: builder.PartManager.FeatureProviders.OfType<MetadataReferenceFeatureProvider>());
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
Assert.Same(metadataReferenceFeatureProvider, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTagHelpersAsServices_ReplacesTagHelperActivatorAndTagHelperTypeResolver()
|
||||
{
|
||||
|
|
|
@ -1,161 +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 Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public class RazorViewEngineOptionsSetupTest
|
||||
{
|
||||
[Fact]
|
||||
public void RazorViewEngineOptionsSetup_SetsUpFileProvider()
|
||||
{
|
||||
// Arrange
|
||||
var options = new RazorViewEngineOptions();
|
||||
var expected = Mock.Of<IFileProvider>();
|
||||
var hostingEnv = new Mock<IHostingEnvironment>();
|
||||
hostingEnv.SetupGet(e => e.ContentRootFileProvider)
|
||||
.Returns(expected);
|
||||
|
||||
var optionsSetup = GetSetup(hostingEnvironment: hostingEnv.Object);
|
||||
|
||||
// Act
|
||||
optionsSetup.Configure(options);
|
||||
|
||||
// Assert
|
||||
var fileProvider = Assert.Single(options.FileProviders);
|
||||
Assert.Same(expected, fileProvider);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PostConfigure_SetsAllowRecompilingViewsOnFileChange_For21()
|
||||
{
|
||||
// Arrange
|
||||
var options = new RazorViewEngineOptions();
|
||||
var optionsSetup = GetSetup(CompatibilityVersion.Version_2_1);
|
||||
|
||||
// Act
|
||||
optionsSetup.Configure(options);
|
||||
optionsSetup.PostConfigure(string.Empty, options);
|
||||
|
||||
// Assert
|
||||
Assert.True(options.AllowRecompilingViewsOnFileChange);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(CompatibilityVersion.Version_2_2)]
|
||||
[InlineData(CompatibilityVersion.Latest)]
|
||||
public void PostConfigure_SetsAllowRecompilingViewsOnFileChange_InDevelopmentMode(CompatibilityVersion compatibilityVersion)
|
||||
{
|
||||
// Arrange
|
||||
var options = new RazorViewEngineOptions();
|
||||
var hostingEnv = Mock.Of<IHostingEnvironment>(env => env.EnvironmentName == EnvironmentName.Development);
|
||||
var optionsSetup = GetSetup(compatibilityVersion, hostingEnv);
|
||||
|
||||
// Act
|
||||
optionsSetup.Configure(options);
|
||||
optionsSetup.PostConfigure(string.Empty, options);
|
||||
|
||||
// Assert
|
||||
Assert.True(options.AllowRecompilingViewsOnFileChange);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(CompatibilityVersion.Version_2_2)]
|
||||
[InlineData(CompatibilityVersion.Latest)]
|
||||
public void PostConfigure_DoesNotSetAllowRecompilingViewsOnFileChange_WhenNotInDevelopment(CompatibilityVersion compatibilityVersion)
|
||||
{
|
||||
// Arrange
|
||||
var options = new RazorViewEngineOptions();
|
||||
var hostingEnv = Mock.Of<IHostingEnvironment>(env => env.EnvironmentName == EnvironmentName.Staging);
|
||||
var optionsSetup = GetSetup(compatibilityVersion, hostingEnv);
|
||||
|
||||
// Act
|
||||
optionsSetup.Configure(options);
|
||||
optionsSetup.PostConfigure(string.Empty, options);
|
||||
|
||||
// Assert
|
||||
Assert.False(options.AllowRecompilingViewsOnFileChange);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RazorViewEngineOptionsSetup_DoesNotOverwriteAllowRecompilingViewsOnFileChange_In21CompatMode()
|
||||
{
|
||||
// Arrange
|
||||
var hostingEnv = Mock.Of<IHostingEnvironment>(env => env.EnvironmentName == EnvironmentName.Staging);
|
||||
var compatibilityVersion = new MvcCompatibilityOptions { CompatibilityVersion = CompatibilityVersion.Version_2_1 };
|
||||
var optionsSetup = GetSetup(CompatibilityVersion.Version_2_1, hostingEnv);
|
||||
var serviceProvider = new ServiceCollection()
|
||||
.AddOptions()
|
||||
.AddSingleton<IConfigureOptions<RazorViewEngineOptions>>(optionsSetup)
|
||||
.Configure<RazorViewEngineOptions>(o => o.AllowRecompilingViewsOnFileChange = false)
|
||||
.BuildServiceProvider();
|
||||
|
||||
// Act
|
||||
var options = serviceProvider.GetRequiredService<IOptions<RazorViewEngineOptions>>();
|
||||
|
||||
// Assert
|
||||
Assert.False(options.Value.AllowRecompilingViewsOnFileChange);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RazorViewEngineOptionsSetup_ConfiguresAllowRecompilingViewsOnFileChange()
|
||||
{
|
||||
// Arrange
|
||||
var hostingEnv = Mock.Of<IHostingEnvironment>(env => env.EnvironmentName == EnvironmentName.Production);
|
||||
var compatibilityVersion = new MvcCompatibilityOptions { CompatibilityVersion = CompatibilityVersion.Version_2_2 };
|
||||
var optionsSetup = GetSetup(CompatibilityVersion.Version_2_2, hostingEnv);
|
||||
var serviceProvider = new ServiceCollection()
|
||||
.AddOptions()
|
||||
.AddSingleton<IConfigureOptions<RazorViewEngineOptions>>(optionsSetup)
|
||||
.BuildServiceProvider();
|
||||
|
||||
// Act
|
||||
var options = serviceProvider.GetRequiredService<IOptions<RazorViewEngineOptions>>();
|
||||
|
||||
// Assert
|
||||
Assert.False(options.Value.AllowRecompilingViewsOnFileChange);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RazorViewEngineOptionsSetup_DoesNotOverwriteAllowRecompilingViewsOnFileChange()
|
||||
{
|
||||
// Arrange
|
||||
var hostingEnv = Mock.Of<IHostingEnvironment>(env => env.EnvironmentName == EnvironmentName.Production);
|
||||
var optionsSetup = GetSetup(CompatibilityVersion.Version_2_2, hostingEnv);
|
||||
var serviceProvider = new ServiceCollection()
|
||||
.AddOptions()
|
||||
.AddSingleton<IConfigureOptions<RazorViewEngineOptions>>(optionsSetup)
|
||||
.Configure<RazorViewEngineOptions>(o => o.AllowRecompilingViewsOnFileChange = true)
|
||||
.BuildServiceProvider();
|
||||
|
||||
// Act
|
||||
var options = serviceProvider.GetRequiredService<IOptions<RazorViewEngineOptions>>();
|
||||
|
||||
// Assert
|
||||
Assert.True(options.Value.AllowRecompilingViewsOnFileChange);
|
||||
}
|
||||
|
||||
private static RazorViewEngineOptionsSetup GetSetup(
|
||||
CompatibilityVersion compatibilityVersion = CompatibilityVersion.Latest,
|
||||
IHostingEnvironment hostingEnvironment = null)
|
||||
{
|
||||
hostingEnvironment = hostingEnvironment ?? Mock.Of<IHostingEnvironment>();
|
||||
var compatibilityOptions = new MvcCompatibilityOptions { CompatibilityVersion = compatibilityVersion };
|
||||
|
||||
return new RazorViewEngineOptionsSetup(
|
||||
hostingEnvironment,
|
||||
NullLoggerFactory.Instance,
|
||||
Options.Options.Create(compatibilityOptions));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,257 +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 System.Linq;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
public class FileProviderRazorProjectFileSystemTest
|
||||
{
|
||||
[Fact]
|
||||
public void EnumerateFiles_ReturnsEmptySequenceIfNoCshtmlFilesArePresent()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider("BasePath");
|
||||
var file1 = fileProvider.AddFile("/File1.txt", "content");
|
||||
var file2 = fileProvider.AddFile("/File2.js", "content");
|
||||
fileProvider.AddDirectoryContent("/", new IFileInfo[] { file1, file2 });
|
||||
|
||||
var accessor = Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == fileProvider);
|
||||
|
||||
var fileSystem = new FileProviderRazorProjectFileSystem(accessor, Mock.Of<IHostingEnvironment>(e => e.ContentRootPath == "BasePath"));
|
||||
|
||||
// Act
|
||||
var razorFiles = fileSystem.EnumerateItems("/");
|
||||
|
||||
// Assert
|
||||
Assert.Empty(razorFiles);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnumerateFiles_ReturnsCshtmlFiles()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider("BasePath");
|
||||
var file1 = fileProvider.AddFile("/File1.cshtml", "content");
|
||||
var file2 = fileProvider.AddFile("/File2.js", "content");
|
||||
var file3 = fileProvider.AddFile("/File3.cshtml", "content");
|
||||
fileProvider.AddDirectoryContent("/", new IFileInfo[] { file1, file2, file3 });
|
||||
|
||||
var accessor = Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == fileProvider);
|
||||
|
||||
var fileSystem = new FileProviderRazorProjectFileSystem(accessor, Mock.Of<IHostingEnvironment>(e => e.ContentRootPath == "BasePath"));
|
||||
|
||||
// Act
|
||||
var razorFiles = fileSystem.EnumerateItems("/");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
razorFiles.OrderBy(f => f.FilePath),
|
||||
file =>
|
||||
{
|
||||
Assert.Equal("/File1.cshtml", file.FilePath);
|
||||
Assert.Equal("/", file.BasePath);
|
||||
Assert.Equal(Path.Combine("BasePath", "File1.cshtml"), file.PhysicalPath);
|
||||
Assert.Equal("File1.cshtml", file.RelativePhysicalPath);
|
||||
},
|
||||
file =>
|
||||
{
|
||||
Assert.Equal("/File3.cshtml", file.FilePath);
|
||||
Assert.Equal("/", file.BasePath);
|
||||
Assert.Equal(Path.Combine("BasePath", "File3.cshtml"), file.PhysicalPath);
|
||||
Assert.Equal("File3.cshtml", file.RelativePhysicalPath);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnumerateFiles_IteratesOverAllCshtmlUnderRoot()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider("BasePath");
|
||||
var directory1 = new TestDirectoryFileInfo
|
||||
{
|
||||
Name = "Level1-Dir1",
|
||||
};
|
||||
var file1 = fileProvider.AddFile("File1.cshtml", "content");
|
||||
var directory2 = new TestDirectoryFileInfo
|
||||
{
|
||||
Name = "Level1-Dir2",
|
||||
};
|
||||
fileProvider.AddDirectoryContent("/", new IFileInfo[] { directory1, file1, directory2 });
|
||||
|
||||
var file2 = fileProvider.AddFile("/Level1-Dir1/File2.cshtml", "content");
|
||||
var file3 = fileProvider.AddFile("/Level1-Dir1/File3.cshtml", "content");
|
||||
var file4 = fileProvider.AddFile("/Level1-Dir1/File4.txt", "content");
|
||||
var directory3 = new TestDirectoryFileInfo
|
||||
{
|
||||
Name = "Level2-Dir1"
|
||||
};
|
||||
fileProvider.AddDirectoryContent("/Level1-Dir1", new IFileInfo[] { file2, directory3, file3, file4 });
|
||||
var file5 = fileProvider.AddFile(Path.Combine("Level1-Dir2", "File5.cshtml"), "content");
|
||||
fileProvider.AddDirectoryContent("/Level1-Dir2", new IFileInfo[] { file5 });
|
||||
fileProvider.AddDirectoryContent("/Level1/Level2", new IFileInfo[0]);
|
||||
|
||||
var accessor = Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == fileProvider);
|
||||
|
||||
var fileSystem = new FileProviderRazorProjectFileSystem(accessor, Mock.Of<IHostingEnvironment>(e => e.ContentRootPath == "BasePath"));
|
||||
|
||||
// Act
|
||||
var razorFiles = fileSystem.EnumerateItems("/");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(razorFiles.OrderBy(f => f.FilePath),
|
||||
file =>
|
||||
{
|
||||
Assert.Equal("/File1.cshtml", file.FilePath);
|
||||
Assert.Equal("/", file.BasePath);
|
||||
Assert.Equal(Path.Combine("BasePath", "File1.cshtml"), file.PhysicalPath);
|
||||
Assert.Equal("File1.cshtml", file.RelativePhysicalPath);
|
||||
},
|
||||
file =>
|
||||
{
|
||||
Assert.Equal("/Level1-Dir1/File2.cshtml", file.FilePath);
|
||||
Assert.Equal("/", file.BasePath);
|
||||
Assert.Equal(Path.Combine("BasePath", "Level1-Dir1", "File2.cshtml"), file.PhysicalPath);
|
||||
Assert.Equal(Path.Combine("Level1-Dir1", "File2.cshtml"), file.RelativePhysicalPath);
|
||||
},
|
||||
file =>
|
||||
{
|
||||
Assert.Equal("/Level1-Dir1/File3.cshtml", file.FilePath);
|
||||
Assert.Equal("/", file.BasePath);
|
||||
Assert.Equal(Path.Combine("BasePath", "Level1-Dir1", "File3.cshtml"), file.PhysicalPath);
|
||||
Assert.Equal(Path.Combine("Level1-Dir1", "File3.cshtml"), file.RelativePhysicalPath);
|
||||
},
|
||||
file =>
|
||||
{
|
||||
Assert.Equal("/Level1-Dir2/File5.cshtml", file.FilePath);
|
||||
Assert.Equal("/", file.BasePath);
|
||||
Assert.Equal(Path.Combine("BasePath", "Level1-Dir2", "File5.cshtml"), file.PhysicalPath);
|
||||
Assert.Equal(Path.Combine("Level1-Dir2", "File5.cshtml"), file.RelativePhysicalPath);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnumerateFiles_IteratesOverAllCshtmlUnderPath()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider("BasePath");
|
||||
var directory1 = new TestDirectoryFileInfo
|
||||
{
|
||||
Name = "Level1-Dir1",
|
||||
};
|
||||
var file1 = fileProvider.AddFile("/File1.cshtml", "content");
|
||||
var directory2 = new TestDirectoryFileInfo
|
||||
{
|
||||
Name = "Level1-Dir2",
|
||||
};
|
||||
fileProvider.AddDirectoryContent("/", new IFileInfo[] { directory1, file1, directory2 });
|
||||
|
||||
var file2 = fileProvider.AddFile("/Level1-Dir1/File2.cshtml", "content");
|
||||
var file3 = fileProvider.AddFile("/Level1-Dir1/File3.cshtml", "content");
|
||||
var file4 = fileProvider.AddFile("/Level1-Dir1/File4.txt", "content");
|
||||
var directory3 = new TestDirectoryFileInfo
|
||||
{
|
||||
Name = "Level2-Dir1"
|
||||
};
|
||||
fileProvider.AddDirectoryContent("/Level1-Dir1", new IFileInfo[] { file2, directory3, file3, file4 });
|
||||
var file5 = fileProvider.AddFile(Path.Combine("Level1-Dir2", "File5.cshtml"), "content");
|
||||
fileProvider.AddDirectoryContent("/Level1-Dir2", new IFileInfo[] { file5 });
|
||||
fileProvider.AddDirectoryContent("/Level1/Level2", new IFileInfo[0]);
|
||||
|
||||
var accessor = Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == fileProvider);
|
||||
|
||||
var fileSystem = new FileProviderRazorProjectFileSystem(accessor, Mock.Of<IHostingEnvironment>(e => e.ContentRootPath == "BasePath"));
|
||||
|
||||
// Act
|
||||
var razorFiles = fileSystem.EnumerateItems("/Level1-Dir1");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(razorFiles.OrderBy(f => f.FilePath),
|
||||
file =>
|
||||
{
|
||||
Assert.Equal("/File2.cshtml", file.FilePath);
|
||||
Assert.Equal("/Level1-Dir1", file.BasePath);
|
||||
Assert.Equal(Path.Combine("BasePath", "Level1-Dir1", "File2.cshtml"), file.PhysicalPath);
|
||||
Assert.Equal(Path.Combine("Level1-Dir1", "File2.cshtml"), file.RelativePhysicalPath);
|
||||
},
|
||||
file =>
|
||||
{
|
||||
Assert.Equal("/File3.cshtml", file.FilePath);
|
||||
Assert.Equal("/Level1-Dir1", file.BasePath);
|
||||
Assert.Equal(Path.Combine("BasePath", "Level1-Dir1", "File3.cshtml"), file.PhysicalPath);
|
||||
Assert.Equal(Path.Combine("Level1-Dir1", "File3.cshtml"), file.RelativePhysicalPath);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetItem_ReturnsFileFromDisk()
|
||||
{
|
||||
var fileProvider = new TestFileProvider("BasePath");
|
||||
var file1 = fileProvider.AddFile("/File1.cshtml", "content");
|
||||
var file2 = fileProvider.AddFile("/File2.js", "content");
|
||||
var file3 = fileProvider.AddFile("/File3.cshtml", "content");
|
||||
fileProvider.AddDirectoryContent("/", new IFileInfo[] { file1, file2, file3 });
|
||||
|
||||
var accessor = Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == fileProvider);
|
||||
|
||||
var fileSystem = new FileProviderRazorProjectFileSystem(accessor, Mock.Of<IHostingEnvironment>(e => e.ContentRootPath == "BasePath"));
|
||||
|
||||
// Act
|
||||
var item = fileSystem.GetItem("/File3.cshtml");
|
||||
|
||||
// Assert
|
||||
Assert.True(item.Exists);
|
||||
Assert.Equal("/File3.cshtml", item.FilePath);
|
||||
Assert.Equal(string.Empty, item.BasePath);
|
||||
Assert.Equal(Path.Combine("BasePath", "File3.cshtml"), item.PhysicalPath);
|
||||
Assert.Equal("File3.cshtml", item.RelativePhysicalPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetItem_PhysicalPathDoesNotStartWithContentRoot_ReturnsNull()
|
||||
{
|
||||
var fileProvider = new TestFileProvider("BasePath2");
|
||||
var file1 = fileProvider.AddFile("/File1.cshtml", "content");
|
||||
var file2 = fileProvider.AddFile("/File2.js", "content");
|
||||
var file3 = fileProvider.AddFile("/File3.cshtml", "content");
|
||||
fileProvider.AddDirectoryContent("/", new IFileInfo[] { file1, file2, file3 });
|
||||
|
||||
var accessor = Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == fileProvider);
|
||||
|
||||
var fileSystem = new FileProviderRazorProjectFileSystem(accessor, Mock.Of<IHostingEnvironment>(e => e.ContentRootPath == "BasePath"));
|
||||
|
||||
// Act
|
||||
var item = fileSystem.GetItem("/File3.cshtml");
|
||||
|
||||
// Assert
|
||||
Assert.True(item.Exists);
|
||||
Assert.Equal("/File3.cshtml", item.FilePath);
|
||||
Assert.Equal(string.Empty, item.BasePath);
|
||||
Assert.Equal(Path.Combine("BasePath2", "File3.cshtml"), item.PhysicalPath);
|
||||
Assert.Null(item.RelativePhysicalPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetItem_ReturnsNotFoundResult()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider("BasePath");
|
||||
var file = fileProvider.AddFile("/SomeFile.cshtml", "content");
|
||||
fileProvider.AddDirectoryContent("/", new IFileInfo[] { file });
|
||||
var accessor = Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == fileProvider);
|
||||
|
||||
var fileSystem = new FileProviderRazorProjectFileSystem(accessor, Mock.Of<IHostingEnvironment>(e => e.ContentRootPath == "BasePath"));
|
||||
|
||||
// Act
|
||||
var item = fileSystem.GetItem("/NotFound.cshtml");
|
||||
|
||||
// Assert
|
||||
Assert.False(item.Exists);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
// 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 Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||
{
|
||||
public class RazorFileHierarchyTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetViewStartPaths_ForFileAtRoot()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[] { "/_ViewStart.cshtml", };
|
||||
var path = "/Home.cshtml";
|
||||
|
||||
// Act
|
||||
var actual = RazorFileHierarchy.GetViewStartPaths(path);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetViewStartPaths_ForForFileInViewsDirectory()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
"/Views/Home/_ViewStart.cshtml",
|
||||
"/Views/_ViewStart.cshtml",
|
||||
"/_ViewStart.cshtml",
|
||||
};
|
||||
var path = "/Views/Home/Index.cshtml";
|
||||
|
||||
// Act
|
||||
var actual = RazorFileHierarchy.GetViewStartPaths(path);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetViewStartPaths_ForForFileInAreasDirectory()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new[]
|
||||
{
|
||||
"/Areas/Views/MyArea/Home/_ViewStart.cshtml",
|
||||
"/Areas/Views/MyArea/_ViewStart.cshtml",
|
||||
"/Areas/Views/_ViewStart.cshtml",
|
||||
"/Areas/_ViewStart.cshtml",
|
||||
"/_ViewStart.cshtml",
|
||||
};
|
||||
var path = "/Areas/Views/MyArea/Home/Index.cshtml";
|
||||
|
||||
// Act
|
||||
var actual = RazorFileHierarchy.GetViewStartPaths(path);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -72,25 +72,5 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
// Assert
|
||||
Assert.Equal(viewLocations, formats, StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRazorOptions_ConfiguresOptionsAsExpected()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection().AddOptions();
|
||||
var fileProvider = new TestFileProvider();
|
||||
|
||||
// Act
|
||||
var builder = new MvcBuilder(services, new ApplicationPartManager());
|
||||
builder.AddRazorOptions(options =>
|
||||
{
|
||||
options.FileProviders.Add(fileProvider);
|
||||
});
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// Assert
|
||||
var accessor = serviceProvider.GetRequiredService<IOptions<RazorViewEngineOptions>>();
|
||||
Assert.Same(fileProvider, accessor.Value.FileProviders[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,17 +8,13 @@ using System.Threading;
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Extensions.WebEncoders.Testing;
|
||||
|
@ -904,62 +900,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
pageFactory.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindView_InvokesPageFactoryIfViewStartExpirationTokensHaveExpired()
|
||||
{
|
||||
// Arrange
|
||||
var page1 = Mock.Of<IRazorPage>();
|
||||
var page2 = Mock.Of<IRazorPage>();
|
||||
var viewStart = Mock.Of<IRazorPage>();
|
||||
var sequence = new MockSequence();
|
||||
var cancellationTokenSource = new CancellationTokenSource();
|
||||
var changeToken = new CancellationChangeToken(cancellationTokenSource.Token);
|
||||
|
||||
var pageFactory = new Mock<IRazorPageFactoryProvider>();
|
||||
pageFactory
|
||||
.InSequence(sequence)
|
||||
.Setup(p => p.CreateFactory("/Views/bar/baz.cshtml"))
|
||||
.Returns(GetPageFactoryResult(() => page1));
|
||||
pageFactory
|
||||
.InSequence(sequence)
|
||||
.Setup(p => p.CreateFactory("/Views/_ViewStart.cshtml"))
|
||||
.Returns(GetPageFactoryResult(factory: null, changeTokens: new[] { changeToken }))
|
||||
.Verifiable();
|
||||
pageFactory
|
||||
.InSequence(sequence)
|
||||
.Setup(p => p.CreateFactory("/Views/bar/baz.cshtml"))
|
||||
.Returns(GetPageFactoryResult(() => page2));
|
||||
pageFactory
|
||||
.InSequence(sequence)
|
||||
.Setup(p => p.CreateFactory("/Views/_ViewStart.cshtml"))
|
||||
.Returns(GetPageFactoryResult(() => viewStart));
|
||||
|
||||
var fileSystem = new VirtualRazorProjectFileSystem();
|
||||
var viewEngine = CreateViewEngine(pageFactory.Object, fileSystem: fileSystem);
|
||||
var context = GetActionContext(_controllerTestContext);
|
||||
|
||||
// Act 1
|
||||
var result1 = viewEngine.FindView(context, "baz", isMainPage: true);
|
||||
|
||||
// Assert 1
|
||||
Assert.True(result1.Success);
|
||||
var view1 = Assert.IsType<RazorView>(result1.View);
|
||||
Assert.Same(page1, view1.RazorPage);
|
||||
Assert.Empty(view1.ViewStartPages);
|
||||
|
||||
// Act 2
|
||||
cancellationTokenSource.Cancel();
|
||||
var result2 = viewEngine.FindView(context, "baz", isMainPage: true);
|
||||
|
||||
// Assert 2
|
||||
Assert.True(result2.Success);
|
||||
var view2 = Assert.IsType<RazorView>(result2.View);
|
||||
Assert.Same(page2, view2.RazorPage);
|
||||
var actualViewStart = Assert.Single(view2.ViewStartPages);
|
||||
Assert.Equal(viewStart, actualViewStart);
|
||||
pageFactory.Verify();
|
||||
}
|
||||
|
||||
// This test validates an important perf scenario of RazorViewEngine not constructing
|
||||
// multiple strings for views that do not exist in the file system on a per-request basis.
|
||||
[Fact]
|
||||
|
@ -1363,39 +1303,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
Assert.Equal(expected, result.SearchedLocations);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateCacheResult_LogsPrecompiledViewFound()
|
||||
{
|
||||
// Arrange
|
||||
var sink = new TestSink();
|
||||
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
||||
|
||||
var relativePath = "/Views/Foo/details.cshtml";
|
||||
var factoryResult = GetPageFactoryResult(() => Mock.Of<IRazorPage>());
|
||||
factoryResult.ViewDescriptor.IsPrecompiled = true;
|
||||
var pageFactory = new Mock<IRazorPageFactoryProvider>();
|
||||
pageFactory
|
||||
.Setup(p => p.CreateFactory(relativePath))
|
||||
.Returns(factoryResult)
|
||||
.Verifiable();
|
||||
|
||||
var viewEngine = new RazorViewEngine(
|
||||
pageFactory.Object,
|
||||
Mock.Of<IRazorPageActivator>(),
|
||||
new HtmlTestEncoder(),
|
||||
GetOptionsAccessor(expanders: null),
|
||||
new VirtualRazorProjectFileSystem(),
|
||||
loggerFactory,
|
||||
new DiagnosticListener("Microsoft.AspNetCore.Mvc.Razor"));
|
||||
|
||||
// Act
|
||||
var result = viewEngine.CreateCacheResult(null, relativePath, false);
|
||||
|
||||
// Assert
|
||||
var logMessage = Assert.Single(sink.Writes);
|
||||
Assert.Equal($"Using precompiled view for '{relativePath}'.", logMessage.State.ToString());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Test-View.cshtml")]
|
||||
[InlineData("~/Test-View.CSHTML")]
|
||||
|
@ -1989,12 +1896,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
|
||||
private TestableRazorViewEngine CreateViewEngine(
|
||||
IRazorPageFactoryProvider pageFactory = null,
|
||||
IEnumerable<IViewLocationExpander> expanders = null,
|
||||
RazorProjectFileSystem fileSystem = null)
|
||||
IEnumerable<IViewLocationExpander> expanders = null)
|
||||
{
|
||||
pageFactory = pageFactory ?? Mock.Of<IRazorPageFactoryProvider>();
|
||||
fileSystem = fileSystem ?? new VirtualRazorProjectFileSystem();
|
||||
return new TestableRazorViewEngine(pageFactory, GetOptionsAccessor(expanders), fileSystem);
|
||||
return new TestableRazorViewEngine(pageFactory, GetOptionsAccessor(expanders));
|
||||
}
|
||||
|
||||
private static IOptions<RazorViewEngineOptions> GetOptionsAccessor(
|
||||
|
@ -2003,10 +1908,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
IEnumerable<string> areaViewLocationFormats = null,
|
||||
IEnumerable<string> pageViewLocationFormats = null)
|
||||
{
|
||||
var optionsSetup = new RazorViewEngineOptionsSetup(
|
||||
Mock.Of<IHostingEnvironment>(),
|
||||
NullLoggerFactory.Instance,
|
||||
Options.Create(new MvcCompatibilityOptions()));
|
||||
var optionsSetup = new RazorViewEngineOptionsSetup(Mock.Of<IHostingEnvironment>());
|
||||
|
||||
var options = new RazorViewEngineOptions();
|
||||
optionsSetup.Configure(options);
|
||||
|
@ -2097,20 +1999,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
public TestableRazorViewEngine(
|
||||
IRazorPageFactoryProvider pageFactory,
|
||||
IOptions<RazorViewEngineOptions> optionsAccessor)
|
||||
: this(
|
||||
pageFactory,
|
||||
optionsAccessor,
|
||||
new FileProviderRazorProjectFileSystem(
|
||||
Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == new TestFileProvider()),
|
||||
Mock.Of<IHostingEnvironment>()))
|
||||
{
|
||||
}
|
||||
|
||||
public TestableRazorViewEngine(
|
||||
IRazorPageFactoryProvider pageFactory,
|
||||
IOptions<RazorViewEngineOptions> optionsAccessor,
|
||||
RazorProjectFileSystem fileSystem)
|
||||
: base(pageFactory, Mock.Of<IRazorPageActivator>(), new HtmlTestEncoder(), optionsAccessor, fileSystem, NullLoggerFactory.Instance, new DiagnosticListener("Microsoft.AspNetCore.Mvc.Razor"))
|
||||
: base(pageFactory, Mock.Of<IRazorPageActivator>(), new HtmlTestEncoder(), optionsAccessor, NullLoggerFactory.Instance, new DiagnosticListener("Microsoft.AspNetCore.Mvc.Razor"))
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -9,12 +9,10 @@ using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
|||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.AspNetCore.Razor.Hosting;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
using static Microsoft.AspNetCore.Razor.Hosting.TestRazorCompiledItem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||
{
|
||||
|
@ -125,88 +123,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_ValidatesChecksum_RejectsPageWhenContentDoesntMatch()
|
||||
{
|
||||
// Arrange
|
||||
var descriptors = new[]
|
||||
{
|
||||
CreateVersion_2_1_Descriptor("/Pages/About.cshtml", metadata: new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Pages/About.cshtml"),
|
||||
}),
|
||||
};
|
||||
|
||||
var fileSystem = new VirtualRazorProjectFileSystem();
|
||||
fileSystem.Add(new TestRazorProjectItem("/Pages/About.cshtml", "some other content"));
|
||||
|
||||
var provider = CreateProvider(descriptors: descriptors, fileSystem: fileSystem);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(context.RouteModels);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_ValidatesChecksum_AcceptsPageWhenContentMatches()
|
||||
{
|
||||
// Arrange
|
||||
var descriptors = new[]
|
||||
{
|
||||
CreateVersion_2_1_Descriptor("/Pages/About.cshtml", metadata: new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Pages/About.cshtml"),
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some import"), "/Pages/_ViewImports.cshtml"),
|
||||
}),
|
||||
};
|
||||
|
||||
var fileSystem = new VirtualRazorProjectFileSystem();
|
||||
fileSystem.Add(new TestRazorProjectItem("/Pages/About.cshtml", "some content"));
|
||||
fileSystem.Add(new TestRazorProjectItem("/Pages/_ViewImports.cshtml", "some import"));
|
||||
|
||||
var provider = CreateProvider(descriptors: descriptors, fileSystem: fileSystem);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
context.RouteModels,
|
||||
result => Assert.Equal("/Pages/About.cshtml", result.RelativePath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_ValidatesChecksum_SkipsValidationWhenMainSourceMissing()
|
||||
{
|
||||
// Arrange
|
||||
var descriptors = new[]
|
||||
{
|
||||
CreateVersion_2_1_Descriptor("/Pages/About.cshtml", metadata: new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Pages/About.cshtml"),
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some import"), "/Pages/_ViewImports.cshtml"),
|
||||
}),
|
||||
};
|
||||
|
||||
var fileSystem = new VirtualRazorProjectFileSystem();
|
||||
fileSystem.Add(new TestRazorProjectItem("/Pages/_ViewImports.cshtml", "some other import"));
|
||||
|
||||
var provider = CreateProvider(descriptors: descriptors, fileSystem: fileSystem);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
context.RouteModels,
|
||||
result => Assert.Equal("/Pages/About.cshtml", result.RelativePath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_AddsModelsForCompiledAreaPages()
|
||||
{
|
||||
|
@ -526,19 +442,10 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
var descriptors = new[]
|
||||
{
|
||||
// Page coming from the app
|
||||
CreateVersion_2_1_Descriptor("/Pages/About.cshtml", metadata: new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Pages/About.cshtml"),
|
||||
}),
|
||||
CreateVersion_2_1_Descriptor("/Pages/Home.cshtml", metadata: new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Pages/Index.cshtml"),
|
||||
}),
|
||||
CreateVersion_2_1_Descriptor("/Pages/About.cshtml"),
|
||||
CreateVersion_2_1_Descriptor("/Pages/Home.cshtml"),
|
||||
// Page coming from the app
|
||||
CreateVersion_2_1_Descriptor("/Pages/About.cshtml", metadata: new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Pages/About.cshtml"),
|
||||
}),
|
||||
CreateVersion_2_1_Descriptor("/Pages/About.cshtml"),
|
||||
};
|
||||
|
||||
var provider = CreateProvider(descriptors: descriptors);
|
||||
|
@ -672,17 +579,13 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
|
||||
private TestCompiledPageRouteModelProvider CreateProvider(
|
||||
RazorPagesOptions options = null,
|
||||
IList<CompiledViewDescriptor> descriptors = null,
|
||||
VirtualRazorProjectFileSystem fileSystem = null)
|
||||
IList<CompiledViewDescriptor> descriptors = null)
|
||||
{
|
||||
options = options ?? new RazorPagesOptions();
|
||||
fileSystem = fileSystem ?? new VirtualRazorProjectFileSystem();
|
||||
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem);
|
||||
|
||||
var provider = new TestCompiledPageRouteModelProvider(
|
||||
new ApplicationPartManager(),
|
||||
Options.Create(options),
|
||||
projectEngine,
|
||||
NullLogger<CompiledPageRouteModelProvider>.Instance);
|
||||
|
||||
provider.Descriptors.AddRange(descriptors ?? Array.Empty<CompiledViewDescriptor>());
|
||||
|
@ -694,7 +597,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
{
|
||||
return new CompiledViewDescriptor
|
||||
{
|
||||
IsPrecompiled = true,
|
||||
RelativePath = path,
|
||||
ViewAttribute = new RazorPageAttribute(path, typeof(object), routeTemplate),
|
||||
};
|
||||
|
@ -706,7 +608,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
{
|
||||
return new CompiledViewDescriptor
|
||||
{
|
||||
IsPrecompiled = true,
|
||||
RelativePath = path,
|
||||
Item = new TestRazorCompiledItem(typeof(object), "mvc.1.0.razor-page", path, metadata ?? Array.Empty<object>()),
|
||||
};
|
||||
|
@ -717,9 +618,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
public TestCompiledPageRouteModelProvider(
|
||||
ApplicationPartManager partManager,
|
||||
IOptions<RazorPagesOptions> options,
|
||||
RazorProjectEngine projectEngine,
|
||||
ILogger<CompiledPageRouteModelProvider> logger)
|
||||
: base(partManager, options, projectEngine, logger)
|
||||
: base(partManager, options, logger)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -1,350 +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.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||
{
|
||||
public class RazorProjectPageRouteModelProviderTest
|
||||
{
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_ReturnsPagesWithPageDirective()
|
||||
{
|
||||
// Arrange
|
||||
var fileSystem = new VirtualRazorProjectFileSystem();
|
||||
fileSystem.Add(new TestRazorProjectItem("/Pages/Home.cshtml", "@page"));
|
||||
fileSystem.Add(new TestRazorProjectItem("/Pages/Test.cshtml", "Hello world"));
|
||||
|
||||
var optionsManager = Options.Create(new RazorPagesOptions());
|
||||
optionsManager.Value.RootDirectory = "/";
|
||||
var provider = new RazorProjectPageRouteModelProvider(fileSystem, optionsManager, NullLoggerFactory.Instance);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.RouteModels,
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Pages/Home.cshtml", model.RelativePath);
|
||||
Assert.Equal("/Pages/Home", model.ViewEnginePath);
|
||||
Assert.Collection(model.Selectors,
|
||||
selector => Assert.Equal("Pages/Home", selector.AttributeRouteModel.Template));
|
||||
Assert.Collection(model.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/Pages/Home", kvp.Value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_AddsPagesUnderAreas()
|
||||
{
|
||||
// Arrange
|
||||
var fileSystem = new VirtualRazorProjectFileSystem();
|
||||
fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/Manage/Categories.cshtml", "@page"));
|
||||
fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/Index.cshtml", "@page"));
|
||||
fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/List.cshtml", "@page \"{sortOrder?}\""));
|
||||
fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/_Test.cshtml", "@page"));
|
||||
|
||||
var optionsManager = Options.Create(new RazorPagesOptions { AllowAreas = true });
|
||||
var provider = new RazorProjectPageRouteModelProvider(fileSystem, optionsManager, NullLoggerFactory.Instance);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.RouteModels,
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Areas/Products/Pages/Index.cshtml", model.RelativePath);
|
||||
Assert.Equal("/Index", model.ViewEnginePath);
|
||||
Assert.Collection(model.Selectors,
|
||||
selector => Assert.Equal("Products/Index", selector.AttributeRouteModel.Template),
|
||||
selector => Assert.Equal("Products", selector.AttributeRouteModel.Template));
|
||||
Assert.Collection(model.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.Equal("Products", kvp.Value);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/Index", kvp.Value);
|
||||
});
|
||||
},
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Areas/Products/Pages/List.cshtml", model.RelativePath);
|
||||
Assert.Equal("/List", model.ViewEnginePath);
|
||||
Assert.Collection(model.Selectors,
|
||||
selector => Assert.Equal("Products/List/{sortOrder?}", selector.AttributeRouteModel.Template));
|
||||
Assert.Collection(model.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.Equal("Products", kvp.Value);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/List", kvp.Value);
|
||||
});
|
||||
},
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Areas/Products/Pages/_Test.cshtml", model.RelativePath);
|
||||
Assert.Equal("/_Test", model.ViewEnginePath);
|
||||
Assert.Collection(model.Selectors,
|
||||
selector => Assert.Equal("Products/_Test", selector.AttributeRouteModel.Template));
|
||||
Assert.Collection(model.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.Equal("Products", kvp.Value);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/_Test", kvp.Value);
|
||||
});
|
||||
},
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Areas/Products/Pages/Manage/Categories.cshtml", model.RelativePath);
|
||||
Assert.Equal("/Manage/Categories", model.ViewEnginePath);
|
||||
Assert.Collection(model.Selectors,
|
||||
selector => Assert.Equal("Products/Manage/Categories", selector.AttributeRouteModel.Template));
|
||||
Assert.Collection(model.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.Equal("Products", kvp.Value);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/Manage/Categories", kvp.Value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_DoesNotAddPagesUnderAreas_WhenFeatureIsDisabled()
|
||||
{
|
||||
// Arrange
|
||||
var fileSystem = new VirtualRazorProjectFileSystem();
|
||||
fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/Manage/Categories.cshtml", "@page"));
|
||||
fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/Index.cshtml", "@page"));
|
||||
fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/List.cshtml", "@page \"{sortOrder?}\""));
|
||||
fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/_ViewStart.cshtml", "@page"));
|
||||
fileSystem.Add(new TestRazorProjectItem("/Pages/About.cshtml", "@page"));
|
||||
|
||||
var optionsManager = Options.Create(new RazorPagesOptions { AllowAreas = false });
|
||||
var provider = new RazorProjectPageRouteModelProvider(fileSystem, optionsManager, NullLoggerFactory.Instance);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.RouteModels,
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Pages/About.cshtml", model.RelativePath);
|
||||
Assert.Equal("/About", model.ViewEnginePath);
|
||||
Assert.Collection(model.Selectors,
|
||||
selector => Assert.Equal("About", selector.AttributeRouteModel.Template));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_DoesNotAddAreaAndNonAreaRoutesForAPage()
|
||||
{
|
||||
// Arrange
|
||||
var fileSystem = new VirtualRazorProjectFileSystem();
|
||||
fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/Categories.cshtml", "@page"));
|
||||
fileSystem.Add(new TestRazorProjectItem("/About.cshtml", "@page"));
|
||||
// We shouldn't add a route for the following paths.
|
||||
fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Categories.cshtml", "@page"));
|
||||
fileSystem.Add(new TestRazorProjectItem("/Areas/Home.cshtml", "@page"));
|
||||
|
||||
var optionsManager = Options.Create(new RazorPagesOptions
|
||||
{
|
||||
RootDirectory = "/",
|
||||
AllowAreas = true,
|
||||
});
|
||||
var provider = new RazorProjectPageRouteModelProvider(fileSystem, optionsManager, NullLoggerFactory.Instance);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.RouteModels,
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Areas/Products/Pages/Categories.cshtml", model.RelativePath);
|
||||
Assert.Equal("/Categories", model.ViewEnginePath);
|
||||
Assert.Collection(model.Selectors,
|
||||
selector => Assert.Equal("Products/Categories", selector.AttributeRouteModel.Template));
|
||||
Assert.Collection(model.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.Equal("Products", kvp.Value);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/Categories", kvp.Value);
|
||||
});
|
||||
},
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/About.cshtml", model.RelativePath);
|
||||
Assert.Equal("/About", model.ViewEnginePath);
|
||||
Assert.Collection(model.Selectors,
|
||||
selector => Assert.Equal("About", selector.AttributeRouteModel.Template));
|
||||
Assert.Collection(model.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/About", kvp.Value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_AddsMultipleSelectorsForIndexPages()
|
||||
{
|
||||
// Arrange
|
||||
var fileSystem = new VirtualRazorProjectFileSystem();
|
||||
fileSystem.Add(new TestRazorProjectItem("/Pages/Index.cshtml", "@page"));
|
||||
fileSystem.Add(new TestRazorProjectItem("/Pages/Test.cshtml", "Hello world"));
|
||||
fileSystem.Add(new TestRazorProjectItem("/Pages/Admin/Index.cshtml", "@page \"test\""));
|
||||
|
||||
var optionsManager = Options.Create(new RazorPagesOptions());
|
||||
optionsManager.Value.RootDirectory = "/";
|
||||
var provider = new RazorProjectPageRouteModelProvider(fileSystem, optionsManager, NullLoggerFactory.Instance);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.RouteModels,
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Pages/Index.cshtml", model.RelativePath);
|
||||
Assert.Equal("/Pages/Index", model.ViewEnginePath);
|
||||
Assert.Collection(model.Selectors,
|
||||
selector => Assert.Equal("Pages/Index", selector.AttributeRouteModel.Template),
|
||||
selector => Assert.Equal("Pages", selector.AttributeRouteModel.Template));
|
||||
},
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Pages/Admin/Index.cshtml", model.RelativePath);
|
||||
Assert.Equal("/Pages/Admin/Index", model.ViewEnginePath);
|
||||
Assert.Collection(model.Selectors,
|
||||
selector => Assert.Equal("Pages/Admin/Index/test", selector.AttributeRouteModel.Template),
|
||||
selector => Assert.Equal("Pages/Admin/test", selector.AttributeRouteModel.Template));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_AllowsRouteTemplateWithOverridePattern()
|
||||
{
|
||||
// Arrange
|
||||
var fileSystem = new VirtualRazorProjectFileSystem();
|
||||
fileSystem.Add(new TestRazorProjectItem("/Index.cshtml", "@page \"/custom-route\""));
|
||||
|
||||
var optionsManager = Options.Create(new RazorPagesOptions());
|
||||
optionsManager.Value.RootDirectory = "/";
|
||||
var provider = new RazorProjectPageRouteModelProvider(fileSystem, optionsManager, NullLoggerFactory.Instance);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
context.RouteModels,
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Index.cshtml", model.RelativePath);
|
||||
Assert.Equal("/Index", model.ViewEnginePath);
|
||||
Assert.Collection(
|
||||
model.Selectors,
|
||||
selector => Assert.Equal("custom-route", selector.AttributeRouteModel.Template));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_DiscoversFilesUnderBasePath()
|
||||
{
|
||||
// Arrange
|
||||
var fileSystem = new VirtualRazorProjectFileSystem();
|
||||
fileSystem.Add(new TestRazorProjectItem("/Pages/Index.cshtml", "@page"));
|
||||
fileSystem.Add(new TestRazorProjectItem("/Pages/_Layout.cshtml", ""));
|
||||
fileSystem.Add(new TestRazorProjectItem("/NotPages/Index.cshtml", "@page"));
|
||||
fileSystem.Add(new TestRazorProjectItem("/NotPages/_Layout.cshtml", "@page"));
|
||||
fileSystem.Add(new TestRazorProjectItem("/Index.cshtml", "@page"));
|
||||
|
||||
var optionsManager = Options.Create(new RazorPagesOptions());
|
||||
optionsManager.Value.RootDirectory = "/Pages";
|
||||
var provider = new RazorProjectPageRouteModelProvider(fileSystem, optionsManager, NullLoggerFactory.Instance);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.RouteModels,
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Pages/Index.cshtml", model.RelativePath);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_DoesNotAddPageDirectivesIfItAlreadyExists()
|
||||
{
|
||||
// Arrange
|
||||
var fileSystem = new VirtualRazorProjectFileSystem();
|
||||
fileSystem.Add(new TestRazorProjectItem("/Pages/Home.cshtml", "@page"));
|
||||
fileSystem.Add(new TestRazorProjectItem("/Pages/Test.cshtml", "@page"));
|
||||
|
||||
var optionsManager = Options.Create(new RazorPagesOptions());
|
||||
optionsManager.Value.RootDirectory = "/";
|
||||
var provider = new RazorProjectPageRouteModelProvider(fileSystem, optionsManager, NullLoggerFactory.Instance);
|
||||
var context = new PageRouteModelProviderContext();
|
||||
var pageModel = new PageRouteModel("/Pages/Test.cshtml", "/Pages/Test");
|
||||
context.RouteModels.Add(pageModel);
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.RouteModels,
|
||||
model => Assert.Same(pageModel, model),
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Pages/Home.cshtml", model.RelativePath);
|
||||
Assert.Equal("/Pages/Home", model.ViewEnginePath);
|
||||
Assert.Collection(model.Selectors,
|
||||
selector => Assert.Equal("Pages/Home", selector.AttributeRouteModel.Template));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,11 +2,9 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
@ -187,10 +185,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
|
||||
private static RazorViewEngineOptions GetViewEngineOptions()
|
||||
{
|
||||
var defaultSetup = new RazorViewEngineOptionsSetup(
|
||||
Mock.Of<IHostingEnvironment>(),
|
||||
NullLoggerFactory.Instance,
|
||||
Options.Options.Create(new MvcCompatibilityOptions()));
|
||||
var defaultSetup = new RazorViewEngineOptionsSetup(Mock.Of<IHostingEnvironment>());
|
||||
var options = new RazorViewEngineOptions();
|
||||
defaultSetup.Configure(options);
|
||||
|
||||
|
|
|
@ -1,195 +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 Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||
{
|
||||
public class PageActionDescriptorChangeProviderTest
|
||||
{
|
||||
private readonly IHostingEnvironment _hostingEnvironment = Mock.Of<IHostingEnvironment>(e => e.ContentRootPath == "BasePath");
|
||||
|
||||
[Fact]
|
||||
public void GetChangeToken_WatchesAllCshtmlFilesUnderFileSystemRoot()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new Mock<IFileProvider>();
|
||||
fileProvider.Setup(f => f.Watch(It.IsAny<string>()))
|
||||
.Returns(Mock.Of<IChangeToken>());
|
||||
var accessor = Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == fileProvider.Object);
|
||||
|
||||
var fileSystem = new FileProviderRazorProjectFileSystem(accessor, _hostingEnvironment);
|
||||
var templateEngine = new RazorTemplateEngine(
|
||||
RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine,
|
||||
fileSystem);
|
||||
var razorPageOptions = Options.Create(new RazorPagesOptions());
|
||||
var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions { AllowRecompilingViewsOnFileChange = true });
|
||||
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, razorPageOptions, razorViewEngineOptions);
|
||||
|
||||
// Act
|
||||
var changeToken = changeProvider.GetChangeToken();
|
||||
|
||||
// Assert
|
||||
fileProvider.Verify(f => f.Watch("/Pages/**/*.cshtml"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/pages-base-dir")]
|
||||
[InlineData("/pages-base-dir/")]
|
||||
public void GetChangeToken_WatchesAllCshtmlFilesUnderSpecifiedRootDirectory(string rootDirectory)
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new Mock<IFileProvider>();
|
||||
fileProvider.Setup(f => f.Watch(It.IsAny<string>()))
|
||||
.Returns(Mock.Of<IChangeToken>());
|
||||
var accessor = Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == fileProvider.Object);
|
||||
|
||||
var fileSystem = new FileProviderRazorProjectFileSystem(accessor, _hostingEnvironment);
|
||||
var templateEngine = new RazorTemplateEngine(
|
||||
RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine,
|
||||
fileSystem);
|
||||
var options = Options.Create(new RazorPagesOptions());
|
||||
options.Value.RootDirectory = rootDirectory;
|
||||
var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions { AllowRecompilingViewsOnFileChange = true });
|
||||
|
||||
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options, razorViewEngineOptions);
|
||||
|
||||
// Act
|
||||
var changeToken = changeProvider.GetChangeToken();
|
||||
|
||||
// Assert
|
||||
fileProvider.Verify(f => f.Watch("/pages-base-dir/**/*.cshtml"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChangeToken_WatchesFilesUnderAreaRoot()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new Mock<IFileProvider>();
|
||||
fileProvider.Setup(f => f.Watch(It.IsAny<string>()))
|
||||
.Returns(Mock.Of<IChangeToken>());
|
||||
var accessor = Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == fileProvider.Object);
|
||||
|
||||
var fileSystem = new FileProviderRazorProjectFileSystem(accessor, _hostingEnvironment);
|
||||
var templateEngine = new RazorTemplateEngine(
|
||||
RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine,
|
||||
fileSystem);
|
||||
var options = Options.Create(new RazorPagesOptions { AllowAreas = true });
|
||||
var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions { AllowRecompilingViewsOnFileChange = true });
|
||||
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options, razorViewEngineOptions);
|
||||
|
||||
// Act
|
||||
var changeToken = changeProvider.GetChangeToken();
|
||||
|
||||
// Assert
|
||||
fileProvider.Verify(f => f.Watch("/Areas/**/*.cshtml"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChangeToken_WatchesViewImportsOutsidePagesRoot_WhenPagesRootIsNested()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
var accessor = Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == fileProvider);
|
||||
|
||||
var fileSystem = new FileProviderRazorProjectFileSystem(accessor, _hostingEnvironment);
|
||||
var templateEngine = new RazorTemplateEngine(
|
||||
RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine,
|
||||
fileSystem);
|
||||
templateEngine.Options.ImportsFileName = "_ViewImports.cshtml";
|
||||
var options = Options.Create(new RazorPagesOptions());
|
||||
options.Value.RootDirectory = "/dir1/dir2";
|
||||
var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions { AllowRecompilingViewsOnFileChange = true });
|
||||
|
||||
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options, razorViewEngineOptions);
|
||||
|
||||
// Act & Assert
|
||||
var compositeChangeToken = Assert.IsType<CompositeChangeToken>(changeProvider.GetChangeToken());
|
||||
Assert.Collection(compositeChangeToken.ChangeTokens,
|
||||
changeToken => Assert.Same(fileProvider.GetChangeToken("/dir1/_ViewImports.cshtml"), changeToken),
|
||||
changeToken => Assert.Same(fileProvider.GetChangeToken("/_ViewImports.cshtml"), changeToken),
|
||||
changeToken => Assert.Same(fileProvider.GetChangeToken("/dir1/dir2/**/*.cshtml"), changeToken));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChangeToken_WatchesViewImportsOutsidePagesRoot_WhenAllowAreasIsSpecified()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
var accessor = Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == fileProvider);
|
||||
|
||||
var fileSystem = new FileProviderRazorProjectFileSystem(accessor, _hostingEnvironment);
|
||||
var templateEngine = new RazorTemplateEngine(
|
||||
RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine,
|
||||
fileSystem);
|
||||
templateEngine.Options.ImportsFileName = "_ViewImports.cshtml";
|
||||
var options = Options.Create(new RazorPagesOptions());
|
||||
options.Value.RootDirectory = "/dir1/dir2";
|
||||
options.Value.AllowAreas = true;
|
||||
|
||||
var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions { AllowRecompilingViewsOnFileChange = true });
|
||||
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options, razorViewEngineOptions);
|
||||
|
||||
// Act & Assert
|
||||
var compositeChangeToken = Assert.IsType<CompositeChangeToken>(changeProvider.GetChangeToken());
|
||||
Assert.Collection(compositeChangeToken.ChangeTokens,
|
||||
changeToken => Assert.Same(fileProvider.GetChangeToken("/dir1/_ViewImports.cshtml"), changeToken),
|
||||
changeToken => Assert.Same(fileProvider.GetChangeToken("/_ViewImports.cshtml"), changeToken),
|
||||
changeToken => Assert.Same(fileProvider.GetChangeToken("/dir1/dir2/**/*.cshtml"), changeToken),
|
||||
changeToken => Assert.Same(fileProvider.GetChangeToken("/Areas/**/*.cshtml"), changeToken));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChangeToken_WatchesViewImportsOutsidePagesRoot_WhenAreaFeatureIsDisabled()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new TestFileProvider();
|
||||
var accessor = Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == fileProvider);
|
||||
|
||||
var fileSystem = new FileProviderRazorProjectFileSystem(accessor, _hostingEnvironment);
|
||||
var templateEngine = new RazorTemplateEngine(
|
||||
RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine,
|
||||
fileSystem);
|
||||
templateEngine.Options.ImportsFileName = "_ViewImports.cshtml";
|
||||
var options = Options.Create(new RazorPagesOptions { AllowAreas = false });
|
||||
var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions { AllowRecompilingViewsOnFileChange = true });
|
||||
|
||||
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options, razorViewEngineOptions);
|
||||
|
||||
// Act & Assert
|
||||
var compositeChangeToken = Assert.IsType<CompositeChangeToken>(changeProvider.GetChangeToken());
|
||||
Assert.Collection(compositeChangeToken.ChangeTokens,
|
||||
changeToken => Assert.Same(fileProvider.GetChangeToken("/_ViewImports.cshtml"), changeToken),
|
||||
changeToken => Assert.Same(fileProvider.GetChangeToken("/Pages/**/*.cshtml"), changeToken));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChangeToken_DoesNotWatch_WhenOptionIsReset()
|
||||
{
|
||||
// Arrange
|
||||
var fileProvider = new Mock<IFileProvider>(MockBehavior.Strict);
|
||||
var accessor = Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == fileProvider.Object);
|
||||
|
||||
var fileSystem = new FileProviderRazorProjectFileSystem(accessor, _hostingEnvironment);
|
||||
var templateEngine = new RazorTemplateEngine(
|
||||
RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine,
|
||||
fileSystem);
|
||||
templateEngine.Options.ImportsFileName = "_ViewImports.cshtml";
|
||||
var options = Options.Create(new RazorPagesOptions());
|
||||
var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions());
|
||||
|
||||
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options, razorViewEngineOptions);
|
||||
|
||||
// Act & Assert
|
||||
var compositeChangeToken = Assert.IsType<NullChangeToken>(changeProvider.GetChangeToken());
|
||||
fileProvider.Verify(f => f.Watch(It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,7 +16,6 @@ using Microsoft.AspNetCore.Mvc.Razor;
|
|||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
@ -28,15 +27,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
{
|
||||
public class PageInvokerProviderTest
|
||||
{
|
||||
private readonly IHostingEnvironment _hostingEnvironment = Mock.Of<IHostingEnvironment>(e => e.ContentRootPath == "BasePath");
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_WithEmptyModel_PopulatesCacheEntry()
|
||||
{
|
||||
// Arrange
|
||||
var descriptor = new PageActionDescriptor
|
||||
{
|
||||
RelativePath = "Path1",
|
||||
RelativePath = "/Path1",
|
||||
FilterDescriptors = new FilterDescriptor[0],
|
||||
};
|
||||
|
||||
|
@ -89,7 +86,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
// Arrange
|
||||
var descriptor = new PageActionDescriptor
|
||||
{
|
||||
RelativePath = "Path1",
|
||||
RelativePath = "/Path1",
|
||||
FilterDescriptors = new FilterDescriptor[0]
|
||||
};
|
||||
|
||||
|
@ -190,15 +187,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
.Setup(f => f.CreateFactory("/_ViewStart.cshtml"))
|
||||
.Returns(new RazorPageFactoryResult(new CompiledViewDescriptor(), factory2));
|
||||
|
||||
var fileSystem = new VirtualRazorProjectFileSystem();
|
||||
fileSystem.Add(new TestRazorProjectItem("/Home/Path1/_ViewStart.cshtml", "content1"));
|
||||
fileSystem.Add(new TestRazorProjectItem("/_ViewStart.cshtml", "content2"));
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile("/Home/Path1/_ViewStart.cshtml", "content1");
|
||||
fileProvider.AddFile("/_ViewStart.cshtml", "content2");
|
||||
|
||||
var invokerProvider = CreateInvokerProvider(
|
||||
loader.Object,
|
||||
CreateActionDescriptorCollection(descriptor),
|
||||
razorPageFactoryProvider: razorPageFactoryProvider.Object,
|
||||
fileSystem: fileSystem);
|
||||
razorPageFactoryProvider: razorPageFactoryProvider.Object);
|
||||
|
||||
var context = new ActionInvokerProviderContext(new ActionContext()
|
||||
{
|
||||
|
@ -223,7 +219,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
// Arrange
|
||||
var descriptor = new PageActionDescriptor
|
||||
{
|
||||
RelativePath = "Path1",
|
||||
RelativePath = "/Path1",
|
||||
FilterDescriptors = new FilterDescriptor[0],
|
||||
};
|
||||
|
||||
|
@ -273,7 +269,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
// Arrange
|
||||
var descriptor = new PageActionDescriptor
|
||||
{
|
||||
RelativePath = "Path1",
|
||||
RelativePath = "/Path1",
|
||||
FilterDescriptors = new FilterDescriptor[0],
|
||||
};
|
||||
|
||||
|
@ -342,15 +338,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
.Setup(l => l.Load(It.IsAny<PageActionDescriptor>()))
|
||||
.Returns(compiledPageDescriptor);
|
||||
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile("/_ViewStart.cshtml", "page content");
|
||||
fileProvider.AddFile("/Pages/_ViewStart.cshtml", "page content");
|
||||
fileProvider.AddFile("/Pages/Level1/_ViewStart.cshtml", "page content");
|
||||
fileProvider.AddFile("/Pages/Level1/Level2/_ViewStart.cshtml", "page content");
|
||||
fileProvider.AddFile("/Pages/Level1/Level3/_ViewStart.cshtml", "page content");
|
||||
|
||||
var fileSystem = new TestRazorProjectFileSystem(fileProvider, _hostingEnvironment);
|
||||
|
||||
var mock = new Mock<IRazorPageFactoryProvider>(MockBehavior.Strict);
|
||||
mock
|
||||
.Setup(p => p.CreateFactory("/Pages/Level1/Level2/_ViewStart.cshtml"))
|
||||
|
@ -374,8 +361,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
var invokerProvider = CreateInvokerProvider(
|
||||
loader.Object,
|
||||
CreateActionDescriptorCollection(descriptor),
|
||||
razorPageFactoryProvider: razorPageFactoryProvider,
|
||||
fileSystem: fileSystem);
|
||||
razorPageFactoryProvider: razorPageFactoryProvider);
|
||||
|
||||
// Act
|
||||
var factories = invokerProvider.GetViewStartFactories(compiledPageDescriptor);
|
||||
|
@ -415,15 +401,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
// No files
|
||||
var fileProvider = new TestFileProvider();
|
||||
var fileSystem = new TestRazorProjectFileSystem(fileProvider, _hostingEnvironment);
|
||||
|
||||
var invokerProvider = CreateInvokerProvider(
|
||||
loader.Object,
|
||||
CreateActionDescriptorCollection(descriptor),
|
||||
pageProvider: null,
|
||||
modelProvider: null,
|
||||
razorPageFactoryProvider: pageFactory.Object,
|
||||
fileSystem: fileSystem);
|
||||
razorPageFactoryProvider: pageFactory.Object);
|
||||
|
||||
var compiledDescriptor = CreateCompiledPageActionDescriptor(descriptor);
|
||||
|
||||
|
@ -468,19 +452,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
IActionDescriptorCollectionProvider actionDescriptorProvider,
|
||||
IPageFactoryProvider pageProvider = null,
|
||||
IPageModelFactoryProvider modelProvider = null,
|
||||
IRazorPageFactoryProvider razorPageFactoryProvider = null,
|
||||
RazorProjectFileSystem fileSystem = null)
|
||||
IRazorPageFactoryProvider razorPageFactoryProvider = null)
|
||||
{
|
||||
var tempDataFactory = new Mock<ITempDataDictionaryFactory>();
|
||||
tempDataFactory
|
||||
.Setup(t => t.GetTempData(It.IsAny<HttpContext>()))
|
||||
.Returns((HttpContext context) => new TempDataDictionary(context, Mock.Of<ITempDataProvider>()));
|
||||
|
||||
if (fileSystem == null)
|
||||
{
|
||||
fileSystem = Mock.Of<RazorProjectFileSystem>();
|
||||
}
|
||||
|
||||
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
|
||||
var mvcOptions = new MvcOptions
|
||||
|
@ -509,7 +487,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
Options.Create(new MvcOptions()),
|
||||
Options.Create(new HtmlHelperOptions()),
|
||||
Mock.Of<IPageHandlerMethodSelector>(),
|
||||
fileSystem,
|
||||
new DiagnosticListener("Microsoft.AspNetCore"),
|
||||
NullLoggerFactory.Instance,
|
||||
new ActionResultTypeMapper());
|
||||
|
|
|
@ -1,156 +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.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||
{
|
||||
public class PageDirectiveFeatureTest
|
||||
{
|
||||
[Fact]
|
||||
public void TryGetPageDirective_FindsTemplate()
|
||||
{
|
||||
// Arrange
|
||||
var projectItem = new TestRazorProjectItem("Test.cshtml", @"@page ""Some/Path/{value}""
|
||||
The rest of the thing");
|
||||
var sink = new TestSink();
|
||||
var logger = new TestLogger("logger", sink, enabled: true);
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(PageDirectiveFeature.TryGetPageDirective(logger, projectItem, out var template));
|
||||
Assert.Equal("Some/Path/{value}", template);
|
||||
Assert.Empty(sink.Writes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetPageDirective_NoNewLine()
|
||||
{
|
||||
// Arrange
|
||||
var projectItem = new TestRazorProjectItem("Test.cshtml", @"@page ""Some/Path/{value}""");
|
||||
var sink = new TestSink();
|
||||
var logger = new TestLogger("logger", sink, enabled: true);
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(PageDirectiveFeature.TryGetPageDirective(logger, projectItem, out var template));
|
||||
Assert.Equal("Some/Path/{value}", template);
|
||||
Assert.Empty(sink.Writes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetPageDirective_JunkBeforeDirective()
|
||||
{
|
||||
// Arrange
|
||||
var projectItem = new TestRazorProjectItem("Test.cshtml", @"Not a directive @page ""Some/Path/{value}""");
|
||||
var sink = new TestSink();
|
||||
var logger = new TestLogger("logger", sink, enabled: true);
|
||||
|
||||
// Act & Assert
|
||||
Assert.False(PageDirectiveFeature.TryGetPageDirective(logger, projectItem, out var template));
|
||||
Assert.Null(template);
|
||||
Assert.Empty(sink.Writes);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"""Some/Path/{value}")]
|
||||
[InlineData(@"Some/Path/{value}""")]
|
||||
public void TryGetPageDirective_WithoutBothQuotes_LogsWarning(string inTemplate)
|
||||
{
|
||||
// Arrange
|
||||
var expected = "The page directive at 'Test.cshtml' is malformed. Please fix the following issues: The 'page' directive expects a string surrounded by double quotes.";
|
||||
var sink = new TestSink();
|
||||
var logger = new TestLogger("logger", sink, enabled: true);
|
||||
var projectItem = new TestRazorProjectItem("Test.cshtml", $@"@page {inTemplate}");
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(PageDirectiveFeature.TryGetPageDirective(logger, projectItem, out var template));
|
||||
Assert.Null(template);
|
||||
Assert.Collection(sink.Writes,
|
||||
log =>
|
||||
{
|
||||
Assert.Equal(LogLevel.Warning, log.LogLevel);
|
||||
Assert.Equal(expected, log.State.ToString());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetPageDirective_NoQuotesAroundPath_LogsWarning()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "The page directive at 'Test.cshtml' is malformed. Please fix the following issues: The 'page' directive expects a string surrounded by double quotes.";
|
||||
var sink = new TestSink();
|
||||
var logger = new TestLogger("logger", sink, enabled: true);
|
||||
var projectItem = new TestRazorProjectItem("Test.cshtml", @"@page Some/Path/{value}");
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(PageDirectiveFeature.TryGetPageDirective(logger, projectItem, out var template));
|
||||
Assert.Null(template);
|
||||
var logs = sink.Writes.Select(w => w.State.ToString().Trim()).ToList();
|
||||
Assert.Collection(sink.Writes,
|
||||
log =>
|
||||
{
|
||||
Assert.Equal(LogLevel.Warning, log.LogLevel);
|
||||
Assert.Equal(expected, log.State.ToString());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetPageDirective_NewLineBeforeDirective()
|
||||
{
|
||||
// Arrange
|
||||
var projectItem = new TestRazorProjectItem("Test.cshtml", "\n @page \"Some/Path/{value}\"");
|
||||
var sink = new TestSink();
|
||||
var logger = new TestLogger("logger", sink, enabled: true);
|
||||
|
||||
// Act
|
||||
Assert.True(PageDirectiveFeature.TryGetPageDirective(logger, projectItem, out var template));
|
||||
Assert.Equal("Some/Path/{value}", template);
|
||||
Assert.Empty(sink.Writes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetPageDirective_Directive_WithoutPathOrContent()
|
||||
{
|
||||
// Arrange
|
||||
var projectItem = new TestRazorProjectItem("Test.cshtml", @"@page");
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(PageDirectiveFeature.TryGetPageDirective(NullLogger.Instance, projectItem, out var template));
|
||||
Assert.Null(template);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetPageDirective_DirectiveWithContent_WithoutPath()
|
||||
{
|
||||
// Arrange
|
||||
var projectItem = new TestRazorProjectItem("Test.cshtml", @"@page
|
||||
Non-path things");
|
||||
var sink = new TestSink();
|
||||
var logger = new TestLogger("logger", sink, enabled: true);
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(PageDirectiveFeature.TryGetPageDirective(logger, projectItem, out var template));
|
||||
Assert.Null(template);
|
||||
Assert.Empty(sink.Writes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetPageDirective_NoDirective()
|
||||
{
|
||||
// Arrange
|
||||
var projectItem = new TestRazorProjectItem("Test.cshtml", @"This is junk
|
||||
Nobody will use it");
|
||||
var sink = new TestSink();
|
||||
var logger = new TestLogger("logger", sink, enabled: true);
|
||||
|
||||
// Act & Assert
|
||||
Assert.False(PageDirectiveFeature.TryGetPageDirective(logger, projectItem, out var template));
|
||||
Assert.Null(template);
|
||||
Assert.Empty(sink.Writes);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +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 Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Moq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||
{
|
||||
internal class TestRazorProjectFileSystem : FileProviderRazorProjectFileSystem
|
||||
{
|
||||
public TestRazorProjectFileSystem(IFileProvider fileProvider, IHostingEnvironment hostingEnvironment)
|
||||
:base(GetAccessor(fileProvider), hostingEnvironment)
|
||||
{
|
||||
}
|
||||
|
||||
private static IRazorViewEngineFileProviderAccessor GetAccessor(IFileProvider fileProvider)
|
||||
{
|
||||
var fileProviderAccessor = new Mock<IRazorViewEngineFileProviderAccessor>();
|
||||
fileProviderAccessor.SetupGet(f => f.FileProvider)
|
||||
.Returns(fileProvider);
|
||||
|
||||
return fileProviderAccessor.Object;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -53,7 +53,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
|||
Assert.Null(mvcOptions.MaxValidationDepth);
|
||||
Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
||||
Assert.True(apiBehaviorOptions.SuppressMapClientErrors);
|
||||
Assert.True(razorViewEngineOptions.AllowRecompilingViewsOnFileChange);
|
||||
Assert.False(razorPagesOptions.AllowDefaultHandlingForOptionsRequests);
|
||||
Assert.False(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat);
|
||||
Assert.False(mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent);
|
||||
|
@ -92,7 +91,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
|||
Assert.Null(mvcOptions.MaxValidationDepth);
|
||||
Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
||||
Assert.True(apiBehaviorOptions.SuppressMapClientErrors);
|
||||
Assert.True(razorViewEngineOptions.AllowRecompilingViewsOnFileChange);
|
||||
Assert.False(razorPagesOptions.AllowDefaultHandlingForOptionsRequests);
|
||||
Assert.False(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat);
|
||||
Assert.False(mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent);
|
||||
|
@ -131,7 +129,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
|||
Assert.Equal(32, mvcOptions.MaxValidationDepth);
|
||||
Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
||||
Assert.False(apiBehaviorOptions.SuppressMapClientErrors);
|
||||
Assert.False(razorViewEngineOptions.AllowRecompilingViewsOnFileChange);
|
||||
Assert.True(razorPagesOptions.AllowDefaultHandlingForOptionsRequests);
|
||||
Assert.True(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat);
|
||||
Assert.True(mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent);
|
||||
|
@ -170,7 +167,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
|||
Assert.Equal(32, mvcOptions.MaxValidationDepth);
|
||||
Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
||||
Assert.False(apiBehaviorOptions.SuppressMapClientErrors);
|
||||
Assert.False(razorViewEngineOptions.AllowRecompilingViewsOnFileChange);
|
||||
Assert.True(razorPagesOptions.AllowDefaultHandlingForOptionsRequests);
|
||||
Assert.True(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat);
|
||||
Assert.True(mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent);
|
||||
|
|
|
@ -210,9 +210,6 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
Assert.Collection(manager.FeatureProviders,
|
||||
feature => Assert.IsType<ControllerFeatureProvider>(feature),
|
||||
feature => Assert.IsType<ViewComponentFeatureProvider>(feature),
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
feature => Assert.IsType<MetadataReferenceFeatureProvider>(feature),
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
feature => Assert.IsType<TagHelperFeatureProvider>(feature),
|
||||
feature => Assert.IsType<RazorCompiledItemFeatureProvider>(feature),
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
@ -462,7 +459,6 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
new[]
|
||||
{
|
||||
typeof(CompiledPageRouteModelProvider),
|
||||
typeof(RazorProjectPageRouteModelProvider),
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Runtime" Version="$(MicrosoftAspNetCoreRazorRuntimePackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="$(MicrosoftAspNetCoreRazorLanguagePackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.WebEncoders" Version="$(MicrosoftExtensionsWebEncodersPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -1,44 +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 System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
public class TestRazorProjectItem : RazorProjectItem
|
||||
{
|
||||
public TestRazorProjectItem(
|
||||
string filePath,
|
||||
string content = "Default content",
|
||||
string physicalPath = null,
|
||||
string relativePhysicalPath = null,
|
||||
string basePath = "/")
|
||||
{
|
||||
FilePath = filePath;
|
||||
PhysicalPath = physicalPath;
|
||||
RelativePhysicalPath = relativePhysicalPath;
|
||||
BasePath = basePath;
|
||||
Content = content;
|
||||
}
|
||||
|
||||
public override string BasePath { get; }
|
||||
|
||||
public override string FilePath { get; }
|
||||
|
||||
public override string PhysicalPath { get; }
|
||||
|
||||
public override string RelativePhysicalPath { get; }
|
||||
|
||||
public override bool Exists { get; } = true;
|
||||
|
||||
public string Content { get; set; }
|
||||
|
||||
public override Stream Read()
|
||||
{
|
||||
var stream = new MemoryStream(Encoding.UTF8.GetBytes(Content));
|
||||
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,233 +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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
public class VirtualRazorProjectFileSystem : RazorProjectFileSystem
|
||||
{
|
||||
private readonly DirectoryNode _root = new DirectoryNode("/");
|
||||
|
||||
public override IEnumerable<RazorProjectItem> EnumerateItems(string basePath)
|
||||
{
|
||||
basePath = NormalizeAndEnsureValidPath(basePath);
|
||||
var directory = _root.GetDirectory(basePath);
|
||||
return directory?.EnumerateItems() ?? Enumerable.Empty<RazorProjectItem>();
|
||||
}
|
||||
|
||||
public override RazorProjectItem GetItem(string path)
|
||||
{
|
||||
path = NormalizeAndEnsureValidPath(path);
|
||||
return _root.GetItem(path) ?? new NotFoundProjectItem(string.Empty, path);
|
||||
}
|
||||
|
||||
public void Add(RazorProjectItem projectItem)
|
||||
{
|
||||
if (projectItem == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectItem));
|
||||
}
|
||||
|
||||
var filePath = NormalizeAndEnsureValidPath(projectItem.FilePath);
|
||||
_root.AddFile(new FileNode(filePath, projectItem));
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
[DebuggerDisplay("{Path}")]
|
||||
internal class DirectoryNode
|
||||
{
|
||||
public DirectoryNode(string path)
|
||||
{
|
||||
Path = path;
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public List<DirectoryNode> Directories { get; } = new List<DirectoryNode>();
|
||||
|
||||
public List<FileNode> Files { get; } = new List<FileNode>();
|
||||
|
||||
public void AddFile(FileNode fileNode)
|
||||
{
|
||||
var filePath = fileNode.Path;
|
||||
if (!filePath.StartsWith(Path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException($"File {fileNode.Path} does not belong to {Path}.");
|
||||
}
|
||||
|
||||
// Look for the first / that appears in the path after the current directory path.
|
||||
var directoryPath = GetDirectoryPath(filePath);
|
||||
var directory = GetOrAddDirectory(this, directoryPath, createIfNotExists: true);
|
||||
Debug.Assert(directory != null);
|
||||
directory.Files.Add(fileNode);
|
||||
}
|
||||
|
||||
public DirectoryNode GetDirectory(string path)
|
||||
{
|
||||
if (!path.StartsWith(Path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException($"File {path} does not belong to {Path}.");
|
||||
}
|
||||
|
||||
return GetOrAddDirectory(this, path);
|
||||
}
|
||||
|
||||
public IEnumerable<RazorProjectItem> EnumerateItems()
|
||||
{
|
||||
foreach (var file in Files)
|
||||
{
|
||||
yield return file.ProjectItem;
|
||||
}
|
||||
|
||||
foreach (var directory in Directories)
|
||||
{
|
||||
foreach (var file in directory.EnumerateItems())
|
||||
{
|
||||
yield return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RazorProjectItem GetItem(string path)
|
||||
{
|
||||
if (!path.StartsWith(Path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException($"File {path} does not belong to {Path}.");
|
||||
}
|
||||
|
||||
var directoryPath = GetDirectoryPath(path);
|
||||
var directory = GetOrAddDirectory(this, directoryPath);
|
||||
if (directory == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var file in directory.Files)
|
||||
{
|
||||
var filePath = file.Path;
|
||||
var directoryLength = directory.Path.Length;
|
||||
|
||||
// path, filePath -> /Views/Home/Index.cshtml
|
||||
// directory.Path -> /Views/Home/
|
||||
// We only need to match the file name portion since we've already matched the directory segment.
|
||||
if (string.Compare(path, directoryLength, filePath, directoryLength, path.Length - directoryLength, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return file.ProjectItem;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetDirectoryPath(string path)
|
||||
{
|
||||
// /dir1/dir2/file.cshtml -> /dir1/dir2/
|
||||
var fileNameIndex = path.LastIndexOf('/');
|
||||
if (fileNameIndex == -1)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
return path.Substring(0, fileNameIndex + 1);
|
||||
}
|
||||
|
||||
private static DirectoryNode GetOrAddDirectory(
|
||||
DirectoryNode directory,
|
||||
string path,
|
||||
bool createIfNotExists = false)
|
||||
{
|
||||
Debug.Assert(!string.IsNullOrEmpty(path));
|
||||
if (path[path.Length - 1] != '/')
|
||||
{
|
||||
path += '/';
|
||||
}
|
||||
|
||||
int index;
|
||||
while ((index = path.IndexOf('/', directory.Path.Length)) != -1 && index != path.Length)
|
||||
{
|
||||
var subDirectory = FindSubDirectory(directory, path);
|
||||
|
||||
if (subDirectory == null)
|
||||
{
|
||||
if (createIfNotExists)
|
||||
{
|
||||
var directoryPath = path.Substring(0, index + 1); // + 1 to include trailing slash
|
||||
subDirectory = new DirectoryNode(directoryPath);
|
||||
directory.Directories.Add(subDirectory);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
directory = subDirectory;
|
||||
}
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
private static DirectoryNode FindSubDirectory(DirectoryNode parentDirectory, string path)
|
||||
{
|
||||
for (var i = 0; i < parentDirectory.Directories.Count; i++)
|
||||
{
|
||||
// ParentDirectory.Path -> /Views/Home/
|
||||
// CurrentDirectory.Path -> /Views/Home/SubDir/
|
||||
// Path -> /Views/Home/SubDir/MorePath/File.cshtml
|
||||
// Each invocation of FindSubDirectory returns the immediate subdirectory along the path to the file.
|
||||
|
||||
var currentDirectory = parentDirectory.Directories[i];
|
||||
var directoryPath = currentDirectory.Path;
|
||||
var startIndex = parentDirectory.Path.Length;
|
||||
var directoryNameLength = directoryPath.Length - startIndex;
|
||||
|
||||
if (string.Compare(path, startIndex, directoryPath, startIndex, directoryPath.Length - startIndex, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return currentDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
[DebuggerDisplay("{Path}")]
|
||||
internal struct FileNode
|
||||
{
|
||||
public FileNode(string path, RazorProjectItem projectItem)
|
||||
{
|
||||
Path = path;
|
||||
ProjectItem = projectItem;
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public RazorProjectItem ProjectItem { get; }
|
||||
}
|
||||
|
||||
private class NotFoundProjectItem : RazorProjectItem
|
||||
{
|
||||
public NotFoundProjectItem(string basePath, string path)
|
||||
{
|
||||
BasePath = basePath;
|
||||
FilePath = path;
|
||||
}
|
||||
|
||||
public override string BasePath { get; }
|
||||
|
||||
public override string FilePath { get; }
|
||||
|
||||
public override bool Exists => false;
|
||||
|
||||
public override string PhysicalPath => throw new NotSupportedException();
|
||||
|
||||
public override Stream Read() => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +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.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace ControllersFromServicesWebSite
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
public class AssemblyMetadataReferenceFeatureProvider : IApplicationFeatureProvider<MetadataReferenceFeature>
|
||||
{
|
||||
public void PopulateFeature(IEnumerable<ApplicationPart> parts, MetadataReferenceFeature feature)
|
||||
{
|
||||
var currentAssembly = GetType().GetTypeInfo().Assembly;
|
||||
feature.MetadataReferences.Add(MetadataReference.CreateFromFile(currentAssembly.Location));
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
|
@ -32,7 +32,13 @@ namespace ControllersFromServicesWebSite
|
|||
typeof(ComponentFromServicesViewComponent),
|
||||
typeof(InServicesTagHelper)));
|
||||
|
||||
manager.FeatureProviders.Add(new AssemblyMetadataReferenceFeatureProvider());
|
||||
var relatedAssenbly = RelatedAssemblyAttribute
|
||||
.GetRelatedAssemblies(GetType().Assembly, throwOnError: true)
|
||||
.SingleOrDefault();
|
||||
foreach (var part in CompiledRazorAssemblyApplicationPartFactory.GetDefaultApplicationParts(relatedAssenbly))
|
||||
{
|
||||
manager.ApplicationParts.Add(part);
|
||||
}
|
||||
})
|
||||
.AddControllersAsServices()
|
||||
.AddViewComponentsAsServices()
|
||||
|
|
|
@ -1,18 +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;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace RazorPagesWebSite
|
||||
{
|
||||
public class NonWatchingPhysicalFileProvider : PhysicalFileProvider, IFileProvider
|
||||
{
|
||||
public NonWatchingPhysicalFileProvider(string root) : base(root)
|
||||
{
|
||||
}
|
||||
|
||||
IChangeToken IFileProvider.Watch(string filter) => throw new ArgumentException("This method should not be called.");
|
||||
}
|
||||
}
|
|
@ -35,14 +35,6 @@ namespace RazorPagesWebSite
|
|||
options.Conventions.Add(new CustomModelTypeConvention());
|
||||
})
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Latest);
|
||||
|
||||
// Ensure we don't have code paths that call IFileProvider.Watch in the default code path.
|
||||
// Comment this code block if you happen to run this site in Development.
|
||||
builder.AddRazorOptions(options =>
|
||||
{
|
||||
options.FileProviders.Clear();
|
||||
options.FileProviders.Add(new NonWatchingPhysicalFileProvider(_hostingEnvironment.ContentRootPath));
|
||||
});
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace RazorWebSite.Controllers
|
|||
|
||||
public IActionResult ViewWithFullPath()
|
||||
{
|
||||
return PartialView("/Views/ViewEngine/ViewWithFullPath.rzr");
|
||||
return PartialView("/Views/ViewEngine/ViewWithFullPath.cshtml");
|
||||
}
|
||||
|
||||
public IActionResult PartialViewWithNamePassedIn()
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace RazorWebSite.Controllers
|
|||
|
||||
public IActionResult ViewWithFullPath()
|
||||
{
|
||||
return View("/Views/ViewEngine/ViewWithFullPath.rzr");
|
||||
return View("/Views/ViewEngine/ViewWithFullPath.cshtml");
|
||||
}
|
||||
|
||||
public IActionResult ViewWithRelativePath()
|
||||
|
|
|
@ -27,10 +27,6 @@ namespace RazorWebSite
|
|||
.AddMvc()
|
||||
.AddRazorOptions(options =>
|
||||
{
|
||||
options.FileProviders.Add(new EmbeddedFileProvider(
|
||||
typeof(Startup).GetTypeInfo().Assembly,
|
||||
$"{nameof(RazorWebSite)}.EmbeddedResources"));
|
||||
options.FileProviders.Add(updateableFileProvider);
|
||||
options.ViewLocationExpanders.Add(new NonMainPageViewLocationExpander());
|
||||
options.ViewLocationExpanders.Add(new BackSlashExpander());
|
||||
})
|
||||
|
|
Загрузка…
Ссылка в новой задаче