Mehrere HttpPost-Methoden im Web-API-Controller

126

Ich beginne mit der Verwendung des MVC4-Web-API-Projekts. Ich habe einen Controller mit mehreren HttpPostMethoden. Der Controller sieht folgendermaßen aus:

Regler

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

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

Hier wird MyRequestTemplatedie Vorlagenklasse dargestellt, die für die Verarbeitung des durch die Anforderung eingehenden Json verantwortlich ist.

Error:

Wenn ich mit Fiddler eine Anfrage stelle http://localhost:52370/api/VTRouting/TSPRouteoder http://localhost:52370/api/VTRouting/Route eine Fehlermeldung erhalte:

Es wurden mehrere Aktionen gefunden, die der Anforderung entsprechen

Wenn ich eine der oben genannten Methoden entferne, funktioniert es einwandfrei.

Global.asax

Ich habe versucht, die Standard-Routing-Tabelle global.asaxin zu ändern, aber es wird immer noch der Fehler angezeigt. Ich glaube, ich habe Probleme beim Definieren von Routen in global.asax. Folgendes mache ich 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" }
    );
}

Ich mache die Anfrage in Fiddler mit POST und übergebe json in RequestBody für MyRequestTemplate.

Habib
quelle

Antworten:

143

Sie können mehrere Aktionen in einem einzigen Controller ausführen.

Dafür müssen Sie die folgenden zwei Dinge tun.

  • Dekorieren Sie zuerst Aktionen mit ActionNameAttributen wie

     [ActionName("route")]
     public class VTRoutingController : ApiController
     {
       [ActionName("route")]
       public MyResult PostRoute(MyRequestTemplate routingRequestTemplate)
       {
         return null;
       }
    
      [ActionName("tspRoute")]
      public MyResult PostTSPRoute(MyRequestTemplate routingRequestTemplate)
      {
         return null;
      }
    }
    
  • Zweitens definieren Sie die folgenden Routen in der WebApiConfigDatei.

    // 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
