Nomi di metodi personalizzati nell'API Web ASP.NET


110

Sto convertendo dall'API Web WCF alla nuova API Web ASP.NET MVC 4. Ho un UsersController e voglio un metodo denominato Authenticate. Vedo esempi di come eseguire GetAll, GetOne, Post e Delete, ma cosa succede se voglio aggiungere metodi extra a questi servizi? Ad esempio, il mio UsersService dovrebbe avere un metodo chiamato Authenticate dove passano un nome utente e una password, ma non funziona.

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

Posso passare a myapi / api / users / e chiamerà GetAll e posso passare a myapi / api / users / 1 e chiamerà Get, tuttavia se chiamo myapi / api / users / authenticate? Username = {0} & password = {1} quindi chiamerà Get (NOT Authenticate) ed errore:

Il dizionario dei parametri contiene una voce nulla per il parametro "id" di tipo non nullable "System.Int32" per il metodo "System.String Get (Int32)" in "Navtrak.Services.WCF.NavtrakAPI.Controllers.UsersController". Un parametro facoltativo deve essere un tipo di riferimento, un tipo nullable o essere dichiarato come parametro facoltativo.

Come posso chiamare nomi di metodi personalizzati come Authenticate?


Si prega di fare riferimento a questo link: 5a risposta stackoverflow.com/questions/12775590/…
Vishwa G

Risposte:


137

Per impostazione predefinita, la configurazione della rotta segue le convenzioni RESTFul, il che significa che accetterà solo i nomi delle azioni Get, Post, Put e Delete (guarda la rotta in global.asax => per impostazione predefinita non ti consente di specificare alcun nome di azione => usa il verbo HTTP per inviare). Quindi, quando invii una richiesta GET /api/users/authenticatestai praticamente chiamando l' Get(int id)azione e passando id=authenticateche ovviamente si blocca perché la tua azione Get si aspetta un numero intero.

Se desideri avere nomi di azioni diversi da quelli standard, puoi modificare la definizione del percorso in global.asax:

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

Ora puoi navigare /api/values/getauthenticateper autenticare l'utente.


20
C'è un modo per far sì che utilizzi ancora Get (id), Get () Put, Delete, Post pur consentendo altre azioni?
Shawn Mclean

@ ShawnMclean Immagino che potresti specificare un altro percorso senza {action}che abbia un vincolo in {id}modo che qualsiasi cosa diversa da into Guid(o qualsiasi altra cosa) non corrisponda. Quindi dovrebbe essere in grado di ricadere su quello suggerito da Darin
Steve Greatrex il

2
Un'altra cosa importante qui è che con questo stile di routing, è necessario utilizzare gli attributi per specificare i metodi HTTP consentiti (come [HttpGet]).
Himalaya Garg

1
sei sicuro di dover utilizzare altre azioni? Hai davvero cercato di adattare ciò che stai facendo all'interno delle convenzioni REST? Non dovrebbe essere necessario utilizzare altre azioni.
niico

1
@niico: immagina di voler avere un metodo Count (), che restituisce il numero di elementi che Get () restituirebbe. Non vedo come inserirli in Get (), Get (id), Post (...), Put (...) o Delete (id). E, naturalmente, ci sono indefinitamente più metodi possibili che potrei immaginare.
Jens Mander

88

Questo è il metodo migliore che ho escogitato finora per incorporare metodi GET aggiuntivi supportando anche i normali metodi REST. Aggiungi i seguenti percorsi al tuo 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)});

Ho verificato questa soluzione con la classe di test di seguito. Sono stato in grado di colpire con successo ogni metodo nel mio controller di seguito:

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

Ho verificato che supporta le seguenti richieste:

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

Nota che se le tue azioni GET extra non iniziano con "Get" potresti voler aggiungere un attributo HttpGet al metodo.


1
bella soluzione, potresti dirmi se configuro i verbi pute deletecome hai fatto tu gete post, funzionerà bene anche tu ?
Felipe Oriani

1
A mio parere questo dovrebbe essere incluso nei valori predefiniti per i progetti WebAPI (forse commentato). Ti offre percorsi in stile WebAPI E MVC allo stesso tempo ...
John Culviner

1
@FelipeOriani, non penso che vorresti o avresti bisogno di configurare puto deleteverbi poiché quelle richieste in genere accompagnerebbero un parametro id per identificare la risorsa a cui desideri applicare quell'operazione. Una deletechiamata a /api/foodovrebbe generare un errore perché quale foo stai cercando di eliminare? Pertanto la route DefaultApiWithId dovrebbe gestire bene quei casi.
nwayve

4
questo non ha funzionato affatto per me. ho ricevuto messaggi di errore quando ho provato a eseguire un GET di base.
Matt

Per il primo, DefaultApiWithId, i valori predefiniti non dovrebbero essere nulli invece di new {id = RouteParameter.Optional}? Non è richiesto l '"id"?
Johnny Oshika

22

Sono giorni nel mondo MVC4.

Per quel che vale, ho un SitesAPIController e avevo bisogno di un metodo personalizzato, che potesse essere chiamato come:

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

