Wiele metody HttpPost w kontrolerze interfejsu API sieci Web

126

Zaczynam korzystać z projektu MVC4 Web API, mam kontroler z wieloma HttpPostmetodami. Kontroler wygląda następująco:

Kontroler

public class VTRoutingController : ApiController
{
    [HttpPost]
    public MyResult Route(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }

    [HttpPost]
    public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }
}

Tutaj MyRequestTemplatereprezentuje klasę szablonu odpowiedzialną za obsługę Json przechodzącą przez żądanie.

Błąd:

Kiedy wykonuję żądanie za pomocą programu Fiddler dla http://localhost:52370/api/VTRouting/TSPRoutelub http://localhost:52370/api/VTRouting/Route otrzymuję błąd:

Znaleziono wiele działań pasujących do żądania

Jeśli usunę jedną z powyższych metod, działa dobrze.

Global.asax

Próbowałem zmodyfikować domyślną tablicę routingu w programie global.asax, ale nadal pojawia się błąd, myślę, że mam problem z definiowaniem tras w global.asax. Oto, co robię w global.asax.

public static void RegisterRoutes(RouteCollection routes)
{
    routes.MapHttpRoute(
        name: "MyTSPRoute",
        routeTemplate: "api/VTRouting/TSPRoute",
        defaults: new { }
    );

    routes.MapHttpRoute(
        name: "MyRoute",
        routeTemplate: "api/VTRouting/Route",
        defaults: new { action="Route" }
    );
}

Wykonuję żądanie w programie Fiddler przy użyciu POST, przekazując json w RequestBody dla MyRequestTemplate.

Habib
źródło

Odpowiedzi:

143

Możesz mieć wiele akcji na jednym kontrolerze.

W tym celu musisz zrobić dwie następujące rzeczy.

  • Najpierw udekoruj akcje ActionNameatrybutem typu

     [ActionName("route")]
     public class VTRoutingController : ApiController
     {
       [ActionName("route")]
       public MyResult PostRoute(MyRequestTemplate routingRequestTemplate)
       {
         return null;
       }
    
      [ActionName("tspRoute")]
      public MyResult PostTSPRoute(MyRequestTemplate routingRequestTemplate)
      {
         return null;
      }
    }
  • Po drugie zdefiniuj następujące trasy w WebApiConfigpliku.

    // Controller Only
    // To handle routes like `/api/VTRouting`
    config.Routes.MapHttpRoute(
        name: "ControllerOnly",
        routeTemplate: "api/{controller}"               
    );
    
    
    // Controller with ID
    // To handle routes like `/api/VTRouting/1`
    config.Routes.MapHttpRoute(
        name: "ControllerAndId",
        routeTemplate: "api/{controller}/{id}",
        defaults: null,
        constraints: new { id = @"^\d+$" } // Only integers 
    );
    
    // Controllers with Actions
    // To handle routes like `/api/VTRouting/route`
    config.Routes.MapHttpRoute(
        name: "ControllerAndAction",
        routeTemplate: "api/{controller}/{action}"
    );
