Merge pull request #8551 from dotnet-maestro-bot/merge/release/2.2-to-master

[automated] Merge branch 'release/2.2' => 'master'
This commit is contained in:
Pranav K 2018-10-04 09:46:45 -07:00 committed by GitHub
commit 523b2223ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 261 additions and 24 deletions

View File

@ -3,7 +3,7 @@ ASP.NET Core MVC
**Note: For ASP.NET MVC 5.x, Web API 2.x, and Web Pages 3.x (not ASP.NET Core), see https://github.com/aspnet/AspNetWebStack** **Note: For ASP.NET MVC 5.x, Web API 2.x, and Web Pages 3.x (not ASP.NET Core), see https://github.com/aspnet/AspNetWebStack**
Travis: [![Travis](https://travis-ci.org/aspnet/Mvc.svg?branch=dev)](https://travis-ci.org/aspnet/Mvc) Travis: [![Travis](https://travis-ci.org/aspnet/Mvc.svg?branch=master)](https://travis-ci.org/aspnet/Mvc)
ASP.NET Core MVC gives you a powerful, patterns-based way to build dynamic websites that enables a clean separation of concerns and gives you full control over markup for enjoyable, agile development. ASP.NET Core MVC includes many features that enable fast, TDD-friendly development for creating sophisticated applications that use the latest web standards. ASP.NET Core MVC gives you a powerful, patterns-based way to build dynamic websites that enables a clean separation of concerns and gives you full control over markup for enjoyable, agile development. ASP.NET Core MVC includes many features that enable fast, TDD-friendly development for creating sophisticated applications that use the latest web standards.

View File

@ -10,6 +10,6 @@ They makes it easier to test local changes than having the App in the Benchmarks
3. If cloned go to the BenchmarksDriver project 3. If cloned go to the BenchmarksDriver project
4. Use the following command as a guideline for running a test using your changes 4. Use the following command as a guideline for running a test using your changes
`benchmarks --server <server-endpoint> --client <client-endpoint> -j https://raw.githubusercontent.com/aspnet/MVC/dev/benchmarkaps/BasicApi/BasicApi.json` `benchmarks --server <server-endpoint> --client <client-endpoint> -j https://raw.githubusercontent.com/aspnet/MVC/{your branch}/benchmarkaps/BasicApi/BasicApi.json`
5. For more info/commands see https://github.com/aspnet/benchmarks/blob/dev/src/BenchmarksDriver/README.md 5. For more info/commands see https://github.com/aspnet/benchmarks/blob/master/src/BenchmarksDriver/README.md

View File

@ -8,7 +8,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{ {
/// <summary> /// <summary>
/// An <see cref="IActionModelConvention"/> that sets attribute routing token replacement /// An <see cref="IActionModelConvention"/> that sets attribute routing token replacement
/// to use the specified <see cref="IOutboundParameterTransformer"/> on <see cref="ActionModel"/> selectors. /// to use the specified <see cref="IOutboundParameterTransformer"/> on <see cref="ActionModel"/>.
/// This convention does not effect Razor page routes.
/// </summary> /// </summary>
public class RouteTokenTransformerConvention : IActionModelConvention public class RouteTokenTransformerConvention : IActionModelConvention
{ {

View File

@ -0,0 +1,42 @@
// 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.Routing;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
/// <summary>
/// An <see cref="IPageRouteModelConvention"/> that sets page route resolution
/// to use the specified <see cref="IOutboundParameterTransformer"/> on <see cref="PageRouteModel"/>.
/// This convention does not effect controller action routes.
/// </summary>
public class PageRouteTransformerConvention : IPageRouteModelConvention
{
private IOutboundParameterTransformer _parameterTransformer;
/// <summary>
/// Creates a new instance of <see cref="PageRouteTransformerConvention"/> with the specified <see cref="IOutboundParameterTransformer"/>.
/// </summary>
/// <param name="parameterTransformer">The <see cref="IOutboundParameterTransformer"/> to use resolve page routes.</param>
public PageRouteTransformerConvention(IOutboundParameterTransformer parameterTransformer)
{
if (parameterTransformer == null)
{
throw new ArgumentNullException(nameof(parameterTransformer));
}
_parameterTransformer = parameterTransformer;
}
public void Apply(PageRouteModel model)
{
if (ShouldApply(model))
{
model.Properties[typeof(IOutboundParameterTransformer)] = _parameterTransformer;
}
}
protected virtual bool ShouldApply(PageRouteModel action) => true;
}
}

View File

@ -4,10 +4,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
@ -81,7 +86,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{ {
Name = selector.AttributeRouteModel.Name, Name = selector.AttributeRouteModel.Name,
Order = selector.AttributeRouteModel.Order ?? 0, Order = selector.AttributeRouteModel.Order ?? 0,
Template = selector.AttributeRouteModel.Template, Template = TransformPageRoute(model, selector),
SuppressLinkGeneration = selector.AttributeRouteModel.SuppressLinkGeneration, SuppressLinkGeneration = selector.AttributeRouteModel.SuppressLinkGeneration,
SuppressPathMatching = selector.AttributeRouteModel.SuppressPathMatching, SuppressPathMatching = selector.AttributeRouteModel.SuppressPathMatching,
}, },
@ -109,5 +114,35 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
actions.Add(descriptor); actions.Add(descriptor);
} }
} }
private static string TransformPageRoute(PageRouteModel model, SelectorModel selectorModel)
{
model.Properties.TryGetValue(typeof(IOutboundParameterTransformer), out var transformer);
var pageRouteTransformer = transformer as IOutboundParameterTransformer;
// Transformer not set on page route
if (pageRouteTransformer == null)
{
return selectorModel.AttributeRouteModel.Template;
}
var pageRouteMetadata = selectorModel.EndpointMetadata.OfType<PageRouteMetadata>().SingleOrDefault();
if (pageRouteMetadata == null)
{
// Selector does not have expected metadata. Should never reach here
throw new InvalidOperationException("Page selector did not have page route metadata.");
}
var segments = pageRouteMetadata.PageRoute.Split('/');
for (var i = 0; i < segments.Length; i++)
{
segments[i] = pageRouteTransformer.TransformOutbound(segments[i]);
}
var transformedPageRoute = string.Join("/", segments);
// Combine transformed page route with template
return AttributeRouteModel.CombineTemplates(transformedPageRoute, pageRouteMetadata.RouteTemplate);
}
} }
} }

