Metodo HttpPost multiplo nel controller API Web


126

Sto iniziando a utilizzare il progetto API Web MVC4, ho un controller con più HttpPostmetodi. Il controller ha il seguente aspetto:

controllore

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

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

Qui MyRequestTemplaterappresenta la classe modello responsabile della gestione di Json che arriva attraverso la richiesta.

Errore:

Quando faccio una richiesta usando Fiddler per http://localhost:52370/api/VTRouting/TSPRouteo http://localhost:52370/api/VTRouting/Route ricevo un errore:

Sono state trovate più azioni che corrispondono alla richiesta

Se rimuovo uno dei metodi di cui sopra funziona benissimo.

global.asax

Ho provato a modificare la tabella di routing predefinita in global.asax, ma sto ancora ricevendo l'errore, penso di avere problemi nella definizione delle rotte in global.asax. Ecco cosa sto facendo in 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" }
    );
}

Sto facendo la richiesta in Fiddler usando POST, passando json in RequestBody per MyRequestTemplate.

Risposte:


143

Puoi avere più azioni in un singolo controller.

Per questo devi fare le seguenti due cose.

  • Innanzitutto decorare le azioni con ActionNameattributi simili

     [ActionName("route")]
     public class VTRoutingController : ApiController
     {
       [ActionName("route")]
       public MyResult PostRoute(MyRequestTemplate routingRequestTemplate)
       {
         return null;
       }
    
      [ActionName("tspRoute")]
      public MyResult PostTSPRoute(MyRequestTemplate routingRequestTemplate)
      {
         return null;
      }
    }
  • Secondo, definire i seguenti percorsi nel WebApiConfigfile.

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

Cosa succede se non desidero impostare alcuna restrizione sul tipo di ID? Significato: come posso accettare anche gli ID stringa?
frapontillo,

5
@frapontillo: L'ID dovrebbe essere un numero intero, in modo che sia distinto dal nome della rotta, altrimenti il ​​motore di routing lo tratterà come un nome di azione piuttosto che un id. Se devi avere l'id come stringa, puoi creare un'azione.
Asif Mushtaq,

Vorrei invece utilizzare il routing degli attributi. In questo modo non dovrai utilizzare più percorsi in WebApiConfig. Dai
Rich