Con valori diversi per l'ultimo parametro per ottenere record con disposizioni diverse.

Quello che alla fine ha funzionato per me è stato:

Il metodo nel 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;
}

E questo nel 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}"
 );

Per tutto il tempo in cui nominavo {disposition} come {id} ho riscontrato:

{
"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."
}

Quando l'ho rinominato in {disposition} ha iniziato a funzionare. Quindi, a quanto pare, il nome del parametro corrisponde al valore nel segnaposto.

Sentiti libero di modificare questa risposta per renderla più precisa / esplicativa.


Grazie per il consiglio. Stavo facendo lo stesso errore che hai fatto tu.
abhi

16

L'API Web per impostazione predefinita si aspetta che l'URL nella forma di api / {controller} / {id}, sovrascriva questo routing predefinito. puoi impostare il percorso in uno dei due modi seguenti.

Prima opzione:

Aggiungi sotto la registrazione del percorso in WebApiConfig.cs

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

Decora il tuo metodo di azione con HttpGet e parametri come di seguito

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

 {

// your code here

}

per chiamare l'URL del metodo sopra sarà come di seguito

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

Seconda opzione Aggiungi prefisso di route alla classe Controller e decora il tuo metodo di azione con HttpGet come di seguito. In questo caso non è necessario modificare alcun WebApiConfig.cs. Può avere un routing predefinito.

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

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

{

// your code here

}

}

per chiamare l'URL del metodo sopra sarà come di seguito

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


Mi piace molto la seconda opzione. Potresti anche mostrarmi come usarlo in VB.net? Molte grazie.
user1617676

12

Nel caso in cui utilizzi ASP.NET 5 con ASP.NET MVC 6 , la maggior parte di queste risposte semplicemente non funzionerà perché normalmente consentirai a MVC di creare la raccolta di route appropriata per te (utilizzando le convenzioni RESTful predefinite), il che significa che non troverai nessuna Routes.MapRoute()chiamata da modificare a piacimento.

Il ConfigureServices()metodo richiamato dal Startup.csfile registrerà MVC con il framework Dependency Injection integrato in ASP.NET 5: in questo modo, quando chiami ApplicationBuilder.UseMvc()più tardi in quella classe, il framework MVC aggiungerà automaticamente queste route predefinite alla tua app. Possiamo dare uno sguardo a cosa succede dietro le quinte osservando l' UseMvc()implementazione del metodo all'interno del codice sorgente del 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 cosa buona di questo è che il framework ora gestisce tutto il duro lavoro, iterando attraverso tutte le azioni del controller e impostando i loro percorsi predefiniti, risparmiando così un po 'di lavoro ridondante.

La cosa brutta è che c'è poca o nessuna documentazione su come aggiungere le proprie rotte. Fortunatamente, puoi farlo facilmente utilizzando un approccio basato su convenzione e / o basato su attributi (noto anche come Attribute Routing ).

Convenzione-Based

Nella tua classe Startup.cs, sostituisci questo:

app.UseMvc();

con questo:

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

Attribute-Based

Una cosa grandiosa di MVC6 è che puoi anche definire le rotte in base al controller decorando la Controllerclasse e / oi Actionmetodi con i parametri appropriati RouteAttributee / o HttpGet/ HttpPosttemplate, come i seguenti:

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

Questo controller gestirà le seguenti richieste:

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

Si noti inoltre che se si utilizzano i due approcci insieme, le rotte basate sugli attributi (quando definite) sovrascriverebbero quelle basate su Convenzione ed entrambe sovrascriverebbero le rotte predefinite definite da UseMvc().

Per maggiori informazioni, puoi anche leggere il seguente post sul mio blog.


1
Questo è perfetto! Nessuna delle altre risposte ha effettivamente fatto ciò di cui avevo bisogno. Ma mi hai salvato :)
King Arthur il

C'è un modo per utilizzare un modello predefinito come secondo parametro? Per esempio, quando sto patching un determinato utente in questo modo: public IActionResult Patch(int id, [FromQuery] Person person), tutte le proprietà in arrivo sono nulli!
King Arthur il terzo


0

Basta modificare il tuo WebAPIConfig.cs come sotto

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

Quindi implementa la tua API come segue

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

0

Web APi 2 e le versioni successive supportano un nuovo tipo di instradamento, denominato instradamento degli attributi. Come suggerisce il nome, il routing degli attributi utilizza gli attributi per definire i percorsi. Il routing degli attributi ti offre un maggiore controllo sugli URI nella tua API web. Ad esempio, puoi facilmente creare URI che descrivono le gerarchie di risorse.

Per esempio:

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

Sarà perfetto e non è necessario alcun codice aggiuntivo, ad esempio in WebApiConfig.cs. Devi solo assicurarti che il routing api web sia abilitato o meno in WebApiConfig.cs, in caso contrario puoi attivarlo come di seguito:

        // Web API routes
        config.MapHttpAttributeRoutes();

Non devi fare qualcosa di più o cambiare qualcosa in WebApiConfig.cs. Per maggiori dettagli puoi dare un'occhiata a questo articolo .

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.