2009-06-25 31 views
117

Tengo dos métodos de acción que son contradictorios. Básicamente, quiero poder acceder a la misma vista usando dos rutas diferentes, ya sea por el ID de un elemento o por el nombre del elemento y el de su padre (los elementos pueden tener el mismo nombre en diferentes padres). Un término de búsqueda puede usarse para filtrar la lista.ASP.NET MVC métodos de acción ambiguos

Por ejemplo ...

Items/{action}/ParentName/ItemName 
Items/{action}/1234-4321-1234-4321 

Éstos son mis métodos de acción (también hay Remove métodos de acción) ...

// Method #1 
public ActionResult Assign(string parentName, string itemName) { 
    // Logic to retrieve item's ID here... 
    string itemId = ...; 
    return RedirectToAction("Assign", "Items", new { itemId }); 
} 

// Method #2 
public ActionResult Assign(string itemId, string searchTerm, int? page) { ... } 

Y aquí son las rutas ...

routes.MapRoute("AssignRemove", 
       "Items/{action}/{itemId}", 
       new { controller = "Items" } 
       ); 

routes.MapRoute("AssignRemovePretty", 
       "Items/{action}/{parentName}/{itemName}", 
       new { controller = "Items" } 
       ); 

Entiendo por qué ocurre el error, ya que el parámetro page puede ser nulo, pero no puedo encontrar la mejor manera de resolverlo. ¿Es mi diseño pobre para empezar? He pensado en extender la firma de Method #1 para incluir los parámetros de búsqueda y mover la lógica en Method #2 a un método privado que ambos llamarían, pero no creo que eso resuelva la ambigüedad.

Cualquier ayuda sería muy apreciada.


solución real (basado en la respuesta de Levi)

que añade la siguiente clase ...

public class RequireRouteValuesAttribute : ActionMethodSelectorAttribute { 
    public RequireRouteValuesAttribute(string[] valueNames) { 
     ValueNames = valueNames; 
    } 

    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) { 
     bool contains = false; 
     foreach (var value in ValueNames) { 
      contains = controllerContext.RequestContext.RouteData.Values.ContainsKey(value); 
      if (!contains) break; 
     } 
     return contains; 
    } 

    public string[] ValueNames { get; private set; } 
} 

y decorada los métodos de acción ...

[RequireRouteValues(new[] { "parentName", "itemName" })] 
public ActionResult Assign(string parentName, string itemName) { ... } 

[RequireRouteValues(new[] { "itemId" })] 
public ActionResult Assign(string itemId) { ... } 
+3

Gracias por publicar la implementación real. Seguro que ayuda a personas con problemas similares. Como lo tuve hoy. :-P –

+4

¡Asombroso! Sugerencia de cambio menor: (realmente útil) 1) params string [] valueNames para hacer que la declaración de atributo sea más concisa y (preferencia) 2) reemplace el cuerpo del método IsValidForRequest con 'return ValueNames.All (v => controllerContext.RequestContext.RouteData. Values.ContainsKey (v)); ' –

+0

Hola Jon, creo que no entiendo nada, porque ¿dónde están los parámetros de consulta en RouteData? – fravelgue

Respuesta

161

MVC no admite la sobrecarga de métodos basada únicamente en la firma, por lo que esto no funcionará:

public ActionResult MyMethod(int someInt) { /* ... */ } 
public ActionResult MyMethod(string someString) { /* ... */ } 

Sin embargo, hace método admiten la sobrecarga basado en el atributo:

[RequireRequestValue("someInt")] 
public ActionResult MyMethod(int someInt) { /* ... */ } 

[RequireRequestValue("someString")] 
public ActionResult MyMethod(string someString) { /* ... */ } 

public class RequireRequestValueAttribute : ActionMethodSelectorAttribute { 
    public RequireRequestValueAttribute(string valueName) { 
     ValueName = valueName; 
    } 
    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) { 
     return (controllerContext.HttpContext.Request[ValueName] != null); 
    } 
    public string ValueName { get; private set; } 
} 

En el ejemplo anterior, el atributo simplemente dice "este método coincide si la clave xxx estuvo presente en el solicitud." También puede filtrar por información contenida dentro de la ruta (controllerContext.RequestContext) si eso se adapta mejor a sus propósitos.

+0

Esto terminó siendo justo lo que necesitaba. Como sugirió, necesitaba usar controllerContext.RequestContext. –

+3

¡Agradable! Todavía no había visto el atributo RequireRequestValue. Es bueno saberlo. – CoderDennis

+1

podemos usar valueprovider para obtener valores de varias fuentes como: controllerContext.Controller.ValueProvider.GetValue (value); –

7

Los parámetros en sus rutas {roleId}, {applicationName} y {roleName} no coinciden con los nombres de los parámetros en sus métodos de acción. No sé si eso importa, pero hace que sea más difícil averiguar cuál es tu intención.

¿Su artículoId se ajusta a un patrón que se puede combinar a través de regex? Si es así, puede agregar una restricción a su ruta para que solo las URL que coincidan con el patrón se identifiquen como que contengan un itemId.

Si su itemId sólo contenía dígitos, entonces esto podría funcionar:

routes.MapRoute("AssignRemove", 
       "Items/{action}/{itemId}", 
       new { controller = "Items" }, 
       new { itemId = "\d+" } 
       ); 

Editar: También puede agregar una restricción a la ruta AssignRemovePretty por lo que se requieren tanto {parentName} y {itemName}.

