Error message for [ApiController] without attribute route needs to be better

Fixes #7277
This commit is contained in:
Pranav K 2018-01-23 08:56:13 -08:00
parent f35e29e87b
commit a74ef9dfd9
12 changed files with 61 additions and 19 deletions

View File

@ -69,6 +69,7 @@
<MicrosoftExtensionsPropertyActivatorSourcesPackageVersion>2.1.0-preview1-28157</MicrosoftExtensionsPropertyActivatorSourcesPackageVersion> <MicrosoftExtensionsPropertyActivatorSourcesPackageVersion>2.1.0-preview1-28157</MicrosoftExtensionsPropertyActivatorSourcesPackageVersion>
<MicrosoftExtensionsPropertyHelperSourcesPackageVersion>2.1.0-preview1-28157</MicrosoftExtensionsPropertyHelperSourcesPackageVersion> <MicrosoftExtensionsPropertyHelperSourcesPackageVersion>2.1.0-preview1-28157</MicrosoftExtensionsPropertyHelperSourcesPackageVersion>
<MicrosoftExtensionsSecurityHelperSourcesPackageVersion>2.1.0-preview1-28157</MicrosoftExtensionsSecurityHelperSourcesPackageVersion> <MicrosoftExtensionsSecurityHelperSourcesPackageVersion>2.1.0-preview1-28157</MicrosoftExtensionsSecurityHelperSourcesPackageVersion>
<MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>2.1.0-preview1-28157</MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>
<MicrosoftExtensionsWebEncodersPackageVersion>2.1.0-preview1-28157</MicrosoftExtensionsWebEncodersPackageVersion> <MicrosoftExtensionsWebEncodersPackageVersion>2.1.0-preview1-28157</MicrosoftExtensionsWebEncodersPackageVersion>
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion> <MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
<MicrosoftNETCoreApp21PackageVersion>2.1.0-preview1-26122-01</MicrosoftNETCoreApp21PackageVersion> <MicrosoftNETCoreApp21PackageVersion>2.1.0-preview1-26122-01</MicrosoftNETCoreApp21PackageVersion>

View File

