зеркало из https://github.com/aspnet/Mvc.git
Changes to make EnableInstrumentation conditionally enabled
This commit is contained in:
Родитель
5d32d224f4
Коммит
12477c9f52
15
Mvc.sln
15
Mvc.sln
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 14
|
# Visual Studio 14
|
||||||
VisualStudioVersion = 14.0.22013.1
|
VisualStudioVersion = 14.0.22130.0
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
||||||
EndProject
|
EndProject
|
||||||
|
@ -86,6 +86,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ReflectedModelWebSite", "te
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "FilesWebSite", "test\WebSites\FilesWebSite\FilesWebSite.kproj", "{0EF9860B-10D7-452F-B0F4-A405B88BEBB3}"
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "FilesWebSite", "test\WebSites\FilesWebSite\FilesWebSite.kproj", "{0EF9860B-10D7-452F-B0F4-A405B88BEBB3}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RazorInstrumentationWebSite", "test\WebSites\RazorInstrumentationWebSite\RazorInstrumentationWebSite.kproj", "{2B2B9876-903C-4065-8D62-2EE832BBA106}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -446,6 +448,16 @@ Global
|
||||||
{C2EF54F8-8886-4260-A322-44F76245F95D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
{C2EF54F8-8886-4260-A322-44F76245F95D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
{C2EF54F8-8886-4260-A322-44F76245F95D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
{C2EF54F8-8886-4260-A322-44F76245F95D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
{C2EF54F8-8886-4260-A322-44F76245F95D}.Release|x86.ActiveCfg = Release|Any CPU
|
{C2EF54F8-8886-4260-A322-44F76245F95D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{2B2B9876-903C-4065-8D62-2EE832BBA106}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{2B2B9876-903C-4065-8D62-2EE832BBA106}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{2B2B9876-903C-4065-8D62-2EE832BBA106}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
{2B2B9876-903C-4065-8D62-2EE832BBA106}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||||
|
{2B2B9876-903C-4065-8D62-2EE832BBA106}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{2B2B9876-903C-4065-8D62-2EE832BBA106}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{2B2B9876-903C-4065-8D62-2EE832BBA106}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{2B2B9876-903C-4065-8D62-2EE832BBA106}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{2B2B9876-903C-4065-8D62-2EE832BBA106}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{2B2B9876-903C-4065-8D62-2EE832BBA106}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
@ -487,5 +499,6 @@ Global
|
||||||
{0EF9860B-10D7-452F-B0F4-A405B88BEBB3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
{0EF9860B-10D7-452F-B0F4-A405B88BEBB3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
{61061528-071E-424E-965A-07BCC2F02672} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
{61061528-071E-424E-965A-07BCC2F02672} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
{C2EF54F8-8886-4260-A322-44F76245F95D} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
{C2EF54F8-8886-4260-A322-44F76245F95D} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
|
{2B2B9876-903C-4065-8D62-2EE832BBA106} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
@ -64,25 +64,30 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompilationResult GetOrAdd(RelativeFileInfo fileInfo, Func<CompilationResult> compile)
|
public CompilationResult GetOrAdd([NotNull] RelativeFileInfo fileInfo,
|
||||||
|
bool enableInstrumentation,
|
||||||
|
[NotNull] Func<CompilationResult> compile)
|
||||||
{
|
{
|
||||||
CompilerCacheEntry cacheEntry;
|
CompilerCacheEntry cacheEntry;
|
||||||
if (!_cache.TryGetValue(fileInfo.RelativePath, out cacheEntry))
|
if (!_cache.TryGetValue(fileInfo.RelativePath, out cacheEntry))
|
||||||
{
|
{
|
||||||
return OnCacheMiss(fileInfo, compile);
|
return OnCacheMiss(fileInfo, enableInstrumentation, compile);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (cacheEntry.Length != fileInfo.FileInfo.Length)
|
if ((cacheEntry.Length != fileInfo.FileInfo.Length) ||
|
||||||
|
(enableInstrumentation && !cacheEntry.IsInstrumented))
|
||||||
{
|
{
|
||||||
// it's not a match, recompile
|
// Recompile if
|
||||||
return OnCacheMiss(fileInfo, compile);
|
// (a) If the file lengths differ
|
||||||
|
// (b) If the compiled type is not instrumented but we require it to be instrumented.
|
||||||
|
return OnCacheMiss(fileInfo, enableInstrumentation, compile);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cacheEntry.LastModified == fileInfo.FileInfo.LastModified)
|
if (cacheEntry.LastModified == fileInfo.FileInfo.LastModified)
|
||||||
{
|
{
|
||||||
// Match, not update needed
|
// Match, not update needed
|
||||||
return CompilationResult.Successful(cacheEntry.ViewType);
|
return CompilationResult.Successful(cacheEntry.CompiledType);
|
||||||
}
|
}
|
||||||
|
|
||||||
var hash = RazorFileHash.GetHash(fileInfo.FileInfo);
|
var hash = RazorFileHash.GetHash(fileInfo.FileInfo);
|
||||||
|
@ -92,20 +97,24 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
string.Equals(cacheEntry.Hash, hash, StringComparison.Ordinal))
|
string.Equals(cacheEntry.Hash, hash, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
// Cache hit, but we need to update the entry
|
// Cache hit, but we need to update the entry
|
||||||
return OnCacheMiss(fileInfo, () => CompilationResult.Successful(cacheEntry.ViewType));
|
return OnCacheMiss(fileInfo,
|
||||||
|
enableInstrumentation,
|
||||||
|
() => CompilationResult.Successful(cacheEntry.CompiledType));
|
||||||
}
|
}
|
||||||
|
|
||||||
// it's not a match, recompile
|
// it's not a match, recompile
|
||||||
return OnCacheMiss(fileInfo, compile);
|
return OnCacheMiss(fileInfo, enableInstrumentation, compile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompilationResult OnCacheMiss(RelativeFileInfo file, Func<CompilationResult> compile)
|
private CompilationResult OnCacheMiss(RelativeFileInfo file,
|
||||||
|
bool isInstrumented,
|
||||||
|
Func<CompilationResult> compile)
|
||||||
{
|
{
|
||||||
var result = compile();
|
var result = compile();
|
||||||
|
|
||||||
var cacheEntry = new CompilerCacheEntry(file, result.CompiledType);
|
var cacheEntry = new CompilerCacheEntry(file, result.CompiledType, isInstrumented);
|
||||||
_cache.AddOrUpdate(file.RelativePath, cacheEntry, (a, b) => cacheEntry);
|
_cache[file.RelativePath] = cacheEntry;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,35 +5,74 @@ using System;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Razor
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An entry in <see cref="CompilerCache"/> that contain metadata about precompiled and dynamically compiled file.
|
||||||
|
/// </summary>
|
||||||
public class CompilerCacheEntry
|
public class CompilerCacheEntry
|
||||||
{
|
{
|
||||||
public CompilerCacheEntry([NotNull] RazorFileInfo info, [NotNull] Type viewType)
|
/// <summary>
|
||||||
|
/// Initializes a new instance of <see cref="CompilerCacheEntry"/> for a file that was precompiled.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">Metadata about the precompiled file.</param>
|
||||||
|
/// <param name="compiledType">The compiled <see cref="Type"/>.</param>
|
||||||
|
public CompilerCacheEntry([NotNull] RazorFileInfo info, [NotNull] Type compiledType)
|
||||||
{
|
{
|
||||||
ViewType = viewType;
|
CompiledType = compiledType;
|
||||||
RelativePath = info.RelativePath;
|
RelativePath = info.RelativePath;
|
||||||
Length = info.Length;
|
Length = info.Length;
|
||||||
LastModified = info.LastModified;
|
LastModified = info.LastModified;
|
||||||
Hash = info.Hash;
|
Hash = info.Hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompilerCacheEntry([NotNull] RelativeFileInfo info, [NotNull] Type viewType)
|
/// <summary>
|
||||||
|
/// Initializes a new instance of <see cref="CompilerCacheEntry"/> for a file that was dynamically compiled.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">Metadata about the file that was compiled.</param>
|
||||||
|
/// <param name="compiledType">The compiled <see cref="Type"/>.</param>
|
||||||
|
/// <param name="isInstrumented">Flag that indicates that the file was generated with instrumentation
|
||||||
|
/// enabled.</param>
|
||||||
|
public CompilerCacheEntry([NotNull] RelativeFileInfo info, [NotNull] Type compiledType, bool isInstrumented)
|
||||||
{
|
{
|
||||||
ViewType = viewType;
|
CompiledType = compiledType;
|
||||||
RelativePath = info.RelativePath;
|
RelativePath = info.RelativePath;
|
||||||
Length = info.FileInfo.Length;
|
Length = info.FileInfo.Length;
|
||||||
LastModified = info.FileInfo.LastModified;
|
LastModified = info.FileInfo.LastModified;
|
||||||
|
IsInstrumented = isInstrumented;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Type ViewType { get; set; }
|
/// <summary>
|
||||||
public string RelativePath { get; set; }
|
/// Gets the <see cref="Type"/> produced as a result of compilation.
|
||||||
public long Length { get; set; }
|
/// </summary>
|
||||||
public DateTime LastModified { get; set; }
|
public Type CompiledType { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The file hash, should only be available for pre compiled files.
|
/// Gets the path of the compiled file relative to the root of the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Hash { get; set; }
|
public string RelativePath { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the size of file (in bytes) on disk.
|
||||||
|
/// </summary>
|
||||||
|
public long Length { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the last modified <see cref="DateTime"/> for the file that was compiled at the time of compilation.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime LastModified { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the file hash, should only be available for pre compiled files.
|
||||||
|
/// </summary>
|
||||||
|
public string Hash { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a flag that indicates if the file is precompiled.
|
||||||
|
/// </summary>
|
||||||
public bool IsPreCompiled { get { return Hash != null; } }
|
public bool IsPreCompiled { get { return Hash != null; } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a flag that indiciates if the page execution in <see cref="CompiledType"/> is instrumeted.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsInstrumented { get; private set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
/// Creates a <see cref="IRazorPage"/> for the specified path.
|
/// Creates a <see cref="IRazorPage"/> for the specified path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="relativePath">The path to locate the page.</param>
|
/// <param name="relativePath">The path to locate the page.</param>
|
||||||
|
/// <param name="enableInstrumentation">Indicates that execution of the page should be instrumented.</param>
|
||||||
/// <returns>The IRazorPage instance if it exists, null otherwise.</returns>
|
/// <returns>The IRazorPage instance if it exists, null otherwise.</returns>
|
||||||
IRazorPage CreateInstance(string relativePath);
|
IRazorPage CreateInstance(string relativePath, bool enableInstrumentation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
|
using Microsoft.AspNet.PageExecutionInstrumentation;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Razor
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
|
@ -17,6 +18,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="razorPage">The <see cref="IRazorPage"/> instance to execute.</param>
|
/// <param name="razorPage">The <see cref="IRazorPage"/> instance to execute.</param>
|
||||||
/// <param name="isPartial">Determines if the view is to be executed as a partial.</param>
|
/// <param name="isPartial">Determines if the view is to be executed as a partial.</param>
|
||||||
void Contextualize(IRazorPage razorPage, bool isPartial);
|
void Contextualize(IRazorPage razorPage,
|
||||||
|
bool isPartial,
|
||||||
|
IPageExecutionListenerFeature pageExecutionListenerFeature);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,7 +15,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
/// that are applicable to the specified view.
|
/// that are applicable to the specified view.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path of the page to locate ViewStart files for.</param>
|
/// <param name="path">The path of the page to locate ViewStart files for.</param>
|
||||||
|
/// <param name="enableInstrumentation">Indicates that execution of the page should be instrumented.</param>
|
||||||
/// <returns>A sequence of <see cref="IRazorPage"/> that represent ViewStart.</returns>
|
/// <returns>A sequence of <see cref="IRazorPage"/> that represent ViewStart.</returns>
|
||||||
IEnumerable<IRazorPage> GetViewStartPages(string path);
|
IEnumerable<IRazorPage> GetViewStartPages(string path, bool enableInstrumentation);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,8 +3,19 @@
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Razor
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies the contracts for a service that compiles Razor files.
|
||||||
|
/// </summary>
|
||||||
public interface IRazorCompilationService
|
public interface IRazorCompilationService
|
||||||
{
|
{
|
||||||
CompilationResult Compile(RelativeFileInfo fileInfo);
|
/// <summary>
|
||||||
|
/// Compiles the razor file located at <paramref name="fileInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileInfo">A <see cref="RelativeFileInfo"/> instance that represents the file to compile.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="isInstrumented">Indicates that the page should be instrumented.</param>
|
||||||
|
/// <returns>A <see cref="CompilationResult"/> that represents the results of parsing and compiling the file.
|
||||||
|
/// </returns>
|
||||||
|
CompilationResult Compile(RelativeFileInfo fileInfo, bool isInstrumented);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,14 @@ using Microsoft.AspNet.Razor;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Razor
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default implementation of <see cref="IRazorCompilationService"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This class must be registered as a singleton service for the caching to work.
|
||||||
|
/// </remarks>
|
||||||
public class RazorCompilationService : IRazorCompilationService
|
public class RazorCompilationService : IRazorCompilationService
|
||||||
{
|
{
|
||||||
// This class must be registered as a singleton service for the caching to work.
|
|
||||||
private readonly CompilerCache _cache;
|
private readonly CompilerCache _cache;
|
||||||
private readonly ICompilationService _baseCompilationService;
|
private readonly ICompilationService _baseCompilationService;
|
||||||
private readonly IMvcRazorHost _razorHost;
|
private readonly IMvcRazorHost _razorHost;
|
||||||
|
@ -22,13 +27,16 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
_cache = new CompilerCache(_controllerAssemblyProvider.CandidateAssemblies);
|
_cache = new CompilerCache(_controllerAssemblyProvider.CandidateAssemblies);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompilationResult Compile([NotNull] RelativeFileInfo file)
|
/// <inheritdoc />
|
||||||
|
public CompilationResult Compile([NotNull] RelativeFileInfo file, bool isInstrumented)
|
||||||
{
|
{
|
||||||
return _cache.GetOrAdd(file, () => CompileCore(file));
|
return _cache.GetOrAdd(file, isInstrumented, () => CompileCore(file, isInstrumented));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal CompilationResult CompileCore(RelativeFileInfo file)
|
internal CompilationResult CompileCore(RelativeFileInfo file, bool isInstrumented)
|
||||||
{
|
{
|
||||||
|
_razorHost.EnableInstrumentation = isInstrumented;
|
||||||
|
|
||||||
GeneratorResults results;
|
GeneratorResults results;
|
||||||
using (var inputStream = file.FileInfo.CreateReadStream())
|
using (var inputStream = file.FileInfo.CreateReadStream())
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.PageExecutionInstrumentation;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Razor
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
|
@ -15,6 +17,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
private readonly IRazorPageFactory _pageFactory;
|
private readonly IRazorPageFactory _pageFactory;
|
||||||
private readonly IRazorPageActivator _pageActivator;
|
private readonly IRazorPageActivator _pageActivator;
|
||||||
private readonly IViewStartProvider _viewStartProvider;
|
private readonly IViewStartProvider _viewStartProvider;
|
||||||
|
private IPageExecutionListenerFeature _pageExecutionFeature;
|
||||||
private IRazorPage _razorPage;
|
private IRazorPage _razorPage;
|
||||||
private bool _isPartial;
|
private bool _isPartial;
|
||||||
|
|
||||||
|
@ -34,11 +37,19 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
_viewStartProvider = viewStartProvider;
|
_viewStartProvider = viewStartProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool EnableInstrumentation
|
||||||
|
{
|
||||||
|
get { return _pageExecutionFeature != null; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual void Contextualize(IRazorPage razorPage, bool isPartial)
|
public virtual void Contextualize([NotNull] IRazorPage razorPage,
|
||||||
|
bool isPartial,
|
||||||
|
IPageExecutionListenerFeature pageExecutionListener)
|
||||||
{
|
{
|
||||||
_razorPage = razorPage;
|
_razorPage = razorPage;
|
||||||
_isPartial = isPartial;
|
_isPartial = isPartial;
|
||||||
|
_pageExecutionFeature = pageExecutionListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -61,45 +72,66 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<RazorTextWriter> RenderPageAsync(IRazorPage page,
|
private async Task<IBufferedTextWriter> RenderPageAsync(IRazorPage page,
|
||||||
ViewContext context,
|
ViewContext context,
|
||||||
bool executeViewStart)
|
bool executeViewStart)
|
||||||
{
|
{
|
||||||
using (var bufferedWriter = new RazorTextWriter(context.Writer, context.Writer.Encoding))
|
var razorTextWriter = new RazorTextWriter(context.Writer, context.Writer.Encoding);
|
||||||
|
TextWriter writer = razorTextWriter;
|
||||||
|
IBufferedTextWriter bufferedWriter = razorTextWriter;
|
||||||
|
|
||||||
|
if (EnableInstrumentation)
|
||||||
{
|
{
|
||||||
// The writer for the body is passed through the ViewContext, allowing things like HtmlHelpers
|
writer = _pageExecutionFeature.DecorateWriter(razorTextWriter);
|
||||||
// and ViewComponents to reference it.
|
bufferedWriter = writer as IBufferedTextWriter;
|
||||||
var oldWriter = context.Writer;
|
if (bufferedWriter == null)
|
||||||
context.Writer = bufferedWriter;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (executeViewStart)
|
var message = Resources.FormatInstrumentation_WriterMustBeBufferedTextWriter(
|
||||||
{
|
nameof(TextWriter),
|
||||||
// Execute view starts using the same context + writer as the page to render.
|
_pageExecutionFeature.GetType().FullName,
|
||||||
await RenderViewStartAsync(context);
|
typeof(IBufferedTextWriter).FullName);
|
||||||
}
|
throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await RenderPageCoreAsync(page, context);
|
// The writer for the body is passed through the ViewContext, allowing things like HtmlHelpers
|
||||||
return bufferedWriter;
|
// and ViewComponents to reference it.
|
||||||
}
|
var oldWriter = context.Writer;
|
||||||
finally
|
context.Writer = writer;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (executeViewStart)
|
||||||
{
|
{
|
||||||
context.Writer = oldWriter;
|
// Execute view starts using the same context + writer as the page to render.
|
||||||
|
await RenderViewStartAsync(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await RenderPageCoreAsync(page, context);
|
||||||
|
return bufferedWriter;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
context.Writer = oldWriter;
|
||||||
|
writer.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RenderPageCoreAsync(IRazorPage page, ViewContext context)
|
private async Task RenderPageCoreAsync(IRazorPage page, ViewContext context)
|
||||||
{
|
{
|
||||||
page.ViewContext = context;
|
page.ViewContext = context;
|
||||||
|
if (EnableInstrumentation)
|
||||||
|
{
|
||||||
|
page.PageExecutionContext = _pageExecutionFeature.GetContext(page.Path, context.Writer);
|
||||||
|
}
|
||||||
|
|
||||||
_pageActivator.Activate(page, context);
|
_pageActivator.Activate(page, context);
|
||||||
await page.ExecuteAsync();
|
await page.ExecuteAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RenderViewStartAsync(ViewContext context)
|
private async Task RenderViewStartAsync(ViewContext context)
|
||||||
{
|
{
|
||||||
var viewStarts = _viewStartProvider.GetViewStartPages(_razorPage.Path);
|
var viewStarts = _viewStartProvider.GetViewStartPages(_razorPage.Path, EnableInstrumentation);
|
||||||
|
|
||||||
foreach (var viewStart in viewStarts)
|
foreach (var viewStart in viewStarts)
|
||||||
{
|
{
|
||||||
|
@ -111,7 +143,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RenderLayoutAsync(ViewContext context,
|
private async Task RenderLayoutAsync(ViewContext context,
|
||||||
RazorTextWriter bodyWriter)
|
IBufferedTextWriter bodyWriter)
|
||||||
{
|
{
|
||||||
// A layout page can specify another layout page. We'll need to continue
|
// A layout page can specify another layout page. We'll need to continue
|
||||||
// looking for layout pages until they're no longer specified.
|
// looking for layout pages until they're no longer specified.
|
||||||
|
@ -129,7 +161,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
throw new InvalidOperationException(message);
|
throw new InvalidOperationException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
var layoutPage = _pageFactory.CreateInstance(previousPage.Layout);
|
var layoutPage = _pageFactory.CreateInstance(previousPage.Layout, EnableInstrumentation);
|
||||||
if (layoutPage == null)
|
if (layoutPage == null)
|
||||||
{
|
{
|
||||||
var message = Resources.FormatLayoutCannotBeLocated(previousPage.Layout);
|
var message = Resources.FormatLayoutCannotBeLocated(previousPage.Layout);
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Microsoft.AspNet.Mvc.Razor.OptionDescriptors;
|
using Microsoft.AspNet.Mvc.Razor.OptionDescriptors;
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
|
using Microsoft.AspNet.PageExecutionInstrumentation;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Razor
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
|
@ -35,6 +37,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
private readonly IRazorPageFactory _pageFactory;
|
private readonly IRazorPageFactory _pageFactory;
|
||||||
private readonly IReadOnlyList<IViewLocationExpander> _viewLocationExpanders;
|
private readonly IReadOnlyList<IViewLocationExpander> _viewLocationExpanders;
|
||||||
private readonly IViewLocationCache _viewLocationCache;
|
private readonly IViewLocationCache _viewLocationCache;
|
||||||
|
// The RazorViewEngine is Request scoped which allows us to cache these value for the lifetime of a Request.
|
||||||
|
private bool _isPageExecutionFeatureInitialized;
|
||||||
|
private IPageExecutionListenerFeature _pageExecutionListenerFeature;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RazorViewEngine" /> class.
|
/// Initializes a new instance of the <see cref="RazorViewEngine" /> class.
|
||||||
|
@ -90,7 +95,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
if (viewName.EndsWith(ViewExtension, StringComparison.OrdinalIgnoreCase))
|
if (viewName.EndsWith(ViewExtension, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var page = _pageFactory.CreateInstance(viewName);
|
var page = _pageFactory.CreateInstance(viewName, IsInstrumentationEnabled(context));
|
||||||
if (page != null)
|
if (page != null)
|
||||||
{
|
{
|
||||||
return CreateFoundResult(context, page, viewName, partial);
|
return CreateFoundResult(context, page, viewName, partial);
|
||||||
|
@ -132,7 +137,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
var viewLocation = _viewLocationCache.Get(expanderContext);
|
var viewLocation = _viewLocationCache.Get(expanderContext);
|
||||||
if (!string.IsNullOrEmpty(viewLocation))
|
if (!string.IsNullOrEmpty(viewLocation))
|
||||||
{
|
{
|
||||||
var page = _pageFactory.CreateInstance(viewLocation);
|
var page = _pageFactory.CreateInstance(viewLocation, IsInstrumentationEnabled(context));
|
||||||
|
|
||||||
if (page != null)
|
if (page != null)
|
||||||
{
|
{
|
||||||
|
@ -158,7 +163,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
viewName,
|
viewName,
|
||||||
controllerName,
|
controllerName,
|
||||||
areaName);
|
areaName);
|
||||||
var page = _pageFactory.CreateInstance(transformedPath);
|
var page = _pageFactory.CreateInstance(transformedPath, IsInstrumentationEnabled(context));
|
||||||
if (page != null)
|
if (page != null)
|
||||||
{
|
{
|
||||||
// 3a. We found a page. Cache the set of values that produced it and return a found result.
|
// 3a. We found a page. Cache the set of values that produced it and return a found result.
|
||||||
|
@ -183,7 +188,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
|
|
||||||
var services = actionContext.HttpContext.RequestServices;
|
var services = actionContext.HttpContext.RequestServices;
|
||||||
var view = services.GetService<IRazorView>();
|
var view = services.GetService<IRazorView>();
|
||||||
view.Contextualize(page, partial);
|
Debug.Assert(_isPageExecutionFeatureInitialized, "IsInstrumentationEnabled must be called prior to this.");
|
||||||
|
|
||||||
|
view.Contextualize(page, partial, _pageExecutionListenerFeature);
|
||||||
return ViewEngineResult.Found(viewName, view);
|
return ViewEngineResult.Found(viewName, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,5 +198,16 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
return name[0] == '~' || name[0] == '/';
|
return name[0] == '~' || name[0] == '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsInstrumentationEnabled(ActionContext context)
|
||||||
|
{
|
||||||
|
if (!_isPageExecutionFeatureInitialized)
|
||||||
|
{
|
||||||
|
_isPageExecutionFeatureInitialized = true;
|
||||||
|
_pageExecutionListenerFeature = context.HttpContext.GetFeature<IPageExecutionListenerFeature>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _pageExecutionListenerFeature != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,14 +22,13 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
_pageFactory = pageFactory;
|
_pageFactory = pageFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<IRazorPage> GetViewStartPages([NotNull] string path)
|
public IEnumerable<IRazorPage> GetViewStartPages([NotNull] string path, bool enableInstrumentation)
|
||||||
{
|
{
|
||||||
var viewStartLocations = ViewStartUtility.GetViewStartLocations(_fileSystem, path);
|
var viewStartLocations = ViewStartUtility.GetViewStartLocations(_fileSystem, path);
|
||||||
var viewStarts = viewStartLocations.Select(_pageFactory.CreateInstance)
|
var viewStarts = viewStartLocations.Select(p => _pageFactory.CreateInstance(p, enableInstrumentation))
|
||||||
.Where(p => p != null)
|
.Where(p => p != null)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
// GetViewStartLocations return ViewStarts inside-out that is the _ViewStart closest to the page
|
// GetViewStartLocations return ViewStarts inside-out that is the _ViewStart closest to the page
|
||||||
// is the first: e.g. [ /Views/Home/_ViewStart, /Views/_ViewStart, /_ViewStart ]
|
// is the first: e.g. [ /Views/Home/_ViewStart, /Views/_ViewStart, /_ViewStart ]
|
||||||
|
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IRazorPage CreateInstance([NotNull] string relativePath)
|
public IRazorPage CreateInstance([NotNull] string relativePath, bool enableInstrumentation)
|
||||||
{
|
{
|
||||||
var fileInfo = _fileInfoCache.GetFileInfo(relativePath);
|
var fileInfo = _fileInfoCache.GetFileInfo(relativePath);
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
RelativePath = relativePath,
|
RelativePath = relativePath,
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _compilationService.Compile(relativeFileInfo);
|
var result = _compilationService.Compile(relativeFileInfo, enableInstrumentation);
|
||||||
var page = (IRazorPage)_activator.CreateInstance(_serviceProvider, result.CompiledType);
|
var page = (IRazorPage)_activator.CreateInstance(_serviceProvider, result.CompiledType);
|
||||||
page.Path = relativePath;
|
page.Path = relativePath;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNet.Mvc.Core;
|
|
||||||
using Microsoft.AspNet.Mvc.Description;
|
using Microsoft.AspNet.Mvc.Description;
|
||||||
using Microsoft.AspNet.Mvc.Filters;
|
using Microsoft.AspNet.Mvc.Filters;
|
||||||
using Microsoft.AspNet.Mvc.Internal;
|
using Microsoft.AspNet.Mvc.Internal;
|
||||||
|
@ -49,7 +48,10 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
yield return describe.Singleton<ICompilationService, RoslynCompilationService>();
|
yield return describe.Singleton<ICompilationService, RoslynCompilationService>();
|
||||||
yield return describe.Singleton<IRazorCompilationService, RazorCompilationService>();
|
yield return describe.Singleton<IRazorCompilationService, RazorCompilationService>();
|
||||||
yield return describe.Singleton<IViewEngineProvider, DefaultViewEngineProvider>();
|
|
||||||
|
// The provider is inexpensive to initialize and provides ViewEngines that may require request
|
||||||
|
// specific services.
|
||||||
|
yield return describe.Transient<IViewEngineProvider, DefaultViewEngineProvider>();
|
||||||
yield return describe.Scoped<ICompositeViewEngine, CompositeViewEngine>();
|
yield return describe.Scoped<ICompositeViewEngine, CompositeViewEngine>();
|
||||||
yield return describe.Singleton<IViewStartProvider, ViewStartProvider>();
|
yield return describe.Singleton<IViewStartProvider, ViewStartProvider>();
|
||||||
yield return describe.Transient<IRazorView, RazorView>();
|
yield return describe.Transient<IRazorView, RazorView>();
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.AspNet.TestHost;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
using Microsoft.Framework.DependencyInjection.Fallback;
|
||||||
|
using RazorInstrumentationWebSite;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
public class RazorInstrumentationTests
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _services = TestHelper.CreateServices("RazorInstrumentationWebsite");
|
||||||
|
private readonly Action<IApplicationBuilder> _app = new Startup().Configure;
|
||||||
|
private readonly string _expected = string.Join(Environment.NewLine,
|
||||||
|
@"<div>",
|
||||||
|
@"2147483647",
|
||||||
|
"",
|
||||||
|
@"viewstart-content",
|
||||||
|
@"<p class=""Hello world"">",
|
||||||
|
@"page-content",
|
||||||
|
@"</p>",
|
||||||
|
@"</div>");
|
||||||
|
private readonly IEnumerable<Tuple<int, int, bool>> _expectedLineMappings = new[]
|
||||||
|
{
|
||||||
|
Tuple.Create(93, 2, true),
|
||||||
|
Tuple.Create(96, 16, false),
|
||||||
|
Tuple.Create(112, 2, true),
|
||||||
|
Tuple.Create(0, 2, true),
|
||||||
|
Tuple.Create(2, 8, true),
|
||||||
|
Tuple.Create(10, 16, false),
|
||||||
|
Tuple.Create(26, 1, true),
|
||||||
|
Tuple.Create(27, 21, true),
|
||||||
|
Tuple.Create(0, 7, true),
|
||||||
|
Tuple.Create(8, 12, false),
|
||||||
|
Tuple.Create(20, 2, true),
|
||||||
|
Tuple.Create(23, 12, false),
|
||||||
|
Tuple.Create(35, 8, true),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> ActionNames
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
yield return new[] { "FullPath" };
|
||||||
|
yield return new[] { "ViewDiscoveryPath" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(ActionNames))]
|
||||||
|
public async Task ViewsAreServedWithoutInstrumentationByDefault(string actionName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = new TestPageExecutionContext();
|
||||||
|
var services = GetServiceProvider(context);
|
||||||
|
var server = TestServer.Create(services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var body = await client.GetStringAsync("http://localhost/Home/" + actionName);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(_expected, body.Trim());
|
||||||
|
Assert.Empty(context.Values);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(ActionNames))]
|
||||||
|
public async Task ViewsAreInstrumentedWhenPageExecutionListenerFeatureIsEnabled(string actionName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = new TestPageExecutionContext();
|
||||||
|
var services = GetServiceProvider(context);
|
||||||
|
var server = TestServer.Create(services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
client.DefaultRequestHeaders.Add("ENABLE-RAZOR-INSTRUMENTATION", "true");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var body = await client.GetStringAsync("http://localhost/Home/" + actionName);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(_expected, body.Trim());
|
||||||
|
Assert.Equal(_expectedLineMappings, context.Values);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(ActionNames))]
|
||||||
|
public async Task ViewsCanSwitchFromRegularToInstrumented(string actionName)
|
||||||
|
{
|
||||||
|
// Arrange - 1
|
||||||
|
var context = new TestPageExecutionContext();
|
||||||
|
var services = GetServiceProvider(context);
|
||||||
|
var server = TestServer.Create(services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act - 1
|
||||||
|
var body = await client.GetStringAsync("http://localhost/Home/" + actionName);
|
||||||
|
|
||||||
|
// Assert - 1
|
||||||
|
Assert.Equal(_expected, body.Trim());
|
||||||
|
Assert.Empty(context.Values);
|
||||||
|
|
||||||
|
// Arrange - 2
|
||||||
|
client.DefaultRequestHeaders.Add("ENABLE-RAZOR-INSTRUMENTATION", "true");
|
||||||
|
|
||||||
|
// Act - 2
|
||||||
|
body = await client.GetStringAsync("http://localhost/Home/" + actionName);
|
||||||
|
|
||||||
|
// Assert - 2
|
||||||
|
Assert.Equal(_expected, body.Trim());
|
||||||
|
Assert.Equal(_expectedLineMappings, context.Values);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SwitchingFromNonInstrumentedToInstrumentedWorksForLayoutAndViewStarts()
|
||||||
|
{
|
||||||
|
// Arrange - 1
|
||||||
|
var context = new TestPageExecutionContext();
|
||||||
|
var services = GetServiceProvider(context);
|
||||||
|
var server = TestServer.Create(services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act - 1
|
||||||
|
var body = await client.GetStringAsync("http://localhost/Home/FullPath");
|
||||||
|
|
||||||
|
// Assert - 1
|
||||||
|
Assert.Equal(_expected, body.Trim());
|
||||||
|
Assert.Empty(context.Values);
|
||||||
|
|
||||||
|
// Arrange - 2
|
||||||
|
client.DefaultRequestHeaders.Add("ENABLE-RAZOR-INSTRUMENTATION", "true");
|
||||||
|
|
||||||
|
// Act - 2
|
||||||
|
body = await client.GetStringAsync("http://localhost/Home/ViewDiscoveryPath");
|
||||||
|
|
||||||
|
// Assert - 2
|
||||||
|
Assert.Equal(_expected, body.Trim());
|
||||||
|
Assert.Equal(_expectedLineMappings, context.Values);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IServiceProvider GetServiceProvider(TestPageExecutionContext pageExecutionContext)
|
||||||
|
{
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddInstance(pageExecutionContext);
|
||||||
|
return services.BuildServiceProvider(_services);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,7 @@
|
||||||
"ReflectedModelWebSite": "",
|
"ReflectedModelWebSite": "",
|
||||||
"RoutingWebSite": "",
|
"RoutingWebSite": "",
|
||||||
"RazorWebSite": "",
|
"RazorWebSite": "",
|
||||||
|
"RazorInstrumentationWebsite": "",
|
||||||
"ValueProvidersSite": "",
|
"ValueProvidersSite": "",
|
||||||
"XmlSerializerWebSite": "",
|
"XmlSerializerWebSite": "",
|
||||||
"UrlHelperWebSite": "",
|
"UrlHelperWebSite": "",
|
||||||
|
|
|
@ -34,7 +34,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var actual = cache.GetOrAdd(runtimeFileInfo, () => expected);
|
var actual = cache.GetOrAdd(runtimeFileInfo, false, () => expected);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Same(expected, actual);
|
Assert.Same(expected, actual);
|
||||||
|
@ -140,7 +140,8 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var actual = cache.GetOrAdd(runtimeFileInfo,
|
var actual = cache.GetOrAdd(runtimeFileInfo,
|
||||||
() => CompilationResult.Successful(resultViewType));
|
enableInstrumentation: false,
|
||||||
|
compile: () => CompilationResult.Successful(resultViewType));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
if (swapsPreCompile)
|
if (swapsPreCompile)
|
||||||
|
@ -174,9 +175,9 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
cache.GetOrAdd(runtimeFileInfo, () => uncachedResult);
|
cache.GetOrAdd(runtimeFileInfo, false, () => uncachedResult);
|
||||||
var actual1 = cache.GetOrAdd(runtimeFileInfo, () => uncachedResult);
|
var actual1 = cache.GetOrAdd(runtimeFileInfo, false, () => uncachedResult);
|
||||||
var actual2 = cache.GetOrAdd(runtimeFileInfo, () => uncachedResult);
|
var actual2 = cache.GetOrAdd(runtimeFileInfo, false, () => uncachedResult);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotSame(uncachedResult, actual1);
|
Assert.NotSame(uncachedResult, actual1);
|
||||||
|
@ -189,5 +190,40 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
Assert.Null(actual2.CompiledContent);
|
Assert.Null(actual2.CompiledContent);
|
||||||
Assert.Same(type, actual2.CompiledType);
|
Assert.Same(type, actual2.CompiledType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetOrAdd_IgnoresCache_IfCachedItemIsNotInstrumentedAndEnableInstrumentationIsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var lastModified = DateTime.UtcNow;
|
||||||
|
var cache = new CompilerCache();
|
||||||
|
var fileInfo = new Mock<IFileInfo>();
|
||||||
|
fileInfo.SetupGet(f => f.PhysicalPath)
|
||||||
|
.Returns("test");
|
||||||
|
fileInfo.SetupGet(f => f.LastModified)
|
||||||
|
.Returns(lastModified);
|
||||||
|
var type = GetType();
|
||||||
|
var uncachedResult1 = UncachedCompilationResult.Successful(type, "hello world");
|
||||||
|
var uncachedResult2 = UncachedCompilationResult.Successful(typeof(object), "hello world");
|
||||||
|
var uncachedResult3 = UncachedCompilationResult.Successful(typeof(Guid), "hello world");
|
||||||
|
|
||||||
|
var runtimeFileInfo = new RelativeFileInfo()
|
||||||
|
{
|
||||||
|
FileInfo = fileInfo.Object,
|
||||||
|
RelativePath = "test",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
cache.GetOrAdd(runtimeFileInfo, false, () => uncachedResult1);
|
||||||
|
var actual1 = cache.GetOrAdd(runtimeFileInfo, true, () => uncachedResult2);
|
||||||
|
var actual2 = cache.GetOrAdd(runtimeFileInfo, false, () => uncachedResult3);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(uncachedResult2, actual1);
|
||||||
|
Assert.Same(typeof(object), actual1.CompiledType);
|
||||||
|
|
||||||
|
Assert.NotSame(actual2, uncachedResult3);
|
||||||
|
Assert.Same(typeof(object), actual2.CompiledType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
// Arrange
|
// Arrange
|
||||||
var host = new Mock<IMvcRazorHost>();
|
var host = new Mock<IMvcRazorHost>();
|
||||||
host.Setup(h => h.GenerateCode(@"views\index\home.cshtml", It.IsAny<Stream>()))
|
host.Setup(h => h.GenerateCode(@"views\index\home.cshtml", It.IsAny<Stream>()))
|
||||||
.Returns(new GeneratorResults(new Block(new BlockBuilder { Type = BlockType.Comment }), new RazorError[0], new CodeBuilderResult("", new LineMapping[0])))
|
.Returns(GetGeneratorResult())
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
var ap = new Mock<IControllerAssemblyProvider>();
|
var ap = new Mock<IControllerAssemblyProvider>();
|
||||||
|
@ -46,10 +46,51 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
razorService.CompileCore(relativeFileInfo);
|
razorService.CompileCore(relativeFileInfo, isInstrumented: false);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
host.Verify();
|
host.Verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public void CompileCoreSetsEnableInstrumentationOnHost(bool enableInstrumentation)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var host = new Mock<IMvcRazorHost>();
|
||||||
|
host.SetupAllProperties();
|
||||||
|
host.Setup(h => h.GenerateCode(It.IsAny<string>(), It.IsAny<Stream>()))
|
||||||
|
.Returns(GetGeneratorResult());
|
||||||
|
var assemblyProvider = new Mock<IControllerAssemblyProvider>();
|
||||||
|
assemblyProvider.SetupGet(e => e.CandidateAssemblies)
|
||||||
|
.Returns(Enumerable.Empty<Assembly>());
|
||||||
|
|
||||||
|
var compiler = new Mock<ICompilationService>();
|
||||||
|
compiler.Setup(c => c.Compile(It.IsAny<IFileInfo>(), It.IsAny<string>()))
|
||||||
|
.Returns(CompilationResult.Successful(GetType()));
|
||||||
|
|
||||||
|
var razorService = new RazorCompilationService(compiler.Object, assemblyProvider.Object, host.Object);
|
||||||
|
var relativeFileInfo = new RelativeFileInfo()
|
||||||
|
{
|
||||||
|
FileInfo = Mock.Of<IFileInfo>(),
|
||||||
|
RelativePath = @"views\index\home.cshtml",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
razorService.CompileCore(relativeFileInfo, isInstrumented: enableInstrumentation);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(enableInstrumentation, host.Object.EnableInstrumentation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GeneratorResults GetGeneratorResult()
|
||||||
|
{
|
||||||
|
return new GeneratorResults(
|
||||||
|
new Block(
|
||||||
|
new BlockBuilder { Type = BlockType.Comment }),
|
||||||
|
new RazorError[0],
|
||||||
|
new CodeBuilderResult("", new LineMapping[0]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,7 +183,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
// Arrange
|
// Arrange
|
||||||
var pageFactory = new Mock<IRazorPageFactory>();
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
var page = Mock.Of<IRazorPage>();
|
var page = Mock.Of<IRazorPage>();
|
||||||
pageFactory.Setup(p => p.CreateInstance(It.IsAny<string>()))
|
pageFactory.Setup(p => p.CreateInstance(It.IsAny<string>(), false))
|
||||||
.Returns(Mock.Of<IRazorPage>());
|
.Returns(Mock.Of<IRazorPage>());
|
||||||
var viewEngine = CreateViewEngine(pageFactory.Object);
|
var viewEngine = CreateViewEngine(pageFactory.Object);
|
||||||
var context = GetActionContext(_controllerTestContext);
|
var context = GetActionContext(_controllerTestContext);
|
||||||
|
@ -203,7 +203,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
// Arrange
|
// Arrange
|
||||||
var pageFactory = new Mock<IRazorPageFactory>();
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
var page = Mock.Of<IRazorPage>();
|
var page = Mock.Of<IRazorPage>();
|
||||||
pageFactory.Setup(p => p.CreateInstance("fake-path1/bar/test-view.rzr"))
|
pageFactory.Setup(p => p.CreateInstance("fake-path1/bar/test-view.rzr", false))
|
||||||
.Returns(Mock.Of<IRazorPage>())
|
.Returns(Mock.Of<IRazorPage>())
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
var viewEngine = new OverloadedLocationViewEngine(pageFactory.Object,
|
var viewEngine = new OverloadedLocationViewEngine(pageFactory.Object,
|
||||||
|
@ -224,7 +224,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
// Arrange
|
// Arrange
|
||||||
var pageFactory = new Mock<IRazorPageFactory>();
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
var page = Mock.Of<IRazorPage>();
|
var page = Mock.Of<IRazorPage>();
|
||||||
pageFactory.Setup(p => p.CreateInstance("fake-area-path/foo/bar/test-view2.rzr"))
|
pageFactory.Setup(p => p.CreateInstance("fake-area-path/foo/bar/test-view2.rzr", false))
|
||||||
.Returns(Mock.Of<IRazorPage>())
|
.Returns(Mock.Of<IRazorPage>())
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
var viewEngine = new OverloadedLocationViewEngine(pageFactory.Object,
|
var viewEngine = new OverloadedLocationViewEngine(pageFactory.Object,
|
||||||
|
@ -273,7 +273,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var pageFactory = new Mock<IRazorPageFactory>();
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
pageFactory.Setup(p => p.CreateInstance("test-string/bar.cshtml"))
|
pageFactory.Setup(p => p.CreateInstance("test-string/bar.cshtml", false))
|
||||||
.Returns(Mock.Of<IRazorPage>())
|
.Returns(Mock.Of<IRazorPage>())
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
var expander1Result = new[] { "some-seed" };
|
var expander1Result = new[] { "some-seed" };
|
||||||
|
@ -325,9 +325,9 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var pageFactory = new Mock<IRazorPageFactory>();
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
pageFactory.Setup(p => p.CreateInstance("/Views/bar/baz.cshtml"))
|
pageFactory.Setup(p => p.CreateInstance("/Views/bar/baz.cshtml", false))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
pageFactory.Setup(p => p.CreateInstance("/Views/Shared/baz.cshtml"))
|
pageFactory.Setup(p => p.CreateInstance("/Views/Shared/baz.cshtml", false))
|
||||||
.Returns(Mock.Of<IRazorPage>())
|
.Returns(Mock.Of<IRazorPage>())
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
var cache = GetViewLocationCache();
|
var cache = GetViewLocationCache();
|
||||||
|
@ -353,7 +353,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var pageFactory = new Mock<IRazorPageFactory>(MockBehavior.Strict);
|
var pageFactory = new Mock<IRazorPageFactory>(MockBehavior.Strict);
|
||||||
pageFactory.Setup(p => p.CreateInstance("some-view-location"))
|
pageFactory.Setup(p => p.CreateInstance("some-view-location", false))
|
||||||
.Returns(Mock.Of<IRazorPage>())
|
.Returns(Mock.Of<IRazorPage>())
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
var expander = new Mock<IViewLocationExpander>(MockBehavior.Strict);
|
var expander = new Mock<IViewLocationExpander>(MockBehavior.Strict);
|
||||||
|
@ -384,10 +384,10 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var pageFactory = new Mock<IRazorPageFactory>();
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
pageFactory.Setup(p => p.CreateInstance("expired-location"))
|
pageFactory.Setup(p => p.CreateInstance("expired-location", false))
|
||||||
.Returns((IRazorPage)null)
|
.Returns((IRazorPage)null)
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
pageFactory.Setup(p => p.CreateInstance("some-view-location"))
|
pageFactory.Setup(p => p.CreateInstance("some-view-location", false))
|
||||||
.Returns(Mock.Of<IRazorPage>())
|
.Returns(Mock.Of<IRazorPage>())
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
var cacheMock = new Mock<IViewLocationCache>();
|
var cacheMock = new Mock<IViewLocationCache>();
|
||||||
|
|
|
@ -3,8 +3,10 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNet.PageExecutionInstrumentation;
|
||||||
using Microsoft.AspNet.PipelineCore;
|
using Microsoft.AspNet.PipelineCore;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
@ -45,7 +47,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
||||||
Mock.Of<IRazorPageActivator>(),
|
Mock.Of<IRazorPageActivator>(),
|
||||||
CreateViewStartProvider());
|
CreateViewStartProvider());
|
||||||
view.Contextualize(page, isPartial: true);
|
view.Contextualize(page, isPartial: true, pageExecutionListener: null);
|
||||||
var viewContext = CreateViewContext(view);
|
var viewContext = CreateViewContext(view);
|
||||||
var expected = viewContext.Writer;
|
var expected = viewContext.Writer;
|
||||||
|
|
||||||
|
@ -72,7 +74,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
||||||
activator.Object,
|
activator.Object,
|
||||||
CreateViewStartProvider());
|
CreateViewStartProvider());
|
||||||
view.Contextualize(page, isPartial: true);
|
view.Contextualize(page, isPartial: true, pageExecutionListener: null);
|
||||||
var viewContext = CreateViewContext(view);
|
var viewContext = CreateViewContext(view);
|
||||||
var expectedWriter = viewContext.Writer;
|
var expectedWriter = viewContext.Writer;
|
||||||
activator.Setup(a => a.Activate(page, It.IsAny<ViewContext>()))
|
activator.Setup(a => a.Activate(page, It.IsAny<ViewContext>()))
|
||||||
|
@ -102,7 +104,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
||||||
activator.Object,
|
activator.Object,
|
||||||
CreateViewStartProvider());
|
CreateViewStartProvider());
|
||||||
view.Contextualize(page, isPartial: true);
|
view.Contextualize(page, isPartial: true, pageExecutionListener: null);
|
||||||
var viewContext = CreateViewContext(view);
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
@ -124,15 +126,16 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
var view = new RazorView(pageFactory.Object,
|
var view = new RazorView(pageFactory.Object,
|
||||||
Mock.Of<IRazorPageActivator>(),
|
Mock.Of<IRazorPageActivator>(),
|
||||||
viewStartProvider);
|
viewStartProvider);
|
||||||
view.Contextualize(page, isPartial: true);
|
view.Contextualize(page, isPartial: true, pageExecutionListener: null);
|
||||||
var viewContext = CreateViewContext(view);
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await view.RenderAsync(viewContext);
|
await view.RenderAsync(viewContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
pageFactory.Verify(v => v.CreateInstance(It.IsAny<string>()), Times.Never());
|
pageFactory.Verify(v => v.CreateInstance(It.IsAny<string>(), It.IsAny<bool>()), Times.Never());
|
||||||
Mock.Get(viewStartProvider).Verify(v => v.GetViewStartPages(It.IsAny<string>()), Times.Never());
|
Mock.Get(viewStartProvider)
|
||||||
|
.Verify(v => v.GetViewStartPages(It.IsAny<string>(), It.IsAny<bool>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
@ -147,7 +150,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
||||||
Mock.Of<IRazorPageActivator>(),
|
Mock.Of<IRazorPageActivator>(),
|
||||||
CreateViewStartProvider());
|
CreateViewStartProvider());
|
||||||
view.Contextualize(page, isPartial: false);
|
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
|
||||||
var viewContext = CreateViewContext(view);
|
var viewContext = CreateViewContext(view);
|
||||||
var original = viewContext.Writer;
|
var original = viewContext.Writer;
|
||||||
|
|
||||||
|
@ -170,7 +173,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
||||||
Mock.Of<IRazorPageActivator>(),
|
Mock.Of<IRazorPageActivator>(),
|
||||||
CreateViewStartProvider());
|
CreateViewStartProvider());
|
||||||
view.Contextualize(page, isPartial: false);
|
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
|
||||||
var viewContext = CreateViewContext(view);
|
var viewContext = CreateViewContext(view);
|
||||||
var original = viewContext.Writer;
|
var original = viewContext.Writer;
|
||||||
|
|
||||||
|
@ -195,7 +198,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
||||||
activator.Object,
|
activator.Object,
|
||||||
CreateViewStartProvider());
|
CreateViewStartProvider());
|
||||||
view.Contextualize(page, isPartial: false);
|
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
|
||||||
|
|
||||||
var viewContext = CreateViewContext(view);
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
|
@ -237,7 +240,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
||||||
activator.Object,
|
activator.Object,
|
||||||
CreateViewStartProvider(viewStart1, viewStart2));
|
CreateViewStartProvider(viewStart1, viewStart2));
|
||||||
view.Contextualize(page, isPartial: false);
|
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
|
||||||
var viewContext = CreateViewContext(view);
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
@ -285,13 +288,13 @@ foot-content";
|
||||||
activator.Setup(a => a.Activate(layout, It.IsAny<ViewContext>()))
|
activator.Setup(a => a.Activate(layout, It.IsAny<ViewContext>()))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
var pageFactory = new Mock<IRazorPageFactory>();
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
pageFactory.Setup(p => p.CreateInstance(LayoutPath))
|
pageFactory.Setup(p => p.CreateInstance(LayoutPath, false))
|
||||||
.Returns(layout);
|
.Returns(layout);
|
||||||
|
|
||||||
var view = new RazorView(pageFactory.Object,
|
var view = new RazorView(pageFactory.Object,
|
||||||
activator.Object,
|
activator.Object,
|
||||||
CreateViewStartProvider());
|
CreateViewStartProvider());
|
||||||
view.Contextualize(page, isPartial: false);
|
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
|
||||||
var viewContext = CreateViewContext(view);
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
@ -318,13 +321,13 @@ foot-content";
|
||||||
v.RenderBodyPublic();
|
v.RenderBodyPublic();
|
||||||
});
|
});
|
||||||
var pageFactory = new Mock<IRazorPageFactory>();
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
pageFactory.Setup(p => p.CreateInstance(LayoutPath))
|
pageFactory.Setup(p => p.CreateInstance(LayoutPath, false))
|
||||||
.Returns(layout);
|
.Returns(layout);
|
||||||
|
|
||||||
var view = new RazorView(pageFactory.Object,
|
var view = new RazorView(pageFactory.Object,
|
||||||
Mock.Of<IRazorPageActivator>(),
|
Mock.Of<IRazorPageActivator>(),
|
||||||
CreateViewStartProvider());
|
CreateViewStartProvider());
|
||||||
view.Contextualize(page, isPartial: false);
|
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
|
||||||
var viewContext = CreateViewContext(view);
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
// Act and Assert
|
// Act and Assert
|
||||||
|
@ -344,13 +347,13 @@ foot-content";
|
||||||
{
|
{
|
||||||
});
|
});
|
||||||
var pageFactory = new Mock<IRazorPageFactory>();
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
pageFactory.Setup(p => p.CreateInstance(LayoutPath))
|
pageFactory.Setup(p => p.CreateInstance(LayoutPath, false))
|
||||||
.Returns(layout);
|
.Returns(layout);
|
||||||
|
|
||||||
var view = new RazorView(pageFactory.Object,
|
var view = new RazorView(pageFactory.Object,
|
||||||
Mock.Of<IRazorPageActivator>(),
|
Mock.Of<IRazorPageActivator>(),
|
||||||
CreateViewStartProvider());
|
CreateViewStartProvider());
|
||||||
view.Contextualize(page, isPartial: false);
|
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
|
||||||
var viewContext = CreateViewContext(view);
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
// Act and Assert
|
// Act and Assert
|
||||||
|
@ -396,15 +399,15 @@ body-content";
|
||||||
v.RenderBodyPublic();
|
v.RenderBodyPublic();
|
||||||
});
|
});
|
||||||
var pageFactory = new Mock<IRazorPageFactory>();
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
pageFactory.Setup(p => p.CreateInstance("~/Shared/Layout1.cshtml"))
|
pageFactory.Setup(p => p.CreateInstance("~/Shared/Layout1.cshtml", false))
|
||||||
.Returns(layout1);
|
.Returns(layout1);
|
||||||
pageFactory.Setup(p => p.CreateInstance("~/Shared/Layout2.cshtml"))
|
pageFactory.Setup(p => p.CreateInstance("~/Shared/Layout2.cshtml", false))
|
||||||
.Returns(layout2);
|
.Returns(layout2);
|
||||||
|
|
||||||
var view = new RazorView(pageFactory.Object,
|
var view = new RazorView(pageFactory.Object,
|
||||||
Mock.Of<IRazorPageActivator>(),
|
Mock.Of<IRazorPageActivator>(),
|
||||||
CreateViewStartProvider());
|
CreateViewStartProvider());
|
||||||
view.Contextualize(page, isPartial: false);
|
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
|
||||||
var viewContext = CreateViewContext(view);
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
@ -444,13 +447,13 @@ section-content-2";
|
||||||
});
|
});
|
||||||
|
|
||||||
var pageFactory = new Mock<IRazorPageFactory>();
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
pageFactory.Setup(p => p.CreateInstance("layout-1"))
|
pageFactory.Setup(p => p.CreateInstance("layout-1", false))
|
||||||
.Returns(layout1);
|
.Returns(layout1);
|
||||||
|
|
||||||
var view = new RazorView(pageFactory.Object,
|
var view = new RazorView(pageFactory.Object,
|
||||||
Mock.Of<IRazorPageActivator>(),
|
Mock.Of<IRazorPageActivator>(),
|
||||||
CreateViewStartProvider());
|
CreateViewStartProvider());
|
||||||
view.Contextualize(page, isPartial: false);
|
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
|
||||||
var viewContext = CreateViewContext(view);
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
@ -488,13 +491,13 @@ section-content-2";
|
||||||
});
|
});
|
||||||
|
|
||||||
var pageFactory = new Mock<IRazorPageFactory>();
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
pageFactory.Setup(p => p.CreateInstance("layout-1"))
|
pageFactory.Setup(p => p.CreateInstance("layout-1", false))
|
||||||
.Returns(layout1);
|
.Returns(layout1);
|
||||||
|
|
||||||
var view = new RazorView(pageFactory.Object,
|
var view = new RazorView(pageFactory.Object,
|
||||||
Mock.Of<IRazorPageActivator>(),
|
Mock.Of<IRazorPageActivator>(),
|
||||||
CreateViewStartProvider());
|
CreateViewStartProvider());
|
||||||
view.Contextualize(page, isPartial: false);
|
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
|
||||||
var viewContext = CreateViewContext(view);
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
@ -520,7 +523,7 @@ section-content-2";
|
||||||
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
||||||
Mock.Of<IRazorPageActivator>(),
|
Mock.Of<IRazorPageActivator>(),
|
||||||
CreateViewStartProvider());
|
CreateViewStartProvider());
|
||||||
view.Contextualize(page, isPartial: false);
|
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
|
||||||
var viewContext = CreateViewContext(view);
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
// Act and Assert
|
// Act and Assert
|
||||||
|
@ -555,13 +558,13 @@ section-content-2";
|
||||||
v.Layout = "~/Shared/Layout2.cshtml";
|
v.Layout = "~/Shared/Layout2.cshtml";
|
||||||
});
|
});
|
||||||
var pageFactory = new Mock<IRazorPageFactory>();
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
pageFactory.Setup(p => p.CreateInstance("~/Shared/Layout1.cshtml"))
|
pageFactory.Setup(p => p.CreateInstance("~/Shared/Layout1.cshtml", false))
|
||||||
.Returns(layout1);
|
.Returns(layout1);
|
||||||
|
|
||||||
var view = new RazorView(pageFactory.Object,
|
var view = new RazorView(pageFactory.Object,
|
||||||
Mock.Of<IRazorPageActivator>(),
|
Mock.Of<IRazorPageActivator>(),
|
||||||
CreateViewStartProvider());
|
CreateViewStartProvider());
|
||||||
view.Contextualize(page, isPartial: false);
|
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
|
||||||
var viewContext = CreateViewContext(view);
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
// Act and Assert
|
// Act and Assert
|
||||||
|
@ -569,6 +572,153 @@ section-content-2";
|
||||||
Assert.Equal(expected, ex.Message);
|
Assert.Equal(expected, ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenderAsync_UsesPageExecutionFeatureFromRequest_ToWrapWriter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var pageWriter = CreateBufferedWriter();
|
||||||
|
var layoutWriter = CreateBufferedWriter();
|
||||||
|
|
||||||
|
var layoutExecuted = false;
|
||||||
|
var count = -1;
|
||||||
|
var feature = new Mock<IPageExecutionListenerFeature>(MockBehavior.Strict);
|
||||||
|
feature.Setup(f => f.DecorateWriter(It.IsAny<RazorTextWriter>()))
|
||||||
|
.Returns(() =>
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
return pageWriter;
|
||||||
|
}
|
||||||
|
else if (count == 1)
|
||||||
|
{
|
||||||
|
return layoutWriter;
|
||||||
|
}
|
||||||
|
throw new Exception();
|
||||||
|
})
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
var pageContext = Mock.Of<IPageExecutionContext>();
|
||||||
|
feature.Setup(f => f.GetContext("/MyPage.cshtml", pageWriter))
|
||||||
|
.Returns(pageContext)
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
var layoutContext = Mock.Of<IPageExecutionContext>();
|
||||||
|
feature.Setup(f => f.GetContext("/Layout.cshtml", layoutWriter))
|
||||||
|
.Returns(layoutContext)
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
var page = new TestableRazorPage(v =>
|
||||||
|
{
|
||||||
|
v.Layout = "/Layout.cshtml";
|
||||||
|
Assert.Same(pageWriter, v.Output);
|
||||||
|
Assert.Same(pageContext, v.PageExecutionContext);
|
||||||
|
});
|
||||||
|
page.Path = "/MyPage.cshtml";
|
||||||
|
|
||||||
|
var layout = new TestableRazorPage(v =>
|
||||||
|
{
|
||||||
|
Assert.Same(layoutWriter, v.Output);
|
||||||
|
Assert.Same(layoutContext, v.PageExecutionContext);
|
||||||
|
v.RenderBodyPublic();
|
||||||
|
|
||||||
|
layoutExecuted = true;
|
||||||
|
});
|
||||||
|
layout.Path = "/Layout.cshtml";
|
||||||
|
|
||||||
|
var pageFactory = new Mock<IRazorPageFactory>();
|
||||||
|
pageFactory.Setup(p => p.CreateInstance("/Layout.cshtml", true))
|
||||||
|
.Returns(layout);
|
||||||
|
var viewStartProvider = new Mock<IViewStartProvider>();
|
||||||
|
viewStartProvider.Setup(v => v.GetViewStartPages(It.IsAny<string>(), true))
|
||||||
|
.Returns(Enumerable.Empty<IRazorPage>())
|
||||||
|
.Verifiable();
|
||||||
|
var view = new RazorView(pageFactory.Object,
|
||||||
|
Mock.Of<IRazorPageActivator>(),
|
||||||
|
viewStartProvider.Object);
|
||||||
|
view.Contextualize(page, isPartial: false, pageExecutionListener: feature.Object);
|
||||||
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await view.RenderAsync(viewContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
feature.Verify();
|
||||||
|
viewStartProvider.Verify();
|
||||||
|
Assert.True(layoutExecuted);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenderAsync_UsesPageExecutionFeatureFromRequest_ToGetExecutionContext()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var writer = Mock.Of<TextWriter>();
|
||||||
|
var executed = false;
|
||||||
|
var feature = new Mock<IPageExecutionListenerFeature>(MockBehavior.Strict);
|
||||||
|
|
||||||
|
var pageContext = Mock.Of<IPageExecutionContext>();
|
||||||
|
feature.Setup(f => f.GetContext("/MyPartialPage.cshtml", writer))
|
||||||
|
.Returns(pageContext)
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
var page = new TestableRazorPage(v =>
|
||||||
|
{
|
||||||
|
Assert.Same(writer, v.Output);
|
||||||
|
Assert.Same(pageContext, v.PageExecutionContext);
|
||||||
|
executed = true;
|
||||||
|
});
|
||||||
|
page.Path = "/MyPartialPage.cshtml";
|
||||||
|
|
||||||
|
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
||||||
|
Mock.Of<IRazorPageActivator>(),
|
||||||
|
Mock.Of<IViewStartProvider>());
|
||||||
|
view.Contextualize(page, isPartial: true, pageExecutionListener: feature.Object);
|
||||||
|
var viewContext = CreateViewContext(view);
|
||||||
|
viewContext.Writer = writer;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await view.RenderAsync(viewContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
feature.Verify();
|
||||||
|
Assert.True(executed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public async Task RenderAsync_DoesNotSetExecutionContextWhenListenerIsNotRegistered(bool isPartial)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var executed = false;
|
||||||
|
var page = new TestableRazorPage(v =>
|
||||||
|
{
|
||||||
|
Assert.Null(v.PageExecutionContext);
|
||||||
|
executed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
|
||||||
|
Mock.Of<IRazorPageActivator>(),
|
||||||
|
Mock.Of<IViewStartProvider>());
|
||||||
|
view.Contextualize(page, isPartial, pageExecutionListener: null);
|
||||||
|
var viewContext = CreateViewContext(view);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await view.RenderAsync(viewContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(executed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TextWriter CreateBufferedWriter()
|
||||||
|
{
|
||||||
|
var mockWriter = new Mock<TextWriter>();
|
||||||
|
var bufferedWriter = mockWriter.As<IBufferedTextWriter>();
|
||||||
|
bufferedWriter.SetupGet(b => b.IsBuffering)
|
||||||
|
.Returns(true);
|
||||||
|
return mockWriter.Object;
|
||||||
|
}
|
||||||
|
|
||||||
private static ViewContext CreateViewContext(RazorView view)
|
private static ViewContext CreateViewContext(RazorView view)
|
||||||
{
|
{
|
||||||
var httpContext = new DefaultHttpContext();
|
var httpContext = new DefaultHttpContext();
|
||||||
|
@ -584,7 +734,7 @@ section-content-2";
|
||||||
{
|
{
|
||||||
viewStartPages = viewStartPages ?? new IRazorPage[0];
|
viewStartPages = viewStartPages ?? new IRazorPage[0];
|
||||||
var viewStartProvider = new Mock<IViewStartProvider>();
|
var viewStartProvider = new Mock<IViewStartProvider>();
|
||||||
viewStartProvider.Setup(v => v.GetViewStartPages(It.IsAny<string>()))
|
viewStartProvider.Setup(v => v.GetViewStartPages(It.IsAny<string>(), false))
|
||||||
.Returns(viewStartPages);
|
.Returns(viewStartPages);
|
||||||
|
|
||||||
return viewStartProvider.Object;
|
return viewStartProvider.Object;
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace RazorInstrumentationWebSite
|
||||||
|
{
|
||||||
|
public class HomeController : Controller
|
||||||
|
{
|
||||||
|
public ActionResult FullPath()
|
||||||
|
{
|
||||||
|
return View("/Views/Home/FullPath.cshtml");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionResult ViewDiscoveryPath()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="__ToolsVersion__" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||||
|
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectGuid>2b2b9876-903c-4065-8d62-2ee832bba106</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
</Project>
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.PageExecutionInstrumentation;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
|
||||||
|
namespace RazorInstrumentationWebSite
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public void Configure(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
var configuration = app.GetTestConfiguration();
|
||||||
|
|
||||||
|
// Set up application services
|
||||||
|
app.UseServices(services =>
|
||||||
|
{
|
||||||
|
// Add MVC services to the services container
|
||||||
|
services.AddMvc(configuration);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.Use(async (HttpContext context, Func<Task> next) =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(context.Request.Headers["ENABLE-RAZOR-INSTRUMENTATION"]))
|
||||||
|
{
|
||||||
|
var pageExecutionContext = context.ApplicationServices.GetService<TestPageExecutionContext>();
|
||||||
|
var listenerFeature = new TestPageExecutionListenerFeature(pageExecutionContext);
|
||||||
|
context.SetFeature<IPageExecutionListenerFeature>(listenerFeature);
|
||||||
|
}
|
||||||
|
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add MVC to the request pipeline
|
||||||
|
app.UseMvc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.PageExecutionInstrumentation;
|
||||||
|
|
||||||
|
namespace RazorInstrumentationWebSite
|
||||||
|
{
|
||||||
|
public class TestPageExecutionContext : IPageExecutionContext
|
||||||
|
{
|
||||||
|
public List<Tuple<int, int, bool>> Values { get; }
|
||||||
|
= new List<Tuple<int, int, bool>>();
|
||||||
|
|
||||||
|
public void BeginContext(int position, int length, bool isLiteral)
|
||||||
|
{
|
||||||
|
Values.Add(Tuple.Create(position, length, isLiteral));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndContext()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.PageExecutionInstrumentation;
|
||||||
|
|
||||||
|
namespace RazorInstrumentationWebSite
|
||||||
|
{
|
||||||
|
public class TestPageExecutionListenerFeature : IPageExecutionListenerFeature
|
||||||
|
{
|
||||||
|
private readonly IPageExecutionContext _context;
|
||||||
|
|
||||||
|
public TestPageExecutionListenerFeature(IPageExecutionContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextWriter DecorateWriter(TextWriter writer)
|
||||||
|
{
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IPageExecutionContext GetContext(string sourceFilePath, TextWriter writer)
|
||||||
|
{
|
||||||
|
return _context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
<p class="@("Hello world")">
|
||||||
|
page-content
|
||||||
|
</p>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<p class="@("Hello world")">
|
||||||
|
page-content
|
||||||
|
</p>
|
|
@ -0,0 +1,4 @@
|
||||||
|
<div>
|
||||||
|
@int.MaxValue
|
||||||
|
@RenderBody()
|
||||||
|
</div>
|
|
@ -0,0 +1,5 @@
|
||||||
|
@{
|
||||||
|
Layout = "/Views/_Layout.cshtml";
|
||||||
|
var viewStartMessage = "viewstart-content";
|
||||||
|
}
|
||||||
|
@viewStartMessage
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.AspNet.Mvc": "",
|
||||||
|
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
||||||
|
"Microsoft.AspNet.Mvc.TestConfiguration": ""
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"aspnet50": { },
|
||||||
|
"aspnetcore50": { }
|
||||||
|
}
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче