通过表达式树构建URL时正确识别ActionNameAttribute

在MvcFutures项目中提供了一个辅助方法,可以将一个表达式树对象转化成一个RouteValueDictionary集合。只可惜,这个辅助方法的毛病比较多。例如,它直接把方法名作为action的值,而忽略了其上标记的ActionNameAttribute。这导致了某个被“改名”的Action方法一旦用在了表达式树中,最终得到的URL便是错误的。例如有一个Action方法:

public class HomeController : Controller
{
    [ActionName
    public ViewResult Index()
    {
        ...
    }
}

如果您使用这样的方式来生成URL(ActionEx方法的实现请参考《使用表达式树构建DomainRoute的URL》):

<a href="<%= Url.ActionEx<HomeController>(c => c.Index()) %>">Home</a>

则最终得到的代码是:

<a href="/Home/Index">Home</a>

而我们需要的结果应该是:

<a href="/Home/Default">Home</a>

正是因为这个原因(以及一些其他因素),许多朋友放弃使用强类型的方式构造URL。不过,如果您继续看下去,就会发现这个功能其实非常简单。只要做稍微一点点修改就可以了。不过现在,让我们来观察MvcFutures是如何实现这部分功能的。我已经把相关的代码复制到自己的RouteExpression类中:

public static class RouteExpression
{
    public static RouteValueDictionary GetRouteValues<TController>(
        Expression<Action<TController>> action)
        where TController : Controller
    {
        if (action == null)
        {
            throw new ArgumentNullException("action");
        }

        MethodCallExpression call = action.Body as MethodCallExpression;
        if (call == null)
        {
            throw new ArgumentException(
                "The action must be a method call.", "action");
        }

        string controllerName = typeof(TController).Name;
        if (!controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
        {
            throw new ArgumentException(
                "The controller name must end with 'Controller'.", "action");
        }

        controllerName = controllerName.Substring(
            0, controllerName.Length - "Controller".Length);
        if (controllerName.Length == 0)
        {
            throw new ArgumentException(
                "Cannot route to the Controller class", "action");
        }

        var rvd = new RouteValueDictionary();
        rvd.Add("controller", controllerName);
        rvd.Add("action", call.Method.Name);

        AddParameterValuesFromExpressionToDictionary(rvd, call);
        return rvd;
    }

    private static void AddParameterValues(
        RouteValueDictionary rvd, MethodCallExpression call)
    {
        ...
    }
}

这段代码大部分内容都是进行参数校验,一旦出现以下情况之一,便会抛出异常:

  • 表达式树为null。
  • 表达式树不是一个MethodCallExpression(应该是一个Action方法的调用)
  • 如果控制器类型的名称不以Controller结尾(破坏了约定)
  • 如果控制器类型的名称就是Controller

经过校验之后,这个方法根据控制器类型的名称计算出controller(HomeController => Home),再把所调方法的名称作为action(Index() => Index)。最后,再使用AddParameterValues方法获得参数,并填充RouteValueDictionary(关于这点我们下次再来讨论)。

不过,问题就出现在从Action方法的MethodInfo“直接获取”名称这个步骤上。这个MethodInfo可能还标记着ActionNameAttribute呢,它的Name属性可不是action的名称。为此,我们必须多做这么一步:

private static ReaderWriterLockSlim s_rwLock = new ReaderWriterLockSlim();
private static Dictionary<MethodInfo, string> s_actionNames = 
    new Dictionary<MethodInfo, string>();

private static string GetActionName(MethodInfo methodInfo)
{
    string actionName = null;

    s_rwLock.EnterReadLock();
    try
    {
        if (s_actionNames.TryGetValue(methodInfo, out actionName))
        {
            return actionName;
        }
    }
    finally
    {
        s_rwLock.ExitReadLock();
    }

    var attribute = (ActionNameAttribute)methodInfo
        .GetCustomAttributes(typeof(ActionNameAttribute), false)
        .SingleOrDefault();
    actionName = attribute == null ? methodInfo.Name : attribute.Name;

    s_rwLock.EnterWriteLock();
    try
    {
        s_actionNames[methodInfo] = actionName;
    }
    finally
    {
        s_rwLock.ExitWriteLock();
    }

    return actionName;
}

在GetActionName方法的中部则是获得action名称的代码。它会根据methodInfo上的ActionNameAttribute标记情况来确定。如果标记了ActionNameAttribute,则使用Attribute的Name属性作为action名称,否则就使用MethodInfo对象的Name属性。获得action名称之后,我们会将其保存在一个字典中。至于使用ReaderWriterLockSlim来控制并发读写的方式已经成为了标准,您甚至可以将其封装为一个组件避免重复编写相同的代码。

最后,我们把原来GetRouteValues方法中的一行代码加以替换即可:

public static RouteValueDictionary GetRouteValues<TController>(
    Expression<Action<TController>> action)
    where TController : Controller
{
    ...
    
  
  
   
   rvd.Add(
   
   "action", call.Method.Name);
  
  
    rvd.Add("action", GetActionName(call.Method));
    ...
}

ASP.NET MVC给了我们充分的自由度定制需要的组件。从中我们也可以了解到如何在项目中编写合适的API。其实很多东西只要多走一步就会美好很多,例如这个例子,需要花费您超过半小时的时间吗?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
是的,我们可以使用表达式来动态构建Lambda表达式表达式是一种数据结构,它可以表示Lambda表达式本身,而不是Lambda表达式的结果。我们可以通过构建表达式来描述Lambda表达式的结构和行为,然后将其编译为可执行的代码。 下面是一些示例代码,演示了如何使用表达式构建Lambda表达式: ```csharp // 创建一个参数表达式 ParameterExpression param = Expression.Parameter(typeof(int), "x"); // 创建一个常量表达式 ConstantExpression constExp = Expression.Constant(10, typeof(int)); // 创建一个加法表达式 BinaryExpression addExp = Expression.Add(param, constExp); // 创建一个Lambda表达式 Expression<Func<int, int>> lambdaExp = Expression.Lambda<Func<int, int>>(addExp, param); // 编译Lambda表达式 Func<int, int> func = lambdaExp.Compile(); // 调用Lambda表达式 int result = func(5); // 结果为 15 ``` 在上面的代码中,我们首先创建了一个参数表达式 `param`,表示Lambda表达式的参数。然后,我们创建了一个常量表达式 `constExp`,表示加法运算中的常量值。接着,我们使用这两个表达式创建了一个加法表达式 `addExp`。最后,我们使用 `Expression.Lambda` 方法构建了一个Lambda表达式,并将其编译为可执行的代码。 需要注意的是,使用表达式构建Lambda表达式,我们需要明确指定Lambda表达式的参数和返回值类型。在上面的示例中,我们使用了 `Expression.Lambda<Func<int, int>>` 来表示参数类型为 `int`,返回值类型也为 `int` 的Lambda表达式

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值