quelle
Was ist, wenn ich den Typ der ID nicht einschränken möchte? Bedeutung: Wie kann ich auch String-IDs akzeptieren?
Frapontillo
5
@frapontillo: Die ID sollte eine Ganzzahl sein, damit sie vom Routennamen unterschieden wird. Andernfalls wird sie vom Routing-Enghine als Aktionsname und nicht als ID behandelt. Wenn Sie die ID als Zeichenfolge benötigen, können Sie eine Aktion erstellen.
Asif Mushtaq
Ich würde stattdessen Attribut-Routing verwenden. Auf diese Weise müssen Sie in der WebApiConfig nicht mehrere Routen verwenden. Schauen Sie sich diesen Link an: docs.microsoft.com/en-us/aspnet/web-api/overview/…
Rich
Wenn ich so hinzufüge, erhalte ich einen Fehler ------------ Namespace ImageDownloadApplication.Controllers {public class FrontModel {public string skus {get; einstellen; }} [ActionName ("ProductController")] öffentliche Klasse ProductController: ApiController {// GET: api / NewCotroller public IEnumerable <string> Get () {neue Zeichenfolge zurückgeben [] {"value1", "value2"}; }
Umashankar
41

Eine viel bessere Lösung für Ihr Problem wäre die Verwendung, mit Routeder Sie die Route auf der Methode durch Annotation angeben können:

[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
quelle
In welchem ​​Namespace befindet sich Route? Ich verwende MVC4 und Route wird nicht erkannt.
eaglei22
Suchen Sie nach
Wisienkas
Ja, so sollte es gehen. Vielen Dank.
Newman
1
Aus irgendeinem Grund kann ich das nicht zum Laufen bringen. Genau das habe ich schon gemacht.
Oligofren
2
Wie würde die URL aussehen, als anzurufen Routeund TSPRoute?
Si8
27

verwenden:

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

Es ist kein RESTful-Ansatz mehr, aber Sie können Ihre Aktionen jetzt beim Namen nennen (anstatt die Web-API anhand des Verbs automatisch eine für Sie bestimmen zu lassen):

[POST] /api/VTRouting/TSPRoute

[POST] /api/VTRouting/Route

Entgegen der landläufigen Meinung ist an diesem Ansatz nichts auszusetzen, und er missbraucht die Web-API nicht. Sie können weiterhin alle fantastischen Funktionen der Web-API nutzen (Delegieren von Handlern, Aushandeln von Inhalten, Mediatyp-Formatierern usw.) - Sie verzichten einfach auf den RESTful-Ansatz.

Filip W.
quelle
1
Vielen Dank für die Antwort, aber es gibt mir immer noch den gleichen Fehler.
Habib
Das ist nicht möglich, dann muss etwas anderes in Ihrer App falsch konfiguriert sein. Können Sie das gesamte Routen-Setup anzeigen? Wie genau rufen Sie die Controller-Aktionen auf?
Filip W
Das gesamte Routen-Setup befindet sich in global.asax. Ich habe diesen Teil in meiner Frage gepostet. Um eine Anfrage zu stellen, verwende ich Fiddler-> Compose-> und wähle Post als Operation aus
Habib
Versuchen Sie, alle anderen Routendefinitionen zu entfernen, und lassen Sie einfach die von mir gepostete. Dann können Sie problemlos beide POST-Aktionen aufrufen, die sich in einem Controller befinden (wie beim alten MVC-Ansatz)
Filip W
1
Filip, ich verwende .Net Framework 4.5 mit mvc4 oder Visual Studio 2012 RC. Welche Vorlage verwenden Sie zum Erstellen Ihres Projekts? Ihre Vorlage funktioniert einwandfrei
Habib
13

Ein Web-API-Endpunkt (Controller) ist eine einzelne Ressource, die Verben zum Abrufen / Veröffentlichen / Setzen / Löschen akzeptiert. Es ist kein normaler MVC-Controller.

Notwendigerweise /api/VTRoutingkann es nur eine HttpPost-Methode geben, die die von Ihnen gesendeten Parameter akzeptiert. Der Funktionsname spielt keine Rolle , solange Sie mit dem [http] -Ding dekorieren. Ich habe es aber nie versucht.

Bearbeiten: Dies funktioniert nicht. Bei der Auflösung scheint es sich um die Anzahl der Parameter zu handeln, ohne zu versuchen, eine Modellbindung an den Typ vorzunehmen.

Sie können die Funktionen überladen, um verschiedene Parameter zu akzeptieren. Ich bin mir ziemlich sicher, dass Sie in Ordnung wären, wenn Sie es so deklarieren würden, aber andere (inkompatible) Parameter als die Methoden verwenden würden. Wenn die Parameter identisch sind, haben Sie kein Glück, da die Modellbindung nicht weiß, welche Sie gemeint haben.

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

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

Dieser Teil funktioniert

Die Standardvorlage, die sie beim Erstellen einer neuen Vorlage angeben, macht dies ziemlich deutlich, und ich würde sagen, Sie sollten sich an diese Konvention halten:

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

Wenn Sie eine Klasse für Ajax erstellen möchten, die viele Aufgaben ausführt, gibt es keinen großen Grund, kein Standard-Controller- / Aktionsmuster zu verwenden. Der einzige wirkliche Unterschied besteht darin, dass Ihre Methodensignaturen nicht so hübsch sind und Sie die Dinge einpacken müssen, Json( returnValue)bevor Sie sie zurückgeben.

Bearbeiten:

Das Überladen funktioniert einwandfrei, wenn die Standardvorlage (bearbeitet, um sie einzuschließen) verwendet wird, wenn einfache Typen verwendet werden. Ich habe auch den umgekehrten Weg mit 2 benutzerdefinierten Objekten mit unterschiedlichen Signaturen getestet. Ich könnte es nie zum Laufen bringen.

  • Das Binden mit komplexen Objekten sieht nicht "tief" aus, das ist also ein No-Go
  • Sie können dies umgehen, indem Sie einen zusätzlichen Parameter an die Abfragezeichenfolge übergeben
  • Eine bessere Beschreibung, als ich zu den verfügbaren Optionen geben kann

In diesem Fall hat das bei mir funktioniert. Sehen Sie, wohin es Sie führt. Ausnahme nur zum Testen.

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

Und so genannt von der Konsole genannt:

$.post("/api/Nerdy?type=white", { 'Name':'Slim', 'Age':'21' } )
Andrew Backer
quelle
Ich habe versucht, die Parametertypen zu ändern, aber es scheint, dass nur eine einzige Post-Methode im Controller zulässig ist. Vielen Dank für Ihre Antwort
Habib
Ich nahm an, dass es versuchen würde, das Modell zu binden, um es zu finden, da Sie überladen können. Es funktioniert jedoch mit verschiedenen Parametern. Es ist vielleicht nicht so schwer, es neu zu schreiben, um dies zu tun, aber sie haben den Quellcode noch nicht veröffentlicht, also sehe ich mir nur die hässliche Demontage an
Andrew Backer
2
+1 für die Erklärung des Grundes, warum es nicht funktioniert, und der Philosophie hinter der Web-API.
MEMark
Schätzen Sie die Panne ... Ich hatte angenommen, dass es sich um einen einzelnen POST / PUT / GET pro Controller handeln sollte, war mir aber nicht sicher ... daher der Grund, warum ich ihn nachgeschlagen habe. Seit ich mit MVC für Web-Apps entwickle, bei denen mehrere ähnliche Aktionen pro Controller die Norm sind ... scheint es fast eine Verschwendung zu sein, damit ich verstehen kann, warum ein Entwickler dies möchte. Gibt es zu viele Controller?
Anthony Griggs
6

Es ist möglich, mehrere Get- und Post-Methoden in demselben Web-API-Controller hinzuzufügen. Hier verursacht die Standardroute das Problem. Die Web-API prüft, ob die Route von oben nach unten übereinstimmt und ob Ihre Standardroute für alle Anforderungen übereinstimmt. Standardmäßig ist nur eine Get- und Post-Methode in einem Controller möglich. Platzieren Sie entweder den folgenden Code oben oder die Standardroute auskommentieren / löschen

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

Setzen Sie das Routenpräfix [RoutePrefix ("api / Profiles")] auf Controller-Ebene und setzen Sie eine Route auf die Aktionsmethode [Route ("LikeProfile")]. Sie müssen nichts in der Datei global.asax ändern

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
quelle
0

Ich denke, die Frage wurde bereits beantwortet. Ich suchte auch nach einem WebApi-Controller, der dieselben signierten Methoden, aber unterschiedliche Namen hat. Ich habe versucht, den Rechner als WebApi zu implementieren. Der Rechner verfügt über 4 Methoden mit derselben Signatur, aber unterschiedlichen Namen.

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

und in der WebApiConfig-Datei, die Sie bereits haben

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

Stellen Sie einfach die Authentifizierung / Autorisierung auf IIS ein und Sie sind fertig!

Hoffe das hilft!

Yawar Murtaza
quelle
0

Sie können diesen Ansatz verwenden:

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

    [HttpPost("TSPRoute")]
    public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }
}
Amirhossein Yari
quelle
-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;
    }
}

