2015-12-17 29 views
7

ich gelesen habe mehrere Beiträge und Blogs ähnlich wiestark typisierte url Aktion

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

Aber keiner von ihnen wirklich ganz tun, was ich tun möchte. Zur Zeit habe ich einen hybriden Ansatz wie:

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

Funktioniert prima, wenn Sie Controller-Methoden sind keine Parameter haben:

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

Dann kann ich:

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

Aber wenn Meine Methode nimmt einen Parameter, dann muss ich einen Wert übergeben (den würde ich nie verwenden):

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

die Frage ist es eine Möglichkeit, die Erweiterungsmethode zu ändern, nach wie vor den ersten Parameter erfordern ein Verfahren definiert nach Typ werden T dass tut Prüfung sicherstellen, dass die Rückgabeparameter ist ein ActionResult ohne tatsächlich einen Parameter angeben, etwas wie:

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

so würde dies einen Zeiger auf das Verfahren (wie ein Spiegelbild MethodInfo) anstelle der Func<> passieren, so wäre es um Parameter nicht. Wie würde diese Unterschrift aussehen, wenn es möglich wäre?

+0

Mit 'c.MyMethod2' verweisen Sie auf eine Methode _group_, von denen jede etwas anderes zurückgeben kann ... Aber ich bin ziemlich sicher, dass ich Bibliotheken gesehen habe, die dies ermöglichen. Vielleicht können Sie etwas Reflektionszauber machen und 'GetControllerAndAction' einchecken, dass die Methode der Gruppe, die den angegebenen Parametern entspricht, tatsächlich 'ActionResult' zurückgibt. Dies gibt Ihnen zwar nicht die gewünschte Sicherheit bei der Kompilierungszeit, aber Sie sollten keine Non-Action-Methoden als öffentliche Methoden in Ihrem Controller haben. – CodeCaster

+0

'c => c.MyMethod2' kann nicht von' Method Group' in den Nicht-Delegate-Typ 'ActionResult' umgewandelt werden. –

+2

Sie haben natürlich Recht, das funktioniert nur im aktuellen Controller, nicht in der Ansicht. Warum verwenden Sie nicht stattdessen 'c => c.MyMethod2 (99)' (mit 'MethodCallExpression.Arguments', um die Argumente zu erhalten)? – CodeCaster

Antwort

3

Sie können dies nicht tun:

c => c.MyMethod2 

Weil das eine Methodengruppe ist. Jedes Verfahren in einem Verfahren Gruppe kann leer kommen oder irgendetwas anderes, so dass der Compiler wird es nicht zulassen, dass:

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

Es sein kann, ein Verfahren in der Gruppe der Rückkehr ein ActionMethod oder keine. Sie müssen das entscheiden.

Sie müssen jedoch keine Methodengruppe angeben. Sie können einfach Ihre vorhandene Signatur verwenden, abzüglich der object routeValues, und es so nennen:

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

Dann in Ihrer Methode, können Sie die MethodInfo methodCallExpression.Method verwenden, um die Methode Parameternamen zu erhalten, und die methodCallExpression.Arguments, um die Argumente zu bekommen.

Dann ist das nächste Problem das Erstellen des anonymen Objekts zur Laufzeit. Zum Glück müssen Sie nicht, wie Url.Action() hat auch eine Überlastung akzeptiert RouteValueDictionary.

Zip die Parameter und Argumente zusammen in einem Wörterbuch, ein RouteValueDictionary aus, dass erstellen, und übergeben Sie das zu 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); 

Die EvaluateExpression Methode sehr naiv kompiliert und jede nicht-konstanten Ausdruck aufruft, so kann beweisen sein schrecklich langsam in der Praxis:

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

jedoch in der Microsoft ASP.NET MVC Futures package ther e ist die bequeme ExpressionHelper.GetRouteValuesFromExpression(expr)‌​, die auch Routing und Bereiche behandelt. Ihr gesamtes Verfahren kann dann ersetzt werden durch:

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

Es verwendet einen Cache gespeicherten Ausdruck Compiler intern, so dass es für alle Anwendungsfälle funktioniert und Sie werden nicht das Rad neu erfinden müssen.

+0

Wenn sie keine 'ConstantExpression' wären, müsste ich diesen Ausdruck nicht kompilieren und ausführen, um einen Wert für den Wert zu erhalten 'RouteValueDictionary'? –

+0

@Erik siehe bearbeiten. – CodeCaster

+0

Ich kann das Futures-Paket nicht verwenden, aber ich kann nur den Ausdruck kompilieren() und den Wert als Objekt verwenden. –

Verwandte Themen