Pouvez-vous surcharger les méthodes de contrôleur dans ASP.NET MVC?

327

Je suis curieux de voir si vous pouvez surcharger les méthodes de contrôleur dans ASP.NET MVC. Chaque fois que j'essaye, j'obtiens l'erreur ci-dessous. Les deux méthodes acceptent des arguments différents. Est-ce quelque chose qui ne peut pas être fait?

La demande d'action actuelle «MyMethod» sur le type de contrôleur «MyController» est ambiguë entre les méthodes d'action suivantes:

Papa Bourgogne
la source
10
@andy c'est la même chose pour mvc 4 aussi :)
basarat
10
Et même pour mvc 5
DhruvJoshi
10
Et même pour mvc 6
Imad
7
Et même pour MVC Core 1.1
kall2sollies
7
Et même pour MVC Core 2.0
Guilherme

Réponses:

201

Vous pouvez utiliser l'attribut si vous souhaitez que votre code effectue une surcharge.

[ActionName("MyOverloadedName")]

Mais, vous devrez utiliser un nom d'action différent pour la même méthode http (comme d'autres l'ont dit). Ce n'est donc que de la sémantique à ce stade. Préférez-vous avoir le nom dans votre code ou votre attribut?

Phil a un article à ce sujet: http://haacked.com/archive/2008/08/29/how-a-method-becomes-an-action.aspx

JD Conley
la source
5
Le principal inconvénient de cette utilisation et de la surcharge de votre action est qu'elle ne peut plus être rendue par le même fichier de vue.
Jeff Martin
66
En fait, il peut toujours restituer le même fichier de vue. Il vous suffit de spécifier le nom de la vue au lieu d'appeler aveuglément return View();. Par exemple: return View("MyOverloadedName");.
EAMann
1
@JD mais Microsoft dit .. Une méthode utilisée comme une action de contrôleur ne peut pas être surchargé .. Vous pouvez voir ici .. asp.net/mvc/tutorials/controllers-and-routing/...
himanshupareek66
@EAMann Nice, je définissais toujours tout le chemin de la vue jusqu'à présent
Alexander Derck
69

Oui. J'ai pu le faire en définissant le HttpGet/ HttpPost(ou AcceptVerbsattribut équivalent ) pour chaque méthode de contrôleur sur quelque chose de distinct, c'est-à-dire, HttpGetou HttpPost, mais pas les deux. De cette façon, il peut dire en fonction du type de demande quelle méthode utiliser.

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

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

Une suggestion que j'ai est que, pour un cas comme celui-ci, ce serait d'avoir une implémentation privée sur laquelle vos deux méthodes d'action publiques s'appuient pour éviter la duplication de code.

tvanfosson
la source
1
Avec MVC2 et versions ultérieures, on peut également utiliser l'attribut HttpPost / HttpGet
yoel halb
@yohal Oui, ce serait la manière canonique de le gérer maintenant si vous n'avez pas besoin de prendre en charge plusieurs verbes.
tvanfosson
3
Attention à ne pas abuser de cela pour violer les principes de REST.
Fred
1
Je suis sûr que cela ne fonctionne que parce que vos Show()méthodes ont des signatures différentes. Si et quand vous devez envoyer des informations dans la version Get, vos versions Get et Post se retrouvent alors avec la même signature, et vous auriez besoin de l' ActionNameattribut ou de l'un des autres correctifs mentionnés dans ce post.
Scott Fraley
1
@ ScottK.Fraley c'est vrai. S'ils avaient besoin de la même signature, vous devrez les nommer différemment et appliquer le ActionNameAttribute. En pratique, j'ai rarement trouvé que c'était le cas.
tvanfosson
42

Voici autre chose que vous pourriez faire ... vous voulez une méthode capable d'avoir un paramètre et non.

Pourquoi ne pas essayer ça ...

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

Cela a fonctionné pour moi ... et dans cette méthode, vous pouvez réellement tester pour voir si vous avez le paramètre entrant.


Mis à jour pour supprimer la syntaxe nullable non valide sur la chaîne et utiliser une valeur de paramètre par défaut.

Farrel
la source
6
( stringne peut pas être annulé.)
Josh M.
23
la chaîne peut être nullable. En fait, il est déjà annulable, n'a tout simplement pas besoin du '?'
ProfK
9
@ProfK - Non, la chaîne est un type de référence qui peut être nul. Ce n'est pas "nul". Nullable signifie que vous utilisez Nullable <T> (c'est-à-dire T?). Le point de Josh est que vous ne pouvez pas mettre le? après la chaîne car il ne s'agit pas d'un type de valeur et Nullable <T> accepte uniquement les types de valeur.
Erik Funkenbusch
4
J'ai retrouvé au hasard mon chemin vers cette question, puis j'ai réalisé que j'avais posté le commentaire ci-dessus. Aucun souvenir de ça ... bizarre! Il est toujours vrai qu'un stringne peut pas être nullable; mais ça peut l'être null! Quoi qu'il en soit, j'ai posté le commentaire initial sans sincérité.
Josh M.
20

Non, Non et Non. Essayez le code de contrôleur ci-dessous où le "LoadCustomer" est surchargé.

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

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

Si vous essayez d'appeler l'action "LoadCustomer", vous obtiendrez une erreur comme indiqué dans la figure ci-dessous.

entrez la description de l'image ici

Le polymorphisme fait partie de la programmation C # tandis que HTTP est un protocole. HTTP ne comprend pas le polymorphisme. HTTP fonctionne sur le concept ou l'URL et l'URL ne peut avoir qu'un nom unique. HTTP n'implémente donc pas le polymorphisme.

Afin de résoudre le même problème, nous devons utiliser l'attribut "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");
        }
    }

Donc maintenant, si vous appelez l'URL "Customer / LoadCustomer", l'action "LoadCustomer" sera invoquée et avec la structure d'URL "Customer / LoadCustomerByName", "LoadCustomer (string str)" sera invoqué.

entrez la description de l'image ici

entrez la description de l'image ici

La réponse ci-dessus que j'ai tirée de cet article de projet de code -> Surcharge de l'action MVC

Shivprasad Koirala
la source
Merci pour cela. Je suppose que vous pouvez tout aussi bien utiliser un nom d'action différent depuis le début plutôt que d'utiliser l'attribut.
Dan
1
@Dan mais nous n'avons pas de polymorphisme du côté C #.
Shivprasad Koirala
Vous avez raison, il n'y a pas de surcharge de méthode de contrôleur mais cela n'a rien à voir avec HTTP.
Chalky
Merci pour la clarification. +1. Devrait penser plus HTTP et non C #. Il n'y a aucune raison d'approcher les actions avec une stratégie OO.
15

Pour surmonter ce problème, vous pouvez écrire un ActionMethodSelectorAttributequi examine le MethodInfopour chaque action et le compare aux valeurs de formulaire publiées, puis rejette toute méthode pour laquelle les valeurs de formulaire ne correspondent pas (à l'exclusion du nom du bouton, bien sûr).

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

MAIS, ce n'est pas une bonne idée.

Ian Mercer
la source
@Cerbrus car c'est un horrible hack et la prochaine personne qui regarde votre code de contrôleur sera confuse par une approche très non standard.
Ian Mercer
Hé, assez bien.
Cerbrus
14

Pour autant que je sache, vous ne pouvez avoir la même méthode que lorsque vous utilisez différentes méthodes http.

c'est à dire

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

}

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

}
keeney
la source
2
les décorations n'ont rien à voir avec la surcharge. c'est la liste des paramètres qui permet la surcharge.
Sky Sanders
@SkySanders Je ne suis pas d'accord, la surcharge basée sur les paramètres ne fonctionne pas dans les méthodes de contrôleur MVC - en avez-vous un exemple fonctionnel? À votre santé.
Chalky
Utilisez l' [HttpPost]attribut au lieu de [AcceptVerbs("POST")].
Fred
9

