¿Se pueden sobrecargar los métodos de controlador en ASP.NET MVC?

327

Tengo curiosidad por ver si puede sobrecargar los métodos de controlador en ASP.NET MVC. Cada vez que lo intento, aparece el siguiente error. Los dos métodos aceptan argumentos diferentes. ¿Es esto algo que no se puede hacer?

La solicitud actual de acción 'MyMethod' en el tipo de controlador 'MyController' es ambigua entre los siguientes métodos de acción:

Papa Borgoña
fuente
10
@andy es lo mismo para mvc 4 también :)
basarat
10
Y lo mismo para mvc 5
DhruvJoshi
10
Y lo mismo para mvc 6
Imad
77
Y lo mismo para MVC Core 1.1
kall2sollies
77
Y lo mismo para MVC Core 2.0
Guilherme

Respuestas:

201

Puede usar el atributo si desea que su código realice una sobrecarga.

[ActionName("MyOverloadedName")]

Pero, tendrá que usar un nombre de acción diferente para el mismo método http (como han dicho otros). Entonces es solo semántica en ese punto. ¿Prefieres tener el nombre en tu código o tu atributo?

Phil tiene un artículo relacionado con esto: http://haacked.com/archive/2008/08/29/how-a-method-becomes-an-action.aspx

JD Conley
fuente
55
La principal desventaja de usar esto y sobrecargar su acción es que ya no puede ser procesado por el mismo archivo de vista.
Jeff Martin
66
En realidad, todavía puede representar el mismo archivo de vista. Solo necesita especificar el nombre de la vista en lugar de llamar ciegamente return View();. Por ejemplo: return View("MyOverloadedName");.
EAMann
1
@JD pero Microsoft dice ... Un método utilizado como acción de controlador no se puede sobrecargar ... Puede verlo aquí ... asp.net/mvc/tutorials/controllers-and-routing/…
himanshupareek66
@EAMann Nice, siempre definí el camino completo para la vista hasta ahora
Alexander Derck el
69

Si. He podido hacer esto estableciendo el HttpGet/ HttpPost(o AcceptVerbsatributo equivalente ) para cada método de controlador en algo distinto, es decir, HttpGeto HttpPost, pero no en ambos. De esa manera, puede determinar, según el tipo de solicitud, qué método utilizar.

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

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

Una sugerencia que tengo es que, para un caso como este, sería tener una implementación privada en la que confíen ambos métodos de Acción públicos para evitar la duplicación de código.

tvanfosson
fuente
1
Con MVC2 y versiones posteriores, también se puede usar el atributo HttpPost / HttpGet
yoel halb
@yohal Sí, esa sería la forma canónica de manejarlo ahora si no necesita admitir múltiples verbos.
tvanfosson
3
Solo tenga cuidado de no abusar de esto para violar los principios de REST.
Fred
1
Estoy bastante seguro de que esto solo funciona porque sus Show()métodos tienen firmas diferentes. Si necesita enviar información a la versión Obtener, sus versiones Obtener y Publicar terminan con la misma firma y necesitaría el ActionNameatributo o alguna de las otras correcciones mencionadas en esta publicación.
Scott Fraley
1
@ ScottK.Fraley es cierto. Si necesitaran la misma firma, tendría que nombrarlos de manera diferente y aplicar el ActionNameAttribute. En la práctica, rara vez he encontrado que ese sea el caso.
tvanfosson
42

Aquí hay algo más que podría hacer ... desea un método que pueda tener un parámetro y no.

¿Por qué no pruebas esto ...

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

Esto me ha funcionado ... y en este método, puedes probar para ver si tienes el parámetro entrante.


Actualizado para eliminar la sintaxis anulable no válida en la cadena y usar un valor de parámetro predeterminado.

Farrel
fuente
66
( stringNo puede ser anulable.)
Josh M.
23
la cadena puede ser anulable. De hecho, ya es anulable, simplemente no necesita el '?'
ProfK
99
@ProfK: no, la cadena es un tipo de referencia que puede ser nulo. No es "anulable". Nullable significa que está utilizando Nullable <T> (es decir, T?). El punto de Josh es que no puedes poner el? después de la cadena porque no es un tipo de valor, y Nullable <T> solo acepta tipos de valor.
Erik Funkenbusch
44
Al azar encontré el camino de regreso a esta pregunta y luego me di cuenta de que había publicado el comentario anterior. No recuerdo de esto ... raro! Todavía es cierto que a stringno puede ser nullable; pero puede ser null! De cualquier manera publiqué el comentario inicial sin sinceridad.
Josh M.
20

No, No y No. Vaya y pruebe el código del controlador a continuación donde tenemos el "LoadCustomer" sobrecargado.

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

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

Si intenta invocar la acción "LoadCustomer", obtendrá un error como se muestra en la figura siguiente.

ingrese la descripción de la imagen aquí