Ich bin nicht sicher, ob das Überladen der get / post-Methode das Konzept der restfull-API verletzt, aber es funktioniert. Wenn jemand diese Angelegenheit hätte aufklären können. Was ist, wenn ich einen Uri als habe?

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

Wie Sie vielleicht gesehen haben, ist mein Journal eine Art Aggregateroot, obwohl ich einen anderen Controller nur für die Veröffentlichung definieren und die ID-Nummer der Veröffentlichung in meiner URL übergeben kann. Dies ist jedoch viel sinnvoller. da meine publikation ohne journal selbst nicht existieren würde.

Mobygeek
quelle
-1

Ich habe gerade "action = action_name" zur URL hinzugefügt und auf diese Weise weiß die Routing-Engine, welche Aktion ich möchte. Ich habe den Aktionen auch das ActionName-Attribut hinzugefügt, bin mir aber nicht sicher, ob es benötigt wird.

Rony Tesler
quelle
-1

Beste und einfachste Erklärung, die ich zu diesem Thema gesehen habe - http://www.binaryintellect.net/articles/9db02aa1-c193-421e-94d0-926e440ed297.aspx

  • Bearbeitet -

Ich habe es nur mit Route zum Laufen gebracht und brauchte RoutePrefix nicht.

Zum Beispiel in der Steuerung

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

und

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

Dann geht der Funktionsname in jquery wie folgt aus:

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

oder

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