Se aggiungo in questo modo mi dà un errore ------------ spazio dei nomi ImageDownloadApplication.Controllers {public class FrontModel {public string skus {get; impostato; }} [ActionName ("ProductController")] public class ProductController: ApiController {// GET: api / NewCotroller public IEnumerable <string> Get () {return new string [] {"value1", "value2"}; }
Umashankar,

41

Una soluzione molto migliore al tuo problema sarebbe quella di utilizzare Routeche ti consente di specificare il percorso sul metodo mediante annotazione:

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

In quale spazio dei nomi si trova Route? Sto usando MVC4 e Route non è riconosciuto.
eaglei22,


Sì, è così che dovrebbe andare. Grazie.
Newman,

1
per qualche motivo non riesco a farlo funzionare. questo è esattamente quello che stavo già facendo.
Oligofren,

2
Come sarebbe l'url che chiamare Routee TSPRoute?
Si8,

27

uso:

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

non è più un approccio RESTful, ma ora puoi chiamare le tue azioni per nome (piuttosto che lasciare che l'API Web ne determini automaticamente una in base al verbo) in questo modo:

[POST] /api/VTRouting/TSPRoute

[POST] /api/VTRouting/Route

Contrariamente alla credenza popolare, non c'è nulla di sbagliato in questo approccio e non sta abusando dell'API Web. Puoi ancora sfruttare tutte le fantastiche funzionalità dell'API Web (delegare gestori, negoziazione dei contenuti, mediatypeformatters e così via) - hai semplicemente abbandonato l'approccio RESTful.


1
Grazie per la risposta, ma mi sta ancora dando lo stesso errore.
Habib,

Non è possibile, quindi qualcos'altro deve essere configurato in modo errato nella tua app. Puoi mostrare l'intera configurazione del percorso? Anche come si chiamano esattamente le azioni dei controller?
Filip W,

L'intera configurazione del percorso è in global.asax, ho postato quella parte nella mia domanda, per fare richiesta, sto usando Fiddler-> Compose-> e selezionando Posta come operazione
Habib

prova a rimuovere tutte le altre definizioni dei percorsi e lascia solo quello che ho pubblicato. Quindi puoi facilmente chiamare entrambe le azioni POST situate all'interno di un controller (lo stesso del vecchio approccio MVC)
Filip W

1
Filip, sto usando .Net framework 4.5, con mvc4 o Visual studio 2012 RC, Quale modello stai usando per creare il tuo progetto, il tuo 'funziona perfettamente
Habib

13

Un endpoint api Web (controller) è una singola risorsa che accetta i verbi get / post / put / delete. E ' non è un controller normale MVC.

Necessariamente, /api/VTRoutingci può essere solo un metodo HttpPost che accetta i parametri che si stanno inviando. Il nome della funzione non ha importanza , fintanto che stai decorando con le cose [http]. Non ho mai provato, però.

Modificare: questo non funziona. Nel risolvere, sembra andare dal numero di parametri, non cercando di legare il modello al tipo.

È possibile sovraccaricare le funzioni per accettare parametri diversi. Sono abbastanza sicuro che staresti bene se lo dichiarassi come fai, ma utilizzassi parametri diversi (incompatibili) con i metodi. Se i parametri sono gli stessi, si è sfortunati poiché l'associazione del modello non saprà quale intendevi.

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

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

Questa parte funziona

Il modello predefinito che danno quando ne crei uno nuovo lo rende piuttosto esplicito, e direi che dovresti rispettare questa convenzione:

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 vuoi creare una classe che fa molte cose, per l'uso di Ajax, non c'è motivo di non usare un controller / modello di azione standard. L'unica vera differenza è che le firme del tuo metodo non sono così belle e devi avvolgere le cose Json( returnValue)prima di restituirle.

Modificare:

Il sovraccarico funziona perfettamente quando si utilizza il modello standard (modificato per includere) quando si utilizzano tipi semplici. Sono andato e testato anche nell'altro modo, con 2 oggetti personalizzati con firme diverse. Non è mai riuscito a farlo funzionare.

  • Il legame con oggetti complessi non sembra "profondo", quindi non c'è niente da fare
  • È possibile aggirare questo problema passando un parametro aggiuntivo, sulla stringa di query
  • Una scrittura migliore di quella che posso dare sulle opzioni disponibili

Questo ha funzionato per me in questo caso, vedi dove ti porta. Eccezione solo per i test.

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 chiamato in questo modo dalla console:

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

Ho provato a cambiare i tipi di parametro, ma sembra che permetta solo un singolo metodo Post nel controller. Grazie per la risposta
Habib il

Supponevo che avrebbe cercato di rilegare il modello per trovarlo, dal momento che puoi sovraccaricarlo. Funziona con diversi # di parametri, però. Potrebbe non essere così difficile riscriverlo per farlo, ma non hanno ancora rilasciato il codice sorgente, quindi sono solo bloccato a guardare il brutto disassemblaggio
Andrew Backer,

2
+1 per spiegare effettivamente il motivo per cui non funziona e la filosofia alla base dell'API Web.
MEM

Apprezzo l'interruzione ... Avevo supposto che dovesse essere un singolo POST / PUT / GET per controller, ma non ero sicuro ... quindi il motivo per cui l'ho cercato. Da quando ho iniziato a sviluppare con MVC per app Web in cui più azioni simili per controller sono la norma ... sembra quasi uno spreco, quindi posso capire perché uno sviluppatore vorrebbe. Esistono troppi controller?
Anthony Griggs,

6

È possibile aggiungere più metodi Get e Post nello stesso controller API Web. Qui il percorso predefinito sta causando il problema. L'API Web verifica la corrispondenza del percorso dall'alto verso il basso e quindi il percorso predefinito corrisponde a tutte le richieste. Come da percorso predefinito è possibile un solo metodo Get and Post in un controller. Posiziona il codice seguente in alto o Commenta / Elimina percorso predefinito

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

1

Metti Prefisso percorso [RoutePrefix ("api / Profiles")] a livello di controller e metti un route nel metodo di azione [Route ("LikeProfile")]. Non è necessario modificare nulla nel file 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()
        {
        }
    }
}

0

Penso che alla domanda sia già stata data una risposta. Stavo anche cercando qualcosa di un controller webApi che abbia gli stessi mehtod firmati ma nomi diversi. Stavo cercando di implementare la calcolatrice come WebApi. La calcolatrice ha 4 metodi con la stessa firma ma nomi diversi.

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 nel file WebApiConfig che hai già

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

Basta impostare l'autenticazione / autorizzazione su IIS e il gioco è fatto!

Spero che questo ti aiuti!


0

Puoi usare questo approccio:

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

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

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

Non sono sicuro che il sovraccarico del metodo get / post violi il concetto di API restfull, ma funziona. Se qualcuno avesse potuto chiarire la questione. E se avessi un uri come

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

così come potresti vedere il mio diario come aggregateroot, anche se posso definire un altro controller per la sola pubblicazione e passare il numero ID della pubblicazione nel mio URL, tuttavia questo ha molto più senso. poiché la mia pubblicazione non esisterebbe senza il diario stesso.


-1

Ho appena aggiunto "action = nome_azione" all'URL e in questo modo il motore di routing sa quale azione voglio. Ho anche aggiunto l'attributo ActionName alle azioni ma non sono sicuro che sia necessario.


-1

La spiegazione migliore e più semplice che ho visto su questo argomento - http://www.binaryintellect.net/articles/9db02aa1-c193-421e-94d0-926e440ed297.aspx

  • Modificato -

Ho funzionato solo con Route e non avevo bisogno di RoutePrefix.

Ad esempio, nel controller

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

e

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

Quindi, il nome della funzione va in jquery come -

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

o

options.url = "/api/customer/PostCustomerAndOrder";
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.