Benutzerdefinierte Methodennamen in der ASP.NET-Web-API

110

Ich konvertiere von der WCF-Web-API in die neue ASP.NET MVC 4-Web-API. Ich habe einen UsersController und möchte eine Methode namens Authenticate. Ich sehe Beispiele für GetAll, GetOne, Post und Delete. Was ist jedoch, wenn ich diesen Diensten zusätzliche Methoden hinzufügen möchte? Zum Beispiel sollte mein UsersService eine Methode namens Authentifizieren haben, bei der er einen Benutzernamen und ein Passwort übergibt, dies funktioniert jedoch nicht.

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

Ich kann zu myapi / api / users / navigieren und es wird GetAll aufrufen und ich kann zu myapi / api / users / 1 navigieren und es wird Get aufrufen. Wenn ich jedoch myapi / api / users / authenticate aufrufe? Username = {0} & password = {1}, dann wird Get (NOT Authenticate) und error aufgerufen:

Das Parameterwörterbuch enthält einen Nulleintrag für den Parameter 'id' vom nicht nullbaren Typ 'System.Int32' für die Methode 'System.String Get (Int32)' in 'Navtrak.Services.WCF.NavtrakAPI.Controllers.UsersController'. Ein optionaler Parameter muss ein Referenztyp oder ein nullbarer Typ sein oder als optionaler Parameter deklariert werden.

Wie kann ich benutzerdefinierte Methodennamen wie Authentifizieren aufrufen?

Justin
quelle
Bitte beziehen Sie sich auf diesen Link: 5. Antwort stackoverflow.com/questions/12775590/…
Vishwa G

Antworten:

136

Standardmäßig folgt die Routenkonfiguration den RESTFul-Konventionen, dh, sie akzeptiert nur die Aktionsnamen Get, Post, Put und Delete (siehe Route in global.asax => Standardmäßig können Sie keinen Aktionsnamen angeben => es verwendet das HTTP-Verb zum Versenden). Wenn Sie also eine GET-Anforderung an Sie senden, /api/users/authenticaterufen Sie im Grunde die Get(int id)Aktion auf und übergeben sie, id=authenticatewas offensichtlich abstürzt, weil Ihre Get-Aktion eine Ganzzahl erwartet.

Wenn Sie andere Aktionsnamen als die Standardnamen haben möchten, können Sie Ihre Routendefinition ändern in global.asax:

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

Jetzt können Sie zu navigieren, /api/values/getauthenticateum den Benutzer zu authentifizieren.

Darin Dimitrov
quelle
20
Gibt es eine Möglichkeit, Get (id), Get () Put, Delete, Post weiterhin zu verwenden und gleichzeitig andere Aktionen zuzulassen?
Shawn Mclean
@ShawnMclean Ich denke, Sie könnten eine andere Route angeben, ohne {action}dass dies eine Einschränkung darstellt, {id}sodass alles andere als intoder Guid(oder was auch immer) nicht übereinstimmt. Dann sollte es in der Lage sein, zu dem von Darin
Steve Greatrex vorgeschlagenen
2
Eine weitere wichtige Sache hierbei ist, dass Sie bei diesem Routing-Stil Attribute verwenden müssen, um die zulässigen HTTP-Methoden anzugeben (wie [HttpGet]).
Himalaya Garg
1
Sind Sie sicher, dass Sie andere Aktionen ausführen müssen? Haben Sie wirklich versucht, Ihre Aktivitäten an die REST-Konventionen anzupassen? Es sollte nicht notwendig sein, andere Aktionen auszuführen.
Niico
1
@niico: Stellen Sie sich vor, Sie möchten eine Count () -Methode, die die Anzahl der Elemente zurückgibt, die Get () zurückgeben würde. Ich sehe nicht, wie ich das in Get (), Get (id), Post (...), Put (...) oder Delete (id) einpassen soll. Und natürlich gibt es unbegrenzt mehr mögliche Methoden, die ich mir vorstellen kann.
Jens Mander
88

