diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRoute.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRoute.cs index 2caa02cbb2..158fd66c49 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRoute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRoute.cs @@ -94,14 +94,24 @@ namespace Microsoft.AspNetCore.Mvc.Internal defaults.Add(kvp.Key, kvp.Value); } - // We use the `NullRouter` as the route handler because we don't need to do anything for link - // generations. The TreeRouter does it all for us. - builder.MapOutbound( - NullRouter.Instance, - routeInfo.RouteTemplate, - defaults, - routeInfo.RouteName, - routeInfo.Order); + try + { + // We use the `NullRouter` as the route handler because we don't need to do anything for link + // generations. The TreeRouter does it all for us. + builder.MapOutbound( + NullRouter.Instance, + routeInfo.RouteTemplate, + defaults, + routeInfo.RouteName, + routeInfo.Order); + } + catch (RouteCreationException routeCreationException) + { + throw new RouteCreationException( + "An error occurred while adding a route to the route builder. " + + $"Route name '{routeInfo.RouteName}' and template '{routeInfo.RouteTemplate.TemplateText}'.", + routeCreationException); + } } // We're creating one AttributeRouteMatchingEntry per group, so we need to identify the distinct set of @@ -113,7 +123,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var handler = _handlerFactory(group.ToArray()); // Note that because we only support 'inline' defaults, each routeInfo group also has the same - // set of defaults. + // set of defaults. // // We then inject the route group as a default for the matcher so it gets passed back to MVC // for use in action selection. @@ -167,7 +177,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal e.ErrorMessage))); var message = Resources.FormatAttributeRoute_AggregateErrorMessage(Environment.NewLine, allErrors); - throw new InvalidOperationException(message); + throw new RouteCreationException(message); } return routeInfos; @@ -227,7 +237,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal public ActionDescriptor ActionDescriptor { get; set; } public string ErrorMessage { get; set; } - + public int Order { get; set; } public string RouteName { get; set; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRouteTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRouteTest.cs index 2e99324d32..4d0408ed92 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRouteTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRouteTest.cs @@ -98,7 +98,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.NotNull(context.Handler); Assert.Equal("5", context.RouteData.Values["key2"]); Assert.Same(actions[1], selected); - + // Arrange 2 - remove the action and update the collection selected = null; actions.RemoveAt(1); @@ -511,6 +511,40 @@ namespace Microsoft.AspNetCore.Mvc.Internal }); } + [Theory] + [InlineData("")] + [InlineData("GetBlogById")] + public void AttributeRoute_ThrowsRouteCreationException_ForConstraintsNotTakingArguments(string routeName) + { + // Arrange + var routeTemplate = "api/Blog/{id:int(10)}"; + var actions = new List() + { + new ActionDescriptor() + { + AttributeRouteInfo = new AttributeRouteInfo() + { + Template = routeTemplate, + Name = routeName + } + }, + }; + var expectedErrorMessage = "An error occurred while adding a route to the route builder. " + + $"Route name '{routeName}' and template '{routeTemplate}'."; + + var builder = CreateBuilder(); + var actionDescriptorProvider = CreateActionDescriptorProvider(actions); + var route = CreateRoute(CreateHandler().Object, actionDescriptorProvider.Object); + + // Act & Assert + var exception = Assert.Throws(() => + { + route.AddEntries(builder, actionDescriptorProvider.Object.ActionDescriptors); + }); + Assert.Equal(expectedErrorMessage, exception.Message); + Assert.IsType(exception.InnerException); + } + private static TreeRouteBuilder CreateBuilder() { var services = new ServiceCollection() @@ -546,7 +580,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } private static AttributeRoute CreateRoute( - IRouter handler, + IRouter handler, IActionDescriptorCollectionProvider actionDescriptorProvider) { return CreateRoute((_) => handler, actionDescriptorProvider); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRoutingTest.cs index 5b77c4d995..f6eedb826e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRoutingTest.cs @@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var routeContext = new RouteContext(new DefaultHttpContext()); // Act & Assert - var ex = await Assert.ThrowsAsync(() => route.RouteAsync(routeContext)); + var ex = await Assert.ThrowsAsync(() => route.RouteAsync(routeContext)); Assert.Equal(expectedMessage, ex.Message); } @@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var route = AttributeRouting.CreateAttributeMegaRoute(services); // Act & Assert - var ex = await Assert.ThrowsAsync(async () => + var ex = await Assert.ThrowsAsync(async () => { await route.RouteAsync(new RouteContext(new DefaultHttpContext())); }); @@ -110,7 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var route = AttributeRouting.CreateAttributeMegaRoute(services); // Act & Assert - var ex = await Assert.ThrowsAsync(async () => + var ex = await Assert.ThrowsAsync(async () => { await route.RouteAsync(new RouteContext(new DefaultHttpContext())); }); @@ -148,7 +148,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var route = AttributeRouting.CreateAttributeMegaRoute(services); // Act & Assert - var ex = await Assert.ThrowsAsync(async () => + var ex = await Assert.ThrowsAsync(async () => { await route.RouteAsync(new RouteContext(new DefaultHttpContext())); });