Endpoing routing 3.0 registration (#8470)

This commit is contained in:
James Newton-King 2018-10-23 14:04:12 +13:00 committed by GitHub
parent a3e999762f
commit e9737a90e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 531 additions and 54 deletions

View File

@ -48,8 +48,8 @@
<MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>
<MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>
<MicrosoftAspNetCoreResponseCachingPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreResponseCachingPackageVersion>
<MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>
<MicrosoftAspNetCoreRoutingPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreRoutingPackageVersion>
<MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>3.0.0-a-alpha1-master-builder-17073</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>
<MicrosoftAspNetCoreRoutingPackageVersion>3.0.0-a-alpha1-master-builder-17073</MicrosoftAspNetCoreRoutingPackageVersion>
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
<MicrosoftAspNetCoreServerKestrelPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreServerKestrelPackageVersion>
<MicrosoftAspNetCoreSessionPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreSessionPackageVersion>

View File

@ -0,0 +1,21 @@
// 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 MvcSandbox.AuthorizationMiddleware;
namespace Microsoft.AspNetCore.Builder
{
public static class AuthorizationAppBuilderExtensions
{
public static IApplicationBuilder UseAuthorization(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
return app.UseMiddleware<AuthorizationMiddleware>();
}
}
}

View File

@ -0,0 +1,16 @@
// 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.Routing;
namespace MvcSandbox.AuthorizationMiddleware
{
public static class AuthorizationEndpointConventionBuilder
{
public static T RequireAuthorization<T>(this T builder, params string[] roles) where T : IEndpointConventionBuilder
{
builder.Apply(model => model.Metadata.Add(new AuthorizeMetadataAttribute(roles)));
return builder;
}
}
}

View File

@ -0,0 +1,52 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
namespace MvcSandbox.AuthorizationMiddleware
{
public class AuthorizationMiddleware
{
private readonly RequestDelegate _next;
public AuthorizationMiddleware(RequestDelegate next)
{
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
var endpoint = httpContext.Features.Get<IEndpointFeature>()?.Endpoint;
var metadata = endpoint?.Metadata?.GetMetadata<AuthorizeMetadataAttribute>();
// Only run authorization if endpoint has metadata
if (metadata != null)
{
// Check if role querystring value is a valid role
if (!httpContext.Request.Query.TryGetValue("role", out var role) ||
!metadata.Roles.Contains(role.ToString(), StringComparer.OrdinalIgnoreCase))
{
httpContext.Response.StatusCode = 401;
httpContext.Response.ContentType = "text/plain";
await httpContext.Response.WriteAsync($"Unauthorized access to '{endpoint.DisplayName}'.");
return;
}
}
await _next(httpContext);
}
}
}

View File

@ -0,0 +1,20 @@
// 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.Linq;
using System.Threading.Tasks;
namespace MvcSandbox.AuthorizationMiddleware
{
public class AuthorizeMetadataAttribute : Attribute
{
public AuthorizeMetadataAttribute(string[] roles)
{
Roles = roles;
}
public string[] Roles { get; }
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Mvc;
using MvcSandbox.AuthorizationMiddleware;
namespace MvcSandbox.Controllers
{

View File

@ -0,0 +1,16 @@
// 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 MvcSandbox.Controllers
{
[Route("[controller]/[action]")]
public class LoginController : Controller
{
public IActionResult Index()
{
return View();
}
}
}

View File

@ -0,0 +1,28 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
namespace Microsoft.AspNetCore.Builder
{
public static class HealthChecksEndpointRouteBuilderExtensions
{
private static readonly Random _random = new Random();
public static IEndpointConventionBuilder MapHealthChecks(this IEndpointRouteBuilder builder, string pattern)
{
return builder.MapGet(
pattern,
async httpContext =>
{
await httpContext.Response.WriteAsync(_random.Next() % 2 == 0 ? "Up!" : "Down!");
});
}
}
}

View File

@ -1,11 +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.
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MvcSandbox.AuthorizationMiddleware;
namespace MvcSandbox
{
@ -20,14 +27,46 @@ namespace MvcSandbox
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app)
{
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseMvc(routes =>
app.UseEndpointRouting(builder =>
{
routes.MapRoute(
builder.MapGet(
requestDelegate: WriteEndpoints,
pattern: "/endpoints",
displayName: "Home");
builder.MapMvcRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
builder.MapMvcControllers();
builder.MapRazorPages();
builder.MapHealthChecks("/healthz");
});
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseAuthorization();
app.UseEndpoint();
}
private static Task WriteEndpoints(HttpContext httpContext)
{
var dataSource = httpContext.RequestServices.GetRequiredService<CompositeEndpointDataSource>();
var sb = new StringBuilder();
sb.AppendLine("Endpoints:");
foreach (var endpoint in dataSource.Endpoints.OfType<RouteEndpoint>().OrderBy(e => e.RoutePattern.RawText, StringComparer.OrdinalIgnoreCase))
{
sb.AppendLine($"- {endpoint.RoutePattern.RawText} '{endpoint.DisplayName}'");
}
var response = httpContext.Response;
response.StatusCode = 200;
response.ContentType = "text/plain";
return response.WriteAsync(sb.ToString());
}
public static void Main(string[] args)

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 System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Routing;
namespace Microsoft.AspNetCore.Builder
{
internal class DefaultEndpointConventionBuilder : IEndpointConventionBuilder
{
public DefaultEndpointConventionBuilder()
{
Conventions = new List<Action<EndpointModel>>();
}
public List<Action<EndpointModel>> Conventions { get; }
public void Apply(Action<EndpointModel> convention)
{
Conventions.Add(convention);
}
}
}

View File

@ -90,9 +90,7 @@ namespace Microsoft.AspNetCore.Builder
if (options.Value.EnableEndpointRouting)
{
var mvcEndpointDataSource = app.ApplicationServices
.GetRequiredService<IEnumerable<EndpointDataSource>>()
.OfType<MvcEndpointDataSource>()
.First();
.GetRequiredService<MvcEndpointDataSource>();
var parameterPolicyFactory = app.ApplicationServices
.GetRequiredService<ParameterPolicyFactory>();
@ -122,11 +120,21 @@ namespace Microsoft.AspNetCore.Builder
}
}
// Include all controllers with attribute routing and Razor pages
var defaultEndpointConventionBuilder = new DefaultEndpointConventionBuilder();
mvcEndpointDataSource.AttributeRoutingConventionResolvers.Add((actionDescriptor) =>
{
return defaultEndpointConventionBuilder;
});
if (!app.Properties.TryGetValue(EndpointRoutingRegisteredKey, out _))
{
// Matching middleware has not been registered yet
// For back-compat register middleware so an endpoint is matched and then immediately used
app.UseEndpointRouting();
app.UseEndpointRouting(routerBuilder =>
{
routerBuilder.DataSources.Add(mvcEndpointDataSource);
});
}
return app.UseEndpoint();

View File

@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Builder
{
internal class MvcEndpointInfo
internal class MvcEndpointInfo : DefaultEndpointConventionBuilder
{
public MvcEndpointInfo(
string name,
@ -43,6 +43,7 @@ namespace Microsoft.AspNetCore.Builder
public string Name { get; }
public string Pattern { get; }
public Type ControllerType { get; set; }
// Non-inline defaults
public RouteValueDictionary Defaults { get; }

View File

@ -0,0 +1,148 @@
// 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.Linq;
using System.Text;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Builder
{
public static class MvcEndpointRouteBuilderExtensions
{
public static IEndpointConventionBuilder MapMvcControllers(
this IEndpointRouteBuilder routeBuilder)
{
return MapMvcControllers<ControllerBase>(routeBuilder);
}
public static IEndpointConventionBuilder MapMvcControllers<TController>(
this IEndpointRouteBuilder routeBuilder) where TController : ControllerBase
{
var mvcEndpointDataSource = routeBuilder.DataSources.OfType<MvcEndpointDataSource>().FirstOrDefault();
if (mvcEndpointDataSource == null)
{
mvcEndpointDataSource = routeBuilder.ServiceProvider.GetRequiredService<MvcEndpointDataSource>();
routeBuilder.DataSources.Add(mvcEndpointDataSource);
}
var conventionBuilder = new DefaultEndpointConventionBuilder();
mvcEndpointDataSource.AttributeRoutingConventionResolvers.Add(actionDescriptor =>
{
if (actionDescriptor is ControllerActionDescriptor controllerActionDescriptor &&
typeof(TController).IsAssignableFrom(controllerActionDescriptor.ControllerTypeInfo))
{
return conventionBuilder;
}
return null;
});
return conventionBuilder;
}
public static IEndpointConventionBuilder MapMvcRoute(
this IEndpointRouteBuilder routeBuilder,
string name,
string template)
{
return MapMvcRoute<ControllerBase>(routeBuilder, name, template, defaults: null);
}
public static IEndpointConventionBuilder MapMvcRoute(
this IEndpointRouteBuilder routeBuilder,
string name,
string template,
object defaults)
{
return MapMvcRoute<ControllerBase>(routeBuilder, name, template, defaults, constraints: null);
}
public static IEndpointConventionBuilder MapMvcRoute(
this IEndpointRouteBuilder routeBuilder,
string name,
string template,
object defaults,
object constraints)
{
return MapMvcRoute<ControllerBase>(routeBuilder, name, template, defaults, constraints, dataTokens: null);
}
public static IEndpointConventionBuilder MapMvcRoute(
this IEndpointRouteBuilder routeBuilder,
string name,
string template,
object defaults,
object constraints,
object dataTokens)
{
return MapMvcRoute<ControllerBase>(routeBuilder, name, template, defaults, constraints, dataTokens);
}
public static IEndpointConventionBuilder MapMvcRoute<TController>(
this IEndpointRouteBuilder routeBuilder,
string name,
string template) where TController : ControllerBase
{
return MapMvcRoute<TController>(routeBuilder, name, template, defaults: null);
}
public static IEndpointConventionBuilder MapMvcRoute<TController>(
this IEndpointRouteBuilder routeBuilder,
string name,
string template,
object defaults) where TController : ControllerBase
{
return MapMvcRoute<TController>(routeBuilder, name, template, defaults, constraints: null);
}
public static IEndpointConventionBuilder MapMvcRoute<TController>(
this IEndpointRouteBuilder routeBuilder,
string name,
string template,
object defaults,
object constraints) where TController : ControllerBase
{
return MapMvcRoute<TController>(routeBuilder, name, template, defaults, constraints, dataTokens: null);
}
public static IEndpointConventionBuilder MapMvcRoute<TController>(
this IEndpointRouteBuilder routeBuilder,
string name,
string template,
object defaults,
object constraints,
object dataTokens) where TController : ControllerBase
{
var mvcEndpointDataSource = routeBuilder.DataSources.OfType<MvcEndpointDataSource>().FirstOrDefault();
if (mvcEndpointDataSource == null)
{
mvcEndpointDataSource = routeBuilder.ServiceProvider.GetRequiredService<MvcEndpointDataSource>();
routeBuilder.DataSources.Add(mvcEndpointDataSource);
}
var endpointInfo = new MvcEndpointInfo(
name,
template,
new RouteValueDictionary(defaults),
new RouteValueDictionary(constraints),
new RouteValueDictionary(dataTokens),
routeBuilder.ServiceProvider.GetRequiredService<ParameterPolicyFactory>());
endpointInfo.ControllerType = typeof(TController);
mvcEndpointDataSource.ConventionalEndpointInfos.Add(endpointInfo);
return endpointInfo;
}
}
}

View File

@ -270,8 +270,7 @@ namespace Microsoft.Extensions.DependencyInjection
//
// Endpoint Routing / Endpoints
//
services.TryAddEnumerable(
ServiceDescriptor.Singleton<EndpointDataSource, MvcEndpointDataSource>());
services.TryAddSingleton<MvcEndpointDataSource>();
services.TryAddSingleton<MvcEndpointInvokerFactory>();
//

View File

@ -10,6 +10,7 @@ using System.Threading;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
@ -57,6 +58,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
_parameterPolicyFactory = parameterPolicyFactory;
ConventionalEndpointInfos = new List<MvcEndpointInfo>();
AttributeRoutingConventionResolvers = new List<Func<ActionDescriptor, DefaultEndpointConventionBuilder>>();
// IMPORTANT: this needs to be the last thing we do in the constructor. Change notifications can happen immediately!
//
@ -72,6 +74,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public List<MvcEndpointInfo> ConventionalEndpointInfos { get; }
public List<Func<ActionDescriptor, DefaultEndpointConventionBuilder>> AttributeRoutingConventionResolvers { get; }
public override IReadOnlyList<Endpoint> Endpoints
{
get
@ -134,6 +138,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// - Home/Login
foreach (var endpointInfo in ConventionalEndpointInfos)
{
if (endpointInfo.ControllerType != null &&
endpointInfo.ControllerType != typeof(ControllerBase))
{
if (!ValidateControllerConstraint(action, endpointInfo))
{
// Action descriptor does not belong to a controller of the specified type
continue;
}
}
// An 'endpointInfo' is applicable if:
// 1. it has a parameter (or default value) for 'required' non-null route value
// 2. it does not have a parameter (or default value) for 'required' null route value
@ -164,11 +178,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal
endpointInfo.DataTokens,
endpointInfo.ParameterPolicies,
suppressLinkGeneration: false,
suppressPathMatching: false);
suppressPathMatching: false,
endpointInfo.Conventions);
}
}
else
{
var conventionBuilder = ResolveActionConventionBuilder(action);
if (conventionBuilder == null)
{
// No convention builder for this action
// Do not create an endpoint for it
continue;
}
var attributeRoutePattern = RoutePatternFactory.Parse(action.AttributeRouteInfo.Template);
CreateEndpoints(
@ -183,7 +206,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
dataTokens: null,
allParameterPolicies: null,
action.AttributeRouteInfo.SuppressLinkGeneration,
action.AttributeRouteInfo.SuppressPathMatching);
action.AttributeRouteInfo.SuppressPathMatching,
conventionBuilder.Conventions);
}
}
@ -205,6 +229,30 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
}
private DefaultEndpointConventionBuilder ResolveActionConventionBuilder(ActionDescriptor action)
{
foreach (var filter in AttributeRoutingConventionResolvers)
{
var conventionBuilder = filter(action);
if (conventionBuilder != null)
{
return conventionBuilder;
}
}
return null;
}
private static bool ValidateControllerConstraint(ActionDescriptor action, MvcEndpointInfo endpointInfo)
{
if (action is ControllerActionDescriptor controllerActionDescriptor)
{
return endpointInfo.ControllerType.IsAssignableFrom(controllerActionDescriptor.ControllerTypeInfo);
}
return false;
}
// CreateEndpoints processes the route pattern, replacing area/controller/action parameters with endpoint values
// Because of default values it is possible for a route pattern to resolve to multiple endpoints
private int CreateEndpoints(
@ -219,7 +267,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
RouteValueDictionary dataTokens,
IDictionary<string, IList<IParameterPolicy>> allParameterPolicies,
bool suppressLinkGeneration,
bool suppressPathMatching)
bool suppressPathMatching,
List<Action<EndpointModel>> conventions)
{
var newPathSegments = routePattern.PathSegments.ToList();
@ -245,7 +294,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
routeOrder++,
dataTokens,
suppressLinkGeneration,
suppressPathMatching);
suppressPathMatching,
conventions);
endpoints.Add(subEndpoint);
}
@ -314,7 +364,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
routeOrder++,
dataTokens,
suppressLinkGeneration,
suppressPathMatching);
suppressPathMatching,
conventions);
endpoints.Add(endpoint);
return routeOrder;
@ -448,7 +499,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
int order,
RouteValueDictionary dataTokens,
bool suppressLinkGeneration,
bool suppressPathMatching)
bool suppressPathMatching,
List<Action<EndpointModel>> conventions)
{
RequestDelegate requestDelegate = (context) =>
{
@ -463,7 +515,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var defaults = new RouteValueDictionary(nonInlineDefaults);
EnsureRequiredValuesInDefaults(action.RouteValues, defaults);
var metadataCollection = BuildEndpointMetadata(
var model = new RouteEndpointModel(requestDelegate, RoutePatternFactory.Pattern(patternRawText, defaults, parameterPolicies: null, segments), order);
AddEndpointMetadata(
model.Metadata,
action,
routeName,
new RouteValueDictionary(action.RouteValues),
@ -471,17 +526,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal
suppressLinkGeneration,
suppressPathMatching);
var endpoint = new RouteEndpoint(
requestDelegate,
RoutePatternFactory.Pattern(patternRawText, defaults, parameterPolicies: null, segments),
order,
metadataCollection,
action.DisplayName);
model.DisplayName = action.DisplayName;
return endpoint;
if (conventions != null)
{
foreach (var convention in conventions)
{
convention(model);
}
}
return (RouteEndpoint)model.Build();
}
private static EndpointMetadataCollection BuildEndpointMetadata(
private static void AddEndpointMetadata(
IList<object> metadata,
ActionDescriptor action,
string routeName,
RouteValueDictionary requiredValues,
@ -489,14 +548,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
bool suppressLinkGeneration,
bool suppressPathMatching)
{
var metadata = new List<object>
{
action
};
metadata.Add(action);
if (action.EndpointMetadata != null)
{
metadata.AddRange(action.EndpointMetadata);
foreach (var d in action.EndpointMetadata)
{
metadata.Add(d);
}
}
if (dataTokens != null)
@ -509,8 +568,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Add filter descriptors to endpoint metadata
if (action.FilterDescriptors != null && action.FilterDescriptors.Count > 0)
{
metadata.AddRange(action.FilterDescriptors.OrderBy(f => f, FilterDescriptorOrderComparer.Comparer)
.Select(f => f.Filter));
foreach (var filter in action.FilterDescriptors.OrderBy(f => f, FilterDescriptorOrderComparer.Comparer).Select(f => f.Filter))
{
metadata.Add(filter);
}
}
if (action.ActionConstraints != null && action.ActionConstraints.Count > 0)
@ -549,9 +610,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
metadata.Add(new SuppressMatchingMetadata());
}
var metadataCollection = new EndpointMetadataCollection(metadata);
return metadataCollection;
}
// Ensure required values are a subset of defaults

View File

@ -0,0 +1,47 @@
// 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.Linq;
using System.Text;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Builder
{
public static class RazorPagesEndpointRouteBuilderExtensions
{
public static IEndpointConventionBuilder MapRazorPages(
this IEndpointRouteBuilder routeBuilder,
string basePath = null)
{
var mvcEndpointDataSource = routeBuilder.DataSources.OfType<MvcEndpointDataSource>().FirstOrDefault();
if (mvcEndpointDataSource == null)
{
mvcEndpointDataSource = routeBuilder.ServiceProvider.GetRequiredService<MvcEndpointDataSource>();
routeBuilder.DataSources.Add(mvcEndpointDataSource);
}
var conventionBuilder = new DefaultEndpointConventionBuilder();
mvcEndpointDataSource.AttributeRoutingConventionResolvers.Add(actionDescriptor =>
{
if (actionDescriptor is PageActionDescriptor pageActionDescriptor)
{
// TODO: Filter pages by path
return conventionBuilder;
}
return null;
});
return conventionBuilder;
}
}
}

View File

@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Builder.Internal;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
@ -56,12 +57,10 @@ namespace Microsoft.AspNetCore.Mvc.Core.Builder
template: "{controller=Home}/{action=Index}/{id?}");
});
var mvcEndpointDataSource = appBuilder.ApplicationServices
.GetRequiredService<IEnumerable<EndpointDataSource>>()
.OfType<MvcEndpointDataSource>()
.First();
var routeOptions = appBuilder.ApplicationServices
.GetRequiredService<IOptions<RouteOptions>>();
Assert.Empty(mvcEndpointDataSource.ConventionalEndpointInfos);
Assert.Empty(routeOptions.Value.EndpointDataSources);
}
[Fact]
@ -83,10 +82,10 @@ namespace Microsoft.AspNetCore.Mvc.Core.Builder
template: "{controller=Home}/{action=Index}/{id?}");
});
var mvcEndpointDataSource = appBuilder.ApplicationServices
.GetRequiredService<IEnumerable<EndpointDataSource>>()
.OfType<MvcEndpointDataSource>()
.First();
var routeOptions = appBuilder.ApplicationServices
.GetRequiredService<IOptions<RouteOptions>>();
var mvcEndpointDataSource = (MvcEndpointDataSource)Assert.Single(routeOptions.Value.EndpointDataSources, ds => ds is MvcEndpointDataSource);
var endpointInfo = Assert.Single(mvcEndpointDataSource.ConventionalEndpointInfos);
Assert.Equal("default", endpointInfo.Name);

View File

@ -315,13 +315,6 @@ namespace Microsoft.AspNetCore.Mvc
typeof(ApiBehaviorApplicationModelProvider),
}
},
{
typeof(EndpointDataSource),
new Type[]
{
typeof(MvcEndpointDataSource),
}
},
{
typeof(IStartupFilter),
new Type[]

View File

@ -804,6 +804,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
mvcEndpointInvokerFactory ?? new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty<IActionInvokerProvider>())),
serviceProvider.GetRequiredService<ParameterPolicyFactory>());
var defaultEndpointConventionBuilder = new DefaultEndpointConventionBuilder();
dataSource.AttributeRoutingConventionResolvers.Add((actionDescriptor) =>
{
return defaultEndpointConventionBuilder;
});
return dataSource;
}

View File

@ -353,6 +353,7 @@ namespace Microsoft.AspNetCore.Mvc
new Type[]
{
typeof(MvcCoreRouteOptionsSetup),
typeof(MvcCoreRouteOptionsSetup),
}
},
{