Dies ist die beste Methode, die ich bisher entwickelt habe, um zusätzliche GET-Methoden zu integrieren und gleichzeitig die normalen REST-Methoden zu unterstützen. Fügen Sie Ihrer WebApiConfig die folgenden Routen hinzu:

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

Ich habe diese Lösung mit der folgenden Testklasse überprüft. Ich konnte jede der folgenden Methoden in meinem Controller erfolgreich ausführen:

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

Ich habe überprüft, ob die folgenden Anforderungen unterstützt werden:

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

Hinweis: Wenn Ihre zusätzlichen GET-Aktionen nicht mit 'Get' beginnen, möchten Sie der Methode möglicherweise ein HttpGet-Attribut hinzufügen.

sky-dev
quelle
1
nette lösung, kannst du mir sagen, ob ich die putund deleteverben so konfiguriere wie du es getan hast getund postauch gut funktioniert?
Felipe Oriani
1
Meiner Meinung nach sollte dies in den Standardeinstellungen für WebAPI-Projekte enthalten sein (möglicherweise auskommentiert). Es gibt Ihnen gleichzeitig Routen im
WebAPI-
1
@FelipeOriani, ich glaube nicht, dass Sie putoder deleteVerben konfigurieren möchten oder müssen, da diese Anforderungen normalerweise einen ID-Parameter begleiten, um die Ressource zu identifizieren, auf die Sie diese Operation anwenden möchten. Ein deleteAufruf von /api/foosollte einen Fehler auslösen, denn welches Foo möchten Sie löschen? Daher sollte die DefaultApiWithId-Route diese Fälle problemlos behandeln.
Nwayve
4
Das hat für mich überhaupt nicht geklappt. Beim Versuch, ein grundlegendes GET durchzuführen, wurden Fehlermeldungen angezeigt.
Matt
Sollten für die erste, DefaultApiWithId, die Standardeinstellungen nicht null anstelle von new {id = RouteParameter.Optional} sein? Ist die 'ID' nicht erforderlich?
Johnny Oshika
22

Ich bin Tage in der MVC4-Welt.

Für was es wert ist, habe ich einen SitesAPIController, und ich brauchte eine benutzerdefinierte Methode, die wie folgt aufgerufen werden könnte:

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

Mit unterschiedlichen Werten für den letzten Parameter, der mit unterschiedlichen Dispositionen aufgezeichnet werden soll.

Was schließlich für mich funktioniert hat war:

Die Methode im 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;
}

Und das in der 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}"
 );

Solange ich die {Disposition} als {id} nannte, begegnete ich:

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

Als ich es in {disposition} umbenannte, fing es an zu funktionieren. Anscheinend stimmt der Parametername also mit dem Wert im Platzhalter überein.

Fühlen Sie sich frei, diese Antwort zu bearbeiten, um sie genauer / erklärender zu machen.

Kinjal Dixit
quelle
Danke für den Tipp. Ich habe den gleichen Fehler gemacht wie du.
Abhi
16

Web-API erwartet standardmäßig, dass eine URL in Form von API / {Controller} / {ID} dieses Standard-Routing überschreibt. Sie können das Routing auf zwei Arten einstellen.

Erste Wahl:

Fügen Sie unten die Routenregistrierung in WebApiConfig.cs hinzu

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

Dekorieren Sie Ihre Aktionsmethode mit HttpGet und den folgenden Parametern

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

 {

// your code here

}

Für den Aufruf der obigen Methode lautet die URL wie unten

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

Zweite Option Fügen Sie der Controller-Klasse ein Routenpräfix hinzu und dekorieren Sie Ihre Aktionsmethode mit HttpGet wie folgt. In diesem Fall müssen Sie keine WebApiConfig.cs ändern. Es kann Standardrouting haben.

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

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

{

// your code here

}

}

Für den Aufruf der obigen Methode lautet die URL wie unten

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

Nagaraju Mengani
quelle
Ich mag die zweite Option sehr. Können Sie mir auch zeigen, wie man es in VB.net benutzt? Vielen Dank.
user1617676
12

