Add support for conventional routes with dispatching (#7928)
This commit is contained in:
parent
58aa16ee69
commit
3547341762
|
|
@ -2,6 +2,8 @@
|
|||
// 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 Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
|
@ -75,15 +77,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
throw new ArgumentNullException(nameof(configureRoutes));
|
||||
}
|
||||
|
||||
// Verify if AddMvc was done before calling UseMvc
|
||||
// We use the MvcMarkerService to make sure if all the services were added.
|
||||
if (app.ApplicationServices.GetService(typeof(MvcMarkerService)) == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatUnableToFindServices(
|
||||
nameof(IServiceCollection),
|
||||
"AddMvc",
|
||||
"ConfigureServices(...)"));
|
||||
}
|
||||
VerifyMvcIsRegistered(app);
|
||||
|
||||
var middlewarePipelineBuilder = app.ApplicationServices.GetRequiredService<MiddlewareFilterBuilder>();
|
||||
middlewarePipelineBuilder.ApplicationBuilder = app.New();
|
||||
|
|
@ -99,5 +93,51 @@ namespace Microsoft.AspNetCore.Builder
|
|||
|
||||
return app.UseRouter(routes.Build());
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseMvcWithEndpoint(
|
||||
this IApplicationBuilder app,
|
||||
Action<MvcEndpointInfoBuilder> configureRoutes)
|
||||
{
|
||||
if (app == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(app));
|
||||
}
|
||||
|
||||
if (configureRoutes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configureRoutes));
|
||||
}
|
||||
|
||||
VerifyMvcIsRegistered(app);
|
||||
|
||||
var mvcEndpointDataSource = app.ApplicationServices
|
||||
.GetRequiredService<IEnumerable<EndpointDataSource>>()
|
||||
.OfType<MvcEndpointDataSource>()
|
||||
.First();
|
||||
|
||||
var constraintResolver = app.ApplicationServices.GetRequiredService<IInlineConstraintResolver>();
|
||||
|
||||
MvcEndpointInfoBuilder routeBuilder = new MvcEndpointInfoBuilder(constraintResolver);
|
||||
|
||||
configureRoutes(routeBuilder);
|
||||
|
||||
mvcEndpointDataSource.ConventionalEndpointInfos.AddRange(routeBuilder.EndpointInfos);
|
||||
mvcEndpointDataSource.InitializeEndpoints();
|
||||
|
||||
return app.UseEndpoint();
|
||||
}
|
||||
|
||||
private static void VerifyMvcIsRegistered(IApplicationBuilder app)
|
||||
{
|
||||
// Verify if AddMvc was done before calling UseMvc
|
||||
// We use the MvcMarkerService to make sure if all the services were added.
|
||||
if (app.ApplicationServices.GetService(typeof(MvcMarkerService)) == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatUnableToFindServices(
|
||||
nameof(IServiceCollection),
|
||||
"AddMvc",
|
||||
"ConfigureServices(...)"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
// 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.Globalization;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
public class MvcEndpointInfo
|
||||
{
|
||||
public MvcEndpointInfo(
|
||||
string name,
|
||||
string template,
|
||||
RouteValueDictionary defaults,
|
||||
IDictionary<string, object> constraints,
|
||||
RouteValueDictionary dataTokens,
|
||||
IInlineConstraintResolver constraintResolver)
|
||||
{
|
||||
Name = name;
|
||||
Template = template ?? string.Empty;
|
||||
DataTokens = dataTokens;
|
||||
|
||||
try
|
||||
{
|
||||
// Data we parse from the template will be used to fill in the rest of the constraints or
|
||||
// defaults. The parser will throw for invalid routes.
|
||||
ParsedTemplate = TemplateParser.Parse(template);
|
||||
|
||||
Constraints = GetConstraints(constraintResolver, ParsedTemplate, constraints);
|
||||
Defaults = GetDefaults(ParsedTemplate, defaults);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new RouteCreationException(
|
||||
string.Format(CultureInfo.CurrentCulture, "An error occurred while creating the route with name '{0}' and template '{1}'.", name, template), exception);
|
||||
}
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public string Template { get; }
|
||||
public RouteValueDictionary Defaults { get; }
|
||||
public IDictionary<string, IRouteConstraint> Constraints { get; }
|
||||
public RouteValueDictionary DataTokens { get; }
|
||||
internal RouteTemplate ParsedTemplate { get; private set; }
|
||||
|
||||
private static IDictionary<string, IRouteConstraint> GetConstraints(
|
||||
IInlineConstraintResolver inlineConstraintResolver,
|
||||
RouteTemplate parsedTemplate,
|
||||
IDictionary<string, object> constraints)
|
||||
{
|
||||
var constraintBuilder = new RouteConstraintBuilder(inlineConstraintResolver, parsedTemplate.TemplateText);
|
||||
|
||||
if (constraints != null)
|
||||
{
|
||||
foreach (var kvp in constraints)
|
||||
{
|
||||
constraintBuilder.AddConstraint(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var parameter in parsedTemplate.Parameters)
|
||||
{
|
||||
if (parameter.IsOptional)
|
||||
{
|
||||
constraintBuilder.SetOptional(parameter.Name);
|
||||
}
|
||||
|
||||
foreach (var inlineConstraint in parameter.InlineConstraints)
|
||||
{
|
||||
constraintBuilder.AddResolvedConstraint(parameter.Name, inlineConstraint.Constraint);
|
||||
}
|
||||
}
|
||||
|
||||
return constraintBuilder.Build();
|
||||
}
|
||||
|
||||
private static RouteValueDictionary GetDefaults(
|
||||
RouteTemplate parsedTemplate,
|
||||
RouteValueDictionary defaults)
|
||||
{
|
||||
var result = defaults == null ? new RouteValueDictionary() : new RouteValueDictionary(defaults);
|
||||
|
||||
foreach (var parameter in parsedTemplate.Parameters)
|
||||
{
|
||||
if (parameter.DefaultValue != null)
|
||||
{
|
||||
if (result.ContainsKey(parameter.Name))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
string.Format(CultureInfo.CurrentCulture, "The route parameter '{0}' has both an inline default value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.", parameter.Name));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(parameter.Name, parameter.DefaultValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// 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.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
public class MvcEndpointInfoBuilder
|
||||
{
|
||||
public MvcEndpointInfoBuilder(IInlineConstraintResolver constraintResolver)
|
||||
{
|
||||
ConstraintResolver = constraintResolver;
|
||||
}
|
||||
|
||||
public List<MvcEndpointInfo> EndpointInfos { get; } = new List<MvcEndpointInfo>();
|
||||
public IInlineConstraintResolver ConstraintResolver { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
// 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.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Constraints;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="MvcEndpointInfoBuilder" /> to add endpoints.
|
||||
/// </summary>
|
||||
public static class MvcEndpointInfoBuilderExtensions
|
||||
{
|
||||
#region MapEndpoint
|
||||
/// <summary>
|
||||
/// Adds a endpoint to the <see cref="MvcEndpointInfoBuilder" /> with the specified name and template.
|
||||
/// </summary>
|
||||
/// <param name="endpointBuilder">The <see cref="MvcEndpointInfoBuilder" /> to add the endpoint to.</param>
|
||||
/// <param name="name">The name of the endpoint.</param>
|
||||
/// <param name="template">The URL pattern of the endpoint.</param>
|
||||
/// <returns>A reference to this instance after the operation has completed.</returns>
|
||||
public static MvcEndpointInfoBuilder MapEndpoint(this MvcEndpointInfoBuilder endpointBuilder, string name, string template)
|
||||
{
|
||||
endpointBuilder.MapEndpoint(name, template, null);
|
||||
return endpointBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a endpoint to the <see cref="MvcEndpointInfoBuilder" /> with the specified name, template, and default values.
|
||||
/// </summary>
|
||||
/// <param name="endpointBuilder">The <see cref="MvcEndpointInfoBuilder" /> to add the endpoint to.</param>
|
||||
/// <param name="name">The name of the endpoint.</param>
|
||||
/// <param name="template">The URL pattern of the endpoint.</param>
|
||||
/// <param name="defaults">
|
||||
/// An object that contains default values for endpoint parameters. The object's properties represent the names
|
||||
/// and values of the default values.
|
||||
/// </param>
|
||||
/// <returns>A reference to this instance after the operation has completed.</returns>
|
||||
public static MvcEndpointInfoBuilder MapEndpoint(this MvcEndpointInfoBuilder endpointBuilder, string name, string template, object defaults)
|
||||
{
|
||||
return endpointBuilder.MapEndpoint(name, template, defaults, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a endpoint to the <see cref="MvcEndpointInfoBuilder" /> with the specified name, template, default values, and
|
||||
/// constraints.
|
||||
/// </summary>
|
||||
/// <param name="endpointBuilder">The <see cref="MvcEndpointInfoBuilder" /> to add the endpoint to.</param>
|
||||
/// <param name="name">The name of the endpoint.</param>
|
||||
/// <param name="template">The URL pattern of the endpoint.</param>
|
||||
/// <param name="defaults">
|
||||
/// An object that contains default values for endpoint parameters. The object's properties represent the names
|
||||
/// and values of the default values.
|
||||
/// </param>
|
||||
/// <param name="constraints">
|
||||
/// An object that contains constraints for the endpoint. The object's properties represent the names and values
|
||||
/// of the constraints.
|
||||
/// </param>
|
||||
/// <returns>A reference to this instance after the operation has completed.</returns>
|
||||
public static MvcEndpointInfoBuilder MapEndpoint(this MvcEndpointInfoBuilder endpointBuilder, string name, string template, object defaults, object constraints)
|
||||
{
|
||||
return endpointBuilder.MapEndpoint(name, template, defaults, constraints, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a endpoint to the <see cref="MvcEndpointInfoBuilder" /> with the specified name, template, default values, and
|
||||
/// data tokens.
|
||||
/// </summary>
|
||||
/// <param name="endpointBuilder">The <see cref="MvcEndpointInfoBuilder" /> to add the endpoint to.</param>
|
||||
/// <param name="name">The name of the endpoint.</param>
|
||||
/// <param name="template">The URL pattern of the endpoint.</param>
|
||||
/// <param name="defaults">
|
||||
/// An object that contains default values for endpoint parameters. The object's properties represent the names
|
||||
/// and values of the default values.
|
||||
/// </param>
|
||||
/// <param name="constraints">
|
||||
/// An object that contains constraints for the endpoint. The object's properties represent the names and values
|
||||
/// of the constraints.
|
||||
/// </param>
|
||||
/// <param name="dataTokens">
|
||||
/// An object that contains data tokens for the endpoint. The object's properties represent the names and values
|
||||
/// of the data tokens.
|
||||
/// </param>
|
||||
/// <returns>A reference to this instance after the operation has completed.</returns>
|
||||
public static MvcEndpointInfoBuilder MapEndpoint(this MvcEndpointInfoBuilder endpointBuilder, string name, string template, object defaults, object constraints, object dataTokens)
|
||||
{
|
||||
endpointBuilder.EndpointInfos.Add(new MvcEndpointInfo(
|
||||
name,
|
||||
template,
|
||||
new RouteValueDictionary(defaults),
|
||||
new RouteValueDictionary(constraints),
|
||||
new RouteValueDictionary(dataTokens),
|
||||
endpointBuilder.ConstraintResolver));
|
||||
|
||||
return endpointBuilder;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region MapAreaEndpoint
|
||||
/// <summary>
|
||||
/// Adds a endpoint to the <see cref="MvcEndpointInfoBuilder"/> with the given MVC area with the specified
|
||||
/// <paramref name="name"/>, <paramref name="areaName"/> and <paramref name="template"/>.
|
||||
/// </summary>
|
||||
/// <param name="endpointBuilder">The <see cref="MvcEndpointInfoBuilder"/> to add the endpoint to.</param>
|
||||
/// <param name="name">The name of the endpoint.</param>
|
||||
/// <param name="areaName">The MVC area name.</param>
|
||||
/// <param name="template">The URL pattern of the endpoint.</param>
|
||||
/// <returns>A reference to this instance after the operation has completed.</returns>
|
||||
public static MvcEndpointInfoBuilder MapAreaEndpoint(
|
||||
this MvcEndpointInfoBuilder endpointBuilder,
|
||||
string name,
|
||||
string areaName,
|
||||
string template)
|
||||
{
|
||||
MapAreaEndpoint(endpointBuilder, name, areaName, template, defaults: null, constraints: null, dataTokens: null);
|
||||
return endpointBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a endpoint to the <see cref="MvcEndpointInfoBuilder"/> with the given MVC area with the specified
|
||||
/// <paramref name="name"/>, <paramref name="areaName"/>, <paramref name="template"/>, and
|
||||
/// <paramref name="defaults"/>.
|
||||
/// </summary>
|
||||
/// <param name="endpointBuilder">The <see cref="MvcEndpointInfoBuilder"/> to add the endpoint to.</param>
|
||||
/// <param name="name">The name of the endpoint.</param>
|
||||
/// <param name="areaName">The MVC area name.</param>
|
||||
/// <param name="template">The URL pattern of the endpoint.</param>
|
||||
/// <param name="defaults">
|
||||
/// An object that contains default values for endpoint parameters. The object's properties represent the
|
||||
/// names and values of the default values.
|
||||
/// </param>
|
||||
/// <returns>A reference to this instance after the operation has completed.</returns>
|
||||
public static MvcEndpointInfoBuilder MapAreaEndpoint(
|
||||
this MvcEndpointInfoBuilder endpointBuilder,
|
||||
string name,
|
||||
string areaName,
|
||||
string template,
|
||||
object defaults)
|
||||
{
|
||||
MapAreaEndpoint(endpointBuilder, name, areaName, template, defaults, constraints: null, dataTokens: null);
|
||||
return endpointBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a endpoint to the <see cref="MvcEndpointInfoBuilder"/> with the given MVC area with the specified
|
||||
/// <paramref name="name"/>, <paramref name="areaName"/>, <paramref name="template"/>,
|
||||
/// <paramref name="defaults"/>, and <paramref name="constraints"/>.
|
||||
/// </summary>
|
||||
/// <param name="endpointBuilder">The <see cref="MvcEndpointInfoBuilder"/> to add the endpoint to.</param>
|
||||
/// <param name="name">The name of the endpoint.</param>
|
||||
/// <param name="areaName">The MVC area name.</param>
|
||||
/// <param name="template">The URL pattern of the endpoint.</param>
|
||||
/// <param name="defaults">
|
||||
/// An object that contains default values for endpoint parameters. The object's properties represent the
|
||||
/// names and values of the default values.
|
||||
/// </param>
|
||||
/// <param name="constraints">
|
||||
/// An object that contains constraints for the endpoint. The object's properties represent the names and
|
||||
/// values of the constraints.
|
||||
/// </param>
|
||||
/// <returns>A reference to this instance after the operation has completed.</returns>
|
||||
public static MvcEndpointInfoBuilder MapAreaEndpoint(
|
||||
this MvcEndpointInfoBuilder endpointBuilder,
|
||||
string name,
|
||||
string areaName,
|
||||
string template,
|
||||
object defaults,
|
||||
object constraints)
|
||||
{
|
||||
MapAreaEndpoint(endpointBuilder, name, areaName, template, defaults, constraints, dataTokens: null);
|
||||
return endpointBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a endpoint to the <see cref="MvcEndpointInfoBuilder"/> with the given MVC area with the specified
|
||||
/// <paramref name="name"/>, <paramref name="areaName"/>, <paramref name="template"/>,
|
||||
/// <paramref name="defaults"/>, <paramref name="constraints"/>, and <paramref name="dataTokens"/>.
|
||||
/// </summary>
|
||||
/// <param name="endpointBuilder">The <see cref="MvcEndpointInfoBuilder"/> to add the endpoint to.</param>
|
||||
/// <param name="name">The name of the endpoint.</param>
|
||||
/// <param name="areaName">The MVC area name.</param>
|
||||
/// <param name="template">The URL pattern of the endpoint.</param>
|
||||
/// <param name="defaults">
|
||||
/// An object that contains default values for endpoint parameters. The object's properties represent the
|
||||
/// names and values of the default values.
|
||||
/// </param>
|
||||
/// <param name="constraints">
|
||||
/// An object that contains constraints for the endpoint. The object's properties represent the names and
|
||||
/// values of the constraints.
|
||||
/// </param>
|
||||
/// <param name="dataTokens">
|
||||
/// An object that contains data tokens for the endpoint. The object's properties represent the names and
|
||||
/// values of the data tokens.
|
||||
/// </param>
|
||||
/// <returns>A reference to this instance after the operation has completed.</returns>
|
||||
public static MvcEndpointInfoBuilder MapAreaEndpoint(
|
||||
this MvcEndpointInfoBuilder endpointBuilder,
|
||||
string name,
|
||||
string areaName,
|
||||
string template,
|
||||
object defaults,
|
||||
object constraints,
|
||||
object dataTokens)
|
||||
{
|
||||
if (endpointBuilder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpointBuilder));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(areaName))
|
||||
{
|
||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(areaName));
|
||||
}
|
||||
|
||||
var defaultsDictionary = new RouteValueDictionary(defaults);
|
||||
defaultsDictionary["area"] = defaultsDictionary["area"] ?? areaName;
|
||||
|
||||
var constraintsDictionary = new RouteValueDictionary(constraints);
|
||||
constraintsDictionary["area"] = constraintsDictionary["area"] ?? new StringRouteConstraint(areaName);
|
||||
|
||||
endpointBuilder.MapEndpoint(name, template, defaultsDictionary, constraintsDictionary, dataTokens);
|
||||
return endpointBuilder;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -4,11 +4,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.EndpointConstraints;
|
||||
using Microsoft.AspNetCore.Routing.Matchers;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||
|
|
@ -17,6 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
private readonly IActionDescriptorCollectionProvider _actions;
|
||||
private readonly MvcEndpointInvokerFactory _invokerFactory;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IActionDescriptorChangeProvider[] _actionDescriptorChangeProviders;
|
||||
private readonly List<Endpoint> _endpoints;
|
||||
|
||||
|
|
@ -25,7 +30,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
public MvcEndpointDataSource(
|
||||
IActionDescriptorCollectionProvider actions,
|
||||
MvcEndpointInvokerFactory invokerFactory,
|
||||
IEnumerable<IActionDescriptorChangeProvider> actionDescriptorChangeProviders)
|
||||
IEnumerable<IActionDescriptorChangeProvider> actionDescriptorChangeProviders,
|
||||
IServiceProvider serviceProvider)
|
||||
{
|
||||
if (actions == null)
|
||||
{
|
||||
|
|
@ -42,70 +48,247 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
throw new ArgumentNullException(nameof(actionDescriptorChangeProviders));
|
||||
}
|
||||
|
||||
if (serviceProvider == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(serviceProvider));
|
||||
}
|
||||
|
||||
_actions = actions;
|
||||
_invokerFactory = invokerFactory;
|
||||
_serviceProvider = serviceProvider;
|
||||
_actionDescriptorChangeProviders = actionDescriptorChangeProviders.ToArray();
|
||||
|
||||
_endpoints = new List<Endpoint>();
|
||||
|
||||
InitializeEndpoints();
|
||||
ConventionalEndpointInfos = new List<MvcEndpointInfo>();
|
||||
}
|
||||
|
||||
private void InitializeEndpoints()
|
||||
public void InitializeEndpoints()
|
||||
{
|
||||
// note: this code has haxxx. This will only work in some constrained scenarios
|
||||
foreach (var action in _actions.ActionDescriptors.Items)
|
||||
{
|
||||
if (action.AttributeRouteInfo == null)
|
||||
{
|
||||
// Action does not have an attribute route
|
||||
continue;
|
||||
}
|
||||
|
||||
RequestDelegate invokerDelegate = (context) =>
|
||||
{
|
||||
var values = context.Features.Get<IEndpointFeature>().Values;
|
||||
var routeData = new RouteData();
|
||||
foreach (var kvp in values)
|
||||
// Check each of the conventional templates to see if the action would be reachable
|
||||
// If the action and template are compatible then create an endpoint with the
|
||||
// area/controller/action parameter parts replaced with literals
|
||||
//
|
||||
// e.g. {controller}/{action} with HomeController.Index and HomeController.Login
|
||||
// would result in endpoints:
|
||||
// - Home/Index
|
||||
// - Home/Login
|
||||
foreach (var endpointInfo in ConventionalEndpointInfos)
|
||||
{
|
||||
routeData.Values.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
var actionRouteValues = action.RouteValues;
|
||||
var endpointTemplateSegments = endpointInfo.ParsedTemplate.Segments;
|
||||
|
||||
var actionContext = new ActionContext(context, routeData, action);
|
||||
|
||||
var invoker = _invokerFactory.CreateInvoker(actionContext);
|
||||
return invoker.InvokeAsync();
|
||||
};
|
||||
|
||||
var metadata = new List<object>();
|
||||
|
||||
// Add filter descriptors to endpoint metadata
|
||||
metadata.AddRange(action.FilterDescriptors.OrderBy(f => f, FilterDescriptorOrderComparer.Comparer).Select(f => f.Filter));
|
||||
|
||||
if (action.ActionConstraints != null && action.ActionConstraints.Count > 0)
|
||||
{
|
||||
foreach (var actionConstraint in action.ActionConstraints)
|
||||
{
|
||||
if (actionConstraint is HttpMethodActionConstraint httpMethodActionConstraint)
|
||||
if (MatchRouteValue(action, endpointInfo, "Area")
|
||||
&& MatchRouteValue(action, endpointInfo, "Controller")
|
||||
&& MatchRouteValue(action, endpointInfo, "Action"))
|
||||
{
|
||||
metadata.Add(new HttpMethodEndpointConstraint(httpMethodActionConstraint.HttpMethods));
|
||||
var newEndpointTemplate = TemplateParser.Parse(endpointInfo.Template);
|
||||
|
||||
for (var i = 0; i < newEndpointTemplate.Segments.Count; i++)
|
||||
{
|
||||
// Check if the template can be shortened because the remaining parameters are optional
|
||||
//
|
||||
// e.g. Matching template {controller=Home}/{action=Index}/{id?} against HomeController.Index
|
||||
// can resolve to the following endpoints:
|
||||
// - /Home/Index/{id?}
|
||||
// - /Home
|
||||
// - /
|
||||
if (UseDefaultValuePlusRemainingSegementsOptional(i, action, endpointInfo, newEndpointTemplate))
|
||||
{
|
||||
var subTemplate = RouteTemplateWriter.ToString(newEndpointTemplate.Segments.Take(i));
|
||||
|
||||
var subEndpoint = CreateEndpoint(action, subTemplate, 0, endpointInfo);
|
||||
_endpoints.Add(subEndpoint);
|
||||
}
|
||||
|
||||
var segment = newEndpointTemplate.Segments[i];
|
||||
for (var j = 0; j < segment.Parts.Count; j++)
|
||||
{
|
||||
var part = segment.Parts[j];
|
||||
|
||||
if (part.IsParameter && IsMvcParameter(part.Name))
|
||||
{
|
||||
// Replace parameter with literal value
|
||||
segment.Parts[j] = TemplatePart.CreateLiteral(action.RouteValues[part.Name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var newTemplate = RouteTemplateWriter.ToString(newEndpointTemplate.Segments);
|
||||
|
||||
var endpoint = CreateEndpoint(action, newTemplate, 0, endpointInfo);
|
||||
_endpoints.Add(endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var metadataCollection = new EndpointMetadataCollection(metadata);
|
||||
|
||||
_endpoints.Add(new MatcherEndpoint(
|
||||
next => invokerDelegate,
|
||||
action.AttributeRouteInfo.Template,
|
||||
action.RouteValues,
|
||||
action.AttributeRouteInfo.Order,
|
||||
metadataCollection,
|
||||
action.DisplayName,
|
||||
new Address(action.AttributeRouteInfo.Name)));
|
||||
else
|
||||
{
|
||||
var endpoint = CreateEndpoint(action, action.AttributeRouteInfo.Template, action.AttributeRouteInfo.Order, action.AttributeRouteInfo);
|
||||
_endpoints.Add(endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsMvcParameter(string name)
|
||||
{
|
||||
if (string.Equals(name, "Area", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(name, "Controller", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(name, "Action", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool UseDefaultValuePlusRemainingSegementsOptional(int segmentIndex, ActionDescriptor action, MvcEndpointInfo endpointInfo, RouteTemplate template)
|
||||
{
|
||||
// Check whether the remaining segments are all optional and one or more of them is
|
||||
// for area/controller/action and has a default value
|
||||
var usedDefaultValue = false;
|
||||
|
||||
for (var i = segmentIndex; i < template.Segments.Count; i++)
|
||||
{
|
||||
var segment = template.Segments[i];
|
||||
for (var j = 0; j < segment.Parts.Count; j++)
|
||||
{
|
||||
var part = segment.Parts[j];
|
||||
if (part.IsOptional || part.IsOptionalSeperator || part.IsCatchAll)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (part.IsParameter)
|
||||
{
|
||||
if (IsMvcParameter(part.Name))
|
||||
{
|
||||
if (endpointInfo.Defaults[part.Name] is string defaultValue
|
||||
&& action.RouteValues.TryGetValue(part.Name, out var routeValue)
|
||||
&& string.Equals(defaultValue, routeValue, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
usedDefaultValue = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop because there is a non-optional/non-defaulted trailing value
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return usedDefaultValue;
|
||||
}
|
||||
|
||||
private bool MatchRouteValue(ActionDescriptor action, MvcEndpointInfo endpointInfo, string routeKey)
|
||||
{
|
||||
if (!action.RouteValues.TryGetValue(routeKey, out var actionValue) || string.IsNullOrWhiteSpace(actionValue))
|
||||
{
|
||||
// Action does not have a value for this routeKey, most likely because action is not in an area
|
||||
// Check that the template does not have a parameter for the routeKey
|
||||
var matchingParameter = endpointInfo.ParsedTemplate.Parameters.SingleOrDefault(p => string.Equals(p.Name, routeKey, StringComparison.OrdinalIgnoreCase));
|
||||
if (matchingParameter == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (endpointInfo.Defaults != null && string.Equals(actionValue, endpointInfo.Defaults[routeKey] as string, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var matchingParameter = endpointInfo.ParsedTemplate.Parameters.SingleOrDefault(p => string.Equals(p.Name, routeKey, StringComparison.OrdinalIgnoreCase));
|
||||
if (matchingParameter != null)
|
||||
{
|
||||
// Check that the value matches against constraints on that parameter
|
||||
// e.g. For {controller:regex((Home|Login))} the controller value must match the regex
|
||||
//
|
||||
// REVIEW: This is really ugly
|
||||
if (endpointInfo.Constraints.TryGetValue(routeKey, out var constraint)
|
||||
&& !constraint.Match(new DefaultHttpContext() { RequestServices = _serviceProvider }, new DummyRouter(), routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest))
|
||||
{
|
||||
// Did not match constraint
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private class DummyRouter : IRouter
|
||||
{
|
||||
public VirtualPathData GetVirtualPath(VirtualPathContext context)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task RouteAsync(RouteContext context)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private MatcherEndpoint CreateEndpoint(ActionDescriptor action, string template, int order, object source)
|
||||
{
|
||||
RequestDelegate invokerDelegate = (context) =>
|
||||
{
|
||||
var values = context.Features.Get<IEndpointFeature>().Values;
|
||||
var routeData = new RouteData();
|
||||
foreach (var kvp in values)
|
||||
{
|
||||
if (kvp.Value != null)
|
||||
{
|
||||
routeData.Values.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
var actionContext = new ActionContext(context, routeData, action);
|
||||
|
||||
var invoker = _invokerFactory.CreateInvoker(actionContext);
|
||||
return invoker.InvokeAsync();
|
||||
};
|
||||
|
||||
var metadata = new List<object>();
|
||||
// REVIEW: Used for debugging. Consider removing before release
|
||||
metadata.Add(source);
|
||||
metadata.Add(action);
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
if (action.ActionConstraints != null && action.ActionConstraints.Count > 0)
|
||||
{
|
||||
foreach (var actionConstraint in action.ActionConstraints)
|
||||
{
|
||||
if (actionConstraint is HttpMethodActionConstraint httpMethodActionConstraint)
|
||||
{
|
||||
metadata.Add(new HttpMethodEndpointConstraint(httpMethodActionConstraint.HttpMethods));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var metadataCollection = new EndpointMetadataCollection(metadata);
|
||||
var endpoint = new MatcherEndpoint(
|
||||
next => invokerDelegate,
|
||||
template,
|
||||
action.RouteValues,
|
||||
order,
|
||||
metadataCollection,
|
||||
action.DisplayName,
|
||||
address: null);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
private IChangeToken GetCompositeChangeToken()
|
||||
{
|
||||
if (_actionDescriptorChangeProviders.Length == 1)
|
||||
|
|
@ -136,5 +319,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
|
||||
public override IReadOnlyList<Endpoint> Endpoints => _endpoints;
|
||||
|
||||
public List<MvcEndpointInfo> ConventionalEndpointInfos { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||
{
|
||||
internal static class RouteTemplateWriter
|
||||
{
|
||||
public static string ToString(IEnumerable<TemplateSegment> routeSegments)
|
||||
{
|
||||
return string.Join("/", routeSegments.Select(s => ToString(s)));
|
||||
}
|
||||
|
||||
private static string ToString(TemplateSegment templateSegment)
|
||||
{
|
||||
return string.Join(string.Empty, templateSegment.Parts.Select(p => ToString(p)));
|
||||
}
|
||||
|
||||
private static string ToString(TemplatePart templatePart)
|
||||
{
|
||||
if (templatePart.IsParameter)
|
||||
{
|
||||
var partText = "{";
|
||||
if (templatePart.IsCatchAll)
|
||||
{
|
||||
partText += "*";
|
||||
}
|
||||
partText += templatePart.Name;
|
||||
foreach (var item in templatePart.InlineConstraints)
|
||||
{
|
||||
partText += ":";
|
||||
partText += item.Constraint;
|
||||
}
|
||||
if (templatePart.DefaultValue != null)
|
||||
{
|
||||
partText += "=";
|
||||
partText += templatePart.DefaultValue;
|
||||
}
|
||||
if (templatePart.IsOptional)
|
||||
{
|
||||
partText += "?";
|
||||
}
|
||||
partText += "}";
|
||||
|
||||
return partText;
|
||||
}
|
||||
else
|
||||
{
|
||||
return templatePart.Text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -29,5 +29,25 @@ namespace Microsoft.AspNetCore.Mvc.Core.Builder
|
|||
"in the application startup code.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseMvcWithEndpoint_ThrowsInvalidOperationException_IfMvcMarkerServiceIsNotRegistered()
|
||||
{
|
||||
// Arrange
|
||||
var applicationBuilderMock = new Mock<IApplicationBuilder>();
|
||||
applicationBuilderMock
|
||||
.Setup(s => s.ApplicationServices)
|
||||
.Returns(Mock.Of<IServiceProvider>());
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<InvalidOperationException>(
|
||||
() => applicationBuilderMock.Object.UseMvcWithEndpoint(rb => { }));
|
||||
|
||||
Assert.Equal(
|
||||
"Unable to find the required services. Please add all the required services by calling " +
|
||||
"'IServiceCollection.AddMvc' inside the call to 'ConfigureServices(...)' " +
|
||||
"in the application startup code.",
|
||||
exception.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,282 @@
|
|||
// 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.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Constraints;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Core.Test.Builder
|
||||
{
|
||||
public class MvcEndpointInfoBuilderExtensionsTest
|
||||
{
|
||||
#region MapAreaEndpoint
|
||||
[Fact]
|
||||
public void MapAreaEndpoint_Simple()
|
||||
{
|
||||
// Arrange
|
||||
var builder = CreateEndpointBuilder();
|
||||
|
||||
// Act
|
||||
builder.MapAreaEndpoint(name: null, areaName: "admin", template: "site/Admin/");
|
||||
|
||||
// Assert
|
||||
var endpointInfo = Assert.Single(builder.EndpointInfos);
|
||||
|
||||
Assert.Null(endpointInfo.Name);
|
||||
Assert.Equal("site/Admin/", endpointInfo.Template);
|
||||
Assert.Collection(
|
||||
endpointInfo.Constraints.OrderBy(kvp => kvp.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.IsType<StringRouteConstraint>(kvp.Value);
|
||||
});
|
||||
Assert.Empty(endpointInfo.DataTokens);
|
||||
Assert.Collection(
|
||||
endpointInfo.Defaults.OrderBy(kvp => kvp.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.Equal("admin", kvp.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapAreaEndpoint_Defaults()
|
||||
{
|
||||
// Arrange
|
||||
var builder = CreateEndpointBuilder();
|
||||
|
||||
// Act
|
||||
builder.MapAreaEndpoint(
|
||||
name: "admin_area",
|
||||
areaName: "admin",
|
||||
template: "site/Admin/",
|
||||
defaults: new { action = "Home" });
|
||||
|
||||
// Assert
|
||||
var endpointInfo = Assert.Single(builder.EndpointInfos);
|
||||
|
||||
Assert.Equal("admin_area", endpointInfo.Name);
|
||||
Assert.Equal("site/Admin/", endpointInfo.Template);
|
||||
Assert.Collection(
|
||||
endpointInfo.Constraints.OrderBy(kvp => kvp.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.IsType<StringRouteConstraint>(kvp.Value);
|
||||
});
|
||||
Assert.Empty(endpointInfo.DataTokens);
|
||||
Assert.Collection(
|
||||
endpointInfo.Defaults.OrderBy(kvp => kvp.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("action", kvp.Key);
|
||||
Assert.Equal("Home", kvp.Value);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.Equal("admin", kvp.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapAreaEndpoint_DefaultsAndConstraints()
|
||||
{
|
||||
// Arrange
|
||||
var builder = CreateEndpointBuilder();
|
||||
|
||||
// Act
|
||||
builder.MapAreaEndpoint(
|
||||
name: "admin_area",
|
||||
areaName: "admin",
|
||||
template: "site/Admin/",
|
||||
defaults: new { action = "Home" },
|
||||
constraints: new { id = new IntRouteConstraint() });
|
||||
|
||||
// Assert
|
||||
var endpointInfo = Assert.Single(builder.EndpointInfos);
|
||||
|
||||
Assert.Equal("admin_area", endpointInfo.Name);
|
||||
Assert.Equal("site/Admin/", endpointInfo.Template);
|
||||
Assert.Collection(
|
||||
endpointInfo.Constraints.OrderBy(kvp => kvp.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.IsType<StringRouteConstraint>(kvp.Value);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("id", kvp.Key);
|
||||
Assert.IsType<IntRouteConstraint>(kvp.Value);
|
||||
});
|
||||
Assert.Empty(endpointInfo.DataTokens);
|
||||
Assert.Collection(
|
||||
endpointInfo.Defaults.OrderBy(kvp => kvp.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("action", kvp.Key);
|
||||
Assert.Equal("Home", kvp.Value);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.Equal("admin", kvp.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapAreaEndpoint_DefaultsConstraintsAndDataTokens()
|
||||
{
|
||||
// Arrange
|
||||
var builder = CreateEndpointBuilder();
|
||||
|
||||
// Act
|
||||
builder.MapAreaEndpoint(
|
||||
name: "admin_area",
|
||||
areaName: "admin",
|
||||
template: "site/Admin/",
|
||||
defaults: new { action = "Home" },
|
||||
constraints: new { id = new IntRouteConstraint() },
|
||||
dataTokens: new { some_token = "hello" });
|
||||
|
||||
// Assert
|
||||
var endpointInfo = Assert.Single(builder.EndpointInfos);
|
||||
|
||||
Assert.Equal("admin_area", endpointInfo.Name);
|
||||
Assert.Equal("site/Admin/", endpointInfo.Template);
|
||||
Assert.Collection(
|
||||
endpointInfo.Constraints.OrderBy(kvp => kvp.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.IsType<StringRouteConstraint>(kvp.Value);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("id", kvp.Key);
|
||||
Assert.IsType<IntRouteConstraint>(kvp.Value);
|
||||
});
|
||||
Assert.Collection(
|
||||
endpointInfo.DataTokens.OrderBy(kvp => kvp.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("some_token", kvp.Key);
|
||||
Assert.Equal("hello", kvp.Value);
|
||||
});
|
||||
Assert.Collection(
|
||||
endpointInfo.Defaults.OrderBy(kvp => kvp.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("action", kvp.Key);
|
||||
Assert.Equal("Home", kvp.Value);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.Equal("admin", kvp.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapAreaEndpoint_DoesNotReplaceValuesForAreaIfAlreadyPresentInConstraintsOrDefaults()
|
||||
{
|
||||
// Arrange
|
||||
var builder = CreateEndpointBuilder();
|
||||
|
||||
// Act
|
||||
builder.MapAreaEndpoint(
|
||||
name: "admin_area",
|
||||
areaName: "admin",
|
||||
template: "site/Admin/",
|
||||
defaults: new { area = "Home" },
|
||||
constraints: new { area = new IntRouteConstraint() },
|
||||
dataTokens: new { some_token = "hello" });
|
||||
|
||||
// Assert
|
||||
var endpointInfo = Assert.Single(builder.EndpointInfos);
|
||||
|
||||
Assert.Equal("admin_area", endpointInfo.Name);
|
||||
Assert.Equal("site/Admin/", endpointInfo.Template);
|
||||
Assert.Collection(
|
||||
endpointInfo.Constraints.OrderBy(kvp => kvp.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.IsType<IntRouteConstraint>(kvp.Value);
|
||||
});
|
||||
Assert.Collection(
|
||||
endpointInfo.DataTokens.OrderBy(kvp => kvp.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("some_token", kvp.Key);
|
||||
Assert.Equal("hello", kvp.Value);
|
||||
});
|
||||
Assert.Collection(
|
||||
endpointInfo.Defaults.OrderBy(kvp => kvp.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.Equal("Home", kvp.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapAreaEndpoint_UsesPassedInAreaNameAsIs()
|
||||
{
|
||||
// Arrange
|
||||
var builder = CreateEndpointBuilder();
|
||||
var areaName = "user.admin";
|
||||
|
||||
// Act
|
||||
builder.MapAreaEndpoint(name: null, areaName: areaName, template: "site/Admin/");
|
||||
|
||||
// Assert
|
||||
var endpointInfo = Assert.Single(builder.EndpointInfos);
|
||||
|
||||
Assert.Null(endpointInfo.Name);
|
||||
Assert.Equal("site/Admin/", endpointInfo.Template);
|
||||
Assert.Collection(
|
||||
endpointInfo.Constraints.OrderBy(kvp => kvp.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.IsType<StringRouteConstraint>(kvp.Value);
|
||||
|
||||
var values = new RouteValueDictionary(new { area = areaName });
|
||||
var match = kvp.Value.Match(
|
||||
new DefaultHttpContext(),
|
||||
route: new Mock<IRouter>().Object,
|
||||
routeKey: kvp.Key,
|
||||
values: values,
|
||||
routeDirection: RouteDirection.UrlGeneration);
|
||||
|
||||
Assert.True(match);
|
||||
});
|
||||
Assert.Empty(endpointInfo.DataTokens);
|
||||
Assert.Collection(
|
||||
endpointInfo.Defaults.OrderBy(kvp => kvp.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("area", kvp.Key);
|
||||
Assert.Equal(kvp.Value, areaName);
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
|
||||
private MvcEndpointInfoBuilder CreateEndpointBuilder()
|
||||
{
|
||||
var builder = new MvcEndpointInfoBuilder(Mock.Of<IInlineConstraintResolver>());
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
|
|
@ -13,6 +15,7 @@ using Microsoft.AspNetCore.Mvc.Internal;
|
|||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Matchers;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
@ -54,17 +57,16 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
|||
}
|
||||
}, 0));
|
||||
|
||||
var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object);
|
||||
|
||||
// Act
|
||||
var dataSource = new MvcEndpointDataSource(
|
||||
mockDescriptorProvider.Object,
|
||||
new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty<IActionInvokerProvider>())),
|
||||
Array.Empty<IActionDescriptorChangeProvider>());
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
// Assert
|
||||
var endpoint = Assert.Single(dataSource.Endpoints);
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
||||
|
||||
object endpointValue = matcherEndpoint.Values["Name"];
|
||||
var endpointValue = matcherEndpoint.Values["Name"];
|
||||
Assert.Equal(routeValue, endpointValue);
|
||||
|
||||
Assert.Equal(displayName, matcherEndpoint.DisplayName);
|
||||
|
|
@ -85,8 +87,8 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
|||
var httpContextMock = new Mock<HttpContext>();
|
||||
httpContextMock.Setup(m => m.Features).Returns(featureCollection);
|
||||
|
||||
var mockDescriptorProviderMock = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProviderMock.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List<ActionDescriptor>
|
||||
var descriptorProviderMock = new Mock<IActionDescriptorCollectionProvider>();
|
||||
descriptorProviderMock.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List<ActionDescriptor>
|
||||
{
|
||||
new ActionDescriptor
|
||||
{
|
||||
|
|
@ -109,11 +111,12 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
|||
var actionInvokerProviderMock = new Mock<IActionInvokerFactory>();
|
||||
actionInvokerProviderMock.Setup(m => m.CreateInvoker(It.IsAny<ActionContext>())).Returns(actionInvokerMock.Object);
|
||||
|
||||
var dataSource = CreateMvcEndpointDataSource(
|
||||
descriptorProviderMock.Object,
|
||||
new MvcEndpointInvokerFactory(actionInvokerProviderMock.Object));
|
||||
|
||||
// Act
|
||||
var dataSource = new MvcEndpointDataSource(
|
||||
mockDescriptorProviderMock.Object,
|
||||
new MvcEndpointInvokerFactory(actionInvokerProviderMock.Object),
|
||||
Array.Empty<IActionDescriptorChangeProvider>());
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
// Assert
|
||||
var endpoint = Assert.Single(dataSource.Endpoints);
|
||||
|
|
@ -139,8 +142,8 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
|||
var httpContextMock = new Mock<HttpContext>();
|
||||
httpContextMock.Setup(m => m.Features).Returns(featureCollection);
|
||||
|
||||
var mockDescriptorProviderMock = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProviderMock.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List<ActionDescriptor>(), 0));
|
||||
var descriptorProviderMock = new Mock<IActionDescriptorCollectionProvider>();
|
||||
descriptorProviderMock.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List<ActionDescriptor>(), 0));
|
||||
|
||||
var actionInvokerMock = new Mock<IActionInvoker>();
|
||||
|
||||
|
|
@ -154,8 +157,8 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
|||
var changeProvider2Mock = new Mock<IActionDescriptorChangeProvider>();
|
||||
changeProvider2Mock.Setup(m => m.GetChangeToken()).Returns(changeTokenMock.Object);
|
||||
|
||||
var dataSource = new MvcEndpointDataSource(
|
||||
mockDescriptorProviderMock.Object,
|
||||
var dataSource = CreateMvcEndpointDataSource(
|
||||
descriptorProviderMock.Object,
|
||||
new MvcEndpointInvokerFactory(actionInvokerProviderMock.Object),
|
||||
new[] { changeProvider1Mock.Object, changeProvider2Mock.Object });
|
||||
|
||||
|
|
@ -166,5 +169,211 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
|||
var compositeChangeToken = Assert.IsType<CompositeChangeToken>(changeToken);
|
||||
Assert.Equal(2, compositeChangeToken.ChangeTokens.Count);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{controller}/{action}/{id?}", new[] { "TestController/TestAction/{id?}" })]
|
||||
[InlineData("{controller}/{id?}", new string[] { })]
|
||||
[InlineData("{action}/{id?}", new string[] { })]
|
||||
[InlineData("{Controller}/{Action}/{id?}", new[] { "TestController/TestAction/{id?}" })]
|
||||
[InlineData("{CONTROLLER}/{ACTION}/{id?}", new[] { "TestController/TestAction/{id?}" })]
|
||||
[InlineData("{controller}/{action=TestAction}", new[] { "TestController", "TestController/TestAction" })]
|
||||
[InlineData("{controller}/{action=TestAction}/{id?}", new[] { "TestController", "TestController/TestAction/{id?}" })]
|
||||
[InlineData("{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestController", "TestController/TestAction/{id?}" })]
|
||||
[InlineData("{controller}/{action}/{*catchAll}", new[] { "TestController/TestAction/{*catchAll}" })]
|
||||
[InlineData("{controller}/{action=TestAction}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{*catchAll}" })]
|
||||
[InlineData("{controller}/{action=TestAction}/{id?}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{*catchAll}" })]
|
||||
//[InlineData("{controller}/{action}.{ext?}", new[] { "TestController/TestAction.{ext?}" })]
|
||||
//[InlineData("{controller}/{action=TestAction}.{ext?}", new[] { "TestController", "TestController/TestAction.{ext?}" })]
|
||||
public void InitializeEndpoints_SingleAction(string endpointInfoRoute, string[] finalEndpointTemplates)
|
||||
{
|
||||
// Arrange
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List<ActionDescriptor>
|
||||
{
|
||||
CreateActionDescriptor("TestController", "TestAction")
|
||||
}, 0));
|
||||
|
||||
var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute));
|
||||
|
||||
// Act
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
// Assert
|
||||
var inspectors = finalEndpointTemplates
|
||||
.Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<MatcherEndpoint>(e).Template)))
|
||||
.ToArray();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(dataSource.Endpoints, inspectors);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{area}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })]
|
||||
[InlineData("{controller}/{action}/{id?}", new string[] { })]
|
||||
[InlineData("{area=TestArea}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })]
|
||||
[InlineData("{area=TestArea}/{controller}/{action=TestAction}/{id?}", new[] { "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })]
|
||||
[InlineData("{area=TestArea}/{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestArea", "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })]
|
||||
[InlineData("{area:exists}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })]
|
||||
public void InitializeEndpoints_AreaSingleAction(string endpointInfoRoute, string[] finalEndpointTemplates)
|
||||
{
|
||||
// Arrange
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List<ActionDescriptor>
|
||||
{
|
||||
CreateActionDescriptor("TestController", "TestAction", "TestArea")
|
||||
}, 0));
|
||||
|
||||
var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute));
|
||||
|
||||
// Act
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
// Assert
|
||||
var inspectors = finalEndpointTemplates
|
||||
.Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<MatcherEndpoint>(e).Template)))
|
||||
.ToArray();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(dataSource.Endpoints, inspectors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InitializeEndpoints_SingleAction_WithActionDefault()
|
||||
{
|
||||
// Arrange
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List<ActionDescriptor>
|
||||
{
|
||||
CreateActionDescriptor("TestController", "TestAction")
|
||||
}, 0));
|
||||
|
||||
var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
||||
string.Empty,
|
||||
"{controller}/{action}",
|
||||
new RouteValueDictionary(new { action = "TestAction" })));
|
||||
|
||||
// Act
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(dataSource.Endpoints,
|
||||
(e) => Assert.Equal("TestController", Assert.IsType<MatcherEndpoint>(e).Template),
|
||||
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<MatcherEndpoint>(e).Template));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InitializeEndpoints_MultipleActions_WithActionConstraint()
|
||||
{
|
||||
// Arrange
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List<ActionDescriptor>
|
||||
{
|
||||
CreateActionDescriptor("TestController", "TestAction"),
|
||||
CreateActionDescriptor("TestController", "TestAction1"),
|
||||
CreateActionDescriptor("TestController", "TestAction2")
|
||||
}, 0));
|
||||
|
||||
var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
||||
string.Empty,
|
||||
"{controller}/{action}",
|
||||
constraints: new RouteValueDictionary(new { action = "(TestAction1|TestAction2)" })));
|
||||
|
||||
// Act
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(dataSource.Endpoints,
|
||||
(e) => Assert.Equal("TestController/TestAction1", Assert.IsType<MatcherEndpoint>(e).Template),
|
||||
(e) => Assert.Equal("TestController/TestAction2", Assert.IsType<MatcherEndpoint>(e).Template));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{controller}/{action}", new[] { "TestController1/TestAction1", "TestController1/TestAction2", "TestController1/TestAction3", "TestController2/TestAction1" })]
|
||||
[InlineData("{controller}/{action:regex((TestAction1|TestAction2))}", new[] { "TestController1/TestAction1", "TestController1/TestAction2", "TestController2/TestAction1" })]
|
||||
public void InitializeEndpoints_MultipleActions(string endpointInfoRoute, string[] finalEndpointTemplates)
|
||||
{
|
||||
// Arrange
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List<ActionDescriptor>
|
||||
{
|
||||
CreateActionDescriptor("TestController1", "TestAction1"),
|
||||
CreateActionDescriptor("TestController1", "TestAction2"),
|
||||
CreateActionDescriptor("TestController1", "TestAction3"),
|
||||
CreateActionDescriptor("TestController2", "TestAction1")
|
||||
}, 0));
|
||||
|
||||
var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
||||
string.Empty,
|
||||
endpointInfoRoute));
|
||||
|
||||
// Act
|
||||
dataSource.InitializeEndpoints();
|
||||
|
||||
var inspectors = finalEndpointTemplates
|
||||
.Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<MatcherEndpoint>(e).Template)))
|
||||
.ToArray();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(dataSource.Endpoints, inspectors);
|
||||
}
|
||||
|
||||
private MvcEndpointDataSource CreateMvcEndpointDataSource(
|
||||
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider = null,
|
||||
MvcEndpointInvokerFactory mvcEndpointInvokerFactory = null,
|
||||
IEnumerable<IActionDescriptorChangeProvider> actionDescriptorChangeProviders = null)
|
||||
{
|
||||
if (actionDescriptorCollectionProvider == null)
|
||||
{
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List<ActionDescriptor>(), 0));
|
||||
|
||||
actionDescriptorCollectionProvider = mockDescriptorProvider.Object;
|
||||
}
|
||||
|
||||
var serviceProviderMock = new Mock<IServiceProvider>();
|
||||
serviceProviderMock.Setup(m => m.GetService(typeof(IActionDescriptorCollectionProvider))).Returns(actionDescriptorCollectionProvider);
|
||||
|
||||
var dataSource = new MvcEndpointDataSource(
|
||||
actionDescriptorCollectionProvider,
|
||||
mvcEndpointInvokerFactory ?? new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty<IActionInvokerProvider>())),
|
||||
actionDescriptorChangeProviders ?? Array.Empty<IActionDescriptorChangeProvider>(),
|
||||
serviceProviderMock.Object);
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
private MvcEndpointInfo CreateEndpointInfo(
|
||||
string name,
|
||||
string template,
|
||||
RouteValueDictionary defaults = null,
|
||||
IDictionary<string, object> constraints = null,
|
||||
RouteValueDictionary dataTokens = null)
|
||||
{
|
||||
var routeOptions = new RouteOptions();
|
||||
var routeOptionsSetup = new MvcCoreRouteOptionsSetup();
|
||||
routeOptionsSetup.Configure(routeOptions);
|
||||
|
||||
var constraintResolver = new DefaultInlineConstraintResolver(Options.Create<RouteOptions>(routeOptions));
|
||||
return new MvcEndpointInfo(name, template, defaults, constraints, dataTokens, constraintResolver);
|
||||
}
|
||||
|
||||
private ActionDescriptor CreateActionDescriptor(string controller, string action, string area = null)
|
||||
{
|
||||
return new ActionDescriptor
|
||||
{
|
||||
RouteValues =
|
||||
{
|
||||
["controller"] = controller,
|
||||
["action"] = action,
|
||||
["area"] = area
|
||||
},
|
||||
DisplayName = string.Empty,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// 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.Internal;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal
|
||||
{
|
||||
public class RouteTemplateWriterTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(@"")]
|
||||
[InlineData(@"Literal")]
|
||||
[InlineData(@"Literal1/Literal2")]
|
||||
[InlineData(@"{controller}")]
|
||||
[InlineData(@"{controller}/{action}")]
|
||||
[InlineData(@"{controller}/{action}/{param:test(\?)?}")]
|
||||
[InlineData(@"{param:test(\w,\w)=jsd}")]
|
||||
[InlineData(@"some/url-{p1:int:test(3)=hello}/{p2=abc}/{p3?}")]
|
||||
[InlineData(@"{param:test(abc:somevalue):name(test1:differentname=default-value}")]
|
||||
[InlineData(@"api/Blog/{controller}/{action}/{id?}")]
|
||||
[InlineData(@"{p1}.{p2}.{p3}")]
|
||||
public void ToString_TemplateRoundtrips(string template)
|
||||
{
|
||||
var routeTemplate = TemplateParser.Parse(template);
|
||||
|
||||
var output = RouteTemplateWriter.ToString(routeTemplate.Segments);
|
||||
|
||||
Assert.Equal(template, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,223 +12,5 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
: base(fixture)
|
||||
{
|
||||
}
|
||||
|
||||
[Fact(Skip = "Conventional routing WIP")]
|
||||
public override Task ConventionalRoutedController_ActionIsReachable()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "Conventional routing WIP")]
|
||||
public override Task ConventionalRoutedController_ActionIsReachable_WithDefaults()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "Conventional routing WIP")]
|
||||
public override Task ConventionalRoutedController_NonActionIsNotReachable()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "Conventional routing WIP")]
|
||||
public override Task ConventionalRoutedController_InArea_ActionIsReachable()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "Conventional routing WIP")]
|
||||
public override Task ConventionalRoutedController_InArea_ActionBlockedByHttpMethod()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Theory(Skip = "Conventional routing WIP")]
|
||||
[InlineData("", "/Home/OptionalPath/default")]
|
||||
[InlineData("CustomPath", "/Home/OptionalPath/CustomPath")]
|
||||
public override Task ConventionalRoutedController_WithOptionalSegment(string optionalSegment, string expected)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Theory(Skip = "URL generation WIP")]
|
||||
[InlineData("http://localhost/api/v1/Maps")]
|
||||
[InlineData("http://localhost/api/v2/Maps")]
|
||||
public override Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithNameAndOrder(string url)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "URL generation WIP")]
|
||||
public override Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithOverrideRoutes()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Theory(Skip = "URL generation WIP")]
|
||||
[InlineData("http://localhost/api/v1/Maps/5", "PUT")]
|
||||
[InlineData("http://localhost/api/v2/Maps/5", "PUT")]
|
||||
[InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PATCH")]
|
||||
[InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PATCH")]
|
||||
public override Task AttributeRoutedAction_MultipleRouteAttributes_CombinesWithMultipleHttpAttributes(
|
||||
string url,
|
||||
string method)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Theory(Skip = "URL generation WIP")]
|
||||
[InlineData("http://localhost/Banks/Get/5")]
|
||||
[InlineData("http://localhost/Bank/Get/5")]
|
||||
public override Task AttributeRoutedAction_MultipleHttpAttributesAndTokenReplacement(string url)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Theory(Skip = "URL generation WIP")]
|
||||
[InlineData("PUT", "Bank")]
|
||||
[InlineData("PATCH", "Bank")]
|
||||
[InlineData("PUT", "Bank/Update")]
|
||||
[InlineData("PATCH", "Bank/Update")]
|
||||
public override Task AttributeRoutedAction_AcceptVerbsAndRouteTemplate_IsReachable(string verb, string path)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "URL generation WIP")]
|
||||
public override Task AttributeRoutedAction_LinkGeneration_OverrideActionOverridesOrderOnController()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "URL generation WIP")]
|
||||
public override Task AttributeRoutedAction_LinkGeneration_OrderOnActionOverridesOrderOnController()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "URL generation WIP")]
|
||||
public override Task AttributeRoutedAction_LinkToSelf()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "URL generation WIP")]
|
||||
public override Task AttributeRoutedAction_LinkWithAmbientController()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "URL generation WIP")]
|
||||
public override Task AttributeRoutedAction_LinkToAttributeRoutedController()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "URL generation WIP")]
|
||||
public override Task AttributeRoutedAction_LinkToConventionalController()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Theory(Skip = "URL generation WIP")]
|
||||
[InlineData("GET", "Get")]
|
||||
[InlineData("PUT", "Put")]
|
||||
public override Task AttributeRoutedAction_LinkWithName_WithNameInheritedFromControllerRoute(
|
||||
string method,
|
||||
string actionName)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "URL generation WIP")]
|
||||
public override Task AttributeRoutedAction_LinkWithName_WithNameOverrridenFromController()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "URL generation WIP")]
|
||||
public override Task AttributeRoutedAction_Link_WithNonEmptyActionRouteTemplateAndNoActionRouteName()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "URL generation WIP")]
|
||||
public override Task AttributeRoutedAction_LinkWithName_WithNonEmptyActionRouteTemplateAndActionRouteName()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "Conventional routing WIP")]
|
||||
public override Task ConventionalRoutedAction_LinkToArea()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "Conventional routing WIP")]
|
||||
public override Task ConventionalRoutedAction_InArea_ImplicitLinkToArea()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "Conventional routing WIP")]
|
||||
public override Task ConventionalRoutedAction_InArea_ExplicitLeaveArea()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "Conventional routing WIP")]
|
||||
public override Task ConventionalRoutedAction_InArea_StaysInArea()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "URL generation WIP")]
|
||||
public override Task AttributeRoutedAction_LinkToArea()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "URL generation WIP")]
|
||||
public override Task AttributeRoutedAction_InArea_ImplicitLinkToArea()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "URL generation WIP")]
|
||||
public override Task AttributeRoutedAction_InArea_ExplicitLeaveArea()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "URL generation WIP")]
|
||||
public override Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "URL generation WIP")]
|
||||
public override Task AttributeRoutedAction_InArea_LinkToConventionalRoutedActionInArea()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "Conventional routing WIP")]
|
||||
public override Task ConventionalRoutedAction_InArea_LinkToAttributeRoutedActionInArea()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "Conventional routing WIP")]
|
||||
public override Task ConventionalRoutedAction_InArea_LinkToAnotherArea()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact(Skip = "URL generation WIP")]
|
||||
public override Task AttributeRoutedAction_InArea_LinkToAnotherArea()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,26 +33,24 @@ namespace RoutingWebSite
|
|||
{
|
||||
app.UseDispatcher();
|
||||
|
||||
app.UseEndpoint();
|
||||
app.UseMvcWithEndpoint(routes =>
|
||||
{
|
||||
routes.MapAreaEndpoint(
|
||||
"flightRoute",
|
||||
"adminRoute",
|
||||
"{area:exists}/{controller}/{action}",
|
||||
new { controller = "Home", action = "Index" },
|
||||
new { area = "Travel" });
|
||||
|
||||
//app.UseMvcWithEndpoint(routes =>
|
||||
//{
|
||||
// routes.MapAreaEndpoint(
|
||||
// "flightRoute",
|
||||
// "adminRoute",
|
||||
// "{area:exists}/{controller}/{action}",
|
||||
// new { controller = "Home", action = "Index" },
|
||||
// new { area = "Travel" });
|
||||
routes.MapEndpoint(
|
||||
"ActionAsMethod",
|
||||
"{controller}/{action}",
|
||||
defaults: new { controller = "Home", action = "Index" });
|
||||
|
||||
// routes.MapEndpoint(
|
||||
// "ActionAsMethod",
|
||||
// "{controller}/{action}",
|
||||
// defaults: new { controller = "Home", action = "Index" });
|
||||
|
||||
// routes.MapEndpoint(
|
||||
// "RouteWithOptionalSegment",
|
||||
// "{controller}/{action}/{path?}");
|
||||
//});
|
||||
routes.MapEndpoint(
|
||||
"RouteWithOptionalSegment",
|
||||
"{controller}/{action}/{path?}");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue