Update MVC/Routing Startup Experience (#7425)
* Relayer MvcEndpointDataSource Separates the statefulness of the data source from the business logic of how endpoints are created. I'm separating these concerns because one of the next steps will split the MvcEndpointDataSource into two data sources. * Simplify MvcEndpointInfo Removing things that are unused and leftovers from the 2.2 design of this feature. * Remove per-route conventions Removes the ability to target endpoint conventions per-conventional-route. This was a neat idea but we have no plans to ship it for now. Simplified MvcEndpointInfo and renamed it to reflect its new purpose. * Remove filtering from MvcEndpointDataSource This was neat-o but we're not going to ship it like this. We're going to implement filtering in another place. Putting this in the data source is pretty clumsy and doesn't work with features like application parts that need to be baked in addservices * Simplify ActionEndpointFactory * Split up data sources * Use UseRouting in functional tests I've rejiggered our functional tests to de-emphasize UseMvc(...) and only use it when we're specifically testing the old scenarios. UseMvc(...) won't appear in templates in 3.0 so it's legacy. * Update templates * Add minor PR feedback * one more
This commit is contained in:
parent
2a9caa0343
commit
acd1cf3251
|
|
@ -14,7 +14,7 @@ using Microsoft.AspNetCore.Routing.Patterns;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.Performance
|
||||
{
|
||||
public class MvcEndpointDataSourceBenchmark
|
||||
public class ActionEndpointDataSourceBenchmark
|
||||
{
|
||||
private const string DefaultRoute = "{Controller=Home}/{Action=Index}/{id?}";
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.Performance
|
|||
|
||||
private MockActionDescriptorCollectionProvider _conventionalActionProvider;
|
||||
private MockActionDescriptorCollectionProvider _attributeActionProvider;
|
||||
private List<MvcEndpointInfo> _conventionalEndpointInfos;
|
||||
private List<ConventionalRouteEntry> _routes;
|
||||
|
||||
[Params(1, 100, 1000)]
|
||||
public int ActionCount;
|
||||
|
|
@ -41,22 +41,21 @@ namespace Microsoft.AspNetCore.Mvc.Performance
|
|||
Enumerable.Range(0, ActionCount).Select(i => CreateAttributeRoutedAction(i)).ToList()
|
||||
);
|
||||
|
||||
_conventionalEndpointInfos = new List<MvcEndpointInfo>
|
||||
_routes = new List<ConventionalRouteEntry>
|
||||
{
|
||||
new MvcEndpointInfo(
|
||||
new ConventionalRouteEntry(
|
||||
"Default",
|
||||
DefaultRoute,
|
||||
new RouteValueDictionary(),
|
||||
new Dictionary<string, object>(),
|
||||
new RouteValueDictionary(),
|
||||
new MockParameterPolicyFactory())
|
||||
new RouteValueDictionary())
|
||||
};
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void AttributeRouteEndpoints()
|
||||
{
|
||||
var endpointDataSource = CreateMvcEndpointDataSource(_attributeActionProvider);
|
||||
var endpointDataSource = CreateDataSource(_attributeActionProvider);
|
||||
var endpoints = endpointDataSource.Endpoints;
|
||||
|
||||
AssertHasEndpoints(endpoints);
|
||||
|
|
@ -65,10 +64,13 @@ namespace Microsoft.AspNetCore.Mvc.Performance
|
|||
[Benchmark]
|
||||
public void ConventionalEndpoints()
|
||||
{
|
||||
var endpointDataSource = CreateMvcEndpointDataSource(_conventionalActionProvider);
|
||||
endpointDataSource.ConventionalEndpointInfos.AddRange(_conventionalEndpointInfos);
|
||||
var endpoints = endpointDataSource.Endpoints;
|
||||
var dataSource = CreateDataSource(_conventionalActionProvider);
|
||||
for (var i = 0; i < _routes.Count; i++)
|
||||
{
|
||||
dataSource.AddRoute(_routes[i]);
|
||||
}
|
||||
|
||||
var endpoints = dataSource.Endpoints;
|
||||
AssertHasEndpoints(endpoints);
|
||||
}
|
||||
|
||||
|
|
@ -108,14 +110,14 @@ namespace Microsoft.AspNetCore.Mvc.Performance
|
|||
};
|
||||
}
|
||||
|
||||
private MvcEndpointDataSource CreateMvcEndpointDataSource(
|
||||
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider)
|
||||
private ActionEndpointDataSource CreateDataSource(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider)
|
||||
{
|
||||
var dataSource = new MvcEndpointDataSource(
|
||||
var dataSource = new ActionEndpointDataSource(
|
||||
actionDescriptorCollectionProvider,
|
||||
new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty<IActionInvokerProvider>())),
|
||||
new MockParameterPolicyFactory(),
|
||||
new MockRoutePatternTransformer());
|
||||
new ActionEndpointFactory(
|
||||
new MockRoutePatternTransformer(),
|
||||
new MvcEndpointInvokerFactory(
|
||||
new ActionInvokerFactory(Array.Empty<IActionInvokerProvider>()))));
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
|
@ -66,7 +66,8 @@ namespace MvcSandbox
|
|||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
builder.MapApplication();
|
||||
builder.MapControllers();
|
||||
builder.MapRazorPages();
|
||||
});
|
||||
|
||||
app.UseDeveloperExceptionPage();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,179 @@
|
|||
// 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.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Constraints;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains extension methods for using Controllers with <see cref="IEndpointRouteBuilder"/>.
|
||||
/// </summary>
|
||||
public static class ControllerEndpointRouteBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds endpoints for controller actions to the <see cref="IEndpointRouteBuilder"/> without specifying any routes.
|
||||
/// </summary>
|
||||
/// <param name="routes">The <see cref="IEndpointRouteBuilder"/>.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for endpoints associated with controller actions.</returns>
|
||||
public static IEndpointConventionBuilder MapControllers(this IEndpointRouteBuilder routes)
|
||||
{
|
||||
if (routes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routes));
|
||||
}
|
||||
|
||||
EnsureControllerServices(routes);
|
||||
|
||||
return GetOrCreateDataSource(routes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds endpoints for controller actions to the <see cref="IEndpointRouteBuilder"/> and adds the default route
|
||||
/// <c>{controller=Home}/{action=Index}/{id?}</c>.
|
||||
/// </summary>
|
||||
/// <param name="routes">The <see cref="IEndpointRouteBuilder"/>.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for endpoints associated with controller actions.</returns>
|
||||
public static IEndpointConventionBuilder MapDefaultControllerRoute(this IEndpointRouteBuilder routes)
|
||||
{
|
||||
if (routes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routes));
|
||||
}
|
||||
|
||||
EnsureControllerServices(routes);
|
||||
|
||||
var dataSource = GetOrCreateDataSource(routes);
|
||||
dataSource.AddRoute(new ConventionalRouteEntry(
|
||||
"default",
|
||||
"{controller=Home}/{action=Index}/{id?}",
|
||||
defaults: null,
|
||||
constraints: null,
|
||||
dataTokens: null));
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds endpoints for controller actions to the <see cref="IEndpointRouteBuilder"/> and specifies a route
|
||||
/// with the given <paramref name="name"/>, <paramref name="template"/>,
|
||||
/// <paramref name="defaults"/>, <paramref name="constraints"/>, and <paramref name="dataTokens"/>.
|
||||
/// </summary>
|
||||
/// <param name="routes">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
|
||||
/// <param name="name">The name of the route.</param>
|
||||
/// <param name="template">The URL pattern of the route.</param>
|
||||
/// <param name="defaults">
|
||||
/// An object that contains default values for route 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 route. The object's properties represent the names and
|
||||
/// values of the constraints.
|
||||
/// </param>
|
||||
/// <param name="dataTokens">
|
||||
/// An object that contains data tokens for the route. The object's properties represent the names and
|
||||
/// values of the data tokens.
|
||||
/// </param>
|
||||
public static void MapControllerRoute(
|
||||
this IEndpointRouteBuilder routes,
|
||||
string name,
|
||||
string template,
|
||||
object defaults = null,
|
||||
object constraints = null,
|
||||
object dataTokens = null)
|
||||
{
|
||||
if (routes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routes));
|
||||
}
|
||||
|
||||
EnsureControllerServices(routes);
|
||||
|
||||
var dataSource = GetOrCreateDataSource(routes);
|
||||
dataSource.AddRoute(new ConventionalRouteEntry(
|
||||
name,
|
||||
template,
|
||||
new RouteValueDictionary(defaults),
|
||||
new RouteValueDictionary(constraints),
|
||||
new RouteValueDictionary(dataTokens)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds endpoints for controller actions to the <see cref="IEndpointRouteBuilder"/> and specifies a route
|
||||
/// with the given <paramref name="name"/>, <paramref name="areaName"/>, <paramref name="template"/>,
|
||||
/// <paramref name="defaults"/>, <paramref name="constraints"/>, and <paramref name="dataTokens"/>.
|
||||
/// </summary>
|
||||
/// <param name="routes">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
|
||||
/// <param name="name">The name of the route.</param>
|
||||
/// <param name="areaName">The area name.</param>
|
||||
/// <param name="template">The URL pattern of the route.</param>
|
||||
/// <param name="defaults">
|
||||
/// An object that contains default values for route 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 route. The object's properties represent the names and
|
||||
/// values of the constraints.
|
||||
/// </param>
|
||||
/// <param name="dataTokens">
|
||||
/// An object that contains data tokens for the route. The object's properties represent the names and
|
||||
/// values of the data tokens.
|
||||
/// </param>
|
||||
public static void MapAreaControllerRoute(
|
||||
this IEndpointRouteBuilder routes,
|
||||
string name,
|
||||
string areaName,
|
||||
string template,
|
||||
object defaults = null,
|
||||
object constraints = null,
|
||||
object dataTokens = null)
|
||||
{
|
||||
if (routes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routes));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
routes.MapControllerRoute(name, template, defaultsDictionary, constraintsDictionary, dataTokens);
|
||||
}
|
||||
|
||||
private static void EnsureControllerServices(IEndpointRouteBuilder routes)
|
||||
{
|
||||
var marker = routes.ServiceProvider.GetService<MvcMarkerService>();
|
||||
if (marker == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatUnableToFindServices(
|
||||
nameof(IServiceCollection),
|
||||
"AddMvc",
|
||||
"ConfigureServices(...)"));
|
||||
}
|
||||
}
|
||||
|
||||
private static ControllerActionEndpointDataSource GetOrCreateDataSource(IEndpointRouteBuilder routes)
|
||||
{
|
||||
var dataSource = routes.DataSources.OfType<ControllerActionEndpointDataSource>().FirstOrDefault();
|
||||
if (dataSource == null)
|
||||
{
|
||||
dataSource = routes.ServiceProvider.GetRequiredService<ControllerActionEndpointDataSource>();
|
||||
routes.DataSources.Add(dataSource);
|
||||
}
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
// 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<EndpointBuilder>>();
|
||||
}
|
||||
|
||||
public List<Action<EndpointBuilder>> Conventions { get; }
|
||||
|
||||
public void Add(Action<EndpointBuilder> convention)
|
||||
{
|
||||
Conventions.Add(convention);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -88,10 +88,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
|
||||
if (options.Value.EnableEndpointRouting)
|
||||
{
|
||||
var mvcEndpointDataSource = app.ApplicationServices
|
||||
.GetRequiredService<MvcEndpointDataSource>();
|
||||
var parameterPolicyFactory = app.ApplicationServices
|
||||
.GetRequiredService<ParameterPolicyFactory>();
|
||||
var dataSource = app.ApplicationServices.GetRequiredService<ActionEndpointDataSource>();
|
||||
|
||||
var endpointRouteBuilder = new EndpointRouteBuilder(app);
|
||||
|
||||
|
|
@ -103,15 +100,14 @@ namespace Microsoft.AspNetCore.Builder
|
|||
// Sub-types could have additional customization that we can't knowingly convert
|
||||
if (router is Route route && router.GetType() == typeof(Route))
|
||||
{
|
||||
var endpointInfo = new MvcEndpointInfo(
|
||||
var entry = new ConventionalRouteEntry(
|
||||
route.Name,
|
||||
route.RouteTemplate,
|
||||
route.Defaults,
|
||||
route.Constraints.ToDictionary(kvp => kvp.Key, kvp => (object)kvp.Value),
|
||||
route.DataTokens,
|
||||
parameterPolicyFactory);
|
||||
route.DataTokens);
|
||||
|
||||
mvcEndpointDataSource.ConventionalEndpointInfos.Add(endpointInfo);
|
||||
dataSource.AddRoute(entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -119,20 +115,13 @@ 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.UseRouting(routerBuilder =>
|
||||
{
|
||||
routerBuilder.DataSources.Add(mvcEndpointDataSource);
|
||||
routerBuilder.DataSources.Add(dataSource);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,79 +0,0 @@
|
|||
// 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.Patterns;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
internal class MvcEndpointInfo : DefaultEndpointConventionBuilder
|
||||
{
|
||||
public MvcEndpointInfo(
|
||||
string name,
|
||||
string pattern,
|
||||
RouteValueDictionary defaults,
|
||||
IDictionary<string, object> constraints,
|
||||
RouteValueDictionary dataTokens,
|
||||
ParameterPolicyFactory parameterPolicyFactory)
|
||||
{
|
||||
Name = name;
|
||||
Pattern = pattern ?? string.Empty;
|
||||
DataTokens = dataTokens;
|
||||
|
||||
try
|
||||
{
|
||||
// Data we parse from the pattern will be used to fill in the rest of the constraints or
|
||||
// defaults. The parser will throw for invalid routes.
|
||||
ParsedPattern = RoutePatternFactory.Parse(pattern, defaults, constraints);
|
||||
ParameterPolicies = BuildParameterPolicies(ParsedPattern.Parameters, parameterPolicyFactory);
|
||||
|
||||
Defaults = defaults;
|
||||
// Merge defaults outside of RoutePattern because the defaults will already have values from pattern
|
||||
MergedDefaults = new RouteValueDictionary(ParsedPattern.Defaults);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new RouteCreationException(
|
||||
string.Format(CultureInfo.CurrentCulture, "An error occurred while creating the route with name '{0}' and pattern '{1}'.", name, pattern), exception);
|
||||
}
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public string Pattern { get; }
|
||||
|
||||
// Non-inline defaults
|
||||
public RouteValueDictionary Defaults { get; }
|
||||
|
||||
// Inline and non-inline defaults merged into one
|
||||
public RouteValueDictionary MergedDefaults { get; }
|
||||
|
||||
public IDictionary<string, IList<IParameterPolicy>> ParameterPolicies { get; }
|
||||
public RouteValueDictionary DataTokens { get; }
|
||||
public RoutePattern ParsedPattern { get; private set; }
|
||||
|
||||
internal static Dictionary<string, IList<IParameterPolicy>> BuildParameterPolicies(IReadOnlyList<RoutePatternParameterPart> parameters, ParameterPolicyFactory parameterPolicyFactory)
|
||||
{
|
||||
var policies = new Dictionary<string, IList<IParameterPolicy>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
foreach (var parameterPolicy in parameter.ParameterPolicies)
|
||||
{
|
||||
var createdPolicy = parameterPolicyFactory.Create(parameter, parameterPolicy);
|
||||
if (!policies.TryGetValue(parameter.Name, out var policyList))
|
||||
{
|
||||
policyList = new List<IParameterPolicy>();
|
||||
policies.Add(parameter.Name, policyList);
|
||||
}
|
||||
|
||||
policyList.Add(createdPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
return policies;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
// 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.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
public static class MvcEndpointRouteBuilderExtensions
|
||||
{
|
||||
public static IEndpointConventionBuilder MapApplication(
|
||||
this IEndpointRouteBuilder routeBuilder)
|
||||
{
|
||||
return MapActionDescriptors(routeBuilder, null);
|
||||
}
|
||||
|
||||
public static IEndpointConventionBuilder MapAssembly<TContainingType>(
|
||||
this IEndpointRouteBuilder routeBuilder)
|
||||
{
|
||||
return MapActionDescriptors(routeBuilder, typeof(TContainingType));
|
||||
}
|
||||
|
||||
private static IEndpointConventionBuilder MapActionDescriptors(
|
||||
this IEndpointRouteBuilder routeBuilder,
|
||||
Type containingType)
|
||||
{
|
||||
var mvcEndpointDataSource = routeBuilder.DataSources.OfType<MvcEndpointDataSource>().FirstOrDefault();
|
||||
|
||||
if (mvcEndpointDataSource == null)
|
||||
{
|
||||
mvcEndpointDataSource = routeBuilder.ServiceProvider.GetRequiredService<MvcEndpointDataSource>();
|
||||
routeBuilder.DataSources.Add(mvcEndpointDataSource);
|
||||
}
|
||||
|
||||
var conventionBuilder = new DefaultEndpointConventionBuilder();
|
||||
|
||||
var assemblyFilter = containingType?.Assembly;
|
||||
|
||||
mvcEndpointDataSource.AttributeRoutingConventionResolvers.Add(actionDescriptor =>
|
||||
{
|
||||
// Filter a descriptor by the assembly
|
||||
// Note that this will only filter actions on controllers
|
||||
// Does not support filtering Razor pages embedded in assemblies
|
||||
if (assemblyFilter != null)
|
||||
{
|
||||
if (actionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
|
||||
{
|
||||
if (controllerActionDescriptor.ControllerTypeInfo.Assembly != assemblyFilter)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return conventionBuilder;
|
||||
});
|
||||
|
||||
return conventionBuilder;
|
||||
}
|
||||
|
||||
public static IEndpointConventionBuilder MapControllerRoute(
|
||||
this IEndpointRouteBuilder routeBuilder,
|
||||
string name,
|
||||
string template)
|
||||
{
|
||||
return MapControllerRoute(routeBuilder, name, template, defaults: null);
|
||||
}
|
||||
|
||||
public static IEndpointConventionBuilder MapControllerRoute(
|
||||
this IEndpointRouteBuilder routeBuilder,
|
||||
string name,
|
||||
string template,
|
||||
object defaults)
|
||||
{
|
||||
return MapControllerRoute(routeBuilder, name, template, defaults, constraints: null);
|
||||
}
|
||||
|
||||
public static IEndpointConventionBuilder MapControllerRoute(
|
||||
this IEndpointRouteBuilder routeBuilder,
|
||||
string name,
|
||||
string template,
|
||||
object defaults,
|
||||
object constraints)
|
||||
{
|
||||
return MapControllerRoute(routeBuilder, name, template, defaults, constraints, dataTokens: null);
|
||||
}
|
||||
|
||||
public static IEndpointConventionBuilder MapControllerRoute(
|
||||
this IEndpointRouteBuilder routeBuilder,
|
||||
string name,
|
||||
string template,
|
||||
object defaults,
|
||||
object constraints,
|
||||
object dataTokens)
|
||||
{
|
||||
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>());
|
||||
|
||||
mvcEndpointDataSource.ConventionalEndpointInfos.Add(endpointInfo);
|
||||
|
||||
return endpointInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -269,7 +269,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
//
|
||||
// Endpoint Routing / Endpoints
|
||||
//
|
||||
services.TryAddSingleton<MvcEndpointDataSource>();
|
||||
services.TryAddSingleton<ActionEndpointDataSource>();
|
||||
services.TryAddSingleton<ControllerActionEndpointDataSource>();
|
||||
services.TryAddSingleton<ActionEndpointFactory>();
|
||||
services.TryAddSingleton<MvcEndpointInvokerFactory>();
|
||||
|
||||
//
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
// 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.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||
{
|
||||
// This is only used to support the scenario where UseMvc is called with
|
||||
// EnableEndpointRouting = true. For layering reasons we can't use the PageActionEndpointDataSource
|
||||
// here.
|
||||
internal class ActionEndpointDataSource : ActionEndpointDataSourceBase
|
||||
{
|
||||
private readonly ActionEndpointFactory _endpointFactory;
|
||||
private readonly List<ConventionalRouteEntry> _routes;
|
||||
|
||||
public ActionEndpointDataSource(IActionDescriptorCollectionProvider actions, ActionEndpointFactory endpointFactory)
|
||||
: base(actions)
|
||||
{
|
||||
_endpointFactory = endpointFactory;
|
||||
|
||||
_routes = new List<ConventionalRouteEntry>();
|
||||
|
||||
// IMPORTANT: this needs to be the last thing we do in the constructor.
|
||||
// Change notifications can happen immediately!
|
||||
Subscribe();
|
||||
}
|
||||
|
||||
// For testing
|
||||
public IReadOnlyList<ConventionalRouteEntry> Routes
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
return _routes.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRoute(in ConventionalRouteEntry route)
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
_routes.Add(route);
|
||||
}
|
||||
}
|
||||
|
||||
protected override List<Endpoint> CreateEndpoints(IReadOnlyList<ActionDescriptor> actions, IReadOnlyList<Action<EndpointBuilder>> conventions)
|
||||
{
|
||||
var endpoints = new List<Endpoint>();
|
||||
for (var i = 0; i < actions.Count; i++)
|
||||
{
|
||||
_endpointFactory.AddEndpoints(endpoints, actions[i], _routes, conventions);
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||
{
|
||||
internal abstract class ActionEndpointDataSourceBase : EndpointDataSource, IEndpointConventionBuilder, IDisposable
|
||||
{
|
||||
private readonly IActionDescriptorCollectionProvider _actions;
|
||||
|
||||
// The following are protected by this lock for WRITES only. This pattern is similar
|
||||
// to DefaultActionDescriptorChangeProvider - see comments there for details on
|
||||
// all of the threading behaviors.
|
||||
protected readonly object Lock = new object();
|
||||
|
||||
private List<Endpoint> _endpoints;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private IChangeToken _changeToken;
|
||||
private IDisposable _disposable;
|
||||
|
||||
// Protected for READS and WRITES.
|
||||
private readonly List<Action<EndpointBuilder>> _conventions;
|
||||
|
||||
public ActionEndpointDataSourceBase(IActionDescriptorCollectionProvider actions)
|
||||
{
|
||||
_actions = actions;
|
||||
|
||||
_conventions = new List<Action<EndpointBuilder>>();
|
||||
}
|
||||
|
||||
public override IReadOnlyList<Endpoint> Endpoints
|
||||
{
|
||||
get
|
||||
{
|
||||
Initialize();
|
||||
Debug.Assert(_changeToken != null);
|
||||
Debug.Assert(_endpoints != null);
|
||||
return _endpoints;
|
||||
}
|
||||
}
|
||||
|
||||
// Will be called with the lock.
|
||||
protected abstract List<Endpoint> CreateEndpoints(IReadOnlyList<ActionDescriptor> actions, IReadOnlyList<Action<EndpointBuilder>> conventions);
|
||||
|
||||
protected void Subscribe()
|
||||
{
|
||||
// IMPORTANT: this needs to be called by the derived class to avoid the fragile base class
|
||||
// problem. We can't call this in the base-class constuctor because it's too early.
|
||||
//
|
||||
// It's possible for someone to override the collection provider without providing
|
||||
// change notifications. If that's the case we won't process changes.
|
||||
if (_actions is ActionDescriptorCollectionProvider collectionProviderWithChangeToken)
|
||||
{
|
||||
_disposable = ChangeToken.OnChange(
|
||||
() => collectionProviderWithChangeToken.GetChangeToken(),
|
||||
UpdateEndpoints);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(Action<EndpointBuilder> convention)
|
||||
{
|
||||
if (convention == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(convention));
|
||||
}
|
||||
|
||||
lock (Lock)
|
||||
{
|
||||
_conventions.Add(convention);
|
||||
}
|
||||
}
|
||||
|
||||
public override IChangeToken GetChangeToken()
|
||||
{
|
||||
Initialize();
|
||||
Debug.Assert(_changeToken != null);
|
||||
Debug.Assert(_endpoints != null);
|
||||
return _changeToken;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Once disposed we won't process updates anymore, but we still allow access to the endpoints.
|
||||
_disposable?.Dispose();
|
||||
_disposable = null;
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (_endpoints == null)
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
if (_endpoints == null)
|
||||
{
|
||||
UpdateEndpoints();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateEndpoints()
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
var endpoints = CreateEndpoints(_actions.ActionDescriptors.Items, _conventions);
|
||||
|
||||
// See comments in DefaultActionDescriptorCollectionProvider. These steps are done
|
||||
// in a specific order to ensure callers always see a consistent state.
|
||||
|
||||
// Step 1 - capture old token
|
||||
var oldCancellationTokenSource = _cancellationTokenSource;
|
||||
|
||||
// Step 2 - update endpoints
|
||||
_endpoints = endpoints;
|
||||
|
||||
// Step 3 - create new change token
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_changeToken = new CancellationChangeToken(_cancellationTokenSource.Token);
|
||||
|
||||
// Step 4 - trigger old token
|
||||
oldCancellationTokenSource?.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
// 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 Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||
{
|
||||
internal class ActionEndpointFactory
|
||||
{
|
||||
private readonly RoutePatternTransformer _routePatternTransformer;
|
||||
private readonly MvcEndpointInvokerFactory _invokerFactory;
|
||||
|
||||
public ActionEndpointFactory(
|
||||
RoutePatternTransformer routePatternTransformer,
|
||||
MvcEndpointInvokerFactory invokerFactory)
|
||||
{
|
||||
if (routePatternTransformer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routePatternTransformer));
|
||||
}
|
||||
|
||||
if (invokerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(invokerFactory));
|
||||
}
|
||||
|
||||
_routePatternTransformer = routePatternTransformer;
|
||||
_invokerFactory = invokerFactory;
|
||||
}
|
||||
|
||||
public void AddEndpoints(
|
||||
List<Endpoint> endpoints,
|
||||
ActionDescriptor action,
|
||||
IReadOnlyList<ConventionalRouteEntry> routes,
|
||||
IReadOnlyList<Action<EndpointBuilder>> conventions)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
if (action == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
}
|
||||
|
||||
if (routes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routes));
|
||||
}
|
||||
|
||||
if (conventions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(conventions));
|
||||
}
|
||||
|
||||
if (action.AttributeRouteInfo == null)
|
||||
{
|
||||
// In traditional conventional routing setup, the routes defined by a user have a static order
|
||||
// defined by how they are added into the list. We would like to maintain the same order when building
|
||||
// up the endpoints too.
|
||||
//
|
||||
// Start with an order of '1' for conventional routes as attribute routes have a default order of '0'.
|
||||
// This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world.
|
||||
var conventionalRouteOrder = 1;
|
||||
|
||||
// Check each of the conventional patterns to see if the action would be reachable.
|
||||
// If the action and pattern are compatible then create an endpoint with action
|
||||
// route values on the pattern.
|
||||
foreach (var route in routes)
|
||||
{
|
||||
// A route 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
|
||||
var updatedRoutePattern = _routePatternTransformer.SubstituteRequiredValues(route.Pattern, action.RouteValues);
|
||||
if (updatedRoutePattern == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var builder = CreateEndpoint(
|
||||
action,
|
||||
updatedRoutePattern,
|
||||
route.RouteName,
|
||||
conventionalRouteOrder++,
|
||||
route.DataTokens,
|
||||
suppressLinkGeneration: false,
|
||||
suppressPathMatching: false,
|
||||
conventions);
|
||||
endpoints.Add(builder);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var attributeRoutePattern = RoutePatternFactory.Parse(action.AttributeRouteInfo.Template);
|
||||
|
||||
// Modify the route and required values to ensure required values can be successfully subsituted.
|
||||
// Subsitituting required values into an attribute route pattern should always succeed.
|
||||
var (resolvedRoutePattern, resolvedRouteValues) = ResolveDefaultsAndRequiredValues(action, attributeRoutePattern);
|
||||
|
||||
var updatedRoutePattern = _routePatternTransformer.SubstituteRequiredValues(resolvedRoutePattern, resolvedRouteValues);
|
||||
if (updatedRoutePattern == null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to update route pattern with required values.");
|
||||
}
|
||||
|
||||
var endpoint = CreateEndpoint(
|
||||
action,
|
||||
updatedRoutePattern,
|
||||
action.AttributeRouteInfo.Name,
|
||||
action.AttributeRouteInfo.Order,
|
||||
dataTokens: null,
|
||||
action.AttributeRouteInfo.SuppressLinkGeneration,
|
||||
action.AttributeRouteInfo.SuppressPathMatching,
|
||||
conventions);
|
||||
endpoints.Add(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
private static (RoutePattern resolvedRoutePattern, IDictionary<string, string> resolvedRequiredValues) ResolveDefaultsAndRequiredValues(ActionDescriptor action, RoutePattern attributeRoutePattern)
|
||||
{
|
||||
RouteValueDictionary updatedDefaults = null;
|
||||
IDictionary<string, string> resolvedRequiredValues = null;
|
||||
|
||||
foreach (var routeValue in action.RouteValues)
|
||||
{
|
||||
var parameter = attributeRoutePattern.GetParameter(routeValue.Key);
|
||||
|
||||
if (!RouteValueEqualityComparer.Default.Equals(routeValue.Value, string.Empty))
|
||||
{
|
||||
if (parameter == null)
|
||||
{
|
||||
// The attribute route has a required value with no matching parameter
|
||||
// Add the required values without a parameter as a default
|
||||
// e.g.
|
||||
// Template: "Login/{action}"
|
||||
// Required values: { controller = "Login", action = "Index" }
|
||||
// Updated defaults: { controller = "Login" }
|
||||
|
||||
if (updatedDefaults == null)
|
||||
{
|
||||
updatedDefaults = new RouteValueDictionary(attributeRoutePattern.Defaults);
|
||||
}
|
||||
|
||||
updatedDefaults[routeValue.Key] = routeValue.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parameter != null)
|
||||
{
|
||||
// The attribute route has a null or empty required value with a matching parameter
|
||||
// Remove the required value from the route
|
||||
|
||||
if (resolvedRequiredValues == null)
|
||||
{
|
||||
resolvedRequiredValues = new Dictionary<string, string>(action.RouteValues);
|
||||
}
|
||||
|
||||
resolvedRequiredValues.Remove(parameter.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (updatedDefaults != null)
|
||||
{
|
||||
attributeRoutePattern = RoutePatternFactory.Parse(action.AttributeRouteInfo.Template, updatedDefaults, parameterPolicies: null);
|
||||
}
|
||||
|
||||
return (attributeRoutePattern, resolvedRequiredValues ?? action.RouteValues);
|
||||
}
|
||||
|
||||
private RouteEndpoint CreateEndpoint(
|
||||
ActionDescriptor action,
|
||||
RoutePattern routePattern,
|
||||
string routeName,
|
||||
int order,
|
||||
RouteValueDictionary dataTokens,
|
||||
bool suppressLinkGeneration,
|
||||
bool suppressPathMatching,
|
||||
IReadOnlyList<Action<EndpointBuilder>> conventions)
|
||||
{
|
||||
RequestDelegate requestDelegate = (context) =>
|
||||
{
|
||||
var routeData = context.GetRouteData();
|
||||
|
||||
var actionContext = new ActionContext(context, routeData, action);
|
||||
|
||||
var invoker = _invokerFactory.CreateInvoker(actionContext);
|
||||
return invoker.InvokeAsync();
|
||||
};
|
||||
|
||||
var builder = new RouteEndpointBuilder(requestDelegate, routePattern, order)
|
||||
{
|
||||
DisplayName = action.DisplayName,
|
||||
};
|
||||
|
||||
// Add action metadata first so it has a low precedence
|
||||
if (action.EndpointMetadata != null)
|
||||
{
|
||||
foreach (var d in action.EndpointMetadata)
|
||||
{
|
||||
builder.Metadata.Add(d);
|
||||
}
|
||||
}
|
||||
|
||||
builder.Metadata.Add(action);
|
||||
|
||||
if (dataTokens != null)
|
||||
{
|
||||
builder.Metadata.Add(new DataTokensMetadata(dataTokens));
|
||||
}
|
||||
|
||||
builder.Metadata.Add(new RouteNameMetadata(routeName));
|
||||
|
||||
// Add filter descriptors to endpoint metadata
|
||||
if (action.FilterDescriptors != null && action.FilterDescriptors.Count > 0)
|
||||
{
|
||||
foreach (var filter in action.FilterDescriptors.OrderBy(f => f, FilterDescriptorOrderComparer.Comparer).Select(f => f.Filter))
|
||||
{
|
||||
builder.Metadata.Add(filter);
|
||||
}
|
||||
}
|
||||
|
||||
if (action.ActionConstraints != null && action.ActionConstraints.Count > 0)
|
||||
{
|
||||
// We explicitly convert a few types of action constraints into MatcherPolicy+Metadata
|
||||
// to better integrate with the DFA matcher.
|
||||
//
|
||||
// Other IActionConstraint data will trigger a back-compat path that can execute
|
||||
// action constraints.
|
||||
foreach (var actionConstraint in action.ActionConstraints)
|
||||
{
|
||||
if (actionConstraint is HttpMethodActionConstraint httpMethodActionConstraint &&
|
||||
!builder.Metadata.OfType<HttpMethodMetadata>().Any())
|
||||
{
|
||||
builder.Metadata.Add(new HttpMethodMetadata(httpMethodActionConstraint.HttpMethods));
|
||||
}
|
||||
else if (actionConstraint is ConsumesAttribute consumesAttribute &&
|
||||
!builder.Metadata.OfType<ConsumesMetadata>().Any())
|
||||
{
|
||||
builder.Metadata.Add(new ConsumesMetadata(consumesAttribute.ContentTypes.ToArray()));
|
||||
}
|
||||
else if (!builder.Metadata.Contains(actionConstraint))
|
||||
{
|
||||
// The constraint might have been added earlier, e.g. it is also a filter descriptor
|
||||
builder.Metadata.Add(actionConstraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (suppressLinkGeneration)
|
||||
{
|
||||
builder.Metadata.Add(new SuppressLinkGenerationMetadata());
|
||||
}
|
||||
|
||||
if (suppressPathMatching)
|
||||
{
|
||||
builder.Metadata.Add(new SuppressMatchingMetadata());
|
||||
}
|
||||
|
||||
for (var i = 0; i < conventions.Count; i++)
|
||||
{
|
||||
conventions[i](builder);
|
||||
}
|
||||
|
||||
return (RouteEndpoint)builder.Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
// 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.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||
{
|
||||
internal class ControllerActionEndpointDataSource : ActionEndpointDataSourceBase
|
||||
{
|
||||
private readonly ActionEndpointFactory _endpointFactory;
|
||||
private readonly List<ConventionalRouteEntry> _routes;
|
||||
|
||||
public ControllerActionEndpointDataSource(IActionDescriptorCollectionProvider actions, ActionEndpointFactory endpointFactory)
|
||||
: base(actions)
|
||||
{
|
||||
_endpointFactory = endpointFactory;
|
||||
|
||||
_routes = new List<ConventionalRouteEntry>();
|
||||
|
||||
// IMPORTANT: this needs to be the last thing we do in the constructor.
|
||||
// Change notifications can happen immediately!
|
||||
Subscribe();
|
||||
}
|
||||
|
||||
public void AddRoute(in ConventionalRouteEntry route)
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
_routes.Add(route);
|
||||
}
|
||||
}
|
||||
|
||||
protected override List<Endpoint> CreateEndpoints(IReadOnlyList<ActionDescriptor> actions, IReadOnlyList<Action<EndpointBuilder>> conventions)
|
||||
{
|
||||
var endpoints = new List<Endpoint>();
|
||||
for (var i = 0; i < actions.Count; i++)
|
||||
{
|
||||
if (actions[i] is ControllerActionDescriptor action)
|
||||
{
|
||||
_endpointFactory.AddEndpoints(endpoints, action, _routes, conventions);
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||
{
|
||||
internal readonly struct ConventionalRouteEntry
|
||||
{
|
||||
public readonly RoutePattern Pattern;
|
||||
public readonly string RouteName;
|
||||
public readonly RouteValueDictionary DataTokens;
|
||||
|
||||
public ConventionalRouteEntry(
|
||||
string routeName,
|
||||
string pattern,
|
||||
RouteValueDictionary defaults,
|
||||
IDictionary<string, object> constraints,
|
||||
RouteValueDictionary dataTokens)
|
||||
{
|
||||
RouteName = routeName;
|
||||
DataTokens = dataTokens;
|
||||
|
||||
try
|
||||
{
|
||||
// Data we parse from the pattern will be used to fill in the rest of the constraints or
|
||||
// defaults. The parser will throw for invalid routes.
|
||||
Pattern = RoutePatternFactory.Parse(pattern, defaults, constraints);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new RouteCreationException(string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
"An error occurred while creating the route with name '{0}' and pattern '{1}'.",
|
||||
routeName,
|
||||
pattern), exception);
|
||||
}
|
||||
}
|
||||
|
||||
public ConventionalRouteEntry(RoutePattern pattern, string routeName, RouteValueDictionary dataTokens)
|
||||
{
|
||||
if (pattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pattern));
|
||||
}
|
||||
|
||||
Pattern = pattern;
|
||||
RouteName = routeName;
|
||||
DataTokens = dataTokens;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,381 +0,0 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||
{
|
||||
internal class MvcEndpointDataSource : EndpointDataSource
|
||||
{
|
||||
private readonly IActionDescriptorCollectionProvider _actions;
|
||||
private readonly MvcEndpointInvokerFactory _invokerFactory;
|
||||
private readonly ParameterPolicyFactory _parameterPolicyFactory;
|
||||
private readonly RoutePatternTransformer _routePatternTransformer;
|
||||
|
||||
// The following are protected by this lock for WRITES only. This pattern is similar
|
||||
// to DefaultActionDescriptorChangeProvider - see comments there for details on
|
||||
// all of the threading behaviors.
|
||||
private readonly object _lock = new object();
|
||||
private List<Endpoint> _endpoints;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private IChangeToken _changeToken;
|
||||
|
||||
public MvcEndpointDataSource(
|
||||
IActionDescriptorCollectionProvider actions,
|
||||
MvcEndpointInvokerFactory invokerFactory,
|
||||
ParameterPolicyFactory parameterPolicyFactory,
|
||||
RoutePatternTransformer routePatternTransformer)
|
||||
{
|
||||
_actions = actions;
|
||||
_invokerFactory = invokerFactory;
|
||||
_parameterPolicyFactory = parameterPolicyFactory;
|
||||
_routePatternTransformer = routePatternTransformer;
|
||||
|
||||
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!
|
||||
//
|
||||
// It's possible for someone to override the collection provider without providing
|
||||
// change notifications. If that's the case we won't process changes.
|
||||
if (actions is ActionDescriptorCollectionProvider collectionProviderWithChangeToken)
|
||||
{
|
||||
ChangeToken.OnChange(
|
||||
() => collectionProviderWithChangeToken.GetChangeToken(),
|
||||
UpdateEndpoints);
|
||||
}
|
||||
}
|
||||
|
||||
public List<MvcEndpointInfo> ConventionalEndpointInfos { get; }
|
||||
|
||||
public List<Func<ActionDescriptor, DefaultEndpointConventionBuilder>> AttributeRoutingConventionResolvers { get; }
|
||||
|
||||
public override IReadOnlyList<Endpoint> Endpoints
|
||||
{
|
||||
get
|
||||
{
|
||||
Initialize();
|
||||
Debug.Assert(_changeToken != null);
|
||||
Debug.Assert(_endpoints != null);
|
||||
return _endpoints;
|
||||
}
|
||||
}
|
||||
|
||||
public override IChangeToken GetChangeToken()
|
||||
{
|
||||
Initialize();
|
||||
Debug.Assert(_changeToken != null);
|
||||
Debug.Assert(_endpoints != null);
|
||||
return _changeToken;
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (_endpoints == null)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_endpoints == null)
|
||||
{
|
||||
UpdateEndpoints();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateEndpoints()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var endpoints = new List<Endpoint>();
|
||||
|
||||
foreach (var action in _actions.ActionDescriptors.Items)
|
||||
{
|
||||
if (action.AttributeRouteInfo == null)
|
||||
{
|
||||
// In traditional conventional routing setup, the routes defined by a user have a static order
|
||||
// defined by how they are added into the list. We would like to maintain the same order when building
|
||||
// up the endpoints too.
|
||||
//
|
||||
// Start with an order of '1' for conventional routes as attribute routes have a default order of '0'.
|
||||
// This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world.
|
||||
var conventionalRouteOrder = 1;
|
||||
|
||||
// Check each of the conventional patterns to see if the action would be reachable.
|
||||
// If the action and pattern are compatible then create an endpoint with action
|
||||
// route values on the pattern.
|
||||
foreach (var endpointInfo in ConventionalEndpointInfos)
|
||||
{
|
||||
// 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
|
||||
var updatedRoutePattern = _routePatternTransformer.SubstituteRequiredValues(endpointInfo.ParsedPattern, action.RouteValues);
|
||||
|
||||
if (updatedRoutePattern == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var endpoint = CreateEndpoint(
|
||||
action,
|
||||
updatedRoutePattern,
|
||||
endpointInfo.Name,
|
||||
conventionalRouteOrder++,
|
||||
endpointInfo.DataTokens,
|
||||
false,
|
||||
false,
|
||||
endpointInfo.Conventions);
|
||||
endpoints.Add(endpoint);
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
// Modify the route and required values to ensure required values can be successfully subsituted.
|
||||
// Subsitituting required values into an attribute route pattern should always succeed.
|
||||
var (resolvedRoutePattern, resolvedRouteValues) = ResolveDefaultsAndRequiredValues(action, attributeRoutePattern);
|
||||
|
||||
var updatedRoutePattern = _routePatternTransformer.SubstituteRequiredValues(resolvedRoutePattern, resolvedRouteValues);
|
||||
|
||||
if (updatedRoutePattern == null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to update route pattern with required values.");
|
||||
}
|
||||
|
||||
var endpoint = CreateEndpoint(
|
||||
action,
|
||||
updatedRoutePattern,
|
||||
action.AttributeRouteInfo.Name,
|
||||
action.AttributeRouteInfo.Order,
|
||||
dataTokens: null,
|
||||
action.AttributeRouteInfo.SuppressLinkGeneration,
|
||||
action.AttributeRouteInfo.SuppressPathMatching,
|
||||
conventionBuilder.Conventions);
|
||||
endpoints.Add(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
// See comments in DefaultActionDescriptorCollectionProvider. These steps are done
|
||||
// in a specific order to ensure callers always see a consistent state.
|
||||
|
||||
// Step 1 - capture old token
|
||||
var oldCancellationTokenSource = _cancellationTokenSource;
|
||||
|
||||
// Step 2 - update endpoints
|
||||
_endpoints = endpoints;
|
||||
|
||||
// Step 3 - create new change token
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_changeToken = new CancellationChangeToken(_cancellationTokenSource.Token);
|
||||
|
||||
// Step 4 - trigger old token
|
||||
oldCancellationTokenSource?.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private static (RoutePattern resolvedRoutePattern, IDictionary<string, string> resolvedRequiredValues) ResolveDefaultsAndRequiredValues(ActionDescriptor action, RoutePattern attributeRoutePattern)
|
||||
{
|
||||
RouteValueDictionary updatedDefaults = null;
|
||||
IDictionary<string, string> resolvedRequiredValues = null;
|
||||
|
||||
foreach (var routeValue in action.RouteValues)
|
||||
{
|
||||
var parameter = attributeRoutePattern.GetParameter(routeValue.Key);
|
||||
|
||||
if (!RouteValueEqualityComparer.Default.Equals(routeValue.Value, string.Empty))
|
||||
{
|
||||
if (parameter == null)
|
||||
{
|
||||
// The attribute route has a required value with no matching parameter
|
||||
// Add the required values without a parameter as a default
|
||||
// e.g.
|
||||
// Template: "Login/{action}"
|
||||
// Required values: { controller = "Login", action = "Index" }
|
||||
// Updated defaults: { controller = "Login" }
|
||||
|
||||
if (updatedDefaults == null)
|
||||
{
|
||||
updatedDefaults = new RouteValueDictionary(attributeRoutePattern.Defaults);
|
||||
}
|
||||
|
||||
updatedDefaults[routeValue.Key] = routeValue.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parameter != null)
|
||||
{
|
||||
// The attribute route has a null or empty required value with a matching parameter
|
||||
// Remove the required value from the route
|
||||
|
||||
if (resolvedRequiredValues == null)
|
||||
{
|
||||
resolvedRequiredValues = new Dictionary<string, string>(action.RouteValues);
|
||||
}
|
||||
|
||||
resolvedRequiredValues.Remove(parameter.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (updatedDefaults != null)
|
||||
{
|
||||
attributeRoutePattern = RoutePatternFactory.Parse(action.AttributeRouteInfo.Template, updatedDefaults, parameterPolicies: null);
|
||||
}
|
||||
|
||||
return (attributeRoutePattern, resolvedRequiredValues ?? action.RouteValues);
|
||||
}
|
||||
|
||||
private DefaultEndpointConventionBuilder ResolveActionConventionBuilder(ActionDescriptor action)
|
||||
{
|
||||
foreach (var filter in AttributeRoutingConventionResolvers)
|
||||
{
|
||||
var conventionBuilder = filter(action);
|
||||
if (conventionBuilder != null)
|
||||
{
|
||||
return conventionBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private RouteEndpoint CreateEndpoint(
|
||||
ActionDescriptor action,
|
||||
RoutePattern routePattern,
|
||||
string routeName,
|
||||
int order,
|
||||
RouteValueDictionary dataTokens,
|
||||
bool suppressLinkGeneration,
|
||||
bool suppressPathMatching,
|
||||
List<Action<EndpointBuilder>> conventions)
|
||||
{
|
||||
RequestDelegate requestDelegate = (context) =>
|
||||
{
|
||||
var routeData = context.GetRouteData();
|
||||
|
||||
var actionContext = new ActionContext(context, routeData, action);
|
||||
|
||||
var invoker = _invokerFactory.CreateInvoker(actionContext);
|
||||
return invoker.InvokeAsync();
|
||||
};
|
||||
|
||||
var endpointBuilder = new RouteEndpointBuilder(requestDelegate, routePattern, order);
|
||||
AddEndpointMetadata(
|
||||
endpointBuilder.Metadata,
|
||||
action,
|
||||
routeName,
|
||||
dataTokens,
|
||||
suppressLinkGeneration,
|
||||
suppressPathMatching);
|
||||
|
||||
endpointBuilder.DisplayName = action.DisplayName;
|
||||
|
||||
// REVIEW: When should conventions be run
|
||||
// Metadata should have lower precedence that data source metadata
|
||||
if (conventions != null)
|
||||
{
|
||||
foreach (var convention in conventions)
|
||||
{
|
||||
convention(endpointBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
return (RouteEndpoint)endpointBuilder.Build();
|
||||
}
|
||||
|
||||
private static void AddEndpointMetadata(
|
||||
IList<object> metadata,
|
||||
ActionDescriptor action,
|
||||
string routeName,
|
||||
RouteValueDictionary dataTokens,
|
||||
bool suppressLinkGeneration,
|
||||
bool suppressPathMatching)
|
||||
{
|
||||
// Add action metadata first so it has a low precedence
|
||||
if (action.EndpointMetadata != null)
|
||||
{
|
||||
foreach (var d in action.EndpointMetadata)
|
||||
{
|
||||
metadata.Add(d);
|
||||
}
|
||||
}
|
||||
|
||||
metadata.Add(action);
|
||||
|
||||
if (dataTokens != null)
|
||||
{
|
||||
metadata.Add(new DataTokensMetadata(dataTokens));
|
||||
}
|
||||
|
||||
metadata.Add(new RouteNameMetadata(routeName));
|
||||
|
||||
// Add filter descriptors to endpoint metadata
|
||||
if (action.FilterDescriptors != null && action.FilterDescriptors.Count > 0)
|
||||
{
|
||||
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)
|
||||
{
|
||||
// We explicitly convert a few types of action constraints into MatcherPolicy+Metadata
|
||||
// to better integrate with the DFA matcher.
|
||||
//
|
||||
// Other IActionConstraint data will trigger a back-compat path that can execute
|
||||
// action constraints.
|
||||
foreach (var actionConstraint in action.ActionConstraints)
|
||||
{
|
||||
if (actionConstraint is HttpMethodActionConstraint httpMethodActionConstraint &&
|
||||
!metadata.OfType<HttpMethodMetadata>().Any())
|
||||
{
|
||||
metadata.Add(new HttpMethodMetadata(httpMethodActionConstraint.HttpMethods));
|
||||
}
|
||||
else if (actionConstraint is ConsumesAttribute consumesAttribute &&
|
||||
!metadata.OfType<ConsumesMetadata>().Any())
|
||||
{
|
||||
metadata.Add(new ConsumesMetadata(consumesAttribute.ContentTypes.ToArray()));
|
||||
}
|
||||
else if (!metadata.Contains(actionConstraint))
|
||||
{
|
||||
// The constraint might have been added earlier, e.g. it is also a filter descriptor
|
||||
metadata.Add(actionConstraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (suppressLinkGeneration)
|
||||
{
|
||||
metadata.Add(new SuppressLinkGenerationMetadata());
|
||||
}
|
||||
|
||||
if (suppressPathMatching)
|
||||
{
|
||||
metadata.Add(new SuppressMatchingMetadata());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
// 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.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
|
|
@ -11,32 +11,40 @@ namespace Microsoft.AspNetCore.Builder
|
|||
{
|
||||
public static class RazorPagesEndpointRouteBuilderExtensions
|
||||
{
|
||||
public static IEndpointConventionBuilder MapRazorPages(
|
||||
this IEndpointRouteBuilder routeBuilder,
|
||||
string basePath = null)
|
||||
public static IEndpointConventionBuilder MapRazorPages(this IEndpointRouteBuilder routes)
|
||||
{
|
||||
var mvcEndpointDataSource = routeBuilder.DataSources.OfType<MvcEndpointDataSource>().FirstOrDefault();
|
||||
|
||||
if (mvcEndpointDataSource == null)
|
||||
if (routes == null)
|
||||
{
|
||||
mvcEndpointDataSource = routeBuilder.ServiceProvider.GetRequiredService<MvcEndpointDataSource>();
|
||||
routeBuilder.DataSources.Add(mvcEndpointDataSource);
|
||||
throw new ArgumentNullException(nameof(routes));
|
||||
}
|
||||
|
||||
var conventionBuilder = new DefaultEndpointConventionBuilder();
|
||||
EnsureRazorPagesServices(routes);
|
||||
|
||||
mvcEndpointDataSource.AttributeRoutingConventionResolvers.Add(actionDescriptor =>
|
||||
return GetOrCreateDataSource(routes);
|
||||
}
|
||||
|
||||
private static void EnsureRazorPagesServices(IEndpointRouteBuilder routes)
|
||||
{
|
||||
var marker = routes.ServiceProvider.GetService<PageActionEndpointDataSource>();
|
||||
if (marker == null)
|
||||
{
|
||||
if (actionDescriptor is PageActionDescriptor pageActionDescriptor)
|
||||
{
|
||||
// TODO: Filter pages by path
|
||||
return conventionBuilder;
|
||||
}
|
||||
throw new InvalidOperationException(Mvc.Core.Resources.FormatUnableToFindServices(
|
||||
nameof(IServiceCollection),
|
||||
"AddMvc",
|
||||
"ConfigureServices(...)"));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
private static PageActionEndpointDataSource GetOrCreateDataSource(IEndpointRouteBuilder routes)
|
||||
{
|
||||
var dataSource = routes.DataSources.OfType<PageActionEndpointDataSource>().FirstOrDefault();
|
||||
if (dataSource == null)
|
||||
{
|
||||
dataSource = routes.ServiceProvider.GetRequiredService<PageActionEndpointDataSource>();
|
||||
routes.DataSources.Add(dataSource);
|
||||
}
|
||||
|
||||
return conventionBuilder;
|
||||
return dataSource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
ServiceDescriptor.Singleton<IActionDescriptorProvider, PageActionDescriptorProvider>());
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Singleton<IPageRouteModelProvider, CompiledPageRouteModelProvider>());
|
||||
services.TryAddSingleton<PageActionEndpointDataSource>();
|
||||
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Singleton<IPageApplicationModelProvider, DefaultPageApplicationModelProvider>());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
// 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.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||
{
|
||||
internal class PageActionEndpointDataSource : ActionEndpointDataSourceBase
|
||||
{
|
||||
private readonly ActionEndpointFactory _endpointFactory;
|
||||
|
||||
public PageActionEndpointDataSource(IActionDescriptorCollectionProvider actions, ActionEndpointFactory endpointFactory)
|
||||
: base(actions)
|
||||
{
|
||||
_endpointFactory = endpointFactory;
|
||||
|
||||
// IMPORTANT: this needs to be the last thing we do in the constructor.
|
||||
// Change notifications can happen immediately!
|
||||
Subscribe();
|
||||
}
|
||||
|
||||
protected override List<Endpoint> CreateEndpoints(IReadOnlyList<ActionDescriptor> actions, IReadOnlyList<Action<EndpointBuilder>> conventions)
|
||||
{
|
||||
var endpoints = new List<Endpoint>();
|
||||
for (var i = 0; i < actions.Count; i++)
|
||||
{
|
||||
if (actions[i] is PageActionDescriptor action)
|
||||
{
|
||||
_endpointFactory.AddEndpoints(endpoints, action, Array.Empty<ConventionalRouteEntry>(), conventions);
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2,9 +2,7 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Builder.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
|
|
@ -64,7 +62,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Builder
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void UseMvc_EndpointRoutingEnabled_NoEndpointInfos()
|
||||
public void UseMvc_EndpointRoutingEnabled_AddsRoute()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
|
|
@ -85,11 +83,11 @@ namespace Microsoft.AspNetCore.Mvc.Core.Builder
|
|||
var routeOptions = appBuilder.ApplicationServices
|
||||
.GetRequiredService<IOptions<RouteOptions>>();
|
||||
|
||||
var mvcEndpointDataSource = (MvcEndpointDataSource)Assert.Single(routeOptions.Value.EndpointDataSources, ds => ds is MvcEndpointDataSource);
|
||||
var dataSource = (ActionEndpointDataSource)Assert.Single(routeOptions.Value.EndpointDataSources, ds => ds is ActionEndpointDataSource);
|
||||
|
||||
var endpointInfo = Assert.Single(mvcEndpointDataSource.ConventionalEndpointInfos);
|
||||
Assert.Equal("default", endpointInfo.Name);
|
||||
Assert.Equal("{controller=Home}/{action=Index}/{id?}", endpointInfo.Pattern);
|
||||
var endpointInfo = Assert.Single(dataSource.Routes);
|
||||
Assert.Equal("default", endpointInfo.RouteName);
|
||||
Assert.Equal("{controller=Home}/{action=Index}/{id?}", endpointInfo.Pattern.RawText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,173 @@
|
|||
// 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.Threading;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||
{
|
||||
// There are some basic sanity tests here for the details of how actions
|
||||
// are turned into endpoints. See ActionEndpointFactoryTest for detailed tests.
|
||||
public abstract class ActionEndpointDataSourceBaseTest
|
||||
{
|
||||
[Fact]
|
||||
public void Endpoints_AccessParameters_InitializedFromProvider()
|
||||
{
|
||||
// Arrange
|
||||
var actions = new Mock<IActionDescriptorCollectionProvider>();
|
||||
actions.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List<ActionDescriptor>
|
||||
{
|
||||
CreateActionDescriptor(new { Name = "Value", }, "/Template!"),
|
||||
}, 0));
|
||||
|
||||
var dataSource = CreateDataSource(actions.Object);
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
var endpoint = Assert.IsType<RouteEndpoint>(Assert.Single(endpoints));
|
||||
Assert.Equal("Value", endpoint.RoutePattern.RequiredValues["Name"]);
|
||||
Assert.Equal("/Template!", endpoint.RoutePattern.RawText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoints_CalledMultipleTimes_ReturnsSameInstance()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptorCollectionProviderMock = new Mock<IActionDescriptorCollectionProvider>();
|
||||
actionDescriptorCollectionProviderMock
|
||||
.Setup(m => m.ActionDescriptors)
|
||||
.Returns(new ActionDescriptorCollection(new[]
|
||||
{
|
||||
CreateActionDescriptor(new { controller = "TestController", action = "TestAction" }, "/test"),
|
||||
}, version: 0));
|
||||
|
||||
var dataSource = CreateDataSource(actionDescriptorCollectionProviderMock.Object);
|
||||
|
||||
// Act
|
||||
var endpoints1 = dataSource.Endpoints;
|
||||
var endpoints2 = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
endpoints1,
|
||||
(e) => Assert.Equal("/test", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
|
||||
Assert.Same(endpoints1, endpoints2);
|
||||
|
||||
actionDescriptorCollectionProviderMock.VerifyGet(m => m.ActionDescriptors, Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoints_ChangeTokenTriggered_EndpointsRecreated()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptorCollectionProviderMock = new Mock<ActionDescriptorCollectionProvider>();
|
||||
actionDescriptorCollectionProviderMock
|
||||
.Setup(m => m.ActionDescriptors)
|
||||
.Returns(new ActionDescriptorCollection(new[]
|
||||
{
|
||||
CreateActionDescriptor(new { controller = "TestController", action = "TestAction" }, "/test")
|
||||
}, version: 0));
|
||||
|
||||
CancellationTokenSource cts = null;
|
||||
actionDescriptorCollectionProviderMock
|
||||
.Setup(m => m.GetChangeToken())
|
||||
.Returns(() =>
|
||||
{
|
||||
cts = new CancellationTokenSource();
|
||||
var changeToken = new CancellationChangeToken(cts.Token);
|
||||
|
||||
return changeToken;
|
||||
});
|
||||
|
||||
var dataSource = CreateDataSource(actionDescriptorCollectionProviderMock.Object);
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
Assert.Collection(endpoints,
|
||||
(e) =>
|
||||
{
|
||||
var routePattern = Assert.IsType<RouteEndpoint>(e).RoutePattern;
|
||||
Assert.Equal("/test", routePattern.RawText);
|
||||
Assert.Equal("TestController", routePattern.RequiredValues["controller"]);
|
||||
Assert.Equal("TestAction", routePattern.RequiredValues["action"]);
|
||||
});
|
||||
|
||||
actionDescriptorCollectionProviderMock
|
||||
.Setup(m => m.ActionDescriptors)
|
||||
.Returns(new ActionDescriptorCollection(new[]
|
||||
{
|
||||
CreateActionDescriptor(new { controller = "NewTestController", action = "NewTestAction" }, "/test")
|
||||
}, version: 1));
|
||||
|
||||
cts.Cancel();
|
||||
|
||||
// Assert
|
||||
var newEndpoints = dataSource.Endpoints;
|
||||
|
||||
Assert.NotSame(endpoints, newEndpoints);
|
||||
Assert.Collection(newEndpoints,
|
||||
(e) =>
|
||||
{
|
||||
var routePattern = Assert.IsType<RouteEndpoint>(e).RoutePattern;
|
||||
Assert.Equal("/test", routePattern.RawText);
|
||||
Assert.Equal("NewTestController", routePattern.RequiredValues["controller"]);
|
||||
Assert.Equal("NewTestAction", routePattern.RequiredValues["action"]);
|
||||
});
|
||||
}
|
||||
|
||||
protected private ActionEndpointDataSourceBase CreateDataSource(IActionDescriptorCollectionProvider actions = null)
|
||||
{
|
||||
if (actions == null)
|
||||
{
|
||||
actions = new DefaultActionDescriptorCollectionProvider(
|
||||
Array.Empty<IActionDescriptorProvider>(),
|
||||
Array.Empty<IActionDescriptorChangeProvider>());
|
||||
}
|
||||
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton(actions);
|
||||
|
||||
var routeOptionsSetup = new MvcCoreRouteOptionsSetup();
|
||||
services.Configure<RouteOptions>(routeOptionsSetup.Configure);
|
||||
services.AddRouting(options =>
|
||||
{
|
||||
options.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
|
||||
});
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
var endpointFactory = new ActionEndpointFactory(
|
||||
serviceProvider.GetRequiredService<RoutePatternTransformer>(),
|
||||
new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty<IActionInvokerProvider>())));
|
||||
|
||||
return CreateDataSource(actions, endpointFactory);
|
||||
}
|
||||
|
||||
protected private abstract ActionEndpointDataSourceBase CreateDataSource(IActionDescriptorCollectionProvider actions, ActionEndpointFactory endpointFactory);
|
||||
|
||||
private class UpperCaseParameterTransform : IOutboundParameterTransformer
|
||||
{
|
||||
public string TransformOutbound(object value)
|
||||
{
|
||||
return value?.ToString().ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract ActionDescriptor CreateActionDescriptor(
|
||||
object values,
|
||||
string pattern = null,
|
||||
IList<object> metadata = null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
// 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 Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||
{
|
||||
public class ActionEndpointDataSourceTest : ActionEndpointDataSourceBaseTest
|
||||
{
|
||||
[Fact]
|
||||
public void Endpoints_MultipledActions_MultipleRoutes()
|
||||
{
|
||||
// Arrange
|
||||
var actions = new List<ActionDescriptor>
|
||||
{
|
||||
new ActionDescriptor
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo()
|
||||
{
|
||||
Template = "/test",
|
||||
},
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "action", "Test" },
|
||||
{ "controller", "Test" },
|
||||
},
|
||||
},
|
||||
new ActionDescriptor
|
||||
{
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "action", "Index" },
|
||||
{ "controller", "Home" },
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider
|
||||
.Setup(m => m.ActionDescriptors)
|
||||
.Returns(new ActionDescriptorCollection(actions, 0));
|
||||
|
||||
var dataSource = (ActionEndpointDataSource)CreateDataSource(mockDescriptorProvider.Object);
|
||||
dataSource.AddRoute(new ConventionalRouteEntry("1", "/1/{controller}/{action}/{id?}", null, null, null));
|
||||
dataSource.AddRoute(new ConventionalRouteEntry("2", "/2/{controller}/{action}/{id?}", null, null, null));
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
endpoints.Cast<RouteEndpoint>().OrderBy(e => e.RoutePattern.RawText),
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("/1/{controller}/{action}/{id?}", e.RoutePattern.RawText);
|
||||
Assert.Same(actions[1], e.Metadata.GetMetadata<ActionDescriptor>());
|
||||
},
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("/2/{controller}/{action}/{id?}", e.RoutePattern.RawText);
|
||||
Assert.Same(actions[1], e.Metadata.GetMetadata<ActionDescriptor>());
|
||||
},
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("/test", e.RoutePattern.RawText);
|
||||
Assert.Same(actions[0], e.Metadata.GetMetadata<ActionDescriptor>());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoints_AppliesConventions()
|
||||
{
|
||||
// Arrange
|
||||
var actions = new List<ActionDescriptor>
|
||||
{
|
||||
new ActionDescriptor
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo()
|
||||
{
|
||||
Template = "/test",
|
||||
},
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "action", "Test" },
|
||||
{ "controller", "Test" },
|
||||
},
|
||||
},
|
||||
new ActionDescriptor
|
||||
{
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "action", "Index" },
|
||||
{ "controller", "Home" },
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(actions, 0));
|
||||
|
||||
var dataSource = (ActionEndpointDataSource)CreateDataSource(mockDescriptorProvider.Object);
|
||||
dataSource.AddRoute(new ConventionalRouteEntry("1", "/1/{controller}/{action}/{id?}", null, null, null));
|
||||
dataSource.AddRoute(new ConventionalRouteEntry("2", "/2/{controller}/{action}/{id?}", null, null, null));
|
||||
|
||||
dataSource.Add((b) =>
|
||||
{
|
||||
b.Metadata.Add("Hi there");
|
||||
});
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
endpoints.OfType<RouteEndpoint>().OrderBy(e => e.RoutePattern.RawText),
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("/1/{controller}/{action}/{id?}", e.RoutePattern.RawText);
|
||||
Assert.Same(actions[1], e.Metadata.GetMetadata<ActionDescriptor>());
|
||||
Assert.Equal("Hi there", e.Metadata.GetMetadata<string>());
|
||||
},
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("/2/{controller}/{action}/{id?}", e.RoutePattern.RawText);
|
||||
Assert.Same(actions[1], e.Metadata.GetMetadata<ActionDescriptor>());
|
||||
Assert.Equal("Hi there", e.Metadata.GetMetadata<string>());
|
||||
},
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("/test", e.RoutePattern.RawText);
|
||||
Assert.Same(actions[0], e.Metadata.GetMetadata<ActionDescriptor>());
|
||||
Assert.Equal("Hi there", e.Metadata.GetMetadata<string>());
|
||||
});
|
||||
}
|
||||
|
||||
private protected override ActionEndpointDataSourceBase CreateDataSource(IActionDescriptorCollectionProvider actions, ActionEndpointFactory endpointFactory)
|
||||
{
|
||||
return new ActionEndpointDataSource(actions, endpointFactory);
|
||||
}
|
||||
|
||||
protected override ActionDescriptor CreateActionDescriptor(
|
||||
object values,
|
||||
string pattern = null,
|
||||
IList<object> metadata = null)
|
||||
{
|
||||
var action = new ActionDescriptor();
|
||||
|
||||
foreach (var kvp in new RouteValueDictionary(values))
|
||||
{
|
||||
action.RouteValues[kvp.Key] = kvp.Value?.ToString();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(pattern))
|
||||
{
|
||||
action.AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Name = "test",
|
||||
Template = pattern,
|
||||
};
|
||||
}
|
||||
|
||||
action.EndpointMetadata = metadata;
|
||||
return action;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,498 @@
|
|||
// 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.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||
{
|
||||
public class ActionEndpointFactoryTest
|
||||
{
|
||||
public ActionEndpointFactoryTest()
|
||||
{
|
||||
var serviceCollection = new ServiceCollection();
|
||||
|
||||
var routeOptionsSetup = new MvcCoreRouteOptionsSetup();
|
||||
serviceCollection.Configure<RouteOptions>(routeOptionsSetup.Configure);
|
||||
serviceCollection.AddRouting(options =>
|
||||
{
|
||||
options.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
|
||||
});
|
||||
|
||||
Services = serviceCollection.BuildServiceProvider();
|
||||
InvokerFactory = new Mock<IActionInvokerFactory>(MockBehavior.Strict);
|
||||
Factory = new ActionEndpointFactory(
|
||||
Services.GetRequiredService<RoutePatternTransformer>(),
|
||||
new MvcEndpointInvokerFactory(InvokerFactory.Object));
|
||||
}
|
||||
|
||||
internal ActionEndpointFactory Factory { get; }
|
||||
|
||||
internal Mock<IActionInvokerFactory> InvokerFactory { get; }
|
||||
|
||||
internal IServiceProvider Services { get; }
|
||||
|
||||
[Fact]
|
||||
public async Task AddEndpoints_AttributeRouted_UsesActionInvoker()
|
||||
{
|
||||
// Arrange
|
||||
var values = new
|
||||
{
|
||||
action = "Test",
|
||||
controller = "Test",
|
||||
page = (string)null,
|
||||
};
|
||||
|
||||
var action = CreateActionDescriptor(values, pattern: "/Test");
|
||||
|
||||
var endpointFeature = new EndpointSelectorContext
|
||||
{
|
||||
RouteValues = new RouteValueDictionary()
|
||||
};
|
||||
|
||||
var featureCollection = new FeatureCollection();
|
||||
featureCollection.Set<IEndpointFeature>(endpointFeature);
|
||||
featureCollection.Set<IRouteValuesFeature>(endpointFeature);
|
||||
featureCollection.Set<IRoutingFeature>(endpointFeature);
|
||||
|
||||
var httpContextMock = new Mock<HttpContext>();
|
||||
httpContextMock.Setup(m => m.Features).Returns(featureCollection);
|
||||
|
||||
var actionInvokerCalled = false;
|
||||
var actionInvokerMock = new Mock<IActionInvoker>();
|
||||
actionInvokerMock.Setup(m => m.InvokeAsync()).Returns(() =>
|
||||
{
|
||||
actionInvokerCalled = true;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
InvokerFactory
|
||||
.Setup(m => m.CreateInvoker(It.IsAny<ActionContext>()))
|
||||
.Returns(actionInvokerMock.Object);
|
||||
|
||||
// Act
|
||||
var endpoint = CreateAttributeRoutedEndpoint(action);
|
||||
|
||||
// Assert
|
||||
await endpoint.RequestDelegate(httpContextMock.Object);
|
||||
|
||||
Assert.True(actionInvokerCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddEndpoints_ConventionalRouted_UsesActionInvoker()
|
||||
{
|
||||
// Arrange
|
||||
var values = new
|
||||
{
|
||||
action = "Test",
|
||||
controller = "Test",
|
||||
page = (string)null,
|
||||
};
|
||||
|
||||
var action = CreateActionDescriptor(values);
|
||||
|
||||
var endpointFeature = new EndpointSelectorContext
|
||||
{
|
||||
RouteValues = new RouteValueDictionary()
|
||||
};
|
||||
|
||||
var featureCollection = new FeatureCollection();
|
||||
featureCollection.Set<IEndpointFeature>(endpointFeature);
|
||||
featureCollection.Set<IRouteValuesFeature>(endpointFeature);
|
||||
featureCollection.Set<IRoutingFeature>(endpointFeature);
|
||||
|
||||
var httpContextMock = new Mock<HttpContext>();
|
||||
httpContextMock.Setup(m => m.Features).Returns(featureCollection);
|
||||
|
||||
var actionInvokerCalled = false;
|
||||
var actionInvokerMock = new Mock<IActionInvoker>();
|
||||
actionInvokerMock.Setup(m => m.InvokeAsync()).Returns(() =>
|
||||
{
|
||||
actionInvokerCalled = true;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
InvokerFactory
|
||||
.Setup(m => m.CreateInvoker(It.IsAny<ActionContext>()))
|
||||
.Returns(actionInvokerMock.Object);
|
||||
|
||||
// Act
|
||||
var endpoint = CreateConventionalRoutedEndpoint(action, "{controller}/{action}");
|
||||
|
||||
// Assert
|
||||
await endpoint.RequestDelegate(httpContextMock.Object);
|
||||
|
||||
Assert.True(actionInvokerCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEndpoints_ConventionalRouted_WithEmptyRouteName_CreatesMetadataWithEmptyRouteName()
|
||||
{
|
||||
// Arrange
|
||||
var values = new { controller = "TestController", action = "TestAction", page = (string)null };
|
||||
var action = CreateActionDescriptor(values);
|
||||
var route = CreateRoute(routeName: string.Empty, pattern: "{controller}/{action}");
|
||||
|
||||
// Act
|
||||
var endpoint = CreateConventionalRoutedEndpoint(action, route);
|
||||
|
||||
// Assert
|
||||
var routeNameMetadata = endpoint.Metadata.GetMetadata<IRouteNameMetadata>();
|
||||
Assert.NotNull(routeNameMetadata);
|
||||
Assert.Equal(string.Empty, routeNameMetadata.RouteName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEndpoints_ConventionalRouted_ContainsParameterWithNullRequiredRouteValue_NoEndpointCreated()
|
||||
{
|
||||
// Arrange
|
||||
var values = new { controller = "TestController", action = "TestAction", page = (string)null };
|
||||
var action = CreateActionDescriptor(values);
|
||||
var route = CreateRoute(
|
||||
routeName: "Test",
|
||||
pattern: "{controller}/{action}/{page}",
|
||||
defaults: new RouteValueDictionary(new { action = "TestAction" }));
|
||||
|
||||
// Act
|
||||
var endpoints = CreateConventionalRoutedEndpoints(action, route);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(endpoints);
|
||||
}
|
||||
|
||||
// area, controller, action and page are special, but not hardcoded. Actions can define custom required
|
||||
// route values. This has been used successfully for localization, versioning and similar schemes. We should
|
||||
// be able to replace custom route values too.
|
||||
[Fact]
|
||||
public void AddEndpoints_ConventionalRouted_NonReservedRequiredValue_WithNoCorresponding_TemplateParameter_DoesNotProduceEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var values = new { controller = "home", action = "index", locale = "en-NZ" };
|
||||
var action = CreateActionDescriptor(values);
|
||||
var route = CreateRoute(routeName: "test", pattern: "{controller}/{action}");
|
||||
|
||||
// Act
|
||||
var endpoints = CreateConventionalRoutedEndpoints(action, route);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(endpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEndpoints_ConventionalRouted_NonReservedRequiredValue_WithCorresponding_TemplateParameter_ProducesEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var values = new { controller = "home", action = "index", locale = "en-NZ" };
|
||||
var action = CreateActionDescriptor(values);
|
||||
var route = CreateRoute(routeName: "test", pattern: "{locale}/{controller}/{action}");
|
||||
|
||||
// Act
|
||||
var endpoints = CreateConventionalRoutedEndpoints(action, route);
|
||||
|
||||
// Assert
|
||||
Assert.Single(endpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEndpoints_ConventionalRouted_NonAreaRouteForAreaAction_DoesNotProduceEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var values = new { controller = "TestController", action = "TestAction", area = "admin", page = (string)null };
|
||||
var action = CreateActionDescriptor(values);
|
||||
var route = CreateRoute(routeName: "test", pattern: "{controller}/{action}");
|
||||
|
||||
// Act
|
||||
var endpoints = CreateConventionalRoutedEndpoints(action, route);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(endpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEndpoints_ConventionalRouted_AreaRouteForNonAreaAction_DoesNotProduceEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var values = new { controller = "TestController", action = "TestAction", area = (string)null, page = (string)null };
|
||||
var action = CreateActionDescriptor(values);
|
||||
var route = CreateRoute(routeName: "test", pattern: "{area}/{controller}/{action}");
|
||||
|
||||
// Act
|
||||
var endpoints = CreateConventionalRoutedEndpoints(action, route);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(endpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEndpoints_ConventionalRouted_RequiredValues_DoesNotMatchParameterDefaults_CreatesEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var values = new { controller = "TestController", action = "TestAction", page = (string)null };
|
||||
var action = CreateActionDescriptor(values);
|
||||
var route = CreateRoute(
|
||||
routeName: "test",
|
||||
pattern: "{controller}/{action}/{id?}",
|
||||
defaults: new RouteValueDictionary(new { controller = "TestController", action = "TestAction1" }));
|
||||
|
||||
// Act
|
||||
var endpoint = CreateConventionalRoutedEndpoint(action, route);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("{controller}/{action}/{id?}", endpoint.RoutePattern.RawText);
|
||||
Assert.Equal("TestController", endpoint.RoutePattern.RequiredValues["controller"]);
|
||||
Assert.Equal("TestAction", endpoint.RoutePattern.RequiredValues["action"]);
|
||||
Assert.Equal("TestController", endpoint.RoutePattern.Defaults["controller"]);
|
||||
Assert.False(endpoint.RoutePattern.Defaults.ContainsKey("action"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEndpoints_ConventionalRouted_RequiredValues_DoesNotMatchNonParameterDefaults_DoesNotProduceEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var values = new { controller = "TestController", action = "TestAction", page = (string)null };
|
||||
var action = CreateActionDescriptor(values);
|
||||
var route = CreateRoute(
|
||||
routeName: "test",
|
||||
pattern: "/Blog/{*slug}",
|
||||
defaults: new RouteValueDictionary(new { controller = "TestController", action = "TestAction1" }));
|
||||
|
||||
// Act
|
||||
var endpoints = CreateConventionalRoutedEndpoints(action, route);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(endpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEndpoints_ConventionalRouted_AttributeRoutes_DefaultDifferentCaseFromRouteValue_UseDefaultCase()
|
||||
{
|
||||
// Arrange
|
||||
var values = new { controller = "TestController", action = "TestAction", page = (string)null };
|
||||
var action = CreateActionDescriptor(values, "{controller}/{action=TESTACTION}/{id?}");
|
||||
// Act
|
||||
var endpoint = CreateAttributeRoutedEndpoint(action);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("{controller}/{action=TESTACTION}/{id?}", endpoint.RoutePattern.RawText);
|
||||
Assert.Equal("TESTACTION", endpoint.RoutePattern.Defaults["action"]);
|
||||
Assert.Equal(0, endpoint.Order);
|
||||
Assert.Equal("TestAction", endpoint.RoutePattern.RequiredValues["action"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEndpoints_ConventionalRouted_RequiredValueWithNoCorrespondingParameter_DoesNotProduceEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var values = new { area = "admin", controller = "home", action = "index" };
|
||||
var action = CreateActionDescriptor(values);
|
||||
var route = CreateRoute(routeName: "test", pattern: "{controller}/{action}");
|
||||
|
||||
// Act
|
||||
var endpoints = CreateConventionalRoutedEndpoints(action, route);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(endpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEndpoints_AttributeRouted_ContainsParameterWithNullRequiredRouteValue_EndpointCreated()
|
||||
{
|
||||
// Arrange
|
||||
var values = new { controller = "TestController", action = "TestAction", page = (string)null };
|
||||
var action = CreateActionDescriptor(values, "{controller}/{action}/{page}");
|
||||
|
||||
// Act
|
||||
var endpoint = CreateAttributeRoutedEndpoint(action);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("{controller}/{action}/{page}", endpoint.RoutePattern.RawText);
|
||||
Assert.Equal("TestController", endpoint.RoutePattern.RequiredValues["controller"]);
|
||||
Assert.Equal("TestAction", endpoint.RoutePattern.RequiredValues["action"]);
|
||||
Assert.False(endpoint.RoutePattern.RequiredValues.ContainsKey("page"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEndpoints_ConventionalRouted_WithMatchingConstraint_CreatesEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var values = new { controller = "TestController", action = "TestAction1", page = (string)null };
|
||||
var action = CreateActionDescriptor(values);
|
||||
var route = CreateRoute(
|
||||
routeName: "test",
|
||||
pattern: "{controller}/{action}",
|
||||
constraints: new RouteValueDictionary(new { action = "(TestAction1|TestAction2)" }));
|
||||
|
||||
// Act
|
||||
var endpoints = CreateConventionalRoutedEndpoints(action, route);
|
||||
|
||||
// Assert
|
||||
Assert.Single(endpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEndpoints_ConventionalRouted_WithNotMatchingConstraint_DoesNotCreateEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var values = new { controller = "TestController", action = "TestAction", page = (string)null };
|
||||
var action = CreateActionDescriptor(values);
|
||||
var route = CreateRoute(
|
||||
routeName: "test",
|
||||
pattern: "{controller}/{action}",
|
||||
constraints: new RouteValueDictionary(new { action = "(TestAction1|TestAction2)" }));
|
||||
|
||||
// Act
|
||||
var endpoints = CreateConventionalRoutedEndpoints(action, route);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(endpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEndpoints_ConventionalRouted_StaticallyDefinedOrder_IsMaintained()
|
||||
{
|
||||
// Arrange
|
||||
var values = new { controller = "Home", action = "Index", page = (string)null };
|
||||
var action = CreateActionDescriptor(values);
|
||||
var routes = new[]
|
||||
{
|
||||
CreateRoute(routeName: "test1", pattern: "{controller}/{action}/{id?}"),
|
||||
CreateRoute(routeName: "test2", pattern: "named/{controller}/{action}/{id?}"),
|
||||
};
|
||||
|
||||
// Act
|
||||
var endpoints = CreateConventionalRoutedEndpoints(action, routes);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
endpoints,
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||
Assert.Equal("{controller}/{action}/{id?}", matcherEndpoint.RoutePattern.RawText);
|
||||
Assert.Equal("Index", matcherEndpoint.RoutePattern.RequiredValues["action"]);
|
||||
Assert.Equal("Home", matcherEndpoint.RoutePattern.RequiredValues["controller"]);
|
||||
Assert.Equal(1, matcherEndpoint.Order);
|
||||
},
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||
Assert.Equal("named/{controller}/{action}/{id?}", matcherEndpoint.RoutePattern.RawText);
|
||||
Assert.Equal("Index", matcherEndpoint.RoutePattern.RequiredValues["action"]);
|
||||
Assert.Equal("Home", matcherEndpoint.RoutePattern.RequiredValues["controller"]);
|
||||
Assert.Equal(2, matcherEndpoint.Order);
|
||||
});
|
||||
}
|
||||
|
||||
private RouteEndpoint CreateAttributeRoutedEndpoint(ActionDescriptor action)
|
||||
{
|
||||
var endpoints = new List<Endpoint>();
|
||||
Factory.AddEndpoints(endpoints, action, Array.Empty<ConventionalRouteEntry>(), Array.Empty<Action<EndpointBuilder>>());
|
||||
return Assert.IsType<RouteEndpoint>(Assert.Single(endpoints));
|
||||
}
|
||||
|
||||
private RouteEndpoint CreateConventionalRoutedEndpoint(ActionDescriptor action, string template)
|
||||
{
|
||||
return CreateConventionalRoutedEndpoint(action, new ConventionalRouteEntry(routeName: null, template, null, null, null));
|
||||
}
|
||||
|
||||
private RouteEndpoint CreateConventionalRoutedEndpoint(ActionDescriptor action, ConventionalRouteEntry route)
|
||||
{
|
||||
Assert.NotNull(action.RouteValues);
|
||||
|
||||
var endpoints = new List<Endpoint>();
|
||||
Factory.AddEndpoints(endpoints, action, new[] { route, }, Array.Empty<Action<EndpointBuilder>>());
|
||||
var endpoint = Assert.IsType<RouteEndpoint>(Assert.Single(endpoints));
|
||||
|
||||
// This should be true for all conventional-routed actions.
|
||||
AssertIsSubset(new RouteValueDictionary(action.RouteValues), endpoint.RoutePattern.RequiredValues);
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
private IReadOnlyList<RouteEndpoint> CreateConventionalRoutedEndpoints(ActionDescriptor action, ConventionalRouteEntry route)
|
||||
{
|
||||
return CreateConventionalRoutedEndpoints(action, new[] { route, });
|
||||
}
|
||||
|
||||
private IReadOnlyList<RouteEndpoint> CreateConventionalRoutedEndpoints(ActionDescriptor action, IReadOnlyList<ConventionalRouteEntry> routes)
|
||||
{
|
||||
var endpoints = new List<Endpoint>();
|
||||
Factory.AddEndpoints(endpoints, action, routes, Array.Empty<Action<EndpointBuilder>>());
|
||||
return endpoints.Cast<RouteEndpoint>().ToList();
|
||||
}
|
||||
|
||||
private ConventionalRouteEntry CreateRoute(
|
||||
string routeName,
|
||||
string pattern,
|
||||
RouteValueDictionary defaults = null,
|
||||
IDictionary<string, object> constraints = null,
|
||||
RouteValueDictionary dataTokens = null)
|
||||
{
|
||||
return new ConventionalRouteEntry(routeName, pattern, defaults, constraints, dataTokens);
|
||||
}
|
||||
|
||||
private ActionDescriptor CreateActionDescriptor(
|
||||
object requiredValues,
|
||||
string pattern = null,
|
||||
IList<object> metadata = null)
|
||||
{
|
||||
var actionDescriptor = new ActionDescriptor();
|
||||
var routeValues = new RouteValueDictionary(requiredValues);
|
||||
foreach (var kvp in routeValues)
|
||||
{
|
||||
actionDescriptor.RouteValues[kvp.Key] = kvp.Value?.ToString();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(pattern))
|
||||
{
|
||||
actionDescriptor.AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Name = pattern,
|
||||
Template = pattern
|
||||
};
|
||||
}
|
||||
|
||||
actionDescriptor.EndpointMetadata = metadata;
|
||||
return actionDescriptor;
|
||||
}
|
||||
|
||||
private void AssertIsSubset(
|
||||
IReadOnlyDictionary<string, object> subset,
|
||||
IReadOnlyDictionary<string, object> fullSet)
|
||||
{
|
||||
foreach (var subsetPair in subset)
|
||||
{
|
||||
var isPresent = fullSet.TryGetValue(subsetPair.Key, out var fullSetPairValue);
|
||||
Assert.True(isPresent);
|
||||
Assert.Equal(subsetPair.Value, fullSetPairValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertMatchingSuppressed(Endpoint endpoint, bool suppressed)
|
||||
{
|
||||
var isEndpointSuppressed = endpoint.Metadata.GetMetadata<ISuppressMatchingMetadata>()?.SuppressMatching ?? false;
|
||||
Assert.Equal(suppressed, isEndpointSuppressed);
|
||||
}
|
||||
|
||||
private class UpperCaseParameterTransform : IOutboundParameterTransformer
|
||||
{
|
||||
public string TransformOutbound(object value)
|
||||
{
|
||||
return value?.ToString().ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
// 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 Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||
{
|
||||
public class ControllerActionEndpointDataSourceTest : ActionEndpointDataSourceBaseTest
|
||||
{
|
||||
[Fact]
|
||||
public void Endpoints_Ignores_NonController()
|
||||
{
|
||||
// Arrange
|
||||
var actions = new List<ActionDescriptor>
|
||||
{
|
||||
new ActionDescriptor
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo()
|
||||
{
|
||||
Template = "/test",
|
||||
},
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "action", "Test" },
|
||||
{ "controller", "Test" },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(actions, 0));
|
||||
|
||||
var dataSource = (ControllerActionEndpointDataSource)CreateDataSource(mockDescriptorProvider.Object);
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Empty(endpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoints_MultipledActions_MultipleRoutes()
|
||||
{
|
||||
// Arrange
|
||||
var actions = new List<ActionDescriptor>
|
||||
{
|
||||
new ControllerActionDescriptor
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo()
|
||||
{
|
||||
Template = "/test",
|
||||
},
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "action", "Test" },
|
||||
{ "controller", "Test" },
|
||||
},
|
||||
},
|
||||
new ControllerActionDescriptor
|
||||
{
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "action", "Index" },
|
||||
{ "controller", "Home" },
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider
|
||||
.Setup(m => m.ActionDescriptors)
|
||||
.Returns(new ActionDescriptorCollection(actions, 0));
|
||||
|
||||
var dataSource = (ControllerActionEndpointDataSource)CreateDataSource(mockDescriptorProvider.Object);
|
||||
dataSource.AddRoute(new ConventionalRouteEntry("1", "/1/{controller}/{action}/{id?}", null, null, null));
|
||||
dataSource.AddRoute(new ConventionalRouteEntry("2", "/2/{controller}/{action}/{id?}", null, null, null));
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
endpoints.Cast<RouteEndpoint>().OrderBy(e => e.RoutePattern.RawText),
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("/1/{controller}/{action}/{id?}", e.RoutePattern.RawText);
|
||||
Assert.Same(actions[1], e.Metadata.GetMetadata<ActionDescriptor>());
|
||||
},
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("/2/{controller}/{action}/{id?}", e.RoutePattern.RawText);
|
||||
Assert.Same(actions[1], e.Metadata.GetMetadata<ActionDescriptor>());
|
||||
},
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("/test", e.RoutePattern.RawText);
|
||||
Assert.Same(actions[0], e.Metadata.GetMetadata<ActionDescriptor>());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoints_AppliesConventions()
|
||||
{
|
||||
// Arrange
|
||||
var actions = new List<ActionDescriptor>
|
||||
{
|
||||
new ControllerActionDescriptor
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo()
|
||||
{
|
||||
Template = "/test",
|
||||
},
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "action", "Test" },
|
||||
{ "controller", "Test" },
|
||||
},
|
||||
},
|
||||
new ControllerActionDescriptor
|
||||
{
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "action", "Index" },
|
||||
{ "controller", "Home" },
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(actions, 0));
|
||||
|
||||
var dataSource = (ControllerActionEndpointDataSource)CreateDataSource(mockDescriptorProvider.Object);
|
||||
dataSource.AddRoute(new ConventionalRouteEntry("1", "/1/{controller}/{action}/{id?}", null, null, null));
|
||||
dataSource.AddRoute(new ConventionalRouteEntry("2", "/2/{controller}/{action}/{id?}", null, null, null));
|
||||
|
||||
dataSource.Add((b) =>
|
||||
{
|
||||
b.Metadata.Add("Hi there");
|
||||
});
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
endpoints.OfType<RouteEndpoint>().OrderBy(e => e.RoutePattern.RawText),
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("/1/{controller}/{action}/{id?}", e.RoutePattern.RawText);
|
||||
Assert.Same(actions[1], e.Metadata.GetMetadata<ActionDescriptor>());
|
||||
Assert.Equal("Hi there", e.Metadata.GetMetadata<string>());
|
||||
},
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("/2/{controller}/{action}/{id?}", e.RoutePattern.RawText);
|
||||
Assert.Same(actions[1], e.Metadata.GetMetadata<ActionDescriptor>());
|
||||
Assert.Equal("Hi there", e.Metadata.GetMetadata<string>());
|
||||
},
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("/test", e.RoutePattern.RawText);
|
||||
Assert.Same(actions[0], e.Metadata.GetMetadata<ActionDescriptor>());
|
||||
Assert.Equal("Hi there", e.Metadata.GetMetadata<string>());
|
||||
});
|
||||
}
|
||||
|
||||
private protected override ActionEndpointDataSourceBase CreateDataSource(IActionDescriptorCollectionProvider actions, ActionEndpointFactory endpointFactory)
|
||||
{
|
||||
return new ControllerActionEndpointDataSource(actions, endpointFactory);
|
||||
}
|
||||
|
||||
protected override ActionDescriptor CreateActionDescriptor(
|
||||
object values,
|
||||
string pattern = null,
|
||||
IList<object> metadata = null)
|
||||
{
|
||||
var action = new ControllerActionDescriptor();
|
||||
|
||||
foreach (var kvp in new RouteValueDictionary(values))
|
||||
{
|
||||
action.RouteValues[kvp.Key] = kvp.Value?.ToString();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(pattern))
|
||||
{
|
||||
action.AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Name = "test",
|
||||
Template = pattern,
|
||||
};
|
||||
}
|
||||
|
||||
action.EndpointMetadata = metadata;
|
||||
return action;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,806 +0,0 @@
|
|||
// 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;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||
{
|
||||
public class MvcEndpointDataSourceTests
|
||||
{
|
||||
[Fact]
|
||||
public void Endpoints_AccessParameters_InitializedFromProvider()
|
||||
{
|
||||
// Arrange
|
||||
var routeValue = "Value";
|
||||
var requiredValues = new Dictionary<string, string>
|
||||
{
|
||||
["Name"] = routeValue
|
||||
};
|
||||
var displayName = "DisplayName!";
|
||||
var order = 1;
|
||||
var template = "/Template!";
|
||||
var filterDescriptor = new FilterDescriptor(new ControllerActionFilter(), 1);
|
||||
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List<ActionDescriptor>
|
||||
{
|
||||
new ActionDescriptor
|
||||
{
|
||||
RouteValues = requiredValues,
|
||||
DisplayName = displayName,
|
||||
AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Order = order,
|
||||
Template = template
|
||||
},
|
||||
FilterDescriptors = new List<FilterDescriptor>
|
||||
{
|
||||
filterDescriptor
|
||||
}
|
||||
}
|
||||
}, 0));
|
||||
|
||||
var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object);
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
var endpoint = Assert.Single(endpoints);
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||
|
||||
var endpointValue = matcherEndpoint.RoutePattern.RequiredValues["Name"];
|
||||
Assert.Equal(routeValue, endpointValue);
|
||||
|
||||
Assert.Equal(displayName, matcherEndpoint.DisplayName);
|
||||
Assert.Equal(order, matcherEndpoint.Order);
|
||||
Assert.Equal("/Template!", matcherEndpoint.RoutePattern.RawText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Endpoints_InvokeReturnedEndpoint_ActionInvokerProviderCalled()
|
||||
{
|
||||
// Arrange
|
||||
var endpointFeature = new EndpointSelectorContext
|
||||
{
|
||||
RouteValues = new RouteValueDictionary()
|
||||
};
|
||||
|
||||
var featureCollection = new FeatureCollection();
|
||||
featureCollection.Set<IEndpointFeature>(endpointFeature);
|
||||
featureCollection.Set<IRouteValuesFeature>(endpointFeature);
|
||||
featureCollection.Set<IRoutingFeature>(endpointFeature);
|
||||
|
||||
var httpContextMock = new Mock<HttpContext>();
|
||||
httpContextMock.Setup(m => m.Features).Returns(featureCollection);
|
||||
|
||||
var descriptorProviderMock = new Mock<IActionDescriptorCollectionProvider>();
|
||||
descriptorProviderMock.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List<ActionDescriptor>
|
||||
{
|
||||
new ActionDescriptor
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Template = string.Empty
|
||||
},
|
||||
FilterDescriptors = new List<FilterDescriptor>()
|
||||
}
|
||||
}, 0));
|
||||
|
||||
var actionInvokerCalled = false;
|
||||
var actionInvokerMock = new Mock<IActionInvoker>();
|
||||
actionInvokerMock.Setup(m => m.InvokeAsync()).Returns(() =>
|
||||
{
|
||||
actionInvokerCalled = true;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
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 endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
var endpoint = Assert.Single(endpoints);
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||
|
||||
await matcherEndpoint.RequestDelegate(httpContextMock.Object);
|
||||
|
||||
Assert.True(actionInvokerCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoints_SingleAction_ConventionalRoute_ContainsParameterWithNullRequiredRouteValue()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||
new { controller = "TestController", action = "TestAction", page = (string)null });
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
||||
string.Empty,
|
||||
"{controller}/{action}/{page}",
|
||||
new RouteValueDictionary(new { action = "TestAction" })));
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Empty(endpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoints_SingleAction_AttributeRoute_ContainsParameterWithNullRequiredRouteValue()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||
"{controller}/{action}/{page}",
|
||||
new { controller = "TestController", action = "TestAction", page = (string)null });
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Collection(endpoints.Cast<RouteEndpoint>(),
|
||||
(e) =>
|
||||
{
|
||||
Assert.Equal("{controller}/{action}/{page}", e.RoutePattern.RawText);
|
||||
Assert.Equal("TestController", e.RoutePattern.RequiredValues["controller"]);
|
||||
Assert.Equal("TestAction", e.RoutePattern.RequiredValues["action"]);
|
||||
Assert.False(e.RoutePattern.RequiredValues.ContainsKey("page"));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoints_CalledMultipleTimes_ReturnsSameInstance()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptorCollectionProviderMock = new Mock<IActionDescriptorCollectionProvider>();
|
||||
actionDescriptorCollectionProviderMock
|
||||
.Setup(m => m.ActionDescriptors)
|
||||
.Returns(new ActionDescriptorCollection(new[]
|
||||
{
|
||||
CreateActionDescriptor(new { controller = "TestController", action = "TestAction" })
|
||||
}, version: 0));
|
||||
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollectionProviderMock.Object);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
||||
string.Empty,
|
||||
"{controller}/{action}",
|
||||
new RouteValueDictionary(new { action = "TestAction" })));
|
||||
|
||||
// Act
|
||||
var endpoints1 = dataSource.Endpoints;
|
||||
var endpoints2 = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Collection(endpoints1,
|
||||
(e) => Assert.Equal("{controller}/{action}", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
|
||||
Assert.Same(endpoints1, endpoints2);
|
||||
|
||||
actionDescriptorCollectionProviderMock.VerifyGet(m => m.ActionDescriptors, Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoints_ChangeTokenTriggered_EndpointsRecreated()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptorCollectionProviderMock = new Mock<ActionDescriptorCollectionProvider>();
|
||||
actionDescriptorCollectionProviderMock
|
||||
.Setup(m => m.ActionDescriptors)
|
||||
.Returns(new ActionDescriptorCollection(new[]
|
||||
{
|
||||
CreateActionDescriptor(new { controller = "TestController", action = "TestAction" })
|
||||
}, version: 0));
|
||||
|
||||
CancellationTokenSource cts = null;
|
||||
actionDescriptorCollectionProviderMock
|
||||
.Setup(m => m.GetChangeToken())
|
||||
.Returns(() =>
|
||||
{
|
||||
cts = new CancellationTokenSource();
|
||||
var changeToken = new CancellationChangeToken(cts.Token);
|
||||
|
||||
return changeToken;
|
||||
});
|
||||
|
||||
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollectionProviderMock.Object);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
||||
string.Empty,
|
||||
"{controller}/{action}",
|
||||
new RouteValueDictionary(new { action = "TestAction" })));
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
Assert.Collection(endpoints,
|
||||
(e) =>
|
||||
{
|
||||
var routePattern = Assert.IsType<RouteEndpoint>(e).RoutePattern;
|
||||
Assert.Equal("{controller}/{action}", routePattern.RawText);
|
||||
Assert.Equal("TestController", routePattern.RequiredValues["controller"]);
|
||||
Assert.Equal("TestAction", routePattern.RequiredValues["action"]);
|
||||
});
|
||||
|
||||
actionDescriptorCollectionProviderMock
|
||||
.Setup(m => m.ActionDescriptors)
|
||||
.Returns(new ActionDescriptorCollection(new[]
|
||||
{
|
||||
CreateActionDescriptor(new { controller = "NewTestController", action = "NewTestAction" })
|
||||
}, version: 1));
|
||||
|
||||
cts.Cancel();
|
||||
|
||||
// Assert
|
||||
var newEndpoints = dataSource.Endpoints;
|
||||
|
||||
Assert.NotSame(endpoints, newEndpoints);
|
||||
Assert.Collection(newEndpoints,
|
||||
(e) =>
|
||||
{
|
||||
var routePattern = Assert.IsType<RouteEndpoint>(e).RoutePattern;
|
||||
Assert.Equal("{controller}/{action}", routePattern.RawText);
|
||||
Assert.Equal("NewTestController", routePattern.RequiredValues["controller"]);
|
||||
Assert.Equal("NewTestAction", routePattern.RequiredValues["action"]);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoints_MultipleActions_WithActionConstraint()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||
new { controller = "TestController", action = "TestAction" },
|
||||
new { controller = "TestController", action = "TestAction1" },
|
||||
new { controller = "TestController", action = "TestAction2" });
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
||||
string.Empty,
|
||||
"{controller}/{action}",
|
||||
constraints: new RouteValueDictionary(new { action = "(TestAction1|TestAction2)" })));
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Collection(endpoints.Cast<RouteEndpoint>(),
|
||||
(e) =>
|
||||
{
|
||||
Assert.Equal("{controller}/{action}", e.RoutePattern.RawText);
|
||||
Assert.Equal("TestController", e.RoutePattern.RequiredValues["controller"]);
|
||||
Assert.Equal("TestAction1", e.RoutePattern.RequiredValues["action"]);
|
||||
},
|
||||
(e) =>
|
||||
{
|
||||
Assert.Equal("{controller}/{action}", e.RoutePattern.RawText);
|
||||
Assert.Equal("TestController", e.RoutePattern.RequiredValues["controller"]);
|
||||
Assert.Equal("TestAction2", e.RoutePattern.RequiredValues["action"]);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoints_ConventionalRoute_WithEmptyRouteName_CreatesMetadataWithEmptyRouteName()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||
new { controller = "Home", action = "Index" });
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(
|
||||
CreateEndpointInfo(string.Empty, "named/{controller}/{action}/{id?}"));
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
var endpoint = Assert.Single(endpoints);
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||
var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteNameMetadata>();
|
||||
Assert.NotNull(routeNameMetadata);
|
||||
Assert.Equal(string.Empty, routeNameMetadata.RouteName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoints_CanCreateMultipleEndpoints_WithSameRouteName()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||
new { controller = "Home", action = "Index" },
|
||||
new { controller = "Products", action = "Details" });
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(
|
||||
CreateEndpointInfo("namedRoute", "named/{controller}/{action}/{id?}"));
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
endpoints,
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||
var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteNameMetadata>();
|
||||
Assert.NotNull(routeNameMetadata);
|
||||
Assert.Equal("namedRoute", routeNameMetadata.RouteName);
|
||||
Assert.Equal("named/{controller}/{action}/{id?}", matcherEndpoint.RoutePattern.RawText);
|
||||
},
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||
var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteNameMetadata>();
|
||||
Assert.NotNull(routeNameMetadata);
|
||||
Assert.Equal("namedRoute", routeNameMetadata.RouteName);
|
||||
Assert.Equal("named/{controller}/{action}/{id?}", matcherEndpoint.RoutePattern.RawText);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoints_ConventionalRoutes_StaticallyDefinedOrder_IsMaintained()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||
new { controller = "Home", action = "Index" },
|
||||
new { controller = "Products", action = "Details" });
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
||||
name: string.Empty,
|
||||
template: "{controller}/{action}/{id?}"));
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
||||
name: "namedRoute",
|
||||
"named/{controller}/{action}/{id?}"));
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
endpoints,
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||
Assert.Equal("{controller}/{action}/{id?}", matcherEndpoint.RoutePattern.RawText);
|
||||
Assert.Equal("Index", matcherEndpoint.RoutePattern.RequiredValues["action"]);
|
||||
Assert.Equal("Home", matcherEndpoint.RoutePattern.RequiredValues["controller"]);
|
||||
Assert.Equal(1, matcherEndpoint.Order);
|
||||
},
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||
Assert.Equal("named/{controller}/{action}/{id?}", matcherEndpoint.RoutePattern.RawText);
|
||||
Assert.Equal("Index", matcherEndpoint.RoutePattern.RequiredValues["action"]);
|
||||
Assert.Equal("Home", matcherEndpoint.RoutePattern.RequiredValues["controller"]);
|
||||
Assert.Equal(2, matcherEndpoint.Order);
|
||||
},
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||
Assert.Equal("{controller}/{action}/{id?}", matcherEndpoint.RoutePattern.RawText);
|
||||
Assert.Equal("Details", matcherEndpoint.RoutePattern.RequiredValues["action"]);
|
||||
Assert.Equal("Products", matcherEndpoint.RoutePattern.RequiredValues["controller"]);
|
||||
Assert.Equal(1, matcherEndpoint.Order);
|
||||
},
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||
Assert.Equal("named/{controller}/{action}/{id?}", matcherEndpoint.RoutePattern.RawText);
|
||||
Assert.Equal("Details", matcherEndpoint.RoutePattern.RequiredValues["action"]);
|
||||
Assert.Equal("Products", matcherEndpoint.RoutePattern.RequiredValues["controller"]);
|
||||
Assert.Equal(2, matcherEndpoint.Order);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequiredValue_WithNoCorresponding_TemplateParameter_DoesNotProduceEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var requiredValues = new RouteValueDictionary(new { area = "admin", controller = "home", action = "index" });
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues);
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{controller}/{action}"));
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Empty(endpoints);
|
||||
}
|
||||
|
||||
// area, controller, action and page are special, but not hardcoded. Actions can define custom required
|
||||
// route values. This has been used successfully for localization, versioning and similar schemes. We should
|
||||
// be able to replace custom route values too.
|
||||
[Fact]
|
||||
public void NonReservedRequiredValue_WithNoCorresponding_TemplateParameter_DoesNotProduceEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var action1 = new RouteValueDictionary(new { controller = "home", action = "index", locale = "en-NZ" });
|
||||
var action2 = new RouteValueDictionary(new { controller = "home", action = "about", locale = "en-CA" });
|
||||
var action3 = new RouteValueDictionary(new { controller = "home", action = "index", locale = (string)null });
|
||||
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(action1, action2, action3);
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
|
||||
// Adding a localized route a non-localized route
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{locale}/{controller}/{action}"));
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{controller}/{action}"));
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
endpoints.Cast<RouteEndpoint>(),
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("{locale}/{controller}/{action}", e.RoutePattern.RawText);
|
||||
Assert.Equal("home", e.RoutePattern.RequiredValues["controller"]);
|
||||
Assert.Equal("index", e.RoutePattern.RequiredValues["action"]);
|
||||
Assert.Equal("en-NZ", e.RoutePattern.RequiredValues["locale"]);
|
||||
},
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("{locale}/{controller}/{action}", e.RoutePattern.RawText);
|
||||
Assert.Equal("home", e.RoutePattern.RequiredValues["controller"]);
|
||||
Assert.Equal("about", e.RoutePattern.RequiredValues["action"]);
|
||||
Assert.Equal("en-CA", e.RoutePattern.RequiredValues["locale"]);
|
||||
},
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("{controller}/{action}", e.RoutePattern.RawText);
|
||||
Assert.Equal("home", e.RoutePattern.RequiredValues["controller"]);
|
||||
Assert.Equal("index", e.RoutePattern.RequiredValues["action"]);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TemplateParameter_WithNoDefaultOrRequiredValue_DoesNotProduceEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var requiredValues = new RouteValueDictionary(new { controller = "home", action = "index", area = (string)null });
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues);
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{area}/{controller}/{action}"));
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Empty(endpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TemplateParameter_WithDefaultValue_AndNullRequiredValue_DoesNotProduceEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var requiredValues = new RouteValueDictionary(new { area = (string)null, controller = "home", action = "index" });
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues);
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{area=admin}/{controller}/{action}"));
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Empty(endpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TemplateParameter_WithNullRequiredValue_DoesNotProduceEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var requiredValues = new RouteValueDictionary(new { area = (string)null, controller = "home", action = "index" });
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues);
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{area}/{controller}/{action}"));
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Empty(endpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoDefaultValues_RequiredValues_UsedToCreateDefaultValues()
|
||||
{
|
||||
// Arrange
|
||||
var requiredValues = new RouteValueDictionary(new { controller = "Foo", action = "Bar" });
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: requiredValues);
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{controller}/{action}"));
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
var endpoint = Assert.Single(endpoints);
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||
Assert.Equal("{controller}/{action}", matcherEndpoint.RoutePattern.RawText);
|
||||
AssertIsSubset(requiredValues, matcherEndpoint.RoutePattern.RequiredValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequiredValues_NotPresent_InDefaultValuesOrParameter_EndpointNotCreated()
|
||||
{
|
||||
// Arrange
|
||||
var requiredValues = new RouteValueDictionary(
|
||||
new { controller = "Foo", action = "Bar", subarea = "test" });
|
||||
var expectedDefaults = requiredValues;
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: requiredValues);
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(
|
||||
CreateEndpointInfo(string.Empty, "{controller=Home}/{action=Index}"));
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Empty(endpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequiredValues_DoesNotMatchParameterDefaults_Included()
|
||||
{
|
||||
// Arrange
|
||||
var action = new RouteValueDictionary(
|
||||
new { controller = "Foo", action = "Baz", }); // Doesn't match default
|
||||
var expectedDefaults = new RouteValueDictionary(
|
||||
new { controller = "Foo", action = "Baz", });
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(action);
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(
|
||||
CreateEndpointInfo(
|
||||
string.Empty,
|
||||
"{controller}/{action}/{id?}",
|
||||
defaults: new RouteValueDictionary(new { controller = "Foo", action = "Bar" })));
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
var endpoint = Assert.Single(endpoints);
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||
Assert.Equal("{controller}/{action}/{id?}", matcherEndpoint.RoutePattern.RawText);
|
||||
Assert.Equal("Foo", matcherEndpoint.RoutePattern.RequiredValues["controller"]);
|
||||
Assert.Equal("Baz", matcherEndpoint.RoutePattern.RequiredValues["action"]);
|
||||
Assert.Equal("Foo", matcherEndpoint.RoutePattern.Defaults["controller"]);
|
||||
Assert.False(matcherEndpoint.RoutePattern.Defaults.ContainsKey("action"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequiredValues_DoesNotMatchNonParameterDefaults_FilteredOut()
|
||||
{
|
||||
// Arrange
|
||||
var action1 = new RouteValueDictionary(
|
||||
new { controller = "Foo", action = "Bar", });
|
||||
var action2 = new RouteValueDictionary(
|
||||
new { controller = "Foo", action = "Baz", }); // Doesn't match default
|
||||
var expectedDefaults = new RouteValueDictionary(
|
||||
new { controller = "Foo", action = "Bar", });
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(action1, action2);
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
dataSource.ConventionalEndpointInfos.Add(
|
||||
CreateEndpointInfo(
|
||||
string.Empty,
|
||||
"Blog/{*slug}",
|
||||
defaults: new RouteValueDictionary(new { controller = "Foo", action = "Bar" })));
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
var endpoint = Assert.Single(endpoints);
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||
Assert.Equal("Blog/{*slug}", matcherEndpoint.RoutePattern.RawText);
|
||||
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoints_ConventionalRoutes_DefaultValuesAndCatchAll_EndpointInfoDefaultsNotModified()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||
new { controller = "TestController", action = "TestAction" });
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
|
||||
var endpointInfo = CreateEndpointInfo(
|
||||
name: string.Empty,
|
||||
defaults: new RouteValueDictionary(),
|
||||
template: "{controller=TestController}/{action=TestAction}/{id=17}/{**catchAll}");
|
||||
dataSource.ConventionalEndpointInfos.Add(endpointInfo);
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Empty(endpointInfo.Defaults);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoints_AttributeRoutes_DefaultDifferentCaseFromRouteValue_UseDefaultCase()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptorCollection = GetActionDescriptorCollection(
|
||||
"{controller}/{action=TESTACTION}/{id?}",
|
||||
new { controller = "TestController", action = "TestAction" });
|
||||
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
endpoints,
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||
Assert.Equal("{controller}/{action=TESTACTION}/{id?}", matcherEndpoint.RoutePattern.RawText);
|
||||
Assert.Equal("TESTACTION", matcherEndpoint.RoutePattern.Defaults["action"]);
|
||||
Assert.Equal(0, matcherEndpoint.Order);
|
||||
|
||||
Assert.Equal("TestAction", matcherEndpoint.RoutePattern.RequiredValues["action"]);
|
||||
});
|
||||
}
|
||||
|
||||
private MvcEndpointDataSource CreateMvcEndpointDataSource(
|
||||
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider = null,
|
||||
MvcEndpointInvokerFactory mvcEndpointInvokerFactory = null)
|
||||
{
|
||||
if (actionDescriptorCollectionProvider == null)
|
||||
{
|
||||
actionDescriptorCollectionProvider = new DefaultActionDescriptorCollectionProvider(
|
||||
Array.Empty<IActionDescriptorProvider>(),
|
||||
Array.Empty<IActionDescriptorChangeProvider>());
|
||||
}
|
||||
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton(actionDescriptorCollectionProvider);
|
||||
|
||||
var routeOptionsSetup = new MvcCoreRouteOptionsSetup();
|
||||
services.Configure<RouteOptions>(routeOptionsSetup.Configure);
|
||||
services.AddRouting(options =>
|
||||
{
|
||||
options.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
|
||||
});
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
var dataSource = new MvcEndpointDataSource(
|
||||
actionDescriptorCollectionProvider,
|
||||
mvcEndpointInvokerFactory ?? new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty<IActionInvokerProvider>())),
|
||||
serviceProvider.GetRequiredService<ParameterPolicyFactory>(),
|
||||
serviceProvider.GetRequiredService<RoutePatternTransformer>());
|
||||
|
||||
var defaultEndpointConventionBuilder = new DefaultEndpointConventionBuilder();
|
||||
dataSource.AttributeRoutingConventionResolvers.Add((actionDescriptor) =>
|
||||
{
|
||||
return defaultEndpointConventionBuilder;
|
||||
});
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
private class UpperCaseParameterTransform : IOutboundParameterTransformer
|
||||
{
|
||||
public string TransformOutbound(object value)
|
||||
{
|
||||
return value?.ToString().ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
private MvcEndpointInfo CreateEndpointInfo(
|
||||
string name,
|
||||
string template,
|
||||
RouteValueDictionary defaults = null,
|
||||
IDictionary<string, object> constraints = null,
|
||||
RouteValueDictionary dataTokens = null,
|
||||
IServiceProvider serviceProvider = null)
|
||||
{
|
||||
if (serviceProvider == null)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddRouting();
|
||||
services.AddSingleton(typeof(UpperCaseParameterTransform), new UpperCaseParameterTransform());
|
||||
|
||||
var routeOptionsSetup = new MvcCoreRouteOptionsSetup();
|
||||
services.Configure<RouteOptions>(routeOptionsSetup.Configure);
|
||||
services.Configure<RouteOptions>(options =>
|
||||
{
|
||||
options.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
|
||||
});
|
||||
|
||||
serviceProvider = services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
var parameterPolicyFactory = serviceProvider.GetRequiredService<ParameterPolicyFactory>();
|
||||
return new MvcEndpointInfo(name, template, defaults, constraints, dataTokens, parameterPolicyFactory);
|
||||
}
|
||||
|
||||
private IActionDescriptorCollectionProvider GetActionDescriptorCollection(params object[] requiredValues)
|
||||
{
|
||||
return GetActionDescriptorCollection(attributeRouteTemplate: null, requiredValues);
|
||||
}
|
||||
|
||||
private IActionDescriptorCollectionProvider GetActionDescriptorCollection(string attributeRouteTemplate, params object[] requiredValues)
|
||||
{
|
||||
var actionDescriptors = new List<ActionDescriptor>();
|
||||
foreach (var requiredValue in requiredValues)
|
||||
{
|
||||
actionDescriptors.Add(CreateActionDescriptor(requiredValue, attributeRouteTemplate));
|
||||
}
|
||||
|
||||
return GetActionDescriptorCollection(actionDescriptors.ToArray());
|
||||
}
|
||||
|
||||
private IActionDescriptorCollectionProvider GetActionDescriptorCollection(params ActionDescriptor[] actionDescriptors)
|
||||
{
|
||||
var actionDescriptorCollectionProviderMock = new Mock<IActionDescriptorCollectionProvider>();
|
||||
actionDescriptorCollectionProviderMock
|
||||
.Setup(m => m.ActionDescriptors)
|
||||
.Returns(new ActionDescriptorCollection(actionDescriptors, version: 0));
|
||||
return actionDescriptorCollectionProviderMock.Object;
|
||||
}
|
||||
|
||||
private ActionDescriptor CreateActionDescriptor(
|
||||
object requiredValues,
|
||||
string attributeRouteTemplate = null,
|
||||
IList<object> metadata = null)
|
||||
{
|
||||
var actionDescriptor = new ActionDescriptor();
|
||||
var routeValues = new RouteValueDictionary(requiredValues);
|
||||
foreach (var kvp in routeValues)
|
||||
{
|
||||
actionDescriptor.RouteValues[kvp.Key] = kvp.Value?.ToString();
|
||||
}
|
||||
if (!string.IsNullOrEmpty(attributeRouteTemplate))
|
||||
{
|
||||
actionDescriptor.AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Name = attributeRouteTemplate,
|
||||
Template = attributeRouteTemplate
|
||||
};
|
||||
}
|
||||
actionDescriptor.EndpointMetadata = metadata;
|
||||
return actionDescriptor;
|
||||
}
|
||||
|
||||
private void AssertIsSubset(
|
||||
IReadOnlyDictionary<string, object> subset,
|
||||
IReadOnlyDictionary<string, object> fullSet)
|
||||
{
|
||||
foreach (var subsetPair in subset)
|
||||
{
|
||||
var isPresent = fullSet.TryGetValue(subsetPair.Key, out var fullSetPairValue);
|
||||
Assert.True(isPresent);
|
||||
Assert.Equal(subsetPair.Value, fullSetPairValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertMatchingSuppressed(Endpoint endpoint, bool suppressed)
|
||||
{
|
||||
var isEndpointSuppressed = endpoint.Metadata.GetMetadata<ISuppressMatchingMetadata>()?.SuppressMatching ?? false;
|
||||
Assert.Equal(suppressed, isEndpointSuppressed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,9 +10,9 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class AntiforgeryTests : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
|
||||
public class AntiforgeryTests : IClassFixture<MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
public AntiforgeryTests(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
public AntiforgeryTests(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
Client = fixture.CreateDefaultClient();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class ApiBehaviorTest : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
|
||||
public class ApiBehaviorTest : IClassFixture<MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
public ApiBehaviorTest(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
public ApiBehaviorTest(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
Client = fixture.CreateDefaultClient();
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class AsyncActionsTests : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
|
||||
public class AsyncActionsTests : IClassFixture<MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
public AsyncActionsTests(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
public AsyncActionsTests(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
Client = fixture.CreateDefaultClient();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class BasicTests : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
|
||||
public class BasicTests : IClassFixture<MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
// Some tests require comparing the actual response body against an expected response baseline
|
||||
// so they require a reference to the assembly on which the resources are located, in order to
|
||||
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
// use it on all the rest of the tests.
|
||||
private static readonly Assembly _resourcesAssembly = typeof(BasicTests).GetTypeInfo().Assembly;
|
||||
|
||||
public BasicTests(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
public BasicTests(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
Client = fixture.CreateDefaultClient();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class ComponentRenderingFunctionalTests : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
|
||||
public class ComponentRenderingFunctionalTests : IClassFixture<MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
public ComponentRenderingFunctionalTests(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
public ComponentRenderingFunctionalTests(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
Client = Client ?? CreateClient(fixture);
|
||||
}
|
||||
|
|
@ -113,7 +113,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
{
|
||||
}
|
||||
|
||||
private HttpClient CreateClient(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
private HttpClient CreateClient(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
var loopHandler = new LoopHttpHandler();
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class ConsumesAttributeEndpointRoutingTests : ConsumesAttributeTestsBase<BasicWebSite.StartupWithEndpointRouting>
|
||||
public class ConsumesAttributeEndpointRoutingTests : ConsumesAttributeTestsBase<BasicWebSite.Startup>
|
||||
{
|
||||
public ConsumesAttributeEndpointRoutingTests(MvcTestFixture<BasicWebSite.StartupWithEndpointRouting> fixture)
|
||||
public ConsumesAttributeEndpointRoutingTests(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
: base(fixture)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class ConsumesAttributeTests : ConsumesAttributeTestsBase<BasicWebSite.Startup>
|
||||
public class ConsumesAttributeTests : ConsumesAttributeTestsBase<BasicWebSite.StartupWithoutEndpointRouting>
|
||||
{
|
||||
public ConsumesAttributeTests(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
public ConsumesAttributeTests(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
: base(fixture)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class ContentNegotiationTest : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
|
||||
public class ContentNegotiationTest : IClassFixture<MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
public ContentNegotiationTest(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
public ContentNegotiationTest(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
Client = fixture.CreateDefaultClient();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class DefaultValuesTest : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
|
||||
public class DefaultValuesTest : IClassFixture<MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
public DefaultValuesTest(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
public DefaultValuesTest(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
Client = fixture.CreateDefaultClient();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class FiltersTest : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
|
||||
public class FiltersTest : IClassFixture<MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
public FiltersTest(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
public FiltersTest(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
Client = fixture.CreateDefaultClient();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class JsonResultTest : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
|
||||
public class JsonResultTest : IClassFixture<MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
public JsonResultTest(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
public JsonResultTest(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
Client = fixture.CreateDefaultClient();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class LinkGenerationTests : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
|
||||
public class LinkGenerationTests : IClassFixture<MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
// Some tests require comparing the actual response body against an expected response baseline
|
||||
// so they require a reference to the assembly on which the resources are located, in order to
|
||||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
// use it on all the rest of the tests.
|
||||
private static readonly Assembly _resourcesAssembly = typeof(LinkGenerationTests).GetTypeInfo().Assembly;
|
||||
|
||||
public LinkGenerationTests(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
public LinkGenerationTests(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
Client = fixture.CreateDefaultClient();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class OutputFormatterTest : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
|
||||
public class OutputFormatterTest : IClassFixture<MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
public OutputFormatterTest(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
public OutputFormatterTest(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
Client = fixture.CreateDefaultClient();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,16 +11,16 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class RazorPageModelTest : IClassFixture<MvcTestFixture<RazorPagesWebSite.Startup>>
|
||||
public class RazorPageModelTest : IClassFixture<MvcTestFixture<RazorPagesWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
public RazorPageModelTest(MvcTestFixture<RazorPagesWebSite.Startup> fixture)
|
||||
public RazorPageModelTest(MvcTestFixture<RazorPagesWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
|
||||
Client = factory.CreateDefaultClient();
|
||||
}
|
||||
|
||||
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) =>
|
||||
builder.UseStartup<RazorPagesWebSite.Startup>();
|
||||
builder.UseStartup<RazorPagesWebSite.StartupWithoutEndpointRouting>();
|
||||
|
||||
public HttpClient Client { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -9,16 +9,16 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class RazorPagesNamespaceTest : IClassFixture<MvcTestFixture<RazorPagesWebSite.Startup>>
|
||||
public class RazorPagesNamespaceTest : IClassFixture<MvcTestFixture<RazorPagesWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
public RazorPagesNamespaceTest(MvcTestFixture<RazorPagesWebSite.Startup> fixture)
|
||||
public RazorPagesNamespaceTest(MvcTestFixture<RazorPagesWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
|
||||
Client = factory.CreateDefaultClient();
|
||||
}
|
||||
|
||||
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) =>
|
||||
builder.UseStartup<RazorPagesWebSite.Startup>();
|
||||
builder.UseStartup<RazorPagesWebSite.StartupWithoutEndpointRouting>();
|
||||
|
||||
public HttpClient Client { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -18,18 +18,18 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class RazorPagesTest : IClassFixture<MvcTestFixture<RazorPagesWebSite.Startup>>
|
||||
public class RazorPagesTest : IClassFixture<MvcTestFixture<RazorPagesWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
private static readonly Assembly _resourcesAssembly = typeof(RazorPagesTest).GetTypeInfo().Assembly;
|
||||
|
||||
public RazorPagesTest(MvcTestFixture<RazorPagesWebSite.Startup> fixture)
|
||||
public RazorPagesTest(MvcTestFixture<RazorPagesWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
|
||||
Client = factory.CreateDefaultClient();
|
||||
}
|
||||
|
||||
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) =>
|
||||
builder.UseStartup<RazorPagesWebSite.Startup>();
|
||||
builder.UseStartup<RazorPagesWebSite.StartupWithoutEndpointRouting>();
|
||||
|
||||
public HttpClient Client { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -9,16 +9,16 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class RazorPagesViewSearchTest : IClassFixture<MvcTestFixture<RazorPagesWebSite.Startup>>
|
||||
public class RazorPagesViewSearchTest : IClassFixture<MvcTestFixture<RazorPagesWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
public RazorPagesViewSearchTest(MvcTestFixture<RazorPagesWebSite.Startup> fixture)
|
||||
public RazorPagesViewSearchTest(MvcTestFixture<RazorPagesWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
|
||||
Client = factory.CreateDefaultClient();
|
||||
}
|
||||
|
||||
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) =>
|
||||
builder.UseStartup<RazorPagesWebSite.Startup>();
|
||||
builder.UseStartup<RazorPagesWebSite.StartupWithoutEndpointRouting>();
|
||||
|
||||
public HttpClient Client { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class RazorPagesWithEndpointRoutingTest : IClassFixture<MvcTestFixture<RazorPagesWebSite.StartupWithEndpointRouting>>
|
||||
public class RazorPagesWithEndpointRoutingTest : IClassFixture<MvcTestFixture<RazorPagesWebSite.Startup>>
|
||||
{
|
||||
public RazorPagesWithEndpointRoutingTest(MvcTestFixture<RazorPagesWebSite.StartupWithEndpointRouting> fixture)
|
||||
public RazorPagesWithEndpointRoutingTest(MvcTestFixture<RazorPagesWebSite.Startup> fixture)
|
||||
{
|
||||
Client = fixture.CreateDefaultClient();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class RemoteAttributeValidationTest : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
|
||||
public class RemoteAttributeValidationTest : IClassFixture<MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
private static readonly Assembly _resourcesAssembly =
|
||||
typeof(RemoteAttributeValidationTest).GetTypeInfo().Assembly;
|
||||
|
||||
public RemoteAttributeValidationTest(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
public RemoteAttributeValidationTest(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
Client = fixture.CreateDefaultClient();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class RequestServicesEndpointRoutingTest : RequestServicesTestBase<BasicWebSite.StartupWithEndpointRouting>
|
||||
public class RequestServicesEndpointRoutingTest : RequestServicesTestBase<BasicWebSite.Startup>
|
||||
{
|
||||
public RequestServicesEndpointRoutingTest(MvcTestFixture<BasicWebSite.StartupWithEndpointRouting> fixture)
|
||||
public RequestServicesEndpointRoutingTest(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
: base(fixture)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class RequestServicesTest : RequestServicesTestBase<BasicWebSite.Startup>
|
||||
public class RequestServicesTest : RequestServicesTestBase<BasicWebSite.StartupWithoutEndpointRouting>
|
||||
{
|
||||
public RequestServicesTest(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
public RequestServicesTest(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
: base(fixture)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class RoutingEndpointRoutingWithoutRazorPagesTests : RoutingWithoutRazorPagesTestsBase<BasicWebSite.StartupWithEndpointRouting>
|
||||
public class RoutingEndpointRoutingWithoutRazorPagesTests : RoutingWithoutRazorPagesTestsBase<BasicWebSite.Startup>
|
||||
{
|
||||
public RoutingEndpointRoutingWithoutRazorPagesTests(MvcTestFixture<BasicWebSite.StartupWithEndpointRouting> fixture)
|
||||
public RoutingEndpointRoutingWithoutRazorPagesTests(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
: base(fixture)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,386 @@
|
|||
// 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.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class RoutingUseMvcWithEndpointRoutingTest : RoutingTestsBase<RoutingWebSite.StartupWithUseMvcAndEndpointRouting>
|
||||
{
|
||||
public RoutingUseMvcWithEndpointRoutingTest(MvcTestFixture<RoutingWebSite.StartupWithUseMvcAndEndpointRouting> fixture)
|
||||
: base(fixture)
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AttributeRoutedAction_ContainsPage_RouteMatched()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/PageRoute/Attribute/pagevalue");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Contains("/PageRoute/Attribute/pagevalue", result.ExpectedUrls);
|
||||
Assert.Equal("PageRoute", result.Controller);
|
||||
Assert.Equal("AttributeRoute", result.Action);
|
||||
|
||||
Assert.Contains(
|
||||
new KeyValuePair<string, object>("page", "pagevalue"),
|
||||
result.RouteValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ParameterTransformer_TokenReplacement_Found()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/parameter-transformer/my-action");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("ParameterTransformer", result.Controller);
|
||||
Assert.Equal("MyAction", result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ParameterTransformer_TokenReplacement_NotFound()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/ParameterTransformer/MyAction");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AttributeRoutedAction_Parameters_Found()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/EndpointRouting/Index");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("EndpointRouting", result.Controller);
|
||||
Assert.Equal("Index", result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AttributeRoutedAction_Parameters_DefaultValue_Found()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/EndpointRouting");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("EndpointRouting", result.Controller);
|
||||
Assert.Equal("Index", result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AttributeRoutedAction_ParameterTransformer_Found()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/endpoint-routing/ParameterTransformer");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("EndpointRouting", result.Controller);
|
||||
Assert.Equal("ParameterTransformer", result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AttributeRoutedAction_ParameterTransformer_NotFound()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/EndpointRouting/ParameterTransformer");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AttributeRoutedAction_ParameterTransformer_LinkToSelf()
|
||||
{
|
||||
// Arrange
|
||||
var url = LinkFrom("http://localhost/endpoint-routing/ParameterTransformer").To(new { });
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync(url);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("EndpointRouting", result.Controller);
|
||||
Assert.Equal("ParameterTransformer", result.Action);
|
||||
|
||||
Assert.Equal("/endpoint-routing/ParameterTransformer", result.Link);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AttributeRoutedAction_ParameterTransformer_LinkWithAmbientController()
|
||||
{
|
||||
// Arrange
|
||||
var url = LinkFrom("http://localhost/endpoint-routing/ParameterTransformer").To(new { action = "Get", id = 5 });
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync(url);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("EndpointRouting", result.Controller);
|
||||
Assert.Equal("ParameterTransformer", result.Action);
|
||||
|
||||
Assert.Equal("/endpoint-routing/5", result.Link);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AttributeRoutedAction_ParameterTransformer_LinkToAttributeRoutedController()
|
||||
{
|
||||
// Arrange
|
||||
var url = LinkFrom("http://localhost/endpoint-routing/ParameterTransformer").To(new { action = "ShowPosts", controller = "Blog" });
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync(url);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("EndpointRouting", result.Controller);
|
||||
Assert.Equal("ParameterTransformer", result.Action);
|
||||
|
||||
Assert.Equal("/Blog/ShowPosts", result.Link);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AttributeRoutedAction_ParameterTransformer_LinkToConventionalController()
|
||||
{
|
||||
// Arrange
|
||||
var url = LinkFrom("http://localhost/endpoint-routing/ParameterTransformer").To(new { action = "Index", controller = "Home" });
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync(url);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("EndpointRouting", result.Controller);
|
||||
Assert.Equal("ParameterTransformer", result.Action);
|
||||
|
||||
Assert.Equal("/", result.Link);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async override Task HasEndpointMatch()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/Routing/HasEndpointMatch");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<bool>(body);
|
||||
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async override Task RouteData_Routers_ConventionalRoute()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/RouteData/Conventional");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<ResultData>(body);
|
||||
|
||||
Assert.Equal(
|
||||
Array.Empty<string>(),
|
||||
result.Routers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async override Task RouteData_Routers_AttributeRoute()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/RouteData/Attribute");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<ResultData>(body);
|
||||
|
||||
Assert.Equal(
|
||||
Array.Empty<string>(),
|
||||
result.Routers);
|
||||
}
|
||||
|
||||
// Endpoint routing exposes HTTP 405s for HTTP method mismatches
|
||||
[Fact]
|
||||
public override async Task ConventionalRoutedController_InArea_ActionBlockedByHttpMethod()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/Travel/Flight/BuyTickets");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConventionalRoutedAction_ParameterTransformer()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/ConventionalTransformerRoute/conventional-transformer/Index");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("ConventionalTransformer", result.Controller);
|
||||
Assert.Equal("Index", result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConventionalRoutedAction_ParameterTransformer_NotFound()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/ConventionalTransformerRoute/ConventionalTransformer/Index");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConventionalRoutedAction_ParameterTransformer_DefaultValue()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/ConventionalTransformerRoute/conventional-transformer");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("ConventionalTransformer", result.Controller);
|
||||
Assert.Equal("Index", result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConventionalRoutedAction_ParameterTransformer_WithParam()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/ConventionalTransformerRoute/conventional-transformer/Param/my-value");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("ConventionalTransformer", result.Controller);
|
||||
Assert.Equal("Param", result.Action);
|
||||
|
||||
Assert.Equal("/ConventionalTransformerRoute/conventional-transformer/Param/my-value", Assert.Single(result.ExpectedUrls));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConventionalRoutedAction_ParameterTransformer_LinkToConventionalController()
|
||||
{
|
||||
// Arrange
|
||||
var url = LinkFrom("http://localhost/ConventionalTransformerRoute/conventional-transformer/Index").To(new { action = "Index", controller = "Home" });
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync(url);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("ConventionalTransformer", result.Controller);
|
||||
Assert.Equal("Index", result.Action);
|
||||
Assert.Equal("/", result.Link);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConventionalRoutedAction_ParameterTransformer_LinkToConventionalControllerWithParam()
|
||||
{
|
||||
// Arrange
|
||||
var url = LinkFrom("http://localhost/ConventionalTransformerRoute/conventional-transformer/Index").To(new { action = "Param", controller = "ConventionalTransformer", param = "MyValue" });
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync(url);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("ConventionalTransformer", result.Controller);
|
||||
Assert.Equal("Index", result.Action);
|
||||
Assert.Equal("/ConventionalTransformerRoute/conventional-transformer/Param/my-value", result.Link);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConventionalRoutedAction_ParameterTransformer_LinkToSelf()
|
||||
{
|
||||
// Arrange
|
||||
var url = LinkFrom("http://localhost/ConventionalTransformerRoute/conventional-transformer/Index").To(new {});
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync(url);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("ConventionalTransformer", result.Controller);
|
||||
Assert.Equal("Index", result.Action);
|
||||
Assert.Equal("/ConventionalTransformerRoute/conventional-transformer", result.Link);
|
||||
}
|
||||
|
||||
// Endpoint routing exposes HTTP 405s for HTTP method mismatches.
|
||||
protected override void AssertCorsRejectionStatusCode(HttpResponseMessage response)
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,9 +9,9 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class RoutingWithoutRazorPagesTests : RoutingWithoutRazorPagesTestsBase<BasicWebSite.Startup>
|
||||
public class RoutingWithoutRazorPagesTests : RoutingWithoutRazorPagesTestsBase<BasicWebSite.StartupWithoutEndpointRouting>
|
||||
{
|
||||
public RoutingWithoutRazorPagesTests(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
public RoutingWithoutRazorPagesTests(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
: base(fixture)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class TempDataInCookiesTest : TempDataTestBase, IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
|
||||
public class TempDataInCookiesTest : TempDataTestBase, IClassFixture<MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
public TempDataInCookiesTest(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
public TempDataInCookiesTest(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
Client = fixture.CreateDefaultClient();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class TempDataPropertyTest : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
|
||||
public class TempDataPropertyTest : IClassFixture<MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
protected HttpClient Client { get; }
|
||||
|
||||
public TempDataPropertyTest(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
public TempDataPropertyTest(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
Client = fixture.CreateDefaultClient();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
public void TestingInfrastructure_WebHost_WithWebHostBuilderRespectsCustomizations()
|
||||
{
|
||||
// Act
|
||||
var factory = new CustomizedFactory<BasicWebSite.Startup>();
|
||||
var factory = new CustomizedFactory<BasicWebSite.StartupWithoutEndpointRouting>();
|
||||
var customized = factory
|
||||
.WithWebHostBuilder(builder => factory.ConfigureWebHostCalled.Add("Customization"))
|
||||
.WithWebHostBuilder(builder => factory.ConfigureWebHostCalled.Add("FurtherCustomization"));
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class TestingInfrastructureTests : IClassFixture<WebApplicationFactory<BasicWebSite.Startup>>
|
||||
public class TestingInfrastructureTests : IClassFixture<WebApplicationFactory<BasicWebSite.StartupWithoutEndpointRouting>>
|
||||
{
|
||||
public TestingInfrastructureTests(WebApplicationFactory<BasicWebSite.Startup> fixture)
|
||||
public TestingInfrastructureTests(WebApplicationFactory<BasicWebSite.StartupWithoutEndpointRouting> fixture)
|
||||
{
|
||||
Factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
|
||||
Client = Factory.CreateClient();
|
||||
|
|
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) =>
|
||||
builder.ConfigureTestServices(s => s.AddSingleton<TestService, OverridenService>());
|
||||
|
||||
public WebApplicationFactory<Startup> Factory { get; }
|
||||
public WebApplicationFactory<StartupWithoutEndpointRouting> Factory { get; }
|
||||
public HttpClient Client { get; }
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
// 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 Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||
{
|
||||
public class PageActionEndpointDataSourceTest : ActionEndpointDataSourceBaseTest
|
||||
{
|
||||
[Fact]
|
||||
public void Endpoints_Ignores_NonPage()
|
||||
{
|
||||
// Arrange
|
||||
var actions = new List<ActionDescriptor>
|
||||
{
|
||||
new ActionDescriptor
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo()
|
||||
{
|
||||
Template = "/test",
|
||||
},
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "action", "Test" },
|
||||
{ "controller", "Test" },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(actions, 0));
|
||||
|
||||
var dataSource = (PageActionEndpointDataSource)CreateDataSource(mockDescriptorProvider.Object);
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Empty(endpoints);
|
||||
}
|
||||
[Fact]
|
||||
public void Endpoints_AppliesConventions()
|
||||
{
|
||||
// Arrange
|
||||
var actions = new List<ActionDescriptor>
|
||||
{
|
||||
new PageActionDescriptor
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo()
|
||||
{
|
||||
Template = "/test",
|
||||
},
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "action", "Test" },
|
||||
{ "controller", "Test" },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var mockDescriptorProvider = new Mock<IActionDescriptorCollectionProvider>();
|
||||
mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(actions, 0));
|
||||
|
||||
var dataSource = (PageActionEndpointDataSource)CreateDataSource(mockDescriptorProvider.Object);
|
||||
|
||||
dataSource.Add((b) =>
|
||||
{
|
||||
b.Metadata.Add("Hi there");
|
||||
});
|
||||
|
||||
// Act
|
||||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
endpoints.OfType<RouteEndpoint>().OrderBy(e => e.RoutePattern.RawText),
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("/test", e.RoutePattern.RawText);
|
||||
Assert.Same(actions[0], e.Metadata.GetMetadata<ActionDescriptor>());
|
||||
Assert.Equal("Hi there", e.Metadata.GetMetadata<string>());
|
||||
});
|
||||
}
|
||||
|
||||
private protected override ActionEndpointDataSourceBase CreateDataSource(IActionDescriptorCollectionProvider actions, ActionEndpointFactory endpointFactory)
|
||||
{
|
||||
return new PageActionEndpointDataSource(actions, endpointFactory);
|
||||
}
|
||||
|
||||
protected override ActionDescriptor CreateActionDescriptor(
|
||||
object values,
|
||||
string pattern = null,
|
||||
IList<object> metadata = null)
|
||||
{
|
||||
var action = new PageActionDescriptor();
|
||||
|
||||
foreach (var kvp in new RouteValueDictionary(values))
|
||||
{
|
||||
action.RouteValues[kvp.Key] = kvp.Value?.ToString();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(pattern))
|
||||
{
|
||||
action.AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Name = "test",
|
||||
Template = pattern,
|
||||
};
|
||||
}
|
||||
|
||||
action.EndpointMetadata = metadata;
|
||||
return action;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,10 @@
|
|||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Microsoft.AspNetCore.Mvc.Core.Test\Routing\ActionEndpointDataSourceBaseTest.cs" Link="Infrastructure\ActionEndpointDataSourceBaseTest.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Mvc.RazorPages" />
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Views.TestCommon\Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj" />
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@ namespace ApiExplorerWebSite
|
|||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvc(routes =>
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapRoute("default", "{controller}/{action}");
|
||||
routes.MapDefaultControllerRoute();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,14 +27,12 @@ namespace ApplicationModelWebSite
|
|||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvc(routes =>
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapRoute(name: "areaRoute",
|
||||
template: "{area:exists}/{controller=Home}/{action=Index}");
|
||||
routes.MapControllerRoute(name: "areaRoute", template: "{area:exists}/{controller=Home}/{action=Index}");
|
||||
routes.MapControllerRoute(name: "default", template: "{controller}/{action}/{id?}");
|
||||
|
||||
routes.MapRoute(
|
||||
name: "default",
|
||||
template: "{controller}/{action}/{id?}");
|
||||
routes.MapRazorPages();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace BasicWebSite
|
|||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||
new WebHostBuilder()
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseStartup<Startup>()
|
||||
.UseStartup<StartupWithoutEndpointRouting>()
|
||||
.UseKestrel()
|
||||
.UseIISIntegration();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,10 @@
|
|||
// 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.Net.Http;
|
||||
using BasicWebSite.Services;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace BasicWebSite
|
||||
{
|
||||
|
|
@ -21,77 +13,38 @@ namespace BasicWebSite
|
|||
// Set up application services
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton(new TestService { Message = "true" });
|
||||
services.AddRouting();
|
||||
|
||||
services.AddAuthentication()
|
||||
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("Api", _ => { });
|
||||
services.AddTransient<IAuthorizationHandler, ManagerHandler>();
|
||||
|
||||
services
|
||||
.AddMvc(options =>
|
||||
{
|
||||
options.Conventions.Add(new ApplicationDescription("This is a basic website."));
|
||||
// Filter that records a value in HttpContext.Items
|
||||
options.Filters.Add(new TraceResourceFilter());
|
||||
|
||||
// Remove when all URL generation tests are passing - https://github.com/aspnet/Routing/issues/590
|
||||
options.EnableEndpointRouting = false;
|
||||
})
|
||||
services.AddMvc()
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Latest)
|
||||
.AddNewtonsoftJson()
|
||||
.AddXmlDataContractSerializerFormatters();
|
||||
|
||||
services.ConfigureBaseWebSiteAuthPolicies();
|
||||
|
||||
services.AddTransient<IAuthorizationHandler, ManagerHandler>();
|
||||
|
||||
services.AddLogging();
|
||||
services.AddSingleton<IActionDescriptorProvider, ActionDescriptorCreationCounter>();
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddSingleton<ContactsRepository>();
|
||||
services.AddScoped<RequestIdService>();
|
||||
services.AddTransient<ServiceActionFilter>();
|
||||
services.AddScoped<TestResponseGenerator>();
|
||||
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||
services.TryAddSingleton(CreateWeatherForecastService);
|
||||
}
|
||||
|
||||
// For manual debug only (running this test site with F5)
|
||||
// This needs to be changed to match the site host
|
||||
private WeatherForecastService CreateWeatherForecastService(IServiceProvider serviceProvider)
|
||||
{
|
||||
var contextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
|
||||
var httpContext = contextAccessor.HttpContext;
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new InvalidOperationException("Needs a request context!");
|
||||
}
|
||||
var client = new HttpClient();
|
||||
client.BaseAddress = new Uri($"{httpContext.Request.Scheme}://{httpContext.Request.Host}");
|
||||
return new WeatherForecastService(client);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
|
||||
app.UseStaticFiles();
|
||||
|
||||
// Initializes the RequestId service for each request
|
||||
app.UseMiddleware<RequestIdMiddleware>();
|
||||
|
||||
// Add MVC to the request pipeline
|
||||
app.UseMvc(routes =>
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
"areaRoute",
|
||||
"{area:exists}/{controller}/{action}",
|
||||
new { controller = "Home", action = "Index" });
|
||||
|
||||
routes.MapRoute("ActionAsMethod", "{controller}/{action}",
|
||||
routes.MapControllerRoute(
|
||||
name: "ActionAsMethod",
|
||||
template: "{controller}/{action}",
|
||||
defaults: new { controller = "Home", action = "Index" });
|
||||
|
||||
routes.MapRoute("PageRoute", "{controller}/{action}/{page}");
|
||||
routes.MapControllerRoute(
|
||||
name: "PageRoute",
|
||||
template: "{controller}/{action}/{page}");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,11 @@ namespace BasicWebSite
|
|||
return next();
|
||||
});
|
||||
|
||||
app.UseMvcWithDefaultRoute();
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapDefaultControllerRoute();
|
||||
routes.MapRazorPages();
|
||||
});
|
||||
}
|
||||
|
||||
private class RequestBodySizeCheckingStream : Stream
|
||||
|
|
|
|||
|
|
@ -29,7 +29,11 @@ namespace BasicWebSite
|
|||
|
||||
app.UseCookiePolicy();
|
||||
|
||||
app.UseMvcWithDefaultRoute();
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapDefaultControllerRoute();
|
||||
routes.MapRazorPages();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,11 @@ namespace BasicWebSite
|
|||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseMvc();
|
||||
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapControllers();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BasicWebSite
|
||||
{
|
||||
public class StartupWithEndpointRouting
|
||||
{
|
||||
// Set up application services
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddRouting();
|
||||
|
||||
services.AddMvc()
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Latest)
|
||||
.AddNewtonsoftJson()
|
||||
.AddXmlDataContractSerializerFormatters();
|
||||
|
||||
services.ConfigureBaseWebSiteAuthPolicies();
|
||||
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddScoped<RequestIdService>();
|
||||
services.AddScoped<TestResponseGenerator>();
|
||||
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
|
||||
// Initializes the RequestId service for each request
|
||||
app.UseMiddleware<RequestIdMiddleware>();
|
||||
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
"ActionAsMethod",
|
||||
"{controller}/{action}",
|
||||
defaults: new { controller = "Home", action = "Index" });
|
||||
|
||||
routes.MapRoute("PageRoute", "{controller}/{action}/{page}");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,12 @@ namespace BasicWebSite
|
|||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseSession();
|
||||
app.UseMvcWithDefaultRoute();
|
||||
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapDefaultControllerRoute();
|
||||
routes.MapRazorPages();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
// 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.Net.Http;
|
||||
using BasicWebSite.Services;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace BasicWebSite
|
||||
{
|
||||
public class StartupWithoutEndpointRouting
|
||||
{
|
||||
// Set up application services
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton(new TestService { Message = "true" });
|
||||
|
||||
services.AddAuthentication()
|
||||
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("Api", _ => { });
|
||||
services.AddTransient<IAuthorizationHandler, ManagerHandler>();
|
||||
|
||||
services
|
||||
.AddMvc(options =>
|
||||
{
|
||||
options.Conventions.Add(new ApplicationDescription("This is a basic website."));
|
||||
// Filter that records a value in HttpContext.Items
|
||||
options.Filters.Add(new TraceResourceFilter());
|
||||
|
||||
options.EnableEndpointRouting = false;
|
||||
})
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Latest)
|
||||
.AddNewtonsoftJson()
|
||||
.AddXmlDataContractSerializerFormatters();
|
||||
|
||||
services.ConfigureBaseWebSiteAuthPolicies();
|
||||
|
||||
services.AddTransient<IAuthorizationHandler, ManagerHandler>();
|
||||
|
||||
services.AddLogging();
|
||||
services.AddSingleton<IActionDescriptorProvider, ActionDescriptorCreationCounter>();
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddSingleton<ContactsRepository>();
|
||||
services.AddScoped<RequestIdService>();
|
||||
services.AddTransient<ServiceActionFilter>();
|
||||
services.AddScoped<TestResponseGenerator>();
|
||||
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||
services.TryAddSingleton(CreateWeatherForecastService);
|
||||
}
|
||||
|
||||
// For manual debug only (running this test site with F5)
|
||||
// This needs to be changed to match the site host
|
||||
private WeatherForecastService CreateWeatherForecastService(IServiceProvider serviceProvider)
|
||||
{
|
||||
var contextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
|
||||
var httpContext = contextAccessor.HttpContext;
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new InvalidOperationException("Needs a request context!");
|
||||
}
|
||||
var client = new HttpClient();
|
||||
client.BaseAddress = new Uri($"{httpContext.Request.Scheme}://{httpContext.Request.Host}");
|
||||
return new WeatherForecastService(client);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
|
||||
app.UseStaticFiles();
|
||||
|
||||
// Initializes the RequestId service for each request
|
||||
app.UseMiddleware<RequestIdMiddleware>();
|
||||
|
||||
// Add MVC to the request pipeline
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
"areaRoute",
|
||||
"{area:exists}/{controller}/{action}",
|
||||
new { controller = "Home", action = "Index" });
|
||||
|
||||
routes.MapRoute("ActionAsMethod", "{controller}/{action}",
|
||||
defaults: new { controller = "Home", action = "Index" });
|
||||
|
||||
routes.MapRoute("PageRoute", "{controller}/{action}/{page}");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -64,9 +64,9 @@ namespace ControllersFromServicesWebSite
|
|||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvc(routes =>
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapRoute("default", "{controller}/{action}/{id}");
|
||||
routes.MapDefaultControllerRoute();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -73,9 +73,12 @@ namespace CorsWebSite
|
|||
});
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
public virtual void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvc();
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapControllers();
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual void ConfigureMvcOptions(MvcOptions options)
|
||||
|
|
|
|||
|
|
@ -1,12 +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 Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CorsWebSite
|
||||
{
|
||||
public class StartupWithoutEndpointRouting : Startup
|
||||
{
|
||||
public override void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvc();
|
||||
}
|
||||
|
||||
protected override void ConfigureMvcOptions(MvcOptions options)
|
||||
{
|
||||
options.EnableEndpointRouting = false;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,10 @@ namespace ErrorPageMiddlewareWebSite
|
|||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseMvc();
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapControllers();
|
||||
});
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ namespace FilesWebSite
|
|||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvc(routes =>
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapRoute(name: null, template: "{controller}/{action}", defaults: null);
|
||||
routes.MapDefaultControllerRoute();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,10 +26,9 @@ namespace FormatterWebSite
|
|||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvc(routes =>
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapRoute("ActionAsMethod", "{controller}/{action}",
|
||||
defaults: new { controller = "Home", action = "Index" });
|
||||
routes.MapDefaultControllerRoute();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,9 @@ namespace FormatterWebSite
|
|||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvc(routes =>
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapRoute("ActionAsMethod", "{controller}/{action}",
|
||||
defaults: new { controller = "Home", action = "Index" });
|
||||
routes.MapDefaultControllerRoute();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,18 +40,17 @@ namespace GenericHostWebSite
|
|||
|
||||
app.UseStaticFiles();
|
||||
|
||||
// Add MVC to the request pipeline
|
||||
app.UseMvc(routes =>
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
routes.MapControllerRoute(
|
||||
"areaRoute",
|
||||
"{area:exists}/{controller}/{action}",
|
||||
new { controller = "Home", action = "Index" });
|
||||
|
||||
routes.MapRoute("ActionAsMethod", "{controller}/{action}",
|
||||
routes.MapControllerRoute("ActionAsMethod", "{controller}/{action}",
|
||||
defaults: new { controller = "Home", action = "Index" });
|
||||
|
||||
routes.MapRoute("PageRoute", "{controller}/{action}/{page}");
|
||||
routes.MapControllerRoute("PageRoute", "{controller}/{action}/{page}");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,23 +26,26 @@ namespace HtmlGenerationWebSite
|
|||
services.AddSingleton<ProductsService>();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
public virtual void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseStaticFiles();
|
||||
app.UseMvc(routes =>
|
||||
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
routes.MapControllerRoute(
|
||||
name: "areaRoute",
|
||||
template: "{area:exists}/{controller}/{action}/{id?}",
|
||||
defaults: new { action = "Index" });
|
||||
routes.MapRoute(
|
||||
routes.MapControllerRoute(
|
||||
name: "productRoute",
|
||||
template: "Product/{action}",
|
||||
defaults: new { controller = "Product" });
|
||||
routes.MapRoute(
|
||||
routes.MapControllerRoute(
|
||||
name: "default",
|
||||
template: "{controller}/{action}/{id?}",
|
||||
defaults: new { controller = "HtmlGeneration_Home", action = "Index" });
|
||||
|
||||
routes.MapRazorPages();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,34 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace HtmlGenerationWebSite
|
||||
{
|
||||
public class StartupWithoutEndpointRouting : Startup
|
||||
{
|
||||
public override void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
name: "areaRoute",
|
||||
template: "{area:exists}/{controller}/{action}/{id?}",
|
||||
defaults: new { action = "Index" });
|
||||
routes.MapRoute(
|
||||
name: "productRoute",
|
||||
template: "Product/{action}",
|
||||
defaults: new { controller = "Product" });
|
||||
routes.MapRoute(
|
||||
name: "default",
|
||||
template: "{controller}/{action}/{id?}",
|
||||
defaults: new { controller = "HtmlGeneration_Home", action = "Index" });
|
||||
});
|
||||
}
|
||||
|
||||
protected override void ConfigureMvcOptions(MvcOptions options)
|
||||
{
|
||||
options.EnableEndpointRouting = false;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,11 @@ namespace RazorBuildWebSite
|
|||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvcWithDefaultRoute();
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapDefaultControllerRoute();
|
||||
routes.MapRazorPages();
|
||||
});
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
// 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.Globalization;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using RazorPagesWebSite.Conventions;
|
||||
|
||||
namespace RazorPagesWebSite
|
||||
{
|
||||
|
|
@ -14,42 +12,26 @@ namespace RazorPagesWebSite
|
|||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options => options.LoginPath = "/Login");
|
||||
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(options => options.LoginPath = "/Login");
|
||||
|
||||
services.AddMvc()
|
||||
.AddMvcLocalization()
|
||||
.AddNewtonsoftJson()
|
||||
.AddRazorPagesOptions(options =>
|
||||
{
|
||||
options.Conventions.AuthorizePage("/HelloWorldWithAuth");
|
||||
options.Conventions.AuthorizeFolder("/Pages/Admin");
|
||||
options.Conventions.AllowAnonymousToPage("/Pages/Admin/Login");
|
||||
options.Conventions.AddPageRoute("/HelloWorldWithRoute", "Different-Route/{text}");
|
||||
options.Conventions.AddPageRoute("/Pages/NotTheRoot", string.Empty);
|
||||
options.Conventions.Add(new CustomModelTypeConvention());
|
||||
options.Conventions.AuthorizeFolder("/Admin");
|
||||
})
|
||||
.WithRazorPagesAtContentRoot()
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Latest);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseAuthentication();
|
||||
|
||||
app.UseStaticFiles();
|
||||
|
||||
var supportedCultures = new[]
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
new CultureInfo("en-US"),
|
||||
new CultureInfo("fr-FR"),
|
||||
};
|
||||
|
||||
app.UseRequestLocalization(new RequestLocalizationOptions
|
||||
{
|
||||
SupportedCultures = supportedCultures,
|
||||
SupportedUICultures = supportedCultures
|
||||
routes.MapControllers();
|
||||
routes.MapRazorPages();
|
||||
});
|
||||
|
||||
app.UseMvc();
|
||||
app.UseAuthorization();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,9 +42,10 @@ namespace RazorPagesWebSite
|
|||
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseMvc(routes =>
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapRoute("areaRoute", "{area:exists}/{controller=Home}/{action=Index}");
|
||||
routes.MapControllerRoute("areaRoute", "{area:exists}/{controller=Home}/{action=Index}");
|
||||
routes.MapRazorPages();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
// 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.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace RazorPagesWebSite
|
||||
{
|
||||
public class StartupWithEndpointRouting
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(options => options.LoginPath = "/Login");
|
||||
|
||||
services.AddMvc()
|
||||
.AddRazorPagesOptions(options =>
|
||||
{
|
||||
options.Conventions.AuthorizeFolder("/Admin");
|
||||
})
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Latest);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapApplication();
|
||||
});
|
||||
|
||||
app.UseAuthorization();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// 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.Globalization;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using RazorPagesWebSite.Conventions;
|
||||
|
||||
namespace RazorPagesWebSite
|
||||
{
|
||||
public class StartupWithoutEndpointRouting
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options => options.LoginPath = "/Login");
|
||||
services.AddMvc()
|
||||
.AddMvcLocalization()
|
||||
.AddNewtonsoftJson()
|
||||
.AddRazorPagesOptions(options =>
|
||||
{
|
||||
options.Conventions.AuthorizePage("/HelloWorldWithAuth");
|
||||
options.Conventions.AuthorizeFolder("/Pages/Admin");
|
||||
options.Conventions.AllowAnonymousToPage("/Pages/Admin/Login");
|
||||
options.Conventions.AddPageRoute("/HelloWorldWithRoute", "Different-Route/{text}");
|
||||
options.Conventions.AddPageRoute("/Pages/NotTheRoot", string.Empty);
|
||||
options.Conventions.Add(new CustomModelTypeConvention());
|
||||
})
|
||||
.WithRazorPagesAtContentRoot()
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Latest);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseAuthentication();
|
||||
|
||||
app.UseStaticFiles();
|
||||
|
||||
var supportedCultures = new[]
|
||||
{
|
||||
new CultureInfo("en-US"),
|
||||
new CultureInfo("fr-FR"),
|
||||
};
|
||||
|
||||
app.UseRequestLocalization(new RequestLocalizationOptions
|
||||
{
|
||||
SupportedCultures = supportedCultures,
|
||||
SupportedUICultures = supportedCultures
|
||||
});
|
||||
|
||||
app.UseMvc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -62,7 +62,11 @@ namespace RazorWebSite
|
|||
}
|
||||
});
|
||||
|
||||
app.UseMvcWithDefaultRoute();
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapDefaultControllerRoute();
|
||||
routes.MapRazorPages();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,9 +45,11 @@ namespace RazorWebSite
|
|||
}
|
||||
});
|
||||
app.UseStaticFiles();
|
||||
|
||||
// Add MVC to the request pipeline
|
||||
app.UseMvcWithDefaultRoute();
|
||||
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapDefaultControllerRoute();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace RoutingWebSite
|
||||
|
|
@ -37,54 +36,64 @@ namespace RoutingWebSite
|
|||
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
public virtual void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvc(routes =>
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
routes.MapControllerRoute(
|
||||
"NonParameterConstraintRoute",
|
||||
"NonParameterConstraintRoute/{controller}/{action}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "NonParameterConstraint", nonParameter = new QueryStringConstraint() });
|
||||
|
||||
routes.MapRoute(
|
||||
routes.MapControllerRoute(
|
||||
"DataTokensRoute",
|
||||
"DataTokensRoute/{controller}/{action}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "DataTokens" },
|
||||
dataTokens: new { hasDataTokens = true });
|
||||
|
||||
ConfigureConventionalTransformerRoute(routes);
|
||||
routes.MapControllerRoute(
|
||||
"ConventionalTransformerRoute",
|
||||
"ConventionalTransformerRoute/{controller:slugify}/{action=Index}/{param:slugify?}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "ConventionalTransformer" });
|
||||
|
||||
routes.MapRoute(
|
||||
routes.MapControllerRoute(
|
||||
"DefaultValuesRoute_OptionalParameter",
|
||||
"DefaultValuesRoute/Optional/{controller=DEFAULTVALUES}/{action=OPTIONALPARAMETER}/{id?}/{**catchAll}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "DefaultValues", action = "OptionalParameter" });
|
||||
|
||||
routes.MapRoute(
|
||||
routes.MapControllerRoute(
|
||||
"DefaultValuesRoute_DefaultParameter",
|
||||
"DefaultValuesRoute/Default/{controller=DEFAULTVALUES}/{action=DEFAULTPARAMETER}/{id=17}/{**catchAll}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "DefaultValues", action = "DefaultParameter" });
|
||||
|
||||
routes.MapAreaRoute(
|
||||
routes.MapAreaControllerRoute(
|
||||
"flightRoute",
|
||||
"adminRoute",
|
||||
"{area:exists}/{controller}/{action}",
|
||||
defaults: new { controller = "Home", action = "Index" },
|
||||
constraints: new { area = "Travel" });
|
||||
|
||||
ConfigurePageRoute(routes);
|
||||
routes.MapControllerRoute(
|
||||
"PageRoute",
|
||||
"{controller}/{action}/{page}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "PageRoute" });
|
||||
|
||||
routes.MapRoute(
|
||||
routes.MapControllerRoute(
|
||||
"ActionAsMethod",
|
||||
"{controller}/{action}",
|
||||
defaults: new { controller = "Home", action = "Index" });
|
||||
|
||||
routes.MapRoute(
|
||||
routes.MapControllerRoute(
|
||||
"RouteWithOptionalSegment",
|
||||
"{controller}/{action}/{path?}");
|
||||
|
||||
routes.MapRazorPages();
|
||||
});
|
||||
|
||||
app.Map("/afterrouting", b => b.Run(c =>
|
||||
|
|
@ -105,23 +114,5 @@ namespace RoutingWebSite
|
|||
{
|
||||
services.AddRouting(options => options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
|
||||
}
|
||||
|
||||
protected virtual void ConfigureConventionalTransformerRoute(IRouteBuilder routes)
|
||||
{
|
||||
routes.MapRoute(
|
||||
"ConventionalTransformerRoute",
|
||||
"ConventionalTransformerRoute/{controller:slugify}/{action=Index}/{param:slugify?}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "ConventionalTransformer" });
|
||||
}
|
||||
|
||||
protected virtual void ConfigurePageRoute(IRouteBuilder routes)
|
||||
{
|
||||
routes.MapRoute(
|
||||
"PageRoute",
|
||||
"{controller}/{action}/{page}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "PageRoute" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,11 @@ namespace RoutingWebSite
|
|||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvcWithDefaultRoute();
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapDefaultControllerRoute();
|
||||
routes.MapRazorPages();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace RoutingWebSite
|
||||
{
|
||||
public class StartupWithUseMvcAndEndpointRouting : Startup
|
||||
{
|
||||
public override void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
"NonParameterConstraintRoute",
|
||||
"NonParameterConstraintRoute/{controller}/{action}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "NonParameterConstraint", nonParameter = new QueryStringConstraint() });
|
||||
|
||||
routes.MapRoute(
|
||||
"DataTokensRoute",
|
||||
"DataTokensRoute/{controller}/{action}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "DataTokens" },
|
||||
dataTokens: new { hasDataTokens = true });
|
||||
|
||||
routes.MapRoute(
|
||||
"ConventionalTransformerRoute",
|
||||
"ConventionalTransformerRoute/{controller:slugify}/{action=Index}/{param:slugify?}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "ConventionalTransformer" });
|
||||
|
||||
routes.MapRoute(
|
||||
"DefaultValuesRoute_OptionalParameter",
|
||||
"DefaultValuesRoute/Optional/{controller=DEFAULTVALUES}/{action=OPTIONALPARAMETER}/{id?}/{**catchAll}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "DefaultValues", action = "OptionalParameter" });
|
||||
|
||||
routes.MapRoute(
|
||||
"DefaultValuesRoute_DefaultParameter",
|
||||
"DefaultValuesRoute/Default/{controller=DEFAULTVALUES}/{action=DEFAULTPARAMETER}/{id=17}/{**catchAll}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "DefaultValues", action = "DefaultParameter" });
|
||||
|
||||
routes.MapAreaRoute(
|
||||
"flightRoute",
|
||||
"adminRoute",
|
||||
"{area:exists}/{controller}/{action}",
|
||||
defaults: new { controller = "Home", action = "Index" },
|
||||
constraints: new { area = "Travel" });
|
||||
|
||||
routes.MapRoute(
|
||||
"PageRoute",
|
||||
"{controller}/{action}/{page}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "PageRoute" });
|
||||
|
||||
routes.MapRoute(
|
||||
"ActionAsMethod",
|
||||
"{controller}/{action}",
|
||||
defaults: new { controller = "Home", action = "Index" });
|
||||
|
||||
routes.MapRoute(
|
||||
"RouteWithOptionalSegment",
|
||||
"{controller}/{action}/{path?}");
|
||||
});
|
||||
|
||||
app.Map("/afterrouting", b => b.Run(c =>
|
||||
{
|
||||
return c.Response.WriteAsync("Hello from middleware after routing");
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
|
@ -11,6 +13,58 @@ namespace RoutingWebSite
|
|||
{
|
||||
public class StartupWithoutEndpointRouting : Startup
|
||||
{
|
||||
public override void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
"NonParameterConstraintRoute",
|
||||
"NonParameterConstraintRoute/{controller}/{action}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "NonParameterConstraint", nonParameter = new QueryStringConstraint() });
|
||||
|
||||
routes.MapRoute(
|
||||
"DataTokensRoute",
|
||||
"DataTokensRoute/{controller}/{action}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "DataTokens" },
|
||||
dataTokens: new { hasDataTokens = true });
|
||||
|
||||
routes.MapRoute(
|
||||
"DefaultValuesRoute_OptionalParameter",
|
||||
"DefaultValuesRoute/Optional/{controller=DEFAULTVALUES}/{action=OPTIONALPARAMETER}/{id?}/{**catchAll}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "DefaultValues", action = "OptionalParameter" });
|
||||
|
||||
routes.MapRoute(
|
||||
"DefaultValuesRoute_DefaultParameter",
|
||||
"DefaultValuesRoute/Default/{controller=DEFAULTVALUES}/{action=DEFAULTPARAMETER}/{id=17}/{**catchAll}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "DefaultValues", action = "DefaultParameter" });
|
||||
|
||||
routes.MapAreaRoute(
|
||||
"flightRoute",
|
||||
"adminRoute",
|
||||
"{area:exists}/{controller}/{action}",
|
||||
defaults: new { controller = "Home", action = "Index" },
|
||||
constraints: new { area = "Travel" });
|
||||
|
||||
routes.MapRoute(
|
||||
"ActionAsMethod",
|
||||
"{controller}/{action}",
|
||||
defaults: new { controller = "Home", action = "Index" });
|
||||
|
||||
routes.MapRoute(
|
||||
"RouteWithOptionalSegment",
|
||||
"{controller}/{action}/{path?}");
|
||||
});
|
||||
|
||||
app.Map("/afterrouting", b => b.Run(c =>
|
||||
{
|
||||
return c.Response.WriteAsync("Hello from middleware after routing");
|
||||
}));
|
||||
}
|
||||
|
||||
// Do not call base implementations of these methods. Those are specific to endpoint routing.
|
||||
protected override void ConfigureMvcOptions(MvcOptions options)
|
||||
{
|
||||
|
|
@ -35,15 +89,5 @@ namespace RoutingWebSite
|
|||
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IActionDescriptorProvider>(actionDescriptorProvider));
|
||||
}
|
||||
|
||||
protected override void ConfigureConventionalTransformerRoute(IRouteBuilder routes)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
protected override void ConfigurePageRoute(IRouteBuilder routes)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,11 +32,9 @@ namespace SecurityWebSite
|
|||
{
|
||||
app.UseAuthentication();
|
||||
|
||||
app.UseMvc(routes =>
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
name: "default",
|
||||
template: "{controller=Home}/{action=Index}/{id?}");
|
||||
routes.MapDefaultControllerRoute();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,10 @@ namespace SecurityWebSite
|
|||
{
|
||||
app.UseAuthentication();
|
||||
|
||||
app.UseMvcWithDefaultRoute();
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapDefaultControllerRoute();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,10 @@ namespace SimpleWebSite
|
|||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvcWithDefaultRoute();
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapDefaultControllerRoute();
|
||||
});
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ namespace TagHelpersWebSite
|
|||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvcWithDefaultRoute();
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapDefaultControllerRoute();
|
||||
});
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
|
|
|
|||
|
|
@ -21,9 +21,12 @@ namespace VersioningWebSite
|
|||
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
public virtual void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvcWithDefaultRoute();
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapDefaultControllerRoute();
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual void ConfigureMvcOptions(MvcOptions options)
|
||||
|
|
|
|||
|
|
@ -1,12 +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 Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace VersioningWebSite
|
||||
{
|
||||
public class StartupWithoutEndpointRouting : Startup
|
||||
{
|
||||
public override void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvcWithDefaultRoute();
|
||||
}
|
||||
|
||||
protected override void ConfigureMvcOptions(MvcOptions options)
|
||||
{
|
||||
options.EnableEndpointRouting = false;
|
||||
|
|
|
|||
|
|
@ -107,10 +107,9 @@ namespace XmlFormattersWebSite
|
|||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMvc(routes =>
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapRoute("ActionAsMethod", "{controller}/{action}",
|
||||
defaults: new { controller = "Home", action = "Index" });
|
||||
routes.MapDefaultControllerRoute();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ namespace Company.WebApplication1
|
|||
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapApplication();
|
||||
routes.MapRazorPages();
|
||||
});
|
||||
|
||||
app.UseCookiePolicy();
|
||||
|
|
|
|||
|
|
@ -159,10 +159,10 @@ namespace Company.WebApplication1
|
|||
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapApplication();
|
||||
routes.MapControllerRoute(
|
||||
name: "default",
|
||||
template: "{controller=Home}/{action=Index}/{id?}");
|
||||
routes.MapRazorPages();
|
||||
});
|
||||
|
||||
app.UseCookiePolicy();
|
||||
|
|
|
|||
|
|
@ -41,11 +41,11 @@ type Startup private () =
|
|||
app.UseStaticFiles() |> ignore
|
||||
|
||||
app.UseRouting(fun routes ->
|
||||
routes.MapApplication() |> ignore
|
||||
routes.MapControllerRoute(
|
||||
name = "default",
|
||||
template = "{controller=Home}/{action=Index}/{id?}") |> ignore
|
||||
) |> ignore
|
||||
routes.MapRazorPages() |> ignore
|
||||
|
||||
app.UseAuthorization() |> ignore
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ namespace Company.WebApplication1
|
|||
|
||||
app.UseRouting(routes =>
|
||||
{
|
||||
routes.MapApplication();
|
||||
routes.MapControllers();
|
||||
});
|
||||
|
||||
#if (OrganizationalAuth || IndividualAuth)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ type Startup private () =
|
|||
|
||||
#endif
|
||||
app.UseRouting(fun routes ->
|
||||
routes.MapApplication() |> ignore
|
||||
routes.MapControllers() |> ignore
|
||||
) |> ignore
|
||||
|
||||
app.UseAuthorization() |> ignore
|
||||
|
|
|
|||
Loading…
Reference in New Issue