Strongly typed url action
I've read multiple posts and blogs similar to
Delegate-based strongly-typed URL generation in ASP.NET MVC
But none of them really quite do what I'd like to do. Currently I have a hybrid approach like:
// 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;
}
}
Works great if you're controller methods don't have any parameters:
public class MyController : Controller
{
public ActionResult MyMethod()
{
return null;
}
public ActionResult MyMethod2(int id)
{
return null;
}
}
Then I can:
Url.Action<MyController>(c => c.MyMethod())
But if my method takes a parameter, then I have to pass a value (that I would never use):
Url.Action<MyController>(c => c.MyMethod2(-1), new { id = 99 })
So the question is there a way to change the extension method to still require the first parameter to be a method defined on type T
that does check to make sure the return parameter is an ActionResult
without actually specifying a parameter, something like:
Url.Action<MyController>(c => c.MyMethod2, new { id = 99 })
So this would pass a pointer to the method (like a reflection MethodInfo
) instead of the Func<>
, so it wouldn't care about parameters. What would that signature look like if it was possible?
You can't do this:
c => c.MyMethod2
Because that is a method group. Any method in a method group can return void or anything else, so the compiler won't allow it:
Error CS0428 Cannot convert method group '...' to non-delegate type '...'
There may be a method in the group returning an ActionMethod
, or none. You need to decide that.
But you don't have to provide a method group anyway. You can just use your existing signature, minus the object routeValues
, and call it like this:
Url.Action<MyController>(c => c.MyMethod(99))
Then in your method, you can use the MethodInfo methodCallExpression.Method
to obtain the method parameter names, and the methodCallExpression.Arguments
to get the arguments.
Then your next problem is creating the anonymous object at runtime. Luckily you don't have to, as Url.Action()
also has an overload accepting a RouteValueDictionary
.
Zip the parameters and arguments together into a dictionary, create a RouteValueDictionary
from that, and pass that to 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);
The EvaluateExpression
method very naively compiles and invokes every non-constant expression, so may prove to be horribly slow in practice:
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();
}
However, in the Microsoft ASP.NET MVC Futures package there's the convenient ExpressionHelper.GetRouteValuesFromExpression(expr)
, which also handles routing and areas. Your entire method then can be replaced with:
var routeValues = Microsoft.Web.Mvc.Internal.ExpressionHelper.GetRouteValuesFromExpression<T>(expression);
return url.Action(routeValues["Action"], routeValues["Controller"], routeValues);
It uses a cached expression compiler internally, so it works for all use cases and you won't have to reinvent the wheel.
链接地址: http://www.djcxy.com/p/31276.html上一篇: 如何发送带有头部参数的HTTP请求?
下一篇: 强类型的url操作