Znaleziono wiele akcji pasujących do żądania w interfejsie WWW

243

Wciąż pojawia się ten błąd, gdy próbuję mieć 2 metody „Get”

Znaleziono wiele akcji pasujących do żądania: webapi

Rozglądałem się za innymi podobnymi pytaniami na ten temat na stosie, ale nie rozumiem.

Mam 2 różne nazwy i używam atrybutu „HttpGet”

[HttpGet]
public HttpResponseMessage Summary(MyVm vm)
{
    return null;
}

[HttpGet]
public HttpResponseMessage FullDetails()
{
    return null;
}
chobo2
źródło

Odpowiedzi:

485

Twoja mapa trasy jest prawdopodobnie taka:

routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });

Ale aby wykonać wiele działań przy użyciu tej samej metody http, musisz podać webapi więcej informacji na trasie:

routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional });

Zauważ, że routeTemplate zawiera teraz akcję. Wiele informacji tutaj: http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api

Aktualizacja:

W porządku, teraz, kiedy myślę, że rozumiem, o co tu chodzi, jest jeszcze jedno podejście:

Być może nie potrzebujesz parametru url akcji i powinieneś opisać treść, której szukasz w inny sposób. Ponieważ mówisz, że metody zwracają dane z tego samego obiektu, pozwól parametrom wykonać opis za Ciebie.

Na przykład dwie metody można przekształcić w:

public HttpResponseMessage Get()
{
    return null;
}

public HttpResponseMessage Get(MyVm vm)
{
    return null;
}

Jakie dane przekazujesz w obiekcie MyVm? Jeśli jesteś w stanie przepuścić zmienne przez URI, sugerowałbym pójście tą drogą. W przeciwnym razie musisz wysłać obiekt w treści żądania, a to nie jest zbyt HTTP podczas wykonywania GET (choć działa, po prostu użyj [FromBody] przed MyVm).

Mam nadzieję, że to pokazuje, że możesz mieć wiele metod GET w jednym kontrolerze bez użycia nazwy akcji lub nawet atrybutu [HttpGet].

Jed
źródło
Czy są jakieś zalety robienia w ten czy inny sposób? Jeśli zrobię to drugie, czy muszę po prostu wykonać akcję HTTP na każdej metodzie? Czy to duża wada?
chobo2
3
To, czy jedna z nich ma przewagę nad drugą, naprawdę zależy od twojego projektu. Jeśli budujesz interfejs API RESTful, powinieneś użyć konwencji HTTP (GET, POST, PUT, DELETE ...). W takim przypadku pierwszy blok kodu routingu jest dobrym rozwiązaniem, ale będziesz potrzebował innego kontrolera dla każdej jednostki, którą ujawniasz za pośrednictwem interfejsu API. W oparciu o nazwy metod, zgaduję, że tak nie jest, więc użyj bardziej opisowego routingu. Gdy twoja trasa zawiera akcję, będziesz chciał jawnie umieścić atrybut http dla każdej metody.
Jed
1
@ chobo2 Dlaczego nie skorzystać z metod, które są odpowiednio nazwane w kontrolerze? GetSummary (MyVm wm) i GetDetails ()
Jed
1
Dzięki za odpowiedź, pomogłem mi zrozumieć, dlaczego rozwiązywanie trasy nie działa, mimo że oba moje działania mają różne nazwy. Jestem naprawdę zdezorientowany, dlaczego nie jest to tylko domyślne zachowanie (tj. Dlaczego domyślny szablon trasy w pliku webapiconfig.cs nie zawiera „{action}”)!
Tejas Sharma
1
@bruno, jeśli korzystasz z obszarów, możesz także dodać interfejsy API specyficzne dla administratora, takie jak to w AdminAreaRegistration stackoverflow.com/a/9849011/16940
Simon_Weaver
67

Aktualizacja od Web API 2.

Dzięki tej konfiguracji interfejsu API w pliku WebApiConfig.cs:

public static void Register(HttpConfiguration config)
{
    //// Web API routes
    config.MapHttpAttributeRoutes(); //Don't miss this

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

Możesz skierować nasz kontroler w następujący sposób:

[Route("api/ControllerName/Summary")]
[HttpGet]
public HttpResponseMessage Summary(MyVm vm)
{
    rturn null;
}

[Route("api/ControllerName/FullDetails")]
[HttpGet]
public HttpResponseMessage FullDetails()
{
    return null;
}

Gdzie ControllerName to nazwa twojego kontrolera (bez „kontrolera”). Pozwoli ci to uzyskać każdą akcję ze wskazaną powyżej trasą.

Do dalszego czytania: http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2

Marc Stevenson
źródło
Naprawdę podobało mi się to rozwiązanie. Moja domyślna trasa jest nadal taka sama i mam trasę „wyjątkową” dla wyjątków
Leandro De Mello Fagundes,
możesz również zmapować parametry na adres URL EX: [Route ("api / ControllerName / Summary / {vm}")]
nulltron
15

W interfejsie API sieci Web (domyślnie) metody są wybierane na podstawie kombinacji metody HTTP i wartości trasy .

MyVmwygląda jak złożony obiekt, odczytany przez program formatujący z treści, więc masz dwie identyczne metody pod względem danych trasy (ponieważ żadna z nich nie ma żadnych parametrów z trasy) - co uniemożliwia funkcji dispatcher ( IHttpActionSelector) dopasowanie odpowiedniej .

Musisz je rozróżnić, używając kwerendy lub parametru trasy, aby rozwiązać niejednoznaczność.

Filip W.
źródło
14

Po wielu poszukiwaniach w sieci i próbie znalezienia najbardziej odpowiedniej formy do routingu mapy, jeśli znalazłem następujące

config.Routes.MapHttpRoute("DefaultApiWithId", "Api/{controller}/{id}", new { id =RouteParameter.Optional }, new { id = @"\d+" });
config.Routes.MapHttpRoute("DefaultApiWithAction", "Api/{controller}/{action}");

Te mapowania dotyczą zarówno mapowania nazw akcji, jak i podstawowej konwencji HTTP (GET, POST, PUT, DELETE)

Moatasem bakri
źródło
9
Dla mnie to zadziałało, ale dopiero po zmianie kolejności tras w konfiguracji tras, tak aby ta z działaniem pojawiła się pierwsza
Fredrik Stolpe
ważne jest dokładnie zamówienie
AT
5

Bez użycia akcji opcje byłyby następujące:

  1. przenieś jedną z metod do innego kontrolera, aby się nie kolidowały.

  2. użyj tylko jednej metody, która bierze parametr, a jeśli jest null, wywołaj drugą metodę z kodu.

Joanna Derks
źródło
To może być rozwiązanie, ale nie optymalne, zresztą +1 z mojej strony :)
satish kumar V
4

To rozwiązanie działało dla mnie.

Najpierw umieść Route2 w WebApiConfig. Dodaj także HttpGet i HttpPost przed każdą metodą i dołącz nazwę kontrolera i nazwę metody do adresu URL.

WebApiConfig =>

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

Kontroler =>

public class ValuesController : ApiController
{

    [HttpPost]
    public string GetCustomer([FromBody] RequestModel req)
    {
        return "Customer";
    }

    [HttpPost]
    public string GetCustomerList([FromBody] RequestModel req)
    {
        return "Customer List";
    }
}

Url =>

http://localhost:7050/api/Values/GetCustomer

http://localhost:7050/api/Values/GetCustomerList
Alan Rezende
źródło
4

To jest odpowiedź dla każdego, kto wie, że wszystko jest poprawne i sprawdził 50 razy .....

Upewnij się, że nie patrzysz wielokrotnie RouteConfig.cs.

Plik, który chcesz edytować, ma nazwę WebApiConfig.cs

Ponadto prawdopodobnie powinien wyglądać dokładnie tak:

using System.Web.Http;

namespace My.Epic.Website
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
          config.MapHttpAttributeRoutes();

          // api/Country/WithStates
          config.Routes.MapHttpRoute(
            name: "ControllerAndActionOnly",
            routeTemplate: "api/{controller}/{action}",
            defaults: new { },
            constraints: new { action = @"^[a-zA-Z]+([\s][a-zA-Z]+)*$" });

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

Mógłbym zaoszczędzić około 3 godzin.

CarComp
źródło
1
Dzięki, oszczędziłeś mi około 3 godzin
geedubb
3

Odkryłem, że gdy mam dwie metody Get, jedną bez parametrów i jedną ze złożonym typem jako parametr, otrzymuję ten sam błąd. Rozwiązałem to, dodając fikcyjny parametr typu int o nazwie Id jako mój pierwszy parametr, a następnie mój parametr typu złożonego. Następnie dodałem parametr typu złożonego do szablonu trasy. Poniższe działało dla mnie.

Najpierw zdobądź:

public IEnumerable<SearchItem> Get()
{
...
}

Drugie zdobądź:

public IEnumerable<SearchItem> Get(int id, [FromUri] List<string> layers)
{
...
}

WebApiConfig:

