Méthode HttpPost multiple dans le contrôleur d'API Web

126

Je commence à utiliser le projet d'API Web MVC4, j'ai un contrôleur avec plusieurs HttpPostméthodes. Le contrôleur ressemble à ce qui suit:

Manette

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

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

Ici MyRequestTemplatereprésente la classe de modèle responsable de la gestion du Json passant par la requête.

Erreur:

Lorsque je fais une demande en utilisant Fiddler pour http://localhost:52370/api/VTRouting/TSPRouteou http://localhost:52370/api/VTRouting/Route j'obtiens une erreur:

Plusieurs actions correspondant à la demande ont été trouvées

Si je supprime l'une des méthodes ci-dessus, cela fonctionne bien.

Global.asax

J'ai essayé de modifier la table de routage par défaut dans global.asax, mais j'obtiens toujours l'erreur, je pense que j'ai un problème pour définir les routes dans global.asax. Voici ce que je fais dans 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" }
    );
}

Je fais la demande dans Fiddler en utilisant POST, en passant json dans RequestBody pour MyRequestTemplate.

Habib
la source

Réponses:

143

Vous pouvez avoir plusieurs actions dans un seul contrôleur.

Pour cela, vous devez faire les deux choses suivantes.

  • Décorez d'abord les actions avec un ActionNameattribut comme

     [ActionName("route")]
     public class VTRoutingController : ApiController
     {
       [ActionName("route")]
       public MyResult PostRoute(MyRequestTemplate routingRequestTemplate)
       {
         return null;
       }
    
      [ActionName("tspRoute")]
      public MyResult PostTSPRoute(MyRequestTemplate routingRequestTemplate)
      {
         return null;
      }
    }
  • Définissez ensuite les itinéraires suivants dans le WebApiConfigfichier.

    // 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
la source
Que faire si je ne souhaite définir aucune restriction sur le type d'identifiant? Signification: comment puis-je également accepter les identifiants de chaîne?
frapontillo
5
@frapontillo: L'ID doit être un entier, afin qu'il soit distinct du nom de la route, sinon le moteur de routage le traitera comme un nom d'action plutôt que comme un id. Si vous avez besoin d'avoir l'id sous forme de chaîne, vous pouvez créer une action.
Asif Mushtaq
J'utiliserais plutôt le routage d'attributs. Vous n'aurez pas à utiliser plusieurs routes dans WebApiConfig de cette façon. Consultez ce lien: docs.microsoft.com/en-us/aspnet/web-api/overview/…
Riche du
Si j'ajoute comme ça, cela me donne une erreur ------------ namespace ImageDownloadApplication.Controllers {public class FrontModel {public string skus {get; ensemble; }} [ActionName ("ProductController")] classe publique ProductController: ApiController {// GET: api / NewCotroller public IEnumerable <string> Get () {return new string [] {"value1", "value2"}; }
Umashankar
41

Une bien meilleure solution à votre problème serait d'utiliser Routece qui vous permet de spécifier l'itinéraire sur la méthode par annotation:

[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
la source
Dans quel espace de noms se trouve Route? J'utilise MVC4 et Route n'est pas reconnue.
eaglei22
Oui, c'est la voie à suivre. Merci.
newman
1
pour une raison quelconque, je ne peux pas faire fonctionner cela. c'est exactement ce que je faisais déjà.
oligofren
2
À quoi ressemblerait l'URL que d'appeler Routeet TSPRoute?
Si8
27

utilisation:

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

ce n'est plus une approche RESTful, mais vous pouvez maintenant appeler vos actions par leur nom (plutôt que de laisser l'API Web en déterminer automatiquement une pour vous en fonction du verbe) comme ceci:

[POST] /api/VTRouting/TSPRoute

[POST] /api/VTRouting/Route

Contrairement à la croyance populaire, il n'y a rien de mal à cette approche et elle n'abuse pas de l'API Web. Vous pouvez toujours tirer parti de toutes les fonctionnalités impressionnantes de l'API Web (gestionnaires de délégation, négociation de contenu, formateurs de types de médias, etc.) - vous abandonnez simplement l'approche RESTful.

Filip W
la source
1
Merci pour la réponse, mais cela me donne toujours la même erreur.
Habib
Ce n'est pas possible, alors quelque chose d'autre doit être mal configuré dans votre application. Pouvez-vous afficher la configuration complète de l'itinéraire? Aussi, comment appelez-vous exactement les actions des contrôleurs?
Filip W
L'ensemble de la configuration de l'itinéraire est dans global.asax, j'ai posté cette partie dans ma question, pour faire une demande, j'utilise Fiddler-> Compose-> et je sélectionne Publier comme opération
Habib
essayez de supprimer toutes les autres définitions de routes et laissez simplement celle que j'ai publiée. Ensuite, vous pouvez facilement appeler les deux actions POST situées dans un contrôleur (identique à l'ancienne approche MVC)
Filip W
1
Filip, j'utilise .Net framework 4.5, avec mvc4 ou Visual studio 2012 RC, quel modèle utilisez-vous pour créer votre projet, le vôtre fonctionne parfaitement
Habib
13

Un point de terminaison d'API Web (contrôleur) est une ressource unique qui accepte les verbes get / post / put / delete. Ce n'est pas un contrôleur MVC normal.

Nécessairement, /api/VTRoutingil ne peut y avoir qu'une seule méthode HttpPost qui accepte les paramètres que vous envoyez. Le nom de la fonction n'a pas d'importance , tant que vous décorez avec le truc [http]. Je n'ai jamais essayé, cependant.