J'ai réalisé cela avec l'aide du routage d'attributs dans MVC5. Certes, je suis nouveau sur MVC, issu d'une décennie de développement Web utilisant WebForms, mais ce qui suit a fonctionné pour moi. Contrairement à la réponse acceptée, cela permet à toutes les actions surchargées d'être rendues par le même fichier de vue.

Activez d'abord le routage d'attributs dans 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 }
        );            
    }
}

Décorez éventuellement votre classe de contrôleur avec un préfixe de route par défaut.

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

Décorez ensuite les actions de votre contrôleur qui se surchargent avec une route et des paramètres communs. En utilisant des paramètres à contrainte de type, vous pouvez utiliser le même format URI avec des ID de types différents.

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

J'espère que cela aide et ne conduit pas quelqu'un sur le mauvais chemin. :-)

cookdn
la source
Bon travail! Je viens de rencontrer ce problème, vous m'avez sauvé! J'ai également "x" années avec WebForms - donc toujours une courbe d'apprentissage. Impossible d'obtenir un emploi sans MVC de nos jours haha
Tez Wingfield
4

Vous pouvez utiliser un seul ActionResultpour gérer les deux Postet Get:

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

Utile si vos méthodes Getet Postont des signatures correspondantes.

DevDave
la source
1
Hmm, en quelque sorte réinventer la roue, mais cette fois dans une forme carrée. Pourquoi ne pas simplement utiliser les attributs [HttpPost / Get]?
SOReader
Cela fait un moment, mais je pense que je l'ai fait parce que MVC ne faisait pas de distinction entre deux méthodes distinctes avec des signatures correspondantes. J'utilisais l'attribut HttpPost, même si je ne mettais pas HttpGet sur l'autre méthode ..
DevDave
@DevDave ainsi que l'attribution des deux méthodes, assurez-vous que vous utilisez des attributs de system.web.mvc - et non ceux de system.web.http!
Chalky
4

Je viens de tomber sur cette question et, même si elle est assez ancienne maintenant, elle est toujours très pertinente. Ironiquement, le seul commentaire correct dans ce fil a été posté par un débutant avoué dans MVC lorsqu'il a écrit le message. Même les documents ASP.NET ne sont pas entièrement corrects. J'ai un grand projet et j'ai réussi à surcharger les méthodes d'action.

Si l'on comprend le routage, au-delà du modèle de route par défaut simple {controller} / {action} / {id}, il peut être évident que les actions du contrôleur peuvent être mappées en utilisant n'importe quel modèle unique. Quelqu'un ici a parlé du polymorphisme et a dit: "HTTP ne comprend pas le polymorphisme", mais le routage n'a rien à voir avec HTTP. Il s'agit, en termes simples, d'un mécanisme de correspondance de motifs de chaînes.

La meilleure façon de faire cela est d'utiliser les attributs de routage, par exemple:

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

Ces actions prendront en charge les URL comme /cars/usa/new-yorket/cars/usa/texas/dallas , qui seront mappées respectivement aux première et deuxième actions d'index.

En examinant cet exemple de contrôleur, il est évident qu'il va au-delà du modèle de route par défaut mentionné ci-dessus. La valeur par défaut fonctionne bien si votre structure d'URL correspond exactement à vos conventions de dénomination de code, mais ce n'est pas toujours le cas. Le code doit être descriptif du domaine, mais les URL doivent souvent aller plus loin car leur contenu doit être basé sur d'autres critères, tels que les exigences SEO.

L'avantage du modèle de routage par défaut est qu'il crée automatiquement des itinéraires uniques. Ceci est appliqué par le compilateur car les URL correspondent à des types et membres de contrôleur uniques. Rouler vos propres modèles d'itinéraire nécessitera une réflexion approfondie pour garantir l'unicité et le bon fonctionnement.

