Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Allow "page" route parameter to be used in Mvc controllers #6705

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,13 @@ private ViewLocationCacheResult LocatePageFromViewLocations(
{
var controllerName = GetNormalizedRouteValue(actionContext, ControllerKey);
var areaName = GetNormalizedRouteValue(actionContext, AreaKey);
var razorPageName = GetNormalizedRouteValue(actionContext, PageKey);
string razorPageName = null;
if (actionContext.ActionDescriptor.RouteValues.ContainsKey(PageKey))
{
// Only calculate the Razor Page name if "page" is registered in RouteValues.
razorPageName = GetNormalizedRouteValue(actionContext, PageKey);
}

var expanderContext = new ViewLocationExpanderContext(
actionContext,
pageName,
Expand Down Expand Up @@ -270,8 +276,7 @@ private ViewLocationCacheResult LocatePageFromViewLocations(
expanderContext.IsMainPage,
expanderValues);

ViewLocationCacheResult cacheResult;
if (!ViewLookupCache.TryGetValue(cacheKey, out cacheResult))
if (!ViewLookupCache.TryGetValue(cacheKey, out ViewLocationCacheResult cacheResult))
{
_logger.ViewLookupCacheMiss(cacheKey.ViewName, cacheKey.ControllerName);
cacheResult = OnCacheMiss(expanderContext, cacheKey);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ public class PageViewLocationExpander : IViewLocationExpander
{
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
if (string.IsNullOrEmpty(context.PageName))
if ((context.ActionContext.ActionDescriptor is PageActionDescriptor) && !string.IsNullOrEmpty(context.PageName))
{
// Not a page - just act natural.
return viewLocations;
return ExpandPageHierarchy();
}

return ExpandPageHierarchy();
// Not a page - just act natural.
return viewLocations;

IEnumerable<string> ExpandPageHierarchy()
{
Expand Down
26 changes: 26 additions & 0 deletions test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -376,5 +376,31 @@ public async Task TypesMarkedAsNonAction_AreInaccessible()
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}

[Fact]
public async Task UsingPageRouteParameterInConventionalRouteWorks()
{
// Arrange
var expected = "ConventionalRoute - Hello from mypage";

// Act
var response = await Client.GetStringAsync("/PageRoute/ConventionalRoute/mypage");

// Assert
Assert.Equal(expected, response.Trim());
}

[Fact]
public async Task UsingPageRouteParameterInAttributeRouteWorks()
{
// Arrange
var expected = "AttributeRoute - Hello from test-page";

// Act
var response = await Client.GetStringAsync("/PageRoute/Attribute/test-page");

// Assert
Assert.Equal(expected, response.Trim());
}
}
}
77 changes: 75 additions & 2 deletions test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1791,6 +1791,79 @@ public void GetViewLocationFormats_NoRouteValues_ReturnsDefaultSet()
Assert.Equal(expected, actual);
}

[Fact]
public void ViewEngine_DoesNotSetPageValue_IfItIsNotSpecifiedInRouteValues()
{
// Arrange
var routeValues = new Dictionary<string, object>
{
{ "controller", "MyController" },
{ "action", "MyAction" }
};

var expected = new[] { "some-seed" };
var expander = new Mock<IViewLocationExpander>();
expander
.Setup(e => e.PopulateValues(It.IsAny<ViewLocationExpanderContext>()))
.Callback((ViewLocationExpanderContext c) =>
{
Assert.Equal("MyController", c.ControllerName);
Assert.Null(c.PageName);
})
.Verifiable();

expander
.Setup(e => e.ExpandViewLocations(
It.IsAny<ViewLocationExpanderContext>(),
It.IsAny<IEnumerable<string>>()))
.Returns(expected);

var viewEngine = CreateViewEngine(expanders: new[] { expander.Object });
var context = GetActionContext(routeValues);

// Act
viewEngine.FindView(context, viewName: "Test-view", isMainPage: true);

// Assert
expander.Verify();
}

[Fact]
public void ViewEngine_SetsPageValue_IfItIsSpecifiedInRouteValues()
{
// Arrange
var routeValues = new Dictionary<string, object>
{
{ "page", "MyPage" },
};

var expected = new[] { "some-seed" };
var expander = new Mock<IViewLocationExpander>();
expander
.Setup(e => e.PopulateValues(It.IsAny<ViewLocationExpanderContext>()))
.Callback((ViewLocationExpanderContext c) =>
{
Assert.Equal("MyPage", c.PageName);
})
.Verifiable();

expander
.Setup(e => e.ExpandViewLocations(
It.IsAny<ViewLocationExpanderContext>(),
It.IsAny<IEnumerable<string>>()))
.Returns(expected);

var viewEngine = CreateViewEngine(expanders: new[] { expander.Object });
var context = GetActionContext(routeValues);
context.ActionDescriptor.RouteValues["page"] = "MyPage";

// Act
viewEngine.FindView(context, viewName: "MyView", isMainPage: true);

// Assert
expander.Verify();
}

// Return RazorViewEngine with a page factory provider that is always successful.
private RazorViewEngine CreateSuccessfulViewEngine()
{
Expand Down Expand Up @@ -1931,8 +2004,8 @@ public TestableRazorViewEngine(
optionsAccessor,
new FileProviderRazorProject(
Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == new TestFileProvider())))
{
}
{
}

public TestableRazorViewEngine(
IRazorPageFactoryProvider pageFactory,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
// 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.IO;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Razor.Language;
using Xunit;

namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
Expand Down Expand Up @@ -48,6 +45,28 @@ public void ExpandLocations_NoOp_ForNonPage()
Assert.Equal(locations, actual);
}

[Fact]
public void ExpandLocations_NoOp_ForNonPageWithPageName()
{
// Verifies the fix for https://github.com/aspnet/Mvc/issues/6660. This ensures that when PageViewLocationExpander is called
// from a non-Razor Page with a route value for "
// Arrange
var context = CreateContext(pageName: "test");
context.ActionContext.ActionDescriptor = new ControllerActionDescriptor();
var locations = new string[]
{
"/ignore-me",
};

var expander = new PageViewLocationExpander();

// Act
var actual = expander.ExpandViewLocations(context, locations);

// Assert
Assert.Equal(locations, actual);
}

[Fact]
public void ExpandLocations_NoOp_WhenLocationDoesNotContainPageToken()
{
Expand Down Expand Up @@ -125,15 +144,21 @@ public void ExpandLocations_ExpandsDirectories_MultipleLocations()

private ViewLocationExpanderContext CreateContext(string viewName = "_LoginPartial.cshtml", string pageName = null)
{
var actionContext = new ActionContext
{
ActionDescriptor = new PageActionDescriptor(),
};

return new ViewLocationExpanderContext(
new ActionContext(),
actionContext,
viewName,
controllerName: null,
areaName: null,
pageName: pageName,
isMainPage: true)
{
Values = new Dictionary<string, string>(),

};
}
}
Expand Down
25 changes: 25 additions & 0 deletions test/WebSites/BasicWebSite/Controllers/PageRouteController.cs
Original file line number Diff line number Diff line change
@@ -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 Microsoft.AspNetCore.Mvc;

namespace BasicWebSite.Controllers
{
// Verifies that we can use the "page" token in routing in a controller only (no Razor Pages) application
// without affecting view lookups.
public class PageRouteController : Controller
{
public IActionResult ConventionalRoute(string page)
{
ViewData["page"] = page;
return View();
}

[HttpGet("/PageRoute/Attribute/{page}")]
public IActionResult AttributeRoute(string page)
{
ViewData["page"] = page;
return View();
}
}
}
1 change: 1 addition & 0 deletions test/WebSites/BasicWebSite/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public void Configure(IApplicationBuilder app)
routes.MapRoute("ActionAsMethod", "{controller}/{action}",
defaults: new { controller = "Home", action = "Index" });

routes.MapRoute("PageRoute", "{controller}/{action}/{page}");
});
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
AttributeRoute - Hello from @ViewBag.Page
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ConventionalRoute - Hello from @ViewBag.Page