Você pode sobrecarregar os métodos do controlador no ASP.NET MVC?


327

Estou curioso para saber se você pode sobrecarregar os métodos do controlador no asp.net MVC. Sempre que tento, recebo o erro abaixo. Os dois métodos aceitam argumentos diferentes. Isso é algo que não pode ser feito?

A solicitação atual de ação 'MyMethod' no tipo de controlador 'MyController' é ambígua entre os seguintes métodos de ação:


10
@andy é o mesmo para o MVC 4 bem :)
basarat

10
E o mesmo para o mvc 5
DhruvJoshi

10
E o mesmo para o mvc 6
Imad

7
E o mesmo para o MVC Core 1.1
kall2sollies

7
E o mesmo para o MVC Core 2.0
Guilherme

Respostas:


201

Você pode usar o atributo se desejar que seu código sobrecarregue.

[ActionName("MyOverloadedName")]

Mas, você precisará usar um nome de ação diferente para o mesmo método http (como outros já disseram). Portanto, é apenas semântica nesse ponto. Você prefere ter o nome no seu código ou atributo?

Phil tem um artigo relacionado a isso: http://haacked.com/archive/2008/08/29/how-a-method-becomes-an-action.aspx


5
A principal desvantagem de usar isso e sobrecarregar sua ação é que ela não pode mais ser renderizada pelo mesmo arquivo de exibição.
Jeff Martin

66
Na verdade, ele ainda pode renderizar o mesmo arquivo de exibição. Você só precisa especificar o nome da visualização em vez de ligar cegamente return View();. Por exemplo: return View("MyOverloadedName");.
EAMann

1
@JD mas a Microsoft diz .. Um método usado como uma ação de controlador não pode ser sobrecarregado .. Você pode vê-lo aqui .. asp.net/mvc/tutorials/controllers-and-routing/...
himanshupareek66

@EAMann agradável, eu estava sempre definir o caminho inteiro para a vista até agora
Alexander Derck

69

Sim. Consegui fazer isso definindo o HttpGet/ HttpPost(ou AcceptVerbsatributo equivalente ) para cada método do controlador como algo distinto, ou seja, HttpGetou HttpPost, mas não ambos. Dessa forma, ele pode dizer com base no tipo de solicitação qual método usar.

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

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

Uma sugestão que tenho é que, para um caso como esse, seria uma implementação privada na qual os dois métodos públicos de Ação dependem para evitar a duplicação de código.


1
Com o MVC2 e superior, também é possível usar o atributo HttpPost / HttpGet
yoel halb

@yohal Sim, essa seria a maneira canônica de lidar com isso agora, se você não precisar suportar vários verbos.
tvanfosson

3
Apenas tome cuidado para não abusar disso para violar os princípios do REST.
25416 Fred

1
Certamente isso só está funcionando porque seus Show()métodos têm assinaturas diferentes. Se e quando você precisar enviar informações para a versão Get, suas versões Get e Post acabarão com a mesma assinatura e você precisará do ActionNameatributo ou de uma das outras correções mencionadas nesta postagem.
Scott Fraley

1
@ ScottK.Fraley isso é verdade. Se eles precisassem da mesma assinatura, você teria que nomeá-los de maneira diferente e aplicar o ActionNameAttribute. Na prática, raramente achei esse o caso.
tvanfosson

42

Aqui está outra coisa que você poderia fazer ... você quer um método capaz de ter um parâmetro e não.

Por que não tentar isso ...

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

Isso funcionou para mim ... e neste método, você pode realmente testar para ver se possui o parâmetro recebido.


Atualizado para remover a sintaxe anulável inválida na sequência e usar um valor de parâmetro padrão.


6
( stringNão pode ser anulável.)
Josh M.

23
string pode ser anulável. Na verdade, ele já é anulável, apenas não precisa do '?'
ProfK

9
@ProfK - Não, string é um tipo de referência que pode ser nulo. Não é "anulável". Nullable significa que você está usando o Nullable <T> (ou seja, T?). O ponto de Josh é que você não pode colocar o? após sequência porque não é um tipo de valor, e Nullable <T> aceita apenas tipos de valor.
Erik Funkenbusch

4
Encontrei aleatoriamente o caminho de volta a essa pergunta e percebi que havia postado o comentário acima. Nenhuma lembrança disso ... estranho! Ainda é verdade que um stringnão pode ser nullable; mas pode ser null! De qualquer maneira, postei o comentário inicial sem sinceridade.
Josh M.

20

Não, Não e Não. Vá e tente o código do controlador abaixo, onde temos o "LoadCustomer" sobrecarregado.

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

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

Se você tentar invocar a ação "LoadCustomer", receberá um erro, conforme mostrado na figura abaixo.

insira a descrição da imagem aqui

