Routage de l'API Web - api / {controller} / {action} / {id} "dysfonctionnements" api / {controller} / {id}

94

J'ai la route par défaut dans Global.asax:

 RouteTable.Routes.MapHttpRoute(
         name: "DefaultApi",
         routeTemplate: "api/{controller}/{id}",
         defaults: new { id = System.Web.Http.RouteParameter.Optional }
         );

Je voulais pouvoir cibler une fonction spécifique, j'ai donc créé une autre route:

RouteTable.Routes.MapHttpRoute(
         name: "WithActionApi",
         routeTemplate: "api/{controller}/{action}/{id}",
         defaults: new { id = System.Web.Http.RouteParameter.Optional }
         );

Donc, dans mon contrôleur, j'ai:

    public string Get(int id)
    {
        return "object of id id";
    }        

    [HttpGet]
    public IEnumerable<string> ByCategoryId(int id)
    {
        return new string[] { "byCategory1", "byCategory2" };
    }

L'appel .../api/records/bycategoryid/5me donnera ce que je veux. Cependant, appeler .../api/records/1me donnera l'erreur

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

Je comprends pourquoi - les routes définissent simplement quelles URL sont valides, mais en ce qui concerne la correspondance des fonctions, les deux Get(int id)et la ByCategoryId(int id)correspondance api/{controller}/{id}, ce qui confond le cadre.

Que dois-je faire pour que la route API par défaut fonctionne à nouveau et que je conserve celle avec {action}? J'ai pensé à créer un contrôleur différent nommé RecordByCategoryIdControllerpour correspondre à la route API par défaut, pour laquelle je demanderais .../api/recordbycategoryid/5. Cependant, je trouve que c'est une solution «sale» (donc insatisfaisante). J'ai cherché des réponses à ce sujet et aucun tutoriel sur l'utilisation d'une route {action}ne mentionne même ce problème.

Mickael Caruso
la source

Réponses:

104

Le moteur d'itinéraire utilise la même séquence que vous ajoutez des règles. Une fois qu'il obtient la première règle correspondante, il arrêtera de vérifier les autres règles et le prendra pour rechercher le contrôleur et l'action.

Donc tu devrais:

  1. Mettez vos règles spécifiques avant vos règles générales (comme par défaut), ce qui signifie que RouteTable.Routes.MapHttpRoutevous devez d'abord mapper "WithActionApi", puis "DefaultApi".

  2. Supprimez le defaults: new { id = System.Web.Http.RouteParameter.Optional }paramètre de votre règle "WithActionApi" car une fois que l'identifiant est facultatif, une URL telle que "/ api / {part1} / {part2}" ne sera jamais placée dans "DefaultApi".

  3. Ajoutez une action nommée à votre "DefaultApi" pour indiquer au moteur d'itinéraire quelle action entrer. Sinon, une fois que vous avez plus d'une action dans votre contrôleur, le moteur ne saura pas laquelle utiliser et lance "Plusieurs actions ont été trouvées qui correspondent à la demande: ...". Ensuite, pour qu'il corresponde à votre méthode Get, utilisez un ActionNameAttribute .

Votre itinéraire devrait donc ressembler à ceci:

// Map this rule first
RouteTable.Routes.MapRoute(
     "WithActionApi",
     "api/{controller}/{action}/{id}"
 );

RouteTable.Routes.MapRoute(
    "DefaultApi",
    "api/{controller}/{id}",
    new { action="DefaultAction", id = System.Web.Http.RouteParameter.Optional }
);

Et votre contrôleur:

[ActionName("DefaultAction")] //Map Action and you can name your method with any text
public string Get(int id)
{
    return "object of id id";
}        

[HttpGet]
public IEnumerable<string> ByCategoryId(int id)
{
    return new string[] { "byCategory1", "byCategory2" };
}
Chris Li
la source
1
J'ai essayé les conseils ci-dessus et tout fonctionne comme prévu. Merci beaucoup pour ce "secret".
Mickael Caruso
Au point 2 de votre réponse, si idest facultatif, une URL comme /api/{part1}/{part2}peut toujours entrer dans la DefaultApiroute si aucune action correspondante n'est trouvée pour la WithActionApiroute. Corrigez-moi si j'ai tort, s'il-vous plait.
orad
que se passe-t-il si je veux avoir api / {controller} / {action} uniquement. sans identifiant. Si quelqu'un d'autre est confronté au même problème. oubliez WebApiConfig. en savoir plus sur le routage d'attributs.
ahsant
40

Vous pouvez résoudre votre problème à l'aide du routage d'attributs

Manette

[Route("api/category/{categoryId}")]
public IEnumerable<Order> GetCategoryId(int categoryId) { ... }

URI dans jquery

api/category/1

Configuration de l'itinéraire

using System.Web.Http;

namespace WebApplication
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API routes
            config.MapHttpAttributeRoutes();

            // Other Web API configuration not shown.
        }
    }
}

et votre routage par défaut fonctionne comme un routage basé sur les conventions par défaut

Manette

public string Get(int id)
    {
        return "object of id id";
    }   

URI dans Jquery

/api/records/1 

Configuration de l'itinéraire

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Attribute routing.
        config.MapHttpAttributeRoutes();

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

Examen article pour plus d' informations de routage d'attributs et onvention routage basé ici et ce

MSTdev
la source
réponse de Dieu. mais ne pouvons-nous pas ajouter pour tous, api / {controller} / {action} / {id} avec api / {controller} / {id}?
karim
Le routage d'attributs résout définitivement le problème. Un point important: avant Web API 2, les modèles de projet de l'API Web généraient du code comme celui-ci: protected void Application_Start () {WebApiConfig.Register (GlobalConfiguration.Configuration); } Si le routage d'attributs est activé, ce code lèvera une exception. Si vous mettez à niveau un projet d'API Web existant pour utiliser le routage d'attributs, assurez-vous de mettre à jour ce code de configuration comme suit: protected void Application_Start () {GlobalConfiguration.Configure (WebApiConfig.Register); }
Deeb
0

Essaye ça.

public class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        var json = config.Formatters.JsonFormatter;
        json.SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
        config.Formatters.Remove(config.Formatters.XmlFormatter);

        // Web API routes
        config.MapHttpAttributeRoutes();

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

        );
    }
}
Amit
la source
0

La raison possible peut également être que vous n'avez pas hérité de Controller d'ApiController. Arrivé avec moi a mis un certain temps à comprendre la même chose.

Paritosh Tiwari
la source
0

Pour différencier les itinéraires, essayez d'ajouter une contrainte selon laquelle l'ID doit être numérique:

RouteTable.Routes.MapHttpRoute(
         name: "DefaultApi",
         routeTemplate: "api/{controller}/{id}",
         constraints: new { id = @"\d+" }, // Only matches if "id" is one or more digits.
         defaults: new { id = System.Web.Http.RouteParameter.Optional }
         );  
Derek Wade
la source