Changes to make EnableInstrumentation conditionally enabled

This commit is contained in:
Pranav K 2014-10-02 17:49:54 -07:00
Родитель 5d32d224f4
Коммит 12477c9f52
29 изменённых файлов: 791 добавлений и 111 удалений

15
Mvc.sln
Просмотреть файл

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.22013.1
VisualStudioVersion = 14.0.22130.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
EndProject
@ -86,6 +86,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ReflectedModelWebSite", "te
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "FilesWebSite", "test\WebSites\FilesWebSite\FilesWebSite.kproj", "{0EF9860B-10D7-452F-B0F4-A405B88BEBB3}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RazorInstrumentationWebSite", "test\WebSites\RazorInstrumentationWebSite\RazorInstrumentationWebSite.kproj", "{2B2B9876-903C-4065-8D62-2EE832BBA106}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
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.Build.0 = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -487,5 +499,6 @@ Global
{0EF9860B-10D7-452F-B0F4-A405B88BEBB3} = {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}
{2B2B9876-903C-4065-8D62-2EE832BBA106} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
EndGlobalSection
EndGlobal

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

@ -64,25 +64,30 @@ namespace Microsoft.AspNet.Mvc.Razor
return false;
}
public CompilationResult GetOrAdd(RelativeFileInfo fileInfo, Func<CompilationResult> compile)
public CompilationResult GetOrAdd([NotNull] RelativeFileInfo fileInfo,
bool enableInstrumentation,
[NotNull] Func<CompilationResult> compile)
{
CompilerCacheEntry cacheEntry;
if (!_cache.TryGetValue(fileInfo.RelativePath, out cacheEntry))
{
return OnCacheMiss(fileInfo, compile);
return OnCacheMiss(fileInfo, enableInstrumentation, compile);
}
else
{
if (cacheEntry.Length != fileInfo.FileInfo.Length)
if ((cacheEntry.Length != fileInfo.FileInfo.Length) ||
(enableInstrumentation && !cacheEntry.IsInstrumented))
{
// it's not a match, recompile
return OnCacheMiss(fileInfo, compile);
// Recompile if
// (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)
{
// Match, not update needed
return CompilationResult.Successful(cacheEntry.ViewType);
return CompilationResult.Successful(cacheEntry.CompiledType);
}
var hash = RazorFileHash.GetHash(fileInfo.FileInfo);
@ -92,20 +97,24 @@ namespace Microsoft.AspNet.Mvc.Razor
string.Equals(cacheEntry.Hash, hash, StringComparison.Ordinal))
{
// 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
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 cacheEntry = new CompilerCacheEntry(file, result.CompiledType);
_cache.AddOrUpdate(file.RelativePath, cacheEntry, (a, b) => cacheEntry);
var cacheEntry = new CompilerCacheEntry(file, result.CompiledType, isInstrumented);
_cache[file.RelativePath] = cacheEntry;
return result;
}

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

@ -5,35 +5,74 @@ using System;
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 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;
Length = info.Length;
LastModified = info.LastModified;
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;
Length = info.FileInfo.Length;
LastModified = info.FileInfo.LastModified;
IsInstrumented = isInstrumented;
}
public Type ViewType { get; set; }
public string RelativePath { get; set; }
public long Length { get; set; }
public DateTime LastModified { get; set; }
/// <summary>
/// Gets the <see cref="Type"/> produced as a result of compilation.
/// </summary>
public Type CompiledType { get; private set; }
/// <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>
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; } }
/// <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.
/// </summary>
/// <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>
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.
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.PageExecutionInstrumentation;
namespace Microsoft.AspNet.Mvc.Razor
{
@ -17,6 +18,8 @@ namespace Microsoft.AspNet.Mvc.Razor
/// </summary>
/// <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>
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.
/// </summary>
/// <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>
IEnumerable<IRazorPage> GetViewStartPages(string path);
IEnumerable<IRazorPage> GetViewStartPages(string path, bool enableInstrumentation);
}
}

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