El polimorfismo es parte de la programación de C #, mientras que HTTP es un protocolo. HTTP no entiende el polimorfismo. HTTP funciona en el concepto o URL y la URL solo puede tener nombres únicos. Entonces HTTP no implementa el polimorfismo.

Para arreglar lo mismo necesitamos usar el 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");
        }
    }

Entonces, si realiza una llamada a la URL "Customer / LoadCustomer", se invocará la acción "LoadCustomer" y con la estructura de URL "Customer / LoadCustomerByName" se invocará la "LoadCustomer (string str)".

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

La respuesta anterior que tomé de este artículo del proyecto de código -> MVC Action overloading

Shivprasad Koirala
fuente
Gracias por esto. Supongo que también puede usar un nombre de acción diferente desde el principio en lugar de usar el atributo.
Dan
1
@Dan, pero no tenemos polimorfismo en el lado C #.
Shivprasad Koirala
Estás en lo correcto, no hay sobrecarga del método del controlador, pero no tiene nada que ver con HTTP.
Chalky
Gracias por la aclaración. +1. Debería estar pensando más en HTTP y no en C #. No hay razón para abordar acciones con una estrategia OO.
15

Para superar este problema, puede escribir un ActionMethodSelectorAttributeque examine elMethodInfo para cada acción y lo compare con los valores del formulario publicados y luego rechace cualquier método para el que los valores del formulario no coincidan (excluyendo el nombre del botón, por supuesto).

Aquí hay un ejemplo: - http://blog.abodit.com/2010/02/asp-net-mvc-ambiguous-match/

PERO, esta no es una buena idea.

Ian Mercer
fuente
@Cerbrus porque es un truco horrible y la siguiente persona que mira el código de tu controlador se confundirá con un enfoque muy poco estándar.
Ian Mercer
Je, bastante justo.
Cerbrus
14

Por lo que sé, solo puede tener el mismo método cuando usa diferentes métodos http.

es decir

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

}

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

}
Keeney
fuente
2
Las decoraciones no tienen nada que ver con la sobrecarga. Es la lista de parámetros que permite la sobrecarga.
Sky Sanders
@SkySanders No estoy de acuerdo, la sobrecarga basada en parámetros no funciona en los métodos del controlador MVC. ¿Tienes un ejemplo que funcione? Salud.
Chalky
Use el [HttpPost]atributo en lugar de [AcceptVerbs("POST")].
Fred
9

He logrado esto con la ayuda de Enrutamiento de atributos en MVC5. Es cierto que soy nuevo en MVC luego de una década de desarrollo web usando WebForms, pero lo siguiente me ha funcionado. A diferencia de la respuesta aceptada, esto permite que todas las acciones sobrecargadas sean representadas por el mismo archivo de vista.

Primero habilite el enrutamiento de atributos en 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 su clase de controlador con un prefijo de ruta predeterminado.

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

Luego, decore las acciones de su controlador que se sobrecargan entre sí con una ruta y parámetros comunes para adaptarse. Usando parámetros de tipo restringido, puede usar el mismo formato de URI con ID de diferentes tipos.

[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 esto ayude y no esté guiando a alguien por el camino equivocado. :-)

cookdn
fuente
¡Buen trabajo! Me encontré con este problema, ¡me salvaste! También tengo "x" años con WebForms, por lo que sigo siendo una curva de aprendizaje. No se puede conseguir un trabajo sin MVC hoy en día jaja
Tez Wingfield
4

Podrías usar un sencillo ActionResultpara lidiar con ambos Posty Get:

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

Útil si sus métodos Gety Posttienen firmas coincidentes.

DevDave
fuente
1
Hmm, amable, reinventando la rueda de nuevo, pero esta vez en forma de cuadrado. ¿Por qué no simplemente usar los atributos [HttpPost / Get]?
SOReader
ha pasado un tiempo, pero creo que hice esto porque MVC no distinguía entre dos métodos separados con signos coincidentes. Estaba usando el atributo HttpPost, aunque no estaba poniendo HttpGet en el otro método ...
DevDave
@DevDave, además de atribuir ambos métodos, asegúrese de utilizar los atributos de system.web.mvc, ¡y no los de system.web.http!
Chalky
4

Acabo de encontrarme con esta pregunta y, aunque ahora es bastante antigua, sigue siendo muy relevante. Irónicamente, el único comentario correcto en este hilo fue publicado por un principiante confeso en MVC cuando escribió la publicación. Incluso los documentos ASP.NET no son del todo correctos. Tengo un gran proyecto y sobrecargo con éxito los métodos de acción.

Si uno comprende el enrutamiento, más allá del simple patrón de ruta predeterminado {controlador} / {acción} / {id}, puede ser obvio que las acciones del controlador pueden asignarse utilizando cualquier patrón único. Alguien aquí habló sobre el polimorfismo y dijo: "HTTP no entiende el polimorfismo", pero el enrutamiento no tiene nada que ver con HTTP. Es, simplemente, un mecanismo para la coincidencia de patrones de cadena.

