使用ActionName属性时如何获取操作名称?

时间:2018-08-20 19:34:58

标签: c# asp.net-core controller asp.net-core-mvc

通过在C#6中引入nameof运算符,您可以以编程方式获得动作名称,而无需使用魔术字符串:

<p>@Html.ActionLink("Contact", nameof(HomeController.Contact), "Home")</p>

如果您不更改视图名称,这将非常有用。

但是,如果操作方法使用[ActionName]属性,是否有一种方法可以获取正确的操作名称(并避免使用魔术字符串)?也许通过nameof()和扩展方法的组合?

[ActionName("Contact2")]
public ActionResult Contact()
{    
    // ...
}

在此示例中,nameof(HomeController.Contact)将返回字符串“ Contact”和URL“ http://localhost:2222/Home/Contact”,而由于{{1},正确的URL应该为“ http://localhost:2222/Home/Contact2” }属性。

3 个答案:

答案 0 :(得分:3)

不。你不能因为属性名称已经是一个魔术字符串,并且就所有意图和目的而言,控制器方法的名称本身就是一个魔术字符串(在您为操作使用隐式名称的情况下)。魔术弦还不错,只是经常被滥用。在这种情况下,最好只使用一个常量。

internal const string ContactActionName2 = nameof(ContactActionName2);

[ActionName(ContactActionName2)]

HomeController.ContactActionName2

对于您的用例应该足够了。

但是,由于每个人对此都感到很恶心,所以我决定去寻找一种完全不依赖字符串的解决方案(除了您不能避免依赖的字符串-动作名称)。我不喜欢这种解决方案,因为1)这太过分了; 2)它仍然只是访问字符串值,使用常量可以更简单地完成此操作; 3)实际上您必须写出整个方法调用作为表达式,并且4)每次使用时都会分配一个表达式。

public static class ActionNameExtensions<TController>
{
    public static string FindActionName<T>(Expression<Func<TController, T>> expression)
    {
        MethodCallExpression outermostExpression = expression.Body as MethodCallExpression;

        if (outermostExpression == null)
        {
            throw new ArgumentException("Not a " + nameof(MethodCallExpression));
        }

        return outermostExpression.Method.GetCustomAttribute<ActionNameAttribute>().Name;
    }
}

用法示例:

public class HomeController : Controller
{
    [ActionName("HelloWorld")]
    public string MyCoolAction(string arg1, string arg2, int arg4)
    {
        return ActionNameExtensions<HomeController>.FindActionName(
            controller => controller.MyCoolAction("a", "b", 3)
        );
    }
}

可以编写重载以接受没有返回void的方法。尽管这很奇怪,但是因为这被假定用于通常返回值的控制器方法。

答案 1 :(得分:1)

  

...如果操作方法使用[ActionName]属性,是否可以获取正确的操作名称(避免使用魔术字符串)?也许通过nameof()和扩展方法的组合?

您可以使用反射。这是一个内联版本:

<p>@(
    (
        (ActionNameAttribute)(
            typeof(HomeController)
            .GetMethod(nameof(HomeController.Contact))
            .GetCustomAttributes(typeof(ActionNameAttribute), false)[0]
        )
    ).Name
)</p>

这里的操作与razor function相同:

@functions {
    public string GetActionName(Type controller, string methodName) {
        var method = controller.GetMethod(methodName);
        var attributeType = typeof(ActionNameAttribute);
        var attribute = method.GetCustomAttributes(attributeType, false)[0];
        return (attribute as ActionNameAttribute).Name;
    }
}

<p>@GetActionName(typeof(HomeController), nameof(HomeController.Contact))</p>

以下是与普通剃刀功能相同的操作:

@functions {
    public string GetActionName<T>(string methodName) {
        var controllerType = typeof(T);
        var method = controllerType.GetMethod(methodName);
        var attributeType = typeof(ActionNameAttribute);
        var attribute = method.GetCustomAttributes(attributeType, false)[0];
        return (attribute as ActionNameAttribute).Name;
    }
}

<p>@(GetActionName<HomeController>(nameof(HomeController.Contact)))</p>

剩下的就是向null函数添加防御性编程(例如GetActionName检查)。


您的问题专门询问了扩展方法。据我所知,扩展方法不会提供太多改进,因为我们正在处理类型和方法,而扩展方法则用于对象。

答案 2 :(得分:1)

如果您不喜欢,可以使用泛型做一些漂亮的逻辑:

public static class HtmlHelperExtensions
{
    public static IHtmlContent ActionLink<TController>(
        this IHtmlHelper htmlHelper, 
        string linkText, 
        string actionName)

      where TController : ControllerBase
    {
        var suffix = nameof(Controller);
        var controllerName = typeof(TController).Name.Replace(suffix, "");
        var method = typeof(TController).GetMethod(actionName);
        var attributeType = typeof(ActionNameAttribute);
        var attribute = method.GetCustomAttributes(attributeType, false);
        actionName = attribute.Length == 0
          ? actionName
          : (attribute[0] as ActionNameAttribute).Name;

        return htmlHelper.ActionLink(linkText, actionName);
    }
}

对此进行了测试,它可能会(肯定会)抱怨签名已经存在,因此您可能必须重命名该方法。无论如何,您都可以这样使用它:

@(Html.ActionLink<HomeController>("Link Text", nameof(HomeController.Index)))