Editar 2: Además, como su primera acción es solo redirigir a su segunda acción, puede eliminar algunas ambigüedades cambiando el nombre de la primera.

// Method #1 
public ActionResult AssignRemovePretty(string parentName, string itemName) { 
    // Logic to retrieve item's ID here... 
    string itemId = ...; 
    return RedirectToAction("Assign", itemId); 
} 

// Method #2 
public ActionResult Assign(string itemId, string searchTerm, int? page) { ... } 

A continuación, especifique los nombres de acción en sus rutas para forzar el método adecuado para ser llamados:

routes.MapRoute("AssignRemove", 
       "Items/Assign/{itemId}", 
       new { controller = "Items", action = "Assign" }, 
       new { itemId = "\d+" } 
       ); 

routes.MapRoute("AssignRemovePretty", 
       "Items/Assign/{parentName}/{itemName}", 
       new { controller = "Items", action = "AssignRemovePretty" }, 
       new { parentName = "\w+", itemName = "\w+" } 
       ); 
+1

Lo siento Dennis, los parámetros realmente coinciden. He arreglado la pregunta. Probaré la restricción de expresiones regulares y te responderé. ¡Gracias! –

+0

Tu segunda edición me ayudó, pero finalmente fue la sugerencia de Levi la que selló el trato. ¡Gracias de nuevo! –

0
routes.MapRoute("AssignRemove", 
       "Items/{parentName}/{itemName}", 
       new { controller = "Items", action = "Assign" } 
       ); 

considerar el uso de MVC contribuciones rutas de prueba biblioteca para probar sus rutas

"Items/parentName/itemName".Route().ShouldMapTo<Items>(x => x.Assign("parentName", itemName)); 
1

Recientemente me tomó la oportunidad de mejorar @ respuesta de Levi para apoyar una amplia gama de escenarios que tenía que tratar, tales como: soporte de parámetros múltiples, que coincida con cualquiera de ellos (en lugar de todos) e incluso no concuerdan con ninguno de ellos.

Aquí es el atributo que estoy usando ahora:

/// <summary> 
/// Flags an Action Method valid for any incoming request only if all, any or none of the given HTTP parameter(s) are set, 
/// enabling the use of multiple Action Methods with the same name (and different signatures) within the same MVC Controller. 
/// </summary> 
public class RequireParameterAttribute : ActionMethodSelectorAttribute 
{ 
    public RequireParameterAttribute(string parameterName) : this(new[] { parameterName }) 
    { 
    } 

    public RequireParameterAttribute(params string[] parameterNames) 
    { 
     IncludeGET = true; 
     IncludePOST = true; 
     IncludeCookies = false; 
     Mode = MatchMode.All; 
    } 

    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) 
    { 
     switch (Mode) 
     { 
      case MatchMode.All: 
      default: 
       return (
        (IncludeGET && ParameterNames.All(p => controllerContext.HttpContext.Request.QueryString.AllKeys.Contains(p))) 
        || (IncludePOST && ParameterNames.All(p => controllerContext.HttpContext.Request.Form.AllKeys.Contains(p))) 
        || (IncludeCookies && ParameterNames.All(p => controllerContext.HttpContext.Request.Cookies.AllKeys.Contains(p))) 
        ); 
      case MatchMode.Any: 
       return (
        (IncludeGET && ParameterNames.Any(p => controllerContext.HttpContext.Request.QueryString.AllKeys.Contains(p))) 
        || (IncludePOST && ParameterNames.Any(p => controllerContext.HttpContext.Request.Form.AllKeys.Contains(p))) 
        || (IncludeCookies && ParameterNames.Any(p => controllerContext.HttpContext.Request.Cookies.AllKeys.Contains(p))) 
        ); 
      case MatchMode.None: 
       return (
        (!IncludeGET || !ParameterNames.Any(p => controllerContext.HttpContext.Request.QueryString.AllKeys.Contains(p))) 
        && (!IncludePOST || !ParameterNames.Any(p => controllerContext.HttpContext.Request.Form.AllKeys.Contains(p))) 
        && (!IncludeCookies || !ParameterNames.Any(p => controllerContext.HttpContext.Request.Cookies.AllKeys.Contains(p))) 
        ); 
     } 
    } 

    public string[] ParameterNames { get; private set; } 

    /// <summary> 
    /// Set it to TRUE to include GET (QueryStirng) parameters, FALSE to exclude them: 
    /// default is TRUE. 
    /// </summary> 
    public bool IncludeGET { get; set; } 

    /// <summary> 
    /// Set it to TRUE to include POST (Form) parameters, FALSE to exclude them: 
    /// default is TRUE. 
    /// </summary> 
    public bool IncludePOST { get; set; } 

    /// <summary> 
    /// Set it to TRUE to include parameters from Cookies, FALSE to exclude them: 
    /// default is FALSE. 
    /// </summary> 
    public bool IncludeCookies { get; set; } 

    /// <summary> 
    /// Use MatchMode.All to invalidate the method unless all the given parameters are set (default). 
    /// Use MatchMode.Any to invalidate the method unless any of the given parameters is set. 
    /// Use MatchMode.None to invalidate the method unless none of the given parameters is set. 
    /// </summary> 
    public MatchMode Mode { get; set; } 

    public enum MatchMode : int 
    { 
     All, 
     Any, 
     None 
    } 
} 

Para más información y también se puede leer this post cómo-a muestras de implementación.

Cuestiones relacionadas