Asif Mushtaq
źródło
A jeśli nie chcę nakładać żadnych ograniczeń na rodzaj identyfikatora? Znaczenie: jak mogę akceptować również identyfikatory ciągów?
frapontillo
5
@frapontillo: Id powinien być integeterem, tak aby był odróżniany od nazwy trasy, w przeciwnym razie mechanizm routingu potraktuje go jako nazwę akcji, a nie id. Jeśli potrzebujesz identyfikatora jako łańcucha, możesz utworzyć akcję.
Asif Mushtaq
Zamiast tego użyłbym routingu atrybutów. W ten sposób nie będziesz musiał używać wielu tras w WebApiConfig. Sprawdź to łącze: docs.microsoft.com/en-us/aspnet/web-api/overview/…
Rich
Jeśli dodam w ten sposób, otrzymuję błąd ------------ przestrzeń nazw ImageDownloadApplication.Controllers {public class FrontModel {public string skus {get; zestaw; }} [ActionName ("ProductController")] public class ProductController: ApiController {// GET: api / NewCotroller public IEnumerable <string> Get () {return new string [] {"value1", "value2"}; }
Umashankar
41

Znacznie lepszym rozwiązaniem problemu byłoby użycie, Routektóre pozwala określić trasę w metodzie przez adnotację:

[RoutePrefix("api/VTRouting")]
public class VTRoutingController : ApiController
{
    [HttpPost]
    [Route("Route")]
    public MyResult Route(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }

    [HttpPost]
    [Route("TSPRoute")]
    public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }
}
Wisienkas
źródło
W jakiej przestrzeni nazw znajduje się Route? Używam MVC4 i Route nie jest rozpoznawany.
eaglei 22
Tak, tak powinno być. Dzięki.
newman
1
z jakiegoś powodu nie mogę zmusić tego do pracy. to jest dokładnie to, co już robiłem.
oligofren
2
Jak wyglądałby adres URL, niż zadzwonić Routei TSPRoute?
Si8
27

posługiwać się:

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

nie jest to już podejście RESTful, ale możesz teraz wywoływać swoje akcje po nazwie (zamiast pozwolić, aby interfejs API sieci Web automatycznie określał je za Ciebie na podstawie czasownika) w następujący sposób:

[POST] /api/VTRouting/TSPRoute

[POST] /api/VTRouting/Route

Wbrew powszechnemu przekonaniu nie ma nic złego w tym podejściu i nie stanowi nadużycia internetowego interfejsu API. Nadal możesz korzystać ze wszystkich niesamowitych funkcji interfejsu API sieci Web (delegowanie programów obsługi, negocjowanie treści, typy mediacji itd.) - po prostu porzuć podejście RESTful.

Filip W.
źródło
1
Dzięki za odpowiedź, ale nadal daje mi ten sam błąd.
Habib,
To niemożliwe, więc coś innego musi być źle skonfigurowane w Twojej aplikacji. Czy możesz pokazać całą konfigurację trasy? Jak dokładnie wywołujesz akcje kontrolerów?
Filip W,
Cała konfiguracja trasy znajduje się w global.asax, opublikowałem tę część w moim pytaniu, Aby złożyć wniosek, używam Fiddler-> Compose-> i wybieram Post jako operację
Habib
spróbuj usunąć wszystkie inne definicje tras i po prostu zostaw tę, którą opublikowałem. Wtedy możesz łatwo wywołać obie akcje POST znajdujące się w jednym kontrolerze (tak samo jak stare podejście MVC)
Filip W
1
Filip, używam .Net framework 4.5 z mvc4 lub Visual studio 2012 RC. Którego szablonu używasz do tworzenia projektu, twój działa idealnie
Habib
13

Punkt końcowy interfejsu API sieci Web (kontroler) to pojedynczy zasób, który akceptuje czasowniki get / post / put / delete. To nie jest normalny kontroler MVC.

Koniecznie pod adresem /api/VTRoutingmoże być tylko jedna metoda HttpPost, która akceptuje wysyłane parametry. Nazwa funkcji nie ma znaczenia , o ile dekorujesz materiałem [http]. Jednak nigdy nie próbowałem.

Edycja: to nie działa. Podczas rozwiązywania wydaje się, że kieruje się liczbą parametrów, a nie próbuje powiązać model z typem.

Możesz przeciążać funkcje, aby akceptować różne parametry. Jestem prawie pewien, że nic by się nie stało, gdybyś zadeklarował to tak, jak robisz, ale używał innych (niekompatybilnych) parametrów niż metody. Jeśli parametry są takie same, nie masz szczęścia, ponieważ powiązanie modelu nie będzie wiedział, który z nich masz na myśli.

[HttpPost]
public MyResult Route(MyRequestTemplate routingRequestTemplate) {...}

[HttpPost]
public MyResult TSPRoute(MyOtherTemplate routingRequestTemplate) {...}

Ta część działa

Domyślny szablon, który podają podczas tworzenia nowego, jest dość wyraźny i powiedziałbym, że powinieneś trzymać się tej konwencji:

public class ValuesController : ApiController
{
    // GET is overloaded here.  one method takes a param, the other not.
    // GET api/values  
    public IEnumerable<string> Get() { .. return new string[] ... }
    // GET api/values/5
    public string Get(int id) { return "hi there"; }

    // POST api/values (OVERLOADED)
    public void Post(string value) { ... }
    public void Post(string value, string anotherValue) { ... }
    // PUT api/values/5
    public void Put(int id, string value) {}
    // DELETE api/values/5
    public void Delete(int id) {}
}

Jeśli chcesz stworzyć jedną klasę, która robi wiele rzeczy, do użytku Ajax, nie ma powodu, aby nie używać standardowego wzorca kontrolera / akcji. Jedyną prawdziwą różnicą jest to, że sygnatury metod nie są tak ładne i musisz je opakować, Json( returnValue)zanim je zwrócisz.

Edytować:

Przeciążanie działa dobrze, gdy używasz standardowego szablonu (edytowanego w celu dołączenia), gdy używasz prostych typów. Poszedłem i przetestowałem również w drugą stronę, z 2 niestandardowymi obiektami z różnymi podpisami. Nigdy nie mogłem zmusić go do pracy.

  • Wiązanie ze złożonymi obiektami nie wygląda na „głębokie”, więc nie da się tego zrobić
  • Możesz obejść ten problem, przekazując dodatkowy parametr w ciągu zapytania
  • Lepszy opis niż mogę podać na temat dostępnych opcji

To zadziałało w tym przypadku, zobacz, dokąd cię doprowadzi. Wyjątek dotyczy tylko testów.

public class NerdyController : ApiController
{
    public void Post(string type, Obj o) { 
        throw new Exception("Type=" + type + ", o.Name=" + o.Name ); 
    }
}

public class Obj {
    public string Name { get; set; }
    public string Age { get; set; }
}

I tak zwany z konsoli:

$.post("/api/Nerdy?type=white", { 'Name':'Slim', 'Age':'21' } )
Andrew Backer
źródło
Próbowałem zmienić typy parametrów, ale wydaje się, że pozwala to tylko na jedną metodę Post w kontrolerze. Dziękuję za odpowiedź
Habib,
Założyłem, że spróbuje powiązać model, aby go znaleźć, ponieważ możesz przeciążać. Działa jednak z różnymi # parametrami. Ponowne napisanie tego, aby to zrobić, może nie być takie trudne, ale nie opublikowali jeszcze kodu źródłowego, więc po prostu utknąłem patrząc na brzydki demontaż
Andrew Backer
2
+1 za wyjaśnienie przyczyny, dla której to nie działa, oraz filozofię interfejsu API sieci Web.
MEMark
Doceniam awarię ... Zakładałem, że miał to być pojedynczy POST / PUT / GET na kontroler, ale nie byłem pewien ... stąd powód, dla którego to sprawdziłem. Odkąd zacząłem programować za pomocą MVC dla aplikacji internetowych, w których wiele podobnych akcji na kontroler jest normą ... wydaje się to prawie stratą, więc mogę zrozumieć, dlaczego programista chciałby to zrobić. Czy istnieje coś takiego, jak zbyt wiele kontrolerów?
Anthony Griggs
6

Istnieje możliwość dodania wielu metod pobierania i wysyłania w tym samym kontrolerze interfejsu API sieci Web. Tutaj domyślna trasa powoduje problem. Interfejs API sieci Web sprawdza dopasowanie trasy od góry do dołu, a tym samym dopasowanie domyślnej trasy dla wszystkich żądań. Domyślnie w jednym kontrolerze możliwa jest tylko jedna metoda pobierania i wysyłania. Umieść następujący kod na górze lub Skomentuj / Usuń domyślną trasę

    config.Routes.MapHttpRoute("API Default", 
                               "api/{controller}/{action}/{id}",
                               new { id = RouteParameter.Optional });
Shahid Ullah
źródło
1

Umieść prefiks trasy [RoutePrefix ("api / Profiles")] na poziomie kontrolera i umieść trasę w metodzie akcji [Route ("LikeProfile")]. Nie musisz niczego zmieniać w pliku global.asax

namespace KhandalVipra.Controllers
{
    [RoutePrefix("api/Profiles")]
    public class ProfilesController : ApiController
    {
        // POST: api/Profiles/LikeProfile
        [Authorize]
        [HttpPost]
        [Route("LikeProfile")]
        [ResponseType(typeof(List<Like>))]
        public async Task<IHttpActionResult> LikeProfile()
        {
        }
    }
}
Sushil Kumar
źródło
0

Myślę, że odpowiedź na to pytanie została już udzielona. Szukałem też czegoś, co jest kontrolerem webApi, który ma te same podpisane mehtody, ale różne nazwy. Próbowałem zaimplementować Kalkulator jako WebApi. Kalkulator ma 4 metody z tym samym podpisem, ale różnymi nazwami.

public class CalculatorController : ApiController
{
    [HttpGet]
    [ActionName("Add")]
    public string Add(int num1 = 1, int num2 = 1, int timeDelay = 1)
    {
        Thread.Sleep(1000 * timeDelay);
        return string.Format("Add = {0}", num1 + num2);
    }

    [HttpGet]
    [ActionName("Sub")]
    public string Sub(int num1 = 1, int num2 = 1, int timeDelay = 1)
    {
        Thread.Sleep(1000 * timeDelay);
        return string.Format("Subtract result = {0}", num1 - num2);
    }

    [HttpGet]
    [ActionName("Mul")]
    public string Mul(int num1 = 1, int num2 = 1, int timeDelay = 1)
    {
        Thread.Sleep(1000 * timeDelay);
        return string.Format("Multiplication result = {0}", num1 * num2);
    }

    [HttpGet]
    [ActionName("Div")]
    public string Div(int num1 = 1, int num2 = 1, int timeDelay = 1)
    {
        Thread.Sleep(1000 * timeDelay);
        return string.Format("Division result = {0}", num1 / num2);
    }
}

oraz w pliku WebApiConfig, który już masz

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

Po prostu ustaw uwierzytelnianie / autoryzację w IIS i gotowe!

Mam nadzieję że to pomoże!

Yawar Murtaza
źródło
0

Możesz użyć tego podejścia:

public class VTRoutingController : ApiController
{
    [HttpPost("Route")]
    public MyResult Route(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }

    [HttpPost("TSPRoute")]
    public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }
}
Amirhossein Yari
źródło
-1
public class Journal : ApiController
{
    public MyResult Get(journal id)
    {
        return null;
    }
}

public class Journal : ApiController
{

    public MyResult Get(journal id, publication id)
    {
        return null;
    }
}

Nie jestem pewien, czy przeciążenie metody get / post narusza koncepcję restfull api, ale działa. Gdyby ktokolwiek mógł oświecić się w tej sprawie. Co jeśli mam URI jako

uri:/api/journal/journalid
uri:/api/journal/journalid/publicationid

więc jak mogłeś zobaczyć mój dziennik w rodzaju agregatoot, chociaż mogę zdefiniować innego kontrolera do publikacji wyłącznie i przekazać numer identyfikacyjny publikacji w moim adresie URL, ale daje to znacznie więcej sensu. ponieważ moja publikacja nie istniałaby bez samego czasopisma.

mobygeek
źródło
-1

Właśnie dodałem „action = action_name” do adresu URL i w ten sposób silnik routingu wie, jakiej akcji chcę. Dodałem również atrybut ActionName do akcji, ale nie jestem pewien, czy jest potrzebny.

Rony Tesler
źródło
-1

Najlepsze i najprostsze wyjaśnienie, jakie widziałem na ten temat - http://www.binaryintellect.net/articles/9db02aa1-c193-421e-94d0-926e440ed297.aspx

  • Edytowano -

Mam go działającego tylko z Route i nie potrzebowałem RoutePrefix.

Na przykład w kontrolerze

[HttpPost]
[Route("[action]")]
public IActionResult PostCustomer
([FromBody]CustomerOrder obj)
{
}

i

[HttpPost]
[Route("[action]")]
public IActionResult PostCustomerAndOrder
([FromBody]CustomerOrder obj)
{
}

Następnie nazwa funkcji pojawia się w jquery jako -

options.url = "/api/customer/PostCustomer";

lub

options.url = "/api/customer/PostCustomerAndOrder";
Howard Shlom
źródło