Niestandardowe nazwy metod w ASP.NET Web API


110

Konwertuję z interfejsu API sieci Web programu WCF do nowego interfejsu API sieci Web ASP.NET MVC 4. Mam UsersController i chcę mieć metodę o nazwie Authenticate. Widzę przykłady wykonywania GetAll, GetOne, Post i Delete, ale co zrobić, jeśli chcę dodać dodatkowe metody do tych usług? Na przykład moja usługa UsersService powinna mieć metodę o nazwie Authenticate, w której przekazuje nazwę użytkownika i hasło, jednak to nie działa.

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

Mogę przejść do myapi / api / users / i wywołać GetAll i mogę przejść do myapi / api / users / 1 i wywoła Get, jednak jeśli wywołam myapi / api / users / authentication? Username = {0} & password = {1} to wywoła Get (NOT Authenticate) i błąd:

Słownik parametrów zawiera wpis o wartości null dla parametru „id” typu „System.Int32” nie dopuszczającego wartości null dla metody „System.String Get (Int32)” w „Navtrak.Services.WCF.NavtrakAPI.Controllers.UsersController”. Opcjonalny parametr musi być typem referencyjnym, typem dopuszczającym wartość null lub być zadeklarowany jako parametr opcjonalny.

Jak mogę wywołać niestandardowe nazwy metod, takie jak Authenticate?


Proszę odnieść się do tego linku: 5. odpowiedź stackoverflow.com/questions/12775590/ ...
Vishwa G

Odpowiedzi:


137

Domyślnie konfiguracja trasy jest zgodna z konwencjami RESTFul, co oznacza, że ​​akceptuje tylko nazwy akcji Get, Post, Put i Delete (spójrz na trasę w global.asax => domyślnie nie pozwala określić żadnej nazwy akcji => używa czasownika HTTP do wysyłki). Więc kiedy wysyłasz do siebie żądanie GET /api/users/authenticate, w zasadzie wywołujesz Get(int id)akcję i podajesz ją, id=authenticateco oczywiście ulega awarii, ponieważ twoja akcja Get oczekuje liczby całkowitej.

Jeśli chcesz mieć inne nazwy akcji niż standardowe, możesz zmodyfikować definicję trasy w global.asax:

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

Teraz możesz przejść do, /api/values/getauthenticateaby uwierzytelnić użytkownika.


20
Czy istnieje sposób, aby nadal używać Get (id), Get () Put, Delete, Post, jednocześnie zezwalając na inne akcje?
Shawn Mclean,

@ShawnMclean Wydaje mi się, że możesz określić inną trasę bez {action}tego ograniczenia {id}, aby nic innego niż intlub Guid(lub cokolwiek) nie pasowało. Wtedy powinien być w stanie przejść do tego, który zasugerował Darin
Steve Greatrex

2
Jeszcze jedną ważną rzeczą jest to, że w tym stylu routingu musisz użyć atrybutów, aby określić dozwolone metody HTTP (np. [HttpGet]).
Himalaya Garg

1
czy na pewno musisz użyć innych czynności? Czy naprawdę próbowałeś dopasować to, co robisz, do konwencji REST? Nie powinno być konieczne wykonywanie innych czynności.
niico

1
@niico: Wyobraź sobie, że chciałbyś mieć metodę Count (), która zwraca liczbę elementów, które zwróci Get (). Nie wiem, jak to dopasować do Get (), Get (id), Post (...), Put (...) lub Delete (id). Oczywiście istnieje nieskończenie więcej możliwych metod, które mógłbym sobie wyobrazić.
Jens Mander

88

Jest to najlepsza metoda, jaką do tej pory wymyśliłem, aby włączyć dodatkowe metody GET, jednocześnie obsługując również zwykłe metody REST. Dodaj następujące trasy do swojej konfiguracji 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)});

Sprawdziłem to rozwiązanie z poniższą klasą testową. Udało mi się pomyślnie trafić każdą metodę w moim kontrolerze poniżej:

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

Sprawdziłem, że obsługuje następujące żądania:

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

Zauważ, że jeśli twoje dodatkowe akcje GET nie zaczynają się od „Get”, możesz chcieć dodać atrybut HttpGet do metody.


1
fajne rozwiązanie, czy mógłbyś mi powiedzieć, czy skonfiguruję czasowniki puti, deletetakie jak zrobiłeś, geti post, czy też będzie działać dobrze?
Felipe Oriani

1
Moim zdaniem powinno to być uwzględnione w ustawieniach domyślnych dla projektów WebAPI (być może zakomentowane). Zapewnia jednocześnie trasy w stylu WebAPI I MVC ...
John Culviner

1
@FelipeOriani, nie sądzę byś chciał lub musiał skonfigurować putlub deleteczasowników ponieważ wnioski te będą zazwyczaj towarzyszą parametr id do identyfikacji zasobu, który chcesz zastosować tę operację. deleteWywołanie /api/foopowinno wyrzucić błąd, ponieważ który foo starasz się usunąć? Dlatego trasa DefaultApiWithId powinna dobrze obsłużyć te przypadki.
nwayve

4
to w ogóle mi nie wyszło. otrzymałem komunikaty o błędach, gdy próbowałem wykonać podstawowe GET.
Matt

W przypadku pierwszego, DefaultApiWithId, czy wartości domyślne nie powinny mieć wartości null zamiast new {id = RouteParameter.Optional}? Czy „identyfikator” nie jest wymagany?
Johnny Oshika

22

Jestem dniami w świecie MVC4.

Ze względu na swoją wartość mam SitesAPIController i potrzebowałem niestandardowej metody, którą można by nazwać:

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

