С введением оператора nameof в C # 6 вы можете программно получить имя действия, не используя волшебную строку:

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

Это прекрасно работает, если вы не измените название представления.

Однако есть ли способ получить правильное имя действия (и избежать магических строк), если метод действия использует атрибут [ActionName]? Возможно, через комбинацию nameof() и метода расширения?

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

В этом примере nameof(HomeController.Contact) вернет строку «Контакт» и URL «http: // localhost : 2222 / Home / Contact ", тогда как правильный URL должен быть" http: // localhost: 2222 / Home / Contact2 "из-за атрибута [ActionName("Contact2")].

2
Wellspring 20 Авг 2018 в 22:34

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 возвратов. Хотя это немного странно, поскольку предполагается, что он используется для методов контроллера, которые обычно возвращают значение.

3
cwharris 20 Авг 2018 в 22:55

Если вам не нравится, вы можете сделать довольно причудливую логику с Generics:

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)))
1
Erik Philips 20 Авг 2018 в 21:49

... есть ли способ получить правильное имя действия (и избежать магических строк), если метод действия использует атрибут [ActionName]? Возможно, через сочетание nameof () и метода расширения?

Вы можете использовать отражение. Вот встроенная версия:

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

Вот та же операция, что и для функция бритвы:

@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.


Ваш вопрос задан конкретно по поводу метода расширения. Насколько я могу судить, метод расширения не принес бы большого улучшения, потому что мы работаем с типами и методами, тогда как методы расширения работают с объектами.

1
Shaun Luttin 20 Авг 2018 в 21:04
51937430