Remarque importante L'inconvénient est que l'utilisation du routage pour générer des URL pour des actions surchargées ne fonctionne pas lorsqu'elle est basée sur un nom d'action, par exemple, lors de l'utilisation d'UrlHelper.Action. Mais cela fonctionne si l'on utilise des routes nommées, par exemple, UrlHelper.RouteUrl. Et l'utilisation de routes nommées est, selon des sources bien respectées, la voie à suivre ( http://haacked.com/archive/2010/11/21/named-routes-to-the-rescue.aspx/ ).

Bonne chance!

DvS
la source
3

Vous pouvez utiliser [ActionName ("NewActionName")] pour utiliser la même méthode avec un nom différent:

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
la source
2

J'avais besoin d'une surcharge pour:

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

Il y avait assez peu d'arguments où j'ai fini par faire ceci:

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

Ce n'est pas une solution parfaite, surtout si vous avez beaucoup d'arguments, mais cela fonctionne bien pour moi.

Kasey Speakman
la source
1

J'ai également rencontré le même problème dans ma candidature. Sans modifier aucune information de méthode, j'ai fourni [ActionName ("SomeMeaningfulName")] sur la tête d'action. problème résolu

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

        }
ಅನಿಲ್
la source
0

Créer la méthode de base comme virtuelle

public virtual ActionResult Index()

Créer la méthode remplacée comme remplacement

public override ActionResult Index()

Edit: Cela ne s'applique évidemment que si la méthode de remplacement est dans une classe dérivée qui ne semble pas avoir été l'intention de l'OP.

Andiih
la source
2
Vous comprenez probablement mal la question. L'OP demande de surcharger la méthode dans le même contrôleur, et non de la remplacer dans une classe dérivée.
Ace
@Andiih: que se passera-t-il si les deux méthodes sont dans le même contrôleur?
Dharmik Bhandari
0

Il n'y a qu'une seule signature publique autorisée pour chaque méthode de contrôleur. Si vous essayez de le surcharger, il se compilera, mais vous obtenez l'erreur d'exécution que vous avez rencontrée.

Si vous n'êtes pas prêt à utiliser différents verbes (comme le [HttpGet]et[HttpPost] attributs ) pour différencier les méthodes surchargées (qui fonctionneront) ou modifier le routage, il reste que vous pouvez soit fournir une autre méthode avec un nom différent, soit vous pouvez expédition à l'intérieur de la méthode existante. Voici comment je l'ai fait:

Une fois, je me suis retrouvé dans une situation où je devais maintenir une compatibilité ascendante. La méthode d'origine attendait deux paramètres, mais la nouvelle n'en avait qu'un. Surcharger comme je m'y attendais n'a pas fonctionné car MVC n'a plus trouvé le point d'entrée.

Pour résoudre cela, j'ai fait ce qui suit:

  1. Changement des 2 méthodes d'action surchargées du public au privé
  2. Création d'une nouvelle méthode publique qui contenait "juste" 2 paramètres de chaîne. Celui-ci a agi comme répartiteur, c'est-à-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);
    }

Bien sûr, c'est un hack et devrait être refactorisé plus tard. Mais pour le moment, cela a fonctionné pour moi.

Vous pouvez également créer un répartiteur comme:

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

Vous pouvez voir que UpdateAction a besoin de 2 paramètres, alors que DeleteAction n'en a besoin que d'un.

Mat
la source
0

Désolé pour le retard. J'étais avec le même problème et j'ai trouvé un lien avec de bonnes réponses, cela pourrait-il aider les nouveaux gars

Tous les crédits pour le site Web BinaryIntellect et les auteurs

Fondamentalement, il existe quatre situations: utiliser différents verbes , utiliser le routage , marquer la surcharge avec l'attribut [NoAction] et changer le nom de l'attribut d'action avec [ActionName]

Cela dépend donc de vos exigences et de votre situation.

Cependant, suivez le lien:

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

Eric Saboia
la source
-1

S'il s'agit d'une tentative d'utiliser une action GET pour plusieurs vues qui POST à ​​plusieurs actions avec différents modèles, essayez alors d'ajouter une action GET pour chaque action POST qui redirige vers le premier GET pour empêcher 404 lors de l'actualisation.

Plan long mais scénario courant.

Panos Roditakis
la source