Können Sie Controller-Methoden in ASP.NET MVC überladen?

327

Ich bin gespannt, ob Sie Controller-Methoden in ASP.NET MVC überladen können. Immer wenn ich es versuche, erhalte ich den folgenden Fehler. Die beiden Methoden akzeptieren unterschiedliche Argumente. Ist das etwas, was nicht geht?

Die aktuelle Anforderung für die Aktion 'MyMethod' für den Controllertyp 'MyController' ist zwischen den folgenden Aktionsmethoden nicht eindeutig:

Papa Burgund
quelle
10
@andy es ist das gleiche für MVC 4 auch :)
Basarat
10
Und das gleiche gilt für MVC 5
DhruvJoshi
10
Und das gleiche gilt für MVC 6
Imad
7
Und das gleiche gilt für MVC Core 1.1
kall2sollies
7
Und das gleiche gilt für MVC Core 2.0
Guilherme

Antworten:

201

Sie können das Attribut verwenden, wenn Ihr Code überladen werden soll.

[ActionName("MyOverloadedName")]

Sie müssen jedoch einen anderen Aktionsnamen für dieselbe http-Methode verwenden (wie andere gesagt haben). An diesem Punkt ist es also nur Semantik. Möchten Sie lieber den Namen in Ihrem Code oder Ihrem Attribut haben?

Phil hat einen Artikel dazu: http://haacked.com/archive/2008/08/29/how-a-method-becomes-an-action.aspx

JD Conley
quelle
5
Der Hauptnachteil dieser Verwendung und Überlastung Ihrer Aktion besteht darin, dass sie nicht mehr von derselben Ansichtsdatei gerendert werden kann.
Jeff Martin
66
Tatsächlich kann es immer noch dieselbe Ansichtsdatei rendern. Sie müssen nur den Namen der Ansicht angeben, anstatt blind aufzurufen return View();. Zum Beispiel : return View("MyOverloadedName");.
EAMann
1
@JD aber Microsoft sagt .. Eine als Controller-Aktion verwendete Methode kann nicht überladen werden .. Sie können es hier sehen .. asp.net/mvc/tutorials/controllers-and-routing/…
himanshupareek66
@ EAMann Schön, ich habe bis jetzt immer den ganzen Weg für die Ansicht definiert
Alexander Derck
69

Ja. Ich konnte dies tun, indem ich das HttpGet/ HttpPost(oder ein gleichwertiges AcceptVerbsAttribut) für jede Controller-Methode auf etwas Besonderes setzte, dh HttpGetoder HttpPost, aber nicht beides. Auf diese Weise kann anhand der Art der Anforderung festgestellt werden, welche Methode verwendet werden soll.

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

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

Ein Vorschlag, den ich habe, ist, dass für einen Fall wie diesen eine private Implementierung erforderlich ist, auf die sich Ihre beiden öffentlichen Aktionsmethoden stützen, um das Duplizieren von Code zu vermeiden.

Tvanfosson
quelle
1
Mit MVC2 und höher kann man auch das Attribut HttpPost / HttpGet verwenden
yoel halb
@yohal Ja, das wäre die kanonische Methode, um jetzt damit umzugehen, wenn Sie nicht mehrere Verben unterstützen müssen.
Tvanfosson
3
Achten Sie nur darauf, dies nicht zu missbrauchen, um die Prinzipien von REST zu verletzen.
Fred
1
Ziemlich sicher, dass dies nur funktioniert, weil Ihre Show()Methoden unterschiedliche Signaturen haben. Wenn Sie Informationen an die Get-Version senden müssen, erhalten Ihre Get- und Post-Versionen dieselbe Signatur, und Sie benötigen das ActionNameAttribut oder eine der anderen in diesem Beitrag genannten Korrekturen.
Scott Fraley
1
@ ScottK.Fraley das stimmt. Wenn sie dieselbe Signatur benötigen, müssen Sie sie anders benennen und anwenden ActionNameAttribute. In der Praxis habe ich das selten festgestellt.
Tvanfosson
42

Hier ist noch etwas, was Sie tun könnten ... Sie möchten eine Methode, die einen Parameter haben kann und nicht.

Warum nicht mal probieren ...

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

Das hat bei mir funktioniert ... und bei dieser einen Methode können Sie tatsächlich testen, ob Sie den eingehenden Parameter haben.


Aktualisiert, um die ungültige nullfähige Syntax für Zeichenfolgen zu entfernen und einen Standardparameterwert zu verwenden.

