Add a PagesOption type that allows configuring the root for Page file discovery
Fixes #5785
This commit is contained in:
parent
85e28ae478
commit
145d27f9b3
|
|
@ -1,8 +1,8 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
<Import Project="..\..\build\common.props" />
|
<Import Project="..\..\build\common.props" />
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net452;netcoreapp1.1</TargetFrameworks>
|
<TargetFrameworks>netcoreapp1.1;net452</TargetFrameworks>
|
||||||
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">netcoreapp1.1</TargetFrameworks>
|
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">netcoreapp1.1</TargetFrameworks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.AspNetCore.Razor.Evolution;
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
using Microsoft.Extensions.Primitives;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
{
|
{
|
||||||
|
|
@ -29,21 +28,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
|
|
||||||
public override IEnumerable<RazorProjectItem> EnumerateItems(string path)
|
public override IEnumerable<RazorProjectItem> EnumerateItems(string path)
|
||||||
{
|
{
|
||||||
if (path == null)
|
EnsureValidPath(path);
|
||||||
{
|
return EnumerateFiles(_provider.GetDirectoryContents(path), path, prefix: string.Empty);
|
||||||
throw new ArgumentNullException(nameof(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.Length == 0 || path[0] != '/')
|
|
||||||
{
|
|
||||||
throw new ArgumentException(Resources.RazorProject_PathMustStartWithForwardSlash);
|
|
||||||
}
|
|
||||||
|
|
||||||
return EnumerateFiles(_provider.GetDirectoryContents(path), path, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual IChangeToken Watch(string pattern) => _provider.Watch(pattern);
|
|
||||||
|
|
||||||
private IEnumerable<RazorProjectItem> EnumerateFiles(IDirectoryContents directory, string basePath, string prefix)
|
private IEnumerable<RazorProjectItem> EnumerateFiles(IDirectoryContents directory, string basePath, string prefix)
|
||||||
{
|
{
|
||||||
if (directory.Exists)
|
if (directory.Exists)
|
||||||
|
|
@ -53,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
if (file.IsDirectory)
|
if (file.IsDirectory)
|
||||||
{
|
{
|
||||||
var relativePath = prefix + "/" + file.Name;
|
var relativePath = prefix + "/" + file.Name;
|
||||||
var subDirectory = _provider.GetDirectoryContents(relativePath);
|
var subDirectory = _provider.GetDirectoryContents(JoinPath(basePath, relativePath));
|
||||||
var children = EnumerateFiles(subDirectory, basePath, relativePath);
|
var children = EnumerateFiles(subDirectory, basePath, relativePath);
|
||||||
foreach (var child in children)
|
foreach (var child in children)
|
||||||
{
|
{
|
||||||
|
|
@ -67,5 +55,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string JoinPath(string path1, string path2)
|
||||||
|
{
|
||||||
|
var hasTrailingSlash = path1.EndsWith("/", StringComparison.Ordinal);
|
||||||
|
var hasLeadingSlash = path2.StartsWith("/", StringComparison.Ordinal);
|
||||||
|
if (hasLeadingSlash && hasTrailingSlash)
|
||||||
|
{
|
||||||
|
return path1 + path2.Substring(1);
|
||||||
|
}
|
||||||
|
else if (hasLeadingSlash || hasTrailingSlash)
|
||||||
|
{
|
||||||
|
return path1 + path2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path1 + "/" + path2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
|
||||||
public void OnProvidersExecuting(ActionDescriptorProviderContext context)
|
public void OnProvidersExecuting(ActionDescriptorProviderContext context)
|
||||||
{
|
{
|
||||||
foreach (var item in _project.EnumerateItems("/"))
|
foreach (var item in _project.EnumerateItems(_pagesOptions.RootDirectory))
|
||||||
{
|
{
|
||||||
if (item.Filename.StartsWith("_"))
|
if (item.Filename.StartsWith("_"))
|
||||||
{
|
{
|
||||||
|
|
@ -66,7 +66,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
private void AddActionDescriptors(IList<ActionDescriptor> actions, RazorProjectItem item, string template)
|
private void AddActionDescriptors(IList<ActionDescriptor> actions, RazorProjectItem item, string template)
|
||||||
{
|
{
|
||||||
var model = new PageApplicationModel(item.CombinedPath, item.PathWithoutExtension);
|
var model = new PageApplicationModel(item.CombinedPath, item.PathWithoutExtension);
|
||||||
var routePrefix = item.BasePath == "/" ? item.PathWithoutExtension : item.BasePath + item.PathWithoutExtension;
|
var routePrefix = item.PathWithoutExtension;
|
||||||
model.Selectors.Add(CreateSelectorModel(routePrefix, template));
|
model.Selectors.Add(CreateSelectorModel(routePrefix, template));
|
||||||
|
|
||||||
if (string.Equals(IndexFileName, item.Filename, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(IndexFileName, item.Filename, StringComparison.OrdinalIgnoreCase))
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,38 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||||
using Microsoft.AspNetCore.Razor.Evolution;
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
{
|
{
|
||||||
public class PageActionDescriptorChangeProvider : IActionDescriptorChangeProvider
|
public class PageActionDescriptorChangeProvider : IActionDescriptorChangeProvider
|
||||||
{
|
{
|
||||||
private readonly RazorProject _razorProject;
|
private readonly IFileProvider _fileProvider;
|
||||||
|
private readonly string _searchPattern;
|
||||||
|
|
||||||
public PageActionDescriptorChangeProvider(RazorProject razorProject)
|
public PageActionDescriptorChangeProvider(
|
||||||
|
IRazorViewEngineFileProviderAccessor fileProviderAccessor,
|
||||||
|
IOptions<RazorPagesOptions> razorPagesOptions)
|
||||||
{
|
{
|
||||||
_razorProject = razorProject;
|
if (fileProviderAccessor == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(fileProviderAccessor));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (razorPagesOptions == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(razorPagesOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
_fileProvider = fileProviderAccessor.FileProvider;
|
||||||
|
_searchPattern = razorPagesOptions.Value.RootDirectory.TrimEnd('/') + "/**/*.cshtml";
|
||||||
}
|
}
|
||||||
|
|
||||||
public IChangeToken GetChangeToken() => ((DefaultRazorProject)_razorProject).Watch("**/*.cshtml");
|
public IChangeToken GetChangeToken() => _fileProvider.Watch(_searchPattern);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unsupported handler method type '{0}'.
|
/// Unsupported handler method return type '{0}'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string UnsupportedHandlerMethodType
|
internal static string UnsupportedHandlerMethodType
|
||||||
{
|
{
|
||||||
|
|
@ -99,13 +99,29 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unsupported handler method type '{0}'.
|
/// Unsupported handler method return type '{0}'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatUnsupportedHandlerMethodType(object p0)
|
internal static string FormatUnsupportedHandlerMethodType(object p0)
|
||||||
{
|
{
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("UnsupportedHandlerMethodType"), p0);
|
return string.Format(CultureInfo.CurrentCulture, GetString("UnsupportedHandlerMethodType"), p0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Path must be an application relative path that starts with a forward slash '/'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string PathMustBeAnAppRelativePath
|
||||||
|
{
|
||||||
|
get { return GetString("PathMustBeAnAppRelativePath"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Path must be an application relative path that starts with a forward slash '/'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatPathMustBeAnAppRelativePath()
|
||||||
|
{
|
||||||
|
return GetString("PathMustBeAnAppRelativePath");
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,34 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RazorPagesOptions
|
public class RazorPagesOptions
|
||||||
{
|
{
|
||||||
|
private string _root = "/";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a list of <see cref="IPageApplicationModelConvention"/> instances that will be applied to
|
/// Gets a list of <see cref="IPageApplicationModelConvention"/> instances that will be applied to
|
||||||
/// the <see cref="PageModel"/> when discovering Razor Pages.
|
/// the <see cref="PageModel"/> when discovering Razor Pages.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IList<IPageApplicationModelConvention> Conventions { get; } = new List<IPageApplicationModelConvention>();
|
public IList<IPageApplicationModelConvention> Conventions { get; } = new List<IPageApplicationModelConvention>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Application relative path used as the root of discovery for Razor Page files.
|
||||||
|
/// </summary>
|
||||||
|
public string RootDirectory
|
||||||
|
{
|
||||||
|
get => _root;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value[0] != '/')
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.PathMustBeAnAppRelativePath, nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
_root = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -135,4 +135,7 @@
|
||||||
<data name="UnsupportedHandlerMethodType" xml:space="preserve">
|
<data name="UnsupportedHandlerMethodType" xml:space="preserve">
|
||||||
<value>Unsupported handler method return type '{0}'.</value>
|
<value>Unsupported handler method return type '{0}'.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="PathMustBeAnAppRelativePath" xml:space="preserve">
|
||||||
|
<value>Path must be an application relative path that starts with a forward slash '/'.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -35,6 +35,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
public async Task HelloWorld_CanGetContent()
|
public async Task HelloWorld_CanGetContent()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
// Note: If the route in this test case ever changes, the negative test case
|
||||||
|
// RazorPagesWithBasePathTest.PageOutsideBasePath_IsNotRouteable needs to be updated too.
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/HelloWorld");
|
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/HelloWorld");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
// 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.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
public class RazorPagesWithBasePathTest : IClassFixture<MvcTestFixture<RazorPagesWebSite.StartupWithBasePath>>
|
||||||
|
{
|
||||||
|
public RazorPagesWithBasePathTest(MvcTestFixture<RazorPagesWebSite.StartupWithBasePath> fixture)
|
||||||
|
{
|
||||||
|
Client = fixture.Client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClient Client { get; }
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PageOutsideBasePath_IsNotRouteable()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await Client.GetAsync("/HelloWorld");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task IndexAtBasePath_IsRouteableAtRoot()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await Client.GetAsync("/");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal("Hello from /Index", content.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task IndexAtBasePath_IsRouteableViaIndex()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await Client.GetAsync("/Index");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal("Hello from /Index", content.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task IndexInSubdirectory_IsRouteableViaDirectoryName()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await Client.GetAsync("/Admin/Index");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal("Hello from /Admin/Index", content.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PageWithRouteTemplateInSubdirectory_IsRouteable()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await Client.GetAsync("/Admin/RouteTemplate/1/MyRouteSuffix/");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal("Hello from /Admin/RouteTemplate 1", content.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PageWithRouteTemplateInSubdirectory_IsRouteable_WithOptionalParameters()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await Client.GetAsync("/Admin/RouteTemplate/my-user-id/MyRouteSuffix/4");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal("Hello from /Admin/RouteTemplate my-user-id 4", content.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AuthConvention_IsAppliedOnBasePathRelativePaths_ForFiles()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await Client.GetAsync("/Conventions/Auth");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
|
||||||
|
Assert.Equal("/Login?ReturnUrl=%2FConventions%2FAuth", response.Headers.Location.PathAndQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AuthConvention_IsAppliedOnBasePathRelativePaths_For_Folders()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await Client.GetAsync("/Conventions/AuthFolder");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
|
||||||
|
Assert.Equal("/Login?ReturnUrl=%2FConventions%2FAuthFolder", response.Headers.Location.PathAndQuery);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
// 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.Linq;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
|
{
|
||||||
|
public class DefaultRazorProjectTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void EnumerateFiles_ReturnsEmptySequenceIfNoCshtmlFilesArePresent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var fileProvider = new TestFileProvider();
|
||||||
|
var file1 = fileProvider.AddFile("File1.txt", "content");
|
||||||
|
var file2 = fileProvider.AddFile("File2.js", "content");
|
||||||
|
fileProvider.AddDirectoryContent("/", new IFileInfo[] { file1, file2 });
|
||||||
|
|
||||||
|
var razorProject = new DefaultRazorProject(fileProvider);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var razorFiles = razorProject.EnumerateItems("/");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(razorFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EnumerateFiles_ReturnsCshtmlFiles()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var fileProvider = new TestFileProvider();
|
||||||
|
var file1 = fileProvider.AddFile("File1.cshtml", "content");
|
||||||
|
var file2 = fileProvider.AddFile("File2.js", "content");
|
||||||
|
var file3 = fileProvider.AddFile("File3.cshtml", "content");
|
||||||
|
fileProvider.AddDirectoryContent("/", new IFileInfo[] { file1, file2, file3 });
|
||||||
|
|
||||||
|
var razorProject = new DefaultRazorProject(fileProvider);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var razorFiles = razorProject.EnumerateItems("/");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(razorFiles.OrderBy(f => f.Path),
|
||||||
|
file => Assert.Equal("/File1.cshtml", file.Path),
|
||||||
|
file => Assert.Equal("/File3.cshtml", file.Path));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EnumerateFiles_IteratesOverAllCshtmlUnderRoot()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var fileProvider = new TestFileProvider();
|
||||||
|
var directory1 = new TestDirectoryFileInfo
|
||||||
|
{
|
||||||
|
Name = "Level1-Dir1",
|
||||||
|
};
|
||||||
|
var file1 = fileProvider.AddFile("File1.cshtml", "content");
|
||||||
|
var directory2 = new TestDirectoryFileInfo
|
||||||
|
{
|
||||||
|
Name = "Level1-Dir2",
|
||||||
|
};
|
||||||
|
fileProvider.AddDirectoryContent("/", new IFileInfo[] { directory1, file1, directory2 });
|
||||||
|
|
||||||
|
var file2 = fileProvider.AddFile("Level1-Dir1/File2.cshtml", "content");
|
||||||
|
var file3 = fileProvider.AddFile("Level1-Dir1/File3.cshtml", "content");
|
||||||
|
var file4 = fileProvider.AddFile("Level1-Dir1/File4.txt", "content");
|
||||||
|
var directory3 = new TestDirectoryFileInfo
|
||||||
|
{
|
||||||
|
Name = "Level2-Dir1"
|
||||||
|
};
|
||||||
|
fileProvider.AddDirectoryContent("/Level1-Dir1", new IFileInfo[] { file2, directory3, file3, file4 });
|
||||||
|
var file5 = fileProvider.AddFile("Level1-Dir2/File5.cshtml", "content");
|
||||||
|
fileProvider.AddDirectoryContent("/Level1-Dir2", new IFileInfo[] { file5 });
|
||||||
|
fileProvider.AddDirectoryContent("/Level1/Level2", new IFileInfo[0]);
|
||||||
|
var razorProject = new DefaultRazorProject(fileProvider);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var razorFiles = razorProject.EnumerateItems("/");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(razorFiles.OrderBy(f => f.Path),
|
||||||
|
file => Assert.Equal("/File1.cshtml", file.Path),
|
||||||
|
file => Assert.Equal("/Level1-Dir1/File2.cshtml", file.Path),
|
||||||
|
file => Assert.Equal("/Level1-Dir1/File3.cshtml", file.Path),
|
||||||
|
file => Assert.Equal("/Level1-Dir2/File5.cshtml", file.Path));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EnumerateFiles_IteratesOverAllCshtmlUnderPath()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var fileProvider = new TestFileProvider();
|
||||||
|
var directory1 = new TestDirectoryFileInfo
|
||||||
|
{
|
||||||
|
Name = "Level1-Dir1",
|
||||||
|
};
|
||||||
|
var file1 = fileProvider.AddFile("File1.cshtml", "content");
|
||||||
|
var directory2 = new TestDirectoryFileInfo
|
||||||
|
{
|
||||||
|
Name = "Level1-Dir2",
|
||||||
|
};
|
||||||
|
fileProvider.AddDirectoryContent("/", new IFileInfo[] { directory1, file1, directory2 });
|
||||||
|
|
||||||
|
var file2 = fileProvider.AddFile("Level1-Dir1/File2.cshtml", "content");
|
||||||
|
var file3 = fileProvider.AddFile("Level1-Dir1/File3.cshtml", "content");
|
||||||
|
var file4 = fileProvider.AddFile("Level1-Dir1/File4.txt", "content");
|
||||||
|
var directory3 = new TestDirectoryFileInfo
|
||||||
|
{
|
||||||
|
Name = "Level2-Dir1"
|
||||||
|
};
|
||||||
|
fileProvider.AddDirectoryContent("/Level1-Dir1", new IFileInfo[] { file2, directory3, file3, file4 });
|
||||||
|
var file5 = fileProvider.AddFile("Level1-Dir2/File5.cshtml", "content");
|
||||||
|
fileProvider.AddDirectoryContent("/Level1-Dir2", new IFileInfo[] { file5 });
|
||||||
|
fileProvider.AddDirectoryContent("/Level1/Level2", new IFileInfo[0]);
|
||||||
|
var razorProject = new DefaultRazorProject(fileProvider);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var razorFiles = razorProject.EnumerateItems("/Level1-Dir1");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(razorFiles.OrderBy(f => f.Path),
|
||||||
|
file => Assert.Equal("/File2.cshtml", file.Path),
|
||||||
|
file => Assert.Equal("/File3.cshtml", file.Path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -95,6 +95,75 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
Assert.Equal("Test/Home", descriptor.AttributeRouteInfo.Template);
|
Assert.Equal("Test/Home", descriptor.AttributeRouteInfo.Template);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetDescriptors_GeneratesRouteTemplate()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var razorProject = new Mock<RazorProject>(MockBehavior.Strict);
|
||||||
|
razorProject.Setup(p => p.EnumerateItems("/"))
|
||||||
|
.Returns(new[]
|
||||||
|
{
|
||||||
|
GetProjectItem("/", "/base-path/Test.cshtml", $"@page \"Home\" {Environment.NewLine}<h1>Hello world</h1>"),
|
||||||
|
GetProjectItem("/", "/base-path/Index.cshtml", $"@page {Environment.NewLine}"),
|
||||||
|
GetProjectItem("/", "/base-path/Admin/Index.cshtml", $"@page{Environment.NewLine}"),
|
||||||
|
GetProjectItem("/", "/base-path/Admin/User.cshtml", $"@page{Environment.NewLine}"),
|
||||||
|
});
|
||||||
|
var options = GetRazorPagesOptions();
|
||||||
|
|
||||||
|
var provider = new PageActionDescriptorProvider(
|
||||||
|
razorProject.Object,
|
||||||
|
GetAccessor<MvcOptions>(),
|
||||||
|
options);
|
||||||
|
var context = new ActionDescriptorProviderContext();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.OnProvidersExecuting(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(context.Results,
|
||||||
|
result => Assert.Equal("base-path/Test/Home", result.AttributeRouteInfo.Template),
|
||||||
|
result => Assert.Equal("base-path/Index", result.AttributeRouteInfo.Template),
|
||||||
|
result => Assert.Equal("base-path", result.AttributeRouteInfo.Template),
|
||||||
|
result => Assert.Equal("base-path/Admin/Index", result.AttributeRouteInfo.Template),
|
||||||
|
result => Assert.Equal("base-path/Admin", result.AttributeRouteInfo.Template),
|
||||||
|
result => Assert.Equal("base-path/Admin/User", result.AttributeRouteInfo.Template));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetDescriptors_UsesBasePathOption_WhenGeneratingRouteTemplate()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var razorProject = new Mock<RazorProject>(MockBehavior.Strict);
|
||||||
|
razorProject.Setup(p => p.EnumerateItems("/base-path"))
|
||||||
|
.Returns(new[]
|
||||||
|
{
|
||||||
|
GetProjectItem("/base-path", "/Test.cshtml", $"@page \"Home\" {Environment.NewLine}<h1>Hello world</h1>"),
|
||||||
|
GetProjectItem("/base-path", "/Index.cshtml", $"@page {Environment.NewLine}"),
|
||||||
|
GetProjectItem("/base-path", "/Admin/Index.cshtml", $"@page{Environment.NewLine}"),
|
||||||
|
GetProjectItem("/base-path", "/Admin/User.cshtml", $"@page{Environment.NewLine}"),
|
||||||
|
});
|
||||||
|
var options = GetRazorPagesOptions();
|
||||||
|
options.Value.RootDirectory = "/base-path";
|
||||||
|
var provider = new PageActionDescriptorProvider(
|
||||||
|
razorProject.Object,
|
||||||
|
GetAccessor<MvcOptions>(),
|
||||||
|
options);
|
||||||
|
var context = new ActionDescriptorProviderContext();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.OnProvidersExecuting(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(context.Results,
|
||||||
|
result => Assert.Equal("Test/Home", result.AttributeRouteInfo.Template),
|
||||||
|
result => Assert.Equal("Index", result.AttributeRouteInfo.Template),
|
||||||
|
result => Assert.Equal("", result.AttributeRouteInfo.Template),
|
||||||
|
result => Assert.Equal("Admin/Index", result.AttributeRouteInfo.Template),
|
||||||
|
result => Assert.Equal("Admin", result.AttributeRouteInfo.Template),
|
||||||
|
result => Assert.Equal("Admin/User", result.AttributeRouteInfo.Template));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("/Path1")]
|
[InlineData("/Path1")]
|
||||||
[InlineData("~/Path1")]
|
[InlineData("~/Path1")]
|
||||||
|
|
@ -232,7 +301,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetDescriptors_AddsGlobalFilters()
|
public void GetDescriptors_AddsGlobalFilters()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
// 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.Mvc.Razor.Internal;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
{
|
||||||
|
public class PageActionDescriptorChangeProviderTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GetChangeToken_WatchesAllCshtmlFilesUnderFileSystemRoot()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var options = new TestOptionsManager<RazorPagesOptions>();
|
||||||
|
var fileProvider = new Mock<IFileProvider>();
|
||||||
|
var fileProviderAccessor = new Mock<IRazorViewEngineFileProviderAccessor>();
|
||||||
|
fileProviderAccessor
|
||||||
|
.Setup(f => f.FileProvider)
|
||||||
|
.Returns(fileProvider.Object);
|
||||||
|
var changeProvider = new PageActionDescriptorChangeProvider(fileProviderAccessor.Object, options);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var changeToken = changeProvider.GetChangeToken();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
fileProvider.Verify(f => f.Watch("/**/*.cshtml"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("/pages-base-dir")]
|
||||||
|
[InlineData("/pages-base-dir/")]
|
||||||
|
public void GetChangeToken_WatchesAllCshtmlFilesUnderSpecifiedRootDirectory(string rootDirectory)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var options = new TestOptionsManager<RazorPagesOptions>();
|
||||||
|
options.Value.RootDirectory = rootDirectory;
|
||||||
|
var fileProvider = new Mock<IFileProvider>();
|
||||||
|
var fileProviderAccessor = new Mock<IRazorViewEngineFileProviderAccessor>();
|
||||||
|
fileProviderAccessor
|
||||||
|
.Setup(f => f.FileProvider)
|
||||||
|
.Returns(fileProvider.Object);
|
||||||
|
var changeProvider = new PageActionDescriptorChangeProvider(fileProviderAccessor.Object, options);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var changeToken = changeProvider.GetChangeToken();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
fileProvider.Verify(f => f.Watch("/pages-base-dir/**/*.cshtml"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// 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.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.TestCommon
|
||||||
|
{
|
||||||
|
public class TestDirectoryContent : IDirectoryContents
|
||||||
|
{
|
||||||
|
private readonly IEnumerable<IFileInfo> _files;
|
||||||
|
|
||||||
|
public TestDirectoryContent(IEnumerable<IFileInfo> files)
|
||||||
|
{
|
||||||
|
_files = files;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Exists => true;
|
||||||
|
|
||||||
|
public IEnumerator<IFileInfo> GetEnumerator() => _files.GetEnumerator();
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
// 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.Text;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
|
{
|
||||||
|
public class TestDirectoryFileInfo : IFileInfo
|
||||||
|
{
|
||||||
|
public bool IsDirectory => true;
|
||||||
|
|
||||||
|
public long Length { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string PhysicalPath { get; set; }
|
||||||
|
|
||||||
|
public bool Exists => true;
|
||||||
|
|
||||||
|
public DateTimeOffset LastModified => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public Stream CreateReadStream()
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Microsoft.AspNetCore.Mvc.TestCommon;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
|
|
@ -13,12 +14,20 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, IFileInfo> _lookup =
|
private readonly Dictionary<string, IFileInfo> _lookup =
|
||||||
new Dictionary<string, IFileInfo>(StringComparer.Ordinal);
|
new Dictionary<string, IFileInfo>(StringComparer.Ordinal);
|
||||||
|
private readonly Dictionary<string, IDirectoryContents> _directoryContentsLookup =
|
||||||
|
new Dictionary<string, IDirectoryContents>();
|
||||||
|
|
||||||
private readonly Dictionary<string, TestFileChangeToken> _fileTriggers =
|
private readonly Dictionary<string, TestFileChangeToken> _fileTriggers =
|
||||||
new Dictionary<string, TestFileChangeToken>(StringComparer.Ordinal);
|
new Dictionary<string, TestFileChangeToken>(StringComparer.Ordinal);
|
||||||
|
|
||||||
public virtual IDirectoryContents GetDirectoryContents(string subpath)
|
public virtual IDirectoryContents GetDirectoryContents(string subpath)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
if (_directoryContentsLookup.TryGetValue(subpath, out var value))
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NotFoundDirectoryContents();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestFileInfo AddFile(string path, string contents)
|
public TestFileInfo AddFile(string path, string contents)
|
||||||
|
|
@ -36,6 +45,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
return fileInfo;
|
return fileInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TestDirectoryContent AddDirectoryContent(string path, IEnumerable<IFileInfo> files)
|
||||||
|
{
|
||||||
|
var directoryContent = new TestDirectoryContent(files);
|
||||||
|
_directoryContentsLookup[path] = directoryContent;
|
||||||
|
return directoryContent;
|
||||||
|
}
|
||||||
|
|
||||||
public void AddFile(string path, IFileInfo contents)
|
public void AddFile(string path, IFileInfo contents)
|
||||||
{
|
{
|
||||||
_lookup[path] = contents;
|
_lookup[path] = contents;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
@page
|
||||||
|
Hello from @ViewContext.RouteData.Values["page"]
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
@page "{id}/MyRouteSuffix/{value:int?}"
|
||||||
|
Hello from @ViewContext.RouteData.Values["page"] @ViewContext.RouteData.Values["id"] @ViewContext.RouteData.Values["value"]
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
@page
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
@page
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
@page
|
||||||
|
Hello from @ViewContext.RouteData.Values["page"]
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
<Import Project="..\..\..\build\common.props" />
|
<Import Project="..\..\..\build\common.props" />
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// 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.Builder;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace RazorPagesWebSite
|
||||||
|
{
|
||||||
|
public class StartupWithBasePath
|
||||||
|
{
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services
|
||||||
|
.AddMvc()
|
||||||
|
.AddCookieTempDataProvider()
|
||||||
|
.AddRazorPagesOptions(options =>
|
||||||
|
{
|
||||||
|
options.RootDirectory = "/Pages";
|
||||||
|
options.AuthorizePage("/Conventions/Auth", string.Empty);
|
||||||
|
options.AuthorizeFolder("/Conventions/AuthFolder", string.Empty);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
app.UseCultureReplacer();
|
||||||
|
|
||||||
|
app.UseCookieAuthentication(new CookieAuthenticationOptions
|
||||||
|
{
|
||||||
|
LoginPath = "/Login",
|
||||||
|
AutomaticAuthenticate = true,
|
||||||
|
AutomaticChallenge = true
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseStaticFiles();
|
||||||
|
|
||||||
|
app.UseMvc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue