Czy można przeciążać metody kontrolera w ASP.NET MVC?

327

Jestem ciekawy, czy można przeciążać metody kontrolera w ASP.NET MVC. Za każdym razem, gdy próbuję, pojawia się błąd poniżej. Dwie metody akceptują różne argumenty. Czy tego nie da się zrobić?

Bieżące żądanie działania „MyMethod” na kontrolerze typu „MyController” jest niejednoznaczne między następującymi metodami działania:

Papa Burgundia
źródło
10
@andy to samo to samo dla mvc 4 :)
basarat 10.04.2013
10
To samo dotyczy mvc 5
DhruvJoshi
10
To samo dotyczy mvc 6
Imad
7
To samo dotyczy MVC Core 1.1
kall2sollies
7
To samo dotyczy MVC Core 2.0
Guilherme

Odpowiedzi:

201

Możesz użyć tego atrybutu, jeśli chcesz, aby kod powodował przeciążenie.

[ActionName("MyOverloadedName")]

Ale będziesz musiał użyć innej nazwy akcji dla tej samej metody http (jak powiedzieli inni). W tym momencie to tylko semantyka. Wolisz mieć nazwę w swoim kodzie lub atrybucie?

Phil ma artykuł na ten temat: http://haacked.com/archive/2008/08/29/how-a-method-becomes-an-action.aspx

JD Conley
źródło
5
Główną wadą korzystania z tego i przeciążania akcji jest to, że nie można jej renderować przez ten sam plik widoku.
Jeff Martin
66
W rzeczywistości nadal może renderować ten sam plik widoku. Musisz tylko podać nazwę widoku zamiast ślepo dzwonić return View();. Na przykład: return View("MyOverloadedName");.
EAMann
1
@JD, ale Microsoft mówi .. Metoda zastosowana jako akcja kontrolera nie może być przeciążona .. Możesz to zobaczyć tutaj .. asp.net/mvc/tutorials/controllers-and-routing/…
himanshupareek66
@EAMann Nice, do tej pory zawsze określałem całą ścieżkę widoku
Alexander Derck
69

Tak. Byłem w stanie to zrobić, ustawiając HttpGet/ HttpPost(lub równoważny AcceptVerbsatrybut) dla każdej metody kontrolera na coś odrębnego, tj. HttpGetLub HttpPost, ale nie oba jednocześnie. W ten sposób może określić na podstawie typu żądania, której metody użyć.

[HttpGet]
public ActionResult Show()
{
   ...
}

[HttpPost]
public ActionResult Show( string userName )
{
   ...
}

Jedną z moich sugestii jest to, że w przypadku takim jak ten byłaby prywatna implementacja, na której polegają obie publiczne metody akcji, aby uniknąć powielania kodu.

tvanfosson
źródło
1
W przypadku MVC2 i nowszych można również użyć atrybutu HttpPost / HttpGet
yoel halb 13.12.12
@yohal Tak, byłby to kanoniczny sposób, aby poradzić sobie teraz, jeśli nie potrzebujesz obsługi wielu czasowników.
tvanfosson
3
Tylko uważaj, aby nie nadużywać tego w celu naruszenia zasad REST.
Fred
1
Jestem pewien, że to działa tylko dlatego, że twoje Show()metody mają różne podpisy. Jeśli i kiedy musisz wysłać informacje do wersji Get, Twoje wersje Get i Post kończą się tym samym podpisem, a będziesz potrzebować ActionNameatrybutu lub jednej z innych poprawek wymienionych w tym poście.
Scott Fraley,
1
@ ScottK.Fraley to prawda. Gdyby potrzebowali tego samego podpisu, trzeba by je nazwać inaczej i zastosować ActionNameAttribute. W praktyce rzadko się tak zdarza.
tvanfosson,
42

Oto coś, co możesz zrobić ... chcesz metody, która może mieć parametr, a nie ma.

Dlaczego nie spróbować tego ...

public ActionResult Show( string username = null )
{
   ...
}

To zadziałało dla mnie ... i w tej jednej metodzie możesz faktycznie przetestować, czy masz parametr przychodzący.


Zaktualizowano, aby usunąć niepoprawną składnię zerowalną w łańcuchu i użyć domyślnej wartości parametru.

Farrel
źródło
6
( stringnie może być zerowy.)
Josh M.
23
ciąg może być zerowy. W rzeczywistości jest już zerowy, po prostu nie potrzebuje „?”
ProfK
9
@ProfK - Nie, ciąg znaków to typ odwołania, który może mieć wartość NULL. To nie jest „zerowalne”. Nullable oznacza, że ​​używasz Nullable <T> (tj. T?). Josh ma na myśli to, że nie możesz tego położyć? po ciągu, ponieważ nie jest to typ wartości, a Nullable <T> akceptuje tylko typy wartości.
Erik Funkenbusch
4
Losowo znalazłem drogę do tego pytania, a potem zdałem sobie sprawę, że zamieściłem powyższy komentarz. Żadnych wspomnień z tego ... dziwne! Nadal jest prawdą, że stringnie może być nullable; ale może być null! Tak czy inaczej opublikowałem pierwszy komentarz bez szczerości.
Josh M.
20

Nie, nie i nie. Idź i wypróbuj kod kontrolera poniżej, w którym mamy przeciążonego „LoadCustomer”.

public class CustomerController : Controller
    {
        //
        // GET: /Customer/

        public ActionResult LoadCustomer()
        {
            return Content("LoadCustomer");
        }
        public ActionResult LoadCustomer(string str)
        {
            return Content("LoadCustomer with a string");
        }
    }

Jeśli spróbujesz wywołać akcję „LoadCustomer”, pojawi się błąd, jak pokazano na poniższym rysunku.

wprowadź opis zdjęcia tutaj

Polimorfizm jest częścią programowania w języku C #, podczas gdy HTTP jest protokołem. HTTP nie rozumie polimorfizmu. HTTP działa na koncepcji lub adresie URL, a adres URL może mieć tylko unikalną nazwę. Dlatego HTTP nie implementuje polimorfizmu.

Aby to naprawić, musimy użyć atrybutu „ActionName”.

public class CustomerController : Controller
    {
        //
        // GET: /Customer/

        public ActionResult LoadCustomer()
        {
            return Content("LoadCustomer");
        }

        [ActionName("LoadCustomerbyName")]
        public ActionResult LoadCustomer(string str)
        {
            return Content("LoadCustomer with a string");
        }
    }

Tak więc teraz, jeśli wykonasz wywołanie do adresu URL „Customer / LoadCustomer”, zostanie wywołana akcja „LoadCustomer”, a ze strukturą URL „Customer / LoadCustomerByName” zostanie wywołane „LoadCustomer (string str)”.

wprowadź opis zdjęcia tutaj

wprowadź opis zdjęcia tutaj

Powyższą odpowiedź wziąłem z tego artykułu kodeproject -> Przeciążenie akcji MVC

Shivprasad Koirala
źródło
Dzięki za to. Myślę, że równie dobrze możesz użyć innej nazwy akcji od samego początku, niż użyć atrybutu.
Dan
1
@ Dan, ale nie mamy polimorfizmu po stronie C #.
Shivprasad Koirala
Masz rację, nie ma przeciążenia metody kontrolera, ale nie ma to nic wspólnego z HTTP.
Chalky
Dzięki za wyjaśnienie. +1. Powinny myśleć więcej HTTP, a nie C #. Nie ma powodu, aby podchodzić do działań ze strategią OO.
15

Aby rozwiązać ten problem, możesz napisać, ActionMethodSelectorAttributektóry bada MethodInfodla każdej akcji i porównuje ją z zaksięgowanymi wartościami formularza, a następnie odrzuca każdą metodę, dla której wartości formularza nie pasują (oczywiście z wyłączeniem nazwy przycisku).

Oto przykład: - http://blog.abodit.com/2010/02/asp-net-mvc-ambiguous-match/

ALE to nie jest dobry pomysł.

Ian Mercer
źródło
@Cerbrus, ponieważ jest to okropny hack, a następna osoba, która spojrzy na kod kontrolera, będzie zdezorientowana bardzo niestandardowym podejściem.
Ian Mercer
Heh, w porządku.
Cerbrus
14

O ile wiem, możesz mieć tylko tę samą metodę, używając różnych metod http.

to znaczy

[AcceptVerbs("GET")]
public ActionResult MyAction()
{

}

[AcceptVerbs("POST")]
public ActionResult MyAction(FormResult fm)
{

}
Keeney
źródło
2
dekoracje nie mają nic wspólnego z przeciążeniem. to lista parametrów, która pozwala na przeciążenie.
Sky Sanders,
@SkySanders Nie zgadzam się, przeciążenie oparte na parametrach nie działa w metodach kontrolera MVC - czy masz na to działający przykład? Twoje zdrowie.
Chalky
Użyj [HttpPost]atrybutu zamiast [AcceptVerbs("POST")].
Fred
9

Osiągnąłem to za pomocą funkcji routingu atrybutów w MVC5. Wprawdzie jestem nowy w MVC od dziesięciolecia tworzenia stron internetowych przy użyciu WebForms, ale poniższe działania zadziałały dla mnie. W przeciwieństwie do przyjętej odpowiedzi pozwala to na renderowanie wszystkich przeciążonych działań przez ten sam plik widoku.

Najpierw włącz routing atrybutów w App_Start / RouteConfig.cs.

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapMvcAttributeRoutes();

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );            
    }
}

Opcjonalnie udekoruj klasę kontrolera domyślnym prefiksem trasy.

[RoutePrefix("Returns")]
public class ReturnsController : BaseController
{
    //.......

Następnie udekoruj działania kontrolera, które przeciążają się nawzajem, wspólną ścieżką i parametrami, które odpowiadają. Używając parametrów ograniczonych typem, możesz użyć tego samego formatu URI z identyfikatorami różnych typów.

[HttpGet]
// Returns
public ActionResult Index()
{
    //.....
}

[HttpGet]
[Route("View")]
// Returns/View
public ActionResult View()
{
    // I wouldn't really do this but it proves the concept.
    int id = 7026;
    return View(id);
}

[HttpGet]
[Route("View/{id:int}")]
// Returns/View/7003
public ActionResult View(int id)
{
    //.....
}

[HttpGet]
[Route("View/{id:Guid}")]
// Returns/View/99300046-0ba4-47db-81bf-ba6e3ac3cf01
public ActionResult View(Guid id)
{
    //.....
}

Mam nadzieję, że to pomaga i nie prowadzi kogoś na złą ścieżkę. :-)

cookdn
źródło
Dobra robota! Właśnie natrafiłem na ten problem, uratowałeś mnie! Mam również „x” lat z WebForms - więc nadal bardzo krzywa uczenia się. Nie mogę znaleźć pracy bez MVC teraz haha
Tez Wingfield
4

Możesz użyć jednego, ActionResultaby poradzić sobie z jednym Posti drugim Get:

public ActionResult Example() {
   if (Request.HttpMethod.ToUpperInvariant() == "GET") {
    // GET
   }
   else if (Request.HttpMethod.ToUpperInvariant() == "POST") {
     // Post  
   }
}

Przydatne, jeśli twoje Geti Postmetody mają pasujące podpisy.

DevDave
źródło
1
Hmm, znów wymyślam koło, ale tym razem w kształcie kwadratu. Dlaczego nie użyć po prostu atrybutów [HttpPost / Get]?
SOReader
minęło trochę czasu, ale myślę, że to zrobiłem, ponieważ MVC nie rozróżniało dwóch osobnych metod z dopasowanymi sygnałami. Używałem atrybutu HttpPost, chociaż nie użyłem HttpGet na innej metodzie.
DevDave
@DevDave oraz przypisując obie metody, upewnij się, że używasz atrybutów z system.web.mvc - a nie tych z system.web.http!
Chalky
4

Właśnie natknąłem się na to pytanie i mimo że jest już dość stare, nadal jest bardzo aktualne. Jak na ironię, jeden poprawny komentarz w tym wątku został opublikowany przez początkującego w MVC, gdy napisał ten post. Nawet dokumenty ASP.NET nie są całkowicie poprawne. Mam duży projekt i skutecznie przeciążam metody działania.

Jeśli rozumiemy routing, poza prostym domyślnym wzorcem trasy {controller} / {action} / {id}, może być oczywiste, że działania kontrolera można odwzorować przy użyciu dowolnego unikalnego wzorca. Ktoś tutaj mówił o polimorfizmie i powiedział: „HTTP nie rozumie polimorfizmu”, ale routing nie ma nic wspólnego z HTTP. Mówiąc najprościej, jest to mechanizm dopasowywania wzorca łańcucha.

Najlepszym sposobem, aby to zadziałało, jest użycie atrybutów routingu, na przykład:

[RoutePrefix("cars/{country:length(3)}")]
public class CarHireController
{
    [Route("{location}/{page:int=1}", Name = "CarHireLocation")]
    public ActionResult Index(string country, string location, int page)
    {
        return Index(country, location, null, page);
    }

    [Route("{location}/{subLocation}/{page:int=1}", Name = "CarHireSubLocation")]
    public ActionResult Index(string country, string location, string subLocation, int page)
    {
        //The main work goes here
    }
}

Działania te zajmą się adresami URL takimi jak /cars/usa/new-yorki /cars/usa/texas/dallas, które będą mapowane odpowiednio na pierwszą i drugą akcję indeksu.

Analizując ten przykładowy kontroler widać, że wykracza on poza domyślny wzorzec trasy wspomniany powyżej. Domyślnie działa dobrze, jeśli struktura adresu URL dokładnie odpowiada konwencjom nazewnictwa kodu, ale nie zawsze tak jest. Kod powinien opisywać domenę, ale adresy URL często muszą iść dalej, ponieważ ich treść powinna opierać się na innych kryteriach, takich jak wymagania SEO.

Zaletą domyślnego wzorca routingu jest to, że automatycznie tworzy on unikalne trasy. Jest to wymuszane przez kompilator, ponieważ adresy URL będą pasować do unikalnych typów kontrolerów i członków. Opracowanie własnych wzorów tras będzie wymagało dokładnego przemyślenia, aby zapewnić wyjątkowość i ich działanie.

Ważna uwaga Jedną wadą jest to, że używanie routingu do generowania adresów URL dla przeciążonych akcji nie działa, gdy jest oparte na nazwie akcji, np. Przy użyciu UrlHelper.Action. Ale działa, jeśli używa się nazwanych tras, np. UrlHelper.RouteUrl. A korzystanie z nazwanych tras jest, zgodnie z dobrze szanowanymi źródłami, sposobem na zrobienie tego ( http://haacked.com/archive/2010/11/21/named-routes-to-the-rescue.aspx/ ).

Powodzenia!

DvS
źródło
3

Możesz użyć [ActionName („NewActionName”)], aby użyć tej samej metody o innej nazwie:

public class HomeController : Controller
{
    public ActionResult GetEmpName()
    {
        return Content("This is the test Message");
    }

    [ActionName("GetEmpWithCode")]
    public ActionResult GetEmpName(string EmpCode)
    {
        return Content("This is the test Messagewith Overloaded");
    }
}
Alex Butenko
źródło
2

Potrzebowałem przeciążenia dla:

public ActionResult Index(string i);
public ActionResult Index(int groupId, int itemId);

Było mało argumentów, w których ostatecznie to zrobiłem:

public ActionResult Index(string i, int? groupId, int? itemId)
{
    if (!string.IsNullOrWhitespace(i))
    {
        // parse i for the id
    }
    else if (groupId.HasValue && itemId.HasValue)
    {
        // use groupId and itemId for the id
    }
}

Nie jest to idealne rozwiązanie, zwłaszcza jeśli masz wiele argumentów, ale dla mnie działa dobrze.

Kasey Speakman
źródło
1

W mojej aplikacji napotkałem ten sam problem. Bez modyfikacji żadnych informacji o metodzie podałem [ActionName („SomeMeaningfulName”)] na głowie akcji. problem rozwiązany

[ActionName("_EmployeeDetailsByModel")]
        public PartialViewResult _EmployeeDetails(Employee model)
        {
            // Some Operation                
                return PartialView(model);
            }
        }

[ActionName("_EmployeeDetailsByModelWithPagination")]
        public PartialViewResult _EmployeeDetails(Employee model,int Page,int PageSize)
        {

                // Some Operation
                return PartialView(model);

        }
ಅನಿಲ್
źródło
0

Utwórz metodę podstawową jako wirtualną

public virtual ActionResult Index()

Utwórz przesłoniętą metodę jako przesłonięcie

public override ActionResult Index()

Edycja: Ma to oczywiście zastosowanie tylko wtedy, gdy metoda przesłonięcia znajduje się w klasie pochodnej, która wydaje się nie być intencją PO.

Andii
źródło
2
Prawdopodobnie nie rozumiesz pytania. OP prosi o przeciążenie metody w tym samym kontrolerze, a nie nadpisanie jej w klasie pochodnej.
Ace
@Andiih: co się stanie, jeśli obie metody będą w tym samym kontrolerze?
Dharmik Bhandari
0

Dla każdej metody kontrolera dozwolony jest tylko jeden podpis publiczny. Jeśli spróbujesz go przeładować, kompiluje się, ale pojawia się błąd działania.

Jeśli nie chcesz używać różnych czasowników (takich jak [HttpGet]i[HttpPost] atrybuty), aby rozróżnić przeciążone metody (które będą działać) lub zmienić routing, pozostaje to, że możesz albo podać inną metodę o innej nazwie, albo możesz wysyłka wewnątrz istniejącej metody. Oto jak to zrobiłem:

Kiedyś wpadłem w sytuację, w której musiałem zachować zgodność wsteczną. Pierwotna metoda oczekiwała dwóch parametrów, ale nowa miała tylko jeden. Przeładowanie zgodne z oczekiwaniami nie działało, ponieważ MVC nie znalazł już punktu wejścia.

Aby rozwiązać ten problem, wykonałem następujące czynności:

  1. Zmieniono 2 przeciążone metody działania z publicznego na prywatny
  2. Utworzono jedną nową metodę publiczną, która zawierała „tylko” 2 parametry ciągu. Ten działał jako dyspozytor, tj .:

    public ActionResult DoSomething(string param1, string param2)
    {
        if (string.IsNullOrEmpty(param2))
        {
            return DoSomething(ProductName: param1);
        }
        else
        {
            int oldId = int.Parse(param1);
            return DoSomething(OldParam: param1, OldId: oldId);
        }
    }
    
    
    private ActionResult DoSomething(string OldParam, int OldId)
    {
        // some code here
        return Json(result);
    }
    
    
    private ActionResult DoSomething(string ProductName)
    {
        // some code here
        return Json(result);
    }

Oczywiście jest to włamanie, które należy później zreformować. Ale na razie zadziałało to dla mnie.

Możesz także utworzyć dyspozytora, takiego jak:

public ActionResult DoSomething(string action, string param1, string param2)
{
    switch (action)
    {
        case "update":
            return UpdateAction(param1, param2);
        case "remove":
            return DeleteAction(param1);
    }
}

Widać, że UpdateAction potrzebuje 2 parametrów, a DeleteAction tylko jednego.

Matt
źródło
0

Przepraszam za opóźnienie. Miałem ten sam problem i znalazłem link z dobrymi odpowiedziami, czy to pomoże nowym chłopakom

Wszystkie kredyty dla strony internetowej BinaryIntellect i autorów

Zasadniczo istnieją cztery sytuacje: użycie czasowników differents , użycie routingu , oznaczenie przeciążenia za pomocą atrybutu [NoAction] i zmiana nazwy atrybutu akcji za pomocą [ActionName]

Zależy to od twoich wymagań i twojej sytuacji.

W każdym razie skorzystaj z linku:

Link: http://www.binaryintellect.net/articles/8f9d9a8f-7abf-4df6-be8a-9895882ab562.aspx

Eric Saboia
źródło
-1

Jeśli jest to próba użycia jednej akcji GET dla kilku widoków POST dla kilku akcji z różnymi modelami, spróbuj dodać akcję GET dla każdej akcji POST, która przekierowuje do pierwszego GET, aby zapobiec 404 przy odświeżaniu.

Długie ujęcie, ale powszechny scenariusz.

Panos Roditakis
źródło