diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs
index 2448688edb..fe6b98006b 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs
@@ -36,6 +36,52 @@ namespace Microsoft.Extensions.DependencyInjection
return options;
}
+ ///
+ /// Adds a to the page with the specified path.
+ ///
+ /// The to configure.
+ /// The path of the Razor Page.
+ /// The .
+ public static RazorPagesOptions AllowAnonymousToPage(this RazorPagesOptions options, string path)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(path));
+ }
+
+ var anonymousFilter = new AllowAnonymousFilter();
+ options.Conventions.Add(new PageConvention(path, model => model.Filters.Add(anonymousFilter)));
+ return options;
+ }
+
+ ///
+ /// Adds a to all pages under the specified path.
+ ///
+ /// The to configure.
+ /// The folder path.
+ /// The .
+ public static RazorPagesOptions AllowAnonymousToFolder(this RazorPagesOptions options, string folderPath)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ if (string.IsNullOrEmpty(folderPath))
+ {
+ throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(folderPath));
+ }
+
+ var anonymousFilter = new AllowAnonymousFilter();
+ options.Conventions.Add(new FolderConvention(folderPath, model => model.Filters.Add(anonymousFilter)));
+ return options;
+ }
+
///
/// Adds a with the specified policy to the page with the specified path.
///
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs
index 1046b880b7..2dc590cfd0 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs
@@ -399,6 +399,22 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal("/Login?ReturnUrl=%2FHelloWorldWithAuth", response.Headers.Location.PathAndQuery);
}
+ [Fact]
+ public async Task AuthorizePage_AllowAnonymousForSpecificPages()
+ {
+ // Arrange
+ var url = "/Pages/Admin/Login";
+
+ // Act
+ var response = await Client.GetAsync(url);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+ Assert.Equal("Login Page", content);
+ }
+
[Fact]
public async Task PageStart_IsDiscoveredWhenRootDirectoryIsNotSpecified()
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs
index c1b88b5442..fb09882688 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs
@@ -37,6 +37,79 @@ namespace Microsoft.Extensions.DependencyInjection
model => Assert.Same(filter, Assert.Single(model.Filters)));
}
+ [Fact]
+ public void AuthorizePage_AddsAllowAnonymousFilterToSpecificPage()
+ {
+ // Arrange
+ var options = new RazorPagesOptions();
+ var models = new[]
+ {
+ new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
+ new PageApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
+ new PageApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
+ };
+
+ // Act
+ options.AuthorizeFolder("/Users");
+ options.AllowAnonymousToPage("/Users/Contact.cshtml");
+ ApplyConventions(options, models);
+
+ // Assert
+ Assert.Collection(models,
+ model => Assert.Empty(model.Filters),
+ model =>
+ {
+ Assert.Equal("/Users/Account.cshtml", model.ViewEnginePath);
+ Assert.IsType(Assert.Single(model.Filters));
+ },
+ model =>
+ {
+ Assert.Equal("/Users/Contact.cshtml", model.ViewEnginePath);
+ Assert.IsType(model.Filters[0]);
+ Assert.IsType(model.Filters[1]);
+ });
+ }
+
+ [Theory]
+ [InlineData("/Users")]
+ [InlineData("/Users/")]
+ public void AuthorizePage_AddsAllowAnonymousFilterToPagesUnderFolder(string folderName)
+ {
+ // Arrange
+ var options = new RazorPagesOptions();
+ var models = new[]
+ {
+ new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
+ new PageApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"),
+ new PageApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"),
+ };
+
+ // Act
+ options.AuthorizeFolder("/");
+ options.AllowAnonymousToFolder("/Users");
+ ApplyConventions(options, models);
+
+ // Assert
+ Assert.Collection(models,
+ model =>
+ {
+ Assert.Equal("/Index.cshtml", model.ViewEnginePath);
+ Assert.IsType(Assert.Single(model.Filters));
+ },
+ model =>
+ {
+ Assert.Equal("/Users/Account.cshtml", model.ViewEnginePath);
+ Assert.IsType(model.Filters[0]);
+ Assert.IsType(model.Filters[1]);
+ },
+ model =>
+ {
+ Assert.Equal("/Users/Contact.cshtml", model.ViewEnginePath);
+ Assert.IsType(model.Filters[0]);
+ Assert.IsType(model.Filters[1]);
+ });
+ }
+
[Fact]
public void AuthorizePage_AddsAuthorizeFilterWithPolicyToSpecificPage()
{
diff --git a/test/WebSites/RazorPagesWebSite/Pages/Admin/Login.cshtml b/test/WebSites/RazorPagesWebSite/Pages/Admin/Login.cshtml
new file mode 100644
index 0000000000..44c9bb85ab
--- /dev/null
+++ b/test/WebSites/RazorPagesWebSite/Pages/Admin/Login.cshtml
@@ -0,0 +1,2 @@
+@page
+Login Page
\ No newline at end of file
diff --git a/test/WebSites/RazorPagesWebSite/Startup.cs b/test/WebSites/RazorPagesWebSite/Startup.cs
index 369d7452a2..4a39e2f302 100644
--- a/test/WebSites/RazorPagesWebSite/Startup.cs
+++ b/test/WebSites/RazorPagesWebSite/Startup.cs
@@ -16,6 +16,8 @@ namespace RazorPagesWebSite
.AddRazorPagesOptions(options =>
{
options.AuthorizePage("/HelloWorldWithAuth");
+ options.AuthorizeFolder("/Pages/Admin");
+ options.AllowAnonymousToPage("/Pages/Admin/Login");
});
}