Merge branch 'release/2.2'

This commit is contained in:
Ryan Nowak 2018-09-30 14:04:56 -07:00
commit c3adc59792
17 changed files with 538 additions and 22 deletions

View File

@ -1313,7 +1313,7 @@ namespace Microsoft.AspNetCore.Mvc.Core
=> string.Format(CultureInfo.CurrentCulture, GetString("NoRoutesMatchedForPage"), p0);
/// <summary>
/// The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page.
/// The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page. If you are using {1} then you must provide the current {2} to use relative pages.
/// </summary>
internal static string UrlHelper_RelativePagePathIsNotSupported
{
@ -1321,10 +1321,10 @@ namespace Microsoft.AspNetCore.Mvc.Core
}
/// <summary>
/// The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page.
/// The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page. If you are using {1} then you must provide the current {2} to use relative pages.
/// </summary>
internal static string FormatUrlHelper_RelativePagePathIsNotSupported(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UrlHelper_RelativePagePathIsNotSupported"), p0);
internal static string FormatUrlHelper_RelativePagePathIsNotSupported(object p0, object p1, object p2)
=> string.Format(CultureInfo.CurrentCulture, GetString("UrlHelper_RelativePagePathIsNotSupported"), p0, p1, p2);
/// <summary>
/// One or more validation errors occurred.

View File

@ -410,7 +410,7 @@
<value>No page named '{0}' matches the supplied values.</value>
</data>
<data name="UrlHelper_RelativePagePathIsNotSupported" xml:space="preserve">
<value>The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page.</value>
<value>The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page. If you are using {1} then you must provide the current {2} to use relative pages.</value>
</data>
<data name="ValidationProblemDescription_Title" xml:space="preserve">
<value>One or more validation errors occurred.</value>

View File

@ -361,7 +361,13 @@ namespace Microsoft.AspNetCore.Mvc.Routing
if (string.IsNullOrEmpty(currentPagePath))
{
// Disallow the use sibling page routing, a Razor page specific feature, from a non-page action.
throw new InvalidOperationException(Resources.FormatUrlHelper_RelativePagePathIsNotSupported(pageName));
// OR - this is a call from LinkGenerator where the HttpContext was not specified.
//
// We can't use a relative path in either case, because we don't know the base path.
throw new InvalidOperationException(Resources.FormatUrlHelper_RelativePagePathIsNotSupported(
pageName,
nameof(LinkGenerator),
nameof(HttpContext)));
}
return ViewEnginePath.CombinePath(currentPagePath, pageName);

View File

@ -10,6 +10,20 @@ namespace Microsoft.AspNetCore.Mvc.Testing
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNetCore.Mvc.Testing.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// The provided Type '{0}' does not belong to an assembly with an entry point. A common cause for this error is providing a Type from a class library.
/// </summary>
internal static string InvalidAssemblyEntryPoint
{
get => GetString("InvalidAssemblyEntryPoint");
}
/// <summary>
/// The provided Type '{0}' does not belong to an assembly with an entry point. A common cause for this error is providing a Type from a class library.
/// </summary>
internal static string FormatInvalidAssemblyEntryPoint(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidAssemblyEntryPoint"), p0);
/// <summary>
/// No method 'public static {0} CreateWebHostBuilder(string[] args)' found on '{1}'. Alternatively, {2} can be extended and 'protected virtual {0} {3}()' can be overridden to provide your own {0} instance.
/// </summary>
@ -38,20 +52,6 @@ namespace Microsoft.AspNetCore.Mvc.Testing
internal static string FormatMissingDepsFile(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("MissingDepsFile"), p0, p1);
/// <summary>
/// The provided Type '{0}' does not belong to an assembly with an entry point. A common cause for this error is providing a Type from a class library.
/// </summary>
internal static string InvalidAssemblyEntryPoint
{
get => GetString("InvalidAssemblyEntryPoint");
}
/// <summary>
/// The provided Type '{0}' does not belong to an assembly with an entry point. A common cause for this error is providing a Type from a class library.
/// </summary>
internal static string FormatInvalidAssemblyEntryPoint(string p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidAssemblyEntryPoint"), p0);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -501,8 +501,11 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Routing
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => urlHelper.Object.Page(expected));
Assert.Equal($"The relative page path '{expected}' can only be used while executing a Razor Page. " +
"Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page.", ex.Message);
Assert.Equal(
$"The relative page path '{expected}' can only be used while executing a Razor Page. " +
"Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page. " +
"If you are using LinkGenerator then you must provide the current HttpContext to use relative pages.",
ex.Message);
}
[Fact]

View File

@ -0,0 +1,221 @@
// 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 System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
// Functional tests for MVC's scenarios with LinkGenerator (2.2+ only)
public class LinkGeneratorTest : IClassFixture<MvcTestFixture<RoutingWebSite.StartupForLinkGenerator>>
{
public LinkGeneratorTest(MvcTestFixture<RoutingWebSite.StartupForLinkGenerator> fixture)
{
var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
Client = factory.CreateDefaultClient();
}
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) =>
builder.UseStartup<RoutingWebSite.StartupForLinkGenerator>();
public HttpClient Client { get; }
[Fact]
public async Task GetPathByAction_CanGeneratePathToSelf()
{
// Act
var response = await Client.GetAsync("LG1/LinkToSelf");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("/LG1/LinkToSelf", responseContent);
}
[Fact]
public async Task GetPathByAction_CanGeneratePathToSelf_PreserveAmbientValues()
{
// Act
var response = await Client.GetAsync("LG1/LinkToSelf/17?another-value=5");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("/LG1/LinkToSelf/17?another-value=5", responseContent);
}
[Fact]
public async Task GetPathByAction_CanGeneratePathToAnotherAction_RemovesAmbientValues()
{
// Act
var response = await Client.GetAsync("LG1/LinkToAnotherAction/17?another-value=5");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("/LG1/LinkToSelf?another-value=5", responseContent);
}
[Fact]
public async Task GetPathByAction_CanGeneratePathToAnotherController_RemovesAmbientValues()
{
// Act
var response = await Client.GetAsync("LG1/LinkToAnotherController/17?another-value=5");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("/LG2/SomeAction?another-value=5", responseContent);
}
[Fact]
public async Task GetPathByAction_CanGeneratePathToAnotherControllerInArea_RemovesAmbientValues()
{
// Act
var response = await Client.GetAsync("LG1/LinkToAnArea/17?another-value=5");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("/Admin/LG3/SomeAction?another-value=5", responseContent);
}
[Fact]
public async Task GetPathByAction_CanGeneratePathWithinArea()
{
// Act
var response = await Client.GetAsync("Admin/LG3/LinkInsideOfArea/17");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("/Admin/LG3/SomeAction", responseContent);
}
// Rejected because the calling code relies on ambient values, but doesn't pass
// the HttpContext.
[Fact]
public async Task GetPathByAction_FailsToGenerateLinkInsideArea()
{
// Act
var response = await Client.GetAsync("Admin/LG3/LinkInsideOfAreaFail/17?another-value=5");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
Assert.Equal(string.Empty, responseContent);
}
[Fact]
public async Task GetPathByAction_CanGeneratePathOutsideOfArea()
{
// Act
var response = await Client.GetAsync("Admin/LG3/LinkOutsideOfArea/17?another-value=5");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
Assert.Equal(string.Empty, responseContent);
}
[Fact]
public async Task GetPathByAction_CanGeneratePathFromPath()
{
// Act
var response = await Client.GetAsync("LGAnotherPage/17");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("/LG2/SomeAction", responseContent);
}
[Fact]
public async Task GetPathByPage_FromPage_CanGeneratePathWithRelativePageName()
{
// Act
var response = await Client.GetAsync("LGPage/17");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("/LGAnotherPage", responseContent);
}
[Fact]
public async Task GetPathByPage_CanGeneratePathToPage()
{
// Act
var response = await Client.GetAsync("LG1/LinkToPage/17?another-value=4");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("/LGPage?another-value=4", responseContent);
}
[Fact]
public async Task GetPathByPage_CanGeneratePathToPageInArea()
{
// Act
var response = await Client.GetAsync("LG1/LinkToPageInArea/17?another-value=4");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("/Admin/LGAreaPage?another-value=4&handler=a-handler", responseContent);
}
[Fact]
public async Task GetUriByAction_CanGenerateFullUri()
{
// Act
var response = await Client.GetAsync("LG1/LinkWithFullUri/17");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("http://localhost/LG1/LinkWithFullUri/17#hi", responseContent);
}
[Fact]
public async Task GetUriByAction_CanGenerateFullUri_WithoutHttpContext()
{
// Act
var response = await Client.GetAsync("LG1/LinkWithFullUriWithoutHttpContext/17");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("https://www.example.com/LG1/LinkWithFullUri#hi", responseContent);
}
[Fact]
public async Task GetUriByPage_CanGenerateFullUri()
{
// Act
var response = await Client.GetAsync("LG1/LinkToPageWithFullUri/17");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("http://localhost/LGPage", responseContent);
}
[Fact]
public async Task GetUriByPage_CanGenerateFullUri_WithoutHttpContext()
{
// Act
var response = await Client.GetAsync("LG1/LinkToPageWithFullUriWithoutHttpContext/17");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("https://www.example.com/Admin/LGAreaPage?handler=a-handler", responseContent);
}
}
}

