Add PageRouteTransformerConvention (#8541)
This commit is contained in:
parent
7854d65c11
commit
94101a9cde
|
|
@ -8,7 +8,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
{
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public class RouteTokenTransformerConvention : IActionModelConvention
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,10 +4,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||
|
|
@ -81,7 +86,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
{
|
||||
Name = selector.AttributeRouteModel.Name,
|
||||
Order = selector.AttributeRouteModel.Order ?? 0,
|
||||
Template = selector.AttributeRouteModel.Template,
|
||||
Template = TransformPageRoute(model, selector),
|
||||
SuppressLinkGeneration = selector.AttributeRouteModel.SuppressLinkGeneration,
|
||||
SuppressPathMatching = selector.AttributeRouteModel.SuppressPathMatching,
|
||||
},
|
||||
|
|
@ -109,5 +114,35 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -159,6 +159,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
AttributeRouteModel = new AttributeRouteModel
|
||||
{
|
||||
Template = AttributeRouteModel.CombineTemplates(prefix, routeTemplate),
|
||||
},
|
||||
EndpointMetadata =
|
||||
{
|
||||
new PageRouteMetadata(prefix, routeTemplate)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,25 +11,6 @@ namespace Microsoft.AspNetCore.Mvc.Test.ApplicationModels
|
|||
{
|
||||
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]
|
||||
public void Apply_HasAttributeRouteModel_SetRouteTokenTransformer()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -158,6 +158,18 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
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]
|
||||
public async Task GetPathByPage_CanGeneratePathToPageInArea()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -115,6 +115,38 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
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]
|
||||
public virtual async Task ConventionalRoutedController_ActionIsReachable()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -59,6 +59,14 @@ namespace RoutingWebSite
|
|||
values: QueryToRouteValues(HttpContext.Request.Query));
|
||||
}
|
||||
|
||||
public string LinkToPageWithTransformedPath()
|
||||
{
|
||||
return _linkGenerator.GetPathByPage(
|
||||
HttpContext,
|
||||
page: "/PageRouteTransformer/TestPage",
|
||||
values: QueryToRouteValues(HttpContext.Request.Query));
|
||||
}
|
||||
|
||||
public string LinkToPageInArea()
|
||||
{
|
||||
var values = QueryToRouteValues(HttpContext.Request.Query);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
@page
|
||||
@{
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
@page "ExtraPath/{id?}"
|
||||
@{
|
||||
}
|
||||
Hello from @ViewContext.RouteData.Values["id"]
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
|
@ -15,6 +16,8 @@ namespace RoutingWebSite
|
|||
// Set up application services
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
var pageRouteTransformerConvention = new PageRouteTransformerConvention(new SlugifyParameterTransformer());
|
||||
|
||||
services
|
||||
.AddMvc(options =>
|
||||
{
|
||||
|
|
@ -23,6 +26,13 @@ namespace RoutingWebSite
|
|||
typeof(ParameterTransformerController),
|
||||
new SlugifyParameterTransformer()));
|
||||
})
|
||||
.AddRazorPagesOptions(options =>
|
||||
{
|
||||
options.Conventions.AddFolderRouteModelConvention("/PageRouteTransformer", model =>
|
||||
{
|
||||
pageRouteTransformerConvention.Apply(model);
|
||||
});
|
||||
})
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Latest);
|
||||
services
|
||||
.AddRouting(options =>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
|
|
@ -13,8 +14,17 @@ namespace RoutingWebSite
|
|||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
var pageRouteTransformerConvention = new PageRouteTransformerConvention(new SlugifyParameterTransformer());
|
||||
|
||||
services
|
||||
.AddMvc()
|
||||
.AddRazorPagesOptions(options =>
|
||||
{
|
||||
options.Conventions.AddFolderRouteModelConvention("/PageRouteTransformer", model =>
|
||||
{
|
||||
pageRouteTransformerConvention.Apply(model);
|
||||
});
|
||||
})
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Latest);
|
||||
services
|
||||
.AddRouting(options =>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Builder;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
|
@ -19,8 +20,17 @@ namespace RoutingWebSite
|
|||
// Set up application services
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
var pageRouteTransformerConvention = new PageRouteTransformerConvention(new SlugifyParameterTransformer());
|
||||
|
||||
services
|
||||
.AddMvc()
|
||||
.AddRazorPagesOptions(options =>
|
||||
{
|
||||
options.Conventions.AddFolderRouteModelConvention("/PageRouteTransformer", model =>
|
||||
{
|
||||
pageRouteTransformerConvention.Apply(model);
|
||||
});
|
||||
})
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
|
||||
|
||||
services.AddScoped<TestResponseGenerator>();
|
||||
|
|
|
|||
Loading…
Reference in New Issue