From 04167676b9f84303534cea79f60ae0f8be7b9c22 Mon Sep 17 00:00:00 2001 From: Alxandr Date: Tue, 28 Apr 2015 10:32:06 +0200 Subject: [PATCH 1/4] Allow returning precompiled views without sources --- .../Compilation/CompilerCache.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs index 0422c39e69..09fb648d47 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs @@ -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); From 202e4a81058a21cfb2b4e6d9a3adfb729bca4ef3 Mon Sep 17 00:00:00 2001 From: Alxandr Date: Wed, 29 Apr 2015 22:50:05 +0200 Subject: [PATCH 2/4] Added unit tests --- .../PrecompilationTest.cs | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs index 2633a64a81..81e577c7bb 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs @@ -162,6 +162,97 @@ 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(); + + 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 - 1 + var response = await client.GetAsync("http://localhost/Home/Index"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert - 1 + 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); + + // Act - 2 + // Delete the Layout file and verify it is still served. + await DeleteFile(viewsDirectory, "Layout.cshtml"); + responseContent = await client.GetStringAsync("http://localhost/Home/Index"); + + // Assert - 2 + var response2 = new ParsedResponse(responseContent); + Assert.StartsWith(assemblyNamePrefix, response2.ViewStart); + Assert.StartsWith(assemblyNamePrefix, response2.Index); + Assert.StartsWith(assemblyNamePrefix, response2.Layout); + + // Act - 3 + // Delete the _ViewStart file and verify it is still served. + await DeleteFile(viewsDirectory, "_ViewStart.cshtml"); + responseContent = await client.GetStringAsync("http://localhost/Home/Index"); + + // Assert - 3 + var response3 = new ParsedResponse(responseContent); + Assert.StartsWith(assemblyNamePrefix, response3.ViewStart); + Assert.StartsWith(response2.Index, response3.Index); + Assert.StartsWith(response2.Layout, response3.Layout); + + // Act - 4 + // Delete the _GlobalImport file and verify it is still served. + await DeleteFile(viewsDirectory, "_GlobalImport.cshtml"); + responseContent = await client.GetStringAsync("http://localhost/Home/Index"); + + // Assert - 4 + var response4 = new ParsedResponse(responseContent); + Assert.StartsWith(response3.ViewStart, response4.ViewStart); + Assert.StartsWith(response3.Index, response4.Index); + Assert.StartsWith(response3.Layout, response4.Layout); + + // Act - 5 + // Delete the Index file and verify it is still served. + await DeleteFile(viewsDirectory, "Index.cshtml"); + responseContent = await client.GetStringAsync("http://localhost/Home/Index"); + + // Assert - 5 + var response5 = new ParsedResponse(responseContent); + Assert.StartsWith(response4.Layout, response5.Layout); + Assert.StartsWith(response4.ViewStart, response5.ViewStart); + Assert.StartsWith(response4.Index, response5.Index); + } + finally + { + File.WriteAllText(Path.Combine(viewsDirectory, "Layout.cshtml"), layoutContent.TrimEnd(' ')); + 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() @@ -284,6 +375,17 @@ private static async Task TouchFile(string viewsDir, string file) return path; } + + private static async Task 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 { From 8d9001685dd90755e0ffd9760353a57d4e567be8 Mon Sep 17 00:00:00 2001 From: Alxandr Date: Sat, 9 May 2015 21:30:22 +0200 Subject: [PATCH 3/4] fixed tests --- .../PrecompilationTest.cs | 54 +++---------------- 1 file changed, 7 insertions(+), 47 deletions(-) diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs index 81e577c7bb..4263c350b3 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs @@ -190,60 +190,20 @@ public async Task PrecompiledView_RendersWithoutSourceFile() try { - // Act - 1 + // 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 - 1 + + // 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); - - // Act - 2 - // Delete the Layout file and verify it is still served. - await DeleteFile(viewsDirectory, "Layout.cshtml"); - responseContent = await client.GetStringAsync("http://localhost/Home/Index"); - - // Assert - 2 - var response2 = new ParsedResponse(responseContent); - Assert.StartsWith(assemblyNamePrefix, response2.ViewStart); - Assert.StartsWith(assemblyNamePrefix, response2.Index); - Assert.StartsWith(assemblyNamePrefix, response2.Layout); - - // Act - 3 - // Delete the _ViewStart file and verify it is still served. - await DeleteFile(viewsDirectory, "_ViewStart.cshtml"); - responseContent = await client.GetStringAsync("http://localhost/Home/Index"); - - // Assert - 3 - var response3 = new ParsedResponse(responseContent); - Assert.StartsWith(assemblyNamePrefix, response3.ViewStart); - Assert.StartsWith(response2.Index, response3.Index); - Assert.StartsWith(response2.Layout, response3.Layout); - - // Act - 4 - // Delete the _GlobalImport file and verify it is still served. - await DeleteFile(viewsDirectory, "_GlobalImport.cshtml"); - responseContent = await client.GetStringAsync("http://localhost/Home/Index"); - - // Assert - 4 - var response4 = new ParsedResponse(responseContent); - Assert.StartsWith(response3.ViewStart, response4.ViewStart); - Assert.StartsWith(response3.Index, response4.Index); - Assert.StartsWith(response3.Layout, response4.Layout); - - // Act - 5 - // Delete the Index file and verify it is still served. - await DeleteFile(viewsDirectory, "Index.cshtml"); - responseContent = await client.GetStringAsync("http://localhost/Home/Index"); - - // Assert - 5 - var response5 = new ParsedResponse(responseContent); - Assert.StartsWith(response4.Layout, response5.Layout); - Assert.StartsWith(response4.ViewStart, response5.ViewStart); - Assert.StartsWith(response4.Index, response5.Index); } finally { From 7e549d955ddd1f8f4ec7b15f71293b2c0da455ef Mon Sep 17 00:00:00 2001 From: Alxandr Date: Sat, 9 May 2015 21:46:15 +0200 Subject: [PATCH 4/4] Fixed another test --- .../Compilation/CompilerCacheTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs index 43526b2d91..f83caf1cbb 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs @@ -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(); @@ -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]