为什么我只有一个 Post 操作时得到 "Multiple actions were found ..."?
Why am I getting "Multiple actions were found ..." when I have exactly one Post action?
我的FBMessageController
只有以下方法:
public string Get() { ... }
[ChildActionOnly]
public SendResponse Send(ComplexType msg) { ... }
[ChildActionOnly]
public SendResponse SendImage(string x, string y) { ... }
[HttpPost]
public SendResponse Post([FromBody]AnotherComplexType yyy) { ... }
public void Put(..) { ... }
public void Delete(..) { ... }
但是,当我尝试使用 POST 向 .../api/fbMessage
发送请求时,
我得到以下异常:
"Mutliple actions were found that match the request"
WebApiConfig.Register
包含默认代码:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
导致错误的原因是什么?
你能不能尝试用这样的属性装饰你的所有动作,看看是否有任何变化
[HttpGet]
public string Get() { ... }
[ChildActionOnly]
public SendResponse Send(..) { ... }
[ChildActionOnly]
public SendResponse SendImage(..) { ... }
[HttpPost]
public SendResponse Post([FromBody]xxx yyy) { ... }
[HttpPut]
public void Put(..) { ... }
[HttpDelete]
public void Delete(..) { ... }
可能是你的 Put 和 Post 有某种冲突。
虽然您似乎已经隔离了问题,但我进行了以下集成测试以重现您的问题并帮助解释导致错误的原因。
[TestClass]
public class FBMessageControllerTests {
[TestMethod]
public async Task HttpClient_Should_Get_OKStatus_From_Post_To_FBMessage() {
using (var server = new TestServer()) {
var config = server.Configuration;
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var handlerMock = new Mock<IExceptionHandler>();
handlerMock
.Setup(m => m.HandleAsync(It.IsAny<ExceptionHandlerContext>(), It.IsAny<System.Threading.CancellationToken>()))
.Callback<ExceptionHandlerContext, CancellationToken>((context, token) => {
var innerException = context.ExceptionContext.Exception;
Assert.Fail(innerException.Message);
});
config.Services.Replace(typeof(IExceptionHandler), handlerMock.Object);
var client = server.CreateClient();
string url = "http://localhost/api/fbMessage";
var body = new { body = "Hello World" };
using (var response = await client.PostAsJsonAsync(url, body)) {
var message = await response.Content.ReadAsStringAsync();
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, message);
}
}
}
[Authorize]
public class FBMessageController : ApiController {
public string Get() {
return null;
}
[System.Web.Mvc.ChildActionOnly]
public SendResponse Send(ComplexType test) {
return null;
}
[System.Web.Mvc.ChildActionOnly]
public SendResponse SendImage(string x, string y) {
return null;
}
[HttpPost]
public SendResponse Post([FromBody]AnotherComplexType body) {
return null;
}
public void Put(string gbody) {
return;
}
public void Delete(string body) {
return;
}
}
public class SendResponse { }
public class ComplexType { }
public class AnotherComplexType { }
}
产生以下结果消息:
Multiple actions were found that match the request:
Send on type MiscUnitTests+FBMessageControllerTests+FBMessageController
Post on type MiscUnitTests+FBMessageControllerTests+FBMessageController
以上消息准确地告诉了您问题的原因。该框架通常会告诉您为什么会出现某些错误。有时你必须知道去哪里看。
参考以下内容Routing and Action Selection in ASP.NET Web API,这就是您遇到问题的原因。
here is the action selection algorithm.
- Create a list of all actions on the controller that match the HTTP request method.
- If the route dictionary has an "action" entry, remove actions whose name does not match this value.
- Try to match action parameters to the URI, as follows:
- For each action, get a list of the parameters that are a simple type, where the binding gets the parameter from the URI.
Exclude optional parameters.
- From this list, try to find a match for each parameter name, either in the route dictionary or in the URI query string. Matches are
case insensitive and do not depend on the parameter order.
- Select an action where every parameter in the list has a match in the URI.
- If more that one action meets these criteria, pick the one with the most parameter matches.
- Ignore actions with the [NonAction] attribute.
Step #3 is probably the most confusing. The basic idea is that a
parameter can get its value either from the URI, from the request
body, or from a custom binding. For parameters that come from the URI,
we want to ensure that the URI actually contains a value for that
parameter, either in the path (via the route dictionary) or in the
query string.
For example, consider the following action:
public void Get(int id)
The id parameter binds to the URI. Therefore, this action can only
match a URI that contains a value for "id", either in the route
dictionary or in the query string.
Optional parameters are an exception, because they are optional. For
an optional parameter, it's OK if the binding can't get the value from
the URI.
Complex types are an exception for a different reason. A complex type
can only bind to the URI through a custom binding. But in that case,
the framework cannot know in advance whether the parameter would bind
to a particular URI. To find out, it would need to invoke the binding.
The goal of the selection algorithm is to select an action from the
static description, before invoking any bindings. Therefore, complex
types are excluded from the matching algorithm.
After the action is selected, all parameter bindings are invoked.
Summary:
- The action must match the HTTP method of the request.
- The action name must match the "action" entry in the route dictionary, if present.
- For every parameter of the action, if the parameter is taken from the URI, then the parameter name must be found either in the route
dictionary or in the URI query string. (Optional parameters and
parameters with complex types are excluded.)
- Try to match the most number of parameters. The best match might be a method with no parameters.
希望这可以帮助您理解为什么会收到“发现多项操作……”消息。
编码愉快!!!
如果没有具体定义HTTP方法属性,默认为POST。所以 Send() 也被认为是 post 方法并且异常发生是因为找到了不止一个动作。
您可以尝试安装 Debug Router 工具,以直观地了解控制器和操作是如何选择的。这里是a link
我的FBMessageController
只有以下方法:
public string Get() { ... }
[ChildActionOnly]
public SendResponse Send(ComplexType msg) { ... }
[ChildActionOnly]
public SendResponse SendImage(string x, string y) { ... }
[HttpPost]
public SendResponse Post([FromBody]AnotherComplexType yyy) { ... }
public void Put(..) { ... }
public void Delete(..) { ... }
但是,当我尝试使用 POST 向 .../api/fbMessage
发送请求时,
我得到以下异常:
"Mutliple actions were found that match the request"
WebApiConfig.Register
包含默认代码:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
导致错误的原因是什么?
你能不能尝试用这样的属性装饰你的所有动作,看看是否有任何变化
[HttpGet]
public string Get() { ... }
[ChildActionOnly]
public SendResponse Send(..) { ... }
[ChildActionOnly]
public SendResponse SendImage(..) { ... }
[HttpPost]
public SendResponse Post([FromBody]xxx yyy) { ... }
[HttpPut]
public void Put(..) { ... }
[HttpDelete]
public void Delete(..) { ... }
可能是你的 Put 和 Post 有某种冲突。
虽然您似乎已经隔离了问题,但我进行了以下集成测试以重现您的问题并帮助解释导致错误的原因。
[TestClass]
public class FBMessageControllerTests {
[TestMethod]
public async Task HttpClient_Should_Get_OKStatus_From_Post_To_FBMessage() {
using (var server = new TestServer()) {
var config = server.Configuration;
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var handlerMock = new Mock<IExceptionHandler>();
handlerMock
.Setup(m => m.HandleAsync(It.IsAny<ExceptionHandlerContext>(), It.IsAny<System.Threading.CancellationToken>()))
.Callback<ExceptionHandlerContext, CancellationToken>((context, token) => {
var innerException = context.ExceptionContext.Exception;
Assert.Fail(innerException.Message);
});
config.Services.Replace(typeof(IExceptionHandler), handlerMock.Object);
var client = server.CreateClient();
string url = "http://localhost/api/fbMessage";
var body = new { body = "Hello World" };
using (var response = await client.PostAsJsonAsync(url, body)) {
var message = await response.Content.ReadAsStringAsync();
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, message);
}
}
}
[Authorize]
public class FBMessageController : ApiController {
public string Get() {
return null;
}
[System.Web.Mvc.ChildActionOnly]
public SendResponse Send(ComplexType test) {
return null;
}
[System.Web.Mvc.ChildActionOnly]
public SendResponse SendImage(string x, string y) {
return null;
}
[HttpPost]
public SendResponse Post([FromBody]AnotherComplexType body) {
return null;
}
public void Put(string gbody) {
return;
}
public void Delete(string body) {
return;
}
}
public class SendResponse { }
public class ComplexType { }
public class AnotherComplexType { }
}
产生以下结果消息:
Multiple actions were found that match the request:
Send on type MiscUnitTests+FBMessageControllerTests+FBMessageController
Post on type MiscUnitTests+FBMessageControllerTests+FBMessageController
以上消息准确地告诉了您问题的原因。该框架通常会告诉您为什么会出现某些错误。有时你必须知道去哪里看。
参考以下内容Routing and Action Selection in ASP.NET Web API,这就是您遇到问题的原因。
here is the action selection algorithm.
- Create a list of all actions on the controller that match the HTTP request method.
- If the route dictionary has an "action" entry, remove actions whose name does not match this value.
- Try to match action parameters to the URI, as follows:
- For each action, get a list of the parameters that are a simple type, where the binding gets the parameter from the URI. Exclude optional parameters.
- From this list, try to find a match for each parameter name, either in the route dictionary or in the URI query string. Matches are case insensitive and do not depend on the parameter order.
- Select an action where every parameter in the list has a match in the URI.
- If more that one action meets these criteria, pick the one with the most parameter matches.
- Ignore actions with the [NonAction] attribute.
Step #3 is probably the most confusing. The basic idea is that a parameter can get its value either from the URI, from the request body, or from a custom binding. For parameters that come from the URI, we want to ensure that the URI actually contains a value for that parameter, either in the path (via the route dictionary) or in the query string.
For example, consider the following action:
public void Get(int id)
The id parameter binds to the URI. Therefore, this action can only match a URI that contains a value for "id", either in the route dictionary or in the query string.
Optional parameters are an exception, because they are optional. For an optional parameter, it's OK if the binding can't get the value from the URI.
Complex types are an exception for a different reason. A complex type can only bind to the URI through a custom binding. But in that case, the framework cannot know in advance whether the parameter would bind to a particular URI. To find out, it would need to invoke the binding. The goal of the selection algorithm is to select an action from the static description, before invoking any bindings. Therefore, complex types are excluded from the matching algorithm.
After the action is selected, all parameter bindings are invoked.
Summary:
- The action must match the HTTP method of the request.
- The action name must match the "action" entry in the route dictionary, if present.
- For every parameter of the action, if the parameter is taken from the URI, then the parameter name must be found either in the route dictionary or in the URI query string. (Optional parameters and parameters with complex types are excluded.)
- Try to match the most number of parameters. The best match might be a method with no parameters.
希望这可以帮助您理解为什么会收到“发现多项操作……”消息。
编码愉快!!!
如果没有具体定义HTTP方法属性,默认为POST。所以 Send() 也被认为是 post 方法并且异常发生是因为找到了不止一个动作。
您可以尝试安装 Debug Router 工具,以直观地了解控制器和操作是如何选择的。这里是a link