Farrel
quelle
6
( stringKann nicht nullbar sein.)
Josh M.
23
Zeichenfolge kann nullwertfähig sein. In der Tat ist es bereits nullbar, braucht nur nicht das '?'
ProfK
9
@ProfK - Nein, Zeichenfolge ist ein Referenztyp, der null sein kann. Es ist nicht "nullable". Nullable bedeutet, dass Sie Nullable <T> (dh T?) Verwenden. Josh meint, dass du das nicht sagen kannst? nach Zeichenfolge, da es sich nicht um einen Werttyp handelt und Nullable <T> nur Werttypen akzeptiert.
Erik Funkenbusch
4
Ich fand zufällig meinen Weg zurück zu dieser Frage und stellte dann fest, dass ich den obigen Kommentar gepostet hatte. Keine Erinnerung daran ... komisch! Es ist immer noch wahr, dass a stringnicht sein kann nullable; aber es kann sein null! So oder so habe ich den ersten Kommentar ohne Aufrichtigkeit gepostet.
Josh M.
20

Nein, Nein und Nein. Probieren Sie den folgenden Controller-Code aus, bei dem der "LoadCustomer" überladen ist.

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

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

Wenn Sie versuchen, die Aktion "LoadCustomer" aufzurufen, wird eine Fehlermeldung angezeigt (siehe Abbildung unten).

Geben Sie hier die Bildbeschreibung ein

Polymorphismus ist ein Teil der C # -Programmierung, während HTTP ein Protokoll ist. HTTP versteht Polymorphismus nicht. HTTP funktioniert mit dem Konzept oder der URL, und die URL kann nur eindeutige Namen haben. HTTP implementiert also keinen Polymorphismus.

Um dies zu beheben, müssen wir das Attribut "ActionName" verwenden.

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

Wenn Sie nun die URL "Customer / LoadCustomer" aufrufen, wird die Aktion "LoadCustomer" aufgerufen und mit der URL-Struktur "Customer / LoadCustomerByName" wird der "LoadCustomer (string str)" aufgerufen.

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Die obige Antwort habe ich aus diesem Codeprojekt-Artikel entnommen -> MVC- Aktionsüberladung

Shivprasad Koirala
quelle
Danke dafür. Ich denke, Sie können auch von Anfang an einen anderen Aktionsnamen verwenden, anstatt das Attribut zu verwenden.
Dan
1
@ Dan, aber dann haben wir keinen Polymorphismus auf der C # -Seite.
Shivprasad Koirala
Sie haben Recht, es gibt keine Überladung der Controller-Methode, aber es hat nichts mit HTTP zu tun.
Chalky
Danke für die Klarstellung. +1. Sollte mehr HTTP und nicht C # denken. Es gibt keinen Grund, Maßnahmen mit einer OO-Strategie anzugehen.
15

Um dieses Problem zu beheben, können Sie eine schreiben ActionMethodSelectorAttribute, die die MethodInfofür jede Aktion untersucht und mit den angegebenen Formularwerten vergleicht und dann jede Methode ablehnt, für die die Formularwerte nicht übereinstimmen (natürlich ohne den Schaltflächennamen).

Hier ein Beispiel: - http://blog.abodit.com/2010/02/asp-net-mvc-ambiguous-match/

ABER das ist keine gute Idee.

Ian Mercer
quelle
"ABER das ist keine gute Idee." Warum nicht?
Cerbrus
@ Cerbrus, weil es ein schrecklicher Hack ist und die nächste Person, die sich Ihren Controller-Code ansieht, durch einen sehr ungewöhnlichen Ansatz verwirrt wird.
Ian Mercer
Heh, fair genug.
Cerbrus
14

Soweit ich weiß, können Sie dieselbe Methode nur verwenden, wenn Sie verschiedene http-Methoden verwenden.

dh

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

}

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

}
Keeney
quelle
2
Die Dekorationen haben nichts mit der Überlastung zu tun. Es ist die Parameterliste, die eine Überladung ermöglicht.
Sky Sanders
@SkySanders Ich bin anderer Meinung, die parameterbasierte Überladung funktioniert bei MVC-Controller-Methoden nicht - haben Sie ein funktionierendes Beispiel dafür? Prost.
Chalky
Verwenden Sie das [HttpPost]Attribut anstelle von [AcceptVerbs("POST")].
Fred
9

Ich habe dies mit Hilfe von Attribute Routing in MVC5 erreicht. Zugegeben, ich bin neu bei MVC und komme aus einem Jahrzehnt der Webentwicklung mit WebForms, aber das Folgende hat für mich funktioniert. Im Gegensatz zur akzeptierten Antwort können hiermit alle überladenen Aktionen von derselben Ansichtsdatei gerendert werden.

Aktivieren Sie zuerst das Attribut-Routing 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 }
        );            
    }
}

Dekorieren Sie Ihre Controller-Klasse optional mit einem Standard-Routenpräfix.

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

Dekorieren Sie dann Ihre Controller-Aktionen, die sich gegenseitig überlasten, mit einer gemeinsamen Route und entsprechenden Parametern. Mit typbeschränkten Parametern können Sie dasselbe URI-Format mit IDs unterschiedlicher Typen verwenden.

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

Hoffe das hilft und führt niemanden auf den falschen Weg. :-)

cookdn
quelle
Gute Arbeit! Ich bin gerade auf dieses Problem gestoßen, du hast mich gerettet! Ich habe auch "x" Jahre mit WebForms - also immer noch eine Lernkurve. Ich kann heutzutage keinen Job ohne MVC bekommen haha
Tez Wingfield
4

Sie könnten eine einzige verwenden ActionResult, um mit beiden umzugehen Postund Get:

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

Nützlich, wenn Ihre Getund PostMethoden übereinstimmende Signaturen haben.

DevDave
quelle
1
Hmm, irgendwie das Rad wieder neu erfinden, aber diesmal in einer quadratischen Form. Warum nicht einfach die Attribute [HttpPost / Get] verwenden?
SOReader
Es ist eine Weile her, aber ich glaube, ich habe dies getan, weil MVC nicht zwischen zwei getrennten Methoden mit passenden Sigs unterschied. Ich habe das HttpPost-Attribut verwendet, obwohl ich HttpGet nicht auf die andere Methode gesetzt habe.
DevDave
@DevDave Vergewissern Sie sich, dass Sie nicht nur beide Methoden zuordnen, sondern auch Attribute aus system.web.mvc verwenden - und nicht die aus system.web.http!
Chalky
4

Ich bin gerade auf diese Frage gestoßen und obwohl sie jetzt ziemlich alt ist, ist sie immer noch sehr relevant. Ironischerweise wurde der einzig richtige Kommentar in diesem Thread von einem bekennenden Anfänger in MVC gepostet, als er den Beitrag schrieb. Selbst die ASP.NET-Dokumente sind nicht ganz korrekt. Ich habe ein großes Projekt und überlade erfolgreich Aktionsmethoden.

Wenn man das Routing versteht, kann es über das einfache Standardroutenmuster {controller} / {action} / {id} hinaus offensichtlich sein, dass Controller-Aktionen mit einem beliebigen eindeutigen Muster zugeordnet werden können. Jemand hier sprach über Polymorphismus und sagte: "HTTP versteht Polymorphismus nicht", aber Routing hat nichts mit HTTP zu tun. Es ist einfach ausgedrückt ein Mechanismus für den String-Mustervergleich.

Der beste Weg, um diese Arbeit zu machen, ist die Verwendung der Routing-Attribute, zum Beispiel:

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

Diese Aktionen kümmern sich um URLs wie /cars/usa/new-yorkund /cars/usa/texas/dallas, die der ersten bzw. zweiten Indexaktion zugeordnet werden.

Bei Betrachtung dieses Beispielcontrollers wird deutlich, dass es über das oben erwähnte Standardroutenmuster hinausgeht. Die Standardeinstellung funktioniert gut, wenn Ihre URL-Struktur genau Ihren Code-Namenskonventionen entspricht. Dies ist jedoch nicht immer der Fall. Code sollte die Domain beschreiben, aber URLs müssen oft noch weiter gehen, da ihr Inhalt auf anderen Kriterien wie SEO-Anforderungen basieren sollte.

Der Vorteil des Standard-Routing-Musters besteht darin, dass automatisch eindeutige Routen erstellt werden. Dies wird vom Compiler erzwungen, da URLs eindeutigen Controller-Typen und -Mitgliedern entsprechen. Das Rollen Ihrer eigenen Routenmuster erfordert sorgfältige Überlegungen, um die Einzigartigkeit sicherzustellen und sicherzustellen, dass sie funktionieren.

