// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections.Generic; using System.Collections.Specialized; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Web.WebPages.TestUtils; using Xunit; using Assert = Microsoft.TestCommon.AssertEx; namespace System.Web.WebPages.Deployment.Test { public class PreApplicationStartCodeTest { private const string DeploymentVersionFile = "System.Web.WebPages.Deployment"; private static readonly Version MaxVersion = new Version(2, 0, 0, 0); [Fact] public void PreApplicationStartCodeDoesNothingIfWebPagesIsExplicitlyDisabled() { // Arrange Version loadedVersion = null; bool registeredForChangeNotification = false; IEnumerable loadedAssemblies = GetAssemblies("1", "2"); var fileSystem = new TestFileSystem(); var buildManager = new TestBuildManager(); var nameValueCollection = GetAppSettings(enabled: false, webPagesVersion: null); Action loadWebPages = (version) => { loadedVersion = version; }; Action registerForChange = () => { registeredForChangeNotification = true; }; // Act bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", "bin", nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, null); // Assert Assert.False(loaded); Assert.Null(loadedVersion); Assert.False(registeredForChangeNotification); Assert.Equal(0, buildManager.Stream.Length); } [Fact] public void PreApplicationStartCodeUsesVersionSpecifiedInConfigIfWebPagesIsImplicitlyEnabled() { // Arrange Version loadedVersion = null; bool registeredForChangeNotification = false; IEnumerable loadedAssemblies = GetAssemblies("1.12.123.1234", "2.0.0.0"); Version webPagesVersion = new Version("1.12.123.1234"); var fileSystem = new TestFileSystem(); fileSystem.AddFile("Default.cshtml"); var buildManager = new TestBuildManager(); var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: webPagesVersion); Action loadWebPages = (version) => { loadedVersion = version; }; Action registerForChange = () => { registeredForChangeNotification = true; }; // Act bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", "bin", nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, getAssemblyNameThunk: null); // Assert Assert.True(loaded); Assert.Equal(webPagesVersion, loadedVersion); Assert.False(registeredForChangeNotification); VerifyVersionFile(buildManager, webPagesVersion); } [Fact] public void PreApplicationStartCodeDoesNotLoadCurrentWebPagesIfOnlyVersionIsListedInConfigAndNoFilesAreFoundInSiteRoot() { // Arrange Version loadedVersion = null; bool registeredForChangeNotification = false; Version webPagesVersion = AssemblyUtils.ThisAssemblyName.Version; IEnumerable loadedAssemblies = GetAssemblies("2.0.0.0"); var fileSystem = new TestFileSystem(); var buildManager = new TestBuildManager(); var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: webPagesVersion); Action loadWebPages = (version) => { loadedVersion = version; }; Action registerForChange = () => { registeredForChangeNotification = true; }; // Arrange bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", "bin", nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, null); // Assert Assert.False(loaded); Assert.Null(loadedVersion); Assert.True(registeredForChangeNotification); Assert.Equal(0, buildManager.Stream.Length); } [Fact] public void PreApplicationStartCodeRegistersForChangeNotificationIfNotExplicitlyDisabledAndNoFilesFoundInSiteRoot() { // Arrange Version loadedVersion = null; bool registeredForChangeNotification = false; IEnumerable loadedAssemblies = GetAssemblies("2.0.0.0"); var fileSystem = new TestFileSystem(); var buildManager = new TestBuildManager(); var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: null); Action loadWebPages = (version) => { loadedVersion = version; }; Action registerForChange = () => { registeredForChangeNotification = true; }; // Act bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", "bin", nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, null); // Assert Assert.False(loaded); Assert.Null(loadedVersion); Assert.True(registeredForChangeNotification); Assert.Equal(0, buildManager.Stream.Length); } [Fact] public void PreApplicationStartCodeDoesNothingIfV1IsAvailableInBinAndSiteIsExplicitlyEnabled() { // Arrange Version loadedVersion = null; bool registeredForChangeNotification = false; var v1Version = new Version("1.0.0.0"); IEnumerable loadedAssemblies = GetAssemblies("1.0.0.0", "2.0.0.0"); var binDirectory = DeploymentUtil.GetBinDirectory(); var fileSystem = new TestFileSystem(); fileSystem.AddFile(Path.Combine(binDirectory, "System.Web.WebPages.Deployment.dll")); var buildManager = new TestBuildManager(); var nameValueCollection = GetAppSettings(enabled: true, webPagesVersion: null); Action loadWebPages = (version) => { loadedVersion = version; }; Action registerForChange = () => { registeredForChangeNotification = true; }; Func getAssembyName = _ => new AssemblyName("System.Web.WebPages.Deployment, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"); // Act bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", binDirectory, nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, getAssembyName); // Assert Assert.False(loaded); Assert.Null(loadedVersion); Assert.False(registeredForChangeNotification); Assert.Equal(0, buildManager.Stream.Length); } [Fact] public void PreApplicationStartCodeDoesNothingIfV1IsAvailableInBinAndFileExistsInRootOfWebSite() { // Arrange Version loadedVersion = null; bool registeredForChangeNotification = false; var v1Version = new Version("1.0.0.0"); IEnumerable loadedAssemblies = GetAssemblies("1.0.0.0", "2.0.0.0"); var binDirectory = DeploymentUtil.GetBinDirectory(); var fileSystem = new TestFileSystem(); var buildManager = new TestBuildManager(); fileSystem.AddFile("Default.cshtml"); fileSystem.AddFile(Path.Combine(binDirectory, "System.Web.WebPages.Deployment.dll")); var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: null); Action loadWebPages = (version) => { loadedVersion = version; }; Action registerForChange = () => { registeredForChangeNotification = true; }; Func getAssembyName = _ => new AssemblyName("System.Web.WebPages.Deployment, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"); // Act bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", binDirectory, nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, getAssembyName); // Assert Assert.False(loaded); Assert.Null(loadedVersion); Assert.False(registeredForChangeNotification); Assert.Equal(0, buildManager.Stream.Length); } [Fact] public void PreApplicationStartCodeDoesNothingIfItIsAvailableInBinAndFileExistsInRootOfWebSite() { // Arrange Version loadedVersion = null; bool registeredForChangeNotification = false; var webPagesVersion = AssemblyUtils.ThisAssemblyName.Version; IEnumerable loadedAssemblies = GetAssemblies(AssemblyUtils.ThisAssemblyName.Version.ToString()); var fileSystem = new TestFileSystem(); var binDirectory = DeploymentUtil.GetBinDirectory(); var buildManager = new TestBuildManager(); fileSystem.AddFile("Default.vbhtml"); fileSystem.AddFile(Path.Combine(binDirectory, "System.Web.WebPages.Deployment.dll")); var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: null); Action loadWebPages = (version) => { loadedVersion = version; }; Action registerForChange = () => { registeredForChangeNotification = true; }; Func getAssembyName = _ => new AssemblyName("System.Web.WebPages.Deployment, Version=" + AssemblyUtils.ThisAssemblyName.Version.ToString() + ", Culture=neutral, PublicKeyToken=31bf3856ad364e35"); // Act bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", binDirectory, nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, getAssembyName); // Assert Assert.False(loaded); Assert.Null(loadedVersion); Assert.False(registeredForChangeNotification); Assert.Equal(0, buildManager.Stream.Length); } [Fact] public void PreApplicationStartCodeLoadsMaxVersionIfNoVersionIsSpecifiedAndCurrentAssemblyIsTheMaximumVersionAvailable() { // Arrange Version loadedVersion = null; bool registeredForChangeNotification = false; var webPagesVersion = AssemblyUtils.ThisAssemblyName.Version; var v1Version = new Version("1.0.0.0"); IEnumerable loadedAssemblies = GetAssemblies("1.0.0.0", "2.0.0.0"); // Note: For this test to work with future versions we would need to create corresponding embedded resources with that version in it. var fileSystem = new TestFileSystem(); var buildManager = new TestBuildManager(); fileSystem.AddFile("Index.cshtml"); var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: null); Action loadWebPages = (version) => { loadedVersion = version; }; Action registerForChange = () => { registeredForChangeNotification = true; }; // Act bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", "bin", nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, null); // Assert Assert.True(loaded); Assert.Equal(MaxVersion, loadedVersion); Assert.False(registeredForChangeNotification); VerifyVersionFile(buildManager, MaxVersion); } [Fact] public void PreApplicationStartCodeDoesNotLoadIfAHigherVersionIsAvailableInBin() { // Arrange Version loadedVersion = null; bool registeredForChangeNotification = false; IEnumerable loadedAssemblies = GetAssemblies("2.0.0.0", "8.0.0.0"); var binDirectory = DeploymentUtil.GetBinDirectory(); var fileSystem = new TestFileSystem(); fileSystem.AddFile("Index.cshtml"); fileSystem.AddFile(Path.Combine(binDirectory, "System.Web.WebPages.Deployment.dll")); var buildManager = new TestBuildManager(); var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: null); Action loadWebPages = (version) => { loadedVersion = version; }; Action registerForChange = () => { registeredForChangeNotification = true; }; Func getAssembyName = _ => new AssemblyName("System.Web.WebPages.Deployment, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"); // Act bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", binDirectory, nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, getAssembyName); // Assert Assert.False(loaded); Assert.Null(loadedVersion); Assert.False(registeredForChangeNotification); Assert.Equal(0, buildManager.Stream.Length); } [Fact] public void PreApplicationStartCodeDoesNotLoadIfAHigherVersionIsAvailableInGac() { // Arrange Version loadedVersion = null; bool registeredForChangeNotification = false; // Hopefully we'd have figured out a better way to load Plan9 by v8. var webPagesVersion = new Version("8.0.0.0"); IEnumerable loadedAssemblies = GetAssemblies("1.0.0.0", "2.0.0.0", "8.0.0.0"); var fileSystem = new TestFileSystem(); fileSystem.AddFile("Index.cshtml"); var buildManager = new TestBuildManager(); var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: null); Action loadWebPages = (version) => { loadedVersion = version; }; Action registerForChange = () => { registeredForChangeNotification = true; }; // Act bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", "bin", nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, null); // Assert Assert.False(loaded); Assert.Null(loadedVersion); Assert.False(registeredForChangeNotification); Assert.Equal(0, buildManager.Stream.Length); } [Fact] public void PreApplicationStartCodeForcesRecompileIfPreviousVersionIsNotTheSameAsCurrentVersion() { // Arrange Version loadedVersion = null; bool registeredForChangeNotification = false; IEnumerable loadedAssemblies = GetAssemblies("2.0.0.0"); var fileSystem = new TestFileSystem(); fileSystem.AddFile("Index.cshtml"); var buildManager = new TestBuildManager(); var content = "1.0.0.0" + Environment.NewLine; buildManager.Stream = new MemoryStream(Encoding.Default.GetBytes(content)); var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: new Version("2.0.0.0")); Action loadWebPages = (version) => { loadedVersion = version; }; Action registerForChange = () => { registeredForChangeNotification = true; }; // Act var ex = Assert.Throws(() => PreApplicationStartCode.StartCore(fileSystem, "", @"site\bin", nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, null) ); // Assert Assert.Equal("Changes were detected in the Web Pages runtime version that require your application to be recompiled. Refresh your browser window to continue.", ex.Message); Assert.Equal(ex.Data["WebPages.VersionChange"], true); Assert.False(registeredForChangeNotification); VerifyVersionFile(buildManager, new Version("2.0.0.0")); Assert.True(fileSystem.FileExists(@"site\bin\WebPagesRecompilation.deleteme")); } [Fact] public void PreApplicationStartCodeDoesNotForceRecompileIfNewVersionIsV1AndCurrentAssemblyIsNotMaxVersion() { // Arrange Version loadedVersion = null; bool registeredForChangeNotification = false; IEnumerable loadedAssemblies = GetAssemblies("2.0.0.0", "5.0.0.0"); var fileSystem = new TestFileSystem(); fileSystem.AddFile("Index.cshtml"); var buildManager = new TestBuildManager(); var content = AssemblyUtils.ThisAssemblyName.Version + Environment.NewLine; buildManager.Stream = new MemoryStream(Encoding.Default.GetBytes(content)); var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: new Version("1.0.0")); Action loadWebPages = (version) => { loadedVersion = version; }; Action registerForChange = () => { registeredForChangeNotification = true; }; // Act bool loaded = PreApplicationStartCode.StartCore(fileSystem, "", @"site\bin", nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, null); // Assert Assert.False(loaded); Assert.False(registeredForChangeNotification); VerifyVersionFile(buildManager, AssemblyUtils.ThisAssemblyName.Version); Assert.False(fileSystem.FileExists(@"site\bin\WebPagesRecompilation.deleteme")); } [Fact] public void PreApplicationStartCodeThrowsIfWebPagesIsInBinAndDifferentVersionIsSpecifiedInConfig() { // Arrange Version loadedVersion = null; bool registeredForChangeNotification = false; IEnumerable loadedAssemblies = GetAssemblies("1.0.0.0", "2.0.0.0"); var binDirectory = DeploymentUtil.GetBinDirectory(); var fileSystem = new TestFileSystem(); fileSystem.AddFile("Index.cshtml"); fileSystem.AddFile(Path.Combine(binDirectory, "System.Web.WebPages.Deployment.dll")); var buildManager = new TestBuildManager(); var content = AssemblyUtils.ThisAssemblyName.Version + Environment.NewLine; buildManager.Stream = new MemoryStream(Encoding.Default.GetBytes(content)); var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: new Version("2.0.0")); Action loadWebPages = (version) => { loadedVersion = version; }; Action registerForChange = () => { registeredForChangeNotification = true; }; Func getAssembyName = _ => new AssemblyName("System.Web.WebPages.Deployment, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"); // Act and Assert Assert.Throws(() => PreApplicationStartCode.StartCore(fileSystem, "", binDirectory, nameValueCollection, loadedAssemblies, buildManager, loadWebPages, registerForChange, getAssembyName), @"Conflicting versions of ASP.NET Web Pages detected: specified version is ""2.0.0.0"", but the version in bin is ""1.0.0.0"". To continue, remove files from the application's bin directory or remove the version specification in web.config." ); Assert.False(registeredForChangeNotification); Assert.Null(loadedVersion); } [Fact] public void PreApplicationStartCodeThrowsIfVersionIsSpecifiedInConfigAndDifferentVersionExistsInBin() { // Arrange Version loadedVersion = null; var binDirectory = DeploymentUtil.GetBinDirectory(); IEnumerable loadedAssemblies = GetAssemblies("1.0.0.0", AssemblyUtils.ThisAssemblyName.Version.ToString()); var fileSystem = new TestFileSystem(); fileSystem.AddFile("Index.cshtml"); fileSystem.AddFile(Path.Combine(binDirectory, "System.Web.WebPages.Deployment.dll")); var buildManager = new TestBuildManager(); var content = AssemblyUtils.ThisAssemblyName.Version + Environment.NewLine; buildManager.Stream = new MemoryStream(Encoding.Default.GetBytes(content)); var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: new Version("1.0.0")); Action loadWebPages = (version) => { loadedVersion = version; }; Action registerForChange = () => { }; Func getAssembyName = _ => new AssemblyName("System.Web.WebPages.Deployment, Version=" + AssemblyUtils.ThisAssemblyName.Version + ", Culture=neutral, PublicKeyToken=31bf3856ad364e35"); // Act and Assert Assert.Throws(() => PreApplicationStartCode.StartCore(fileSystem, "", binDirectory, nameValueCollection, loadedAssemblies, buildManager: buildManager, loadWebPages: loadWebPages, registerForChangeNotification: registerForChange, getAssemblyNameThunk: getAssembyName), String.Format(@"Conflicting versions of ASP.NET Web Pages detected: specified version is ""1.0.0.0"", but the version in bin is ""{0}"". To continue, remove files from the application's bin directory or remove the version specification in web.config.", AssemblyUtils.ThisAssemblyName.Version)); } [Fact] public void PreApplicationStartCodeThrowsIfVersionSpecifiedInConfigIsNotAvailable() { // Arrange Version loadedVersion = null; var binDirectory = DeploymentUtil.GetBinDirectory(); IEnumerable loadedAssemblies = GetAssemblies("1.0.0.0", AssemblyUtils.ThisAssemblyName.Version.ToString()); var fileSystem = new TestFileSystem(); fileSystem.AddFile("Index.cshtml"); var buildManager = new TestBuildManager(); var content = AssemblyUtils.ThisAssemblyName.Version + Environment.NewLine; buildManager.Stream = new MemoryStream(Encoding.Default.GetBytes(content)); var nameValueCollection = GetAppSettings(enabled: null, webPagesVersion: new Version("1.5")); Action loadWebPages = (version) => { loadedVersion = version; }; Action registerForChange = () => { }; // Act and Assert Assert.Throws(() => PreApplicationStartCode.StartCore(fileSystem, "", binDirectory, nameValueCollection, loadedAssemblies, buildManager: buildManager, loadWebPages: loadWebPages, registerForChangeNotification: registerForChange, getAssemblyNameThunk: null), String.Format("Specified Web Pages version \"1.5.0.0\" could not be found. Update your web.config to specify a different version. Current version: \"{0}\".", AssemblyUtils.ThisAssemblyName.Version)); } [Fact] public void TestPreAppStartClass() { PreAppStartTestHelper.TestPreAppStartClass(typeof(PreApplicationStartCode)); } private static NameValueCollection GetAppSettings(bool? enabled, Version webPagesVersion) { var nameValueCollection = new NameValueCollection(); if (enabled.HasValue) { nameValueCollection["webpages:enabled"] = enabled.Value ? "true" : "false"; } if (webPagesVersion != null) { nameValueCollection["webpages:version"] = webPagesVersion.ToString(); } return nameValueCollection; } private static void VerifyVersionFile(TestBuildManager buildManager, Version webPagesVersion) { var content = Encoding.UTF8.GetString(buildManager.Stream.ToArray()); Version version = Version.Parse(content); Assert.Equal(webPagesVersion, version); } private class TestBuildManager : IBuildManager { private MemoryStream _memoryStream = new MemoryStream(); public MemoryStream Stream { get { return _memoryStream; } set { _memoryStream = value; } } public Stream CreateCachedFile(string fileName) { Assert.Equal(DeploymentVersionFile, fileName); CopyMemoryStream(); return _memoryStream; } public Stream ReadCachedFile(string fileName) { Assert.Equal(DeploymentVersionFile, fileName); CopyMemoryStream(); return _memoryStream; } /// /// Need to do this because the MemoryStream is read and written to in consecutive calls which causes it to be closed / non-expandable. /// private void CopyMemoryStream() { var content = _memoryStream.ToArray(); if (content.Length > 0) { _memoryStream = new MemoryStream(_memoryStream.ToArray()); } else { _memoryStream = new MemoryStream(); } } } private static IEnumerable GetAssemblies(params string[] versions) { return from version in versions select new AssemblyName("System.Web.WebPages.Deployment, Version=" + version + ", Culture=neutral, PublicKeyToken=31bf3856ad364e35"); } } }