Polimorfismo faz parte da programação C # enquanto HTTP é um protocolo. HTTP não entende polimorfismo. O HTTP funciona na URL ou no conceito e a URL pode ter apenas nomes exclusivos. Portanto, o HTTP não implementa polimorfismo.

Para corrigir o mesmo, precisamos usar o atributo "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");
        }
    }

Portanto, agora, se você fizer uma chamada para o URL "Customer / LoadCustomer", a ação "LoadCustomer" será chamada e, com a estrutura de URL "Customer / LoadCustomerByName", o "LoadCustomer (string str)" será chamado.

insira a descrição da imagem aqui

insira a descrição da imagem aqui

A resposta acima que tirei deste artigo de codeproject -> MVC Action overloading


Obrigado por isso. Acho que você também pode usar um nome de ação diferente desde o início, em vez de usar o atributo
Dan

1
@ Dan, mas não temos polimorfismo no lado C #.
Shivprasad Koirala 12/02

Você está correto, não há sobrecarga no método do controlador, mas não tem nada a ver com HTTP.
Chalky

Obrigado pela clarificação. +1. Deveria estar pensando mais em HTTP e não em C #. Não há razão para abordar ações com uma estratégia OO.

15

Para superar esse problema, você pode escrever um ActionMethodSelectorAttributeque analise o MethodInfopara cada ação e o compare com os valores do formulário postados e, em seguida, rejeite qualquer método para o qual os valores do formulário não coincidam (excluindo o nome do botão, é claro).

Aqui está um exemplo: - http://blog.abodit.com/2010/02/asp-net-mvc-ambiguous-match/

MAS, isso não é uma boa ideia.


@ Cerbrus porque é um hack horrível e a próxima pessoa que olhar o código do seu controlador ficará confusa com uma abordagem muito fora do padrão.
Ian Mercer

Heh, justo o suficiente.
Cerbrus 11/03

14

Tanto quanto eu sei, você só pode ter o mesmo método ao usar diferentes métodos http.

ie

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

}

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

}

2
as decorações não têm nada a ver com a sobrecarga. a lista de parâmetros que permite sobrecarga.
Sky Sanders

@SkySanders Eu discordo, a sobrecarga baseada em parâmetros não funciona nos métodos do controlador MVC - você tem um exemplo de trabalho? Felicidades.
Chalky

Use o [HttpPost]atributo em vez de [AcceptVerbs("POST")].
25416 Fred

9

Consegui isso com a ajuda do roteamento de atributos no MVC5. É certo que eu sou novo no MVC vindo de uma década de desenvolvimento web usando WebForms, mas o seguinte funcionou para mim. Diferentemente da resposta aceita, isso permite que todas as ações sobrecarregadas sejam renderizadas pelo mesmo arquivo de visualização.

Primeiro, ative o roteamento de atributos em 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 }
        );            
    }
}

Opcionalmente, decore sua classe de controlador com um prefixo de rota padrão.

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

Em seguida, decore as ações do seu controlador que se sobrecarregam com uma rota e parâmetros comuns adequados. Usando parâmetros de tipo restrito, você pode usar o mesmo formato de URI com IDs de tipos diferentes.

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

Espero que isso ajude e não leve alguém a seguir o caminho errado. :-)


Bom trabalho! Acabei de encontrar este problema, você me salvou! Eu também tenho "x" anos com WebForms - ainda assim uma curva de aprendizado. Não é possível obter um emprego sem MVC agora-a-dias haha
Tez Wingfield

4

Você pode usar um único ActionResultpara lidar com ambos Poste Get:

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

Útil se seus métodos Gete Posttiverem assinaturas correspondentes.


1
Hmm, meio que reinventando a roda novamente, mas desta vez em formato de quadrado. Por que não simplesmente usar os atributos [HttpPost / Get]?
SOReader 27/02

já faz um tempo, mas acho que fiz isso porque o MVC não fazia distinção entre dois métodos separados com sigs correspondentes. Eu estava usando o atributo HttpPost, embora eu não estava colocando HttpGet por outro método ..
DevDave

@DevDave, além de atribuir os dois métodos, verifique se você está usando atributos de system.web.mvc - e não os atributos de system.web.http!
Chalky

4

Acabei de me deparar com essa pergunta e, apesar de já estar antiga, ainda é muito relevante. Ironicamente, o único comentário correto neste tópico foi postado por um iniciante confesso no MVC quando ele escreveu o post. Mesmo os documentos do ASP.NET não estão totalmente corretos. Tenho um projeto grande e sobrecarrego com êxito os métodos de ação.

Se você entender o roteamento, além do padrão de rota padrão simples de {controller} / {action} / {id}, pode ser óbvio que as ações do controlador podem ser mapeadas usando qualquer padrão exclusivo. Alguém aqui falou sobre polimorfismo e disse: "HTTP não entende polimorfismo", mas o roteamento não tem nada a ver com HTTP. É, simplesmente, um mecanismo para correspondência de padrões de string.

