Noms de méthode personnalisés dans l'API Web ASP.NET

110

Je passe de l'API Web WCF à la nouvelle API Web ASP.NET MVC 4. J'ai un UsersController et je souhaite avoir une méthode nommée Authenticate. Je vois des exemples de la façon de faire GetAll, GetOne, Publier et Supprimer, mais que faire si je veux ajouter des méthodes supplémentaires à ces services? Par exemple, mon UsersService devrait avoir une méthode appelée Authenticate où ils transmettent un nom d'utilisateur et un mot de passe, mais cela ne fonctionne pas.

public class UsersController : BaseApiController
{
    public string GetAll()
    {
        return "getall!";
    }

    public string Get(int id)
    {
        return "get 1! " + id;
    }

    public User GetAuthenticate(string userName, string password, string applicationName)
    {
        LogWriter.Write(String.Format("Received authenticate request for username {0} and password {1} and application {2}",
            userName, password, applicationName));

        //check if valid leapfrog login.
        var decodedUsername = userName.Replace("%40", "@");
        var encodedPassword = password.Length > 0 ? Utility.HashString(password) : String.Empty;
        var leapFrogUsers = LeapFrogUserData.FindAll(decodedUsername, encodedPassword);

        if (leapFrogUsers.Count > 0)
        {
            return new User
            {
                Id = (uint)leapFrogUsers[0].Id,
                Guid = leapFrogUsers[0].Guid
            };
        }
        else
            throw new HttpResponseException("Invalid login credentials");
    }
}

Je peux accéder à myapi / api / users / et il appellera GetAll et je peux accéder à myapi / api / users / 1 et il appellera Get, cependant si j'appelle myapi / api / users / authenticate? Username = {0} & password = {1} alors il appellera Get (NOT Authenticate) et l'erreur:

Le dictionnaire de paramètres contient une entrée nulle pour le paramètre 'id' de type non nullable 'System.Int32' pour la méthode 'System.String Get (Int32)' dans 'Navtrak.Services.WCF.NavtrakAPI.Controllers.UsersController'. Un paramètre facultatif doit être un type référence, un type Nullable ou être déclaré en tant que paramètre facultatif.

Comment puis-je appeler des noms de méthode personnalisés tels que Authenticate?

Justin
la source
Veuillez vous référer à ce lien: 5e réponse stackoverflow.com/questions/12775590/…
Vishwa G

Réponses:

137

Par défaut, la configuration de la route suit les conventions RESTFul, ce qui signifie qu'elle n'acceptera que les noms d'action Get, Post, Put et Delete (regardez la route dans global.asax => par défaut, cela ne vous permet pas de spécifier un nom d'action => il utilise le verbe HTTP pour distribuer). Ainsi, lorsque vous envoyez une requête GET, /api/users/authenticatevous appelez essentiellement l' Get(int id)action et la transmettez, id=authenticatece qui se bloque évidemment car votre action Get attend un entier.

Si vous souhaitez avoir des noms d'action différents de ceux standard, vous pouvez modifier la définition de votre itinéraire dans global.asax:

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

Vous pouvez maintenant accéder à /api/values/getauthenticatepour authentifier l'utilisateur.

Darin Dimitrov
la source
20
Existe-t-il un moyen de continuer à utiliser Get (id), Get () Put, Delete, Post tout en autorisant d'autres actions?
Shawn Mclean
@ShawnMclean Je suppose que vous pouvez spécifier une autre route sans {action}que cela ait une contrainte {id}pour que tout autre chose que intou Guid(ou autre) ne corresponde pas. Ensuite, il devrait pouvoir passer à celui suggéré par Darin
Steve Greatrex
2
Une chose plus importante ici est qu'avec ce style de routage, vous devez utiliser des attributs pour spécifier les méthodes HTTP autorisées (comme [HttpGet]).
Himalaya Garg
1
êtes-vous sûr de devoir utiliser d'autres actions? Avez-vous vraiment essayé d'adapter ce que vous faites aux conventions REST? Il ne devrait pas être nécessaire d'utiliser d'autres actions.
niico
1
@niico: Imaginez que vous vouliez avoir une méthode Count (), qui renvoie le nombre d'éléments que Get () renverrait. Je ne vois pas comment intégrer cela dans Get (), Get (id), Post (...), Put (...) ou Delete (id). Et, bien sûr, il y a indéfiniment plus de méthodes possibles que je pourrais imaginer.
Jens Mander
88

C'est la meilleure méthode que j'ai trouvée jusqu'à présent pour incorporer des méthodes GET supplémentaires tout en prenant en charge les méthodes REST normales. Ajoutez les routes suivantes à votre WebApiConfig:

routes.MapHttpRoute("DefaultApiWithId", "Api/{controller}/{id}", new { id = RouteParameter.Optional }, new { id = @"\d+" });
routes.MapHttpRoute("DefaultApiWithAction", "Api/{controller}/{action}");
routes.MapHttpRoute("DefaultApiGet", "Api/{controller}", new { action = "Get" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
routes.MapHttpRoute("DefaultApiPost", "Api/{controller}", new {action = "Post"}, new {httpMethod = new HttpMethodConstraint(HttpMethod.Post)});

J'ai vérifié cette solution avec la classe de test ci-dessous. J'ai pu réussir chaque méthode de mon contrôleur ci-dessous:

public class TestController : ApiController
{
    public string Get()
    {
        return string.Empty;
    }

    public string Get(int id)
    {
        return string.Empty;
    }

    public string GetAll()
    {
        return string.Empty;
    }

    public void Post([FromBody]string value)
    {
    }

    public void Put(int id, [FromBody]string value)
    {
    }

    public void Delete(int id)
    {
    }
}

J'ai vérifié qu'il prend en charge les demandes suivantes:

GET /Test
GET /Test/1
GET /Test/GetAll
POST /Test
PUT /Test/1
DELETE /Test/1

Notez que si vos actions GET supplémentaires ne commencent pas par «Get», vous souhaiterez peut-être ajouter un attribut HttpGet à la méthode.

sky-dev
la source
1
belle solution, pourriez-vous me dire si je configure les verbes putet deletecomme vous l'avez fait sur getet postfonctionnera bien aussi?
Felipe Oriani
1
À mon avis, cela devrait être inclus dans les valeurs par défaut des projets WebAPI (peut-être commenté). Il vous donne des itinéraires de style WebAPI et MVC en même temps ...
John Culviner
1
@FelipeOriani, je ne pense pas que vous voulez ou besoin de configurer putou des deleteverbes étant donné que ces demandes seraient généralement accompagner un paramètre d'identification pour identifier la ressource que vous souhaitez appliquer cette opération. Un deleteappel à /api/foodevrait générer une erreur car quel toto essayez-vous de supprimer? Par conséquent, la route DefaultApiWithId doit gérer ces cas correctement.
nwayve
4
cela n'a pas du tout fonctionné pour moi. reçu des messages d'erreur lorsque j'ai essayé de faire un GET de base.
Matt
Pour le premier, DefaultApiWithId, les valeurs par défaut ne devraient-elles pas être nulles au lieu de new {id = RouteParameter.Optional}? L'identifiant n'est-il pas obligatoire?
Johnny Oshika
22

Je suis dans le monde MVC4.

Pour ce que ça vaut, j'ai un SitesAPIController, et j'avais besoin d'une méthode personnalisée, qui pourrait s'appeler comme:

http://localhost:9000/api/SitesAPI/Disposition/0

Avec des valeurs différentes pour le dernier paramètre pour obtenir un enregistrement avec des dispositions différentes.

Ce qui a finalement fonctionné pour moi était:

La méthode dans SitesAPIController:

// GET api/SitesAPI/Disposition/1
[ActionName("Disposition")]
[HttpGet]
public Site Disposition(int disposition)
{
    Site site = db.Sites.Where(s => s.Disposition == disposition).First();
    return site;
}

Et cela dans le WebApiConfig.cs

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

// this i added
config.Routes.MapHttpRoute(
    name: "Action",
    routeTemplate: "api/{controller}/{action}/{disposition}"
 );

Tant que je nommais la {disposition} comme {id} je rencontrais:

{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost:9000/api/SitesAPI/Disposition/0'.",
"MessageDetail": "No action was found on the controller 'SitesAPI' that matches the request."
}

Quand je l'ai renommé en {disposition}, il a commencé à fonctionner. Donc, apparemment, le nom du paramètre correspond à la valeur de l'espace réservé.

N'hésitez pas à modifier cette réponse pour la rendre plus précise / explicative.

Kinjal Dixit
la source
Merci pour le conseil. Je faisais la même erreur que vous.
abhi
16

Web Api attend par défaut qu'une URL sous la forme api / {controller} / {id} remplace ce routage par défaut. vous pouvez définir le routage avec l'une des deux méthodes ci-dessous.

Première option:

Ajouter ci-dessous l'enregistrement de l'itinéraire dans WebApiConfig.cs

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

Décorez votre méthode d'action avec HttpGet et les paramètres comme ci-dessous

[HttpGet]
public HttpResponseMessage ReadMyData(string param1,
                        string param2, string param3)

 {

// your code here

}

pour appeler la méthode ci-dessus, l'URL sera comme ci-dessous

http: // localhost: [yourport] / api / MyData / ReadMyData? param1 = value1 & param2 = value2 & param3 = value3

Deuxième option Ajoutez un préfixe de route à la classe Controller et décorez votre méthode d'action avec HttpGet comme ci-dessous. Dans ce cas, il n'est pas nécessaire de modifier les WebApiConfig.cs. Il peut avoir un routage par défaut.

[RoutePrefix("api/{controller}/{action}")]
public class MyDataController : ApiController
{

[HttpGet]
public HttpResponseMessage ReadMyData(string param1,
                        string param2, string param3)

{

// your code here

}

}

pour appeler la méthode ci-dessus, l'URL sera comme ci-dessous

http: // localhost: [yourport] / api / MyData / ReadMyData? param1 = value1 & param2 = value2 & param3 = value3

Nagaraju Mengani
la source
J'aime beaucoup la deuxième option. Pourriez-vous également me montrer comment l'utiliser dans VB.net? Merci beaucoup.
user1617676
12

Si vous utilisez ASP.NET 5 avec ASP.NET MVC 6 , la plupart de ces réponses ne fonctionneront tout simplement pas car vous laisserez normalement MVC créer la collection de routes appropriée pour vous (en utilisant les conventions RESTful par défaut), ce qui signifie que vous ne trouverez aucun Routes.MapRoute()appel à modifier à volonté.

La ConfigureServices()méthode appelée par le Startup.csfichier inscrira MVC avec le framework d'injection de dépendances intégré à ASP.NET 5: de cette façon, lorsque vous appelez ApplicationBuilder.UseMvc()plus tard dans cette classe, le framework MVC ajoutera automatiquement ces routes par défaut à votre application. Nous pouvons jeter un œil à ce qui se passe derrière le capot en regardant l' UseMvc()implémentation de la méthode dans le code source du framework:

public static IApplicationBuilder UseMvc(
    [NotNull] this IApplicationBuilder app,
    [NotNull] Action<IRouteBuilder> configureRoutes)
{
    // Verify if AddMvc was done before calling UseMvc
    // We use the MvcMarkerService to make sure if all the services were added.
    MvcServicesHelper.ThrowIfMvcNotRegistered(app.ApplicationServices);

    var routes = new RouteBuilder
    {
        DefaultHandler = new MvcRouteHandler(),
        ServiceProvider = app.ApplicationServices
    };

    configureRoutes(routes);

    // Adding the attribute route comes after running the user-code because
    // we want to respect any changes to the DefaultHandler.
    routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(
        routes.DefaultHandler,
        app.ApplicationServices));

    return app.UseRouter(routes.Build());
}

La bonne chose à ce sujet est que le framework gère maintenant tout le travail acharné, itérant à travers toutes les actions du contrôleur et en configurant leurs routes par défaut, vous évitant ainsi un travail redondant.

Le problème, c'est qu'il y a peu ou pas de documentation sur la façon dont vous pouvez ajouter vos propres routes. Heureusement, vous pouvez facilement le faire en utilisant une approche basée sur la convention et / ou une approche basée sur les attributs (également appelée routage d'attributs ).

Fondé sur la convention

Dans votre classe Startup.cs, remplacez ceci:

app.UseMvc();

avec ça:

app.UseMvc(routes =>
            {
                // Route Sample A
                routes.MapRoute(
                    name: "RouteSampleA",
                    template: "MyOwnGet",
                    defaults: new { controller = "Items", action = "Get" }
                );
                // Route Sample B
                routes.MapRoute(
                    name: "RouteSampleB",
                    template: "MyOwnPost",
                    defaults: new { controller = "Items", action = "Post" }
                );
            });

Basé sur les attributs

Un grand avantage de MVC6 est que vous pouvez également définir des routes par contrôleur en décorant la Controllerclasse et / ou les Actionméthodes avec les paramètres appropriés RouteAttributeet / ou HttpGet/ HttpPosttemplate, tels que les suivants:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;

namespace MyNamespace.Controllers
{
    [Route("api/[controller]")]
    public class ItemsController : Controller
    {
        // GET: api/items
        [HttpGet()]
        public IEnumerable<string> Get()
        {
            return GetLatestItems();
        }

        // GET: api/items/5
        [HttpGet("{num}")]
        public IEnumerable<string> Get(int num)
        {
            return GetLatestItems(5);
        }       

        // GET: api/items/GetLatestItems
        [HttpGet("GetLatestItems")]
        public IEnumerable<string> GetLatestItems()
        {
            return GetLatestItems(5);
        }

        // GET api/items/GetLatestItems/5
        [HttpGet("GetLatestItems/{num}")]
        public IEnumerable<string> GetLatestItems(int num)
        {
            return new string[] { "test", "test2" };
        }

        // POST: /api/items/PostSomething
        [HttpPost("PostSomething")]
        public IActionResult Post([FromBody]string someData)
        {
            return Content("OK, got it!");
        }
    }
}

Ce contrôleur traitera les demandes suivantes:

 [GET] api/items
 [GET] api/items/5
 [GET] api/items/GetLatestItems
 [GET] api/items/GetLatestItems/5
 [POST] api/items/PostSomething

Notez également que si vous utilisez les deux approches pour gheter, les routes basées sur les attributs (lorsqu'elles sont définies) remplaceront celles basées sur la Convention, et les deux remplaceront les routes par défaut définies par UseMvc() .

Pour plus d'informations, vous pouvez également lire l'article suivant sur mon blog.

Darkseal
la source
1
C'est parfait! Aucune des autres réponses n'a vraiment fait ce dont j'avais besoin. Mais tu m'as sauvé :)
King Arthur the Third
Existe-t-il un moyen d'utiliser un modèle prédéfini comme deuxième paramètre? Par exemple, quand je patcher un utilisateur comme ceci: public IActionResult Patch(int id, [FromQuery] Person person), toutes les propriétés sont nuls entrants!
King Arthur the Third
0

Modifiez simplement votre WebAPIConfig.cs comme ci-dessous

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

Ensuite, implémentez votre API comme ci-dessous

    // GET: api/Controller_Name/Show/1
    [ActionName("Show")]
    [HttpGet]
    public EventPlanner Id(int id){}
Dinuwan Kalubowila
la source
0

Web APi 2 et les versions ultérieures prennent en charge un nouveau type de routage, appelé routage d'attributs. Comme son nom l'indique, le routage d'attributs utilise des attributs pour définir les itinéraires. Le routage d'attributs vous donne plus de contrôle sur les URI de votre API Web. Par exemple, vous pouvez facilement créer des URI qui décrivent des hiérarchies de ressources.

Par exemple:

[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }

Sera parfait et vous n'avez pas besoin de code supplémentaire par exemple dans WebApiConfig.cs. Vous devez simplement vous assurer que le routage de l'API Web est activé ou non dans WebApiConfig.cs, sinon vous pouvez l'activer comme ci-dessous:

        // Web API routes
        config.MapHttpAttributeRoutes();

Vous n'avez pas besoin de faire quelque chose de plus ou de changer quelque chose dans WebApiConfig.cs. Pour plus de détails, vous pouvez consulter cet article .

nzrytmn
la source