Wenn Sie ASP.NET 5 mit ASP.NET MVC 6 verwenden , funktionieren die meisten dieser Antworten einfach nicht, da MVC normalerweise die entsprechende Routensammlung für Sie erstellen lässt (unter Verwendung der Standardkonventionen von RESTful) Sie werden keinen Routes.MapRoute()Anruf zum Bearbeiten nach Belieben finden.

Die ConfigureServices()von der Startup.csDatei aufgerufene Methode registriert MVC beim in ASP.NET 5 integrierten Dependency Injection-Framework. Wenn Sie ApplicationBuilder.UseMvc()später in dieser Klasse aufrufen , fügt das MVC-Framework diese Standardrouten automatisch zu Ihrer App hinzu. Wir können einen Blick darauf werfen, was hinter der Haube passiert, indem wir uns die UseMvc()Methodenimplementierung im Framework-Quellcode ansehen :

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

Das Gute daran ist, dass das Framework jetzt die gesamte harte Arbeit erledigt, alle Aktionen des Controllers durchläuft und deren Standardrouten einrichtet, wodurch Sie überflüssige Arbeit sparen.

Das Schlimme ist, dass es wenig oder keine Dokumentation darüber gibt, wie Sie Ihre eigenen Routen hinzufügen können. Glücklicherweise können Sie dies problemlos tun, indem Sie entweder einen konventionellen und / oder einen attributbasierten Ansatz (auch bekannt als Attribut-Routing ) verwenden.

Konventionsbasiert

Ersetzen Sie in Ihrer Startup.cs-Klasse Folgendes:

app.UseMvc();

mit diesem:

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

Attributbasiert

Eine großartige Sache bei MVC6 ist, dass Sie Routen auch pro Controller definieren können, indem Sie entweder die ControllerKlasse und / oder die ActionMethoden mit den entsprechenden RouteAttributeund / oder HttpGet/ HttpPostVorlagenparametern wie den folgenden dekorieren :

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

Dieser Controller verarbeitet die folgenden Anforderungen:

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

Beachten Sie außerdem, dass bei Verwendung der beiden Ansätze zusammen Attributbasierte Routen (sofern definiert) die auf Konventionen basierenden Routen überschreiben und beide die von definierten Standardrouten überschreiben UseMvc().

Für weitere Informationen können Sie auch den folgenden Beitrag in meinem Blog lesen .

Darkseal
quelle
1
Dies ist perfekt! Keine der anderen Antworten hat tatsächlich das getan, was ich brauchte. Aber du hast mich gerettet :)
König Arthur der Dritte
Gibt es eine Möglichkeit, ein vordefiniertes Modell als zweiten Parameter zu verwenden? Wenn ich zum Beispiel einen bestimmten Benutzer wie folgt patche: public IActionResult Patch(int id, [FromQuery] Person person)sind alle eingehenden Eigenschaften null!
König Arthur der Dritte
0

Ändern Sie einfach Ihre WebAPIConfig.cs wie folgt

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

Implementieren Sie dann Ihre API wie folgt

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

Web APi 2 und neuere Versionen unterstützen eine neue Art von Routing, das so genannte Attribut-Routing. Wie der Name schon sagt, verwendet das Attribut-Routing Attribute, um Routen zu definieren. Durch das Attribut-Routing haben Sie mehr Kontrolle über die URIs in Ihrer Web-API. Sie können beispielsweise problemlos URIs erstellen, die Hierarchien von Ressourcen beschreiben.

Beispielsweise:

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

Wird perfekt und Sie benötigen keinen zusätzlichen Code, zum Beispiel in WebApiConfig.cs. Sie müssen nur sicherstellen, dass das Web-API-Routing in WebApiConfig.cs aktiviert ist oder nicht. Andernfalls können Sie Folgendes aktivieren:

        // Web API routes
        config.MapHttpAttributeRoutes();

Sie müssen in WebApiConfig.cs nichts mehr tun oder etwas ändern. Weitere Informationen finden Sie in diesem Artikel .

nzrytmn
quelle