Added logging for precompiled views in RazorViewEngine

Addresses #5462
This commit is contained in:
Jass Bagga 2016-11-07 10:46:54 -08:00 committed by GitHub
parent 1dc4b28bbe
commit 304000095a
8 changed files with 92 additions and 4 deletions

View File

@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
foreach (var item in views)
{
var cacheEntry = new CompilerCacheResult(item.Key, new CompilationResult(item.Value));
var cacheEntry = new CompilerCacheResult(item.Key, new CompilationResult(item.Value), isPrecompiled: true);
_cache.Set(GetNormalizedPath(item.Key), Task.FromResult(cacheEntry));
}
}

View File

@ -27,6 +27,19 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
{
}
/// <summary>
/// Initializes a new instance of <see cref="CompilerCacheResult"/> with the specified
/// <see cref="Compilation.CompilationResult"/>.
/// </summary>
/// <param name="relativePath">Path of the view file relative to the application base.</param>
/// <param name="compilationResult">The <see cref="Compilation.CompilationResult"/>.</param>
/// <param name="isPrecompiled"><c>true</c> if the view is precompiled, <c>false</c> otherwise.</param>
public CompilerCacheResult(string relativePath, CompilationResult compilationResult, bool isPrecompiled)
: this(relativePath, compilationResult, EmptyArray<IChangeToken>.Instance)
{
IsPrecompiled = isPrecompiled;
}
/// <summary>
/// Initializes a new instance of <see cref="CompilerCacheResult"/> with the specified
/// <see cref="Compilation.CompilationResult"/>.
@ -51,9 +64,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
var propertyBindExpression = Expression.Bind(pathProperty, Expression.Constant(relativePath));
var objectInitializeExpression = Expression.MemberInit(newExpression, propertyBindExpression);
PageFactory = Expression
PageFactory = Expression
.Lambda<Func<IRazorPage>>(objectInitializeExpression)
.Compile();
IsPrecompiled = false;
}
/// <summary>
@ -71,6 +85,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
ExpirationTokens = expirationTokens;
PageFactory = null;
IsPrecompiled = false;
}
/// <summary>
@ -87,5 +102,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
/// Gets a delegate that creates an instance of the <see cref="IRazorPage"/>.
/// </summary>
public Func<IRazorPage> PageFactory { get; }
/// <summary>
/// Gets a value that determines if the view is precompiled.
/// </summary>
public bool IsPrecompiled { get; }
}
}

View File

@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
var result = CompilerCache.GetOrAdd(relativePath, _compileDelegate);
if (result.Success)
{
return new RazorPageFactoryResult(result.PageFactory, result.ExpirationTokens);
return new RazorPageFactoryResult(result.PageFactory, result.ExpirationTokens, result.IsPrecompiled);
}
else
{

View File

@ -19,6 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
private static readonly Action<ILogger, string, string, Exception> _viewLookupCacheMiss;
private static readonly Action<ILogger, string, string, Exception> _viewLookupCacheHit;
private static readonly Action<ILogger, string, Exception> _precompiledViewFound;
static MvcRazorLoggerExtensions()
{
@ -42,6 +43,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
2,
"View lookup cache hit for view '{ViewName}' in controller '{ControllerName}'.");
_precompiledViewFound = LoggerMessage.Define<string>(
LogLevel.Debug,
3,
"Using precompiled view for '{RelativePath}'.");
_generatedCodeToAssemblyCompilationStart = LoggerMessage.Define<string>(
LogLevel.Debug,
1,
@ -79,6 +85,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
_viewLookupCacheHit(logger, viewName, controllerName, null);
}
public static void PrecompiledViewFound(this ILogger logger, string relativePath)
{
_precompiledViewFound(logger, relativePath, null);
}
public static void GeneratedCodeToAssemblyCompilationStart(this ILogger logger, string filePath)
{
_generatedCodeToAssemblyCompilationStart(logger, filePath, null);

View File

@ -26,6 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
ExpirationTokens = expirationTokens;
RazorPageFactory = null;
IsPrecompiled = false;
}
/// <summary>
@ -37,6 +38,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor
public RazorPageFactoryResult(
Func<IRazorPage> razorPageFactory,
IList<IChangeToken> expirationTokens)
: this(razorPageFactory, expirationTokens, isPrecompiled: false)
{
}
/// <summary>
/// Initializes a new instance of <see cref="RazorPageFactoryResult"/> with the
/// specified <see cref="IRazorPage"/> factory.
/// </summary>
/// <param name="razorPageFactory">The <see cref="IRazorPage"/> factory.</param>
/// <param name="expirationTokens">One or more <see cref="IChangeToken"/> instances.</param>
/// <param name="isPrecompiled"><c>true</c> if the view is precompiled, <c>false</c> otherwise.</param>
public RazorPageFactoryResult(
Func<IRazorPage> razorPageFactory,
IList<IChangeToken> expirationTokens,
bool isPrecompiled)
{
if (razorPageFactory == null)
{
@ -50,6 +66,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
RazorPageFactory = razorPageFactory;
ExpirationTokens = expirationTokens;
IsPrecompiled = isPrecompiled;
}
/// <summary>
@ -68,5 +85,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor
/// Gets a value that determines if the page was successfully located.
/// </summary>
public bool Success => RazorPageFactory != null;
/// <summary>
/// Gets a value that determines if the view is precompiled.
/// </summary>
public bool IsPrecompiled { get; }
}
}

View File

@ -395,7 +395,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor
return ViewLookupCache.Set<ViewLocationCacheResult>(cacheKey, cacheResult, cacheEntryOptions);
}
private ViewLocationCacheResult CreateCacheResult(
// Internal for unit testing
internal ViewLocationCacheResult CreateCacheResult(
HashSet<IChangeToken> expirationTokens,
string relativePath,
bool isMainPage)
@ -415,6 +416,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor
var viewStartPages = isMainPage ?
GetViewStartPages(relativePath, expirationTokens) :
EmptyArray<ViewLocationCacheItem>.Instance;
if (factoryResult.IsPrecompiled)
{
_logger.PrecompiledViewFound(relativePath);
}
return new ViewLocationCacheResult(
new ViewLocationCacheItem(factoryResult.RazorPageFactory, relativePath),

View File

@ -241,6 +241,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
// Assert
Assert.True(result.Success);
Assert.True(result.IsPrecompiled);
Assert.IsType<PreCompile>(result.PageFactory());
}

View File

@ -1283,6 +1283,35 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
Assert.Equal(expected, result.SearchedLocations);
}
[Fact]
public void CreateCacheResult_LogsPrecompiledViewFound()
{
// Arrange
var sink = new TestSink();
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
var relativePath = "/Views/Foo/details.cshtml";
var pageFactory = new Mock<IRazorPageFactoryProvider>();
pageFactory
.Setup(p => p.CreateFactory(relativePath))
.Returns(new RazorPageFactoryResult(() => Mock.Of<IRazorPage>(), new IChangeToken[0], isPrecompiled: true))
.Verifiable();
var viewEngine = new RazorViewEngine(
pageFactory.Object,
Mock.Of<IRazorPageActivator>(),
new HtmlTestEncoder(),
GetOptionsAccessor(expanders: null),
loggerFactory);
// Act
var result = viewEngine.CreateCacheResult(null, relativePath, false);
// Assert
var logMessage = Assert.Single(sink.Writes);
Assert.Equal($"Using precompiled view for '{relativePath}'.", logMessage.State.ToString());
}
[Theory]
[InlineData("/Test-View.cshtml")]
[InlineData("~/Test-View.CSHTML")]