Remove runtime compilation from Mvc.Razor (#8755)

* Remove runtime compilation from Mvc.Razor
* Remove RazorViewEngineOptions.FileProviders
This commit is contained in:
Pranav K 2018-11-21 17:14:12 -08:00 коммит произвёл GitHub
Родитель bfdecc8f5e
Коммит f80490f99d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
80 изменённых файлов: 267 добавлений и 7064 удалений

Просмотреть файл

@ -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());
})