【问题标题】:Why am I getting "Multiple actions were found ..." when I have exactly one Post action?当我只有一个发布操作时,为什么我会收到“找到多个操作...”?
【发布时间】:2016-06-11 13:14:22
【问题描述】:

我的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 发送请求时,

我得到以下异常:

“找到与请求匹配的多个操作”

WebApiConfig.Register 包含默认代码:

config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }

是什么导致了错误?

【问题讨论】:

  • 异常消息通常会告诉您哪些操作发生了冲突。检查是否有任何内部异常可以为您提供线索。
  • 疑难解答思路:虽然 'Send' 和 'SendImage' 没有标记为 'Post',但它们可能通过 Http Post 进行通信 - 并且可能具有与 'Post' 方法相同的“签名”。使用 Fiddler 并注释掉“Send”和“SendImage”,看看这是否是追求的途径。
  • @SRQCoder 我也在想同样的事情。现在查找文档以查看 Send 是否是约定在选择 sction 时使用的特殊名称之一。
  • @OldGeezer。您似乎在混合 Web API 和 MVC 属性,因为 System.Web.Mvc.ChildActionOnly 是 MVC 属性而不是 Web API

标签: c# asp.net-mvc asp.net-web-api asp.net-web-api2 asp.net-web-api-routing


【解决方案1】:

如果没有特别定义 HTTP 方法属性,默认为 POST。所以 Send() 也被认为是 post 方法,发生异常是因为发现了多个动作。

您可以尝试安装调试路由器工具,以直观地了解控制器和操作的选择方式。这里是a link

【讨论】:

    【解决方案2】:

    尽管您似乎已经隔离了问题,但我进行了以下集成测试以重现您的问题并帮助解释导致错误的原因。

    [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,这就是您遇到问题的原因。

    这里是动作选择算法。

    1. 在控制器上创建与 HTTP 请求方法匹配的所有操作的列表。
    2. 如果路由字典有一个“动作”条目,则删除名称与该值不匹配的动作。
    3. 尝试将动作参数与URI匹配,如下:
      • 对于每个操作,获取简单类型的参数列表,其中绑定从 URI 获取参数。 排除可选参数。
      • 从此列表中,尝试在路由字典或 URI 查询字符串中查找每个参数名称的匹配项。比赛是 不区分大小写,不依赖于参数顺序。
      • 选择一个操作,其中列表中的每个参数在 URI 中都有匹配项。
      • 如果有多个操作满足这些条件,请选择参数匹配最多的操作。
    4. 忽略具有 [NonAction] 属性的操作。

    第 3 步可能是最令人困惑的。基本思想是 参数可以从 URI 或请求中获取其值 正文,或来自自定义绑定。对于来自 URI 的参数, 我们要确保 URI 实际上包含一个值 参数,在路径中(通过路由字典)或在 查询字符串。

    例如,考虑以下操作:

    public void Get(int id)
    

    id 参数绑定到 URI。所以这个动作只能 在路由中匹配包含“id”值的 URI 字典或查询字符串中。

    可选参数是一个例外,因为它们是可选的。为了 一个可选参数,如果绑定无法从中获取值也没关系 URI。

    复杂类型是一个例外,原因不同。复杂类型 只能通过自定义绑定绑定到 URI。但在那种情况下, 框架无法提前知道参数是否会绑定 到特定的 URI。要找出答案,它需要调用绑定。 选择算法的目标是从 静态描述,在调用任何绑定之前。因此,复杂 类型被排除在匹配算法之外。

    选择动作后,调用所有参数绑定。

    总结:

    • 操作必须与请求的 HTTP 方法相匹配。
    • 动作名称必须与路由字典中的“动作”条目匹配(如果存在)。
    • 对于动作的每个参数,如果参数取自URI,那么参数名称必须在路由中找到 字典或 URI 查询字符串中。 (可选参数和 复杂类型的参数被排除在外。)
    • 尝试匹配最多的参数。最好的匹配可能是没有参数的方法。

    希望这可以帮助您理解为什么您会收到“找到多个操作...”消息。

    编码愉快!!!

    【讨论】:

    • @Nikosi,感谢您的奉献。请原谅我的缓慢,但我仍然不知道哪个选择标准导致Send 成为另一个匹配操作。我想如果不显式修饰,默认的Http方法是Get?使用第一个标准不会消除Send 吗?另外,我的ComplexTypeAnotherComplexType 很不一样。
    • SendPost 是可以在约定中互换使用的操作名称。此外,这两个动作都有一种复杂的参数类型,在尝试匹配参数时会被排除。我会将与您的评论相关的部分加粗。
    【解决方案3】:

    你能不能试着用这样的属性来装饰你的所有动作,看看是否有任何变化

    [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 以某种方式发生冲突。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-27
      • 2016-02-07
      相关资源
      最近更新 更多