È possibile sovraccaricare i metodi del controller in ASP.NET MVC?


327

Sono curioso di vedere se è possibile sovraccaricare i metodi del controller in ASP.NET MVC. Ogni volta che provo, visualizzo l'errore di seguito. I due metodi accettano argomenti diversi. È qualcosa che non si può fare?

L'attuale richiesta di azione "MyMethod" sul tipo di controller "MyController" è ambigua tra i seguenti metodi di azione:


10
@andy è lo stesso anche per mvc 4 :)
basarat

10
E lo stesso per mvc 5
DhruvJoshi il

10
E lo stesso per mvc 6
Imad

7
E lo stesso per MVC Core 1.1
kall2sollies,

7
E lo stesso per MVC Core 2.0
Guilherme,

Risposte:


201

È possibile utilizzare l'attributo se si desidera sovraccaricare il codice.

[ActionName("MyOverloadedName")]

Ma dovrai usare un nome di azione diverso per lo stesso metodo http (come altri hanno già detto). Quindi è solo la semantica a quel punto. Preferiresti avere il nome nel tuo codice o nel tuo attributo?

Phil ha un articolo correlato a questo: http://haacked.com/archive/2008/08/29/how-a-method-becomes-an-action.aspx


5
Il principale svantaggio di utilizzare questo e sovraccaricare la tua azione è che non può più essere renderizzato dallo stesso file di visualizzazione.
Jeff Martin,

66
In realtà, può comunque eseguire il rendering dello stesso file di visualizzazione. Devi solo specificare il nome della vista invece di chiamare alla cieca return View();. Ad esempio: return View("MyOverloadedName");.
EAMann,

1
@JD ma Microsoft dice .. Un metodo usato come azione del controller non può essere sovraccaricato .. Puoi vederlo qui .. asp.net/mvc/tutorials/controllers-and-routing/…
himanshupareek66

@EAMann Nice, ho sempre definito l'intero percorso per la vista fino ad ora
Alexander Derck

69

Sì. Sono stato in grado di farlo impostando HttpGet/ HttpPost(o AcceptVerbsattributo equivalente ) per ciascun metodo del controller su qualcosa di distinto, cioè, HttpGeto HttpPost, ma non entrambi. In questo modo può dire in base al tipo di richiesta quale metodo utilizzare.

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

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

Un suggerimento che ho è che, per un caso come questo, sarebbe di avere un'implementazione privata su cui entrambi i metodi di azione pubblica si basano per evitare la duplicazione del codice.


1
Con MVC2 e fino si può utilizzare anche la HttpPost / HttpGet attributo
halb yoel

@yohal Sì, sarebbe il modo canonico di gestirlo ora se non hai bisogno di supportare più verbi.
tvanfosson,

3
Fai attenzione a non abusare di questo per violare i principi di REST.
Fred,

1
Abbastanza sicuro che questo funziona solo perché i tuoi Show()metodi hanno firme diverse. Se e quando è necessario inviare informazioni nella versione Get, le versioni Get e Post finiranno con la stessa firma e sarebbe necessario l' ActionNameattributo o una delle altre correzioni menzionate in questo post.
Scott Fraley,

1
@ ScottK.Fraley è vero. Se avessero bisogno della stessa firma, dovresti nominarli in modo diverso e applicare il ActionNameAttribute. In pratica, raramente ho scoperto che era così.
tvanfosson,

42

Ecco qualcos'altro che potresti fare ... vuoi un metodo che sia in grado di avere un parametro e non.

Perché non provare questo ...

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

Questo ha funzionato per me ... e in questo metodo, puoi effettivamente testare per vedere se hai il parametro in arrivo.


Aggiornato per rimuovere la sintassi nullable non valida sulla stringa e utilizzare un valore di parametro predefinito.


6
( stringnon può essere nullable.)
Josh M.

23
la stringa può essere nullable. In realtà, è già nullable, semplicemente non è necessario il '?'
ProfK

9
@ProfK - No, la stringa è un tipo di riferimento che può essere nullo. Non è "nullable". Nullable significa che stai usando la Nullable <T> (cioè T?). Il punto di Josh è che non puoi mettere il? dopo stringa perché non è un tipo di valore e Nullable <T> accetta solo tipi di valore.
Erik Funkenbusch,

4
Ho trovato casualmente la strada per tornare a questa domanda e poi mi sono reso conto di aver pubblicato il commento sopra. Nessun ricordo di questo ... strano! È ancora vero che a stringnon può essere nullable; ma può essere null! Ad ogni modo ho pubblicato il commento iniziale senza sincerità.
Josh M.,

20

No, No e No. Vai e prova il codice del controller qui sotto dove abbiamo "LoadCustomer" sovraccarico.

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

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

Se si tenta di richiamare l'azione "LoadCustomer", si otterrà un errore, come mostrato nella figura seguente.

inserisci qui la descrizione dell'immagine

Il polimorfismo fa parte della programmazione C # mentre HTTP è un protocollo. HTTP non capisce il polimorfismo. HTTP funziona sul concetto o sull'URL e l'URL può avere solo nomi univoci. Quindi HTTP non implementa il polimorfismo.

Per risolvere lo stesso dobbiamo usare l'attributo "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");
        }
    }

Quindi ora se si effettua una chiamata all'URL "Cliente / LoadCustomer" verrà invocata l'azione "LoadCustomer" e con la struttura URL "Customer / LoadCustomerByName" verrà invocato il "LoadCustomer (string str)".

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

La risposta di cui sopra ho preso da questo articolo di codeproject -> Sovraccarico dell'azione MVC


Grazie per questo. Suppongo che potresti anche usare un nome di azione diverso dall'inizio piuttosto che usare l'attributo.
Dan,

1
@Dan ma non abbiamo polimorfismo sul lato C #.
Shivprasad Koirala,

Hai ragione, non c'è un sovraccarico del metodo del controller ma non ha nulla a che fare con HTTP.
Chalky,

Grazie per il chiarimento. +1. Dovrebbe pensare più HTTP e non C #. Non c'è motivo di affrontare le azioni con una strategia OO.

15

Per ovviare a questo problema è possibile scrivere un oggetto ActionMethodSelectorAttributeche esamina il MethodInfoper ogni azione e lo confronta con i valori del modulo pubblicati e quindi rifiuta qualsiasi metodo per il quale i valori del modulo non corrispondono (escluso il nome del pulsante, ovviamente).

Ecco un esempio: - http://blog.abodit.com/2010/02/asp-net-mvc-ambiguous-match/

MA questa non è una buona idea.


@Cerbrus perché è un hack orribile e la prossima persona che guarda il tuo codice controller sarà confusa da un approccio molto non standard.
Ian Mercer,

Heh, abbastanza giusto.
Cerbrus

14

Per quanto ne so, puoi avere lo stesso metodo solo quando usi metodi http diversi.

vale a dire

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

}

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

}

2
le decorazioni non hanno nulla a che fare con il sovraccarico. è l'elenco dei parametri che consente il sovraccarico.
Sky Sanders,

@SkySanders Non sono d'accordo, il sovraccarico basato sui parametri non funziona nei metodi del controller MVC - ne hai un esempio funzionante? Saluti.
Chalky,

Utilizzare l' [HttpPost]attributo anziché [AcceptVerbs("POST")].
Fred,

9

Ho raggiunto questo obiettivo con l'aiuto di routing attributi in MVC5. Devo ammettere che sono una novità di MVC proveniente da un decennio di sviluppo web con WebForms, ma per me ha funzionato. A differenza della risposta accettata, ciò consente di eseguire il rendering di tutte le azioni sovraccaricate dallo stesso file di visualizzazione.

Abilitare innanzitutto il routing degli attributi in 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 }
        );            
    }
}

Facoltativamente, decorare la classe del controller con un prefisso di route predefinito.

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

Quindi decorare le azioni del controller che si sovraccaricano a vicenda con un percorso comune e parametri adeguati. Utilizzando i parametri con vincolo di tipo è possibile utilizzare lo stesso formato URI con ID di tipi diversi.

[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)
{
    //.....
}

Spero che questo aiuti e non porti qualcuno sulla strada sbagliata. :-)


Bel lavoro! Ho appena incontrato questo problema, mi hai salvato! Ho anche "x" anni con WebForms, quindi ancora una curva di apprendimento. Non riesco a trovare un lavoro senza MVC ora-a-day haha
Tez Wingfield

4

È possibile utilizzare un singolo ActionResultper gestire entrambi Poste Get:

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

Utile se i tuoi metodi Gete Posthanno firme corrispondenti.


1
Hmm, kind-a reinventando di nuovo la ruota, ma questa volta in una forma quadrata. Perché non usare semplicemente gli attributi [HttpPost / Get]?
SOReader,

è passato un po 'di tempo, ma penso di averlo fatto perché MVC non stava distinguendo tra due metodi separati con sigs corrispondenti. Stavo usando l'attributo HttpPost, anche se non stavo mettendo HttpGet sull'altro metodo ..
DevDave,

@DevDave oltre ad attribuire entrambi i metodi, assicurati di utilizzare gli attributi di system.web.mvc e non quelli di system.web.http!
Chalky,

4

Mi sono appena imbattuto in questa domanda e, anche se ora è piuttosto vecchio, è ancora molto rilevante. Ironia della sorte, l'unico commento corretto in questa discussione è stato pubblicato da un principiante confesso in MVC quando ha scritto il post. Anche i documenti ASP.NET non sono completamente corretti. Ho un grande progetto e sovraccarico con successo i metodi di azione.

Se si comprende il routing, al di là del semplice modello di route predefinito {controller} / {action} / {id}, potrebbe essere ovvio che le azioni del controller possono essere mappate utilizzando qualsiasi modello univoco. Qualcuno qui ha parlato di polimorfismo e ha detto: "HTTP non capisce il polimorfismo", ma il routing non ha nulla a che fare con HTTP. È, in poche parole, un meccanismo per la corrispondenza del modello di stringa.

Il modo migliore per farlo funzionare è utilizzare gli attributi di routing, ad esempio:

[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
    }
}

Queste azioni si prenderanno cura di URL come /cars/usa/new-yorke/cars/usa/texas/dallas , che mapperanno rispettivamente alla prima e alla seconda azione dell'Indice.

Esaminando questo controller di esempio è evidente che va oltre il modello di route predefinito menzionato sopra. L'impostazione predefinita funziona bene se la struttura dell'URL corrisponde esattamente alle convenzioni di denominazione del codice, ma non è sempre così. Il codice dovrebbe essere descrittivo del dominio, ma spesso gli URL devono andare oltre perché il loro contenuto dovrebbe essere basato su altri criteri, come i requisiti SEO.

Il vantaggio del modello di routing predefinito è che crea automaticamente percorsi univoci. Ciò viene applicato dal compilatore poiché gli URL corrisponderanno a tipi e membri di controller univoci. Rotolare i tuoi percorsi personali richiederà un'attenta riflessione per garantire l'unicità e il loro funzionamento.

Nota importante L'unico inconveniente è che l'uso del routing per generare URL per azioni sovraccariche non funziona quando si basa su un nome di azione, ad esempio quando si utilizza UrlHelper.Action. Ma funziona se si utilizzano route denominate, ad esempio UrlHelper.RouteUrl. E utilizzare percorsi con nome è, secondo fonti rispettate, la strada da percorrere in ogni caso ( http://haacked.com/archive/2010/11/21/named-routes-to-the-rescue.aspx/ ).

In bocca al lupo!


3

È possibile utilizzare [ActionName ("NewActionName")] per utilizzare lo stesso metodo con un nome diverso:

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

2

Avevo bisogno di un sovraccarico per:

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

Ci sono stati pochi argomenti sufficienti in cui ho finito per fare questo:

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

Non è una soluzione perfetta, soprattutto se hai molti argomenti, ma funziona bene per me.


1

Ho riscontrato lo stesso problema anche nella mia domanda. Senza modificare alcuna informazione sul metodo, ho fornito [ActionName ("SomeMeaningfulName")] sul capo azione. problema risolto

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

        }

0

Creare il metodo di base come virtuale

public virtual ActionResult Index()

Crea il metodo override come override

public override ActionResult Index()

Modifica: questo ovviamente si applica solo se il metodo di sostituzione si trova in una classe derivata che sembra non essere stata l'intenzione del PO.


2
Probabilmente stai fraintendendo la domanda. L'OP chiede di sovraccaricare il metodo nello stesso controller, non di sostituirlo in una classe derivata.
Ace

@Andiih: cosa succederà se entrambi i metodi si trovano nello stesso controller?
Dharmik Bhandari,


0

È consentita una sola firma pubblica per ciascun metodo del controller. Se si tenta di sovraccaricarlo, verrà compilato, ma viene visualizzato l'errore di runtime che si è verificato.

Se non sei disposto a usare diversi verbi (come il [HttpGet]e[HttpPost] attributi ) per differenziare i metodi sovraccarichi (che funzioneranno) o modificare il routing, ciò che rimane è che puoi fornire un altro metodo con un nome diverso, oppure puoi spedizione all'interno del metodo esistente. Ecco come l'ho fatto:

Una volta sono entrato in una situazione in cui dovevo mantenere la retrocompatibilità. Il metodo originale prevedeva due parametri, ma il nuovo ne aveva solo uno. Il sovraccarico come mi aspettavo non funzionava perché MVC non trovava più il punto di ingresso.

Per risolvere ciò, ho fatto quanto segue:

  1. Modificati i 2 metodi di azione sovraccarichi da pubblico a privato
  2. Creato un nuovo metodo pubblico che conteneva "solo" 2 parametri di stringa. Quello ha agito come un dispatcher, vale a dire:

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

Naturalmente, questo è un trucco e dovrebbe essere refactored più tardi. Ma per il momento, ha funzionato per me.

Puoi anche creare un dispatcher come:

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

Puoi vedere che UpdateAction ha bisogno di 2 parametri, mentre DeleteAction ha solo bisogno di uno.


0

Scusa per il ritardo. Ero con lo stesso problema e ho trovato un link con buone risposte, questo potrebbe aiutare i nuovi ragazzi

Tutti i crediti per il sito Web BinaryIntellect e gli autori

Fondamentalmente, ci sono quattro situazioni: usare verbi differenti , usare routing , contrassegno di sovraccarico con l'attributo [NoAction] e cambiare il nome dell'attributo dell'azione con [ActionName]

Quindi, dipende che sono i tuoi requisiti e la tua situazione.

Tuttavia, segui il link:

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


-1

Se si tratta di un tentativo di utilizzare un'azione GET per più viste che POST a più azioni con modelli diversi, provare ad aggiungere un'azione GET per ciascuna azione POST che reindirizza al primo GET per impedire l'aggiornamento 404.

Colpo lungo ma scenario comune.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.