'Multiple actions were found' RESTful Web API 路由错误
'Multiple actions were found' error with RESTful Web API Routing
我有以下 class:
public class GetLogsRequestDto
{
public LogLevel Level { get; set; }
public LogSortOrder SortOrder { get; set; }
}
我有一个 Web API 控制器 (LogsController),具有以下 2 个操作:
async Task<IHttpActionResult> Get( [FromUri]int id )
async Task<IHttpActionResult> Get( [FromUri]GetLogsRequestDto dto )
第一个用于检索特定日志,第二个用于检索日志列表。当我通过以下方式对特定日志发出 GET 请求时:/logs/123,它会正确调用第一个操作,同样,如果我对 /logs 发出 GET 请求,它会正确调用第二个操作([= 中定义的属性45=] 是可选的,不需要总是提供)。
但是,我想更改第一个 GET 方法,使其使用 class 而不是 int id 参数,如下所示(请注意,它为上面的第二个操作指定了不同的(单一)类型):
async Task<IHttpActionResult> Get( [FromUri]GetLogRequestDto dto )
这个 GetLogRequestDto
class 看起来像这样:
public class GetLogRequestDto
{
[Required]
[Range( 100, int.MaxValue )]
public int Id { get; set; }
}
我采用这种方法的原因是,我可以通过我的标准 ModelStateValidationActionFilter
对模型进行验证,并将任何特定的验证属性放入此 class,而不是在使用'int id' 参数方法,然后必须执行验证。
当我实施此方法并尝试调用 /logs/1 时,我收到以下错误:
Multiple actions were found that match the request
这两种方法中用作参数的两种不同类型没有区别。
我配置的默认路由是:
config.Routes.MapHttpRoute(
name: "controller-id",
routeTemplate: "{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
我不明白为什么会出现问题 - 为什么它以一种方式工作而不是另一种方式。
在 GET 请求中使用复杂类型来处理单个基本类型参数(这也是路由的一部分)并不是一个好主意。
通过使用这种方法,框架将无法将您的路由参数绑定到该复杂类型(路由定义需要一个必须是简单类型的 id
参数)。
我强烈建议您恢复更改并再次将 id
参数设为 int
。
作为替代方法,您可以遵循 this great post 并实施一个操作过滤器,该过滤器可以验证您的方法参数,这些参数由验证属性修饰,即使它们是简单类型也是如此。
这是 Mark Vincze 博客 post 的摘录,表示用于验证操作参数的操作过滤器属性:
public class ValidateActionParametersAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var descriptor = context.ActionDescriptor as ControllerActionDescriptor;
if (descriptor != null)
{
var parameters = descriptor.MethodInfo.GetParameters();
foreach (var parameter in parameters)
{
var argument = context.ActionArguments[parameter.Name];
EvaluateValidationAttributes(parameter, argument, context.ModelState);
}
}
base.OnActionExecuting(context);
}
private void EvaluateValidationAttributes(ParameterInfo parameter, object argument, ModelStateDictionary modelState)
{
var validationAttributes = parameter.CustomAttributes;
foreach (var attributeData in validationAttributes)
{
var attributeInstance = CustomAttributeExtensions.GetCustomAttribute(parameter, attributeData.AttributeType);
var validationAttribute = attributeInstance as ValidationAttribute;
if (validationAttribute != null)
{
var isValid = validationAttribute.IsValid(argument);
if (!isValid)
{
modelState.AddModelError(parameter.Name, validationAttribute.FormatErrorMessage(parameter.Name));
}
}
}
}
}
我有以下 class:
public class GetLogsRequestDto
{
public LogLevel Level { get; set; }
public LogSortOrder SortOrder { get; set; }
}
我有一个 Web API 控制器 (LogsController),具有以下 2 个操作:
async Task<IHttpActionResult> Get( [FromUri]int id )
async Task<IHttpActionResult> Get( [FromUri]GetLogsRequestDto dto )
第一个用于检索特定日志,第二个用于检索日志列表。当我通过以下方式对特定日志发出 GET 请求时:/logs/123,它会正确调用第一个操作,同样,如果我对 /logs 发出 GET 请求,它会正确调用第二个操作([= 中定义的属性45=] 是可选的,不需要总是提供)。
但是,我想更改第一个 GET 方法,使其使用 class 而不是 int id 参数,如下所示(请注意,它为上面的第二个操作指定了不同的(单一)类型):
async Task<IHttpActionResult> Get( [FromUri]GetLogRequestDto dto )
这个 GetLogRequestDto
class 看起来像这样:
public class GetLogRequestDto
{
[Required]
[Range( 100, int.MaxValue )]
public int Id { get; set; }
}
我采用这种方法的原因是,我可以通过我的标准 ModelStateValidationActionFilter
对模型进行验证,并将任何特定的验证属性放入此 class,而不是在使用'int id' 参数方法,然后必须执行验证。
当我实施此方法并尝试调用 /logs/1 时,我收到以下错误:
Multiple actions were found that match the request
这两种方法中用作参数的两种不同类型没有区别。
我配置的默认路由是:
config.Routes.MapHttpRoute(
name: "controller-id",
routeTemplate: "{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
我不明白为什么会出现问题 - 为什么它以一种方式工作而不是另一种方式。
在 GET 请求中使用复杂类型来处理单个基本类型参数(这也是路由的一部分)并不是一个好主意。
通过使用这种方法,框架将无法将您的路由参数绑定到该复杂类型(路由定义需要一个必须是简单类型的 id
参数)。
我强烈建议您恢复更改并再次将 id
参数设为 int
。
作为替代方法,您可以遵循 this great post 并实施一个操作过滤器,该过滤器可以验证您的方法参数,这些参数由验证属性修饰,即使它们是简单类型也是如此。
这是 Mark Vincze 博客 post 的摘录,表示用于验证操作参数的操作过滤器属性:
public class ValidateActionParametersAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var descriptor = context.ActionDescriptor as ControllerActionDescriptor;
if (descriptor != null)
{
var parameters = descriptor.MethodInfo.GetParameters();
foreach (var parameter in parameters)
{
var argument = context.ActionArguments[parameter.Name];
EvaluateValidationAttributes(parameter, argument, context.ModelState);
}
}
base.OnActionExecuting(context);
}
private void EvaluateValidationAttributes(ParameterInfo parameter, object argument, ModelStateDictionary modelState)
{
var validationAttributes = parameter.CustomAttributes;
foreach (var attributeData in validationAttributes)
{
var attributeInstance = CustomAttributeExtensions.GetCustomAttribute(parameter, attributeData.AttributeType);
var validationAttribute = attributeInstance as ValidationAttribute;
if (validationAttribute != null)
{
var isValid = validationAttribute.IsValid(argument);
if (!isValid)
{
modelState.AddModelError(parameter.Name, validationAttribute.FormatErrorMessage(parameter.Name));
}
}
}
}
}