尽管您似乎已经隔离了问题,但我进行了以下集成测试以重现您的问题并帮助解释导致错误的原因。
[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 { }
}
产生以下结果消息:
找到多个与请求匹配的操作:
发送类型 MiscUnitTests+FBMessageControllerTests+FBMessageController
发布类型 MiscUnitTests+FBMessageControllerTests+FBMessageController
上面的消息告诉您问题的确切原因。该框架通常会告诉您为什么会出现某些错误。有时您必须知道在哪里寻找。
引用以下Routing and Action Selection in ASP.NET Web API,这就是您遇到问题的原因。
这里是动作选择算法。
- 在控制器上创建与 HTTP 请求方法匹配的所有操作的列表。
- 如果路由字典有一个“动作”条目,则删除名称与该值不匹配的动作。
- 尝试将动作参数与URI匹配,如下:
- 对于每个操作,获取简单类型的参数列表,其中绑定从 URI 获取参数。
排除可选参数。
- 从此列表中,尝试在路由字典或 URI 查询字符串中查找每个参数名称的匹配项。比赛是
不区分大小写,不依赖于参数顺序。
- 选择一个操作,其中列表中的每个参数在 URI 中都有匹配项。
- 如果有多个操作满足这些条件,请选择参数匹配最多的操作。
- 忽略具有 [NonAction] 属性的操作。
第 3 步可能是最令人困惑的。基本思想是
参数可以从 URI 或请求中获取其值
正文,或来自自定义绑定。对于来自 URI 的参数,
我们要确保 URI 实际上包含一个值
参数,在路径中(通过路由字典)或在
查询字符串。
例如,考虑以下操作:
public void Get(int id)
id 参数绑定到 URI。所以这个动作只能
在路由中匹配包含“id”值的 URI
字典或查询字符串中。
可选参数是一个例外,因为它们是可选的。为了
一个可选参数,如果绑定无法从中获取值也没关系
URI。
复杂类型是一个例外,原因不同。复杂类型
只能通过自定义绑定绑定到 URI。但在那种情况下,
框架无法提前知道参数是否会绑定
到特定的 URI。要找出答案,它需要调用绑定。
选择算法的目标是从
静态描述,在调用任何绑定之前。因此,复杂
类型被排除在匹配算法之外。
选择动作后,调用所有参数绑定。
总结:
- 操作必须与请求的 HTTP 方法相匹配。
- 动作名称必须与路由字典中的“动作”条目匹配(如果存在)。
- 对于动作的每个参数,如果参数取自URI,那么参数名称必须在路由中找到
字典或 URI 查询字符串中。 (可选参数和
复杂类型的参数被排除在外。)
- 尝试匹配最多的参数。最好的匹配可能是没有参数的方法。
希望这可以帮助您理解为什么您会收到“找到多个操作...”消息。
编码愉快!!!