902 lines
41 KiB
C#
902 lines
41 KiB
C#
// 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.Mvc.Routing;
|
|
using Microsoft.AspNetCore.Routing;
|
|
using Microsoft.AspNetCore.Routing.Matching;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Options;
|
|
using Microsoft.Extensions.Primitives;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AspNetCore.Mvc.Internal
|
|
{
|
|
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 routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata<RouteValuesAddressMetadata>();
|
|
Assert.NotNull(routeValuesAddressMetadata);
|
|
var endpointValue = routeValuesAddressMetadata.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);
|
|
}
|
|
|
|
public static TheoryData GetSingleActionData_Conventional
|
|
{
|
|
get => GetSingleActionData(true);
|
|
}
|
|
|
|
public static TheoryData GetSingleActionData_Attribute
|
|
{
|
|
get => GetSingleActionData(false);
|
|
}
|
|
|
|
private static TheoryData GetSingleActionData(bool isConventionalRouting)
|
|
{
|
|
var data = new TheoryData<string, string[]>
|
|
{
|
|
{"{controller}/{action}/{id?}", new[] { "TestController/TestAction/{id?}" }},
|
|
{"{controller}/{id?}", isConventionalRouting ? new string[] { } : new[] { "TestController/{id?}" }},
|
|
{"{action}/{id?}", isConventionalRouting ? new string[] { } : new[] { "TestAction/{id?}" }},
|
|
{"{Controller}/{Action}/{id?}", new[] { "TestController/TestAction/{id?}" }},
|
|
{"{CONTROLLER}/{ACTION}/{id?}", new[] { "TestController/TestAction/{id?}" }},
|
|
{"{controller}/{action=TestAction}", new[] { "TestController", "TestController/TestAction" }},
|
|
{"{controller}/{action=TestAction}/{id?}", new[] { "TestController", "TestController/TestAction/{id?}" }},
|
|
{"{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestController", "TestController/TestAction/{id?}" }},
|
|
{"{controller}/{action}/{*catchAll}", new[] { "TestController/TestAction/{*catchAll}" }},
|
|
{"{controller}/{action=TestAction}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{*catchAll}" }},
|
|
{"{controller}/{action=TestAction}/{id?}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{*catchAll}" }},
|
|
{"{controller}/{action}.{ext?}", new[] { "TestController/TestAction.{ext?}" }},
|
|
{"{controller}/{action=TestAction}.{ext?}", new[] { "TestController", "TestController/TestAction.{ext?}" }},
|
|
{"{controller:upper-case}/{action=TestAction}.{ext?}", new[] { "TESTCONTROLLER", "TESTCONTROLLER/TestAction.{ext?}" }},
|
|
};
|
|
|
|
return data;
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(GetSingleActionData_Conventional))]
|
|
public void Endpoints_Conventional_SingleAction(string endpointInfoRoute, string[] finalEndpointPatterns)
|
|
{
|
|
// Arrange
|
|
var actionDescriptorCollection = GetActionDescriptorCollection(
|
|
new { controller = "TestController", action = "TestAction" });
|
|
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
|
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute));
|
|
|
|
// Act
|
|
var endpoints = dataSource.Endpoints;
|
|
|
|
// Assert
|
|
var inspectors = finalEndpointPatterns
|
|
.Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText)))
|
|
.ToArray();
|
|
|
|
// Assert
|
|
Assert.Collection(endpoints, inspectors);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(GetSingleActionData_Attribute))]
|
|
public void Endpoints_AttributeRouting_SingleAction(string endpointInfoRoute, string[] finalEndpointPatterns)
|
|
{
|
|
// Arrange
|
|
var actionDescriptorCollection = GetActionDescriptorCollection(
|
|
attributeRouteTemplate: endpointInfoRoute,
|
|
new { controller = "TestController", action = "TestAction" });
|
|
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
|
|
|
// Act
|
|
var endpoints = dataSource.Endpoints;
|
|
|
|
// Assert
|
|
var inspectors = finalEndpointPatterns
|
|
.Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText)))
|
|
.ToArray();
|
|
|
|
// Assert
|
|
Assert.Collection(endpoints, inspectors);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("{area}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })]
|
|
[InlineData("{controller}/{action}/{id?}", new string[] { })]
|
|
[InlineData("{area=TestArea}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })]
|
|
[InlineData("{area=TestArea}/{controller}/{action=TestAction}/{id?}", new[] { "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })]
|
|
[InlineData("{area=TestArea}/{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestArea", "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })]
|
|
[InlineData("{area:exists}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })]
|
|
[InlineData("{area:exists:upper-case}/{controller}/{action}/{id?}", new[] { "TESTAREA/TestController/TestAction/{id?}" })]
|
|
public void Endpoints_AreaSingleAction(string endpointInfoRoute, string[] finalEndpointTemplates)
|
|
{
|
|
// Arrange
|
|
var actionDescriptorCollection = GetActionDescriptorCollection(
|
|
new { controller = "TestController", action = "TestAction", area = "TestArea" });
|
|
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
|
|
|
var services = new ServiceCollection();
|
|
services.AddRouting();
|
|
services.AddSingleton(actionDescriptorCollection);
|
|
|
|
var routeOptionsSetup = new MvcCoreRouteOptionsSetup();
|
|
services.Configure<RouteOptions>(routeOptionsSetup.Configure);
|
|
services.Configure<RouteOptions>(options =>
|
|
{
|
|
options.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
|
|
});
|
|
|
|
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute, serviceProvider: services.BuildServiceProvider()));
|
|
|
|
// Act
|
|
var endpoints = dataSource.Endpoints;
|
|
|
|
// Assert
|
|
var inspectors = finalEndpointTemplates
|
|
.Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText)))
|
|
.ToArray();
|
|
|
|
// Assert
|
|
Assert.Collection(endpoints, inspectors);
|
|
}
|
|
|
|
[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,
|
|
(e) => Assert.Equal("TestController/TestAction/{page}", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
|
|
}
|
|
|
|
[Fact]
|
|
public void Endpoints_SingleAction_WithActionDefault()
|
|
{
|
|
// Arrange
|
|
var actionDescriptorCollection = GetActionDescriptorCollection(
|
|
new { controller = "TestController", action = "TestAction" });
|
|
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
|
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
|
string.Empty,
|
|
"{controller}/{action}",
|
|
new RouteValueDictionary(new { action = "TestAction" })));
|
|
|
|
// Act
|
|
var endpoints = dataSource.Endpoints;
|
|
|
|
// Assert
|
|
Assert.Collection(endpoints,
|
|
(e) => Assert.Equal("TestController", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText),
|
|
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<RouteEndpoint>(e).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" })
|
|
}, 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("TestController", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText),
|
|
(e) => Assert.Equal("TestController/TestAction", 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) => Assert.Equal("TestController", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText),
|
|
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
|
|
|
|
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) => Assert.Equal("NewTestController/NewTestAction", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
|
|
}
|
|
|
|
[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,
|
|
(e) => Assert.Equal("TestController/TestAction1", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText),
|
|
(e) => Assert.Equal("TestController/TestAction2", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("{controller}/{action}", new[] { "TestController1/TestAction1", "TestController1/TestAction2", "TestController1/TestAction3", "TestController2/TestAction1" })]
|
|
[InlineData("{controller}/{action:regex((TestAction1|TestAction2))}", new[] { "TestController1/TestAction1", "TestController1/TestAction2", "TestController2/TestAction1" })]
|
|
[InlineData("{controller}/{action:regex((TestAction1|TestAction2)):upper-case}", new[] { "TestController1/TESTACTION1", "TestController1/TESTACTION2", "TestController2/TESTACTION1" })]
|
|
public void Endpoints_MultipleActions(string endpointInfoRoute, string[] finalEndpointTemplates)
|
|
{
|
|
// Arrange
|
|
var actionDescriptorCollection = GetActionDescriptorCollection(
|
|
new { controller = "TestController1", action = "TestAction1" },
|
|
new { controller = "TestController1", action = "TestAction2" },
|
|
new { controller = "TestController1", action = "TestAction3" },
|
|
new { controller = "TestController2", action = "TestAction1" });
|
|
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
|
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
|
|
string.Empty,
|
|
endpointInfoRoute));
|
|
|
|
// Act
|
|
var endpoints = dataSource.Endpoints;
|
|
|
|
var inspectors = finalEndpointTemplates
|
|
.Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText)))
|
|
.ToArray();
|
|
|
|
// Assert
|
|
Assert.Collection(endpoints, inspectors);
|
|
}
|
|
|
|
[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 routeValuesAddressNameMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
|
Assert.NotNull(routeValuesAddressNameMetadata);
|
|
Assert.Equal(string.Empty, routeValuesAddressNameMetadata.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 routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
|
Assert.NotNull(routeValuesAddressMetadata);
|
|
Assert.Equal("namedRoute", routeValuesAddressMetadata.RouteName);
|
|
Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText);
|
|
},
|
|
(ep) =>
|
|
{
|
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
|
var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
|
Assert.NotNull(routeValuesAddressMetadata);
|
|
Assert.Equal("namedRoute", routeValuesAddressMetadata.RouteName);
|
|
Assert.Equal("named/Products/Details/{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("Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText);
|
|
Assert.Equal(1, matcherEndpoint.Order);
|
|
},
|
|
(ep) =>
|
|
{
|
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
|
Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText);
|
|
Assert.Equal(2, matcherEndpoint.Order);
|
|
},
|
|
(ep) =>
|
|
{
|
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
|
Assert.Equal("Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText);
|
|
Assert.Equal(1, matcherEndpoint.Order);
|
|
},
|
|
(ep) =>
|
|
{
|
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
|
Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText);
|
|
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>().OrderBy(e => e.RoutePattern.RawText),
|
|
e => Assert.Equal("en-CA/home/about", e.RoutePattern.RawText),
|
|
e => Assert.Equal("en-NZ/home/index", e.RoutePattern.RawText),
|
|
e => Assert.Equal("home/index", e.RoutePattern.RawText));
|
|
}
|
|
|
|
[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 expectedDefaults = new RouteValueDictionary(new { controller = "Foo", action = "Bar" });
|
|
var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: expectedDefaults);
|
|
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("Foo/Bar", matcherEndpoint.RoutePattern.RawText);
|
|
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
|
|
}
|
|
|
|
[Fact]
|
|
public void RequiredValues_NotPresent_InDefaultValues_IsAddedToDefaultValues()
|
|
{
|
|
// 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, "{subarea}/{controller=Home}/{action=Index}"));
|
|
|
|
// Act
|
|
var endpoints = dataSource.Endpoints;
|
|
|
|
// Assert
|
|
var endpoint = Assert.Single(endpoints);
|
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
|
Assert.Equal("test/Foo/Bar", matcherEndpoint.RoutePattern.RawText);
|
|
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
|
|
}
|
|
|
|
[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_IsSubsetOf_DefaultValues()
|
|
{
|
|
// Arrange
|
|
var requiredValues = new RouteValueDictionary(
|
|
new { controller = "Foo", action = "Bar", subarea = "test" });
|
|
var expectedDefaults = new RouteValueDictionary(
|
|
new { controller = "Foo", action = "Bar", subarea = "test", subscription = "general" });
|
|
var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues);
|
|
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
|
|
dataSource.ConventionalEndpointInfos.Add(
|
|
CreateEndpointInfo(
|
|
string.Empty,
|
|
"{controller=Home}/{action=Index}/{subscription=general}",
|
|
defaults: new RouteValueDictionary(new { subarea = "test", })));
|
|
|
|
// Act
|
|
var endpoints = dataSource.Endpoints;
|
|
|
|
// Assert
|
|
var endpoint = Assert.Single(endpoints);
|
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
|
Assert.Equal("Foo/Bar/{subscription=general}", matcherEndpoint.RoutePattern.RawText);
|
|
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
|
|
}
|
|
|
|
[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("Foo/Baz/{id?}", matcherEndpoint.RoutePattern.RawText);
|
|
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
|
|
}
|
|
|
|
[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);
|
|
}
|
|
|
|
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);
|
|
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>());
|
|
|
|
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));
|
|
}
|
|
|
|
var actionDescriptorCollectionProviderMock = new Mock<IActionDescriptorCollectionProvider>();
|
|
actionDescriptorCollectionProviderMock
|
|
.Setup(m => m.ActionDescriptors)
|
|
.Returns(new ActionDescriptorCollection(actionDescriptors, version: 0));
|
|
return actionDescriptorCollectionProviderMock.Object;
|
|
}
|
|
|
|
private ActionDescriptor CreateActionDescriptor(string controller, string action, string area = null)
|
|
{
|
|
return CreateActionDescriptor(new { controller = controller, action = action, area = area }, attributeRouteTemplate: null);
|
|
}
|
|
|
|
private ActionDescriptor CreateActionDescriptor(object requiredValues, string attributeRouteTemplate = 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
|
|
};
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
} |