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