Vários métodos HttpPost no controlador de API da Web

126

Estou começando a usar o projeto MVC4 Web API, tenho um controlador com vários HttpPostmétodos. O controlador é semelhante ao seguinte:

Controlador

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

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

Aqui MyRequestTemplaterepresenta a classe de modelo responsável por manipular o Json passando pela solicitação.

Erro:

Quando faço uma solicitação usando o Fiddler para http://localhost:52370/api/VTRouting/TSPRouteou http://localhost:52370/api/VTRouting/Route recebo um erro:

Foram encontradas várias ações que correspondem à solicitação

Se eu remover um dos métodos acima, ele funciona bem.

Global.asax

Eu tentei modificar a tabela de roteamento padrão global.asax, mas ainda estou recebendo o erro, acho que tenho problemas ao definir rotas no global.asax. Aqui está o que estou fazendo no 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" }
    );
}

Estou fazendo a solicitação no Fiddler usando POST, passando json em RequestBody para MyRequestTemplate.

Habib
fonte

Respostas:

143

Você pode ter várias ações em um único controlador.

Para isso, você deve fazer as duas coisas a seguir.

  • Primeiro decorar ações com ActionNameatributos como

     [ActionName("route")]
     public class VTRoutingController : ApiController
     {
       [ActionName("route")]
       public MyResult PostRoute(MyRequestTemplate routingRequestTemplate)
       {
         return null;
       }
    
      [ActionName("tspRoute")]
      public MyResult PostTSPRoute(MyRequestTemplate routingRequestTemplate)
      {
         return null;
      }
    }
  • Segundo, defina as seguintes rotas no WebApiConfigarquivo.

    // 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}"
    );
Asif Mushtaq
fonte
E se eu não quiser definir nenhuma restrição ao tipo de ID? Significado: como também posso aceitar IDs de string?
Frapontillo
5
@ frapontillo: O ID deve ser um número inteiro, para que seja distinto do nome da rota, caso contrário, o mecanismo de roteamento o tratará como um nome de ação e não como um ID. Se você precisar ter o ID como string, poderá criar uma ação.
Asif Mushtaq
Eu usaria o roteamento de atributos. Você não precisará usar várias rotas no WebApiConfig dessa maneira. Confira este link: docs.microsoft.com/en-us/aspnet/web-api/overview/…
Rich
Se eu adicionar assim, ocorrerá um erro ------------ namespace ImageDownloadApplication.Controllers {public class FrontModel {public string skus {get; conjunto; }} [ActionName ("ProductController")] classe pública ProductController: ApiController {// GET: api / NewCotroller public IEnumerable <string> Get () {retorna nova string [] {"value1", "value2"}; }
Umashankar 16/02
41

Uma solução muito melhor para o seu problema seria usar o Routeque permite especificar a rota no método por anotação:

[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;
    }
}
Wisienkas
fonte
Em que namespace está o Route? Estou usando o MVC4 e o Route não é reconhecido.
precisa saber é o seguinte
Sim, é assim que deve ser. Obrigado.
newman
1
por alguma razão, não consigo fazer isso funcionar. isso é exatamente o que eu já estava fazendo.
Oligofren
2
Como seria o URL do que chamar Routee TSPRoute?
precisa saber é
27

usar:

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

não é mais uma abordagem RESTful, mas agora você pode chamar suas ações por nome (em vez de permitir que a API da Web determine automaticamente uma para você com base no verbo) assim:

[POST] /api/VTRouting/TSPRoute

[POST] /api/VTRouting/Route

Ao contrário da crença popular, não há nada de errado com essa abordagem e ela não está abusando da API da Web. Você ainda pode aproveitar todos os recursos impressionantes da API da Web (delegar manipuladores, negociação de conteúdo, formadores de mídia e assim por diante) - basta abandonar a abordagem RESTful.

Filip W
fonte
1
Obrigado pela resposta, mas ainda está me dando o mesmo erro.
Habib
Isso não é possível; outra coisa deve ser configurada incorretamente no seu aplicativo. Você pode mostrar toda a configuração da rota? Além disso, como exatamente você está chamando as ações dos controladores?
27560 Filipino W
Toda a configuração Route está em global.asax, tenho postar essa parte na minha pergunta, Para fazer pedido, estou usando Fiddler-> Compose-> e selecionando Post como a operação
Habib
tente remover todas as outras definições de rotas e deixe a que eu publiquei. Então você pode facilmente chamar as duas ações pós localizados dentro de um controlador (o mesmo que abordagem MVC de idade)
Filip W
1
Filip, estou usando o framework .Net 4.5, com mvc4 ou Visual Studio 2012 RC, qual modelo você está usando para criar seu projeto, seu está funcionando perfeitamente
Habib
13

Um ponto de extremidade da API da Web (controlador) é um recurso único que aceita verbos get / post / put / delete. É não um controlador normal MVC.

Necessariamente, /api/VTRoutingpode haver apenas um método HttpPost que aceita os parâmetros que você está enviando. O nome da função não importa , desde que você esteja decorando com o material [http]. Eu nunca tentei, no entanto.

Edit: Isso não funciona. Na resolução, parece corresponder ao número de parâmetros, não tentando vincular o modelo ao tipo.

Você pode sobrecarregar as funções para aceitar parâmetros diferentes. Tenho certeza de que você ficaria bem se o declarasse da maneira que faz, mas usasse parâmetros diferentes (incompatíveis) para os métodos. Se os parâmetros forem os mesmos, você não terá sorte, pois o modelo de ligação não saberá qual deles você quis dizer.

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

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

Esta parte funciona

O modelo padrão que eles fornecem quando você cria um novo torna isso bastante explícito, e eu diria que você deve seguir esta convenção:

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) {}
}

