【问题标题】:How to get the action name when using the ActionName attribute?使用 ActionName 属性时如何获取动作名称?
【发布时间】:2018-08-20 19:34:57
【问题描述】:

通过在 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”,而正确的 URL 应该是“http://localhost:2222/Home/Contact2”,因为 [ActionName("Contact2")] 属性。

【问题讨论】:

  • 您可能需要调整您的问题。无论如何都不能使用nameof() 来执行此操作。你可能只想问“怎么做”,而不是“怎么用nameof()”
  • @maccettura 很好的建议,谢谢。我已经更新了问题。
  • 如果你理解how nameof works,那么你就会明白为什么它在这里不起作用。
  • 请问您的用例是什么?
  • ControllerContext.ActionDescriptor.ActionName 也许这有帮助?

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


【解决方案1】:

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

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 返回的方法。虽然这有点奇怪,因为这是假设用于控制器方法,通常返回一个值。

【讨论】:

  • 他的意思是使用属性不需要魔法字符串。将魔术字符串移动到常量,仍然使其成为魔术字符串,如果控制器发生更改,则代码在运行时中断。
  • @ErikPhilips 你不认为[ActionName("Contact2")] 已经有了魔法字符串吗?
  • ASP.NET MVC 基于属性的路由基于魔术字符串。我尽量避免用 x 的答案来回答 xy 的问题。
  • @ErikPhilips 实际上,不,它不会中断,因为常量是整个操作的真实来源。您在操作名称中使用常量。除非常数改变,否则它不会改变。这种中断的唯一方法是,如果另一个库针对该常量进行编译,然后该常量发生了变化,而另一个库没有针对新值重新编译。
  • @ShaunLuttin 虽然我同意有人可以更改它,但当我与其他开发人员一起走这条路时,我们不会更改这些东西成为惯例......同样我们不制作控制器不以Controller结尾的。
【解决方案2】:

...如果动作方法使用 [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>

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


您的问题专门询问了扩展方法。据我所知,扩展方法不会带来太大的改进,因为我们使用的是类型和方法,而扩展方法则适用于对象。

【讨论】:

  • 可能是typeof(HomeController).GetMethod(nameof(HomeController.Contact)).GetCustomAttribute&lt;ActionNameAttribute&gt;().Name(需要使用System.Reflection的指令)。
【解决方案3】:

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

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)))

【讨论】:

  • 语法是否需要为@{ ... }@( ... ) 才能使通用调用起作用?请参阅docs.microsoft.com/en-us/aspnet/core/mvc/views/… 了解我的意思。
  • 怎么说有人不在他们的类名末尾使用Controller,或者他们输入错误? "Controller" 仍然是一根魔线。
  • @ShaunLuttin 是的,它需要 (),谢谢,我面前没有 VS。
  • @cwharris 这显然是对硬编码“控制器”的反对,这在 OP 中是必需的,也是 hard-coded asp.net-mvc..看来你不熟悉......所以不,这不是任何人关心的问题。
  • @ErikPhilips 我熟悉该约定,但如果/当使用基于属性的路由时,它并不严格适用。您似乎不熟悉...所以有些人会担心。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-02
相关资源
最近更新 更多