【问题标题】:Multiple HttpPost method in Web API controllerWeb API 控制器中的多个 HttpPost 方法
【发布时间】:2012-07-09 13:44:30
【问题描述】:

我开始使用 MVC4 Web API 项目,我有多个 HttpPost 方法的控制器。控制器如下所示:

控制器

public class VTRoutingController : ApiController
{
    [HttpPost]
    public MyResult Route(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }

    [HttpPost]
    public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }
}

这里MyRequestTemplate代表模板类,负责处理通过请求传来的Json。

错误:

当我使用 Fiddler 向 http://localhost:52370/api/VTRouting/TSPRoutehttp://localhost:52370/api/VTRouting/Route 发出请求时,我收到一个错误:

找到多个匹配请求的操作

如果我删除上述方法之一,它可以正常工作。

Global.asax

我已经尝试修改global.asax 中的默认路由表,但我仍然收到错误消息,我认为我在 global.asax 中定义路由时遇到了问题。这是我在 global.asax 中所做的。

public static void RegisterRoutes(RouteCollection routes)
{
    routes.MapHttpRoute(
        name: "MyTSPRoute",
        routeTemplate: "api/VTRouting/TSPRoute",
        defaults: new { }
    );

    routes.MapHttpRoute(
        name: "MyRoute",
        routeTemplate: "api/VTRouting/Route",
        defaults: new { action="Route" }
    );
}

我正在使用 POST 在 Fiddler 中发出请求,在 RequestBody 中为 MyRequestTemplate 传递 json。

