// 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.IO; using System.Linq; using System.Net; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.TestHost; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.Runtime; using PrecompilationWebSite; using Xunit; namespace Microsoft.AspNet.Mvc.FunctionalTests { public class PrecompilationTest { private static readonly TimeSpan _cacheDelayInterval = TimeSpan.FromSeconds(2); private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(PrecompilationWebSite)); private readonly Action _app = new Startup().Configure; [Fact] public async Task PrecompiledView_RendersCorrectly() { // Arrange var applicationEnvironment = _services.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 server = TestServer.Create(_services, _app); var client = server.CreateClient(); // 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 assemblyName = typeof(Startup).GetTypeInfo().Assembly.GetName().ToString(); 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.Equal(assemblyName, parsedResponse1.ViewStart); Assert.Equal(assemblyName, parsedResponse1.Layout); Assert.Equal(assemblyName, parsedResponse1.Index); // Act - 2 // Touch the Layout file and verify it is now dynamically compiled. await TouchFile(viewsDirectory, "Layout.cshtml"); responseContent = await client.GetStringAsync("http://localhost/Home/Index"); // Assert - 2 var response2 = new ParsedResponse(responseContent); Assert.NotEqual(assemblyName, response2.Layout); Assert.Equal(assemblyName, response2.ViewStart); Assert.Equal(assemblyName, response2.Index); // Act - 3 // Touch the _ViewStart file and verify it causes all files to recompile. await TouchFile(viewsDirectory, "_ViewStart.cshtml"); responseContent = await client.GetStringAsync("http://localhost/Home/Index"); // Assert - 3 var response3 = new ParsedResponse(responseContent); Assert.NotEqual(assemblyName, response3.ViewStart); Assert.NotEqual(assemblyName, response3.Index); Assert.NotEqual(response2.Layout, response3.Layout); // Act - 4 // Touch Index file and verify it is the only page that recompiles. await TouchFile(viewsDirectory, "Index.cshtml"); responseContent = await client.GetStringAsync("http://localhost/Home/Index"); // Assert - 4 var response4 = new ParsedResponse(responseContent); // Layout and _ViewStart should not have changed. Assert.Equal(response3.Layout, response4.Layout); Assert.Equal(response3.ViewStart, response4.ViewStart); Assert.NotEqual(response3.Index, response4.Index); // Act - 5 // Touch the _ViewStart file. This time, we'll verify the Non-precompiled -> Non-precompiled workflow. await TouchFile(viewsDirectory, "_ViewStart.cshtml"); responseContent = await client.GetStringAsync("http://localhost/Home/Index"); // Assert - 5 var response5 = new ParsedResponse(responseContent); // Everything should've recompiled. Assert.NotEqual(response4.ViewStart, response5.ViewStart); Assert.NotEqual(response4.Index, response5.Index); Assert.NotEqual(response4.Layout, response5.Layout); // Act - 6 // Add a new _ViewStart file File.WriteAllText(Path.Combine(viewsDirectory, "..", "_ViewStart.cshtml"), string.Empty); await Task.Delay(_cacheDelayInterval); responseContent = await client.GetStringAsync("http://localhost/Home/Index"); // Assert - 6 // Everything should've recompiled. var response6 = new ParsedResponse(responseContent); Assert.NotEqual(response5.ViewStart, response6.ViewStart); Assert.NotEqual(response5.Index, response6.Index); Assert.NotEqual(response5.Layout, response6.Layout); // Act - 7 // Remove new _ViewStart file File.Delete(Path.Combine(viewsDirectory, "..", "_ViewStart.cshtml")); await Task.Delay(_cacheDelayInterval); responseContent = await client.GetStringAsync("http://localhost/Home/Index"); // Assert - 7 // Everything should've recompiled. var response7 = new ParsedResponse(responseContent); Assert.NotEqual(response6.ViewStart, response7.ViewStart); Assert.NotEqual(response6.Index, response7.Index); Assert.NotEqual(response6.Layout, response7.Layout); // Act - 8 // Refetch and verify we get cached types responseContent = await client.GetStringAsync("http://localhost/Home/Index"); // Assert - 7 var response8 = new ParsedResponse(responseContent); Assert.Equal(response7.ViewStart, response8.ViewStart); Assert.Equal(response7.Index, response8.Index); Assert.Equal(response7.Layout, response8.Layout); } finally { File.WriteAllText(Path.Combine(viewsDirectory, "Layout.cshtml"), layoutContent); File.WriteAllText(Path.Combine(viewsDirectory, "Index.cshtml"), indexContent); File.WriteAllText(Path.Combine(viewsDirectory, "_ViewStart.cshtml"), viewstartContent); } } [Fact] public async Task PrecompiledView_UsesCompilationOptionsFromApplication() { // Arrange var assemblyName = typeof(Startup).GetTypeInfo().Assembly.GetName().ToString(); #if ASPNET50 var expected = @"Value set inside ASPNET50 " + assemblyName; #elif ASPNETCORE50 var expected = @"Value set inside ASPNETCORE50 " + assemblyName; #endif var server = TestServer.Create(_services, _app); var client = server.CreateClient(); // Act var response = await client.GetAsync("http://localhost/Home/PrecompiledViewsCanConsumeCompilationOptions"); var responseContent = await response.Content.ReadAsStringAsync(); // Assert Assert.Equal(expected, responseContent.Trim()); } private static Task TouchFile(string viewsDir, string file) { File.AppendAllText(Path.Combine(viewsDir, file), " "); // Delay to ensure we don't hit the cached file system. return Task.Delay(_cacheDelayInterval); } private sealed class ParsedResponse { public ParsedResponse(string responseContent) { var results = responseContent.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) .Select(s => s.Trim()) .ToArray(); Assert.True(results[0].StartsWith("Layout:")); Layout = results[0].Substring("Layout:".Length); Assert.True(results[1].StartsWith("_viewstart:")); ViewStart = results[1].Substring("_viewstart:".Length); Assert.True(results[2].StartsWith("index:")); Index = results[2].Substring("index:".Length); } public string Layout { get; } public string ViewStart { get; } public string Index { get; } } } }