config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}/{layers}",
    defaults: new { id = RouteParameter.Optional, layers RouteParameter.Optional }
);
Andrew Terwiel
źródło
3

Jest to możliwe dzięki zastosowaniu kontrolera MVC zamiast kontrolera Web API. Sprawdź przestrzeń nazw w kontrolerze interfejsu API sieci Web, powinna ona wyglądać następująco

using System.Net;
using System.Net.Http;
using System.Web.Http;

Jeśli przestrzeń nazw jest następująca, oznacza to, że wystąpił powyższy błąd w wywołaniu metody kontrolera interfejsu API

using System.Web;
using System.Web.Mvc;
Ujwal Khairnar
źródło
2

Sprawdź, czy masz dwie metody, które mają inną nazwę i te same parametry.

Jeśli tak, usuń jedną z metod i spróbuj.

Ramesh
źródło
2

Natknąłem się na ten problem, próbując rozszerzyć moje kontrolery WebAPI o dodatkowe działania.

Załóżmy, że tak

public IEnumerable<string> Get()
{
    return this.Repository.GetAll();
}

[HttpGet]
public void ReSeed()
{
    // Your custom action here
}

Istnieją teraz dwie metody, które spełniają żądanie dla / api / controller, które wyzwala problem opisany przez TS.

Nie chciałem dodawać parametrów „obojętnych” do moich dodatkowych akcji, więc sprawdziłem domyślne akcje i wymyśliłem:

[ActionName("builtin")]
public IEnumerable<string> Get()
{
    return this.Repository.GetAll();
}

dla pierwszej metody w połączeniu z „podwójnym” wiązaniem trasy:

config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { action = "builtin", id = RouteParameter.Optional },
    constraints: new { id = @"\d+" });

config.Routes.MapHttpRoute(
    name: "CustomActionApi",
    routeTemplate: "api/{controller}/{action}");

Zauważ, że nawet jeśli w pierwszym szablonie trasy nie ma parametru „akcji”, najwyraźniej nadal możesz skonfigurować akcję domyślną, która pozwala nam oddzielić routing „normalnych” wywołań WebAPI i wywołań do dodatkowej akcji.

Bjørn van Dommelen
źródło
2

W moim przypadku wszystko było w porządku

1) Web Config został poprawnie skonfigurowany 2) Prefiks trasy i atrybuty trasy były prawidłowe

Nadal otrzymywałem błąd. W moim przypadku atrybut „Trasa” (naciskając F12) wskazywał na System.Web.MVc, ale nie System.Web.Http, który spowodował problem.

Hemanth Vatti
źródło
Ta odpowiedź bardzo mi pomogła!
tvb108108
1

Możesz dodać [Route("api/[controller]/[action]")]do swojej klasy kontrolera.

[Route("api/[controller]/[action]")]
[ApiController]
public class MySuperController : ControllerBase
{
 ...
}
emert117
źródło
0

Wiem, że to stare pytanie, ale czasami, gdy używasz zasobów usług, takich jak AngularJS, do łączenia się z WebAPI, upewnij się, że używasz właściwej trasy, w przeciwnym razie ten błąd się zdarza.

Suresh Kaushik
źródło
0

Upewnij się, że NIE dekorujesz metod kontrolera dla domyślnych akcji GET | PUT | POST | DELETE atrybutem [HttpPost / Put / Get / Delete]. Dodałem ten atrybut do mojej akcji kontrolera waniliowej poczty i spowodował błąd 404.

Mam nadzieję, że to pomaga komuś, ponieważ może być bardzo frustrujące i zatrzymać postęp.

Wejdź
źródło
0

Na przykład => TestController

        [HttpGet]
        public string TestMethod(int arg0)
        {
            return "";
        }

        [HttpGet]
        public string TestMethod2(string arg0)
        {
            return "";
        }

        [HttpGet]
        public string TestMethod3(int arg0,string arg1)
        {
            return "";
        }

Jeśli możesz zmienić tylko plik WebApiConfig.cs.

 config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}/",
                defaults: null
            );

Otóż ​​to :)

I wynik: wprowadź opis zdjęcia tutaj

Cemre Onur Baş
źródło
0

Czy próbowałeś:

[HttpGet("Summary")]
public HttpResponseMessage Summary(MyVm vm)
{
    return null;
}

[HttpGet("FullDetails")]
public HttpResponseMessage FullDetails()
{
    return null;
}
Deepak Shaw
źródło
1
Nie będzie to kompilować w projektach innych niż .NET Core, ponieważ HttpGetatrybut nie ma konstruktora, który akceptuje argument ciągu.
Hoppeduppeanut