@ -9,10 +9,11 @@ using System.Reflection;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{ {
[DebuggerDisplay("{Controller.ControllerType.Name}.{ActionMethod.Name}")] [DebuggerDisplay("{DisplayName}")]
public class ActionModel : ICommonModel, IFilterModel, IApiExplorerModel public class ActionModel : ICommonModel, IFilterModel, IApiExplorerModel
{ {
public ActionModel( public ActionModel(
@ -123,5 +124,20 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
string ICommonModel.Name => ActionName; string ICommonModel.Name => ActionName;
public IList<SelectorModel> Selectors { get; } public IList<SelectorModel> Selectors { get; }
public string DisplayName
{
get
{
if (Controller == null)
{
return ActionMethod.Name;
}
var controllerType = TypeNameHelper.GetTypeDisplayName(Controller.ControllerType);
var controllerAssembly = Controller?.ControllerType.Assembly.GetName().Name;
return $"{controllerType}.{ActionMethod.Name} ({controllerAssembly})";
}
}
} }
} }

View File

@ -8,10 +8,11 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{ {
[DebuggerDisplay("Name={ControllerName}, Type={ControllerType.Name}")] [DebuggerDisplay("{DisplayName}")]
public class ControllerModel : ICommonModel, IFilterModel, IApiExplorerModel public class ControllerModel : ICommonModel, IFilterModel, IApiExplorerModel
{ {
public ControllerModel( public ControllerModel(
@ -118,5 +119,15 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
public IDictionary<object, object> Properties { get; } public IDictionary<object, object> Properties { get; }
public IList<SelectorModel> Selectors { get; } public IList<SelectorModel> Selectors { get; }
public string DisplayName
{
get
{
var controllerType = TypeNameHelper.GetTypeDisplayName(ControllerType);
var controllerAssembly = ControllerType.Assembly.GetName().Name;
return $"{controllerType} ({controllerAssembly})";
}
}
} }
} }

View File

@ -6,6 +6,7 @@ using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Mvc.Controllers namespace Microsoft.AspNetCore.Mvc.Controllers
{ {
@ -29,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.Controllers
base.DisplayName = string.Format( base.DisplayName = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"{0}.{1} ({2})", "{0}.{1} ({2})",
ControllerTypeInfo.FullName, TypeNameHelper.GetTypeDisplayName(ControllerTypeInfo),
MethodInfo.Name, MethodInfo.Name,
ControllerTypeInfo.Assembly.GetName().Name); ControllerTypeInfo.Assembly.GetName().Name);
} }

View File

@ -114,7 +114,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
if (!controllerHasSelectorModel && !actionModel.Selectors.Any(s => s.AttributeRouteModel != null)) if (!controllerHasSelectorModel && !actionModel.Selectors.Any(s => s.AttributeRouteModel != null))
{ {
// Require attribute routing with controllers annotated with ApiControllerAttribute // Require attribute routing with controllers annotated with ApiControllerAttribute
throw new InvalidOperationException(Resources.FormatApiController_AttributeRouteRequired(nameof(ApiControllerAttribute))); var message = Resources.FormatApiController_AttributeRouteRequired(
actionModel.DisplayName,
nameof(ApiControllerAttribute));
throw new InvalidOperationException(message);
} }
} }

View File

@ -298,7 +298,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
actionModel.RouteValues.Add(routeValueProvider.RouteKey, routeValueProvider.RouteValue); actionModel.RouteValues.Add(routeValueProvider.RouteKey, routeValueProvider.RouteValue);
} }
//TODO: modify comment
// Now we need to determine the action selection info (cross-section of routes and constraints) // Now we need to determine the action selection info (cross-section of routes and constraints)
// //
// For attribute routes on a action, we want to support 'overriding' routes on a // For attribute routes on a action, we want to support 'overriding' routes on a
@ -313,9 +312,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
while (true) while (true)
{ {
routeAttributes = currentMethodInfo routeAttributes = currentMethodInfo
.GetCustomAttributes(inherit: false) .GetCustomAttributes(inherit: false)
.OfType<IRouteTemplateProvider>() .OfType<IRouteTemplateProvider>()
.ToArray(); .ToArray();
if (routeAttributes.Length > 0) if (routeAttributes.Length > 0)
{ {
@ -679,4 +678,4 @@ namespace Microsoft.AspNetCore.Mvc.Internal
typeof(IEnumerable<IFormFile>).IsAssignableFrom(parameterType); typeof(IEnumerable<IFormFile>).IsAssignableFrom(parameterType);
} }
} }
} }

View File

@ -37,6 +37,7 @@ Microsoft.AspNetCore.Mvc.RouteAttribute</Description>
<PackageReference Include="Microsoft.Extensions.PropertyActivator.Sources" PrivateAssets="All" Version="$(MicrosoftExtensionsPropertyActivatorSourcesPackageVersion)" /> <PackageReference Include="Microsoft.Extensions.PropertyActivator.Sources" PrivateAssets="All" Version="$(MicrosoftExtensionsPropertyActivatorSourcesPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.PropertyHelper.Sources" PrivateAssets="All" Version="$(MicrosoftExtensionsPropertyHelperSourcesPackageVersion)" /> <PackageReference Include="Microsoft.Extensions.PropertyHelper.Sources" PrivateAssets="All" Version="$(MicrosoftExtensionsPropertyHelperSourcesPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.SecurityHelper.Sources" PrivateAssets="All" Version="$(MicrosoftExtensionsSecurityHelperSourcesPackageVersion)" /> <PackageReference Include="Microsoft.Extensions.SecurityHelper.Sources" PrivateAssets="All" Version="$(MicrosoftExtensionsSecurityHelperSourcesPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.TypeNameHelper.Sources" PrivateAssets="All" Version="$(MicrosoftExtensionsTypeNameHelperSourcesPackageVersion)" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="$(SystemDiagnosticsDiagnosticSourcePackageVersion)" /> <PackageReference Include="System.Diagnostics.DiagnosticSource" Version="$(SystemDiagnosticsDiagnosticSourcePackageVersion)" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensionsPackageVersion)" /> <PackageReference Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensionsPackageVersion)" />
</ItemGroup> </ItemGroup>

View File

