diff --git a/build/dependencies.props b/build/dependencies.props index dda51a1a0f..34909837e4 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -28,6 +28,7 @@ 2.1.0-preview3-32110 2.1.0-preview3-32110 2.1.0-preview3-32110 + 2.1.0-preview3-32110 2.1.0-preview3-32110 2.1.0-preview3-32110 2.1.0-preview3-32110 diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ChecksumValidatorTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ChecksumValidatorTest.cs index 22b52f8b7a..b1e72b56c9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ChecksumValidatorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ChecksumValidatorTest.cs @@ -1,10 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Razor.Hosting; using Microsoft.AspNetCore.Razor.Language; -using Moq; using Xunit; using static Microsoft.AspNetCore.Razor.Hosting.TestRazorCompiledItem; @@ -14,15 +12,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal { public ChecksumValidatorTest() { - FileProvider = new TestFileProvider(); - FileSystem = new FileProviderRazorProjectFileSystem( - Mock.Of(a => a.FileProvider == FileProvider), - Mock.Of(e => e.ContentRootPath == "BasePath")); + ProjectFileSystem = new VirtualRazorProjectFileSystem(); } - public RazorProjectFileSystem FileSystem { get; } - - public TestFileProvider FileProvider { get; } + public VirtualRazorProjectFileSystem ProjectFileSystem { get; } [Fact] public void IsRecompilationSupported_NoChecksums_ReturnsFalse() @@ -77,7 +70,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal var item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", "/Views/Home/Index.cstml", new object[] { }); // Act - var result = ChecksumValidator.IsItemValid(FileSystem, item); + var result = ChecksumValidator.IsItemValid(ProjectFileSystem, item); // Assert Assert.True(result); @@ -94,7 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal }); // Act - var result = ChecksumValidator.IsItemValid(FileSystem, item); + var result = ChecksumValidator.IsItemValid(ProjectFileSystem, item); // Assert Assert.True(result); @@ -110,10 +103,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Views/Home/Index.cstml"), }); - FileProvider.AddFile("/Views/Home/_ViewImports.cstml", "dkdkfkdf"); // This will be ignored + ProjectFileSystem.Add(new TestRazorProjectItem("/Views/Home/_ViewImports.cstml", "dkdkfkdf")); // This will be ignored // Act - var result = ChecksumValidator.IsItemValid(FileSystem, item); + var result = ChecksumValidator.IsItemValid(ProjectFileSystem, item); // Assert Assert.True(result); @@ -129,10 +122,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Views/Home/Index.cstml"), }); - FileProvider.AddFile("/Views/Home/Index.cstml", "other content"); + ProjectFileSystem.Add(new TestRazorProjectItem("/Views/Home/Index.cstml", "other content")); // Act - var result = ChecksumValidator.IsItemValid(FileSystem, item); + var result = ChecksumValidator.IsItemValid(ProjectFileSystem, item); // Assert Assert.False(result); @@ -148,10 +141,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Views/Home/Index.cstml"), }); - FileProvider.AddFile("/Views/Home/Index.cstml", "some content"); + ProjectFileSystem.Add(new TestRazorProjectItem("/Views/Home/Index.cstml", "some content")); // Act - var result = ChecksumValidator.IsItemValid(FileSystem, item); + var result = ChecksumValidator.IsItemValid(ProjectFileSystem, item); // Assert Assert.False(result); @@ -167,11 +160,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Views/Home/Index.cstml"), }); - FileProvider.AddFile("/Views/Home/Index.cstml", "some content"); - FileProvider.AddFile("/Views/Home/_ViewImports.cstml", "some other import"); + ProjectFileSystem.Add(new TestRazorProjectItem("/Views/Home/Index.cstml", "some content")); + ProjectFileSystem.Add(new TestRazorProjectItem("/Views/Home/_ViewImports.cstml", "some other import")); // Act - var result = ChecksumValidator.IsItemValid(FileSystem, item); + var result = ChecksumValidator.IsItemValid(ProjectFileSystem, item); // Assert Assert.False(result); @@ -188,12 +181,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), "/Views/Home/Index.cstml"), }); - FileProvider.AddFile("/Views/Home/Index.cstml", "some content"); - FileProvider.AddFile("/Views/Home/_ViewImports.cstml", "some import"); - FileProvider.AddFile("/Views/_ViewImports.cstml", "some other import"); + ProjectFileSystem.Add(new TestRazorProjectItem("/Views/Home/Index.cstml", "some content")); + ProjectFileSystem.Add(new TestRazorProjectItem("/Views/Home/_ViewImports.cstml", "some import")); + ProjectFileSystem.Add(new TestRazorProjectItem("/Views/_ViewImports.cstml", "some other import")); // Act - var result = ChecksumValidator.IsItemValid(FileSystem, item); + var result = ChecksumValidator.IsItemValid(ProjectFileSystem, item); // Assert Assert.True(result); diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerFailedExceptionFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerFailedExceptionFactoryTest.cs index bc7cac8920..6ce35abac7 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerFailedExceptionFactoryTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerFailedExceptionFactoryTest.cs @@ -3,20 +3,16 @@ using System.IO; using System.Text; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Razor.Extensions; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; -using Moq; using Xunit; namespace Microsoft.AspNetCore.Mvc.Razor.Internal { public class CompilerFailedExceptionFactoryTest { - private readonly IHostingEnvironment _hostingEnvironment = Mock.Of(e => e.ContentRootPath == "BasePath"); - [Fact] public void GetCompilationFailedResult_ReadsRazorErrorsFromPage() { @@ -24,11 +20,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal var viewPath = "/Views/Home/Index.cshtml"; var razorEngine = RazorEngine.Create(); - var fileProvider = new TestFileProvider(); - fileProvider.AddFile(viewPath, ""); - var accessor = Mock.Of(a => a.FileProvider == fileProvider); - - var fileSystem = new FileProviderRazorProjectFileSystem(accessor, _hostingEnvironment); + var fileSystem = new VirtualRazorProjectFileSystem(); + fileSystem.Add(new TestRazorProjectItem(viewPath, "")); var templateEngine = new MvcRazorTemplateEngine(razorEngine, fileSystem); var codeDocument = templateEngine.CreateCodeDocument(viewPath); @@ -39,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal // Assert var failure = Assert.Single(compilationResult.CompilationFailures); - Assert.Equal(Path.Combine("Views", "Home", "Index.cshtml"), failure.SourceFilePath); + Assert.Equal(viewPath, failure.SourceFilePath); Assert.Collection(failure.Messages, message => Assert.StartsWith( @"Unterminated string literal.", @@ -56,13 +49,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal var viewPath = "/Views/Home/Index.cshtml"; var physicalPath = @"x:\myapp\views\home\index.cshtml"; - var fileProvider = new TestFileProvider(); - var file = fileProvider.AddFile(viewPath, ""); - file.PhysicalPath = physicalPath; - var accessor = Mock.Of(a => a.FileProvider == fileProvider); + var fileSystem = new VirtualRazorProjectFileSystem(); + fileSystem.Add(new TestRazorProjectItem(viewPath, "", physicalPath: physicalPath)); var razorEngine = RazorEngine.Create(); - var fileSystem = new FileProviderRazorProjectFileSystem(accessor, _hostingEnvironment); var templateEngine = new MvcRazorTemplateEngine(razorEngine, fileSystem); var codeDocument = templateEngine.CreateCodeDocument(viewPath); @@ -90,11 +80,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal "; var razorEngine = RazorEngine.Create(); - var fileProvider = new TestFileProvider(); - fileProvider.AddFile(viewPath, fileContent); - var accessor = Mock.Of(a => a.FileProvider == fileProvider); - - var fileSystem = new FileProviderRazorProjectFileSystem(accessor, _hostingEnvironment); + var fileSystem = new VirtualRazorProjectFileSystem(); + fileSystem.Add(new TestRazorProjectItem(viewPath, fileContent)); var templateEngine = new MvcRazorTemplateEngine(razorEngine, fileSystem); var codeDocument = templateEngine.CreateCodeDocument(viewPath); @@ -113,18 +100,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal { // Arrange var viewPath = "/Views/Home/Index.cshtml"; - var importsFilePath = @"x:\views\_MyImports.cshtml"; + var importsPath = "/Views/_MyImports.cshtml"; var fileContent = "@ "; var importsContent = "@(abc"; - var fileProvider = new TestFileProvider(); - fileProvider.AddFile(viewPath, fileContent); - var importsFile = fileProvider.AddFile("/Views/_MyImports.cshtml", importsContent); - importsFile.PhysicalPath = importsFilePath; - var accessor = Mock.Of(a => a.FileProvider == fileProvider); + var fileSystem = new VirtualRazorProjectFileSystem(); + fileSystem.Add(new TestRazorProjectItem(viewPath, fileContent)); + fileSystem.Add(new TestRazorProjectItem("/Views/_MyImports.cshtml", importsContent)); var razorEngine = RazorEngine.Create(); - var fileSystem = new FileProviderRazorProjectFileSystem(accessor, _hostingEnvironment); var templateEngine = new MvcRazorTemplateEngine(razorEngine, fileSystem) { Options = @@ -143,7 +127,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal compilationResult.CompilationFailures, failure => { - Assert.Equal(Path.Combine("Views", "Home", "Index.cshtml"), failure.SourceFilePath); + Assert.Equal(viewPath, failure.SourceFilePath); Assert.Collection(failure.Messages, message => { @@ -153,7 +137,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal }, failure => { - Assert.Equal(importsFilePath, failure.SourceFilePath); + Assert.Equal(importsPath, failure.SourceFilePath); Assert.Collection(failure.Messages, message => { @@ -179,7 +163,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal GetRazorDiagnostic("message-3", SourceLocation.Undefined, length: -1), GetRazorDiagnostic("message-4", new SourceLocation(viewImportsPath, 1, 3, 8), length: 4), }; - var fileProvider = new TestFileProvider(); // Act var result = CompilationFailedExceptionFactory.Create(codeDocument, diagnostics); diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs index 9874cbfcba..378ca51d42 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs @@ -933,9 +933,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor .Setup(p => p.CreateFactory("/Views/_ViewStart.cshtml")) .Returns(GetPageFactoryResult(() => viewStart)); - var fileProvider = new TestFileProvider(); - var accessor = Mock.Of(a => a.FileProvider == fileProvider); - var fileSystem = new FileProviderRazorProjectFileSystem(accessor, Mock.Of()); + var fileSystem = new VirtualRazorProjectFileSystem(); var viewEngine = CreateViewEngine(pageFactory.Object, fileSystem: fileSystem); var context = GetActionContext(_controllerTestContext); @@ -1385,9 +1383,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor Mock.Of(), new HtmlTestEncoder(), GetOptionsAccessor(expanders: null), - new FileProviderRazorProjectFileSystem( - Mock.Of(a => a.FileProvider == new TestFileProvider()), - Mock.Of()), + new VirtualRazorProjectFileSystem(), loggerFactory, new DiagnosticListener("Microsoft.AspNetCore.Mvc.Razor")); @@ -1972,11 +1968,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor RazorProjectFileSystem fileSystem = null) { pageFactory = pageFactory ?? Mock.Of(); - if (fileSystem == null) - { - var accessor = Mock.Of(a => a.FileProvider == new TestFileProvider()); - fileSystem = new FileProviderRazorProjectFileSystem(accessor, Mock.Of()); - } + fileSystem = fileSystem ?? new VirtualRazorProjectFileSystem(); return new TestableRazorViewEngine(pageFactory, GetOptionsAccessor(expanders), fileSystem); } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageDirectiveFeatureTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageDirectiveFeatureTest.cs index 9b713fab29..a566331451 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageDirectiveFeatureTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageDirectiveFeatureTest.cs @@ -1,10 +1,7 @@ // Copyright (c) .NET Foundation. 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.Text; using Microsoft.AspNetCore.Razor.Language; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -19,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure public void TryGetPageDirective_FindsTemplate() { // Arrange - var projectItem = new TestRazorProjectItem(@"@page ""Some/Path/{value}"" + var projectItem = new TestRazorProjectItem("Test.cshtml", @"@page ""Some/Path/{value}"" The rest of the thing"); var sink = new TestSink(); var logger = new TestLogger("logger", sink, enabled: true); @@ -34,7 +31,7 @@ The rest of the thing"); public void TryGetPageDirective_NoNewLine() { // Arrange - var projectItem = new TestRazorProjectItem(@"@page ""Some/Path/{value}"""); + var projectItem = new TestRazorProjectItem("Test.cshtml", @"@page ""Some/Path/{value}"""); var sink = new TestSink(); var logger = new TestLogger("logger", sink, enabled: true); @@ -48,7 +45,7 @@ The rest of the thing"); public void TryGetPageDirective_JunkBeforeDirective() { // Arrange - var projectItem = new TestRazorProjectItem(@"Not a directive @page ""Some/Path/{value}"""); + var projectItem = new TestRazorProjectItem("Test.cshtml", @"Not a directive @page ""Some/Path/{value}"""); var sink = new TestSink(); var logger = new TestLogger("logger", sink, enabled: true); @@ -67,7 +64,7 @@ The rest of the thing"); var expected = "The page directive at 'Test.cshtml' is malformed. Please fix the following issues: The 'page' directive expects a string surrounded by double quotes."; var sink = new TestSink(); var logger = new TestLogger("logger", sink, enabled: true); - var projectItem = new TestRazorProjectItem($@"@page {inTemplate}"); + var projectItem = new TestRazorProjectItem("Test.cshtml", $@"@page {inTemplate}"); // Act & Assert Assert.True(PageDirectiveFeature.TryGetPageDirective(logger, projectItem, out var template)); @@ -87,7 +84,7 @@ The rest of the thing"); var expected = "The page directive at 'Test.cshtml' is malformed. Please fix the following issues: The 'page' directive expects a string surrounded by double quotes."; var sink = new TestSink(); var logger = new TestLogger("logger", sink, enabled: true); - var projectItem = new TestRazorProjectItem(@"@page Some/Path/{value}"); + var projectItem = new TestRazorProjectItem("Test.cshtml", @"@page Some/Path/{value}"); // Act & Assert Assert.True(PageDirectiveFeature.TryGetPageDirective(logger, projectItem, out var template)); @@ -105,7 +102,7 @@ The rest of the thing"); public void TryGetPageDirective_NewLineBeforeDirective() { // Arrange - var projectItem = new TestRazorProjectItem("\n @page \"Some/Path/{value}\""); + var projectItem = new TestRazorProjectItem("Test.cshtml", "\n @page \"Some/Path/{value}\""); var sink = new TestSink(); var logger = new TestLogger("logger", sink, enabled: true); @@ -119,7 +116,7 @@ The rest of the thing"); public void TryGetPageDirective_Directive_WithoutPathOrContent() { // Arrange - var projectItem = new TestRazorProjectItem(@"@page"); + var projectItem = new TestRazorProjectItem("Test.cshtml", @"@page"); // Act & Assert Assert.True(PageDirectiveFeature.TryGetPageDirective(NullLogger.Instance, projectItem, out var template)); @@ -130,7 +127,7 @@ The rest of the thing"); public void TryGetPageDirective_DirectiveWithContent_WithoutPath() { // Arrange - var projectItem = new TestRazorProjectItem(@"@page + var projectItem = new TestRazorProjectItem("Test.cshtml", @"@page Non-path things"); var sink = new TestSink(); var logger = new TestLogger("logger", sink, enabled: true); @@ -145,7 +142,7 @@ Non-path things"); public void TryGetPageDirective_NoDirective() { // Arrange - var projectItem = new TestRazorProjectItem(@"This is junk + var projectItem = new TestRazorProjectItem("Test.cshtml", @"This is junk Nobody will use it"); var sink = new TestSink(); var logger = new TestLogger("logger", sink, enabled: true); @@ -156,34 +153,4 @@ Nobody will use it"); Assert.Empty(sink.Writes); } } - - public class TestRazorProjectItem : RazorProjectItem - { - private string _content; - - public TestRazorProjectItem(string content) - { - _content = content; - } - - public override string BasePath => throw new NotImplementedException(); - - public override bool Exists => throw new NotImplementedException(); - - public override string FilePath => "Test.cshtml"; - - public override string PhysicalPath => null; - - public override Stream Read() - { - if (_content == null) - { - return null; - } - else - { - return new MemoryStream(Encoding.UTF8.GetBytes(_content)); - } - } - } } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs index 2a66a9e08c..837d5124f0 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs @@ -4,19 +4,16 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor.Compilation; -using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Razor.Hosting; using Microsoft.AspNetCore.Razor.Language; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; -using Moq; using Xunit; using static Microsoft.AspNetCore.Razor.Hosting.TestRazorCompiledItem; @@ -141,10 +138,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal }), }; - var fileProvider = new TestFileProvider(); - fileProvider.AddFile("/Pages/About.cshtml", "some other content"); + var fileSystem = new VirtualRazorProjectFileSystem(); + fileSystem.Add(new TestRazorProjectItem("/Pages/About.cshtml", "some other content")); - var provider = CreateProvider(descriptors: descriptors, fileProvider: fileProvider); + var provider = CreateProvider(descriptors: descriptors, fileSystem: fileSystem); var context = new PageRouteModelProviderContext(); // Act @@ -167,11 +164,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal }), }; - var fileProvider = new TestFileProvider(); - fileProvider.AddFile("/Pages/About.cshtml", "some content"); - fileProvider.AddFile("/Pages/_ViewImports.cshtml", "some import"); + var fileSystem = new VirtualRazorProjectFileSystem(); + fileSystem.Add(new TestRazorProjectItem("/Pages/About.cshtml", "some content")); + fileSystem.Add(new TestRazorProjectItem("/Pages/_ViewImports.cshtml", "some import")); - var provider = CreateProvider(descriptors: descriptors, fileProvider: fileProvider); + var provider = CreateProvider(descriptors: descriptors, fileSystem: fileSystem); var context = new PageRouteModelProviderContext(); // Act @@ -196,10 +193,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal }), }; - var fileProvider = new TestFileProvider(); - fileProvider.AddFile("/Pages/_ViewImports.cshtml", "some other import"); + var fileSystem = new VirtualRazorProjectFileSystem(); + fileSystem.Add(new TestRazorProjectItem("/Pages/_ViewImports.cshtml", "some other import")); - var provider = CreateProvider(descriptors: descriptors, fileProvider: fileProvider); + var provider = CreateProvider(descriptors: descriptors, fileSystem: fileSystem); var context = new PageRouteModelProviderContext(); // Act @@ -625,13 +622,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal private TestCompiledPageRouteModelProvider CreateProvider( RazorPagesOptions options = null, IList descriptors = null, - TestFileProvider fileProvider = null) + VirtualRazorProjectFileSystem fileSystem = null) { options = options ?? new RazorPagesOptions(); - fileProvider = fileProvider ?? new TestFileProvider(); - var fileSystem = new FileProviderRazorProjectFileSystem( - Mock.Of(a => a.FileProvider == fileProvider), - Mock.Of(e => e.ContentRootPath == "BasePath")); + fileSystem = fileSystem ?? new VirtualRazorProjectFileSystem(); var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem); var provider = new TestCompiledPageRouteModelProvider( diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs index 4d5c14dbd1..b2d6aa0186 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs @@ -192,18 +192,15 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal .Setup(f => f.CreateFactory("/_ViewStart.cshtml")) .Returns(new RazorPageFactoryResult(new CompiledViewDescriptor(), factory2)); - var fileProvider = new TestFileProvider(); - fileProvider.AddFile("/Home/Path1/_ViewStart.cshtml", "content1"); - fileProvider.AddFile("/_ViewStart.cshtml", "content2"); - var accessor = Mock.Of(a => a.FileProvider == fileProvider); - - var defaultFileSystem = new FileProviderRazorProjectFileSystem(accessor, _hostingEnvironment); + var fileSystem = new VirtualRazorProjectFileSystem(); + fileSystem.Add(new TestRazorProjectItem("/Home/Path1/_ViewStart.cshtml", "content1")); + fileSystem.Add(new TestRazorProjectItem("/_ViewStart.cshtml", "content2")); var invokerProvider = CreateInvokerProvider( loader.Object, CreateActionDescriptorCollection(descriptor), razorPageFactoryProvider: razorPageFactoryProvider.Object, - fileSystem: defaultFileSystem); + fileSystem: fileSystem); var context = new ActionInvokerProviderContext(new ActionContext() { diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs index 6c66cfcc27..7f439549df 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs @@ -1,35 +1,24 @@ // Copyright (c) .NET Foundation. 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.Linq; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.ApplicationModels; -using Microsoft.AspNetCore.Mvc.Razor; -using Microsoft.Extensions.FileProviders; +using Microsoft.AspNetCore.Razor.Language; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; -using Moq; using Xunit; namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { public class RazorProjectPageRouteModelProviderTest { - private readonly IHostingEnvironment _hostingEnvironment = Mock.Of(e => e.ContentRootPath == "BasePath"); - [Fact] public void OnProvidersExecuting_ReturnsPagesWithPageDirective() { // Arrange - var fileProvider = new TestFileProvider(); - var file1 = fileProvider.AddFile("/Pages/Home.cshtml", "@page"); - var file2 = fileProvider.AddFile("/Pages/Test.cshtml", "Hello world"); - - var dir1 = fileProvider.AddDirectoryContent("/Pages", new IFileInfo[] { file1, file2 }); - fileProvider.AddDirectoryContent("/", new[] { dir1 }); - - var fileSystem = new TestRazorProjectFileSystem(fileProvider, _hostingEnvironment); + var fileSystem = new VirtualRazorProjectFileSystem(); + fileSystem.Add(new TestRazorProjectItem("/Pages/Home.cshtml", "@page")); + fileSystem.Add(new TestRazorProjectItem("/Pages/Test.cshtml", "Hello world")); var optionsManager = Options.Create(new RazorPagesOptions()); optionsManager.Value.RootDirectory = "/"; @@ -60,18 +49,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_AddsPagesUnderAreas() { // Arrange - var fileProvider = new TestFileProvider(); - var file1 = fileProvider.AddFile("Categories.cshtml", "@page"); - var file2 = fileProvider.AddFile("Index.cshtml", "@page"); - var file3 = fileProvider.AddFile("List.cshtml", "@page \"{sortOrder?}\""); - var file4 = fileProvider.AddFile("_ViewStart.cshtml", "@page"); - var manageDir = fileProvider.AddDirectoryContent("/Areas/Products/Pages/Manage", new[] { file1 }); - var pagesDir = fileProvider.AddDirectoryContent("/Areas/Products/Pages", new IFileInfo[] { manageDir, file2, file3, file4 }); - var productsDir = fileProvider.AddDirectoryContent("/Areas/Products", new[] { pagesDir }); - var areasDir = fileProvider.AddDirectoryContent("/Areas", new[] { productsDir }); - var rootDir = fileProvider.AddDirectoryContent("/", new[] { areasDir }); - - var fileSystem = new TestRazorProjectFileSystem(fileProvider, _hostingEnvironment); + var fileSystem = new VirtualRazorProjectFileSystem(); + fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/Manage/Categories.cshtml", "@page")); + fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/Index.cshtml", "@page")); + fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/List.cshtml", "@page \"{sortOrder?}\"")); + fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/_ViewStart.cshtml", "@page")); var optionsManager = Options.Create(new RazorPagesOptions { AllowAreas = true }); var provider = new RazorProjectPageRouteModelProvider(fileSystem, optionsManager, NullLoggerFactory.Instance); @@ -82,24 +64,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal // Assert Assert.Collection(context.RouteModels, - model => - { - Assert.Equal("/Areas/Products/Pages/Manage/Categories.cshtml", model.RelativePath); - Assert.Equal("/Manage/Categories", model.ViewEnginePath); - Assert.Collection(model.Selectors, - selector => Assert.Equal("Products/Manage/Categories", selector.AttributeRouteModel.Template)); - Assert.Collection(model.RouteValues.OrderBy(k => k.Key), - kvp => - { - Assert.Equal("area", kvp.Key); - Assert.Equal("Products", kvp.Value); - }, - kvp => - { - Assert.Equal("page", kvp.Key); - Assert.Equal("/Manage/Categories", kvp.Value); - }); - }, model => { Assert.Equal("/Areas/Products/Pages/Index.cshtml", model.RelativePath); @@ -136,6 +100,24 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal Assert.Equal("page", kvp.Key); Assert.Equal("/List", kvp.Value); }); + }, + model => + { + Assert.Equal("/Areas/Products/Pages/Manage/Categories.cshtml", model.RelativePath); + Assert.Equal("/Manage/Categories", model.ViewEnginePath); + Assert.Collection(model.Selectors, + selector => Assert.Equal("Products/Manage/Categories", selector.AttributeRouteModel.Template)); + Assert.Collection(model.RouteValues.OrderBy(k => k.Key), + kvp => + { + Assert.Equal("area", kvp.Key); + Assert.Equal("Products", kvp.Value); + }, + kvp => + { + Assert.Equal("page", kvp.Key); + Assert.Equal("/Manage/Categories", kvp.Value); + }); }); } @@ -143,19 +125,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_DoesNotAddPagesUnderAreas_WhenFeatureIsDisabled() { // Arrange - var fileProvider = new TestFileProvider(); - var file1 = fileProvider.AddFile("Categories.cshtml", "@page"); - var file2 = fileProvider.AddFile("Index.cshtml", "@page"); - var file3 = fileProvider.AddFile("List.cshtml", "@page \"{sortOrder?}\""); - var file4 = fileProvider.AddFile("About.cshtml", "@page"); - var manageDir = fileProvider.AddDirectoryContent("/Areas/Products/Pages/Manage", new[] { file1 }); - var areaPagesDir = fileProvider.AddDirectoryContent("/Areas/Products/Pages", new IFileInfo[] { manageDir, file2, file3, }); - var productsDir = fileProvider.AddDirectoryContent("/Areas/Products", new[] { areaPagesDir }); - var areasDir = fileProvider.AddDirectoryContent("/Areas", new[] { productsDir }); - var pagesDir = fileProvider.AddDirectoryContent("/Pages", new[] { file4 }); - var rootDir = fileProvider.AddDirectoryContent("/", new[] { areasDir, pagesDir }); - - var fileSystem = new TestRazorProjectFileSystem(fileProvider, _hostingEnvironment); + var fileSystem = new VirtualRazorProjectFileSystem(); + fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/Manage/Categories.cshtml", "@page")); + fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/Index.cshtml", "@page")); + fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/List.cshtml", "@page \"{sortOrder?}\"")); + fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/_ViewStart.cshtml", "@page")); + fileSystem.Add(new TestRazorProjectItem("/Pages/About.cshtml", "@page")); var optionsManager = Options.Create(new RazorPagesOptions { AllowAreas = false }); var provider = new RazorProjectPageRouteModelProvider(fileSystem, optionsManager, NullLoggerFactory.Instance); @@ -179,17 +154,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_DoesNotAddAreaAndNonAreaRoutesForAPage() { // Arrange - var fileProvider = new TestFileProvider(); - var conformingFileUnderAreasDirectory = fileProvider.AddFile("Categories.cshtml", "@page"); + var fileSystem = new VirtualRazorProjectFileSystem(); + fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Categories.cshtml", "@page")); // We shouldn't add a route for this. - var nonConformingFileUnderAreasDirectory = fileProvider.AddFile("Home.cshtml", "@page"); - var rootFile = fileProvider.AddFile("About.cshtml", "@page"); - - var productsDir = fileProvider.AddDirectoryContent("/Areas/Products", new[] { conformingFileUnderAreasDirectory }); - var areasDir = fileProvider.AddDirectoryContent("/Areas", new IFileInfo[] { productsDir, nonConformingFileUnderAreasDirectory }); - var rootDir = fileProvider.AddDirectoryContent("/", new IFileInfo[] { areasDir, rootFile }); - - var fileSystem = new TestRazorProjectFileSystem(fileProvider, _hostingEnvironment); + fileSystem.Add(new TestRazorProjectItem("/Areas/Home.cshtml", "@page")); + fileSystem.Add(new TestRazorProjectItem("/About.cshtml", "@page")); var optionsManager = Options.Create(new RazorPagesOptions { @@ -242,16 +211,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_AddsMultipleSelectorsForIndexPages() { // Arrange - var fileProvider = new TestFileProvider(); - var file1 = fileProvider.AddFile("/Pages/Index.cshtml", "@page"); - var file2 = fileProvider.AddFile("/Pages/Test.cshtml", "Hello world"); - var file3 = fileProvider.AddFile("/Pages/Admin/Index.cshtml", "@page \"test\""); - - var dir2 = fileProvider.AddDirectoryContent("/Pages/Admin", new[] { file3 }); - var dir1 = fileProvider.AddDirectoryContent("/Pages", new IFileInfo[] { dir2, file1, file2 }); - fileProvider.AddDirectoryContent("/", new[] { dir1 }); - - var fileSystem = new TestRazorProjectFileSystem(fileProvider, _hostingEnvironment); + var fileSystem = new VirtualRazorProjectFileSystem(); + fileSystem.Add(new TestRazorProjectItem("/Pages/Index.cshtml", "@page")); + fileSystem.Add(new TestRazorProjectItem("/Pages/Test.cshtml", "Hello world")); + fileSystem.Add(new TestRazorProjectItem("/Pages/Admin/Index.cshtml", "@page \"test\"")); var optionsManager = Options.Create(new RazorPagesOptions()); optionsManager.Value.RootDirectory = "/"; @@ -263,14 +226,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal // Assert Assert.Collection(context.RouteModels, - model => - { - Assert.Equal("/Pages/Admin/Index.cshtml", model.RelativePath); - Assert.Equal("/Pages/Admin/Index", model.ViewEnginePath); - Assert.Collection(model.Selectors, - selector => Assert.Equal("Pages/Admin/Index/test", selector.AttributeRouteModel.Template), - selector => Assert.Equal("Pages/Admin/test", selector.AttributeRouteModel.Template)); - }, model => { Assert.Equal("/Pages/Index.cshtml", model.RelativePath); @@ -278,6 +233,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal Assert.Collection(model.Selectors, selector => Assert.Equal("Pages/Index", selector.AttributeRouteModel.Template), selector => Assert.Equal("Pages", selector.AttributeRouteModel.Template)); + }, + model => + { + Assert.Equal("/Pages/Admin/Index.cshtml", model.RelativePath); + Assert.Equal("/Pages/Admin/Index", model.ViewEnginePath); + Assert.Collection(model.Selectors, + selector => Assert.Equal("Pages/Admin/Index/test", selector.AttributeRouteModel.Template), + selector => Assert.Equal("Pages/Admin/test", selector.AttributeRouteModel.Template)); }); } @@ -285,11 +248,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_AllowsRouteTemplateWithOverridePattern() { // Arrange - var fileProvider = new TestFileProvider(); - var file = fileProvider.AddFile("/Index.cshtml", "@page \"/custom-route\""); - fileProvider.AddDirectoryContent("/", new[] { file }); - - var fileSystem = new TestRazorProjectFileSystem(fileProvider, _hostingEnvironment); + var fileSystem = new VirtualRazorProjectFileSystem(); + fileSystem.Add(new TestRazorProjectItem("/Index.cshtml", "@page \"/custom-route\"")); var optionsManager = Options.Create(new RazorPagesOptions()); optionsManager.Value.RootDirectory = "/"; @@ -316,16 +276,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_SkipsPagesStartingWithUnderscore() { // Arrange - var fileProvider = new TestFileProvider(); - var dir1 = fileProvider.AddDirectoryContent("/Pages", - new[] - { - fileProvider.AddFile("/Pages/Home.cshtml", "@page"), - fileProvider.AddFile("/Pages/_Layout.cshtml", "@page") - }); - fileProvider.AddDirectoryContent("/", new[] { dir1 }); - - var fileSystem = new TestRazorProjectFileSystem(fileProvider, _hostingEnvironment); + var fileSystem = new VirtualRazorProjectFileSystem(); + fileSystem.Add(new TestRazorProjectItem("/Pages/Home.cshtml", "@page")); + fileSystem.Add(new TestRazorProjectItem("/Pages/_Layout.cshtml", "@page")); var optionsManager = Options.Create(new RazorPagesOptions()); optionsManager.Value.RootDirectory = "/"; @@ -347,23 +300,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_DiscoversFilesUnderBasePath() { // Arrange - var fileProvider = new TestFileProvider(); - var dir1 = fileProvider.AddDirectoryContent("/Pages", - new[] - { - fileProvider.AddFile("/Pages/Index.cshtml", "@page"), - fileProvider.AddFile("/Pages/_Layout.cshtml", "@page") - }); - var dir2 = fileProvider.AddDirectoryContent("/NotPages", - new[] - { - fileProvider.AddFile("/NotPages/Index.cshtml", "@page"), - fileProvider.AddFile("/NotPages/_Layout.cshtml", "@page") - }); - var rootFile = fileProvider.AddFile("/Index.cshtml", "@page"); - fileProvider.AddDirectoryContent("/", new IFileInfo[] { rootFile, dir1, dir2 }); - - var fileSystem = new TestRazorProjectFileSystem(fileProvider, _hostingEnvironment); + var fileSystem = new VirtualRazorProjectFileSystem(); + fileSystem.Add(new TestRazorProjectItem("/Pages/Index.cshtml", "@page")); + fileSystem.Add(new TestRazorProjectItem("/Pages/_Layout.cshtml", "@page")); + fileSystem.Add(new TestRazorProjectItem("/NotPages/Index.cshtml", "@page")); + fileSystem.Add(new TestRazorProjectItem("/NotPages/_Layout.cshtml", "@page")); + fileSystem.Add(new TestRazorProjectItem("/Index.cshtml", "@page")); var optionsManager = Options.Create(new RazorPagesOptions()); optionsManager.Value.RootDirectory = "/Pages"; @@ -385,14 +327,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_DoesNotAddPageDirectivesIfItAlreadyExists() { // Arrange - var fileProvider = new TestFileProvider(); - var file1 = fileProvider.AddFile("/Pages/Home.cshtml", "@page"); - var file2 = fileProvider.AddFile("/Pages/Test.cshtml", "@page"); - - var dir1 = fileProvider.AddDirectoryContent("/Pages", new IFileInfo[] { file1, file2 }); - fileProvider.AddDirectoryContent("/", new[] { dir1 }); - - var fileSystem = new TestRazorProjectFileSystem(fileProvider, _hostingEnvironment); + var fileSystem = new VirtualRazorProjectFileSystem(); + fileSystem.Add(new TestRazorProjectItem("/Pages/Home.cshtml", "@page")); + fileSystem.Add(new TestRazorProjectItem("/Pages/Test.cshtml", "@page")); var optionsManager = Options.Create(new RazorPagesOptions()); optionsManager.Value.RootDirectory = "/"; diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/Microsoft.AspNetCore.Mvc.TestCommon.csproj b/test/Microsoft.AspNetCore.Mvc.TestCommon/Microsoft.AspNetCore.Mvc.TestCommon.csproj index 1949c7f2c8..203c301faa 100644 --- a/test/Microsoft.AspNetCore.Mvc.TestCommon/Microsoft.AspNetCore.Mvc.TestCommon.csproj +++ b/test/Microsoft.AspNetCore.Mvc.TestCommon/Microsoft.AspNetCore.Mvc.TestCommon.csproj @@ -12,6 +12,7 @@ + diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestRazorProjectItem.cs b/test/Microsoft.AspNetCore.Mvc.TestCommon/TestRazorProjectItem.cs new file mode 100644 index 0000000000..05b9aee207 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.TestCommon/TestRazorProjectItem.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using System.Text; + +namespace Microsoft.AspNetCore.Razor.Language +{ + public class TestRazorProjectItem : RazorProjectItem + { + public TestRazorProjectItem( + string filePath, + string content = "Default content", + string physicalPath = null, + string relativePhysicalPath = null, + string basePath = "/") + { + FilePath = filePath; + PhysicalPath = physicalPath; + RelativePhysicalPath = relativePhysicalPath; + BasePath = basePath; + Content = content; + } + + public override string BasePath { get; } + + public override string FilePath { get; } + + public override string PhysicalPath { get; } + + public override string RelativePhysicalPath { get; } + + public override bool Exists { get; } = true; + + public string Content { get; set; } + + public override Stream Read() + { + var stream = new MemoryStream(Encoding.UTF8.GetBytes(Content)); + + return stream; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/VirtualRazorProjectFileSystem.cs b/test/Microsoft.AspNetCore.Mvc.TestCommon/VirtualRazorProjectFileSystem.cs new file mode 100644 index 0000000000..2d848bc7ea --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.TestCommon/VirtualRazorProjectFileSystem.cs @@ -0,0 +1,233 @@ +// Copyright (c) .NET Foundation. 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.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace Microsoft.AspNetCore.Razor.Language +{ + public class VirtualRazorProjectFileSystem : RazorProjectFileSystem + { + private readonly DirectoryNode _root = new DirectoryNode("/"); + + public override IEnumerable EnumerateItems(string basePath) + { + basePath = NormalizeAndEnsureValidPath(basePath); + var directory = _root.GetDirectory(basePath); + return directory?.EnumerateItems() ?? Enumerable.Empty(); + } + + public override RazorProjectItem GetItem(string path) + { + path = NormalizeAndEnsureValidPath(path); + return _root.GetItem(path) ?? new NotFoundProjectItem(string.Empty, path); + } + + public void Add(RazorProjectItem projectItem) + { + if (projectItem == null) + { + throw new ArgumentNullException(nameof(projectItem)); + } + + var filePath = NormalizeAndEnsureValidPath(projectItem.FilePath); + _root.AddFile(new FileNode(filePath, projectItem)); + } + + // Internal for testing + [DebuggerDisplay("{Path}")] + internal class DirectoryNode + { + public DirectoryNode(string path) + { + Path = path; + } + + public string Path { get; } + + public List Directories { get; } = new List(); + + public List Files { get; } = new List(); + + public void AddFile(FileNode fileNode) + { + var filePath = fileNode.Path; + if (!filePath.StartsWith(Path, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException($"File {fileNode.Path} does not belong to {Path}."); + } + + // Look for the first / that appears in the path after the current directory path. + var directoryPath = GetDirectoryPath(filePath); + var directory = GetOrAddDirectory(this, directoryPath, createIfNotExists: true); + Debug.Assert(directory != null); + directory.Files.Add(fileNode); + } + + public DirectoryNode GetDirectory(string path) + { + if (!path.StartsWith(Path, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException($"File {path} does not belong to {Path}."); + } + + return GetOrAddDirectory(this, path); + } + + public IEnumerable EnumerateItems() + { + foreach (var file in Files) + { + yield return file.ProjectItem; + } + + foreach (var directory in Directories) + { + foreach (var file in directory.EnumerateItems()) + { + yield return file; + } + } + } + + public RazorProjectItem GetItem(string path) + { + if (!path.StartsWith(Path, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException($"File {path} does not belong to {Path}."); + } + + var directoryPath = GetDirectoryPath(path); + var directory = GetOrAddDirectory(this, directoryPath); + if (directory == null) + { + return null; + } + + foreach (var file in directory.Files) + { + var filePath = file.Path; + var directoryLength = directory.Path.Length; + + // path, filePath -> /Views/Home/Index.cshtml + // directory.Path -> /Views/Home/ + // We only need to match the file name portion since we've already matched the directory segment. + if (string.Compare(path, directoryLength, filePath, directoryLength, path.Length - directoryLength, StringComparison.OrdinalIgnoreCase) == 0) + { + return file.ProjectItem; + } + } + + return null; + } + + private static string GetDirectoryPath(string path) + { + // /dir1/dir2/file.cshtml -> /dir1/dir2/ + var fileNameIndex = path.LastIndexOf('/'); + if (fileNameIndex == -1) + { + return path; + } + + return path.Substring(0, fileNameIndex + 1); + } + + private static DirectoryNode GetOrAddDirectory( + DirectoryNode directory, + string path, + bool createIfNotExists = false) + { + Debug.Assert(!string.IsNullOrEmpty(path)); + if (path[path.Length - 1] != '/') + { + path += '/'; + } + + int index; + while ((index = path.IndexOf('/', directory.Path.Length)) != -1 && index != path.Length) + { + var subDirectory = FindSubDirectory(directory, path); + + if (subDirectory == null) + { + if (createIfNotExists) + { + var directoryPath = path.Substring(0, index + 1); // + 1 to include trailing slash + subDirectory = new DirectoryNode(directoryPath); + directory.Directories.Add(subDirectory); + } + else + { + return null; + } + } + + directory = subDirectory; + } + + return directory; + } + + private static DirectoryNode FindSubDirectory(DirectoryNode parentDirectory, string path) + { + for (var i = 0; i < parentDirectory.Directories.Count; i++) + { + // ParentDirectory.Path -> /Views/Home/ + // CurrentDirectory.Path -> /Views/Home/SubDir/ + // Path -> /Views/Home/SubDir/MorePath/File.cshtml + // Each invocation of FindSubDirectory returns the immediate subdirectory along the path to the file. + + var currentDirectory = parentDirectory.Directories[i]; + var directoryPath = currentDirectory.Path; + var startIndex = parentDirectory.Path.Length; + var directoryNameLength = directoryPath.Length - startIndex; + + if (string.Compare(path, startIndex, directoryPath, startIndex, directoryPath.Length - startIndex, StringComparison.OrdinalIgnoreCase) == 0) + { + return currentDirectory; + } + } + + return null; + } + } + + // Internal for testing + [DebuggerDisplay("{Path}")] + internal struct FileNode + { + public FileNode(string path, RazorProjectItem projectItem) + { + Path = path; + ProjectItem = projectItem; + } + + public string Path { get; } + + public RazorProjectItem ProjectItem { get; } + } + + private class NotFoundProjectItem : RazorProjectItem + { + public NotFoundProjectItem(string basePath, string path) + { + BasePath = basePath; + FilePath = path; + } + + public override string BasePath { get; } + + public override string FilePath { get; } + + public override bool Exists => false; + + public override string PhysicalPath => throw new NotSupportedException(); + + public override Stream Read() => throw new NotSupportedException(); + } + } +} \ No newline at end of file