La mejor manera de hacer que esto funcione es usar los atributos de enrutamiento, por ejemplo:

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

Estas acciones se encargarán de URL como /cars/usa/new-yorky/cars/usa/texas/dallas , que se asignarán a la primera y segunda acciones del índice, respectivamente.

Al examinar este controlador de ejemplo, es evidente que va más allá del patrón de ruta predeterminado mencionado anteriormente. El valor predeterminado funciona bien si su estructura de URL coincide exactamente con las convenciones de denominación de código, pero este no es siempre el caso. El código debe ser descriptivo del dominio, pero las URL a menudo necesitan ir más allá porque su contenido debe basarse en otros criterios, como los requisitos de SEO.

El beneficio del patrón de enrutamiento predeterminado es que crea automáticamente rutas únicas. El compilador lo aplica, ya que las URL coincidirán con tipos y miembros de controlador únicos. Hacer rodar sus propios patrones de ruta requerirá una cuidadosa reflexión para garantizar la unicidad y que funcionen.

Nota importante El único inconveniente es que usar el enrutamiento para generar URL para acciones sobrecargadas no funciona cuando se basa en un nombre de acción, por ejemplo, cuando se usa UrlHelper.Action. Pero funciona si uno usa rutas con nombre, por ejemplo, UrlHelper.RouteUrl. Y usar rutas con nombre es, de acuerdo con fuentes muy respetadas, el camino a seguir de todos modos ( http://haacked.com/archive/2010/11/21/named-routes-to-the-rescue.aspx/ ).

¡Buena suerte!

DvS
fuente
3

Puede usar [ActionName ("NewActionName")] para usar el mismo método con un nombre 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");
    }
}
Alex Butenko
fuente
2

Necesitaba una sobrecarga para:

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

Hubo pocos argumentos suficientes donde terminé haciendo esto:

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

No es una solución perfecta, especialmente si tienes muchos argumentos, pero funciona bien para mí.

Kasey Speakman
fuente
1

También he enfrentado el mismo problema en mi aplicación. Sin modificar ninguna información del Método, he proporcionado [ActionName ("SomeMeaningfulName")] en el encabezado de Action. problema resuelto

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

        }
ಅನಿಲ್
fuente
0

Crea el método base como virtual

public virtual ActionResult Index()

Crear el método anulado como anulación

public override ActionResult Index()

Editar: Esto obviamente se aplica solo si el método de anulación se encuentra en una clase derivada que parece no haber sido la intención del OP.

Andiih
fuente
2
Probablemente estés malinterpretando la pregunta. El OP pregunta sobre sobrecargar el método en el mismo controlador, no sobreescribirlo en una clase derivada.
Ace
@Andiih: ¿qué sucederá si ambos métodos están en el mismo controlador?
Dharmik Bhandari
0

Solo se permite una firma pública para cada método de controlador. Si intenta sobrecargarlo, se compilará, pero obtendrá el error de tiempo de ejecución que ha experimentado.

Si no estás dispuesto a usar verbos diferentes (como el [HttpGet]y[HttpPost] atributos ) para diferenciar los métodos sobrecargados (que funcionarán) o cambiar la ruta, entonces lo que queda es que puede proporcionar otro método con un nombre diferente, o puede despacho dentro del método existente. Así es como lo hice:

Una vez llegué a una situación en la que tenía que mantener la compatibilidad con versiones anteriores. El método original esperaba dos parámetros, pero el nuevo tenía solo uno. Sobrecargar la forma que esperaba no funcionó porque MVC ya no encontró el punto de entrada.

Para resolver eso, hice lo siguiente:

  1. Cambió los 2 métodos de acción sobrecargados de público a privado
  2. Creó un nuevo método público que contenía "solo" 2 parámetros de cadena. Ese actuó como despachador, es decir:

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

Por supuesto, este es un truco y debería ser refactorizado más tarde. Pero por el momento, funcionó para mí.

También puede crear un despachador como:

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

Puede ver que UpdateAction necesita 2 parámetros, mientras que DeleteAction solo necesita uno.

Mate
fuente
0

Perdón por el retraso. Estaba con el mismo problema y encontré un enlace con buenas respuestas, ¿podría eso ayudar a los chicos nuevos?

Todos los créditos para el sitio web BinaryIntellect y los autores.

Básicamente, hay cuatro situaciones: usar verbos diferentes , usar enrutamiento , marcar sobrecarga con el atributo [NoAction] y cambiar el nombre del atributo de acción con [ActionName]

Entonces, depende de que sean sus requisitos y su situación.

Sin embargo, siga el enlace:

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

Eric Saboia
fuente
-1

Si se trata de un intento de usar una acción GET para varias vistas que PUBLICAN en varias acciones con diferentes modelos, intente agregar una acción GET para cada acción POST que redirija al primer GET para evitar que 404 se actualice.

Posibilidad muy remota pero escenario común.

Panos Roditakis
fuente