Wichtiger Hinweis Der einzige Nachteil ist, dass die Verwendung des Routings zum Generieren von URLs für überladene Aktionen nicht funktioniert, wenn ein Aktionsname verwendet wird, z. B. wenn UrlHelper.Action verwendet wird. Es funktioniert jedoch, wenn benannte Routen verwendet werden, z. B. UrlHelper.RouteUrl. Die Verwendung benannter Routen ist nach Ansicht angesehener Quellen ohnehin der richtige Weg ( http://haacked.com/archive/2010/11/21/named-routes-to-the-rescue.aspx/ ).

Viel Glück!

DvS
quelle
3

Sie können [ActionName ("NewActionName")] verwenden, um dieselbe Methode mit einem anderen Namen zu verwenden:

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
quelle
2

Ich brauchte eine Überlastung für:

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

Es gab nur wenige Argumente, bei denen ich dies getan habe:

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

Es ist keine perfekte Lösung, besonders wenn Sie viele Argumente haben, aber es funktioniert gut für mich.

Kasey Speakman
quelle
1

Ich habe das gleiche Problem auch in meiner Bewerbung gesehen. Ohne Änderung der Methodeninformationen habe ich [Aktionsname ("SomeMeaningfulName")]] auf dem Aktionskopf angegeben. Problem gelöst

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

        }
ಅನಿಲ್
quelle
0

Erstellen Sie die Basismethode als virtuell

public virtual ActionResult Index()

Erstellen Sie die überschriebene Methode als Überschreibung

public override ActionResult Index()

Bearbeiten: Dies gilt natürlich nur, wenn sich die Überschreibungsmethode in einer abgeleiteten Klasse befindet, die anscheinend nicht die Absicht des OP war.

Andiih
quelle
2
Sie verstehen die Frage wahrscheinlich falsch. Das OP fragt nach einer Überladung der Methode in demselben Controller und nicht nach einer Überschreibung in einer abgeleiteten Klasse.
Ass
@Andiih: Was passiert, wenn sich beide Methoden im selben Controller befinden?
Dharmik Bhandari
0

Für jede Controller-Methode ist nur eine öffentliche Signatur zulässig. Wenn Sie versuchen, es zu überladen, wird es kompiliert, aber Sie erhalten den Laufzeitfehler, den Sie festgestellt haben.

Wenn Sie nicht bereit sind, verschiedene Verben (wie die [HttpGet]und [HttpPost]Attribute) zu verwenden, um überladene Methoden zu unterscheiden (was funktionieren wird) oder das Routing zu ändern, müssen Sie entweder eine andere Methode mit einem anderen Namen angeben oder Sie können Versand innerhalb der bestehenden Methode. So habe ich es gemacht:

Ich war einmal in einer Situation, in der ich die Abwärtskompatibilität aufrechterhalten musste. Die ursprüngliche Methode erwartete zwei Parameter, aber die neue hatte nur einen. Das erwartete Überladen funktionierte nicht, da MVC den Einstiegspunkt nicht mehr fand.

Um das zu lösen, habe ich Folgendes getan:

  1. Die 2 überladenen Aktionsmethoden wurden von öffentlich auf privat geändert
  2. Erstellt eine neue öffentliche Methode, die "nur" 2 Zeichenfolgenparameter enthält. Dieser fungierte als Dispatcher, dh:

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

Dies ist natürlich ein Hack und sollte später überarbeitet werden. Aber vorerst hat es bei mir funktioniert.

Sie können auch einen Dispatcher erstellen wie:

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

Sie können sehen, dass UpdateAction zwei Parameter benötigt, während DeleteAction nur einen benötigt.

Matt
quelle
0

Entschuldigung für die Verspätung. Ich hatte das gleiche Problem und fand einen Link mit guten Antworten, könnte das neuen Leuten helfen

Alle Credits für die BinaryIntellect-Website und die Autoren

Grundsätzlich gibt es vier Situationen: Verwenden verschiedener Verben , Routing , Überlastungsmarkierung mit dem Attribut [NoAction] und Ändern des Namens des Aktionsattributs mit [Aktionsname]

Das hängt also von Ihren Anforderungen und Ihrer Situation ab.

Wie auch immer, folgen Sie dem Link:

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

Eric Saboia
quelle
-1

Wenn dies ein Versuch ist, eine GET-Aktion für mehrere Ansichten zu verwenden, die zu mehreren Aktionen mit unterschiedlichen Modellen POSTEN, fügen Sie für jede POST-Aktion, die zum ersten GET umleitet, eine GET-Aktion hinzu, um 404 beim Aktualisieren zu verhindern.

Long Shot, aber häufiges Szenario.

Panos Roditakis
quelle