[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())) if (!typeof(IRouteConstraint).GetTypeInfo().IsAssignableFrom(constraintType.GetTypeInfo()))
{ {
throw new InvalidOperationException( throw new RouteCreationException(
Resources.FormatDefaultInlineConstraintResolver_TypeNotConstraint( Resources.FormatDefaultInlineConstraintResolver_TypeNotConstraint(
constraintType, constraintKey, typeof(IRouteConstraint).Name)); 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) private static IRouteConstraint CreateConstraint(Type constraintType, string argumentString)
@ -107,7 +120,7 @@ namespace Microsoft.AspNetCore.Routing
if (constructorMatches == 0) if (constructorMatches == 0)
{ {
throw new InvalidOperationException( throw new RouteCreationException(
Resources.FormatDefaultInlineConstraintResolver_CouldNotFindCtor( Resources.FormatDefaultInlineConstraintResolver_CouldNotFindCtor(
constraintTypeInfo.Name, arguments.Length)); constraintTypeInfo.Name, arguments.Length));
} }
@ -118,7 +131,7 @@ namespace Microsoft.AspNetCore.Routing
} }
else else
{ {
throw new InvalidOperationException( throw new RouteCreationException(
Resources.FormatDefaultInlineConstraintResolver_AmbiguousCtors( Resources.FormatDefaultInlineConstraintResolver_AmbiguousCtors(
constraintTypeInfo.Name, arguments.Length)); 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="name">The name of the route.</param>
/// <param name="template">The URL pattern of the route.</param> /// <param name="template">The URL pattern of the route.</param>
/// <param name="defaults"> /// <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. /// and values of the default values.
/// </param> /// </param>
/// <returns>A reference to this instance after the operation has completed.</returns> /// <returns>A reference to this instance after the operation has completed.</returns>
@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Builder
{ {
if (routeBuilder.DefaultHandler == null) if (routeBuilder.DefaultHandler == null)
{ {
throw new InvalidOperationException(Resources.DefaultHandler_MustBeSet); throw new RouteCreationException(Resources.DefaultHandler_MustBeSet);
} }
var inlineConstraintResolver = routeBuilder var inlineConstraintResolver = routeBuilder

View File

@ -442,6 +442,22 @@ namespace Microsoft.AspNetCore.Routing
return string.Format(CultureInfo.CurrentCulture, GetString("UnableToFindServices"), p0, p1, p2); 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) private static string GetString(string name, params string[] formatterNames)
{ {
var value = _resourceManager.GetString(name); var value = _resourceManager.GetString(name);

View File

@ -198,4 +198,7 @@
<data name="UnableToFindServices" xml:space="preserve"> <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> <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>
<data name="TemplateRoute_Exception" xml:space="preserve">
<value>An error occurred while creating the route with name '{0}' and template '{1}'.</value>
</data>
</root> </root>

View File

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

View File

@ -111,7 +111,7 @@ namespace Microsoft.AspNetCore.Routing
var regexPattern = value as string; var regexPattern = value as string;
if (regexPattern == null) if (regexPattern == null)
{ {
throw new InvalidOperationException( throw new RouteCreationException(
Resources.FormatRouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint( Resources.FormatRouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint(
key, key,
value, 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() public void ResolveConstraint_IntConstraintWithArgument_Throws()
{ {
// Arrange, Act & Assert // Arrange, Act & Assert
var ex = Assert.Throws<InvalidOperationException>( var ex = Assert.Throws<RouteCreationException>(
() => _constraintResolver.ResolveConstraint("int(5)")); () => _constraintResolver.ResolveConstraint("int(5)"));
Assert.Equal("Could not find a constructor for constraint type 'IntRouteConstraint'" + Assert.Equal("Could not find a constructor for constraint type 'IntRouteConstraint'" +
" with the following number of parameters: 1.", " with the following number of parameters: 1.",
ex.Message); ex.Message);
@ -276,7 +277,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
var resolver = GetInlineConstraintResolver(routeOptions); var resolver = GetInlineConstraintResolver(routeOptions);
// Act & Assert // 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'" + Assert.Equal("The constraint type 'System.String' which is mapped to constraint key 'custom'" +
" must implement the 'IRouteConstraint' interface.", " must implement the 'IRouteConstraint' interface.",
ex.Message); ex.Message);
@ -291,7 +292,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
var resolver = GetInlineConstraintResolver(routeOptions); var resolver = GetInlineConstraintResolver(routeOptions);
// Act & Assert // 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." + 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.", " Multiple constructors were found with the following number of parameters: 2.",
ex.Message); ex.Message);
@ -317,7 +318,7 @@ namespace Microsoft.AspNetCore.Routing.Tests
{ {
// Arrange // Arrange
// Act & Assert // 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'" + Assert.Equal("Could not find a constructor for constraint type 'IntRouteConstraint'" +
" with the following number of parameters: 2.", " with the following number of parameters: 2.",
ex.Message); ex.Message);

View File

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

View File

@ -30,11 +30,8 @@ namespace Microsoft.AspNetCore.Routing
// Arrange // Arrange
var template = @"{controller}/{action}/ {p1:regex(abc} "; var template = @"{controller}/{action}/ {p1:regex(abc} ";
var mockTarget = new Mock<IRouter>(MockBehavior.Strict); 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( () => new Route(
mockTarget.Object, mockTarget.Object,
template, template,
@ -43,7 +40,15 @@ namespace Microsoft.AspNetCore.Routing
dataTokens: null, dataTokens: null,
inlineConstraintResolver: _inlineConstraintResolver)); inlineConstraintResolver: _inlineConstraintResolver));
var expected = "An error occurred while creating the route with name '' and template" +
$" '{template}'.";
Assert.Equal(expected, exception.Message); 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] [Fact]
@ -1460,14 +1465,21 @@ namespace Microsoft.AspNetCore.Routing
var routeBuilder = CreateRouteBuilder(); var routeBuilder = CreateRouteBuilder();
// Assert // 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", () => routeBuilder.MapRoute("mockName",
"{controller}/{action}", "{controller}/{action}",
defaults: null, defaults: null,
constraints: new { controller = "a.*", action = 17 }), 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 '" + "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] [Fact]

View File

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