Se você deseja criar uma classe que faça muitas coisas, para o uso do ajax, não há grande motivo para não usar um padrão padrão de controlador / ação. A única diferença real é que suas assinaturas de método não são tão bonitas e é necessário agrupar as coisas Json( returnValue)antes de devolvê-las.

Editar:

A sobrecarga funciona muito bem ao usar o modelo padrão (editado para incluir) ao usar tipos simples. Também testei o contrário, com 2 objetos personalizados com assinaturas diferentes. Nunca consegui fazê-lo funcionar.

  • A vinculação a objetos complexos não parece "profunda", portanto é impossível
  • Você pode contornar isso passando um parâmetro extra, na string de consulta
  • Uma redação melhor do que posso oferecer nas opções disponíveis

Isso funcionou para mim, neste caso, veja onde isso o leva. Exceção apenas para teste.

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

E chamado desta forma o console:

$.post("/api/Nerdy?type=white", { 'Name':'Slim', 'Age':'21' } )
Andrew Backer
fonte
Eu tentei alterar os tipos de parâmetro, mas parece que só permite um único método Post no controlador. Obrigado pela sua resposta
Habib
Eu assumi que ele tentaria vincular o modelo para encontrá-lo, já que você pode sobrecarregar. Porém, ele funciona com diferentes # de parâmetros. Pode não ser tão difícil de reescrevê-lo de fazer isso, porém, mas eles ainda não liberou o código fonte ainda, então eu estou apenas preso olhando feio desmontagem
Andrew Backer
2
+1 por realmente explicar o motivo pelo qual não está funcionando e a filosofia por trás da API da Web.
MEMark
Aprecie a quebra ... Eu tinha assumido que era para ser um único POST / PUT / GET por controlador, mas não tinha certeza ... portanto, a razão pela qual procurei. Desde que comecei a desenvolver com o MVC para aplicativos da Web, onde várias ações semelhantes por controlador são a norma ... quase parece um desperdício, para que eu possa entender por que um desenvolvedor gostaria. Existe algo como muitos controladores?
Anthony Griggs
6

É possível adicionar vários métodos Get e Post no mesmo Web API Controller. Aqui, a rota padrão está causando o problema. A API da Web verifica a rota correspondente da parte superior à inferior e, portanto, a sua rota padrão corresponde a todas as solicitações. Conforme a rota padrão, apenas um método Get e Post é possível em um controlador. Coloque o seguinte código em cima ou Comentar / Excluir rota padrão

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

Coloque o prefixo da rota [RoutePrefix ("api / Profiles")] no nível do controlador e coloque uma rota no método de ação [Route ("LikeProfile")]. Não precisa alterar nada no arquivo 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()
        {
        }
    }
}
Sushil Kumar
fonte
0

Eu acho que a pergunta já foi respondida. Eu também estava procurando por algo que fosse um controlador webApi que tivesse os mesmos métodos assinados, mas com nomes diferentes. Eu estava tentando implementar a calculadora como WebApi. A calculadora possui 4 métodos com a mesma assinatura, mas com nomes diferentes.

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

e no arquivo WebApiConfig você já possui

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

Basta definir a autenticação / autorização no IIS e pronto!

Espero que isto ajude!

Yawar Murtaza
fonte
0

Você pode usar esta abordagem:

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

    [HttpPost("TSPRoute")]
    public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }
}
Amirhossein Yari
fonte
-1
public class Journal : ApiController
{
    public MyResult Get(journal id)
    {
        return null;
    }
}

public class Journal : ApiController
{

    public MyResult Get(journal id, publication id)
    {
        return null;
    }
}

Não tenho certeza se a sobrecarga do método get / post viola o conceito de API restfull, mas funciona. Se alguém pudesse esclarecer sobre este assunto. E se eu tiver uma uri como

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

Então, como você pode ver meu diário meio agregado, embora eu possa definir outro controlador para publicação apenas e passar o número de identificação da publicação no meu URL, no entanto, isso dá muito mais sentido. pois minha publicação não existiria sem o próprio periódico.

mobygeek
fonte
-1

Acabei de adicionar "action = action_name" ao URL e, dessa forma, o mecanismo de roteamento sabe qual ação eu quero. Também adicionei o atributo ActionName às ações, mas não tenho certeza de que seja necessário.

Rony Tesler
fonte
-1

A melhor e mais simples explicação que eu já vi sobre esse tópico - http://www.binaryintellect.net/articles/9db02aa1-c193-421e-94d0-926e440ed297.aspx

  • Editado -

Eu consegui trabalhar apenas com o Route e não precisava do RoutePrefix.

Por exemplo, no controlador

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

e

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

Em seguida, o nome da função entra no jquery como -

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

ou

options.url = "/api/customer/PostCustomerAndOrder";
Howard Shlom
fonte