View File

@ -0,0 +1,44 @@
// 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;
using Microsoft.AspNetCore.Routing;
namespace RoutingWebSite
{
[Area("Admin")]
[Route("[area]/[controller]/[action]/{id?}")]
public class LG3Controller : Controller
{
private readonly LinkGenerator _linkGenerator;
public LG3Controller(LinkGenerator linkGenerator)
{
_linkGenerator = linkGenerator;
}
public void SomeAction()
{
}
public string LinkInsideOfArea()
{
return _linkGenerator.GetPathByAction(HttpContext, action: nameof(SomeAction));
}
public string LinkInsideOfAreaFail()
{
// No ambient values - this will fail.
return _linkGenerator.GetPathByAction(controller: "LG3", action: nameof(SomeAction));
}
public string LinkOutsideOfArea()
{
return _linkGenerator.GetPathByAction(
HttpContext,
action: nameof(SomeAction),
controller: "LG1",
values: new { area = "", });
}
}
}

View File

@ -0,0 +1,4 @@
@page "{id?}"
@model RoutingWebSite.Areas.Admin.Pages.LGAreaPageModel
@{
}

View File

@ -0,0 +1,14 @@
// 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.RazorPages;
namespace RoutingWebSite.Areas.Admin.Pages
{
public class LGAreaPageModel : PageModel
{
public void OnGet()
{
}
}
}

