[Fixes #4991] Misleading MissingMethodException message for incorrect routes

This commit is contained in:
Kiran Challa 2016-07-19 09:39:45 -07:00
parent d20cb17c52
commit 714c3d6659
11 changed files with 136 additions and 36 deletions

View File

@ -69,12 +69,25 @@ namespace Microsoft.AspNetCore.Routing
if (!typeof(IRouteConstraint).GetTypeInfo().IsAssignableFrom(constraintType.GetTypeInfo()))
{
throw new InvalidOperationException(
throw new RouteCreationException(
Resources.FormatDefaultInlineConstraintResolver_TypeNotConstraint(
constraintType, constraintKey, typeof(IRouteConstraint).Name));
}
return CreateConstraint(constraintType, argumentString);
try
{
return CreateConstraint(constraintType, argumentString);
}
catch (RouteCreationException)
{
throw;
}
catch (Exception exception)
{
throw new RouteCreationException(
$"An error occurred while trying to create an instance of route constraint '{constraintType.FullName}'.",
exception);
}
}
private static IRouteConstraint CreateConstraint(Type constraintType, string argumentString)
@ -107,7 +120,7 @@ namespace Microsoft.AspNetCore.Routing
if (constructorMatches == 0)
{
throw new InvalidOperationException(
throw new RouteCreationException(
Resources.FormatDefaultInlineConstraintResolver_CouldNotFindCtor(
constraintTypeInfo.Name, arguments.Length));
}
@ -118,7 +131,7 @@ namespace Microsoft.AspNetCore.Routing
}
else
{
throw new InvalidOperationException(
throw new RouteCreationException(
Resources.FormatDefaultInlineConstraintResolver_AmbiguousCtors(
constraintTypeInfo.Name, arguments.Length));
}

View File

@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Builder
/// <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
/// An object that contains default values for route parameters. The object's properties represent the names
/// and values of the default values.
/// </param>
/// <returns>A reference to this instance after the operation has completed.</returns>
@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Builder
{
if (routeBuilder.DefaultHandler == null)
{
throw new InvalidOperationException(Resources.DefaultHandler_MustBeSet);
throw new RouteCreationException(Resources.DefaultHandler_MustBeSet);
}
var inlineConstraintResolver = routeBuilder

View File

@ -442,6 +442,22 @@ namespace Microsoft.AspNetCore.Routing
return string.Format(CultureInfo.CurrentCulture, GetString("UnableToFindServices"), p0, p1, p2);
}
/// <summary>
/// An error occurred while creating the route with name '{0}' and template '{1}'.
/// </summary>
internal static string TemplateRoute_Exception
{
get { return GetString("TemplateRoute_Exception"); }
}
/// <summary>
/// An error occurred while creating the route with name '{0}' and template '{1}'.
/// </summary>
internal static string FormatTemplateRoute_Exception(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_Exception"), p0, p1);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -198,4 +198,7 @@
<data name="UnableToFindServices" xml:space="preserve">
<value>Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code.</value>
</data>
<data name="TemplateRoute_Exception" xml:space="preserve">
<value>An error occurred while creating the route with name '{0}' and template '{1}'.</value>
</data>
</root>

View File

@ -46,12 +46,19 @@ namespace Microsoft.AspNetCore.Routing
ConstraintResolver = constraintResolver;
DataTokens = dataTokens ?? new RouteValueDictionary();
// Data we parse from the template will be used to fill in the rest of the constraints or
// defaults. The parser will throw for invalid routes.
ParsedTemplate = TemplateParser.Parse(template);
try
{
// Data we parse from the template will be used to fill in the rest of the constraints or
// defaults. The parser will throw for invalid routes.
ParsedTemplate = TemplateParser.Parse(template);
Constraints = GetConstraints(constraintResolver, ParsedTemplate, constraints);
Defaults = GetDefaults(ParsedTemplate, defaults);
Constraints = GetConstraints(constraintResolver, ParsedTemplate, constraints);
Defaults = GetDefaults(ParsedTemplate, defaults);
}
catch (Exception exception)
{
throw new RouteCreationException(Resources.FormatTemplateRoute_Exception(name, template), exception);
}
}
public virtual IDictionary<string, IRouteConstraint> Constraints { get; protected set; }

View File

@ -111,7 +111,7 @@ namespace Microsoft.AspNetCore.Routing
var regexPattern = value as string;
if (regexPattern == null)
{
throw new InvalidOperationException(
throw new RouteCreationException(
Resources.FormatRouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint(
key,
value,

View File

@ -0,0 +1,33 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Routing
{
/// <summary>
/// The exception that is thrown for invalid routes or constraints.
/// </summary>
public class RouteCreationException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="RouteCreationException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public RouteCreationException(string message)
: base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RouteCreationException"/> class with a specified error message
/// and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception.</param>
public RouteCreationException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@ -46,8 +46,9 @@ namespace Microsoft.AspNetCore.Routing.Tests
public void ResolveConstraint_IntConstraintWithArgument_Throws()
{
// Arrange, Act & Assert
var ex = Assert.Throws<InvalidOperationException>(
var ex = Assert.Throws<RouteCreationException>(
() => _constraintResolver.ResolveConstraint("int(5)"));
Assert.Equal("Could not find a constructor for constraint type 'IntRouteConstraint'" +
" with the following number of parameters: 1.",
ex.Message);
@ -276,7 +277,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
var resolver = GetInlineConstraintResolver(routeOptions);
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => resolver.ResolveConstraint("custom"));
var ex = Assert.Throws<RouteCreationException>(() => resolver.ResolveConstraint("custom"));
Assert.Equal("The constraint type 'System.String' which is mapped to constraint key 'custom'" +
" must implement the 'IRouteConstraint' interface.",
ex.Message);
@ -291,7 +292,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
var resolver = GetInlineConstraintResolver(routeOptions);
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => resolver.ResolveConstraint("custom(5,6)"));
var ex = Assert.Throws<RouteCreationException>(() => resolver.ResolveConstraint("custom(5,6)"));
Assert.Equal("The constructor to use for activating the constraint type 'MultiConstructorRouteConstraint' is ambiguous." +
" Multiple constructors were found with the following number of parameters: 2.",
ex.Message);
@ -317,7 +318,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
{
// Arrange
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => _constraintResolver.ResolveConstraint("int(5,6)"));
var ex = Assert.Throws<RouteCreationException>(() => _constraintResolver.ResolveConstraint("int(5,6)"));
Assert.Equal("Could not find a constructor for constraint type 'IntRouteConstraint'" +
" with the following number of parameters: 2.",
ex.Message);

View File

@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Routing
var builder = CreateBuilder("{controller}/{action}");
// Act & Assert
ExceptionAssert.Throws<InvalidOperationException>(
ExceptionAssert.Throws<RouteCreationException>(
() => builder.AddConstraint("controller", 5),
"The constraint entry 'controller' - '5' on the route " +
"'{controller}/{action}' must have a string value or be of a type which implements '" +
@ -117,7 +117,7 @@ namespace Microsoft.AspNetCore.Routing
[Fact]
public void AddResolvedConstraint_SetOptionalParameter_AfterAddingTheParameter()
{
var builder = CreateBuilder("{controller}/{action}/{id}");
var builder = CreateBuilder("{controller}/{action}/{id}");
builder.AddResolvedConstraint("id", "int");
builder.SetOptional("id");
@ -140,7 +140,7 @@ namespace Microsoft.AspNetCore.Routing
Assert.Equal(1, result.Count);
Assert.Equal("name", result.First().Key);
Assert.IsType<OptionalRouteConstraint>(Assert.Single(result).Value);
var optionalConstraint = (OptionalRouteConstraint)result.First().Value;
var optionalConstraint = (OptionalRouteConstraint)result.First().Value;
var compositeConstraint = Assert.IsType<CompositeRouteConstraint>(optionalConstraint.InnerConstraint); ;
Assert.Equal(compositeConstraint.Constraints.Count(), 2);

View File

@ -30,11 +30,8 @@ namespace Microsoft.AspNetCore.Routing
// Arrange
var template = @"{controller}/{action}/ {p1:regex(abc} ";
var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
var expected = "The constraint entry 'p1' - 'regex(abc' on the route " +
"'{controller}/{action}/ {p1:regex(abc} ' could not be resolved by the constraint resolver of type " +
"'IInlineConstraintResolverProxy'.";
var exception = Assert.Throws<InvalidOperationException>(
var exception = Assert.Throws<RouteCreationException>(
() => new Route(
mockTarget.Object,
template,
@ -43,7 +40,15 @@ namespace Microsoft.AspNetCore.Routing
dataTokens: null,
inlineConstraintResolver: _inlineConstraintResolver));
var expected = "An error occurred while creating the route with name '' and template" +
$" '{template}'.";
Assert.Equal(expected, exception.Message);
Assert.NotNull(exception.InnerException);
expected = "The constraint entry 'p1' - 'regex(abc' on the route " +
"'{controller}/{action}/ {p1:regex(abc} ' could not be resolved by the constraint resolver of type " +
"'IInlineConstraintResolverProxy'.";
Assert.Equal(expected, exception.InnerException.Message);
}
[Fact]
@ -1460,14 +1465,21 @@ namespace Microsoft.AspNetCore.Routing
var routeBuilder = CreateRouteBuilder();
// Assert
ExceptionAssert.Throws<InvalidOperationException>(
var expectedMessage = "An error occurred while creating the route with name 'mockName' and template" +
" '{controller}/{action}'.";
var exception = ExceptionAssert.Throws<RouteCreationException>(
() => routeBuilder.MapRoute("mockName",
"{controller}/{action}",
defaults: null,
constraints: new { controller = "a.*", action = 17 }),
"The constraint entry 'action' - '17' on the route '{controller}/{action}' " +
expectedMessage);
expectedMessage = "The constraint entry 'action' - '17' on the route '{controller}/{action}' " +
"must have a string value or be of a type which implements '" +
typeof(IRouteConstraint) + "'.");
typeof(IRouteConstraint) + "'.";
Assert.NotNull(exception.InnerException);
Assert.Equal(expectedMessage, exception.InnerException.Message);
}
[Fact]

View File

@ -58,46 +58,54 @@ namespace Microsoft.AspNetCore.Routing.Tests
var routeBuilder = CreateRouteBuilder();
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(
var ex = Assert.Throws<RouteCreationException>(
() => routeBuilder.MapRoute("mockName",
"{controller}/{action}/{id:int=12}",
defaults: new { id = 13 },
constraints: null));
var message = "The route parameter 'id' has both an inline default value and an explicit default" +
" value specified. A route parameter cannot contain an inline default value when" +
" a default value is specified explicitly. Consider removing one of them.";
var message = "An error occurred while creating the route with name 'mockName' and template" +
" '{controller}/{action}/{id:int=12}'.";
Assert.Equal(message, ex.Message);
Assert.NotNull(ex.InnerException);
message = "The route parameter 'id' has both an inline default value and an explicit default" +
" value specified. A route parameter cannot contain an inline default value when" +
" a default value is specified explicitly. Consider removing one of them.";
Assert.Equal(message, ex.InnerException.Message);
}
[Fact]
public void EmptyDefaultValue_WithOptionalParameter_Throws()
{
// Arrange
var message = "An optional parameter cannot have default value." + Environment.NewLine +
"Parameter name: routeTemplate";
var routeBuilder = CreateRouteBuilder();
// Act & Assert
var ex = Assert.Throws<ArgumentException>(
var ex = Assert.Throws<RouteCreationException>(
() => routeBuilder.MapRoute("mockName",
"{controller}/{action}/{id:int=?}",
defaults: new { id = 13 },
constraints: null));
var message = "An error occurred while creating the route with name 'mockName' and template" +
" '{controller}/{action}/{id:int=?}'.";
Assert.Equal(message, ex.Message);
Assert.NotNull(ex.InnerException);
message = "An optional parameter cannot have default value." + Environment.NewLine +
"Parameter name: routeTemplate";
Assert.Equal(message, ex.InnerException.Message);
}
[Fact]
public void NonEmptyDefaultValue_WithOptionalParameter_Throws()
{
// Arrange
var message = "An optional parameter cannot have default value." + Environment.NewLine +
"Parameter name: routeTemplate";
var routeBuilder = CreateRouteBuilder();
// Act & Assert
var ex = Assert.Throws<ArgumentException>(() =>
var ex = Assert.Throws<RouteCreationException>(() =>
{
routeBuilder.MapRoute(
"mockName",
@ -106,7 +114,14 @@ namespace Microsoft.AspNetCore.Routing.Tests
constraints: null);
});
var message = "An error occurred while creating the route with name 'mockName' and template" +
" '{controller}/{action}/{id:int=12?}'.";
Assert.Equal(message, ex.Message);
Assert.NotNull(ex.InnerException);
message = "An optional parameter cannot have default value." + Environment.NewLine +
"Parameter name: routeTemplate";
Assert.Equal(message, ex.InnerException.Message);
}
private static IRouteBuilder CreateRouteBuilder()