A melhor maneira de fazer isso funcionar é usar os atributos de roteamento, por exemplo:

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

Essas ações cuidarão de URLs como /cars/usa/new-yorke /cars/usa/texas/dallas, que serão mapeados para a primeira e a segunda ações do Índice, respectivamente.

Examinando este exemplo de controlador, é evidente que ele vai além do padrão de rota padrão mencionado acima. O padrão funciona bem se sua estrutura de URL corresponder exatamente às convenções de nomenclatura de código, mas esse nem sempre é o caso. O código deve ser descritivo do domínio, mas os URLs geralmente precisam ir além, porque seu conteúdo deve ser baseado em outros critérios, como requisitos de SEO.

O benefício do padrão de roteamento padrão é que ele cria automaticamente rotas exclusivas. Isso é imposto pelo compilador, já que os URLs corresponderão a tipos e membros exclusivos de controladores. A rolagem de seus próprios padrões de rota exigirá uma reflexão cuidadosa para garantir a exclusividade e o funcionamento.

Nota importante A única desvantagem é que o uso de roteamento para gerar URLs para ações sobrecarregadas não funciona quando baseado em um nome de ação, por exemplo, ao usar UrlHelper.Action. Mas funciona se alguém usa rotas nomeadas, por exemplo, UrlHelper.RouteUrl. E usar rotas nomeadas é, de acordo com fontes bem respeitadas, o caminho a seguir de qualquer maneira ( http://haacked.com/archive/2010/11/21/named-routes-to-the-rescue.aspx/ ).

Boa sorte!


3

Você pode usar [ActionName ("NewActionName")] para usar o mesmo método com um nome diferente:

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

Eu precisava de uma sobrecarga para:

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

Havia poucos argumentos suficientes onde acabei fazendo isso:

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

Não é uma solução perfeita, especialmente se você tiver muitos argumentos, mas funciona bem para mim.


1

Também enfrentei o mesmo problema no meu aplicativo. Sem modificar nenhuma informação de método, eu forneci [ActionName ("SomeMeaningfulName")]] no cabeçalho Action. Problema resolvido

[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

Crie o método base como virtual

public virtual ActionResult Index()

Crie o método substituído como substituição

public override ActionResult Index()

Editar: Isso obviamente se aplica somente se o método de substituição estiver em uma classe derivada que parece não ter sido a intenção do OP.


2
Você provavelmente está entendendo mal a pergunta. O OP está perguntando sobre sobrecarregar o método no mesmo controlador, não substituindo-o em uma classe derivada.
Ace

@ Andiih: o que acontecerá se ambos os métodos estiverem no mesmo controlador?
Dharmik Bhandari


0

Há apenas uma assinatura pública permitida para cada método do controlador. Se você tentar sobrecarregá-lo, ele será compilado, mas você está recebendo o erro em tempo de execução que ocorreu.

Se você não estiver disposto a usar verbos diferentes (como os atributos [HttpGet]e [HttpPost]) para diferenciar métodos sobrecarregados (que funcionarão) ou alterar o roteamento, o que resta é que você pode fornecer outro método com um nome diferente ou pode despachar dentro do método existente. Aqui está como eu fiz isso:

Uma vez, entrei em uma situação em que tinha que manter a compatibilidade com versões anteriores. O método original esperava dois parâmetros, mas o novo tinha apenas um. Sobrecarregar da maneira que eu esperava não funcionou porque o MVC não encontrou mais o ponto de entrada.

Para resolver isso, fiz o seguinte:

  1. Foram alterados os 2 métodos de ação sobrecarregados de públicos para privados
  2. Criou um novo método público que continha "apenas" 2 parâmetros de string. Aquele atuou como despachante, ou seja:

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

Obviamente, isso é um hack e deve ser refatorado mais tarde. Mas, por enquanto, funcionou para mim.

Você também pode criar um expedidor como:

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

Você pode ver que UpdateAction precisa de 2 parâmetros, enquanto DeleteAction precisa apenas de um.


0

Desculpe o atraso. Eu estava com o mesmo problema e encontrei um link com boas respostas, poderia ajudar novos caras

Todos os créditos para o site BinaryIntellect e os autores

Basicamente, existem quatro situações: usando verbos diferentes , roteamento , marcação de sobrecarga com o atributo [NoAction] e altere o nome do atributo de ação com [ActionName]

Então, depende de seus requisitos e da sua situação.

No entanto, siga o link:

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


-1

Se for uma tentativa de usar uma ação GET para várias visualizações que POST a várias ações com modelos diferentes, tente adicionar uma ação GET para cada ação POST que redireciona para o primeiro GET para impedir 404 na atualização.

Plano geral, mas comum.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.