@ -3,8 +3,19 @@
namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// Specifies the contracts for a service that compiles Razor files.
/// </summary>
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
{
/// <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
{
// This class must be registered as a singleton service for the caching to work.
private readonly CompilerCache _cache;
private readonly ICompilationService _baseCompilationService;
private readonly IMvcRazorHost _razorHost;
@ -22,13 +27,16 @@ namespace Microsoft.AspNet.Mvc.Razor
_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;
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.
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.PageExecutionInstrumentation;
namespace Microsoft.AspNet.Mvc.Razor
{
@ -15,6 +17,7 @@ namespace Microsoft.AspNet.Mvc.Razor
private readonly IRazorPageFactory _pageFactory;
private readonly IRazorPageActivator _pageActivator;
private readonly IViewStartProvider _viewStartProvider;
private IPageExecutionListenerFeature _pageExecutionFeature;
private IRazorPage _razorPage;
private bool _isPartial;
@ -34,11 +37,19 @@ namespace Microsoft.AspNet.Mvc.Razor
_viewStartProvider = viewStartProvider;
}
private bool EnableInstrumentation
{
get { return _pageExecutionFeature != null; }
}
/// <inheritdoc />
public virtual void Contextualize(IRazorPage razorPage, bool isPartial)
public virtual void Contextualize([NotNull] IRazorPage razorPage,
bool isPartial,
IPageExecutionListenerFeature pageExecutionListener)
{
_razorPage = razorPage;
_isPartial = isPartial;
_pageExecutionFeature = pageExecutionListener;
}
/// <inheritdoc />
@ -61,45 +72,66 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
private async Task<RazorTextWriter> RenderPageAsync(IRazorPage page,
ViewContext context,
bool executeViewStart)
private async Task<IBufferedTextWriter> RenderPageAsync(IRazorPage page,
ViewContext context,
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
// and ViewComponents to reference it.
var oldWriter = context.Writer;
context.Writer = bufferedWriter;
try
writer = _pageExecutionFeature.DecorateWriter(razorTextWriter);
bufferedWriter = writer as IBufferedTextWriter;
if (bufferedWriter == null)
{
if (executeViewStart)
{
// Execute view starts using the same context + writer as the page to render.
await RenderViewStartAsync(context);
}
var message = Resources.FormatInstrumentation_WriterMustBeBufferedTextWriter(
nameof(TextWriter),
_pageExecutionFeature.GetType().FullName,
typeof(IBufferedTextWriter).FullName);
throw new InvalidOperationException(message);
}
}
await RenderPageCoreAsync(page, context);
return bufferedWriter;
}
finally
// The writer for the body is passed through the ViewContext, allowing things like HtmlHelpers
// and ViewComponents to reference it.
var oldWriter = context.Writer;
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)
{
page.ViewContext = context;
if (EnableInstrumentation)
{
page.PageExecutionContext = _pageExecutionFeature.GetContext(page.Path, context.Writer);
}
_pageActivator.Activate(page, context);
await page.ExecuteAsync();
}
private async Task RenderViewStartAsync(ViewContext context)
{
var viewStarts = _viewStartProvider.GetViewStartPages(_razorPage.Path);
var viewStarts = _viewStartProvider.GetViewStartPages(_razorPage.Path, EnableInstrumentation);
foreach (var viewStart in viewStarts)
{
@ -111,7 +143,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
private async Task RenderLayoutAsync(ViewContext context,
RazorTextWriter bodyWriter)
IBufferedTextWriter bodyWriter)
{
// A layout page can specify another layout page. We'll need to continue
// looking for layout pages until they're no longer specified.
@ -129,7 +161,7 @@ namespace Microsoft.AspNet.Mvc.Razor
throw new InvalidOperationException(message);
}
var layoutPage = _pageFactory.CreateInstance(previousPage.Layout);
var layoutPage = _pageFactory.CreateInstance(previousPage.Layout, EnableInstrumentation);
if (layoutPage == null)
{
var message = Resources.FormatLayoutCannotBeLocated(previousPage.Layout);

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

@ -3,9 +3,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using Microsoft.AspNet.Mvc.Razor.OptionDescriptors;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.PageExecutionInstrumentation;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc.Razor
@ -35,6 +37,9 @@ namespace Microsoft.AspNet.Mvc.Razor
private readonly IRazorPageFactory _pageFactory;
private readonly IReadOnlyList<IViewLocationExpander> _viewLocationExpanders;
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>
/// 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))
{
var page = _pageFactory.CreateInstance(viewName);
var page = _pageFactory.CreateInstance(viewName, IsInstrumentationEnabled(context));
if (page != null)
{
return CreateFoundResult(context, page, viewName, partial);
@ -132,7 +137,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var viewLocation = _viewLocationCache.Get(expanderContext);
if (!string.IsNullOrEmpty(viewLocation))
{
var page = _pageFactory.CreateInstance(viewLocation);
var page = _pageFactory.CreateInstance(viewLocation, IsInstrumentationEnabled(context));
if (page != null)
{
@ -158,7 +163,7 @@ namespace Microsoft.AspNet.Mvc.Razor
viewName,
controllerName,
areaName);
var page = _pageFactory.CreateInstance(transformedPath);
var page = _pageFactory.CreateInstance(transformedPath, IsInstrumentationEnabled(context));
if (page != null)
{
// 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 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);
}
@ -191,5 +198,16 @@ namespace Microsoft.AspNet.Mvc.Razor
{
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;
}
/// <inheritdoc />
public IEnumerable<IRazorPage> GetViewStartPages([NotNull] string path)
public IEnumerable<IRazorPage> GetViewStartPages([NotNull] string path, bool enableInstrumentation)
{
var viewStartLocations = ViewStartUtility.GetViewStartLocations(_fileSystem, path);
var viewStarts = viewStartLocations.Select(_pageFactory.CreateInstance)
.Where(p => p != null)
.ToArray();
var viewStarts = viewStartLocations.Select(p => _pageFactory.CreateInstance(p, enableInstrumentation))
.Where(p => p != null)
.ToArray();
// GetViewStartLocations return ViewStarts inside-out that is the _ViewStart closest to the page
// is the first: e.g. [ /Views/Home/_ViewStart, /Views/_ViewStart, /_ViewStart ]

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

@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
/// <inheritdoc />
public IRazorPage CreateInstance([NotNull] string relativePath)
public IRazorPage CreateInstance([NotNull] string relativePath, bool enableInstrumentation)
{
var fileInfo = _fileInfoCache.GetFileInfo(relativePath);
@ -42,7 +42,7 @@ namespace Microsoft.AspNet.Mvc.Razor
RelativePath = relativePath,
};
var result = _compilationService.Compile(relativeFileInfo);
var result = _compilationService.Compile(relativeFileInfo, enableInstrumentation);
var page = (IRazorPage)_activator.CreateInstance(_serviceProvider, result.CompiledType);
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.
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.Description;
using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.Internal;
@ -49,7 +48,10 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Singleton<ICompilationService, RoslynCompilationService>();
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.Singleton<IViewStartProvider, ViewStartProvider>();
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": "",
"RoutingWebSite": "",
"RazorWebSite": "",
"RazorInstrumentationWebsite": "",
"ValueProvidersSite": "",
"XmlSerializerWebSite": "",
"UrlHelperWebSite": "",

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

@ -34,7 +34,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
};
// Act
var actual = cache.GetOrAdd(runtimeFileInfo, () => expected);
var actual = cache.GetOrAdd(runtimeFileInfo, false, () => expected);
// Assert
Assert.Same(expected, actual);
@ -140,7 +140,8 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
// Act
var actual = cache.GetOrAdd(runtimeFileInfo,
() => CompilationResult.Successful(resultViewType));
enableInstrumentation: false,
compile: () => CompilationResult.Successful(resultViewType));
// Assert
if (swapsPreCompile)
@ -174,9 +175,9 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
};
// Act
cache.GetOrAdd(runtimeFileInfo, () => uncachedResult);
var actual1 = cache.GetOrAdd(runtimeFileInfo, () => uncachedResult);
var actual2 = cache.GetOrAdd(runtimeFileInfo, () => uncachedResult);
cache.GetOrAdd(runtimeFileInfo, false, () => uncachedResult);
var actual1 = cache.GetOrAdd(runtimeFileInfo, false, () => uncachedResult);
var actual2 = cache.GetOrAdd(runtimeFileInfo, false, () => uncachedResult);
// Assert
Assert.NotSame(uncachedResult, actual1);
@ -189,5 +190,40 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
Assert.Null(actual2.CompiledContent);
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
var host = new Mock<IMvcRazorHost>();
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();
var ap = new Mock<IControllerAssemblyProvider>();
@ -46,10 +46,51 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
};
// Act
razorService.CompileCore(relativeFileInfo);
razorService.CompileCore(relativeFileInfo, isInstrumented: false);
// Assert
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
var pageFactory = new Mock<IRazorPageFactory>();
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>());
var viewEngine = CreateViewEngine(pageFactory.Object);
var context = GetActionContext(_controllerTestContext);
@ -203,7 +203,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
// Arrange
var pageFactory = new Mock<IRazorPageFactory>();
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>())
.Verifiable();
var viewEngine = new OverloadedLocationViewEngine(pageFactory.Object,
@ -224,7 +224,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
// Arrange
var pageFactory = new Mock<IRazorPageFactory>();
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>())
.Verifiable();
var viewEngine = new OverloadedLocationViewEngine(pageFactory.Object,
@ -273,7 +273,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
{
// Arrange
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>())
.Verifiable();
var expander1Result = new[] { "some-seed" };
@ -325,9 +325,9 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
{
// Arrange
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();
pageFactory.Setup(p => p.CreateInstance("/Views/Shared/baz.cshtml"))
pageFactory.Setup(p => p.CreateInstance("/Views/Shared/baz.cshtml", false))
.Returns(Mock.Of<IRazorPage>())
.Verifiable();
var cache = GetViewLocationCache();
@ -353,7 +353,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
{
// Arrange
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>())
.Verifiable();
var expander = new Mock<IViewLocationExpander>(MockBehavior.Strict);
@ -384,10 +384,10 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
{
// Arrange
var pageFactory = new Mock<IRazorPageFactory>();
pageFactory.Setup(p => p.CreateInstance("expired-location"))
pageFactory.Setup(p => p.CreateInstance("expired-location", false))
.Returns((IRazorPage)null)
.Verifiable();
pageFactory.Setup(p => p.CreateInstance("some-view-location"))
pageFactory.Setup(p => p.CreateInstance("some-view-location", false))
.Returns(Mock.Of<IRazorPage>())
.Verifiable();
var cacheMock = new Mock<IViewLocationCache>();

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

@ -3,8 +3,10 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.PageExecutionInstrumentation;
using Microsoft.AspNet.PipelineCore;
using Moq;
using Xunit;
@ -45,7 +47,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: true);
view.Contextualize(page, isPartial: true, pageExecutionListener: null);
var viewContext = CreateViewContext(view);
var expected = viewContext.Writer;
@ -72,7 +74,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
activator.Object,
CreateViewStartProvider());
view.Contextualize(page, isPartial: true);
view.Contextualize(page, isPartial: true, pageExecutionListener: null);
var viewContext = CreateViewContext(view);
var expectedWriter = viewContext.Writer;
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>(),
activator.Object,
CreateViewStartProvider());
view.Contextualize(page, isPartial: true);
view.Contextualize(page, isPartial: true, pageExecutionListener: null);
var viewContext = CreateViewContext(view);
// Act
@ -124,15 +126,16 @@ namespace Microsoft.AspNet.Mvc.Razor
var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(),
viewStartProvider);
view.Contextualize(page, isPartial: true);
view.Contextualize(page, isPartial: true, pageExecutionListener: null);
var viewContext = CreateViewContext(view);
// Act
await view.RenderAsync(viewContext);
// Assert
pageFactory.Verify(v => v.CreateInstance(It.IsAny<string>()), Times.Never());
Mock.Get(viewStartProvider).Verify(v => v.GetViewStartPages(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>(), It.IsAny<bool>()), Times.Never());
}
[Fact]
@ -147,7 +150,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
var viewContext = CreateViewContext(view);
var original = viewContext.Writer;
@ -170,7 +173,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
var viewContext = CreateViewContext(view);
var original = viewContext.Writer;
@ -195,7 +198,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
activator.Object,
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
var viewContext = CreateViewContext(view);
@ -237,7 +240,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
activator.Object,
CreateViewStartProvider(viewStart1, viewStart2));
view.Contextualize(page, isPartial: false);
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
var viewContext = CreateViewContext(view);
// Act
@ -285,13 +288,13 @@ foot-content";
activator.Setup(a => a.Activate(layout, It.IsAny<ViewContext>()))
.Verifiable();
var pageFactory = new Mock<IRazorPageFactory>();
pageFactory.Setup(p => p.CreateInstance(LayoutPath))
pageFactory.Setup(p => p.CreateInstance(LayoutPath, false))
.Returns(layout);
var view = new RazorView(pageFactory.Object,
activator.Object,
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
var viewContext = CreateViewContext(view);
// Act
@ -318,13 +321,13 @@ foot-content";
v.RenderBodyPublic();
});
var pageFactory = new Mock<IRazorPageFactory>();
pageFactory.Setup(p => p.CreateInstance(LayoutPath))
pageFactory.Setup(p => p.CreateInstance(LayoutPath, false))
.Returns(layout);
var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
var viewContext = CreateViewContext(view);
// Act and Assert
@ -344,13 +347,13 @@ foot-content";
{
});
var pageFactory = new Mock<IRazorPageFactory>();
pageFactory.Setup(p => p.CreateInstance(LayoutPath))
pageFactory.Setup(p => p.CreateInstance(LayoutPath, false))
.Returns(layout);
var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
var viewContext = CreateViewContext(view);
// Act and Assert
@ -396,15 +399,15 @@ body-content";
v.RenderBodyPublic();
});
var pageFactory = new Mock<IRazorPageFactory>();
pageFactory.Setup(p => p.CreateInstance("~/Shared/Layout1.cshtml"))
pageFactory.Setup(p => p.CreateInstance("~/Shared/Layout1.cshtml", false))
.Returns(layout1);
pageFactory.Setup(p => p.CreateInstance("~/Shared/Layout2.cshtml"))
pageFactory.Setup(p => p.CreateInstance("~/Shared/Layout2.cshtml", false))
.Returns(layout2);
var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
var viewContext = CreateViewContext(view);
// Act
@ -444,13 +447,13 @@ section-content-2";
});
var pageFactory = new Mock<IRazorPageFactory>();
pageFactory.Setup(p => p.CreateInstance("layout-1"))
pageFactory.Setup(p => p.CreateInstance("layout-1", false))
.Returns(layout1);
var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
var viewContext = CreateViewContext(view);
// Act
@ -488,13 +491,13 @@ section-content-2";
});
var pageFactory = new Mock<IRazorPageFactory>();
pageFactory.Setup(p => p.CreateInstance("layout-1"))
pageFactory.Setup(p => p.CreateInstance("layout-1", false))
.Returns(layout1);
var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
var viewContext = CreateViewContext(view);
// Act
@ -520,7 +523,7 @@ section-content-2";
var view = new RazorView(Mock.Of<IRazorPageFactory>(),
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
var viewContext = CreateViewContext(view);
// Act and Assert
@ -555,13 +558,13 @@ section-content-2";
v.Layout = "~/Shared/Layout2.cshtml";
});
var pageFactory = new Mock<IRazorPageFactory>();
pageFactory.Setup(p => p.CreateInstance("~/Shared/Layout1.cshtml"))
pageFactory.Setup(p => p.CreateInstance("~/Shared/Layout1.cshtml", false))
.Returns(layout1);
var view = new RazorView(pageFactory.Object,
Mock.Of<IRazorPageActivator>(),
CreateViewStartProvider());
view.Contextualize(page, isPartial: false);
view.Contextualize(page, isPartial: false, pageExecutionListener: null);
var viewContext = CreateViewContext(view);
// Act and Assert
@ -569,6 +572,153 @@ section-content-2";
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)
{
var httpContext = new DefaultHttpContext();
@ -584,7 +734,7 @@ section-content-2";
{
viewStartPages = viewStartPages ?? new IRazorPage[0];
var viewStartProvider = new Mock<IViewStartProvider>();
viewStartProvider.Setup(v => v.GetViewStartPages(It.IsAny<string>()))
viewStartProvider.Setup(v => v.GetViewStartPages(It.IsAny<string>(), false))
.Returns(viewStartPages);
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": { }
}
}