Pregunta

Tengo dos métodos de acción que están en conflicto.Básicamente, quiero ser capaz de conseguir a la misma vista utilizando dos rutas diferentes, ya sea por un IDENTIFICADOR del elemento o por el nombre del elemento y de sus padres (los elementos pueden tener el mismo nombre a través de diferentes padres).Un término de búsqueda puede ser utilizado para filtrar la lista.

Por ejemplo...

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

Aquí están mis métodos de acción (también hay Remove los 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í están las rutas...

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

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

Entiendo por qué se está produciendo el error, ya que el page parámetro puede ser nulo, pero no puedo averiguar la mejor manera de resolverlo.Es mi diseño pobres, para empezar?He pensado acerca de la extensión de Method #1's firma para incluir los parámetros de búsqueda y moviendo la lógica en Method #2 a un método privado que ambos se llame, pero no creo que realmente resolver la ambigüedad.

Cualquier ayuda sería muy apreciada.


Solución Real (basado en el Levi's respuesta)

He añadido 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 luego de la decoración de 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) { ... }
¿Fue útil?

Solución

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

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

Sin embargo, admite la sobrecarga de métodos según 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 estaba presente en la solicitud. " También puede filtrar por información contenida en la ruta (controllerContext.RequestContext) si eso se adapta mejor a sus propósitos.

Otros consejos

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

Hacer su itemId del ajustan a un patrón que podría ser igualada a través de la expresión regular?Si es así, entonces usted puede agregar una restricción a la ruta de modo que sólo la dirección url que coincida con el patrón que se identifican como que contiene un itemId.

Si su itemId sólo dígitos, esto debería funcionar:

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

Editar:También puede agregar una restricción a la AssignRemovePretty la ruta, de manera que ambos {parentName} y {itemName} son necesarios.

Edit 2:También, ya que su primera acción es sólo para redirigir su 2ª acción, se puede quitar cierta ambigüedad por el cambio de 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 a la fuerza con el método apropiado para ser llamado:

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

Otro enfoque es cambiar el nombre de uno de los métodos para que no haya conflicto. Por ejemplo

// GET: /Movies/Delete/5
public ActionResult Delete(int id = 0)

// POST: /Movies/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id = 0)

Ver http://www.asp.net/mvc/tutorials/ comenzando con mvc3-part9-cs

Recientemente aproveché la oportunidad de mejorar la respuesta de @ Levi para admitir una gama más amplia de escenarios con los que tuve que lidiar, tales como: soporte de múltiples parámetros, igualar cualquiera de ellos (en lugar de todos) e incluso igualar ninguno de ellos.

Aquí está 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 obtener más información y ejemplos de implementación de procedimientos, consulte esta publicación de blog que escribí sobre este tema.

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

considere usar la biblioteca de rutas de prueba de MVC Contribs para probar sus rutas

"Items/parentName/itemName".Route().ShouldMapTo<Items>(x => x.Assign("parentName", itemName));
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top