Edit: cela ne fonctionne pas. En résolvant, il semble aller par le nombre de paramètres, sans essayer de lier le modèle au type.

Vous pouvez surcharger les fonctions pour accepter différents paramètres. Je suis à peu près sûr que tout irait bien si vous le déclariez comme vous le faites, mais que vous utilisiez des paramètres différents (incompatibles) avec les méthodes. Si les paramètres sont les mêmes, vous n'avez pas de chance car la liaison de modèle ne saura pas laquelle vous vouliez dire.

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

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

Cette partie fonctionne

Le modèle par défaut qu'ils donnent lorsque vous en créez un nouveau rend cela assez explicite, et je dirais que vous devriez vous en tenir à cette convention:

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

Si vous voulez créer une classe qui fait beaucoup de choses, pour une utilisation ajax, il n'y a pas de grande raison de ne pas utiliser un modèle de contrôleur / action standard. La seule vraie différence est que vos signatures de méthode ne sont pas aussi jolies et que vous devez encapsuler les choses Json( returnValue)avant de les renvoyer.

Éditer:

La surcharge fonctionne très bien lors de l'utilisation du modèle standard (modifié pour inclure) lors de l'utilisation de types simples. Je suis allé tester l'autre manière aussi, avec 2 objets personnalisés avec des signatures différentes. Je n'ai jamais pu le faire fonctionner.

  • La liaison avec des objets complexes n'a pas l'air "profonde", donc c'est interdit
  • Vous pouvez contourner ce problème en passant un paramètre supplémentaire sur la chaîne de requête
  • Une meilleure description que je peux donner sur les options disponibles

Cela a fonctionné pour moi dans ce cas, voyez où cela vous mène. Exception pour les tests uniquement.

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

Et appelé comme ça de la console:

$.post("/api/Nerdy?type=white", { 'Name':'Slim', 'Age':'21' } )
Andrew Backer
la source
J'ai essayé de changer les types de paramètres, mais il semble que cela n'autorise qu'une seule méthode Post dans le contrôleur. Merci pour votre réponse
Habib
J'ai supposé qu'il essaierait la liaison de modèle pour le trouver, car vous pouvez surcharger. Cela fonctionne avec différents # de paramètres, cependant. Ce n'est peut-être pas si difficile de le réécrire pour le faire, mais ils n'ont pas encore publié le code source, alors je suis juste coincé à regarder un démontage laid
Andrew Backer
2
+1 pour avoir expliqué la raison pour laquelle cela ne fonctionne pas et la philosophie derrière l'API Web.
MEMark
J'apprécie la panne ... J'avais supposé que c'était censé être un seul POST / PUT / GET par contrôleur mais je n'étais pas sûr ... donc la raison pour laquelle je l'ai recherché. Depuis que j'ai commencé à développer avec MVC pour des applications Web où plusieurs actions similaires par contrôleur sont la norme ... cela semble presque un gaspillage, donc je peux comprendre pourquoi un développeur le voudrait. Y a-t-il trop de contrôleurs?
Anthony Griggs
6

Il est possible d'ajouter plusieurs méthodes Get et Post dans le même contrôleur API Web. Ici, la route par défaut est à l'origine du problème. L'API Web vérifie la correspondance d'itinéraire de haut en bas et, par conséquent, vos correspondances d'itinéraire par défaut pour toutes les demandes. Selon la route par défaut, une seule méthode Get et Post est possible dans un contrôleur. Placez le code suivant en haut ou Commenter / Supprimer l'itinéraire par défaut

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

Mettez Route Prefix [RoutePrefix ("api / Profiles")] au niveau du contrôleur et mettez une route à la méthode d'action [Route ("LikeProfile")]. Pas besoin de changer quoi que ce soit dans le fichier 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
la source
0

Je pense que la question a déjà reçu une réponse. Je cherchais également quelque chose d'un contrôleur webApi qui a les mêmes mehtods signaturés mais des noms différents. J'essayais d'implémenter la calculatrice en tant que WebApi. La calculatrice a 4 méthodes avec la même signature mais des noms différents.

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

et dans le fichier WebApiConfig que vous avez déjà

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

Définissez simplement l'authentification / l'autorisation sur IIS et vous avez terminé!

J'espère que cela t'aides!

Yawar Murtaza
la source
0

Vous pouvez utiliser cette approche:

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

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

Je ne sais pas si la surcharge de la méthode get / post viole le concept d'API restfull, mais cela fonctionne. Si quelqu'un aurait pu éclairer sur cette question. Et si j'ai un uri comme

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

donc comme vous pourriez voir mon journal une sorte d'agrégatoot, bien que je puisse définir un autre contrôleur pour la publication uniquement et passer le numéro d'identification de la publication dans mon URL, mais cela donne beaucoup plus de sens. puisque ma publication n'existerait pas sans la revue elle-même.

Mobygeek
la source
-1

Je viens d'ajouter "action = action_name" à l'url et de cette façon le moteur de routage sait quelle action je veux. J'ai également ajouté l'attribut ActionName aux actions mais je ne suis pas sûr qu'il soit nécessaire.

Rony Tesler
la source
-1

La meilleure et la plus simple explication que j'ai vue sur ce sujet - http://www.binaryintellect.net/articles/9db02aa1-c193-421e-94d0-926e440ed297.aspx

  • Modifié -

Je l'ai fait fonctionner uniquement avec Route, et je n'avais pas besoin de RoutePrefix.

Par exemple, dans le contrôleur

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

et

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

Ensuite, le nom de la fonction entre dans jquery soit -

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

ou

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