2015-12-17 16 views
7

che ho letto più post e blog simili afortemente tipizzato azione url

Delegate-based strongly-typed URL generation in ASP.NET MVC

ma nessuno di loro davvero fanno quello che mi piacerebbe fare. Attualmente ho un approccio ibrido come:

// shortened for Brevity 
public static Exts 
{ 
    public string Action(this UrlHelper url, 
    Expression<Func<T, ActionResult>> expression) 
    where T : ControllerBase 
    { 
    return Exts.Action(url, expression, null); 
    } 

    public string Action(this UrlHelper url, 
    Expression<Func<T, ActionResult>> expression, 
    object routeValues) 
    where T : ControllerBase 
    { 
    string controller; 
    string action; 

    // extension method 
    expression.GetControllerAndAction(out controller, out action); 

    var result = url.Action(action, controller, routeValues); 

    return result; 
    } 
} 

Funziona grande se siete metodi del controllore non hanno alcun parametro:

public class MyController : Controller 
{ 
    public ActionResult MyMethod() 
    { 
    return null; 
    } 
    public ActionResult MyMethod2(int id) 
    { 
    return null; 
    } 
} 

allora posso:

Url.Action<MyController>(c => c.MyMethod()) 

Ma se il mio metodo accetta un parametro, quindi devo passare un valore (che non utilizzerei mai):

Url.Action<MyController>(c => c.MyMethod2(-1), new { id = 99 }) 

Quindi la domanda c'è un modo per cambiare il metodo di estensione per richiedere ancora il primo parametro da un metodo definito dal tipo T che fa controllo per assicurarsi che il parametro di ritorno è un ActionResult senza in realtà specificare un parametro, qualcosa come:

Url.Action<MyController>(c => c.MyMethod2, new { id = 99 }) 

quindi questo passerebbe un puntatore al metodo (come una riflessione MethodInfo) invece del Func<>, quindi non sarebbe preoccuparsi parametri. Come sarebbe quella firma se fosse possibile?

+0

Con 'c.MyMethod2' stai indicando un metodo _group_, ognuno dei quali può restituire qualcos'altro ... Ma sono abbastanza sicuro di aver visto librerie che lo abilitano. Magari puoi fare qualche riflessione magica e controllare in 'GetControllerAndAction' che il metodo del gruppo che corrisponde ai parametri forniti restituisca effettivamente' ActionResult'. Questo non ti darà esattamente la sicurezza in fase di compilazione che stai cercando, ma non dovresti comunque avere metodi non di azione come metodi pubblici nel tuo controller. – CodeCaster

+0

'c => c.MyMethod2' non può essere lanciato da' Metodo gruppo' a non delegato tipo 'ActionResult'. –

+2

Ovviamente hai ragione, funzionerà solo nel controller corrente, non nella vista. Perché non stai usando 'c => c.MyMethod2 (99)' invece (usando 'MethodCallExpression.Arguments' per ottenere gli argomenti)? – CodeCaster

risposta

3

Non si può fare questo:

c => c.MyMethod2 

Perché questo è un gruppo di metodo. Qualsiasi metodo in un gruppo di metodo può restituire nulla o qualsiasi altra cosa, in modo che il compilatore non lo permetterà:

Error CS0428 Cannot convert method group '...' to non-delegate type '...' 

Ci può essere un metodo nel gruppo di restituzione di un ActionMethod, o nessuno. Devi decidere che.

Ma non è necessario fornire un gruppo di metodi. Si può semplicemente utilizzare la tua firma esistente, meno il object routeValues, e chiamare in questo modo:

Url.Action<MyController>(c => c.MyMethod(99)) 

Poi nel metodo, è possibile utilizzare il MethodInfo methodCallExpression.Method per ottenere i nomi dei parametri di metodo, e il methodCallExpression.Arguments per ottenere gli argomenti.

Quindi il tuo prossimo problema è la creazione dell'oggetto anonimo in fase di esecuzione. Fortunatamente non è necessario, poiché Url.Action() ha anche un sovraccarico che accetta un RouteValueDictionary.

Zip i parametri e gli argomenti insieme in un dizionario, creare un RouteValueDictionary da questo, e passare che a Url.Action():

var methodCallExpression = expression.Body as MethodCallExpression; 
if (methodCallExpression == null) 
{     
    throw new ArgumentException("Not a MethodCallExpression", "expression"); 
} 

var methodParameters = methodCallExpression.Method.GetParameters(); 
var routeValueArguments = methodCallExpression.Arguments.Select(EvaluateExpression); 

var rawRouteValueDictionary = methodParameters.Select(m => m.Name) 
          .Zip(routeValueArguments, (parameter, argument) => new 
          { 
           parameter, 
           argument 
          }) 
          .ToDictionary(kvp => kvp.parameter, kvp => kvp.argument); 

var routeValueDictionary = new RouteValueDictionary(rawRouteValueDictionary); 

// action and controller obtained through your logic 

return url.Action(action, controller, routeValueDictionary); 

Procedimento EvaluateExpression compila molto ingenuamente e invoca ogni espressione non costante, così può rivelarsi di essere terribilmente lento nella pratica:

private static object EvaluateExpression(Expression expression) 
{ 
    var constExpr = expression as ConstantExpression; 
    if (constExpr != null) 
    { 
     return constExpr.Value; 
    } 

    var lambda = Expression.Lambda(expression); 
    var compiled = lambda.Compile(); 
    return compiled.DynamicInvoke(); 
} 

Tuttavia, nel Microsoft ASP.NET MVC Futures package ther e è il conveniente ExpressionHelper.GetRouteValuesFromExpression(expr)‌​, che gestisce anche il routing e le aree. L'intero metodo di allora può essere sostituito con:

var routeValues = Microsoft.Web.Mvc.Internal.ExpressionHelper.GetRouteValuesFromExpression<T>(expression); 
return url.Action(routeValues["Action"], routeValues["Controller"], routeValues); 

Esso utilizza un compilatore espressione nella cache internamente, in modo che funziona per tutti i casi di utilizzo e non dovrà reinventare la ruota.

+0

Se non sono un 'espressione costante' non dovrei compilare ed eseguire quell'espressione per ottenere un valore per 'RouteValueDictionary'? –

+0

@Erik vedi modifica. – CodeCaster

+0

Non riesco a usare il pacchetto Futures, ma posso semplicemente compile() 'l'espressione e uso il valore come oggetto sperabilmente .. –

Problemi correlati