@ -1341,7 +1341,7 @@ namespace Microsoft.AspNetCore.Mvc.Core
=> GetString("ValidationProblemDescription_Title"); => GetString("ValidationProblemDescription_Title");
/// <summary> /// <summary>
/// Action methods on controllers annotated with {0} must have an attribute route. /// Action '{0}' does not have an attribute route. Action methods on controllers annotated with {1} must be attribute routed.
/// </summary> /// </summary>
internal static string ApiController_AttributeRouteRequired internal static string ApiController_AttributeRouteRequired
{ {
@ -1349,10 +1349,10 @@ namespace Microsoft.AspNetCore.Mvc.Core
} }
/// <summary> /// <summary>
/// Action methods on controllers annotated with {0} must have an attribute route. /// Action '{0}' does not have an attribute route. Action methods on controllers annotated with {1} must be attribute routed.
/// </summary> /// </summary>
internal static string FormatApiController_AttributeRouteRequired(object p0) internal static string FormatApiController_AttributeRouteRequired(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("ApiController_AttributeRouteRequired"), p0); => string.Format(CultureInfo.CurrentCulture, GetString("ApiController_AttributeRouteRequired"), p0, p1);
/// <summary> /// <summary>
/// No file provider has been configured to process the supplied file. /// No file provider has been configured to process the supplied file.

View File

@ -416,9 +416,9 @@
<value>One or more validation errors occurred.</value> <value>One or more validation errors occurred.</value>
</data> </data>
<data name="ApiController_AttributeRouteRequired" xml:space="preserve"> <data name="ApiController_AttributeRouteRequired" xml:space="preserve">
<value>Action methods on controllers annotated with {0} must have an attribute route.</value> <value>Action '{0}' does not have an attribute route. Action methods on controllers annotated with {1} must be attribute routed.</value>
</data> </data>
<data name="VirtualFileResultExecutor_NoFileProviderConfigured" xml:space="preserve"> <data name="VirtualFileResultExecutor_NoFileProviderConfigured" xml:space="preserve">
<value>No file provider has been configured to process the supplied file.</value> <value>No file provider has been configured to process the supplied file.</value>
</data> </data>
</root> </root>

View File

@ -125,6 +125,11 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
// Ensure non-default value // Ensure non-default value
Assert.NotEqual(value1, Activator.CreateInstance(property.PropertyType)); Assert.NotEqual(value1, Activator.CreateInstance(property.PropertyType));
} }
else if (property.Name.Equals(nameof(ActionModel.DisplayName)))
{
// DisplayName is re-calculated, hence reference equality wouldn't work.
Assert.Equal(value1, value2);
}
else else
{ {
Assert.Same(value1, value2); Assert.Same(value1, value2);
@ -153,4 +158,4 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
public string RouteValue { get; set; } public string RouteValue { get; set; }
} }
} }
} }

View File

@ -132,6 +132,11 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
// Ensure non-default value // Ensure non-default value
Assert.NotEqual(value1, Activator.CreateInstance(property.PropertyType)); Assert.NotEqual(value1, Activator.CreateInstance(property.PropertyType));
} }
else if (property.Name.Equals(nameof(ControllerModel.DisplayName)))
{
// DisplayName is re-calculated, hence reference equality wouldn't work.
Assert.Equal(value1, value2);
}
else else
{ {
Assert.Same(value1, value2); Assert.Same(value1, value2);

View File

@ -148,14 +148,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public void OnProvidersExecuting_ThrowsIfControllerWithAttribute_HasActionsWithoutAttributeRouting() public void OnProvidersExecuting_ThrowsIfControllerWithAttribute_HasActionsWithoutAttributeRouting()
{ {
// Arrange // Arrange
var actionName = $"{typeof(ActionsWithoutAttributeRouting).FullName}.{nameof(ActionsWithoutAttributeRouting.Index)} ({typeof(ActionsWithoutAttributeRouting).Assembly.GetName().Name})";
var expected = $"Action '{actionName}' does not have an attribute route. Action methods on controllers annotated with ApiControllerAttribute must be attribute routed.";
var context = GetContext(typeof(ActionsWithoutAttributeRouting)); var context = GetContext(typeof(ActionsWithoutAttributeRouting));
var provider = GetProvider(); var provider = GetProvider();
// Act & Assert // Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => provider.OnProvidersExecuting(context)); var ex = Assert.Throws<InvalidOperationException>(() => provider.OnProvidersExecuting(context));
Assert.Equal( Assert.Equal(expected, ex.Message);
"Action methods on controllers annotated with ApiControllerAttribute must have an attribute route.",
ex.Message);
} }
[Fact] [Fact]