View File

@ -0,0 +1,119 @@
// 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.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
namespace RoutingWebSite
{
public class LG1Controller : Controller
{
private readonly LinkGenerator _linkGenerator;
public LG1Controller(LinkGenerator linkGenerator)
{
_linkGenerator = linkGenerator;
}
public string LinkToSelf()
{
return _linkGenerator.GetPathByAction(HttpContext, values: QueryToRouteValues(HttpContext.Request.Query));
}
public string LinkToAnotherAction()
{
return _linkGenerator.GetPathByAction(
HttpContext,
action: nameof(LinkToSelf),
values: QueryToRouteValues(HttpContext.Request.Query));
}
public string LinkToAnotherController()
{
return _linkGenerator.GetPathByAction(
HttpContext,
controller: "LG2",
action: nameof(LG2Controller.SomeAction),
values: QueryToRouteValues(HttpContext.Request.Query));
}
public string LinkToAnArea()
{
var values = QueryToRouteValues(HttpContext.Request.Query);
values["area"] = "Admin";
return _linkGenerator.GetPathByAction(
HttpContext,
controller: "LG3",
action: nameof(LG3Controller.SomeAction),
values: values);
}
public string LinkToPage()
{
return _linkGenerator.GetPathByPage(
HttpContext,
page: "/LGPage",
values: QueryToRouteValues(HttpContext.Request.Query));
}
public string LinkToPageInArea()
{
var values = QueryToRouteValues(HttpContext.Request.Query);
values["area"] = "Admin";
return _linkGenerator.GetPathByPage(
HttpContext,
page: "/LGAreaPage",
handler: "a-handler",
values: values);
}
public string LinkWithFullUri()
{
return _linkGenerator.GetUriByAction(
HttpContext,
controller: "LG1",
action: nameof(LinkWithFullUri),
values: QueryToRouteValues(HttpContext.Request.Query),
fragment: new FragmentString("#hi"));
}
public string LinkToPageWithFullUri()
{
return _linkGenerator.GetUriByPage(
HttpContext,
page: "/LGPage",
values: QueryToRouteValues(HttpContext.Request.Query));
}
public string LinkWithFullUriWithoutHttpContext()
{
return _linkGenerator.GetUriByAction(
scheme: "https",
host: new HostString("www.example.com"),
controller: "LG1",
action: nameof(LinkWithFullUri),
values: QueryToRouteValues(HttpContext.Request.Query),
fragment: new FragmentString("#hi"));
}
public string LinkToPageWithFullUriWithoutHttpContext()
{
var values = QueryToRouteValues(HttpContext.Request.Query);
values["area"] = "Admin";
return _linkGenerator.GetUriByPage(
scheme: "https",
host: new HostString("www.example.com"),
page: "/LGAreaPage",
handler: "a-handler",
values: values);
}
private static RouteValueDictionary QueryToRouteValues(IQueryCollection query)
{
return new RouteValueDictionary(query.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToString()));
}
}
}

