2009-06-25 15 views
117

Ho due metodi di azione che sono in conflitto. Fondamentalmente, voglio essere in grado di ottenere la stessa vista utilizzando due percorsi diversi, sia per l'ID di un articolo o per il nome dell'oggetto e dei suoi genitori (gli articoli possono avere lo stesso nome su genitori diversi). Un termine di ricerca può essere utilizzato per filtrare l'elenco.Metodi di azione ambigua di ASP.NET MVC

Per esempio ...

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

Qui sono i miei metodi di azione (ci sono anche Remove metodi d'azione) ...

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

E qui sono gli itinerari ...

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

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

Capisco perché si sta verificando l'errore, dal momento che il parametro page può essere nullo, ma non riesco a capire il modo migliore per risolverlo. Il mio design è scarso per cominciare? Ho pensato di estendere la firma di Method #1 per includere i parametri di ricerca e spostare la logica in Method #2 in un metodo privato che entrambi chiamerebbero, ma non credo che in realtà risolverà l'ambiguità.

Qualsiasi aiuto sarebbe molto apprezzato.


Actual Solution (in base alla risposta di Levi)

ho aggiunto la seguente classe ...

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

E poi decorato i metodi di azione ...

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

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

Grazie per aver pubblicato l'implementazione effettiva. Di certo aiuta le persone con problemi simili. Come ho avuto oggi. :-P –

+4

Incredibile! Suggerimento per il cambiamento minore: (molto utile) 1) params string [] valoreNomi per rendere più concisa la dichiarazione dell'attributo e (preferenza) 2) sostituire il corpo del metodo IsValidForRequest con 'return ValueNames.All (v => controllerContext.RequestContext.RouteData. Values.ContainsKey (v)); ' –

+0

Ciao Jon, penso di non sottovalutare qualcosa, perché dove sono i parametri di query in RouteData? – fravelgue

risposta

161

MVC non supporta l'overloading dei metodi basato esclusivamente sulla firma, pertanto non riuscirà:

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

Tuttavia, fa metodo supporto sovraccarico sulla base di attributi:

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

Nell'esempio precedente, l'attributo dice semplicemente "questo metodo corrisponde se la chiave xxx era presente nel richiesta." Puoi anche filtrare in base alle informazioni contenute all'interno del percorso (controllerContext.RequestContext) se ciò è più adatto ai tuoi scopi.

+0

Questo finì per essere proprio quello di cui avevo bisogno. Come hai suggerito, avevo bisogno di usare controllerContext.RequestContext. –

+3

Bello! Non avevo ancora visto l'attributo RequireRequestValue. È una buona cosa sapere. – CoderDennis

+1

possiamo usare valueprovider per ottenere valori da diverse fonti come: controllerContext.Controller.ValueProvider.GetValue (value); –

7

I parametri nei percorsi {roleId}, {applicationName} e {roleName} non corrispondono ai nomi dei parametri nei metodi di azione. Non so se questo è importante, ma rende più difficile capire quale sia la tua intenzione.

I tuoi itemId sono conformi a un modello che potrebbe essere abbinato tramite regex? In tal caso, è possibile aggiungere un vincolo al percorso in modo che solo gli URL che corrispondono al modello siano identificati come contenenti un itemId.

Se l'itemId conteneva solo cifre, allora questo dovrebbe funzionare:

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

Edit: Si potrebbe anche aggiungere un vincolo per il percorso AssignRemovePretty in modo tale che sono necessarie sia {parentName} e {itemName}.

Modifica 2: Inoltre, poiché la prima azione è semplicemente il reindirizzamento alla seconda azione, è possibile rimuovere alcune ambiguità rinominando la prima.

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

Poi specificare i nomi di azione nei vostri percorsi per forzare il metodo corretto da chiamare:

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

Scusa Dennis, i parametri corrispondono effettivamente. Ho risolto la domanda. Proverò la regex moderazione e tornare a voi. Grazie! –

+0

La tua seconda modifica mi ha aiutato, ma alla fine è stato il suggerimento di Levi a suggellare l'accordo. Grazie ancora! –

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

considerano usando MVC contributi percorsi di prova biblioteca per testare i percorsi

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

Recentemente ho preso la possibilità di migliorare @ risposta di Levi per supportare una più ampia gamma di scenari che ho dovuto affrontare, come ad esempio: sostegno parametri multipli, adattarsi a qualsiasi di essi (invece di tutti tutti) e persino non corrispondono a nessuno di loro.

Ecco l'attributo che sto usando ora:

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

Per ulteriori informazioni e how-to i campioni di implementazione si può anche leggere this post.

Problemi correlati