View File

@ -0,0 +1,18 @@
// 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.
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
// This is used to store the uncombined parts of the final page route
internal class PageRouteMetadata
{
public PageRouteMetadata(string pageRoute, string routeTemplate)
{
PageRoute = pageRoute;
RouteTemplate = routeTemplate;
}
public string PageRoute { get; }
public string RouteTemplate { get; }
}
}

View File

@ -159,6 +159,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
AttributeRouteModel = new AttributeRouteModel AttributeRouteModel = new AttributeRouteModel
{ {
Template = AttributeRouteModel.CombineTemplates(prefix, routeTemplate), Template = AttributeRouteModel.CombineTemplates(prefix, routeTemplate),
},
EndpointMetadata =
{
new PageRouteMetadata(prefix, routeTemplate)
} }
}; };
} }

View File

@ -11,25 +11,6 @@ namespace Microsoft.AspNetCore.Mvc.Test.ApplicationModels
{ {
public class RouteTokenTransformerConventionTest public class RouteTokenTransformerConventionTest
{ {
[Fact]
public void Apply_NullAttributeRouteModel_NoOp()
{
// Arrange
var convention = new RouteTokenTransformerConvention(new TestParameterTransformer());
var model = new ActionModel(GetMethodInfo(), Array.Empty<object>());
model.Selectors.Add(new SelectorModel()
{
AttributeRouteModel = null
});
// Act
convention.Apply(model);
// Assert
Assert.Null(model.Selectors[0].AttributeRouteModel);
}
[Fact] [Fact]
public void Apply_HasAttributeRouteModel_SetRouteTokenTransformer() public void Apply_HasAttributeRouteModel_SetRouteTokenTransformer()
{ {

View File

@ -158,6 +158,18 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal("/LGPage?another-value=4", responseContent); Assert.Equal("/LGPage?another-value=4", responseContent);
} }
[Fact]
public async Task GetPathByPage_CanGeneratePathToPage_PathTransformed()
{
// Act
var response = await Client.GetAsync("LG1/LinkToPageWithTransformedPath?id=HelloWorld");
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("/page-route-transformer/test-page/ExtraPath/HelloWorld", responseContent);
}
[Fact] [Fact]
public async Task GetPathByPage_CanGeneratePathToPageInArea() public async Task GetPathByPage_CanGeneratePathToPageInArea()
{ {

View File

@ -115,6 +115,38 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Empty(result); Assert.Empty(result);
} }
[Fact]
public async Task Page_PageRouteTransformer()
{
// Arrange & Act
var response = await Client.GetAsync("http://localhost/page-route-transformer/index");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
[Fact]
public async Task Page_PageRouteTransformer_WithoutIndex()
{
// Arrange & Act
var response = await Client.GetAsync("http://localhost/page-route-transformer");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
[Fact]
public async Task Page_PageRouteTransformer_RouteParameter()
{
// Arrange & Act
var response = await Client.GetAsync("http://localhost/page-route-transformer/test-page/ExtraPath/World");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("Hello from World", body);
}
[Fact] [Fact]
public virtual async Task ConventionalRoutedController_ActionIsReachable() public virtual async Task ConventionalRoutedController_ActionIsReachable()
{ {

View File

@ -0,0 +1,67 @@
// 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.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Routing;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Test.ApplicationModels
{
public class PageRouteTransformerConventionTest
{
[Fact]
public void Apply_SetTransformer()
{
// Arrange
var transformer = new TestParameterTransformer();
var convention = new PageRouteTransformerConvention(transformer);
var model = new PageRouteModel(string.Empty, string.Empty);
// Act
convention.Apply(model);
// Assert
Assert.True(model.Properties.TryGetValue(typeof(IOutboundParameterTransformer), out var routeTokenTransformer));
Assert.Equal(transformer, routeTokenTransformer);
}
[Fact]
public void Apply_ShouldApplyFalse_NoOp()
{
// Arrange
var transformer = new TestParameterTransformer();
var convention = new CustomPageRouteTransformerConvention(transformer);
var model = new PageRouteModel(string.Empty, string.Empty);
// Act
convention.Apply(model);
// Assert
Assert.False(model.Properties.TryGetValue(typeof(IOutboundParameterTransformer), out _));
}
private class TestParameterTransformer : IOutboundParameterTransformer
{
public string TransformOutbound(object value)
{
return value?.ToString();
}
}
private class CustomPageRouteTransformerConvention : PageRouteTransformerConvention
{
public CustomPageRouteTransformerConvention(IOutboundParameterTransformer parameterTransformer) : base(parameterTransformer)
{
}
protected override bool ShouldApply(PageRouteModel action)
{
return false;
}
}
}
}

View File

@ -59,6 +59,14 @@ namespace RoutingWebSite
values: QueryToRouteValues(HttpContext.Request.Query)); values: QueryToRouteValues(HttpContext.Request.Query));
} }
public string LinkToPageWithTransformedPath()
{
return _linkGenerator.GetPathByPage(
HttpContext,
page: "/PageRouteTransformer/TestPage",
values: QueryToRouteValues(HttpContext.Request.Query));
}
public string LinkToPageInArea() public string LinkToPageInArea()
{ {
var values = QueryToRouteValues(HttpContext.Request.Query); var values = QueryToRouteValues(HttpContext.Request.Query);

View File

@ -0,0 +1,3 @@
@page
@{
}

View File

@ -0,0 +1,4 @@
@page "ExtraPath/{id?}"
@{
}
Hello from @ViewContext.RouteData.Values["id"]

View File

@ -4,6 +4,7 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -15,6 +16,8 @@ namespace RoutingWebSite
// Set up application services // Set up application services
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
var pageRouteTransformerConvention = new PageRouteTransformerConvention(new SlugifyParameterTransformer());
services services
.AddMvc(options => .AddMvc(options =>
{ {
@ -23,6 +26,13 @@ namespace RoutingWebSite
typeof(ParameterTransformerController), typeof(ParameterTransformerController),
new SlugifyParameterTransformer())); new SlugifyParameterTransformer()));
}) })
.AddRazorPagesOptions(options =>
{
options.Conventions.AddFolderRouteModelConvention("/PageRouteTransformer", model =>
{
pageRouteTransformerConvention.Apply(model);
});
})
.SetCompatibilityVersion(CompatibilityVersion.Latest); .SetCompatibilityVersion(CompatibilityVersion.Latest);
services services
.AddRouting(options => .AddRouting(options =>

View File

@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -13,8 +14,17 @@ namespace RoutingWebSite
{ {
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
var pageRouteTransformerConvention = new PageRouteTransformerConvention(new SlugifyParameterTransformer());
services services
.AddMvc() .AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AddFolderRouteModelConvention("/PageRouteTransformer", model =>
{
pageRouteTransformerConvention.Apply(model);
});
})
.SetCompatibilityVersion(CompatibilityVersion.Latest); .SetCompatibilityVersion(CompatibilityVersion.Latest);
services services
.AddRouting(options => .AddRouting(options =>

View File

@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -19,8 +20,17 @@ namespace RoutingWebSite
// Set up application services // Set up application services
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
var pageRouteTransformerConvention = new PageRouteTransformerConvention(new SlugifyParameterTransformer());
services services
.AddMvc() .AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AddFolderRouteModelConvention("/PageRouteTransformer", model =>
{
pageRouteTransformerConvention.Apply(model);
});
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1); .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddScoped<TestResponseGenerator>(); services.AddScoped<TestResponseGenerator>();