View File

@ -0,0 +1,14 @@
// 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 RoutingWebSite
{
public class LG2Controller : Controller
{
public void SomeAction()
{
}
}
}

View File

@ -0,0 +1,4 @@
@page "{id?}"
@model RoutingWebSite.Pages.LGAnotherPageModel
@{
}

View File

@ -0,0 +1,24 @@
// 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;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Routing;
namespace RoutingWebSite.Pages
{
public class LGAnotherPageModel : PageModel
{
private readonly LinkGenerator _linkGenerator;
public LGAnotherPageModel(LinkGenerator linkGenerator)
{
_linkGenerator = linkGenerator;
}
public ContentResult OnGet()
{
return Content(_linkGenerator.GetPathByAction(HttpContext, action: nameof(LG2Controller.SomeAction), controller: "LG2"));
}
}
}

View File

@ -0,0 +1,4 @@
@page "{id?}"
@model BasicWebSite.Pages.LGPageModel
@{
}

View File

@ -0,0 +1,24 @@
// 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;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Routing;
namespace BasicWebSite.Pages
{
public class LGPageModel : PageModel
{
private readonly LinkGenerator _linkGenerator;
public LGPageModel(LinkGenerator linkGenerator)
{
_linkGenerator = linkGenerator;
}
public ContentResult OnGet()
{
return Content(_linkGenerator.GetPathByPage(HttpContext, "./LGAnotherPage"));
}
}
}

View File

@ -10,5 +10,6 @@
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
<PackageReference Include="Microsoft.NET.Sdk.Razor" Version="$(MicrosoftNETSdkRazorPackageVersion)" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,34 @@
// 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.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
namespace RoutingWebSite
{
// A very basic routing configuration for LinkGenerator tests
public class StartupForLinkGenerator
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Latest);
services
.AddRouting(options =>
{
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
});
services.AddScoped<TestResponseGenerator>();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvcWithDefaultRoute();
}
}
}