Add support for friendly routes in pages

FIxes #6325
This commit is contained in:
Pranav K 2017-05-25 14:59:21 -07:00
parent 688e518991
commit 597a0c9a11
4 changed files with 155 additions and 19 deletions

View File

@ -59,30 +59,30 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Adds a <see cref="AllowAnonymousFilter"/> to the page with the specified path.
/// Adds a <see cref="AllowAnonymousFilter"/> to the page with the specified name.
/// </summary>
/// <param name="options">The <see cref="RazorPagesOptions"/> to configure.</param>
/// <param name="path">The path of the Razor Page.</param>
/// <param name="pageName">The page name.</param>
/// <returns>The <see cref="RazorPagesOptions"/>.</returns>
public static RazorPagesOptions AllowAnonymousToPage(this RazorPagesOptions options, string path)
public static RazorPagesOptions AllowAnonymousToPage(this RazorPagesOptions options, string pageName)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (string.IsNullOrEmpty(path))
if (string.IsNullOrEmpty(pageName))
{
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(path));
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName));
}
var anonymousFilter = new AllowAnonymousFilter();
options.Conventions.Add(new PageConvention(path, model => model.Filters.Add(anonymousFilter)));
options.Conventions.Add(new PageConvention(pageName, model => model.Filters.Add(anonymousFilter)));
return options;
}
/// <summary>
/// Adds a <see cref="AllowAnonymousFilter"/> to all pages under the specified path.
/// Adds a <see cref="AllowAnonymousFilter"/> to all pages under the specified folder.
/// </summary>
/// <param name="options">The <see cref="RazorPagesOptions"/> to configure.</param>
/// <param name="folderPath">The folder path.</param>
@ -105,40 +105,40 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Adds a <see cref="AuthorizeFilter"/> with the specified policy to the page with the specified path.
/// Adds a <see cref="AuthorizeFilter"/> with the specified policy to the page with the specified name.
/// </summary>
/// <param name="options">The <see cref="RazorPagesOptions"/> to configure.</param>
/// <param name="path">The path of the Razor Page.</param>
/// <param name="pageName">The page name.</param>
/// <param name="policy">The authorization policy.</param>
/// <returns>The <see cref="RazorPagesOptions"/>.</returns>
public static RazorPagesOptions AuthorizePage(this RazorPagesOptions options, string path, string policy)
public static RazorPagesOptions AuthorizePage(this RazorPagesOptions options, string pageName, string policy)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (string.IsNullOrEmpty(path))
if (string.IsNullOrEmpty(pageName))
{
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(path));
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName));
}
var authorizeFilter = new AuthorizeFilter(policy);
options.Conventions.Add(new PageConvention(path, model => model.Filters.Add(authorizeFilter)));
options.Conventions.Add(new PageConvention(pageName, model => model.Filters.Add(authorizeFilter)));
return options;
}
/// <summary>
/// Adds a <see cref="AuthorizeFilter"/> to the page with the specified path.
/// Adds a <see cref="AuthorizeFilter"/> to the page with the specified name.
/// </summary>
/// <param name="options">The <see cref="RazorPagesOptions"/> to configure.</param>
/// <param name="path">The path of the Razor Page.</param>
/// <param name="pageName">The page name.</param>
/// <returns>The <see cref="RazorPagesOptions"/>.</returns>
public static RazorPagesOptions AuthorizePage(this RazorPagesOptions options, string path) =>
AuthorizePage(options, path, policy: string.Empty);
public static RazorPagesOptions AuthorizePage(this RazorPagesOptions options, string pageName) =>
AuthorizePage(options, pageName, policy: string.Empty);
/// <summary>
/// Adds a <see cref="AuthorizeFilter"/> with the specified policy to all pages under the specified path.
/// Adds a <see cref="AuthorizeFilter"/> with the specified policy to all pages under the specified folder.
/// </summary>
/// <param name="options">The <see cref="RazorPagesOptions"/> to configure.</param>
/// <param name="folderPath">The folder path.</param>
@ -162,7 +162,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Adds a <see cref="AuthorizeFilter"/> to all pages under the specified path.
/// Adds a <see cref="AuthorizeFilter"/> to all pages under the specified folder.
/// </summary>
/// <param name="options">The <see cref="RazorPagesOptions"/> to configure.</param>
/// <param name="folderPath">The folder path.</param>
@ -170,6 +170,54 @@ namespace Microsoft.Extensions.DependencyInjection
public static RazorPagesOptions AuthorizeFolder(this RazorPagesOptions options, string folderPath) =>
AuthorizeFolder(options, folderPath, policy: string.Empty);
/// <summary>
/// Adds the specified <paramref name="route"/> to the page at the specified <paramref name="pageName"/>.
/// <para>
/// The page can be routed via <paramref name="route"/> in addition to the default set of path based routes.
/// All links generated for this page will use the specified route.
/// </para>
/// </summary>
/// <param name="options">The <see cref="RazorPagesOptions"/>.</param>
/// <param name="pageName">The page name.</param>
/// <param name="route">The route to associate with the page.</param>
/// <returns>The <see cref="RazorPagesOptions"/>.</returns>
public static RazorPagesOptions AddPageRoute(this RazorPagesOptions options, string pageName, string route)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (string.IsNullOrEmpty(pageName))
{
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName));
}
if (string.IsNullOrEmpty(route))
{
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(route));
}
options.Conventions.Add(new PageConvention(pageName, model =>
{
// Use the route specified in MapPageRoute for outbound routing.
foreach (var selector in model.Selectors)
{
selector.AttributeRouteModel.SuppressLinkGeneration = true;
}
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Template = route,
}
});
}));
return options;
}
private class PageConvention : IPageApplicationModelConvention
{
private readonly string _path;

View File

@ -1024,6 +1024,19 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[AspNetCore._InjectedP
Assert.StartsWith(expected, response.Trim());
}
[Fact]
public async Task PagesCanByRoutedViaRoute_AddedViaAddPageRoute()
{
// Arrange
var expected = "Hello, test!";
// Act
var response = await Client.GetStringAsync("/Different-Route/test");
// Assert
Assert.StartsWith(expected, response.Trim());
}
private async Task AddAntiforgeryHeaders(HttpRequestMessage request)
{
var getResponse = await Client.GetAsync(request.RequestUri);

View File

@ -242,6 +242,80 @@ namespace Microsoft.Extensions.DependencyInjection
});
}
[Fact]
public void AddPageRoute_AddsRouteToSelector()
{
// Arrange
var options = new RazorPagesOptions();
var models = new[]
{
new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml")
{
Selectors =
{
CreateSelectorModel("Index", suppressLinkGeneration: true),
CreateSelectorModel(""),
}
},
new PageApplicationModel("/Pages/About.cshtml", "/About.cshtml")
{
Selectors =
{
CreateSelectorModel("About"),
}
}
};
// Act
options.AddPageRoute("/Index.cshtml", "Different-Route");
ApplyConventions(options, models);
// Assert
Assert.Collection(models,
model =>
{
Assert.Equal("/Index.cshtml", model.ViewEnginePath);
Assert.Collection(model.Selectors,
selector =>
{
Assert.Equal("Index", selector.AttributeRouteModel.Template);
Assert.True(selector.AttributeRouteModel.SuppressLinkGeneration);
},
selector =>
{
Assert.Equal("", selector.AttributeRouteModel.Template);
Assert.True(selector.AttributeRouteModel.SuppressLinkGeneration);
},
selector =>
{
Assert.Equal("Different-Route", selector.AttributeRouteModel.Template);
Assert.False(selector.AttributeRouteModel.SuppressLinkGeneration);
});
},
model =>
{
Assert.Equal("/About.cshtml", model.ViewEnginePath);
Assert.Collection(model.Selectors,
selector =>
{
Assert.Equal("About", selector.AttributeRouteModel.Template);
Assert.False(selector.AttributeRouteModel.SuppressLinkGeneration);
});
});
}
private static SelectorModel CreateSelectorModel(string template, bool suppressLinkGeneration = false)
{
return new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Template = template,
SuppressLinkGeneration = suppressLinkGeneration
},
};
}
private static void ApplyConventions(RazorPagesOptions options, PageApplicationModel[] models)
{
foreach (var convention in options.Conventions)

View File

@ -19,6 +19,7 @@ namespace RazorPagesWebSite
options.AuthorizePage("/HelloWorldWithAuth");
options.AuthorizeFolder("/Pages/Admin");
options.AllowAnonymousToPage("/Pages/Admin/Login");
options.AddPageRoute("/HelloWorldWithRoute", "Different-Route/{text}");
})
.WithRazorPagesAtContentRoot();
}