Web API控制器中的多个HttpPost方法

134

我开始使用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/TSPRoute或者http://localhost:52370/api/VTRouting/Route时,出现了如下错误:

发现多个匹配该请求的操作

如果我删除上述方法中的一个,它就可以正常工作。

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" }
    );
}

我正在使用Fiddler进行POST请求,在RequestBody中传递json作为MyRequestTemplate的参数。

12个回答

150

你可以在单个控制器中拥有多个操作。

为此,您需要执行以下两个步骤:

  • 首先,在操作上使用 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
5
Id 应该是一个整数,这样它就可以与路由名称区分开来,否则路由引擎会将其视为操作名称而不是 ID。如果您需要将 ID 设置为字符串,则可以创建一个操作。 - Asif Mushtaq
我会使用属性路由。这样,您就不必在WebApiConfig中使用多个路由。请查看此链接:https://learn.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2 - Rich
如果我这样添加会出现错误 ------------
命名空间 ImageDownloadApplication.Controllers { public class FrontModel { public string skus { get; set; } } [ActionName("ProductController")] public class ProductController : ApiController { // GET: api/NewCotroller public IEnumerable Get() { return new string[] { "value1", "value2" }; }
- Umashankar

50

解决您的问题的另一种方法是使用 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;
    }
}

1
查找以下两个来源:http://www.asp.net/web-api/overview/web-api-routing-and-actions/create-a-rest-api-with-attribute-routing 和 http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2 - Wisienkas
我的“Route”属性中有不同的名称,但最终我通过向其中一个操作添加名称使其正常工作。很奇怪。 - Mathachew
1
由于某些原因,我无法让它工作。这正是我已经在做的。 - oligofren
2
调用 RouteTSPRoute 的 URL 会是什么样子? - Si8
@Wisienkas 为什么你称它为“更好”? - user1451111
显示剩余4条评论

28

使用:

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 方法。


1
谢谢您的回答,但它仍然给我相同的错误。 - Habib
这是不可能的,那么你的应用程序中一定有其他配置错误。你能展示整个路由设置吗?另外,你是如何精确地调用控制器操作的? - Filip W
尝试删除所有其他路由定义,只留下我发布的那个。然后您可以轻松调用位于同一控制器中的两个POST操作(与旧的MVC方法相同)。 - Filip W
1
Filip,我正在使用.NET Framework 4.5,配合MVC4或Visual Studio 2012 RC。你用哪个模板来创建你的项目?你的项目运行得非常完美。 - Habib
无论如何,这都不应该成为问题——唯一需要关注的是路由定义和控制器本身。确保在从 Fiddler 调用时使用正确的 URL、请求类型、传递正确的内容类型和请求有效负载。如果你将我的虚拟 jQuery/控制器演示复制到你的项目中并从那里尝试,可能会有所帮助。 - Filip W
显示剩余3条评论

13

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 个具有不同签名的自定义对象。但从来没有成功过。
绑定复杂对象时看起来并不 "深入",所以行不通。 您可以通过在查询字符串上传递额外的参数来解决此问题。 可用选项的更好的说明
这在我的案例中可行,看看是否适用于您。仅用于测试的异常情况。
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方法。感谢您的回复。 - Habib
我假设它会尝试使用模型绑定来查找它,因为您可以进行重载。它可以处理不同数量的参数,虽然这可能不太难重新编写它来实现此功能,但他们还没有发布源代码,所以我只能看着丑陋的反汇编代码。 - Andrew
2
+1 是因为你解释了它为什么不起作用,以及 Web API 背后的哲学。 - MEMark
感谢您的解释...我一直以为每个控制器只能有一个POST/PUT/GET,但我不确定...这就是我查找原因。自从我开始使用MVC开发Web应用程序以来,每个控制器都有多个类似操作,这似乎是一种浪费,所以我可以理解为什么开发人员会想要这样做。是否存在太多控制器的情况? - Anthony Griggs

7

在同一个 Web API 控制器中可以添加多个 Get 和 Post 方法。但默认路由会引起问题。Web API 会从上到下检查匹配的路由,因此您的默认路由将与所有请求匹配。根据默认路由,在一个控制器中只能有一个 Get 和 Post 方法。要解决这个问题,请将以下代码放在顶部或注释/删除默认路由。

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

3
在创建其他 Http 方法时,请添加 [HttpPost("Description")]
[HttpPost("Method1")]
public DataType Method1(MyRequestTemplate routingRequestTemplate)
{
    return null;
}

[HttpPost("Method2")]
public DataType Method2(MyRequestTemplate routingRequestTemplate){}

2
你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

2
在控制器级别上放置路由前缀 [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()
        {
        }
    }
}

1
你可以使用这种方法:

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

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

0

我认为这个问题已经得到了回答。我也在寻找一个具有相同签名但不同名称的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上设置身份验证/授权,您就完成了!

希望这可以帮到您!


0

这是我见过的关于这个主题最好和最简单的解释 - 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";

这是完美的答案,不知道为什么会被踩。 - Sivashankar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接