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" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net452;netcoreapp1.1</TargetFrameworks>
|
||||
<TargetFrameworks>netcoreapp1.1;net452</TargetFrameworks>
|
||||
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">netcoreapp1.1</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||
{
|
||||
|
|
@ -29,21 +28,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
|
||||
public override IEnumerable<RazorProjectItem> EnumerateItems(string path)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
if (path.Length == 0 || path[0] != '/')
|
||||
{
|
||||
throw new ArgumentException(Resources.RazorProject_PathMustStartWithForwardSlash);
|
||||
}
|
||||
|
||||
return EnumerateFiles(_provider.GetDirectoryContents(path), path, "");
|
||||
EnsureValidPath(path);
|
||||
return EnumerateFiles(_provider.GetDirectoryContents(path), path, prefix: string.Empty);
|
||||
}
|
||||
|
||||
public virtual IChangeToken Watch(string pattern) => _provider.Watch(pattern);
|
||||
|
||||
private IEnumerable<RazorProjectItem> EnumerateFiles(IDirectoryContents directory, string basePath, string prefix)
|
||||
{
|
||||
if (directory.Exists)
|
||||
|
|
@ -53,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
if (file.IsDirectory)
|
||||
{
|
||||
var relativePath = prefix + "/" + file.Name;
|
||||
var subDirectory = _provider.GetDirectoryContents(relativePath);
|
||||
var subDirectory = _provider.GetDirectoryContents(JoinPath(basePath, relativePath));
|
||||
var children = EnumerateFiles(subDirectory, basePath, relativePath);
|
||||
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)
|
||||
{
|
||||
foreach (var item in _project.EnumerateItems("/"))
|
||||
foreach (var item in _project.EnumerateItems(_pagesOptions.RootDirectory))
|
||||
{
|
||||
if (item.Filename.StartsWith("_"))
|
||||
{
|
||||
|
|
@ -66,7 +66,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
private void AddActionDescriptors(IList<ActionDescriptor> actions, RazorProjectItem item, string template)
|
||||
{
|
||||
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));
|
||||
|
||||
if (string.Equals(IndexFileName, item.Filename, StringComparison.OrdinalIgnoreCase))
|
||||
|
|
|
|||
|
|
@ -1,22 +1,38 @@
|
|||
// 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 Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||
{
|
||||
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>
|
||||
/// Unsupported handler method type '{0}'.
|
||||
/// Unsupported handler method return type '{0}'.
|
||||
/// </summary>
|
||||
internal static string UnsupportedHandlerMethodType
|
||||
{
|
||||
|
|
@ -99,13 +99,29 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsupported handler method type '{0}'.
|
||||
/// Unsupported handler method return type '{0}'.
|
||||
/// </summary>
|
||||
internal static string FormatUnsupportedHandlerMethodType(object 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)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -12,10 +12,34 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
|||
/// </summary>
|
||||
public class RazorPagesOptions
|
||||
{
|
||||
private string _root = "/";
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="IPageApplicationModelConvention"/> instances that will be applied to
|
||||
/// the <see cref="PageModel"/> when discovering Razor Pages.
|
||||
/// </summary>
|
||||
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">
|
||||
<value>Unsupported handler method return type '{0}'.</value>
|
||||
</data>
|
||||
<data name="PathMustBeAnAppRelativePath" xml:space="preserve">
|
||||
<value>Path must be an application relative path that starts with a forward slash '/'.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -35,6 +35,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
public async Task HelloWorld_CanGetContent()
|
||||
{
|
||||
// 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");
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
[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]
|
||||
[InlineData("/Path1")]
|
||||
[InlineData("~/Path1")]
|
||||
|
|
@ -232,7 +301,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
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.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Mvc.TestCommon;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
|
|
@ -13,12 +14,20 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
{
|
||||
private readonly Dictionary<string, IFileInfo> _lookup =
|
||||
new Dictionary<string, IFileInfo>(StringComparer.Ordinal);
|
||||
private readonly Dictionary<string, IDirectoryContents> _directoryContentsLookup =
|
||||
new Dictionary<string, IDirectoryContents>();
|
||||
|
||||
private readonly Dictionary<string, TestFileChangeToken> _fileTriggers =
|
||||
new Dictionary<string, TestFileChangeToken>(StringComparer.Ordinal);
|
||||
|
||||
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)
|
||||
|
|
@ -36,6 +45,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
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)
|
||||
{
|
||||
_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" />
|
||||
|
||||
<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