【问题讨论】:

    标签: c# asp.net-web-api global-asax asp.net-web-api-routing


    【解决方案1】:

    您可以在单个控制器中执行多个操作。

    为此,您必须做以下两件事。

    • 首先用ActionName属性装饰动作,比如

       [ActionName("route")]
       public class VTRoutingController : ApiController
       {
         [ActionName("route")]
         public MyResult PostRoute(MyRequestTemplate routingRequestTemplate)
         {
           return null;
         }
      
        [ActionName("tspRoute")]
        public MyResult PostTSPRoute(MyRequestTemplate routingRequestTemplate)
        {
           return null;
        }
      }
      
    • 第二次在WebApiConfig文件中定义如下路由。

      // Controller Only
      // To handle routes like `/api/VTRouting`
      config.Routes.MapHttpRoute(
          name: "ControllerOnly",
          routeTemplate: "api/{controller}"               
      );
      
      
      // Controller with ID
      // To handle routes like `/api/VTRouting/1`
      config.Routes.MapHttpRoute(
          name: "ControllerAndId",
          routeTemplate: "api/{controller}/{id}",
          defaults: null,
          constraints: new { id = @"^\d+$" } // Only integers 
      );
      
      // Controllers with Actions
      // To handle routes like `/api/VTRouting/route`
      config.Routes.MapHttpRoute(
          name: "ControllerAndAction",
          routeTemplate: "api/{controller}/{action}"
      );
      

    【讨论】:

    • 如果我不想对ID的类型设置任何限制怎么办?含义:如何也接受字符串 ID?
    • @frapontillo:Id 应该是一个整数,以便它与路由名称区分开来,否则路由引擎会将其视为操作名称而不是 id。如果您需要将 id 作为字符串,那么您可以创建一个操作。
    • 我会改用属性路由。您不必以这种方式在 WebApiConfig 中使用多个路由。查看此链接:docs.microsoft.com/en-us/aspnet/web-api/overview/…
    • 如果我像这样添加它会给我一个错误------------ namespace ImageDownloadApplication.Controllers { public class FrontModel { public string skus { get;放; } } [ActionName("ProductController")] public class ProductController : ApiController { // GET: api/NewCotroller public IEnumerable Get() { return new string[] { "value1", "value2" }; }
    【解决方案2】:

    您的问题的另一个解决方案是使用Route,它允许您通过注释指定方法上的路由:

    [RoutePrefix("api/VTRouting")]
    public class VTRoutingController : ApiController
    {
        [HttpPost]
        [Route("Route")]
        public MyResult Route(MyRequestTemplate routingRequestTemplate)
        {
            return null;
        }
    
        [HttpPost]
        [Route("TSPRoute")]
        public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
        {
            return null;
        }
    }
    

    【讨论】:

    【解决方案3】:

    使用:

    routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{action}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );
    

    它不再是一种 RESTful 方法,但您现在可以按名称调用您的操作(而不是让 Web API 根据动词自动为您确定一个),如下所示:

    [POST] /api/VTRouting/TSPRoute
    
    [POST] /api/VTRouting/Route
    

    与流行的看法相反,这种方法没有任何问题,也没有滥用 Web API。您仍然可以利用 Web API 的所有出色功能(委托处理程序、内容协商、媒体类型格式化程序等)——您只需放弃 RESTful 方法。

    【讨论】:

    • 感谢您的回答,但它仍然给我同样的错误。
    • 这是不可能的,那么您的应用程序中必须配置错误。你能显示整个路线设置吗?另外,您如何确切地调用控制器操作?
    • 整个路由设置在 global.asax 中,我已经在我的问题中发布了那部分,为了提出请求,我使用 Fiddler->Compose-> 并选择 Post 作为操作
    • 尝试删除所有其他路由定义并留下我发布的那个。然后,您可以轻松调用位于一个控制器中的两个 POST 操作(与旧的 MVC 方法相同)
    • Filip,我正在使用 .Net framework 4.5,带有 mvc4 或 Visual Studio 2012 RC,您使用哪个模板来创建您的项目,您的工作完美
    【解决方案4】:

    Web api 端点(控制器)是接受 get/post/put/delete 动词的单一资源。它不是普通的 MVC 控制器。

    当然,/api/VTRouting 只能有一个 HttpPost 方法来接受您发送的参数。函数名无所谓,只要你用[http]的东西装饰。不过,我从未尝试过。

    编辑:这不起作用。在解决过程中,它似乎取决于参数的数量,而不是试图将模型绑定到类型。

    您可以重载函数以接受不同的参数。我敢肯定,如果您按照自己的方式声明它,但对方法使用不同的(不兼容的)参数,您会没事的。如果参数相同,则您很不走运,因为模型绑定不会知道您的意思。

    [HttpPost]
    public MyResult Route(MyRequestTemplate routingRequestTemplate) {...}
    
    [HttpPost]
    public MyResult TSPRoute(MyOtherTemplate routingRequestTemplate) {...}
    

    这部分有效

    当你创建一个新模板时,他们提供的默认模板非常明确,我想说你应该坚持这个约定:

    public class ValuesController : ApiController
    {
        // GET is overloaded here.  one method takes a param, the other not.
        // GET api/values  
        public IEnumerable<string> Get() { .. return new string[] ... }
        // GET api/values/5
        public string Get(int id) { return "hi there"; }
    
        // POST api/values (OVERLOADED)
        public void Post(string value) { ... }
        public void Post(string value, string anotherValue) { ... }
        // PUT api/values/5
        public void Put(int id, string value) {}
        // DELETE api/values/5
        public void Delete(int id) {}
    }
    

    如果你想创建一个可以做很多事情的类,对于 ajax 的使用,没有理由不使用标准的控制器/动作模式。唯一真正的区别是您的方法签名不那么漂亮,并且您必须在返回之前将它们包装在 Json( returnValue) 中。

    编辑:

    在使用简单类型时使用标准模板(编辑为包含)时,重载工作得很好。我也用另一种方式进行了测试,使用了 2 个具有不同签名的自定义对象。永远无法让它工作。

    • 与复杂对象的绑定看起来并不“深”,所以这是不行的
    • 您可以通过在查询字符串上传递一个额外的参数来解决此问题
    • A better writeup than I can give 可用选项

    在这种情况下,这对我有用,看看它会带给你什么。仅用于测试的例外情况。

    public class NerdyController : ApiController
    {
        public void Post(string type, Obj o) { 
            throw new Exception("Type=" + type + ", o.Name=" + o.Name ); 
        }
    }
    
    public class Obj {
        public string Name { get; set; }
        public string Age { get; set; }
    }
    

    并像这样从控制台调用:

    $.post("/api/Nerdy?type=white", { 'Name':'Slim', 'Age':'21' } )
    

    【讨论】:

    • 我尝试过更改参数类型,但似乎它只允许控制器中的单个 Post 方法。感谢您的回复
    • 我假设它会尝试模型绑定来找到它,因为你可以重载。不过,它适用于不同的 # 参数。虽然重写它可能并不难,但他们还没有发布源代码,所以我只是看着丑陋的反汇编
    • +1 实际解释了它不起作用的原因,以及 web api 背后的哲学。
    • 欣赏故障...我以为每个控制器应该是一个 POST/PUT/GET 但我不确定...这就是我查找它的原因。自从我开始使用 MVC 为 Web 应用程序开发,每个控制器的多个类似操作是常态......这几乎看起来是一种浪费,所以我可以理解为什么开发人员想要这样做。是不是控制器太多了?
    【解决方案5】:

    可以在同一个 Web API 控制器中添加多个 Get 和 Post 方法。这里默认路由是导致问题的原因。 Web API 检查从上到下的匹配路由,因此您的默认路由匹配所有请求。根据默认路由,一个控制器中只能使用一种 Get 和 Post 方法。将以下代码放在顶部或注释掉/删除默认路由

        config.Routes.MapHttpRoute("API Default", 
                                   "api/{controller}/{action}/{id}",
                                   new { id = RouteParameter.Optional });
    

    【讨论】:

      【解决方案6】:

      将路由前缀 [RoutePrefix("api/Profiles")] 放在控制器级别,并将路由放在操作方法 [Route("LikeProfile")] 中。不需要更改 global.asax 文件中的任何内容

      namespace KhandalVipra.Controllers
      {
          [RoutePrefix("api/Profiles")]
          public class ProfilesController : ApiController
          {
              // POST: api/Profiles/LikeProfile
              [Authorize]
              [HttpPost]
              [Route("LikeProfile")]
              [ResponseType(typeof(List<Like>))]
              public async Task<IHttpActionResult> LikeProfile()
              {
              }
          }
      }
      

      【讨论】:

        【解决方案7】:

        您可以使用这种方法:

        public class VTRoutingController : ApiController
        {
            [HttpPost("Route")]
            public MyResult Route(MyRequestTemplate routingRequestTemplate)
            {
                return null;
            }
        
            [HttpPost("TSPRoute")]
            public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
            {
                return null;
            }
        }
        

        【讨论】:

          【解决方案8】:

          我认为这个问题已经得到解答。我还在寻找具有相同签名方法但名称不同的 webApi 控制器。我试图将计算器实现为 WebApi。计算器有 4 个签名相同但名称不同的方法。

          public class CalculatorController : ApiController
          {
              [HttpGet]
              [ActionName("Add")]
              public string Add(int num1 = 1, int num2 = 1, int timeDelay = 1)
              {
                  Thread.Sleep(1000 * timeDelay);
                  return string.Format("Add = {0}", num1 + num2);
              }
          
              [HttpGet]
              [ActionName("Sub")]
              public string Sub(int num1 = 1, int num2 = 1, int timeDelay = 1)
              {
                  Thread.Sleep(1000 * timeDelay);
                  return string.Format("Subtract result = {0}", num1 - num2);
              }
          
              [HttpGet]
              [ActionName("Mul")]
              public string Mul(int num1 = 1, int num2 = 1, int timeDelay = 1)
              {
                  Thread.Sleep(1000 * timeDelay);
                  return string.Format("Multiplication result = {0}", num1 * num2);
              }
          
              [HttpGet]
              [ActionName("Div")]
              public string Div(int num1 = 1, int num2 = 1, int timeDelay = 1)
              {
                  Thread.Sleep(1000 * timeDelay);
                  return string.Format("Division result = {0}", num1 / num2);
              }
          }
          

          并且在您已经拥有的 WebApiConfig 文件中

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

          只需在 IIS 上设置身份验证/授权即可!

          希望这会有所帮助!

          【讨论】:

            【解决方案9】:

            我在这个主题上看到的最好和最简单的解释 - http://www.binaryintellect.net/articles/9db02aa1-c193-421e-94d0-926e440ed297.aspx

            • 已编辑 -

            我只使用了 Route,不需要 RoutePrefix。

            例如在控制器中

            [HttpPost]
            [Route("[action]")]
            public IActionResult PostCustomer
            ([FromBody]CustomerOrder obj)
            {
            }
            

            [HttpPost]
            [Route("[action]")]
            public IActionResult PostCustomerAndOrder
            ([FromBody]CustomerOrder obj)
            {
            }
            

            然后,函数名在 jquery 中作为 -

            options.url = "/api/customer/PostCustomer";
            

            options.url = "/api/customer/PostCustomerAndOrder";
            

            【讨论】:

            • 这是完美的答案,不知道为什么被否决。
            【解决方案10】:
            public class Journal : ApiController
            {
                public MyResult Get(journal id)
                {
                    return null;
                }
            }
            
            public class Journal : ApiController
            {
            
                public MyResult Get(journal id, publication id)
                {
                    return null;
                }
            }
            

            我不确定重载 get/post 方法是否违反了 restfull api 的概念,但它确实有效。如果有人能在这件事上有所启发。如果我有一个 uri 作为

            uri:/api/journal/journalid
            uri:/api/journal/journalid/publicationid
            

            所以你可能会看到我的日志类型是聚合根,虽然我可以单独定义另一个控制器用于发布,并在我的 url 中传递发布的 id 编号,但这更有意义。因为如果没有期刊本身,我的出版物就不会存在。

            【讨论】:

              猜你喜欢
              • 2021-12-25
              • 2023-03-12
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-04-30
              相关资源
              最近更新 更多