Add support for recompilation
This commit is contained in:
parent
73bd09dc1c
commit
d58d0f917f
45
Mvc.sln
45
Mvc.sln
|
|
@ -155,6 +155,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{FDC66952-A3EA-4074-899E-C29816BF7C1F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RazorBuildWebSite", "test\WebSites\RazorBuildWebSite\RazorBuildWebSite.csproj", "{BF8A3392-C3D2-4813-855A-E906564600E1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RazorBuildWebSite.PrecompiledViews", "test\WebSites\RazorBuildWebSite.PrecompiledViews\RazorBuildWebSite.PrecompiledViews.csproj", "{856D7E25-E033-477D-9ABD-0B50CF428C80}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RazorBuildWebSite.Views", "test\WebSites\RazorBuildWebSite.Views\RazorBuildWebSite.Views.csproj", "{8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -791,6 +797,42 @@ Global
|
|||
{7500B228-1769-4CFB-A571-3DFAC6678A06}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{7500B228-1769-4CFB-A571-3DFAC6678A06}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{7500B228-1769-4CFB-A571-3DFAC6678A06}.Release|x86.Build.0 = Release|Any CPU
|
||||
{BF8A3392-C3D2-4813-855A-E906564600E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BF8A3392-C3D2-4813-855A-E906564600E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BF8A3392-C3D2-4813-855A-E906564600E1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{BF8A3392-C3D2-4813-855A-E906564600E1}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{BF8A3392-C3D2-4813-855A-E906564600E1}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{BF8A3392-C3D2-4813-855A-E906564600E1}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{BF8A3392-C3D2-4813-855A-E906564600E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BF8A3392-C3D2-4813-855A-E906564600E1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BF8A3392-C3D2-4813-855A-E906564600E1}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{BF8A3392-C3D2-4813-855A-E906564600E1}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{BF8A3392-C3D2-4813-855A-E906564600E1}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{BF8A3392-C3D2-4813-855A-E906564600E1}.Release|x86.Build.0 = Release|Any CPU
|
||||
{856D7E25-E033-477D-9ABD-0B50CF428C80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{856D7E25-E033-477D-9ABD-0B50CF428C80}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{856D7E25-E033-477D-9ABD-0B50CF428C80}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{856D7E25-E033-477D-9ABD-0B50CF428C80}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{856D7E25-E033-477D-9ABD-0B50CF428C80}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{856D7E25-E033-477D-9ABD-0B50CF428C80}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{856D7E25-E033-477D-9ABD-0B50CF428C80}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{856D7E25-E033-477D-9ABD-0B50CF428C80}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{856D7E25-E033-477D-9ABD-0B50CF428C80}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{856D7E25-E033-477D-9ABD-0B50CF428C80}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{856D7E25-E033-477D-9ABD-0B50CF428C80}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{856D7E25-E033-477D-9ABD-0B50CF428C80}.Release|x86.Build.0 = Release|Any CPU
|
||||
{8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -853,6 +895,9 @@ Global
|
|||
{4BA6EC9A-B6D9-41F2-BFDA-D82B22D80352} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
{F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE} = {FDC66952-A3EA-4074-899E-C29816BF7C1F}
|
||||
{7500B228-1769-4CFB-A571-3DFAC6678A06} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
|
||||
{BF8A3392-C3D2-4813-855A-E906564600E1} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
{856D7E25-E033-477D-9ABD-0B50CF428C80} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
{8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,60 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNetCore.Razor.Hosting;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a compiled Razor View or Page.
|
||||
/// </summary>
|
||||
public class CompiledViewDescriptor
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="CompiledViewDescriptor"/>.
|
||||
/// </summary>
|
||||
public CompiledViewDescriptor()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="CompiledViewDescriptor"/>. At least one of <paramref name="attribute"/> or
|
||||
/// <paramref name="item"/> must be non-<c>null</c>.
|
||||
/// </summary>
|
||||
/// <param name="item">The <see cref="RazorCompiledItem"/>.</param>
|
||||
/// <param name="attribute">The <see cref="RazorViewAttribute"/>.</param>
|
||||
public CompiledViewDescriptor(RazorCompiledItem item, RazorViewAttribute attribute)
|
||||
{
|
||||
if (item == null && attribute == null)
|
||||
{
|
||||
// We require at least one of these to be specified.
|
||||
throw new ArgumentException(Resources.FormatCompiledViewDescriptor_NoData(nameof(item), nameof(attribute)));
|
||||
}
|
||||
|
||||
Item = item;
|
||||
|
||||
//
|
||||
// For now we expect that MVC views and pages will still have either:
|
||||
// [RazorView(...)] or
|
||||
// [RazorPage(...)].
|
||||
//
|
||||
// In theory we could look at the 'Item.Kind' to determine what kind of thing we're dealing
|
||||
// with, but for compat reasons we're basing it on ViewAttribute since that's what 2.0 had.
|
||||
ViewAttribute = attribute;
|
||||
|
||||
// We don't have access to the file provider here so we can't check if the files
|
||||
// even exist or what their checksums are. For now leave this empty, it will be updated
|
||||
// later.
|
||||
ExpirationTokens = Array.Empty<IChangeToken>();
|
||||
RelativePath = ViewPath.NormalizePath(item?.Identifier ?? attribute.Path);
|
||||
IsPrecompiled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The normalized application relative path of the view.
|
||||
/// </summary>
|
||||
|
|
@ -27,5 +74,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
/// 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>
|
||||
public RazorCompiledItem Item { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the compiled item.
|
||||
/// </summary>
|
||||
public Type Type => Item?.Type ?? ViewAttribute?.ViewType;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.AspNetCore.Razor.Hosting;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
{
|
||||
|
|
@ -19,28 +18,81 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
{
|
||||
public static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews";
|
||||
|
||||
public static readonly IReadOnlyList<string> ViewAssemblySuffixes = new string[]
|
||||
{
|
||||
PrecompiledViewsAssemblySuffix,
|
||||
".Views",
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewsFeature feature)
|
||||
{
|
||||
foreach (var assemblyPart in parts.OfType<AssemblyPart>())
|
||||
{
|
||||
var viewAttributes = GetViewAttributes(assemblyPart);
|
||||
foreach (var attribute in viewAttributes)
|
||||
{
|
||||
var relativePath = ViewPath.NormalizePath(attribute.Path);
|
||||
var viewDescriptor = new CompiledViewDescriptor
|
||||
{
|
||||
ExpirationTokens = Array.Empty<IChangeToken>(),
|
||||
RelativePath = relativePath,
|
||||
ViewAttribute = attribute,
|
||||
IsPrecompiled = true,
|
||||
};
|
||||
var attributes = GetViewAttributes(assemblyPart);
|
||||
var items = LoadItems(assemblyPart);
|
||||
|
||||
feature.ViewDescriptors.Add(viewDescriptor);
|
||||
var merged = Merge(items, attributes);
|
||||
foreach (var entry in merged)
|
||||
{
|
||||
feature.ViewDescriptors.Add(new CompiledViewDescriptor(entry.item, entry.attribute));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ICollection<(RazorCompiledItem item, RazorViewAttribute attribute)> Merge(
|
||||
IReadOnlyList<RazorCompiledItem> items,
|
||||
IEnumerable<RazorViewAttribute> attributes)
|
||||
{
|
||||
// This code is a intentionally defensive. We assume that it's possible to have duplicates
|
||||
// of attributes, and also items that have a single kind of metadata, but not the other.
|
||||
var dictionary = new Dictionary<string, (RazorCompiledItem item, RazorViewAttribute attribute)>(StringComparer.OrdinalIgnoreCase);
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
var item = items[i];
|
||||
if (!dictionary.TryGetValue(item.Identifier, out var entry))
|
||||
{
|
||||
dictionary.Add(item.Identifier, (item, null));
|
||||
|
||||
}
|
||||
else if (entry.item == null)
|
||||
{
|
||||
dictionary[item.Identifier] = (item, entry.attribute);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
if (!dictionary.TryGetValue(attribute.Path, out var entry))
|
||||
{
|
||||
dictionary.Add(attribute.Path, (null, attribute));
|
||||
}
|
||||
else if (entry.attribute == null)
|
||||
{
|
||||
dictionary[attribute.Path] = (entry.item, attribute);
|
||||
}
|
||||
}
|
||||
|
||||
return dictionary.Values;
|
||||
}
|
||||
|
||||
protected virtual IReadOnlyList<RazorCompiledItem> LoadItems(AssemblyPart assemblyPart)
|
||||
{
|
||||
if (assemblyPart == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(assemblyPart));
|
||||
}
|
||||
|
||||
var viewAssembly = GetViewAssembly(assemblyPart);
|
||||
if (viewAssembly != null)
|
||||
{
|
||||
var loader = new RazorCompiledItemLoader();
|
||||
return loader.LoadItems(viewAssembly);
|
||||
}
|
||||
|
||||
return Array.Empty<RazorCompiledItem>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sequence of <see cref="RazorViewAttribute"/> instances associated with the specified <paramref name="assemblyPart"/>.
|
||||
/// </summary>
|
||||
|
|
@ -53,7 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
throw new ArgumentNullException(nameof(assemblyPart));
|
||||
}
|
||||
|
||||
var featureAssembly = GetFeatureAssembly(assemblyPart);
|
||||
var featureAssembly = GetViewAssembly(assemblyPart);
|
||||
if (featureAssembly != null)
|
||||
{
|
||||
return featureAssembly.GetCustomAttributes<RazorViewAttribute>();
|
||||
|
|
@ -62,29 +114,28 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
return Enumerable.Empty<RazorViewAttribute>();
|
||||
}
|
||||
|
||||
private static Assembly GetFeatureAssembly(AssemblyPart assemblyPart)
|
||||
private Assembly GetViewAssembly(AssemblyPart assemblyPart)
|
||||
{
|
||||
if (assemblyPart.Assembly.IsDynamic || string.IsNullOrEmpty(assemblyPart.Assembly.Location))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var precompiledAssemblyFileName = assemblyPart.Assembly.GetName().Name
|
||||
+ PrecompiledViewsAssemblySuffix
|
||||
+ ".dll";
|
||||
var precompiledAssemblyFilePath = Path.Combine(
|
||||
Path.GetDirectoryName(assemblyPart.Assembly.Location),
|
||||
precompiledAssemblyFileName);
|
||||
|
||||
if (File.Exists(precompiledAssemblyFilePath))
|
||||
for (var i = 0; i < ViewAssemblySuffixes.Count; i++)
|
||||
{
|
||||
try
|
||||
var fileName = assemblyPart.Assembly.GetName().Name + ViewAssemblySuffixes[i] + ".dll";
|
||||
var filePath = Path.Combine(Path.GetDirectoryName(assemblyPart.Assembly.Location), fileName);
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
return Assembly.LoadFile(precompiledAssemblyFilePath);
|
||||
}
|
||||
catch (FileLoadException)
|
||||
{
|
||||
// Don't throw if assembly cannot be loaded. This can happen if the file is not a managed assembly.
|
||||
try
|
||||
{
|
||||
return Assembly.LoadFile(filePath);
|
||||
}
|
||||
catch (FileLoadException)
|
||||
{
|
||||
// Don't throw if assembly cannot be loaded. This can happen if the file is not a managed assembly.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
// 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.Internal
|
||||
{
|
||||
public 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(RazorProject project, RazorCompiledItem item)
|
||||
{
|
||||
if (project == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(project));
|
||||
}
|
||||
|
||||
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 = project.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 = project.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -43,12 +43,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
|
||||
var compileTask = Compiler.CompileAsync(relativePath);
|
||||
var viewDescriptor = compileTask.GetAwaiter().GetResult();
|
||||
if (viewDescriptor.ViewAttribute != null)
|
||||
{
|
||||
var compiledType = viewDescriptor.ViewAttribute.ViewType;
|
||||
|
||||
var newExpression = Expression.New(compiledType);
|
||||
var pathProperty = compiledType.GetTypeInfo().GetProperty(nameof(IRazorPage.Path));
|
||||
var viewType = viewDescriptor.Type;
|
||||
if (viewType != null)
|
||||
{
|
||||
var newExpression = Expression.New(viewType);
|
||||
var pathProperty = viewType.GetTypeInfo().GetProperty(nameof(IRazorPage.Path));
|
||||
|
||||
// Generate: page.Path = relativePath;
|
||||
// Use the normalized path specified from the result.
|
||||
|
|
|
|||
|
|
@ -6,16 +6,19 @@ 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.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Razor.Hosting;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||
{
|
||||
|
|
@ -25,8 +28,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
public class RazorViewCompiler : IViewCompiler
|
||||
{
|
||||
private readonly object _cacheLock = new object();
|
||||
private readonly Dictionary<string, Task<CompiledViewDescriptor>> _precompiledViewLookup;
|
||||
private readonly ConcurrentDictionary<string, string> _normalizedPathLookup;
|
||||
private readonly Dictionary<string, CompiledViewDescriptor> _precompiledViews;
|
||||
private readonly ConcurrentDictionary<string, string> _normalizedPathCache;
|
||||
private readonly IFileProvider _fileProvider;
|
||||
private readonly RazorTemplateEngine _templateEngine;
|
||||
private readonly Action<RoslynCompilationContext> _compilationCallback;
|
||||
|
|
@ -78,26 +81,33 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
_compilationCallback = compilationCallback;
|
||||
_logger = logger;
|
||||
|
||||
_normalizedPathLookup = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
|
||||
_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 = new MemoryCache(new MemoryCacheOptions());
|
||||
|
||||
_precompiledViewLookup = new Dictionary<string, Task<CompiledViewDescriptor>>(
|
||||
// We need to validate that the all of the precompiled views are unique by path (case-insenstive).
|
||||
// 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,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var precompiledView in precompiledViews)
|
||||
{
|
||||
if (_precompiledViewLookup.TryGetValue(precompiledView.RelativePath, out var otherValue))
|
||||
if (_precompiledViews.TryGetValue(precompiledView.RelativePath, out var otherValue))
|
||||
{
|
||||
var message = string.Join(
|
||||
Environment.NewLine,
|
||||
Resources.RazorViewCompiler_ViewPathsDifferOnlyInCase,
|
||||
otherValue.Result.RelativePath,
|
||||
otherValue.RelativePath,
|
||||
precompiledView.RelativePath);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
_precompiledViewLookup.Add(precompiledView.RelativePath, Task.FromResult(precompiledView));
|
||||
_precompiledViews.Add(precompiledView.RelativePath, precompiledView);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -109,103 +119,192 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
throw new ArgumentNullException(nameof(relativePath));
|
||||
}
|
||||
|
||||
// Lookup precompiled views first.
|
||||
|
||||
// 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.
|
||||
string normalizedPath = null;
|
||||
Task<CompiledViewDescriptor> cachedResult;
|
||||
|
||||
if (_precompiledViewLookup.Count > 0)
|
||||
{
|
||||
if (_precompiledViewLookup.TryGetValue(relativePath, out cachedResult))
|
||||
{
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
normalizedPath = GetNormalizedPath(relativePath);
|
||||
if (_precompiledViewLookup.TryGetValue(normalizedPath, out cachedResult))
|
||||
{
|
||||
return cachedResult;
|
||||
}
|
||||
}
|
||||
|
||||
if (_cache.TryGetValue(relativePath, out cachedResult))
|
||||
{
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
normalizedPath = normalizedPath ?? GetNormalizedPath(relativePath);
|
||||
var normalizedPath = GetNormalizedPath(relativePath);
|
||||
if (_cache.TryGetValue(normalizedPath, out cachedResult))
|
||||
{
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
// Entry does not exist. Attempt to create one.
|
||||
cachedResult = CreateCacheEntry(normalizedPath);
|
||||
cachedResult = OnCacheMiss(normalizedPath);
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
private Task<CompiledViewDescriptor> CreateCacheEntry(string normalizedPath)
|
||||
private Task<CompiledViewDescriptor> OnCacheMiss(string normalizedPath)
|
||||
{
|
||||
TaskCompletionSource<CompiledViewDescriptor> compilationTaskSource = null;
|
||||
ViewCompilerWorkItem item;
|
||||
TaskCompletionSource<CompiledViewDescriptor> taskSource;
|
||||
MemoryCacheEntryOptions cacheEntryOptions;
|
||||
Task<CompiledViewDescriptor> cacheEntry;
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (_cache.TryGetValue(normalizedPath, out cacheEntry))
|
||||
// Double-checked locking to handle a possible race.
|
||||
if (_cache.TryGetValue(normalizedPath, out Task<CompiledViewDescriptor> result))
|
||||
{
|
||||
return cacheEntry;
|
||||
return result;
|
||||
}
|
||||
|
||||
cacheEntryOptions = new MemoryCacheEntryOptions();
|
||||
|
||||
cacheEntryOptions.ExpirationTokens.Add(_fileProvider.Watch(normalizedPath));
|
||||
var projectItem = _templateEngine.Project.GetItem(normalizedPath);
|
||||
if (!projectItem.Exists)
|
||||
if (_precompiledViews.TryGetValue(normalizedPath, out var precompiledView))
|
||||
{
|
||||
cacheEntry = Task.FromResult(new CompiledViewDescriptor
|
||||
{
|
||||
RelativePath = normalizedPath,
|
||||
ExpirationTokens = cacheEntryOptions.ExpirationTokens,
|
||||
});
|
||||
item = CreatePrecompiledWorkItem(normalizedPath, precompiledView);
|
||||
}
|
||||
else
|
||||
{
|
||||
// A file exists and needs to be compiled.
|
||||
compilationTaskSource = new TaskCompletionSource<CompiledViewDescriptor>();
|
||||
foreach (var importItem in _templateEngine.GetImportItems(projectItem))
|
||||
{
|
||||
cacheEntryOptions.ExpirationTokens.Add(_fileProvider.Watch(importItem.FilePath));
|
||||
}
|
||||
cacheEntry = compilationTaskSource.Task;
|
||||
item = CreateRuntimeCompilationWorkItem(normalizedPath);
|
||||
}
|
||||
|
||||
cacheEntry = _cache.Set(normalizedPath, cacheEntry, cacheEntryOptions);
|
||||
// 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>();
|
||||
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);
|
||||
}
|
||||
|
||||
if (compilationTaskSource != null)
|
||||
// Now the lock has been released so we can do more expensive processing.
|
||||
if (item.SupportsCompilation)
|
||||
{
|
||||
// Indicates that a file was found and needs to be compiled.
|
||||
Debug.Assert(cacheEntryOptions != null);
|
||||
Debug.Assert(taskSource != null);
|
||||
|
||||
if (item.Descriptor?.Item != null &&
|
||||
ChecksumValidator.IsItemValid(_templateEngine.Project, 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;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var descriptor = CompileAndEmit(normalizedPath);
|
||||
descriptor.ExpirationTokens = cacheEntryOptions.ExpirationTokens;
|
||||
compilationTaskSource.SetResult(descriptor);
|
||||
taskSource.SetResult(descriptor);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
compilationTaskSource.SetException(ex);
|
||||
taskSource.SetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
return cacheEntry;
|
||||
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 = new List<IChangeToken>(),
|
||||
};
|
||||
|
||||
var checksums = precompiledView.Item.GetChecksumMetadata();
|
||||
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.
|
||||
item.ExpirationTokens.Add(_fileProvider.Watch(checksums[i].Identifier));
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private ViewCompilerWorkItem CreateRuntimeCompilationWorkItem(string normalizedPath)
|
||||
{
|
||||
var expirationTokens = new List<IChangeToken>()
|
||||
{
|
||||
_fileProvider.Watch(normalizedPath),
|
||||
};
|
||||
|
||||
var projectItem = _templateEngine.Project.GetItem(normalizedPath);
|
||||
if (!projectItem.Exists)
|
||||
{
|
||||
// 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 retrigger 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,
|
||||
};
|
||||
}
|
||||
|
||||
// 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.
|
||||
foreach (var importItem in _templateEngine.GetImportItems(projectItem))
|
||||
{
|
||||
expirationTokens.Add(_fileProvider.Watch(importItem.FilePath));
|
||||
}
|
||||
|
||||
return new ViewCompilerWorkItem()
|
||||
{
|
||||
SupportsCompilation = true,
|
||||
|
||||
NormalizedPath = normalizedPath,
|
||||
ExpirationTokens = expirationTokens,
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual CompiledViewDescriptor CompileAndEmit(string relativePath)
|
||||
|
|
@ -220,13 +319,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
cSharpDocument.Diagnostics);
|
||||
}
|
||||
|
||||
var generatedAssembly = CompileAndEmit(codeDocument, cSharpDocument.GeneratedCode);
|
||||
var viewAttribute = generatedAssembly.GetCustomAttribute<RazorViewAttribute>();
|
||||
return new CompiledViewDescriptor
|
||||
{
|
||||
ViewAttribute = viewAttribute,
|
||||
RelativePath = relativePath,
|
||||
};
|
||||
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)
|
||||
|
|
@ -288,13 +388,24 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
return relativePath;
|
||||
}
|
||||
|
||||
if (!_normalizedPathLookup.TryGetValue(relativePath, out var normalizedPath))
|
||||
if (!_normalizedPathCache.TryGetValue(relativePath, out var normalizedPath))
|
||||
{
|
||||
normalizedPath = ViewPath.NormalizePath(relativePath);
|
||||
_normalizedPathLookup[relativePath] = normalizedPath;
|
||||
_normalizedPathCache[relativePath] = normalizedPath;
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -378,6 +378,20 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
internal static string FormatUnsupportedDebugInformationFormat(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("UnsupportedDebugInformationFormat"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// At least one of the '{0}' or '{1}' values must be non-null.
|
||||
/// </summary>
|
||||
internal static string CompiledViewDescriptor_NoData
|
||||
{
|
||||
get => GetString("CompiledViewDescriptor_NoData");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// At least one of the '{0}' or '{1}' values must be non-null.
|
||||
/// </summary>
|
||||
internal static string FormatCompiledViewDescriptor_NoData(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("CompiledViewDescriptor_NoData"), p0, p1);
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -197,4 +197,7 @@
|
|||
<data name="UnsupportedDebugInformationFormat" xml:space="preserve">
|
||||
<value>The debug type specified in the dependency context could be parsed. The debug type value '{0}' is not supported.</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="CompiledViewDescriptor_NoData" xml:space="preserve">
|
||||
<value>At least one of the '{0}' or '{1}' values must be non-null.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -8,7 +8,9 @@ using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
|||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
|
@ -16,19 +18,40 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
{
|
||||
public class CompiledPageRouteModelProvider : IPageRouteModelProvider
|
||||
{
|
||||
private readonly object _cacheLock = new object();
|
||||
private readonly ApplicationPartManager _applicationManager;
|
||||
private readonly RazorPagesOptions _pagesOptions;
|
||||
private readonly RazorTemplateEngine _templateEngine;
|
||||
private readonly ILogger<CompiledPageRouteModelProvider> _logger;
|
||||
private List<PageRouteModel> _cachedModels;
|
||||
|
||||
public CompiledPageRouteModelProvider(
|
||||
ApplicationPartManager applicationManager,
|
||||
IOptions<RazorPagesOptions> pagesOptionsAccessor,
|
||||
RazorTemplateEngine templateEngine,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
if (applicationManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationManager));
|
||||
}
|
||||
|
||||
if (pagesOptionsAccessor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pagesOptionsAccessor));
|
||||
}
|
||||
|
||||
if (templateEngine == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(templateEngine));
|
||||
}
|
||||
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(loggerFactory));
|
||||
}
|
||||
|
||||
_applicationManager = applicationManager;
|
||||
_pagesOptions = pagesOptionsAccessor.Value;
|
||||
_templateEngine = templateEngine;
|
||||
_logger = loggerFactory.CreateLogger<CompiledPageRouteModelProvider>();
|
||||
}
|
||||
|
||||
|
|
@ -36,61 +59,60 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
|
||||
public void OnProvidersExecuting(PageRouteModelProviderContext context)
|
||||
{
|
||||
EnsureCache();
|
||||
for (var i = 0; i < _cachedModels.Count; i++)
|
||||
if (context == null)
|
||||
{
|
||||
var pageModel = _cachedModels[i];
|
||||
context.RouteModels.Add(new PageRouteModel(pageModel));
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
CreateModels(context.RouteModels);
|
||||
}
|
||||
|
||||
public void OnProvidersExecuted(PageRouteModelProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureCache()
|
||||
private void CreateModels(IList<PageRouteModel> results)
|
||||
{
|
||||
lock (_cacheLock)
|
||||
var rootDirectory = _pagesOptions.RootDirectory;
|
||||
if (!rootDirectory.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
if (_cachedModels != null)
|
||||
rootDirectory = rootDirectory + "/";
|
||||
}
|
||||
|
||||
var areaRootDirectory = _pagesOptions.AreaRootDirectory;
|
||||
if (!areaRootDirectory.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
areaRootDirectory = areaRootDirectory + "/";
|
||||
}
|
||||
|
||||
foreach (var viewDescriptor in GetViewDescriptors(_applicationManager))
|
||||
{
|
||||
if (viewDescriptor.Item != null && !ChecksumValidator.IsItemValid(_templateEngine.Project, viewDescriptor.Item))
|
||||
{
|
||||
return;
|
||||
// If we get here, this compiled Page has different local content, so ignore it.
|
||||
continue;
|
||||
}
|
||||
|
||||
var rootDirectory = _pagesOptions.RootDirectory;
|
||||
if (!rootDirectory.EndsWith("/", StringComparison.Ordinal))
|
||||
PageRouteModel model = null;
|
||||
// When RootDirectory and AreaRootDirectory overlap (e.g. RootDirectory = '/', AreaRootDirectory = '/Areas'), we
|
||||
// only want to allow a page to be associated with the area route.
|
||||
if (_pagesOptions.AllowAreas && viewDescriptor.RelativePath.StartsWith(areaRootDirectory, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
rootDirectory = rootDirectory + "/";
|
||||
model = GetAreaPageRouteModel(areaRootDirectory, viewDescriptor);
|
||||
}
|
||||
else if (viewDescriptor.RelativePath.StartsWith(rootDirectory, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
model = GetPageRouteModel(rootDirectory, viewDescriptor);
|
||||
}
|
||||
|
||||
var areaRootDirectory = _pagesOptions.AreaRootDirectory;
|
||||
if (!areaRootDirectory.EndsWith("/", StringComparison.Ordinal))
|
||||
if (model != null)
|
||||
{
|
||||
areaRootDirectory = areaRootDirectory + "/";
|
||||
results.Add(model);
|
||||
}
|
||||
|
||||
var cachedApplicationModels = new List<PageRouteModel>();
|
||||
foreach (var viewDescriptor in GetViewDescriptors(_applicationManager))
|
||||
{
|
||||
PageRouteModel model = null;
|
||||
// When RootDirectory and AreaRootDirectory overlap (e.g. RootDirectory = '/', AreaRootDirectory = '/Areas'), we
|
||||
// only want to allow a page to be associated with the area route.
|
||||
if (_pagesOptions.AllowAreas && viewDescriptor.RelativePath.StartsWith(areaRootDirectory, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
model = GetAreaPageRouteModel(areaRootDirectory, viewDescriptor);
|
||||
}
|
||||
else if (viewDescriptor.RelativePath.StartsWith(rootDirectory, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
model = GetPageRouteModel(rootDirectory, viewDescriptor);
|
||||
}
|
||||
|
||||
if (model != null)
|
||||
{
|
||||
cachedApplicationModels.Add(model);
|
||||
}
|
||||
}
|
||||
|
||||
_cachedModels = cachedApplicationModels;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
<ProjectReference Include="..\WebSites\HtmlGenerationWebSite\HtmlGenerationWebSite.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.TestCommon\Microsoft.AspNetCore.Mvc.TestCommon.csproj" />
|
||||
<ProjectReference Include="..\..\samples\MvcSandbox\MvcSandbox.csproj" />
|
||||
<ProjectReference Include="..\WebSites\RazorBuildWebSite\RazorBuildWebSite.csproj" />
|
||||
<ProjectReference Include="..\WebSites\RazorPageExecutionInstrumentationWebSite\RazorPageExecutionInstrumentationWebSite.csproj" />
|
||||
<ProjectReference Include="..\WebSites\RazorPagesWebSite\RazorPagesWebSite.csproj" />
|
||||
<ProjectReference Include="..\WebSites\RazorWebSite\RazorWebSite.csproj" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
// 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.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class RazorBuildTest : IClassFixture<MvcTestFixture<RazorBuildWebSite.Startup>>
|
||||
{
|
||||
public RazorBuildTest(MvcTestFixture<RazorBuildWebSite.Startup> fixture)
|
||||
{
|
||||
Client = fixture.Client;
|
||||
}
|
||||
|
||||
public HttpClient Client { get; }
|
||||
|
||||
[Fact]
|
||||
public async Task PrecompiledPage_LocalPageWithDifferentContent_NotUsed()
|
||||
{
|
||||
// Act
|
||||
var response = await Client.GetAsync("http://localhost/Precompilation/Page");
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("Hello from buildtime-compiled precompilation page!", responseBody.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PrecompiledView_LocalViewWithDifferentContent_NotUsed()
|
||||
{
|
||||
// Act
|
||||
var response = await Client.GetAsync("http://localhost/Precompilation/View");
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("Hello from buildtime-compiled precompilation view!", responseBody.Trim());
|
||||
}
|
||||
|
||||
[Fact(Skip = "Not yet implemented")]
|
||||
public async Task Rzc_LocalPageWithDifferentContent_IsUsed()
|
||||
{
|
||||
// Act
|
||||
var response = await Client.GetAsync("http://localhost/Rzc/Page");
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("Hello from runtime-compiled rzc page!", responseBody.Trim());
|
||||
}
|
||||
|
||||
[Fact(Skip = "Not yet implemented")]
|
||||
public async Task Rzc_LocalViewWithDifferentContent_IsUsed()
|
||||
{
|
||||
// Act
|
||||
var response = await Client.GetAsync("http://localhost/Rzc/View");
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("Hello from runtime-compiled rzc view!", responseBody.Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,9 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Razor.Hosting;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
||||
|
|
@ -17,14 +19,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
public void PopulateFeature_ReturnsEmptySequenceIfNoAssemblyPartHasViewAssembly()
|
||||
{
|
||||
// Arrange
|
||||
var applicationPartManager = new ApplicationPartManager();
|
||||
applicationPartManager.ApplicationParts.Add(
|
||||
new AssemblyPart(typeof(ViewsFeatureProviderTest).GetTypeInfo().Assembly));
|
||||
applicationPartManager.FeatureProviders.Add(new ViewsFeatureProvider());
|
||||
var partManager = new ApplicationPartManager();
|
||||
partManager.ApplicationParts.Add(new AssemblyPart(typeof(ViewsFeatureProviderTest).GetTypeInfo().Assembly));
|
||||
partManager.FeatureProviders.Add(new ViewsFeatureProvider());
|
||||
var feature = new ViewsFeature();
|
||||
|
||||
// Act
|
||||
applicationPartManager.PopulateFeature(feature);
|
||||
partManager.PopulateFeature(feature);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(feature.ViewDescriptors);
|
||||
|
|
@ -36,7 +37,29 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
// Arrange
|
||||
var part1 = new AssemblyPart(typeof(object).GetTypeInfo().Assembly);
|
||||
var part2 = new AssemblyPart(GetType().GetTypeInfo().Assembly);
|
||||
var featureProvider = new TestableViewsFeatureProvider(new Dictionary<AssemblyPart, IEnumerable<RazorViewAttribute>>
|
||||
|
||||
var items = new Dictionary<AssemblyPart, IReadOnlyList<RazorCompiledItem>>
|
||||
{
|
||||
{
|
||||
part1,
|
||||
new[]
|
||||
{
|
||||
new TestRazorCompiledItem(typeof(object), "mvc.1.0.view", "/Views/test/Index.cshtml", new object[]{ }),
|
||||
|
||||
// This one doesn't have a RazorViewAttribute
|
||||
new TestRazorCompiledItem(typeof(StringBuilder), "mvc.1.0.view", "/Views/test/About.cshtml", new object[]{ }),
|
||||
}
|
||||
},
|
||||
{
|
||||
part2,
|
||||
new[]
|
||||
{
|
||||
new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", "/Areas/Admin/Views/Index.cshtml", new object[]{ }),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var attributes = new Dictionary<AssemblyPart, IEnumerable<RazorViewAttribute>>
|
||||
{
|
||||
{
|
||||
part1,
|
||||
|
|
@ -50,35 +73,70 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
new[]
|
||||
{
|
||||
new RazorViewAttribute("/Areas/Admin/Views/Index.cshtml", typeof(string)),
|
||||
|
||||
// This one doesn't have a RazorCompiledItem
|
||||
new RazorViewAttribute("/Areas/Admin/Views/About.cshtml", typeof(int)),
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
var applicationPartManager = new ApplicationPartManager();
|
||||
applicationPartManager.ApplicationParts.Add(part1);
|
||||
applicationPartManager.ApplicationParts.Add(part2);
|
||||
applicationPartManager.FeatureProviders.Add(featureProvider);
|
||||
var featureProvider = new TestableViewsFeatureProvider(items, attributes);
|
||||
var partManager = new ApplicationPartManager();
|
||||
partManager.ApplicationParts.Add(part1);
|
||||
partManager.ApplicationParts.Add(part2);
|
||||
partManager.FeatureProviders.Add(featureProvider);
|
||||
var feature = new ViewsFeature();
|
||||
|
||||
// Act
|
||||
applicationPartManager.PopulateFeature(feature);
|
||||
partManager.PopulateFeature(feature);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(feature.ViewDescriptors.OrderBy(f => f.RelativePath, StringComparer.Ordinal),
|
||||
view =>
|
||||
{
|
||||
Assert.Empty(view.ExpirationTokens);
|
||||
Assert.True(view.IsPrecompiled);
|
||||
Assert.Null(view.Item);
|
||||
Assert.Equal("/Areas/Admin/Views/About.cshtml", view.RelativePath);
|
||||
Assert.Equal(typeof(int), view.Type);
|
||||
Assert.Equal("/Areas/Admin/Views/About.cshtml", view.ViewAttribute.Path);
|
||||
Assert.Equal(typeof(int), view.ViewAttribute.ViewType);
|
||||
},
|
||||
view =>
|
||||
{
|
||||
// This one doesn't have a RazorCompiledItem
|
||||
Assert.Empty(view.ExpirationTokens);
|
||||
Assert.True(view.IsPrecompiled);
|
||||
Assert.Equal("/Areas/Admin/Views/Index.cshtml", view.Item.Identifier);
|
||||
Assert.Equal("mvc.1.0.view", view.Item.Kind);
|
||||
Assert.Equal(typeof(string), view.Item.Type);
|
||||
Assert.Equal("/Areas/Admin/Views/Index.cshtml", view.RelativePath);
|
||||
Assert.Equal(typeof(string), view.Type);
|
||||
Assert.Equal("/Areas/Admin/Views/Index.cshtml", view.ViewAttribute.Path);
|
||||
Assert.Equal(typeof(string), view.ViewAttribute.ViewType);
|
||||
},
|
||||
view =>
|
||||
{
|
||||
// This one doesn't have a RazorViewAttribute
|
||||
Assert.Empty(view.ExpirationTokens);
|
||||
Assert.True(view.IsPrecompiled);
|
||||
Assert.Equal("/Views/test/About.cshtml", view.Item.Identifier);
|
||||
Assert.Equal("mvc.1.0.view", view.Item.Kind);
|
||||
Assert.Equal(typeof(StringBuilder), view.Item.Type);
|
||||
Assert.Equal("/Views/test/About.cshtml", view.RelativePath);
|
||||
Assert.Equal(typeof(StringBuilder), view.Type);
|
||||
Assert.Null(view.ViewAttribute);
|
||||
},
|
||||
view =>
|
||||
{
|
||||
Assert.Empty(view.ExpirationTokens);
|
||||
Assert.True(view.IsPrecompiled);
|
||||
Assert.Equal("/Views/test/Index.cshtml", view.Item.Identifier);
|
||||
Assert.Equal("mvc.1.0.view", view.Item.Kind);
|
||||
Assert.Equal(typeof(object), view.Item.Type);
|
||||
Assert.Equal("/Views/test/Index.cshtml", view.RelativePath);
|
||||
Assert.Equal(typeof(object), view.Type);
|
||||
Assert.Equal("/Views/test/Index.cshtml", view.ViewAttribute.Path);
|
||||
Assert.Equal(typeof(object), view.ViewAttribute.ViewType);
|
||||
});
|
||||
}
|
||||
|
|
@ -88,50 +146,78 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
|
|||
{
|
||||
// Arrange
|
||||
var name = new AssemblyName($"DynamicAssembly-{Guid.NewGuid()}");
|
||||
var assembly = AssemblyBuilder.DefineDynamicAssembly(name,
|
||||
AssemblyBuilderAccess.RunAndCollect);
|
||||
var assembly = AssemblyBuilder.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndCollect);
|
||||
|
||||
var applicationPartManager = new ApplicationPartManager();
|
||||
applicationPartManager.ApplicationParts.Add(new AssemblyPart(assembly));
|
||||
applicationPartManager.FeatureProviders.Add(new ViewsFeatureProvider());
|
||||
var partManager = new ApplicationPartManager();
|
||||
partManager.ApplicationParts.Add(new AssemblyPart(assembly));
|
||||
partManager.FeatureProviders.Add(new ViewsFeatureProvider());
|
||||
var feature = new ViewsFeature();
|
||||
|
||||
// Act
|
||||
applicationPartManager.PopulateFeature(feature);
|
||||
partManager.PopulateFeature(feature);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(feature.ViewDescriptors);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void PopulateFeature_DoesNotFail_IfAssemblyHasEmptyLocation()
|
||||
{
|
||||
// Arrange
|
||||
var assembly = new AssemblyWithEmptyLocation();
|
||||
var applicationPartManager = new ApplicationPartManager();
|
||||
applicationPartManager.ApplicationParts.Add(new AssemblyPart(assembly));
|
||||
applicationPartManager.FeatureProviders.Add(new ViewsFeatureProvider());
|
||||
var partManager = new ApplicationPartManager();
|
||||
partManager.ApplicationParts.Add(new AssemblyPart(assembly));
|
||||
partManager.FeatureProviders.Add(new ViewsFeatureProvider());
|
||||
var feature = new ViewsFeature();
|
||||
|
||||
// Act
|
||||
applicationPartManager.PopulateFeature(feature);
|
||||
partManager.PopulateFeature(feature);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(feature.ViewDescriptors);
|
||||
}
|
||||
|
||||
private class TestRazorCompiledItem : RazorCompiledItem
|
||||
{
|
||||
public TestRazorCompiledItem(Type type, string kind, string identifier, object[] metadata)
|
||||
{
|
||||
Type = type;
|
||||
Kind = kind;
|
||||
Identifier = identifier;
|
||||
Metadata = metadata;
|
||||
}
|
||||
|
||||
public override string Identifier { get; }
|
||||
|
||||
public override string Kind { get; }
|
||||
|
||||
public override IReadOnlyList<object> Metadata { get; }
|
||||
|
||||
public override Type Type { get; }
|
||||
}
|
||||
|
||||
private class TestableViewsFeatureProvider : ViewsFeatureProvider
|
||||
{
|
||||
private readonly Dictionary<AssemblyPart, IEnumerable<RazorViewAttribute>> _attributeLookup;
|
||||
private readonly Dictionary<AssemblyPart, IEnumerable<RazorViewAttribute>> _attributes;
|
||||
private readonly Dictionary<AssemblyPart, IReadOnlyList<RazorCompiledItem>> _items;
|
||||
|
||||
public TestableViewsFeatureProvider(Dictionary<AssemblyPart, IEnumerable<RazorViewAttribute>> attributeLookup)
|
||||
public TestableViewsFeatureProvider(
|
||||
Dictionary<AssemblyPart, IReadOnlyList<RazorCompiledItem>> items,
|
||||
Dictionary<AssemblyPart, IEnumerable<RazorViewAttribute>> attributes)
|
||||
{
|
||||
_attributeLookup = attributeLookup;
|
||||
_items = items;
|
||||
_attributes = attributes;
|
||||
}
|
||||
|
||||
protected override IEnumerable<RazorViewAttribute> GetViewAttributes(AssemblyPart assemblyPart)
|
||||
{
|
||||
return _attributeLookup[assemblyPart];
|
||||
return _attributes[assemblyPart];
|
||||
}
|
||||
|
||||
protected override IReadOnlyList<RazorCompiledItem> LoadItems(AssemblyPart assemblyPart)
|
||||
{
|
||||
return _items[assemblyPart];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,199 @@
|
|||
// 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 Moq;
|
||||
using Xunit;
|
||||
using static Microsoft.AspNetCore.Razor.Hosting.TestRazorCompiledItem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||
{
|
||||
public class ChecksumValidatorTest
|
||||
{
|
||||
public ChecksumValidatorTest()
|
||||
{
|
||||
FileProvider = new TestFileProvider();
|
||||
Project = new FileProviderRazorProject(Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == FileProvider));
|
||||
}
|
||||
|
||||
public RazorProject Project { get; }
|
||||
|
||||
public TestFileProvider FileProvider { 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(Project, 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(Project, 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"),
|
||||
});
|
||||
|
||||
FileProvider.AddFile("/Views/Home/_ViewImports.cstml", "dkdkfkdf"); // This will be ignored
|
||||
|
||||
// Act
|
||||
var result = ChecksumValidator.IsItemValid(Project, 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"),
|
||||
});
|
||||
|
||||
FileProvider.AddFile("/Views/Home/Index.cstml", "other content");
|
||||
|
||||
// Act
|
||||
var result = ChecksumValidator.IsItemValid(Project, 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"),
|
||||
});
|
||||
|
||||
FileProvider.AddFile("/Views/Home/Index.cstml", "some content");
|
||||
|
||||
// Act
|
||||
var result = ChecksumValidator.IsItemValid(Project, 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"),
|
||||
});
|
||||
|
||||
FileProvider.AddFile("/Views/Home/Index.cstml", "some content");
|
||||
FileProvider.AddFile("/Views/Home/_ViewImports.cstml", "some other import");
|
||||
|
||||
// Act
|
||||
var result = ChecksumValidator.IsItemValid(Project, 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"),
|
||||
});
|
||||
|
||||
FileProvider.AddFile("/Views/Home/Index.cstml", "some content");
|
||||
FileProvider.AddFile("/Views/Home/_ViewImports.cstml", "some import");
|
||||
FileProvider.AddFile("/Views/_ViewImports.cstml", "some other import");
|
||||
|
||||
// Act
|
||||
var result = ChecksumValidator.IsItemValid(Project, item);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,12 +8,14 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Razor.Hosting;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using static Microsoft.AspNetCore.Razor.Hosting.TestRazorCompiledItem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||
{
|
||||
|
|
@ -55,7 +57,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
// Assert
|
||||
Assert.Same(result1, result2);
|
||||
Assert.Null(result1.ViewAttribute);
|
||||
Assert.Collection(result1.ExpirationTokens,
|
||||
Assert.Collection(
|
||||
result1.ExpirationTokens,
|
||||
token => Assert.Equal(fileProvider.GetChangeToken(path), token));
|
||||
}
|
||||
|
||||
|
|
@ -73,7 +76,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
|
||||
// Assert
|
||||
Assert.NotNull(result.ViewAttribute);
|
||||
Assert.Collection(result.ExpirationTokens,
|
||||
Assert.Collection(
|
||||
result.ExpirationTokens,
|
||||
token => Assert.Same(fileProvider.GetChangeToken(path), token),
|
||||
token => Assert.Same(fileProvider.GetChangeToken("/file/exists/_ViewImports.cshtml"), token),
|
||||
token => Assert.Same(fileProvider.GetChangeToken("/file/_ViewImports.cshtml"), token),
|
||||
|
|
@ -246,27 +250,280 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompileAsync_DoesNotRecompile_IfFileTriggerWasSetForPrecompiledView()
|
||||
public async Task CompileAsync_PrecompiledViewWithoutChecksumForMainSource_DoesNotSupportRecompilation()
|
||||
{
|
||||
// Arrange
|
||||
var path = "/Views/Home/Index.cshtml";
|
||||
|
||||
var fileProvider = new TestFileProvider();
|
||||
var fileInfo = fileProvider.AddFile(path, "some content");
|
||||
|
||||
var precompiledView = new CompiledViewDescriptor
|
||||
{
|
||||
RelativePath = path,
|
||||
Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("sha1", GetChecksum("some content"), "/Views/Some-Other-View"),
|
||||
}),
|
||||
};
|
||||
|
||||
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
||||
|
||||
// Act - 1
|
||||
var result = await viewCompiler.CompileAsync(path);
|
||||
|
||||
// Assert - 1
|
||||
Assert.Same(precompiledView, result);
|
||||
|
||||
// Act - 2
|
||||
fileProvider.Watch(path);
|
||||
fileProvider.GetChangeToken(path).HasChanged = true;
|
||||
result = await viewCompiler.CompileAsync(path);
|
||||
|
||||
// Assert - 2
|
||||
Assert.Same(precompiledView, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompileAsync_PrecompiledViewWithoutAnyChecksum_DoesNotSupportRecompilation()
|
||||
{
|
||||
// Arrange
|
||||
var path = "/Views/Home/Index.cshtml";
|
||||
|
||||
var fileProvider = new TestFileProvider();
|
||||
var fileInfo = fileProvider.AddFile(path, "some content");
|
||||
|
||||
var precompiledView = new CompiledViewDescriptor
|
||||
{
|
||||
RelativePath = path,
|
||||
Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[] { }),
|
||||
};
|
||||
|
||||
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
||||
|
||||
// Act - 1
|
||||
var result = await viewCompiler.CompileAsync(path);
|
||||
|
||||
// Assert - 1
|
||||
Assert.Same(precompiledView, result);
|
||||
|
||||
// Act - 2
|
||||
fileProvider.Watch(path);
|
||||
fileProvider.GetChangeToken(path).HasChanged = true;
|
||||
result = await viewCompiler.CompileAsync(path);
|
||||
|
||||
// Assert - 2
|
||||
Assert.Same(precompiledView, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompileAsync_PrecompiledViewWithChecksum_UsesPrecompiledViewWhenChecksumIsMatch()
|
||||
{
|
||||
// Arrange
|
||||
var path = "/Views/Home/Index.cshtml";
|
||||
|
||||
var fileProvider = new TestFileProvider();
|
||||
var fileInfo = fileProvider.AddFile(path, "some content");
|
||||
|
||||
var precompiledView = new CompiledViewDescriptor
|
||||
{
|
||||
RelativePath = path,
|
||||
Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), path),
|
||||
}),
|
||||
};
|
||||
|
||||
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
||||
|
||||
// Act
|
||||
fileProvider.Watch(path);
|
||||
fileProvider.GetChangeToken(path).HasChanged = true;
|
||||
var result = await viewCompiler.CompileAsync(path);
|
||||
|
||||
// Assert
|
||||
Assert.Same(precompiledView, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompileAsync_PrecompiledViewWithChecksum_CanRejectWhenChecksumFails()
|
||||
{
|
||||
// Arrange
|
||||
var path = "/Views/Home/Index.cshtml";
|
||||
|
||||
var fileProvider = new TestFileProvider();
|
||||
var fileInfo = fileProvider.AddFile(path, "some content");
|
||||
|
||||
var expected = new CompiledViewDescriptor();
|
||||
|
||||
var precompiledView = new CompiledViewDescriptor
|
||||
{
|
||||
RelativePath = path,
|
||||
Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some other content"), path),
|
||||
}),
|
||||
};
|
||||
|
||||
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
||||
viewCompiler.Compile = _ => expected;
|
||||
|
||||
// Act
|
||||
var result = await viewCompiler.CompileAsync(path);
|
||||
|
||||
// Assert
|
||||
Assert.Same(expected, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompileAsync_PrecompiledViewWithChecksum_CanRecompile()
|
||||
{
|
||||
// Arrange
|
||||
var path = "/Views/Home/Index.cshtml";
|
||||
|
||||
var fileProvider = new TestFileProvider();
|
||||
var fileInfo = fileProvider.AddFile(path, "some content");
|
||||
|
||||
var expected2 = new CompiledViewDescriptor();
|
||||
|
||||
var precompiledView = new CompiledViewDescriptor
|
||||
{
|
||||
RelativePath = path,
|
||||
Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), path),
|
||||
}),
|
||||
};
|
||||
|
||||
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
||||
|
||||
// Act - 1
|
||||
var result = await viewCompiler.CompileAsync(path);
|
||||
|
||||
// Assert - 1
|
||||
Assert.Same(precompiledView, result);
|
||||
|
||||
// Act - 2
|
||||
fileInfo.Content = "some other content";
|
||||
fileProvider.GetChangeToken(path).HasChanged = true;
|
||||
viewCompiler.Compile = _ => expected2;
|
||||
result = await viewCompiler.CompileAsync(path);
|
||||
|
||||
// Assert - 2
|
||||
Assert.Same(expected2, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompileAsync_PrecompiledViewWithChecksum_DoesNotRecompiledWithoutContentChange()
|
||||
{
|
||||
// Arrange
|
||||
var path = "/Views/Home/Index.cshtml";
|
||||
|
||||
var fileProvider = new TestFileProvider();
|
||||
var fileInfo = fileProvider.AddFile(path, "some content");
|
||||
|
||||
var precompiledView = new CompiledViewDescriptor
|
||||
{
|
||||
RelativePath = path,
|
||||
Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), path),
|
||||
}),
|
||||
};
|
||||
|
||||
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
||||
|
||||
// Act - 1
|
||||
var result = await viewCompiler.CompileAsync(path);
|
||||
|
||||
// Assert - 1
|
||||
Assert.Same(precompiledView, result);
|
||||
|
||||
// Act - 2
|
||||
fileProvider.GetChangeToken(path).HasChanged = true;
|
||||
result = await viewCompiler.CompileAsync(path);
|
||||
|
||||
// Assert - 2
|
||||
Assert.Same(precompiledView, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompileAsync_PrecompiledViewWithChecksum_CanReusePrecompiledViewIfContentChangesToMatch()
|
||||
{
|
||||
// Arrange
|
||||
var path = "/Views/Home/Index.cshtml";
|
||||
|
||||
var fileProvider = new TestFileProvider();
|
||||
var fileInfo = fileProvider.AddFile(path, "some other content");
|
||||
|
||||
var expected1 = new CompiledViewDescriptor();
|
||||
|
||||
var precompiledView = new CompiledViewDescriptor
|
||||
{
|
||||
RelativePath = path,
|
||||
Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), path),
|
||||
}),
|
||||
};
|
||||
|
||||
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
||||
viewCompiler.Compile = _ => expected1;
|
||||
|
||||
// Act - 1
|
||||
var result = await viewCompiler.CompileAsync(path);
|
||||
|
||||
// Assert - 1
|
||||
Assert.Same(expected1, result);
|
||||
|
||||
// Act - 2
|
||||
fileInfo.Content = "some content";
|
||||
fileProvider.GetChangeToken(path).HasChanged = true;
|
||||
result = await viewCompiler.CompileAsync(path);
|
||||
|
||||
// Assert - 2
|
||||
Assert.Same(precompiledView, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompileAsync_PrecompiledViewWithChecksum_CanRecompileWhenViewImportChanges()
|
||||
{
|
||||
// Arrange
|
||||
var path = "/Views/Home/Index.cshtml";
|
||||
var importPath = "/Views/_ViewImports.cshtml";
|
||||
|
||||
var fileProvider = new TestFileProvider();
|
||||
var fileInfo = fileProvider.AddFile(path, "some content");
|
||||
var importFileInfo = fileProvider.AddFile(importPath, "some import");
|
||||
|
||||
var expected2 = new CompiledViewDescriptor();
|
||||
|
||||
var precompiledView = new CompiledViewDescriptor
|
||||
{
|
||||
RelativePath = path,
|
||||
Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), path),
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some import"), importPath),
|
||||
}),
|
||||
};
|
||||
|
||||
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
||||
|
||||
// Act - 1
|
||||
var result = await viewCompiler.CompileAsync(path);
|
||||
|
||||
// Assert - 1
|
||||
Assert.Same(precompiledView, result);
|
||||
|
||||
// Act - 2
|
||||
importFileInfo.Content = "some import changed";
|
||||
fileProvider.GetChangeToken(importPath).HasChanged = true;
|
||||
viewCompiler.Compile = _ => expected2;
|
||||
result = await viewCompiler.CompileAsync(path);
|
||||
|
||||
// Assert - 2
|
||||
Assert.Same(expected2, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetOrAdd_AllowsConcurrentCompilationOfMultipleRazorPages()
|
||||
{
|
||||
|
|
@ -274,18 +531,23 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
var path1 = "/Views/Home/Index.cshtml";
|
||||
var path2 = "/Views/Home/About.cshtml";
|
||||
var waitDuration = TimeSpan.FromSeconds(20);
|
||||
|
||||
var fileProvider = new TestFileProvider();
|
||||
fileProvider.AddFile(path1, "Index content");
|
||||
fileProvider.AddFile(path2, "About content");
|
||||
|
||||
var resetEvent1 = new AutoResetEvent(initialState: false);
|
||||
var resetEvent2 = new ManualResetEvent(initialState: false);
|
||||
var cache = GetViewCompiler(fileProvider);
|
||||
|
||||
var compilingOne = false;
|
||||
var compilingTwo = false;
|
||||
|
||||
var result1 = new CompiledViewDescriptor();
|
||||
var result2 = new CompiledViewDescriptor();
|
||||
|
||||
cache.Compile = path =>
|
||||
var compiler = GetViewCompiler(fileProvider);
|
||||
|
||||
compiler.Compile = path =>
|
||||
{
|
||||
if (path == path1)
|
||||
{
|
||||
|
|
@ -325,8 +587,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
};
|
||||
|
||||
// Act
|
||||
var task1 = Task.Run(() => cache.CompileAsync(path1));
|
||||
var task2 = Task.Run(() => cache.CompileAsync(path2));
|
||||
var task1 = Task.Run(() => compiler.CompileAsync(path1));
|
||||
var task2 = Task.Run(() => compiler.CompileAsync(path2));
|
||||
|
||||
// Event 1
|
||||
resetEvent1.Set();
|
||||
|
|
|
|||
|
|
@ -6,102 +6,261 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.AspNetCore.Razor.Hosting;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using static Microsoft.AspNetCore.Razor.Hosting.TestRazorCompiledItem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||
{
|
||||
public class CompiledPageRouteModelProviderTest
|
||||
{
|
||||
public CompiledPageRouteModelProviderTest()
|
||||
{
|
||||
FileProvider = new TestFileProvider();
|
||||
Project = new FileProviderRazorProject(Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == FileProvider));
|
||||
TemplateEngine = new RazorTemplateEngine(RazorEngine.Create(), Project);
|
||||
|
||||
PagesOptions = new RazorPagesOptions();
|
||||
Provider = new TestCompiledPageRouteModelProvider(new ApplicationPartManager(), Options.Create(PagesOptions), TemplateEngine, NullLoggerFactory.Instance);
|
||||
}
|
||||
|
||||
public TestFileProvider FileProvider { get; }
|
||||
|
||||
public RazorProject Project { get; }
|
||||
|
||||
public RazorTemplateEngine TemplateEngine { get; }
|
||||
|
||||
public RazorPagesOptions PagesOptions { get; }
|
||||
|
||||
public TestCompiledPageRouteModelProvider Provider { get; }
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_AddsModelsForCompiledViews()
|
||||
{
|
||||
// Arrange
|
||||
var descriptors = new[]
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
{
|
||||
GetDescriptor("/Pages/About.cshtml"),
|
||||
GetDescriptor("/Pages/Home.cshtml", "some-prefix"),
|
||||
};
|
||||
var provider = new TestCompiledPageRouteModelProvider(descriptors, new RazorPagesOptions());
|
||||
CreateVersion_2_0_Descriptor("/Pages/About.cshtml"),
|
||||
CreateVersion_2_0_Descriptor("/Pages/Home.cshtml", "some-prefix"),
|
||||
});
|
||||
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
Provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.RouteModels,
|
||||
Assert.Collection(
|
||||
context.RouteModels,
|
||||
result =>
|
||||
{
|
||||
Assert.Equal("/Pages/About.cshtml", result.RelativePath);
|
||||
Assert.Equal("/About", result.ViewEnginePath);
|
||||
Assert.Collection(result.Selectors,
|
||||
Assert.Collection(
|
||||
result.Selectors,
|
||||
selector => Assert.Equal("About", selector.AttributeRouteModel.Template));
|
||||
Assert.Collection(result.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/About", kvp.Value);
|
||||
});
|
||||
Assert.Collection(
|
||||
result.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/About", kvp.Value);
|
||||
});
|
||||
},
|
||||
result =>
|
||||
{
|
||||
Assert.Equal("/Pages/Home.cshtml", result.RelativePath);
|
||||
Assert.Equal("/Home", result.ViewEnginePath);
|
||||
Assert.Collection(result.Selectors,
|
||||
Assert.Collection(
|
||||
result.Selectors,
|
||||
selector => Assert.Equal("Home/some-prefix", selector.AttributeRouteModel.Template));
|
||||
Assert.Collection(result.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/Home", kvp.Value);
|
||||
});
|
||||
Assert.Collection(
|
||||
result.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/Home", kvp.Value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Fact] // 2.1 adds some additional metadata to the view descriptors. We want to make sure both versions work.
|
||||
public void OnProvidersExecuting_AddsModelsForCompiledViews_Version_2_1()
|
||||
{
|
||||
// Arrange
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
{
|
||||
CreateVersion_2_1_Descriptor("/Pages/About.cshtml"),
|
||||
CreateVersion_2_1_Descriptor("/Pages/Home.cshtml", "some-prefix"),
|
||||
});
|
||||
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
Provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
context.RouteModels,
|
||||
result =>
|
||||
{
|
||||
Assert.Equal("/Pages/About.cshtml", result.RelativePath);
|
||||
Assert.Equal("/About", result.ViewEnginePath);
|
||||
Assert.Collection(
|
||||
result.Selectors,
|
||||
selector => Assert.Equal("About", selector.AttributeRouteModel.Template));
|
||||
Assert.Collection(
|
||||
result.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/About", kvp.Value);
|
||||
});
|
||||
},
|
||||
result =>
|
||||
{
|
||||
Assert.Equal("/Pages/Home.cshtml", result.RelativePath);
|
||||
Assert.Equal("/Home", result.ViewEnginePath);
|
||||
Assert.Collection(
|
||||
result.Selectors,
|
||||
selector => Assert.Equal("Home/some-prefix", selector.AttributeRouteModel.Template));
|
||||
Assert.Collection(
|
||||
result.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/Home", kvp.Value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_ValidatesChecksum_RejectsPageWhenContentDoesntMatch()
|
||||
{
|
||||
// Arrange
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
{
|
||||
CreateVersion_2_1_Descriptor("/Pages/About.cshtml", metadata: new object[]
|
||||
{
|
||||
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Pages/About.cshtml"),
|
||||
}),
|
||||
});
|
||||
|
||||
FileProvider.AddFile("/Pages/About.cshtml", "some other content");
|
||||
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
Provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(context.RouteModels);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_ValidatesChecksum_AcceptsPageWhenContentMatches()
|
||||
{
|
||||
// Arrange
|
||||
Provider.Descriptors.AddRange(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"),
|
||||
}),
|
||||
});
|
||||
|
||||
FileProvider.AddFile("/Pages/About.cshtml", "some content");
|
||||
FileProvider.AddFile("/Pages/_ViewImports.cshtml", "some import");
|
||||
|
||||
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
|
||||
Provider.Descriptors.AddRange(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"),
|
||||
}),
|
||||
});
|
||||
|
||||
FileProvider.AddFile("/Pages/_ViewImports.cshtml", "some other import");
|
||||
|
||||
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()
|
||||
{
|
||||
// Arrange
|
||||
var descriptors = new[]
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
{
|
||||
GetDescriptor("/Features/Products/Files/About.cshtml"),
|
||||
GetDescriptor("/Features/Products/Files/Manage/Index.cshtml"),
|
||||
GetDescriptor("/Features/Products/Files/Manage/Edit.cshtml", "{id}"),
|
||||
};
|
||||
var options = new RazorPagesOptions
|
||||
{
|
||||
AllowAreas = true,
|
||||
AreaRootDirectory = "/Features",
|
||||
RootDirectory = "/Files",
|
||||
};
|
||||
var provider = new TestCompiledPageRouteModelProvider(descriptors, options);
|
||||
CreateVersion_2_0_Descriptor("/Features/Products/Files/About.cshtml"),
|
||||
CreateVersion_2_0_Descriptor("/Features/Products/Files/Manage/Index.cshtml"),
|
||||
CreateVersion_2_0_Descriptor("/Features/Products/Files/Manage/Edit.cshtml", "{id}"),
|
||||
});
|
||||
|
||||
PagesOptions.AllowAreas = true;
|
||||
PagesOptions.AreaRootDirectory = "/Features";
|
||||
PagesOptions.RootDirectory = "/Files";
|
||||
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
Provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.RouteModels,
|
||||
Assert.Collection(
|
||||
context.RouteModels,
|
||||
result =>
|
||||
{
|
||||
Assert.Equal("/Features/Products/Files/About.cshtml", result.RelativePath);
|
||||
Assert.Equal("/About", result.ViewEnginePath);
|
||||
Assert.Collection(result.Selectors,
|
||||
Assert.Collection(
|
||||
result.Selectors,
|
||||
selector => Assert.Equal("Products/About", selector.AttributeRouteModel.Template));
|
||||
Assert.Collection(result.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.Equal("Products", kvp.Value);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/About", kvp.Value);
|
||||
});
|
||||
Assert.Collection(
|
||||
result.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.Equal("Products", kvp.Value);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/About", kvp.Value);
|
||||
});
|
||||
},
|
||||
result =>
|
||||
{
|
||||
|
|
@ -110,35 +269,38 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
Assert.Collection(result.Selectors,
|
||||
selector => Assert.Equal("Products/Manage/Index", selector.AttributeRouteModel.Template),
|
||||
selector => Assert.Equal("Products/Manage", selector.AttributeRouteModel.Template));
|
||||
Assert.Collection(result.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/Index", kvp.Value);
|
||||
});
|
||||
Assert.Collection(
|
||||
result.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/Index", kvp.Value);
|
||||
});
|
||||
},
|
||||
result =>
|
||||
{
|
||||
Assert.Equal("/Features/Products/Files/Manage/Edit.cshtml", result.RelativePath);
|
||||
Assert.Equal("/Manage/Edit", result.ViewEnginePath);
|
||||
Assert.Collection(result.Selectors,
|
||||
Assert.Collection(
|
||||
result.Selectors,
|
||||
selector => Assert.Equal("Products/Manage/Edit/{id}", selector.AttributeRouteModel.Template));
|
||||
Assert.Collection(result.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/Edit", kvp.Value);
|
||||
});
|
||||
Assert.Collection(
|
||||
result.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/Edit", kvp.Value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -146,32 +308,36 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
public void OnProvidersExecuting_DoesNotAddsModelsForAreaPages_IfFeatureIsDisabled()
|
||||
{
|
||||
// Arrange
|
||||
var descriptors = new[]
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
{
|
||||
GetDescriptor("/Pages/About.cshtml"),
|
||||
GetDescriptor("/Areas/Accounts/Pages/Home.cshtml"),
|
||||
};
|
||||
var options = new RazorPagesOptions { AllowAreas = false };
|
||||
var provider = new TestCompiledPageRouteModelProvider(descriptors, options);
|
||||
CreateVersion_2_0_Descriptor("/Pages/About.cshtml"),
|
||||
CreateVersion_2_0_Descriptor("/Areas/Accounts/Pages/Home.cshtml"),
|
||||
});
|
||||
|
||||
PagesOptions.AllowAreas = false;
|
||||
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
Provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.RouteModels,
|
||||
Assert.Collection(
|
||||
context.RouteModels,
|
||||
result =>
|
||||
{
|
||||
Assert.Equal("/Pages/About.cshtml", result.RelativePath);
|
||||
Assert.Equal("/About", result.ViewEnginePath);
|
||||
Assert.Collection(result.Selectors,
|
||||
Assert.Collection(
|
||||
result.Selectors,
|
||||
selector => Assert.Equal("About", selector.AttributeRouteModel.Template));
|
||||
Assert.Collection(result.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/About", kvp.Value);
|
||||
});
|
||||
Assert.Collection(
|
||||
result.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/About", kvp.Value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -179,56 +345,59 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
public void OnProvidersExecuting_DoesNotAddAreaAndNonAreaRoutesForAPage()
|
||||
{
|
||||
// Arrange
|
||||
var descriptors = new[]
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
{
|
||||
GetDescriptor("/Areas/Accounts/Manage/Home.cshtml"),
|
||||
GetDescriptor("/Areas/About.cshtml"),
|
||||
GetDescriptor("/Contact.cshtml"),
|
||||
};
|
||||
var options = new RazorPagesOptions
|
||||
{
|
||||
AllowAreas = true,
|
||||
AreaRootDirectory = "/Areas",
|
||||
RootDirectory = "/",
|
||||
};
|
||||
var provider = new TestCompiledPageRouteModelProvider(descriptors, options);
|
||||
CreateVersion_2_0_Descriptor("/Areas/Accounts/Manage/Home.cshtml"),
|
||||
CreateVersion_2_0_Descriptor("/Areas/About.cshtml"),
|
||||
CreateVersion_2_0_Descriptor("/Contact.cshtml"),
|
||||
});
|
||||
|
||||
PagesOptions.AllowAreas = true;
|
||||
PagesOptions.AreaRootDirectory = "/Areas";
|
||||
PagesOptions.RootDirectory = "/";
|
||||
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
Provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.RouteModels,
|
||||
Assert.Collection(
|
||||
context.RouteModels,
|
||||
result =>
|
||||
{
|
||||
Assert.Equal("/Areas/Accounts/Manage/Home.cshtml", result.RelativePath);
|
||||
Assert.Equal("/Manage/Home", result.ViewEnginePath);
|
||||
Assert.Collection(result.Selectors,
|
||||
Assert.Collection(
|
||||
result.Selectors,
|
||||
selector => Assert.Equal("Accounts/Manage/Home", selector.AttributeRouteModel.Template));
|
||||
Assert.Collection(result.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.Equal("Accounts", kvp.Value);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/Manage/Home", kvp.Value);
|
||||
});
|
||||
Assert.Collection(
|
||||
result.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.Equal("Accounts", kvp.Value);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/Manage/Home", kvp.Value);
|
||||
});
|
||||
},
|
||||
result =>
|
||||
{
|
||||
Assert.Equal("/Contact.cshtml", result.RelativePath);
|
||||
Assert.Equal("/Contact", result.ViewEnginePath);
|
||||
Assert.Collection(result.Selectors,
|
||||
Assert.Collection(
|
||||
result.Selectors,
|
||||
selector => Assert.Equal("Contact", selector.AttributeRouteModel.Template));
|
||||
Assert.Collection(result.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/Contact", kvp.Value);
|
||||
});
|
||||
Assert.Collection(
|
||||
result.RouteValues.OrderBy(k => k.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("page", kvp.Key);
|
||||
Assert.Equal("/Contact", kvp.Value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -236,24 +405,28 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
public void OnProvidersExecuting_AddsMultipleSelectorsForIndexPage_WithIndexAtRoot()
|
||||
{
|
||||
// Arrange
|
||||
var descriptors = new[]
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
{
|
||||
GetDescriptor("/Pages/Index.cshtml"),
|
||||
GetDescriptor("/Pages/Admin/Index.cshtml", "some-template"),
|
||||
};
|
||||
var provider = new TestCompiledPageRouteModelProvider(descriptors, new RazorPagesOptions { RootDirectory = "/" });
|
||||
CreateVersion_2_0_Descriptor("/Pages/Index.cshtml"),
|
||||
CreateVersion_2_0_Descriptor("/Pages/Admin/Index.cshtml", "some-template"),
|
||||
});
|
||||
|
||||
PagesOptions.RootDirectory = "/";
|
||||
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
Provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.RouteModels,
|
||||
Assert.Collection(
|
||||
context.RouteModels,
|
||||
result =>
|
||||
{
|
||||
Assert.Equal("/Pages/Index.cshtml", result.RelativePath);
|
||||
Assert.Equal("/Pages/Index", result.ViewEnginePath);
|
||||
Assert.Collection(result.Selectors,
|
||||
Assert.Collection(
|
||||
result.Selectors,
|
||||
selector => Assert.Equal("Pages/Index", selector.AttributeRouteModel.Template),
|
||||
selector => Assert.Equal("Pages", selector.AttributeRouteModel.Template));
|
||||
},
|
||||
|
|
@ -261,7 +434,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
{
|
||||
Assert.Equal("/Pages/Admin/Index.cshtml", result.RelativePath);
|
||||
Assert.Equal("/Pages/Admin/Index", result.ViewEnginePath);
|
||||
Assert.Collection(result.Selectors,
|
||||
Assert.Collection(
|
||||
result.Selectors,
|
||||
selector => Assert.Equal("Pages/Admin/Index/some-template", selector.AttributeRouteModel.Template),
|
||||
selector => Assert.Equal("Pages/Admin/some-template", selector.AttributeRouteModel.Template));
|
||||
});
|
||||
|
|
@ -271,24 +445,26 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
public void OnProvidersExecuting_AddsMultipleSelectorsForIndexPage()
|
||||
{
|
||||
// Arrange
|
||||
var descriptors = new[]
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
{
|
||||
GetDescriptor("/Pages/Index.cshtml"),
|
||||
GetDescriptor("/Pages/Admin/Index.cshtml", "some-template"),
|
||||
};
|
||||
var provider = new TestCompiledPageRouteModelProvider(descriptors, new RazorPagesOptions());
|
||||
CreateVersion_2_0_Descriptor("/Pages/Index.cshtml"),
|
||||
CreateVersion_2_0_Descriptor("/Pages/Admin/Index.cshtml", "some-template"),
|
||||
});
|
||||
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
Provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(context.RouteModels,
|
||||
Assert.Collection(
|
||||
context.RouteModels,
|
||||
result =>
|
||||
{
|
||||
Assert.Equal("/Pages/Index.cshtml", result.RelativePath);
|
||||
Assert.Equal("/Index", result.ViewEnginePath);
|
||||
Assert.Collection(result.Selectors,
|
||||
Assert.Collection(
|
||||
result.Selectors,
|
||||
selector => Assert.Equal("Index", selector.AttributeRouteModel.Template),
|
||||
selector => Assert.Equal("", selector.AttributeRouteModel.Template));
|
||||
},
|
||||
|
|
@ -296,7 +472,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
{
|
||||
Assert.Equal("/Pages/Admin/Index.cshtml", result.RelativePath);
|
||||
Assert.Equal("/Admin/Index", result.ViewEnginePath);
|
||||
Assert.Collection(result.Selectors,
|
||||
Assert.Collection(
|
||||
result.Selectors,
|
||||
selector => Assert.Equal("Admin/Index/some-template", selector.AttributeRouteModel.Template),
|
||||
selector => Assert.Equal("Admin/some-template", selector.AttributeRouteModel.Template));
|
||||
});
|
||||
|
|
@ -306,40 +483,57 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
public void OnProvidersExecuting_ThrowsIfRouteTemplateHasOverridePattern()
|
||||
{
|
||||
// Arrange
|
||||
var descriptors = new[]
|
||||
Provider.Descriptors.AddRange(new[]
|
||||
{
|
||||
GetDescriptor("/Pages/Index.cshtml"),
|
||||
GetDescriptor("/Pages/Home.cshtml", "/some-prefix"),
|
||||
};
|
||||
var provider = new TestCompiledPageRouteModelProvider(descriptors, new RazorPagesOptions());
|
||||
CreateVersion_2_0_Descriptor("/Pages/Index.cshtml"),
|
||||
CreateVersion_2_0_Descriptor("/Pages/Home.cshtml", "/some-prefix"),
|
||||
});
|
||||
|
||||
var context = new PageRouteModelProviderContext();
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => provider.OnProvidersExecuting(context));
|
||||
Assert.Equal("The route for the page at '/Pages/Home.cshtml' cannot start with / or ~/. Pages do not support overriding the file path of the page.",
|
||||
ex.Message);
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => Provider.OnProvidersExecuting(context));
|
||||
Assert.Equal(
|
||||
"The route for the page at '/Pages/Home.cshtml' cannot start with / or ~/. Pages do not support overriding the file path of the page.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
private static CompiledViewDescriptor GetDescriptor(string path, string prefix = "")
|
||||
private static CompiledViewDescriptor CreateVersion_2_0_Descriptor(string path, string routeTemplate = "")
|
||||
{
|
||||
return new CompiledViewDescriptor
|
||||
{
|
||||
RelativePath = path,
|
||||
ViewAttribute = new RazorPageAttribute(path, typeof(object), prefix),
|
||||
ViewAttribute = new RazorPageAttribute(path, typeof(object), routeTemplate),
|
||||
};
|
||||
}
|
||||
|
||||
private static CompiledViewDescriptor CreateVersion_2_1_Descriptor(
|
||||
string path,
|
||||
string routeTemplate = "",
|
||||
object[] metadata = null)
|
||||
{
|
||||
return new CompiledViewDescriptor
|
||||
{
|
||||
RelativePath = path,
|
||||
ViewAttribute = new RazorPageAttribute(path, typeof(object), routeTemplate),
|
||||
Item = new TestRazorCompiledItem(typeof(object), "mvc.1.0.razor-page", path, metadata ?? Array.Empty<object>()),
|
||||
};
|
||||
}
|
||||
|
||||
public class TestCompiledPageRouteModelProvider : CompiledPageRouteModelProvider
|
||||
{
|
||||
private readonly IEnumerable<CompiledViewDescriptor> _descriptors;
|
||||
|
||||
public TestCompiledPageRouteModelProvider(IEnumerable<CompiledViewDescriptor> descriptors, RazorPagesOptions options)
|
||||
: base(new ApplicationPartManager(), Options.Create(options), NullLoggerFactory.Instance)
|
||||
public TestCompiledPageRouteModelProvider(
|
||||
ApplicationPartManager partManager,
|
||||
IOptions<RazorPagesOptions> options,
|
||||
RazorTemplateEngine templateEngine,
|
||||
ILoggerFactory loggerFactory)
|
||||
: base(partManager, options, templateEngine, loggerFactory)
|
||||
{
|
||||
_descriptors = descriptors;
|
||||
}
|
||||
|
||||
protected override IEnumerable<CompiledViewDescriptor> GetViewDescriptors(ApplicationPartManager applicationManager) => _descriptors;
|
||||
public List<CompiledViewDescriptor> Descriptors { get; } = new List<CompiledViewDescriptor>();
|
||||
|
||||
protected override IEnumerable<CompiledViewDescriptor> GetViewDescriptors(ApplicationPartManager applicationManager) => Descriptors;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Html.Abstractions" Version="$(MicrosoftAspNetCoreHtmlAbstractionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Runtime" Version="$(MicrosoftAspNetCoreRazorRuntimePackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Abstractions" Version="$(MicrosoftExtensionsFileProvidersAbstractionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPackageVersion)" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
// 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.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Hosting
|
||||
{
|
||||
public class TestRazorCompiledItem : RazorCompiledItem
|
||||
{
|
||||
public TestRazorCompiledItem(Type type, string kind, string identifier, object[] metadata)
|
||||
{
|
||||
Type = type;
|
||||
Kind = kind;
|
||||
Identifier = identifier;
|
||||
Metadata = metadata;
|
||||
}
|
||||
|
||||
public override string Identifier { get; }
|
||||
|
||||
public override string Kind { get; }
|
||||
|
||||
public override IReadOnlyList<object> Metadata { get; }
|
||||
|
||||
public override Type Type { get; }
|
||||
|
||||
public static string GetChecksum(string content)
|
||||
{
|
||||
byte[] bytes;
|
||||
using (var sha = SHA1.Create())
|
||||
{
|
||||
bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(content));
|
||||
}
|
||||
|
||||
var result = new StringBuilder(bytes.Length);
|
||||
for (var i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
// The x2 format means lowercase hex, where each byte is a 2-character string.
|
||||
result.Append(bytes[i].ToString("x2"));
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#pragma checksum "D:\k\Mvc\test\WebSites\RazorBuildWebSite\Pages\Precompilation\Page.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "f3a9fa49018f90b3470f6c0e4f475d1d8c9cd456"
|
||||
// <auto-generated/>
|
||||
#pragma warning disable 1591
|
||||
[assembly: global::Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.RazorPageAttribute(@"/Pages/Precompilation/Page.cshtml", typeof(RazorBuildWebSite.Pages.Precompilation._Pages_Precompilation_Page), null)]
|
||||
namespace RazorBuildWebSite.Pages.Precompilation
|
||||
{
|
||||
#line hidden
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
public class _Pages_Precompilation_Page : global::Microsoft.AspNetCore.Mvc.RazorPages.Page
|
||||
{
|
||||
#pragma warning disable 1998
|
||||
public async override global::System.Threading.Tasks.Task ExecuteAsync()
|
||||
{
|
||||
BeginContext(26, 52, true);
|
||||
WriteLiteral("\r\nHello from buildtime-compiled precompilation page!\r\n");
|
||||
EndContext();
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
|
||||
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
|
||||
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
|
||||
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
|
||||
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<Page_Model> Html { get; private set; }
|
||||
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<Page_Model> ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<Page_Model>)PageContext?.ViewData;
|
||||
public Page_Model Model => ViewData.Model;
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace RazorBuildWebSite.Pages.Precompilation
|
||||
{
|
||||
public class Page_Model : PageModel
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(StandardTestWebsiteTfms)</TargetFrameworks>
|
||||
<DefineConstants>$(DefineConstants)</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
#pragma checksum "D:\k\Mvc\test\WebSites\RazorBuildWebSite\Views\Precompilation\View.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "a09a0106df2e63aecf6fc6ddf30df39b489d9783"
|
||||
// <auto-generated/>
|
||||
#pragma warning disable 1591
|
||||
[assembly: global::Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorViewAttribute(@"/Views/Precompilation/View.cshtml", typeof(RazorBuildWebSite.Views.Precompilation._Views_Precompilation_View))]
|
||||
namespace RazorBuildWebSite.Views.Precompilation
|
||||
{
|
||||
#line hidden
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
public class _Views_Precompilation_View : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
|
||||
{
|
||||
#pragma warning disable 1998
|
||||
public async override global::System.Threading.Tasks.Task ExecuteAsync()
|
||||
{
|
||||
BeginContext(0, 48, true);
|
||||
WriteLiteral("Hello from buildtime-compiled precompilation view!");
|
||||
EndContext();
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
|
||||
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
|
||||
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
|
||||
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
|
||||
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
|
||||
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; }
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace RazorBuildWebSite.Views.Pages.Rzc
|
||||
{
|
||||
class Page
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace RazorBuildWebSite.Views.Pages.Rzc
|
||||
{
|
||||
public class Page_Model : PageModel
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(StandardTestWebsiteTfms)</TargetFrameworks>
|
||||
<DefineConstants>$(DefineConstants)</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace RazorBuildWebSite.Views.Views.Rzc
|
||||
{
|
||||
class Index
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace RazorBuildWebSite.Controllers
|
||||
{
|
||||
public class PrecompilationController : Controller
|
||||
{
|
||||
public new ActionResult View()
|
||||
{
|
||||
return base.View();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace RazorBuildWebSite.Controllers
|
||||
{
|
||||
public class RzcController : Controller
|
||||
{
|
||||
public new ActionResult View()
|
||||
{
|
||||
return base.View();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
@page
|
||||
@model Page_Model
|
||||
|
||||
Hello from runtime-compiled precompilation page!
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace RazorBuildWebSite.Pages.Precompilation
|
||||
{
|
||||
public class Page_Model : PageModel
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
@page
|
||||
@model Page_Model
|
||||
|
||||
Hello from runtime-compiled rzc page!
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace RazorBuildWebSite.Pages.Rzc
|
||||
{
|
||||
public class Page_Model : PageModel
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
@namespace RazorBuildWebSite.Pages
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(StandardTestWebsiteTfms)</TargetFrameworks>
|
||||
<DefineConstants>$(DefineConstants)</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj" />
|
||||
|
||||
<!-- Faking like we had Razor-on-Build (Rzc) and MvcPrecompilation -->
|
||||
<ProjectReference Include="..\RazorBuildWebSite.PrecompiledViews\RazorBuildWebSite.PrecompiledViews.csproj" />
|
||||
<ProjectReference Include="..\RazorBuildWebSite.Views\RazorBuildWebSite.Views.csproj" />
|
||||
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(MicrosoftAspNetCoreDiagnosticsPackageVersion)" />
|
||||
|
||||
<!--
|
||||
Referencing here so you can easily regenerate the C# from Razor.
|
||||
Just do `dotnet build /t:RazorGenerate /p:TargetFramework=netcoreapp2.0` and look in obj/Debug/netcoreapp2.0/Razor
|
||||
-->
|
||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="$(MicrosoftAspNetCoreRazorDesignPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace RazorBuildWebSite
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddMvc();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvcWithDefaultRoute();
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseStartup<Startup>()
|
||||
.UseKestrel()
|
||||
.UseIISIntegration()
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
Hello from runtime-compiled precompilation view!
|
||||
|
|
@ -0,0 +1 @@
|
|||
Hello from runtime-compiled rzc view!
|
||||
|
|
@ -0,0 +1 @@
|
|||
@namespace RazorBuildWebSite.Views
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
RazorBuildWebSite
|
||||
===
|
||||
|
||||
This web site tests how the Razor view engine interacts with pre-built Razor assemblies.
|
||||
Loading…
Reference in New Issue