Z różnymi wartościami ostatniego parametru, aby uzyskać rekord z różnymi dyspozycjami.

W końcu zadziałało dla mnie:

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

I to w pliku 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}"
 );

Tak długo, jak nazywam {dyspozycję} jako {id}, z którą się spotykałem:

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

Kiedy zmieniłem jego nazwę na {disposition}, zaczęło działać. Najwyraźniej nazwa parametru jest dopasowywana do wartości w symbolu zastępczym.

Możesz edytować tę odpowiedź, aby była bardziej dokładna / wyjaśniająca.


Dzięki za wskazówkę. Popełniłem ten sam błąd co Ty.
abhi

16

Interfejs API sieci Web domyślnie oczekuje adresu URL w postaci api / {kontroler} / {id}, aby zastąpić ten domyślny routing. możesz ustawić routing na jeden z poniższych dwóch sposobów.

Pierwsza opcja:

Dodaj poniżej rejestrację trasy w WebApiConfig.cs

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

Udekoruj swoją metodę akcji za pomocą HttpGet i parametrów, jak poniżej

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

 {

// your code here

}

dla wywołania powyższej metody adres URL będzie taki jak poniżej

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

Druga opcja Dodaj prefiks trasy do klasy kontrolera i Udekoruj swoją metodę akcji za pomocą HttpGet, jak poniżej. W takim przypadku nie trzeba zmieniać żadnego pliku WebApiConfig.cs. Może mieć routing domyślny.

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

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

{

// your code here

}

}

dla wywołania powyższej metody adres URL będzie taki jak poniżej

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


Bardzo podoba mi się ta druga opcja. Czy możesz mi również pokazać, jak go używać w VB.net? Wielkie dzięki.
user1617676

12

Jeśli używasz ASP.NET 5 z ASP.NET MVC 6 , większość z tych odpowiedzi po prostu nie zadziała, ponieważ zwykle pozwolisz MVC utworzyć dla Ciebie odpowiednią kolekcję tras (przy użyciu domyślnych konwencji RESTful), co oznacza, że nie znajdziesz żadnego Routes.MapRoute()wezwania do edycji do woli.

ConfigureServices()Metoda wywoływana przez Startup.cspliku zarejestruje MVC z ramami wstrzykiwania zależności wbudowanych w ASP.NET 5: w ten sposób, kiedy zadzwonić ApplicationBuilder.UseMvc()później w tej klasie, ramy MVC automatycznie doda tych tras domyślnych aplikacji. Możemy spojrzeć na to, co dzieje się za maską, patrząc na UseMvc()implementację metody w kodzie źródłowym frameworka:

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

Zaletą tego jest to, że framework obsługuje teraz całą ciężką pracę, iterując przez wszystkie akcje kontrolera i konfigurując ich domyślne trasy, oszczędzając w ten sposób trochę zbędnej pracy.

Złe jest to, że istnieje niewiele lub nie ma dokumentacji na temat dodawania własnych tras. Na szczęście możesz to łatwo zrobić, używając podejścia opartego na konwencji i / lub podejściu opartym na atrybutach (inaczej routing atrybutów ).

Oparte na konwencji

W swojej klasie Startup.cs zamień to:

app.UseMvc();

z tym:

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

Oparte na atrybutach

Wspaniałą cechą MVC6 jest to, że można również definiować trasy dla poszczególnych kontrolerów, dekorując Controllerklasę i / lub Actionmetody odpowiednimi parametrami RouteAttributei / lub HttpGet/ HttpPostszablonowymi, takimi jak następujące:

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

Ten kontroler będzie obsługiwał następujące żądania:

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

Zauważ również, że jeśli użyjesz tych dwóch podejść jednocześnie, trasy oparte na atrybutach (jeśli zostaną zdefiniowane) zastąpią trasy oparte na Konwencji, a obie z nich zastąpią trasy domyślne zdefiniowane przez UseMvc().

Aby uzyskać więcej informacji, możesz również przeczytać następujący post na moim blogu.


1
To jest doskonałe! Żadna z pozostałych odpowiedzi nie dała mi tego, czego potrzebowałem. Ale uratowałeś mnie :)
Król Artur Trzeci

Czy istnieje sposób na użycie predefiniowanego modelu jako drugiego parametru? Na przykład, kiedy jestem łatanie pewną użytkownikowi takiego: public IActionResult Patch(int id, [FromQuery] Person person), wszystkie właściwości przychodzące są nieważne!
Król Artur Trzeci


0

Po prostu zmodyfikuj plik WebAPIConfig.cs, jak pokazano poniżej

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

Następnie zaimplementuj swoje API, jak poniżej

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

0

Web APi 2 i nowsze wersje obsługują nowy typ routingu, zwany routingiem atrybutów. Jak sama nazwa wskazuje, routing atrybutów używa atrybutów do definiowania tras. Routing atrybutów zapewnia większą kontrolę nad identyfikatorami URI w internetowym interfejsie API. Na przykład można łatwo utworzyć identyfikatory URI opisujące hierarchie zasobów.

Na przykład:

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

Idealnie sprawdzi się i nie potrzebujesz żadnego dodatkowego kodu np. W WebApiConfig.cs. Musisz tylko upewnić się, że routing interfejsu API sieci Web jest włączony lub nie w WebApiConfig.cs, jeśli nie, możesz aktywować jak poniżej:

        // Web API routes
        config.MapHttpAttributeRoutes();

Nie musisz robić nic więcej ani niczego zmieniać w WebApiConfig.cs. Aby uzyskać więcej informacji, zajrzyj do tego artykułu .

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.