Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Allow returning precompiled views without sources #2463

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,14 @@ private GetOrAddResult GetOrAddCore(string relativePath,
var fileInfo = _fileProvider.GetFileInfo(relativePath);
if (!fileInfo.Exists)
{
return null;
// Assigning to IsValidatedPreCompiled is an atomic operation and will result in a safe race
// if it is being concurrently updated and read.
cacheEntry.IsValidatedPreCompiled = true;
return new GetOrAddResult
{
CompilationResult = CompilationResult.Successful(cacheEntry.CompiledType),
CompilerCacheEntry = cacheEntry
};
}

var relativeFileInfo = new RelativeFileInfo(fileInfo, relativePath);
Expand Down
62 changes: 62 additions & 0 deletions test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,57 @@ public async Task PrecompiledView_RendersCorrectly()
File.WriteAllText(Path.Combine(viewsDirectory, "_GlobalImport.cshtml"), globalContent.TrimEnd(' '));
}
}

[Fact]
public async Task PrecompiledView_RendersWithoutSourceFile()
{
// Arrange
IServiceCollection serviceCollection = null;
var server = TestHelper.CreateServer(_app, SiteName, services =>
{
_configureServices(services);
serviceCollection = services;
});
var client = server.CreateClient();

var serviceProvider = serviceCollection.BuildServiceProvider();
var applicationEnvironment = serviceProvider.GetRequiredService<IApplicationEnvironment>();

var viewsDirectory = Path.Combine(applicationEnvironment.ApplicationBasePath, "Views", "Home");
var layoutContent = File.ReadAllText(Path.Combine(viewsDirectory, "Layout.cshtml"));
var indexContent = File.ReadAllText(Path.Combine(viewsDirectory, "Index.cshtml"));
var viewstartContent = File.ReadAllText(Path.Combine(viewsDirectory, "_ViewStart.cshtml"));
var globalContent = File.ReadAllText(Path.Combine(viewsDirectory, "_GlobalImport.cshtml"));

// We will render a view that writes the fully qualified name of the Assembly containing the type of
// the view. If the view is precompiled, this assembly will be PrecompilationWebsite.
var assemblyNamePrefix = GetAssemblyNamePrefix();

try
{
// Act
await DeleteFile(viewsDirectory, "_ViewStart.cshtml");
await DeleteFile(viewsDirectory, "_Layout.cshtml");
await DeleteFile(viewsDirectory, "Index.cshtml");

var response = await client.GetAsync("http://localhost/Home/Index");
var responseContent = await response.Content.ReadAsStringAsync();

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var parsedResponse1 = new ParsedResponse(responseContent);
Assert.StartsWith(assemblyNamePrefix, parsedResponse1.ViewStart);
Assert.StartsWith(assemblyNamePrefix, parsedResponse1.Layout);
Assert.StartsWith(assemblyNamePrefix, parsedResponse1.Index);
}
finally
{
File.WriteAllText(Path.Combine(viewsDirectory, "Layout.cshtml"), layoutContent.TrimEnd(' '));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's with the trimming at the end?

File.WriteAllText(Path.Combine(viewsDirectory, "Index.cshtml"), indexContent.TrimEnd(' '));
File.WriteAllText(Path.Combine(viewsDirectory, "_ViewStart.cshtml"), viewstartContent.TrimEnd(' '));
File.WriteAllText(Path.Combine(viewsDirectory, "_GlobalImport.cshtml"), globalContent.TrimEnd(' '));
}
}

[Fact]
public async Task PrecompiledView_UsesCompilationOptionsFromApplication()
Expand Down Expand Up @@ -284,6 +335,17 @@ private static async Task<string> TouchFile(string viewsDir, string file)

return path;
}

private static async Task<string> DeleteFile(string viewsDir, string file)
{
var path = Path.Combine(viewsDir, file);
File.Delete(path);

// Delay to allow the file system watcher to catch up.
await Task.Delay(_cacheDelayInterval);

return path;
}

private sealed class ParsedResponse
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ public void GetOrAdd_UsesValueFromCache_IfGlobalHasNotChanged()
}

[Fact]
public void GetOrAdd_ReturnsFileNotFoundResult_IfPrecompiledViewWasRemovedFromFileSystem()
public void GetOrAdd_ReturnsPrecompiledView_IfPrecompiledViewWasRemovedFromFileSystem()
{
// Arrange
var precompiledViews = new ViewCollection();
Expand All @@ -305,8 +305,8 @@ public void GetOrAdd_ReturnsFileNotFoundResult_IfPrecompiledViewWasRemovedFromFi
compile: _ => { throw new Exception("shouldn't be invoked"); });

// Assert
Assert.Same(CompilerCacheResult.FileNotFound, result);
Assert.Null(result.CompilationResult);
Assert.NotSame(CompilerCacheResult.FileNotFound, result);
Assert.NotNull(result.CompilationResult);
}

[Fact]
Expand Down