强类型的url动作

Eri*_*ips 8 c# asp.net-mvc expression func

我读了很多类似的帖子和博客

ASP.NET MVC中基于代理的强类型URL生成

但他们中没有一个人真的做我想做的事.目前我有一个混合方法,如:

// 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;
  }
}
Run Code Online (Sandbox Code Playgroud)

如果你的控制器方法没有任何参数,那么效果很好:

public class MyController : Controller 
{
  public ActionResult MyMethod()
  {
    return null;
  }
  public ActionResult MyMethod2(int id)
  {
    return null;
  }
}
Run Code Online (Sandbox Code Playgroud)

然后我可以:

Url.Action<MyController>(c => c.MyMethod())
Run Code Online (Sandbox Code Playgroud)

但是如果我的方法接受一个参数,那么我必须传递一个值(我永远不会使用):

Url.Action<MyController>(c => c.MyMethod2(-1), new { id = 99 })
Run Code Online (Sandbox Code Playgroud)

所以问题是有一种方法可以改变扩展方法,仍然要求第一个参数是一个在类型T上定义的方法,确保返回参数是一个ActionResult没有实际指定参数的类型,如:

Url.Action<MyController>(c => c.MyMethod2, new { id = 99 })
Run Code Online (Sandbox Code Playgroud)

所以这会传递一个指向方法的指针(比如反射MethodInfo)而不是Func<>它,所以它不会关心参数.如果有可能,那签名会是什么样子?

Cod*_*ter 4

你不能这样做:

\n\n
c => c.MyMethod2\n
Run Code Online (Sandbox Code Playgroud)\n\n

因为那是一个方法组。方法组中的任何方法都可以返回 void 或其他任何内容,因此编译器不会允许这样做:

\n\n
\n
Error CS0428  Cannot convert method group \'...\' to non-delegate type \'...\'\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

组中可能有一个方法返回ActionMethod方法返回,或者不返回。你需要做出决定。

\n\n

但无论如何您不必提供方法组。您可以只使用现有的签名,减去object routeValues,然后像这样调用它:

\n\n
Url.Action<MyController>(c => c.MyMethod(99))\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后在你的方法中,你可以使用MethodInfo methodCallExpression.Method来获取方法参数名称,并且methodCallExpression.Arguments来获取参数。

\n\n

那么你的下一个问题是在运行时创建匿名对象。幸运的是你不必这样做,因为Url.Action()也有一个接受a的重载RouteValueDictionary.

\n\n

将参数和参数一起压缩到一个字典中,从中创建一个字典RouteValueDictionary,然后将其传递给Url.Action()

\n\n
var methodCallExpression = expression.Body as MethodCallExpression;\nif (methodCallExpression == null)\n{                \n    throw new ArgumentException("Not a MethodCallExpression", "expression");\n}\n\nvar methodParameters = methodCallExpression.Method.GetParameters();\nvar routeValueArguments = methodCallExpression.Arguments.Select(EvaluateExpression);\n\nvar rawRouteValueDictionary = methodParameters.Select(m => m.Name)\n                            .Zip(routeValueArguments, (parameter, argument) => new\n                            {\n                                parameter,\n                                argument\n                            })\n                            .ToDictionary(kvp => kvp.parameter, kvp => kvp.argument);\n\nvar routeValueDictionary = new RouteValueDictionary(rawRouteValueDictionary);\n\n// action and controller obtained through your logic \n\nreturn url.Action(action, controller, routeValueDictionary);\n
Run Code Online (Sandbox Code Playgroud)\n\n

EvaluateExpression方法非常天真地编译并调用每个非常量表达式,因此在实践中可能会非常慢:

\n\n
private static object EvaluateExpression(Expression expression)\n{\n    var constExpr = expression as ConstantExpression;\n    if (constExpr != null)\n    {\n        return constExpr.Value;\n    }\n\n    var lambda = Expression.Lambda(expression);\n    var compiled = lambda.Compile();\n    return compiled.DynamicInvoke();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

然而,在Microsoft ASP.NET MVC Futures 包中,有一个方便的ExpressionHelper.GetRouteValuesFromExpression(expr)\xe2\x80\x8c\xe2\x80\x8b,它也可以处理路由和区域。然后您的整个方法可以替换为:

\n\n
var routeValues = Microsoft.Web.Mvc.Internal.ExpressionHelper.GetRouteValuesFromExpression<T>(expression);\nreturn url.Action(routeValues["Action"], routeValues["Controller"], routeValues);\n
Run Code Online (Sandbox Code Playgroud)\n\n

它在内部使用缓存的表达式编译器,因此它适用于所有用例,并且您不必重新发明轮子。

\n